* 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


Reply via email to