* xargs/xargs.c (parse_num): delete and replace with parse_signed_num
and parse_unsigned_num. The replacement functions diagnose
out-of-range values.
(show_option_arg_bounds): show uppper and lower bounds for
arguments (of e.g. -L, -l, -n).
(main): check bounds of the -L and -l options. It is an error for the
values to be out of range. Check that the argument of -s is
non-negative. However, values which are too large no longer generate
an error message as POSIX requires the value to be silently reduced to
the maximum supported value. Handle large arguments to -P similarly.
Update --show-limits to show the lower and upper limits for the -L,
-l, -n and -s options.
* lib/buildcmd.h: define macros BC_LINES_PER_EXEC_MAX and
BC_ARGS_PER_EXEC_MAX; these are the limits for the -L and -n options.
* tests/xargs/test-option-range.c: new file; tests for the limits of
the xargs options -L, -l, -n and -s.
* tests/local.mk (check_PROGRAMS): add tests/xargs/test-option-range.
(LDADD): link against libfind.a.
(binary_tests): add add tests/xargs/test-option-range.
* xargs/xargs.1 (STANDARDS CONFORMANCE): Describe the values accepted
for the arguments of the -L, -n and -s options.
* doc/find.texi (Limiting Command Size): Likewise.
* cfg.mk (exclude_file_name_regexp--sc_bindtextdomain): the new test
program is not required to call bindtextdomain().
(exclude_file_name_regexp--sc_unmarked_diagnostics): exclude all C
programs under tests/.
---
cfg.mk | 6 +-
doc/find.texi | 9 +
lib/buildcmd.h | 4 +
tests/local.mk | 5 +-
tests/xargs/test-option-range.c | 288 +++++++++++++++++++++
xargs/xargs.1 | 45 +++-
xargs/xargs.c | 446 ++++++++++++++++++++++++++------
7 files changed, 711 insertions(+), 92 deletions(-)
create mode 100644 tests/xargs/test-option-range.c
diff --git a/cfg.mk b/cfg.mk
index 0789056b..dd9fd6c5 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -72,7 +72,7 @@ exclude_file_name_regexp--sc_trailing_blank = \
exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \
^(.*/testsuite/.*\.(xo|xi|xe))|COPYING|doc/regexprops\.texi|m4/order-(bad|good)\.bin$$
exclude_file_name_regexp--sc_bindtextdomain = \
- ^lib/(regexprops|test_splitstring)\.c$$
+ ^lib/(regexprops|test_splitstring|test-add-one)
exclude_file_name_regexp--sc_prohibit_always_true_header_tests = \
^(build-aux/src-sniff\.py)|ChangeLog$$
exclude_file_name_regexp--sc_prohibit_test_minus_ao = \
@@ -90,11 +90,11 @@ exclude_file_name_regexp--sc_texinfo_acronym =
doc/perm\.texi
# List syntax-check exemptions.
exclude_file_name_regexp--sc_bindtextdomain = \
-
^(locate/frcode|lib/regexprops|lib/test_splitstring|find/getlimits|tests/xargs/test-sigusr)\.c$$
+
^(locate/frcode|lib/regexprops|lib/test-add-one|lib/test_splitstring|find/getlimits|tests/.*/test.*)\.c$$
# sc_unmarked_diagnostics: exempt internal programs.
exclude_file_name_regexp--sc_unmarked_diagnostics = \
- ^(tests/xargs/test-sigusr)\.c$$
+ ^(tests/[a-z_0-9/-]*)\.c$$
# Things that 'codespell' mistakenly flags as typos.
codespell_ignore_words_list = afile,bu,debbugs,filll,fo,hel,ois,siz,ublic,TE,
diff --git a/doc/find.texi b/doc/find.texi
index 3d039b34..ad4591e2 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -2809,6 +2809,11 @@ input line to be logically continued on the next input
line, for the
purpose of counting the lines. Implies @samp{-x}. The preferred name
for this option is @samp{-L} as this is specified by POSIX.
+The valid range for the @var{max-lines} argument exceeds the range
+required by POSIX (1--2147483647). To see the actual range of
+supported values, use @samp{--show-limits}.
+
+
@item --max-args=@var{max-args}
@itemx -n @var{max-args}
Use at most @var{max-args} arguments per command line. Fewer than
@@ -2816,6 +2821,10 @@ Use at most @var{max-args} arguments per command line.
Fewer than
option) is exceeded, unless the @samp{-x} option is given, in which
case @code{xargs} will exit.
+The valid range for the @var{max-args} argument exceeds the range
+required by POSIX (1--2147483647). To see the actual range of
+supported values, use @samp{--show-limits}.
+
@item --max-chars=@var{max-chars}
@itemx -s @var{max-chars}
Use at most @var{max-chars} characters per command line, including the
diff --git a/lib/buildcmd.h b/lib/buildcmd.h
index 55eb5779..5af85961 100644
--- a/lib/buildcmd.h
+++ b/lib/buildcmd.h
@@ -21,6 +21,7 @@
# define INC_BUILDCMD_H 1
# include <stdbool.h>
+# include <limits.h>
struct buildcmd_state
{
@@ -104,6 +105,9 @@ struct buildcmd_control
size_t args_per_exec;
};
+# define BC_LINES_PER_EXEC_MAX ULONG_MAX
+# define BC_ARGS_PER_EXEC_MAX SIZE_MAX
+
enum BC_INIT_STATUS
{
BC_INIT_OK = 0,
diff --git a/tests/local.mk b/tests/local.mk
index 98e5b15d..d4a1330e 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -17,8 +17,8 @@
built_programs = find xargs frcode locate updatedb
-AM_CPPFLAGS = -I$(top_srcdir)/gl/lib
-LDADD = $(top_builddir)/gl/lib/libgnulib.a
+AM_CPPFLAGS = -I$(top_srcdir)/gl/lib -I$(top_srcdir)/lib
+LDADD = $(top_builddir)/lib/libfind.a $(top_builddir)/gl/lib/libgnulib.a
# Indirections required so that we'll still be able to know the
# complete list of our tests even if the user overrides TESTS
@@ -91,6 +91,7 @@ all_root_tests = \
check_PROGRAMS = $(binary_tests)
binary_tests = \
+ tests/xargs/test-option-range \
tests/xargs/test-sigusr
ALL_RECURSIVE_TARGETS += check-root
diff --git a/tests/xargs/test-option-range.c b/tests/xargs/test-option-range.c
new file mode 100644
index 00000000..f869daa9
--- /dev/null
+++ b/tests/xargs/test-option-range.c
@@ -0,0 +1,288 @@
+/* test-xargs-option-range -- Verify correct handling of -L, -l, -n range
+ Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+ Written by James Youngman <[email protected]>
+*/
+
+/* config.h must be included first. */
+#include <config.h>
+
+/* System headers */
+#include <stdarg.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h> /* PRIuMAX */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h> /* waitpid() */
+#include <unistd.h> /* sleep() */
+#include <stdint.h> /* uintmax_t */
+
+/* gnulib headers */
+#include <error.h>
+#include "intprops.h"
+#include "inttostr.h"
+#include "xalloc.h"
+
+/* findutils headers */
+#include "add-one.h"
+#include "buildcmd.h"
+
+
+static void
+cleanup_argv (char **argv)
+{
+ for (size_t i = 0; argv[i]; ++i)
+ {
+ free (argv[i]);
+ }
+ free (argv);
+}
+
+
+static void
+print_argv (char **argv)
+{
+ for (int i = 0; argv[i]; ++i)
+ {
+ printf ("argv[%d] = %s\n", i, argv[i]);
+ }
+}
+
+static int
+run_xargs_get_retval (int devnull_fd, ...)
+{
+ va_list ap;
+ va_start (ap, devnull_fd);
+ char **argv = xmalloc (sizeof (const char *));
+ argv[0] = xstrdup ("xargs");
+ int arg_count = 1;
+ for (;;)
+ {
+ const char *s = va_arg (ap, const char *);
+ argv = xrealloc (argv, (1 + arg_count) * sizeof (const char *));
+ if (s)
+ {
+ argv[arg_count] = xstrdup (s);
+ }
+ else
+ {
+ argv[arg_count] = NULL;
+ break;
+ }
+ ++arg_count;
+ }
+ va_end (ap);
+
+ pid_t child;
+ int childstatus;
+ printf ("Full xargs arguments:\n");
+ print_argv (argv);
+
+ fflush (stderr);
+ fflush (stdout);
+
+ while ((child = fork ()) < 0 && errno == EAGAIN)
+ {
+ sleep (1);
+ }
+ if (child == -1)
+ {
+ error (EXIT_FAILURE, errno, "cannot fork");
+ /*NOTREACHED*/ return -42;
+ }
+ else if (child == 0)
+ {
+ /* We are the child. */
+ /* redirect stdin from /dev/null */
+ close (0);
+ int newfd = dup (devnull_fd);
+ if (-1 == newfd)
+ {
+ int saved_errno = errno;
+ close (devnull_fd);
+ error (EXIT_FAILURE, saved_errno, "failed to dup2 /dev/null");
+ }
+ if (close (devnull_fd) < 0)
+ {
+ error (EXIT_FAILURE, errno, "failed to close /dev/null");
+ }
+ execvp ("xargs", argv);
+ error (EXIT_FAILURE, errno, "failed to run xargs");
+ /*NOTREACHED*/ return -43;
+ }
+
+ /* In the parent. */
+ cleanup_argv (argv);
+ waitpid (child, &childstatus, 0);
+ if (WIFSIGNALED (childstatus))
+ {
+ error (EXIT_FAILURE, 0, "xargs exited due to a fatal signal");
+ }
+ else if (!WIFEXITED (childstatus))
+ {
+ error (EXIT_FAILURE, 0, "xargs did not exit normally");
+ }
+ return WEXITSTATUS (childstatus);
+}
+
+static void
+run_xargs_expect_retval (int devnull_fd, int retval_min, int retval_max,
+ const char *option, const char *optval)
+{
+ if (optval)
+ {
+ printf ("Testing xargs with option and value %s %s\n", option, optval);
+ }
+ else
+ {
+ printf ("Testing xargs with option %s\n", option);
+ }
+ int retval = run_xargs_get_retval (devnull_fd, option, optval, NULL);
+ fputs ("Expecting return value ", stdout);
+ if (retval_min == retval_max)
+ {
+ printf ("of %d", retval_min);
+ }
+ else
+ {
+ printf ("between %d and %d (inclusive)", retval_min, retval_max);
+ }
+ printf ("; actual return value was %d\n", retval);
+ if (retval < retval_min)
+ {
+ error (EXIT_FAILURE, 0,
+ "FAILED; actual return value %d < minimum value %d\n",
+ retval, retval_min);
+ }
+ else if (retval > retval_max)
+ {
+ error (EXIT_FAILURE, 0,
+ "FAILED; actual return value %d > maximum value %d\n",
+ retval, retval_max);
+ }
+ else
+ {
+ printf ("success: exit value %d is acceptable.\n", retval);
+ }
+}
+
+
+static char *
+value_to_str (uintmax_t val, bool add_one)
+{
+ char buf[1 + INT_BUFSIZE_BOUND (uintmax_t)];
+ char *s = umaxtostr (val, buf + 1);
+ if (add_one)
+ {
+ return xstrdup (decimal_absval_add_one (s - 1));
+ }
+ return xstrdup (s);
+}
+
+static void
+check_xargs_for_limiting_joined_option_value (int devnull_fd,
+ const char *option,
+ uintmax_t limit_value)
+{
+ char *inrange = value_to_str (limit_value, false);
+ char *joined = xmalloc (strlen (inrange) + 2);
+ sprintf (joined, "-l%s", inrange);
+ printf ("Testing xargs with in-range option value %s\n", joined);
+ run_xargs_expect_retval (devnull_fd, 0, 0, joined, NULL);
+ free (joined);
+ free (inrange);
+
+ char *toobig = value_to_str (limit_value, true);
+ char *toobig_joined = xmalloc (strlen (toobig) + 2);
+ sprintf (toobig_joined, "-l%s", toobig);
+ printf ("Testing xargs with out-of--range option value %s\n",
+ toobig_joined);
+ run_xargs_expect_retval (devnull_fd, 1, 125, toobig_joined, NULL);
+ free (toobig);
+ free (toobig_joined);
+}
+
+static void
+check_xargs_for_limiting_option_value (int devnull_fd,
+ const char *option,
+ uintmax_t limit_value)
+{
+ char *inrange = value_to_str (limit_value, false);
+ printf ("Testing xargs with in-range option value %s %s\n",
+ option, inrange);
+ run_xargs_expect_retval (devnull_fd, 0, 0, option, inrange);
+ free (inrange);
+ putchar ('\n');
+
+
+ char *toobig = value_to_str (limit_value, true);
+ printf ("Testing xargs with out-of-range option value %s %s\n",
+ option, toobig);
+ run_xargs_expect_retval (devnull_fd, 1, 125, option, toobig);
+ free (toobig);
+}
+
+
+static void
+check_xargs_s_option_accepts_val (int devnull_fd, uintmax_t value,
+ bool add_one)
+{
+ char *val = value_to_str (value, add_one);
+ run_xargs_expect_retval (devnull_fd, 0, 0, "-s", val);
+ free (val);
+}
+
+static void
+check_xargs_s_option (int devnull_fd)
+{
+ /* Any positive value should be accepted (even if it is silently clamped) */
+ check_xargs_s_option_accepts_val (devnull_fd, UINTMAX_MAX, false);
+ check_xargs_s_option_accepts_val (devnull_fd, UINTMAX_MAX, true);
+
+ /* Very small values for -s are valid but will fail as there is not
+ * enough room for the utility name (which is echo by default). Zero
+ * would be accepted but it would not work (as all command lines
+ * would be too long) so we don't test zero.
+ */
+ run_xargs_expect_retval (devnull_fd, 0, 0, "-s", "6");
+
+ /* We should reject negative values */
+ run_xargs_expect_retval (devnull_fd, 1, 1, "-s", "-1");
+}
+
+int
+main (int argc, char *argv[])
+{
+ int devnull_fd = open ("/dev/null", O_RDONLY);
+ if (devnull_fd < 0)
+ {
+ error (EXIT_FAILURE, errno, "cannot open /dev/null");
+ }
+
+ run_xargs_get_retval (devnull_fd, "--version", NULL);
+ putchar ('\n');
+
+ check_xargs_for_limiting_option_value (devnull_fd, "-L",
+ BC_LINES_PER_EXEC_MAX);
+ check_xargs_for_limiting_joined_option_value (devnull_fd, "-l",
+ BC_LINES_PER_EXEC_MAX);
+ check_xargs_for_limiting_joined_option_value (devnull_fd, "-n",
+ BC_ARGS_PER_EXEC_MAX);
+ check_xargs_s_option (devnull_fd);
+ return EXIT_SUCCESS;
+}
diff --git a/xargs/xargs.1 b/xargs/xargs.1
index 6168153b..e6b4a836 100644
--- a/xargs/xargs.1
+++ b/xargs/xargs.1
@@ -606,6 +606,15 @@ This may not be true of other implementations, so you
should use
.B \-E \[dq]\[dq]
to be certain.
.TP
+.B \-I
+Supported.
+.TP
+.B \-L
+Supported.
+The valid range for the argument exceeds the range required by POSIX
+(1\[en]2147483647). To see the actual range of supported values, use
+\-\-show-limits.
+.TP
.B \-e
Removed from POSIX in IEEE Std 1003.1, 2004.
GNU
@@ -630,6 +639,12 @@ still supports this but you should use
.B \-L
instead.
.TP
+.B \-n
+Supported.
+The valid range for the argument exceeds the range required by POSIX
+(1\[en]2147483647). To see the actual range of supported values, use
+\-\-show-limits.
+.TP
.B \-o
An extension to the POSIX standard for better
compatibility with BSD. Not in POSIX.
@@ -642,20 +657,40 @@ Added to POSIX in IEEE Std 1003.1, 2004.
A GNU extension since before 1994.
Added to POSIX in IEEE Std 1003.1, 2024.
.TP
+.B \-s
+Supported.
+.
+To see the actual range of supported values, use
+\-\-show-limits. See also EXEC SYSTEM CALL AND COMMAND LINE LENGTH
+LIMITS below.
+.TP
.B \-0
A GNU extension since before 1994.
Added to POSIX in IEEE Std 1003.1, 2024.
.
-.SS EXEC SYSTEM CALL LIMITS
+.SS EXEC SYSTEM CALL AND COMMAND LINE LENGTH LIMITS
.P
The POSIX standard allows implementations to have a limit on the size
of arguments to the
.B exec
+functions. This limit could be as low as 4096 bytes including the
+size of the environment. For scripts to be portable, they must not
+rely on a value larger than 4096. However, I know of no
+implementation whose actual limit is that small.
+.P
+The \-s option controls the maximum command-line length used by
+.BR xargs ,
+though this is independent of any limit on
+.B exec
functions.
-This limit could be as low as 4096 bytes including the size of the
-environment.
-For scripts to be portable, they must not rely on a larger value.
-However, I know of no implementation whose actual limit is that small.
+The maximum supported value for the argument of
+.B \-s
+exceeds the POSIX requirement, which is that it should be at least as
+great as
+.BR "$(getconf LINE_MAX)" .
+.B LINE_MAX
+must be at least 2048.
+.
The
.B \-\-show\-limits
option can be used to discover the actual limits in force on the
diff --git a/xargs/xargs.c b/xargs/xargs.c
index 2fc75588..d7d0ae88 100644
--- a/xargs/xargs.c
+++ b/xargs/xargs.c
@@ -222,7 +222,12 @@ static void wait_for_proc (bool all, unsigned int minreap);
static void wait_for_proc_all (void);
static void increment_proc_max (int);
static void decrement_proc_max (int);
-static long parse_num (char *str, int option, long min, long max, int fatal);
+
+static bool parse_signed_num (char *str, int option, intmax_t min,
+ intmax_t max, bool silent, intmax_t * out);
+static bool parse_unsigned_num (char *str, int option, uintmax_t min,
+ uintmax_t max, bool silent, uintmax_t * out);
+
/* *INDENT-OFF* */
@@ -398,6 +403,13 @@ warn_mutually_exclusive (const char *option, const char
*offending)
"ignoring previous %s value"), offending, option, offending);
}
+static void
+show_option_arg_bounds (char opt, uintmax_t min, uintmax_t max)
+{
+ fprintf (stderr,
+ _("Values for the argument of the -%c option must be between %"
+ PRIuMAX " and %" PRIuMAX " inclusive.\n"), opt, min, max);
+}
int
main (int argc, char **argv)
@@ -571,28 +583,52 @@ main (int argc, char **argv)
break;
case 'L': /* POSIX */
- bc_ctl.lines_per_exec =
- parse_num (optarg, /* option= */ 'L', /*min= */ 1L, /*max= */ -1L,
- /*fatal= */ 1);
- /* -L excludes -i -n. */
- if (bc_ctl.args_per_exec != 0)
- {
- warn_mutually_exclusive ("-L", "--max-args");
- bc_ctl.args_per_exec = 0;
- }
- if (bc_ctl.replace_pat != NULL)
- {
- warn_mutually_exclusive ("-L", "--replace");
- bc_ctl.replace_pat = NULL;
- }
+ {
+ uintmax_t val = 0;
+ if (!parse_unsigned_num (optarg,
+ /* option= */ 'L',
+ /* min= */ 1,
+ /* max= */ BC_LINES_PER_EXEC_MAX,
+ /* silent= */ false,
+ &val))
+ {
+ usage (EXIT_FAILURE);
+ }
+ bc_ctl.lines_per_exec = val;
+
+ /* -L excludes -i -n. */
+ if (bc_ctl.args_per_exec != 0)
+ {
+ warn_mutually_exclusive ("-L", "--max-args");
+ bc_ctl.args_per_exec = 0;
+ }
+ if (bc_ctl.replace_pat != NULL)
+ {
+ warn_mutually_exclusive ("-L", "--replace");
+ bc_ctl.replace_pat = NULL;
+ }
+ }
break;
case 'l': /* deprecated */
if (optarg)
- bc_ctl.lines_per_exec = parse_num (optarg, /*option= */ 'l',
/*min= */ 1L, /*max= */
- -1L, /*fatal= */ 1);
+ {
+ uintmax_t val = 0;
+ if (!parse_unsigned_num (optarg,
+ /* option= */ 'l',
+ /* min= */ 1,
+ /* max= */ BC_LINES_PER_EXEC_MAX,
+ /* silent= */ false,
+ &val))
+ {
+ usage (EXIT_FAILURE);
+ }
+ bc_ctl.lines_per_exec = val;
+ }
else
- bc_ctl.lines_per_exec = 1;
+ {
+ bc_ctl.lines_per_exec = 1;
+ }
/* -l excludes -i -n. */
if (bc_ctl.args_per_exec != 0)
{
@@ -607,28 +643,40 @@ main (int argc, char **argv)
break;
case 'n':
- bc_ctl.args_per_exec =
- parse_num (optarg, /*option= */ 'n', /*min= */ 1L, /*max= */ -1L,
- /*fatal= */ 1);
- /* -n excludes -i -l. */
- if (bc_ctl.lines_per_exec != 0)
- {
- warn_mutually_exclusive ("--max-args/-n", "--max-lines");
- bc_ctl.lines_per_exec = 0;
- }
- if (bc_ctl.replace_pat != NULL)
- {
- if (bc_ctl.args_per_exec == 1)
- {
- /* ignore -n1 in '-i -n1' - https://sv.gnu.org/patch/?1500 */
- bc_ctl.args_per_exec = 0;
- }
- else
- {
- warn_mutually_exclusive ("--max-args/-n", "--replace");
- bc_ctl.replace_pat = NULL;
- }
- }
+ {
+ uintmax_t val;
+ if (!parse_unsigned_num (optarg,
+ /* option= */ 'n',
+ /* min= */ 1,
+ /* max= */ BC_ARGS_PER_EXEC_MAX,
+ /* silent= */ false,
+ &val))
+ {
+ usage (EXIT_FAILURE);
+ }
+ bc_ctl.args_per_exec = val;
+
+
+ /* -n excludes -i -l. */
+ if (bc_ctl.lines_per_exec != 0)
+ {
+ warn_mutually_exclusive ("--max-args/-n", "--max-lines");
+ bc_ctl.lines_per_exec = 0;
+ }
+ if (bc_ctl.replace_pat != NULL)
+ {
+ if (bc_ctl.args_per_exec == 1)
+ {
+ /* ignore -n1 in '-i -n1' - https://sv.gnu.org/patch/?1500
*/
+ bc_ctl.args_per_exec = 0;
+ }
+ else
+ {
+ warn_mutually_exclusive ("--max-args/-n", "--replace");
+ bc_ctl.replace_pat = NULL;
+ }
+ }
+ }
break;
/* The POSIX standard specifies that it is not an error
@@ -637,22 +685,51 @@ main (int argc, char **argv)
*/
case 's':
{
- size_t arg_size;
+ uintmax_t val = 2;
+ bool positive_overflow = false;
act_on_init_result ();
- arg_size = parse_num (optarg,
- /*option= */ 's',
- /*min= */ 1L,
- /*max= */ bc_ctl.posix_arg_size_max,
- /*fatal= */ 0);
- if (arg_size > bc_ctl.posix_arg_size_max)
+ /* Convert the number but don't clamp it because clamping
+ * this value is supposed to be silent. We set
+ * silent=true below in order to get error messages about
+ * un-convertable values since we still need those.
+ */
+ if (!parse_unsigned_num (optarg,
+ /* option= */ 's',
+ /* min= */ 0,
+ /* max= */ UINTMAX_MAX,
+ /* silent= */ true,
+ &val))
+ {
+ if (val == UINTMAX_MAX)
+ {
+ positive_overflow = true;
+ }
+ else
+ {
+ /* Option argument wasn't a number or it was a
+ * negative number.
+ */
+ fprintf (stderr,
+ _
+ ("%s: option argument %s for the -s option should
be a non-negative decimal number\n"),
+ program_name, optarg);
+ usage (EXIT_FAILURE);
+ }
+ }
+ if (positive_overflow || (val > bc_ctl.posix_arg_size_max))
+ {
+ /* If the -s argument can be parsed as a number but is
+ * outside the supported range, POSIX requires that we
+ * reduce it to the maximum allowable value. This
+ * probably also means that we cannot issue a
+ * diagnostic.
+ */
+ bc_ctl.arg_max = bc_ctl.posix_arg_size_max;
+ }
+ else
{
- error (0, 0,
- _("warning: value %ld for -s option is too large, "
- "using %ld instead"),
- (long) arg_size, (long) bc_ctl.posix_arg_size_max);
- arg_size = bc_ctl.posix_arg_size_max;
+ bc_ctl.arg_max = val;
}
- bc_ctl.arg_max = arg_size;
}
break;
@@ -682,9 +759,30 @@ main (int argc, char **argv)
break;
case 'P':
- /* Allow only up to MAX_PROC_MAX child processes. */
- proc_max = parse_num (optarg, /*option= */ 'P', /*min= */ 0L, /*max=
*/
- MAX_PROC_MAX, /*fatal= */ 1);
+ {
+ uintmax_t val = 2;
+ /* Allow only up to MAX_PROC_MAX child processes. */
+ if (!parse_unsigned_num (optarg,
+ /* option= */ 'P',
+ /* min= */ 0L,
+ /* max= */ MAX_PROC_MAX,
+ /* silent= */ false,
+ &val))
+ {
+ /* The number is invalid or was clamped to the range
+ * [0, MAX_PROC_MAX]. Clamping to MAX_PROC_MAX is
+ * harmless; other failures indicate that the user's
+ * instruction can't be followed (the argument was not
+ * a valid number or it was a negative number).
+ */
+ if (val != MAX_PROC_MAX)
+ {
+ usage (EXIT_FAILURE);
+ }
+ }
+ proc_max = val;
+ }
+
#if defined SIGUSR1 && defined SIGUSR2
catch_usr_signals = true;
#else
@@ -822,6 +920,14 @@ main (int argc, char **argv)
fprintf (stderr,
_("Maximum parallelism (--max-procs must be no greater): %"
PRIuMAX "\n"), (uintmax_t) MAX_PROC_MAX);
+ show_option_arg_bounds ('L', 1, BC_LINES_PER_EXEC_MAX);
+ show_option_arg_bounds ('l', 1, BC_LINES_PER_EXEC_MAX);
+ show_option_arg_bounds ('n', 1, BC_ARGS_PER_EXEC_MAX);
+ fprintf (stderr,
+ _("Option arguments for the -s option should be positive. "
+ "If they are over %" PRIuMAX
+ ", they are silently reduced.\n"),
+ bc_ctl.posix_arg_size_max);
if (isatty (STDIN_FILENO))
{
@@ -1729,44 +1835,220 @@ decrement_proc_max (int ignore)
}
-/* Return the value of the number represented in STR.
- OPTION is the command line option to which STR is the argument.
- If the value does not fall within the boundaries MIN and MAX,
- Print an error message mentioning OPTION. If FATAL is true,
- we also exit. */
-static long
-parse_num (char *str, int option, long int min, long int max, int fatal)
+/* Return the value of the number represented in STR. OPTION is the
+ command line option to which STR is the argument. MIN and MAX
+ specify the valid range. SILENT controls the printing of error
+ messages.
+
+ On success: *OUT is set to the converted value, and true is
+ returned.
+
+ Failure cases:
+
+ 1. Input is not a number or is followed by non-digits: *OUT is
+ unchanged. False is returned.
+
+ 2. Input is out of range: *OUT is set to the corresponding extreme
+ of the valid range [MIN, MAX]. False is returned.
+
+ In failure cases, an error message is printed unless SILENT is
+ true. No error message is printed in the success case.
+*/
+static bool
+parse_signed_num (char *str,
+ int option,
+ intmax_t min, intmax_t max, bool silent, intmax_t *out)
{
char *eptr;
- long val;
+ intmax_t val;
- val = strtol (str, &eptr, 10);
+ errno = 0;
+ val = strtoimax (str, &eptr, 10);
if (eptr == str || *eptr)
{
- fprintf (stderr, _("%s: invalid number \"%s\" for -%c option\n"),
- program_name, str, option);
- usage (EXIT_FAILURE);
- exit (EXIT_FAILURE);
+ if (!silent)
+ {
+ fprintf (stderr, _("%s: invalid number \"%s\" for -%c option\n"),
+ program_name, str, option);
+ }
+ return false;
+ }
+
+ if (val == INTMAX_MAX && errno)
+ {
+ if (!silent)
+ {
+ fprintf (stderr,
+ _
+ ("%s: value %s for -%c option could not be converted
(values above %"
+ PRIiMAX " are not supported)\n"), program_name, str,
+ option, INTMAX_MAX);
+ }
+ *out = max < INTMAX_MAX ? max : INTMAX_MAX;
+ return false;
}
- else if (val < min)
+
+ if (val == INTMAX_MIN && errno)
{
- fprintf (stderr, _("%s: value %s for -%c option should be >= %ld\n"),
- program_name, str, option, min);
- if (fatal)
- usage (EXIT_FAILURE);
+ if (!silent)
+ {
+ fprintf (stderr,
+ _
+ ("%s: value %s for -%c option could not be converted
(values below %"
+ PRIiMAX " are not supported)\n"), program_name, str,
+ option, INTMAX_MIN);
+ }
+ *out = min > INTMAX_MIN ? min : INTMAX_MIN;
+ return false;
+ }
- val = min;
+ if (val < min)
+ {
+ if (!silent)
+ {
+ fprintf (stderr,
+ _("%s: value %s for -%c option should be >= %ld\n"),
+ program_name, str, option, min);
+ }
+ *out = min;
+ return false;
}
- else if (max >= 0 && val > max)
+
+ if (max >= 0 && val > max)
{
- fprintf (stderr, _("%s: value %s for -%c option should be <= %ld\n"),
- program_name, str, option, max);
- if (fatal)
- usage (EXIT_FAILURE);
+ if (!silent)
+ {
+ fprintf (stderr,
+ _("%s: value %s for -%c option should be <= %ld\n"),
+ program_name, str, option, max);
+ }
+ *out = max;
+ return false;
+ }
+
+ *out = val;
+ return true;
+}
+
+/* Return the value of the number represented in STR. OPTION is the
+ command line option to which STR is the argument. MIN and MAX
+ specify the valid range. SILENT controls the printing of error
+ messages.
+
+ On success: *OUT is set to the converted value, and true is
+ returned.
+
+ Failure cases:
+
+ 1. Input is not a number or is followed by non-digits: *OUT is
+ unchanged. False is returned.
- val = max;
+ 2. Input is out of range: *OUT is set to the corresponding extreme
+ of the valid range [MIN, MAX]. False is returned.
+
+ In failure cases, an error message is printed unless SILENT is
+ true. No error message is printed in the success case.
+*/
+static bool
+parse_unsigned_num (char *str, int option, uintmax_t min, uintmax_t max,
+ bool silent, uintmax_t *out)
+{
+ /* We initialise ival to determine whether a converted value got
+ * clamped in the call to parse_signed_num.
+ */
+ intmax_t ival = 2;
+ /* Attempt a signed conversion so that we can tell if the number
+ * specified was negative.
+ */
+ if (!parse_signed_num (str, option,
+ /* min= */ 0,
+ /* max= */ INTMAX_MAX,
+ /* silent= */ true,
+ &ival))
+ {
+ /* Since parse_signed_num() failed, we know that the string
+ * value is either not a valid number, it is less than zero, or
+ * it is greater than INTMAX_MAX.
+ *
+ * In the n > INTMAX_MAX case, val==INTMAX_MAX; we will handle
+ * this case below after attempting an unsigned conversion.
+ *
+ * In the not-valid-number case, ival will not have been
+ * modified (i.e. will still be 2).
+ */
+ if (ival == 0)
+ {
+ /* Input is a valid but negative number. strtoumax() cannot
+ * detect this case (it converts them as positive numbers).
+ */
+ *out = 0;
+ if (!silent)
+ {
+ /* The limit we mention here is MIN rather than 0,
+ * because MIN is the actual lower limit. */
+ fprintf (stderr,
+ _("%s: value %s for -%c option should be >= %" PRIuMAX
+ "\n"), program_name, str, option, min);
+ }
+ return false;
+ }
+ }
+
+ char *eptr;
+ uintmax_t val;
+
+ errno = 0;
+ val = strtoumax (str, &eptr, 10);
+ if (eptr == str || *eptr)
+ {
+ if (!silent)
+ {
+ fprintf (stderr, _("%s: invalid number \"%s\" for -%c option\n"),
+ program_name, str, option);
+ }
+ return false;
+ }
+
+ if (val == UINTMAX_MAX && errno)
+ {
+ if (!silent)
+ {
+ fprintf (stderr,
+ _
+ ("%s: value %s for -%c option could not be converted
(values above %"
+ PRIuMAX " are not supported)\n"), program_name, str,
+ option, UINTMAX_MAX);
+ }
+ *out = max < UINTMAX_MAX ? max : UINTMAX_MAX;
+ return false;
+ }
+
+ if (val < min)
+ {
+ if (!silent)
+ {
+ fprintf (stderr,
+ _("%s: value %s for -%c option should be >= %" PRIuMAX
+ "\n"), program_name, str, option, min);
+ }
+ *out = min;
+ return false;
}
- return val;
+
+ if (val > max)
+ {
+ if (!silent)
+ {
+ fprintf (stderr,
+ _("%s: value %s for -%c option should be <= %" PRIuMAX
+ "\n"), program_name, str, option, max);
+ }
+ *out = max;
+ return false;
+ }
+
+ *out = val;
+ return true;
}
static void
--
2.47.3