NetBSD Problem Report #59828

From www@netbsd.org  Wed Dec 10 07:09:01 2025
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
	 client-signature RSA-PSS (2048 bits) client-digest SHA256)
	(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id A45C91A9239
	for <gnats-bugs@gnats.NetBSD.org>; Wed, 10 Dec 2025 07:09:01 +0000 (UTC)
Message-Id: <20251210070900.48EC81A923A@mollari.NetBSD.org>
Date: Wed, 10 Dec 2025 07:09:00 +0000 (UTC)
From: rootnode+netbsd@wollwage.com
Reply-To: rootnode+netbsd@wollwage.com
To: gnats-bugs@NetBSD.org
Subject: getopt(3) GNU extension wrong behavior
X-Send-Pr-Version: www-1.0

>Number:         59828
>Category:       lib
>Synopsis:       getopt(3) GNU extension wrong behavior
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    lib-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Wed Dec 10 07:10:01 +0000 2025
>Last-Modified:  Thu Dec 11 17:40:00 +0000 2025
>Originator:     Simon Wollwage
>Release:        getopt(3) GNU extension wrong behavior
>Organization:
>Environment:
NetBSD  11.99.4 NetBSD 11.99.4 (GENERIC) #0: Mon Dec  8 23:00:22 UTC 2025  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd
64/compile/GENERIC amd64
>Description:
Originally posted in freebsd-bugs@freebsd.org (see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291374)

From the getopt(3) manual page:

     The option string optstring may contain the following elements:
     individual characters, and characters followed by a colon to indicate an
     option argument is to follow.  If an individual character is followed by
     two colons, then the option argument is optional; optarg is set to the
     rest of the current argv word, or NULL if there were no more characters
     in the current word.  This is a GNU extension.  For example, an option
     string "x" recognizes an option "-x", and an option string "x:"
     recognizes an option and argument "-x argument".  It does not matter to
     getopt() if a following argument has leading white space.

Note the last clause about leading white space. It does not match the code.
>How-To-Repeat:
For example, our date(1) utility uses getopt(argc, argv, "f:I::jnRr:uv:z:") and look at this:

$ date -Idate
2025-12-04
$ date -I date
date: illegal time format
usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]
            [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
            [[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]

#include <stdio.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    int         ch;

    while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:u:U:wW")) != -1) {
        printf("ch is %c optarg is %s\n",
            (char)ch, optarg ? optarg : " NULL");
    }

    return (0);
}

Option list taken from our tftpd(8) programm that uses "-s path" for a chroot and optional "-d value" for debugging.

Build it: cc -o getoptest -ansi -pedantic -Wall getoptest.c
Run it:

$ ./getoptest -l -w -d14 -s /usr/local/admin/tftproot -u admin -U 0
ch is l optarg is  NULL
ch is w optarg is  NULL
ch is d optarg is 14
ch is s optarg is /usr/local/admin/tftproot
ch is u optarg is admin
ch is U optarg is 0

So far, so good. And now:

$ ./getoptest -l -w -d 14 -s /usr/local/admin/tftproot -u admin -U 0
ch is l optarg is  NULL
ch is w optarg is  NULL
ch is d optarg is  NULL

Option list silently truncated. No -s, no chrooting for tftpd.
>Fix:
Index: common/lib/libc/stdlib/getopt.c
===================================================================
RCS file: /cvsroot/src/common/lib/libc/stdlib/getopt.c,v
retrieving revision 1.2
diff -u -r1.2 getopt.c
--- common/lib/libc/stdlib/getopt.c     29 Jun 2024 07:56:56 -0000      1.2
+++ common/lib/libc/stdlib/getopt.c     10 Dec 2025 07:06:08 -0000
@@ -128,13 +128,17 @@
                   entire next argument. */
                if (*place)
                        optarg = __UNCONST(place);
-               else if (oli[2] == ':')
+               else if (oli[2] == ':') {
                        /*
                         * GNU Extension, for optional arguments if the rest of
                         * the argument is empty, we return NULL
                         */
-                       optarg = NULL;
-               else if (nargc > ++optind)
+                       if (nargc > (optind + 1) && nargv[optind + 1][0] != '-') {
+                               optarg = nargv[++optind];
+                       } else {
+                               optarg = NULL;
+                       }
+               } else if (nargc > ++optind)
                        optarg = nargv[optind];
                else {
                        /* option-argument absent */

>Audit-Trail:
From: mlelstv@serpens.de (Michael van Elst)
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Wed, 10 Dec 2025 09:18:22 -0000 (UTC)

 rootnode+netbsd@wollwage.com writes:

 >$ date -Idate
 >2025-12-04
 >$ date -I date
 >date: illegal time format
 >usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]
 >            [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
 >            [[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]


 NetBSD date doesn't have an -I option.

 The coreutils package from pkgsrc contains a date command, but
 which works the same as on Linux:

 NetBSD-10, coreutils-9.6nb1:

 % /usr/pkg/gnu/bin/date -I date
 date: invalid date date

 % /usr/pkg/gnu/bin/date -"I date"
 date: invalid argument  date for --iso-8601
 Valid arguments are:
   - hours
   - minutes
   - date
   - seconds
   - ns
 Try '/usr/pkg/gnu/bin/date --help' for more information.

 % /usr/pkg/gnu/bin/date -Idate
 2025-12-10

 Debian trixie, coreutils 9.7-3:

 $ date -I date
 date: invalid date 'date'

 $ date -"I date"
 date: invalid argument ' date' for '--iso-8601'
 Valid arguments are:
   - 'hours'
   - 'minutes'
   - 'date'
   - 'seconds'
   - 'ns'
 Try 'date --help' for more information.

 $ date -Idate
 2025-12-10


 I don't think your interpretation of the manual page is correct,
 but without an example for "x::" maybe misleading.

 "If an individual character is followed by two colons, then the
 option argument is optional; optarg is set to the rest of the
 current argv word, or NULL if there were no more characters in the
 current word."

 So, the option argument is "the rest of the current argv word" or "NULL".
 It is never "the following word if that doesn't start with a dash".


 No idea why FreeBSD calls it a "GNU extension" while NetBSD calls it
 a "NetBSD extension" or who actually created it...


From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Wed, 10 Dec 2025 14:30:13 +0300

 I haven't surveyed all of our base system, but at least sed(1) uses
 double-colon for -i[suffix]

 -uwe

From: Jose Luis Duran <jlduran@gmail.com>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Wed, 10 Dec 2025 12:07:43 -0300

 There appears to have been some confusion when filing this bug report.
 It was my suggestion to report it here, since FreeBSD's getopt(3)
 comes from NetBSD.

 Given the following MWE script (getopttest.c):

 #include <stdio.h>
 #include <unistd.h>

 int
 main(int argc, char *argv[])
 {
   int ch;

   while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:u:U:wW")) != -1) {
     printf("ch is %c optarg is %s\n", (char)ch, optarg ? optarg : " NULL");
   }

   return (0);
 }

 Notice the "d" option has double colons, and is the subject at hand.
 By reading the manual page for getopt(3):

   If an individual character is followed by two colons,
   then the option argument is optional; optarg is set
   to the rest of the current argv word,
   or NULL if there were no more characters in the current word.
   This is a NetBSD extension.

 The following output from the above MWE program is expected (or is it not?):

 1. With a space between "-d" and the option argument:
 $ ./getopttest -l -w -d 14 -s /usr/local/admin/tftproot -u admin -U 0
 ch is l optarg is  NULL
 ch is w optarg is  NULL
 ch is d optarg is 14 # <- XXX [1]
 ch is s optarg is /usr/local/admin/tftproot # <- XXX missing
 ch is u optarg is admin # <- XXX missing
 ch is U optarg is 0 # <- XXX missing

 [1]: "It does not matter to getopt() if a following argument has
 leading whitespace."

 Current output:
 $ ./getopttest -l -w -d 14 -s /usr/local/admin/tftproot -u admin -U 0
 ch is l optarg is  NULL
 ch is w optarg is  NULL
 ch is d optarg is  NULL

 Notice the arguments after "d" are missing.

 For completeness, all these other tests currently pass on NetBSD:

 2. Without a space between "-d" and the option argument:
 $ ./getopttest -l -w -d14 -s /usr/local/admin/tftproot -u admin -U 0
 ch is l optarg is  NULL
 ch is w optarg is  NULL
 ch is d optarg is 14
 ch is s optarg is /usr/local/admin/tftproot
 ch is u optarg is admin
 ch is U optarg is 0

 3. The "d" option without any arguments:
 $ ./getopttest -l -w -d -s /usr/local/admin/tftproot -u admin -U 0
 ch is l optarg is  NULL
 ch is w optarg is  NULL
 ch is d optarg is  NULL
 ch is s optarg is /usr/local/admin/tftproot
 ch is u optarg is admin
 ch is U optarg is 0

 4. Invalid options after the "d" option:
 ./getopttest -l -w -d -14 -s /usr/local/admin/tftproot -u admin -U 0
 ch is l optarg is  NULL
 ch is w optarg is  NULL
 ch is d optarg is  NULL
 getopttest: unknown option -- 1
 ch is ? optarg is  NULL
 getopttest: unknown option -- 4
 ch is ? optarg is  NULL
 ch is s optarg is /usr/local/admin/tftproot
 ch is u optarg is admin
 ch is U optarg is 0

 Regards,

From: Simon Wollwage <rootnode@wollwage.com>
To: "Michael van Elst via gnats" <gnats-admin@NetBSD.org>
Cc: lib-bug-people@netbsd.org, netbsd-bugs@netbsd.org,
 rootnode+netbsd@wollwage.com,  gnats-bugs@netbsd.org
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Wed, 10 Dec 2025 22:57:54 +0900

 "Michael van Elst via gnats" <gnats-admin@NetBSD.org> writes:

 >  NetBSD date doesn't have an -I option.
 >  
 >  The coreutils package from pkgsrc contains a date command, but
 >  which works the same as on Linux:
 >  
 >  NetBSD-10, coreutils-9.6nb1:
 >  
 >  % /usr/pkg/gnu/bin/date -I date
 >  date: invalid date date

 Sorry, copied that stupidly forgetting that the date tool might be
 different. First contribution, so I didn't have that on my radar.

 >  I don't think your interpretation of the manual page is correct,
 >  but without an example for "x::" maybe misleading.

 >From a quick perusal it seems that sed ant syslogd for example are using
 it.  On the ports side it might be more.

 >  "If an individual character is followed by two colons, then the
 >  option argument is optional; optarg is set to the rest of the
 >  current argv word, or NULL if there were no more characters in the
 >  current word."
 >  
 >  So, the option argument is "the rest of the current argv word" or "NULL".
 >  It is never "the following word if that doesn't start with a dash".

 The paragraph ends with

 "It does not matter to getopt() if a following argument has leading
 whitespace."

 I guess that is the part I am unsure about and interpretet as also
 counting for the double colon case.

 >  No idea why FreeBSD calls it a "GNU extension" while NetBSD calls it
 >  a "NetBSD extension" or who actually created it...

 In the source code to getopt it is calledt a GNU extension.

 If the consensus is that the wording about the whitespace doesn't apply
 to the double colon case, then happy to drop it.  From a consistency or
 UX viewpoint I would say it makes sense to allow the whitespace for
 both, single and double colon.

From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Wed, 10 Dec 2025 19:47:32 +0300

 On Wed, Dec 10, 2025 at 15:10:02 +0000, Jose Luis Duran via gnats wrote:

 >    while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:u:U:wW")) != -1) {
 >      printf("ch is %c optarg is %s\n", (char)ch, optarg ? optarg : " NULL");
 >    }
 [...]
 >  Notice the "d" option has double colons, and is the subject at hand.
 >  By reading the manual page for getopt(3):
 >  
 >    If an individual character is followed by two colons,
 >    then the option argument is optional; optarg is set
 >    to the rest of the current argv word,
 >    or NULL if there were no more characters in the current word.
 >    This is a NetBSD extension.
 [...]
 > 1. With a space between "-d" and the option argument:

 So you are making a conceptual mistake here.  The phrase above is
 contradiction in terms.

 As the man page says, for double colon optios the argument is:

   optarg is set to the rest of the current argv word ...

 If the current argv word, "-d", it does not have anything after 'd',
 there's no argument.  The space that delimts "-d" word is not
 "between" anything.

 If the option were "-d14", optarg would get set to point after 'd', to
 the "14" part of the "-d14" word.


 >  Current output:
 >  $ ./getopttest -l -w -d 14 -s /usr/local/admin/tftproot -u admin -U 0
 >  ch is l optarg is  NULL
 >  ch is w optarg is  NULL
 >  ch is d optarg is  NULL
 >  
 >  Notice the arguments after "d" are missing.

 Right, b/c "14" is not an option, so getopt stops.


 -uwe

From: Simon Wollwage <rootnode+netbsd@wollwage.com>
To: <gnats-bugs@NetBSD.org>
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 02:08:45 +0900

 "Valery Ushakov via gnats" <gnats-admin@NetBSD.org> writes:

 >  So you are making a conceptual mistake here.  The phrase above is
 >  contradiction in terms.
 >  
 >  As the man page says, for double colon optios the argument is:
 >  
 >    optarg is set to the rest of the current argv word ...
 >  
 >  If the current argv word, "-d", it does not have anything after 'd',
 >  there's no argument.  The space that delimts "-d" word is not
 >  "between" anything.

 Isn't that maybe then just an issue with the wording?  The description
 of the extension in the man page also says that whitespace doesn't
 matter in this case.

 Or at least a conceptual issue. If non-optional arguments, like "-s
 /usr/local/admin/tftproot" are allowed to have a whitespace between the
 option and its value, why not also optional arguments?

From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Wed, 10 Dec 2025 20:49:19 +0300

 On Wed, Dec 10, 2025 at 17:10:02 +0000, Simon Wollwage via gnats wrote:

 >  Isn't that maybe then just an issue with the wording?  The description
 >  of the extension in the man page also says that whitespace doesn't
 >  matter in this case.

 But the description you refer to is not the description of the
 extension.  It's the description of the "x:" example (I think Michael
 has pointed that out already):

      For example, an option string "x" recognizes an option “-x”, and
      an option string "x:" recognizes an option and argument “-x
      argument”.  It does not matter to getopt() if a following
      argument has leading whitespace.


 Yes, it's awkward and needs to be made less confusing.

 -uwe

From: Simon Wollwage <rootnode+netbsd@wollwage.com>
To: gnats-bugs@NetBSD.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 03:02:05 +0900

 "Valery Ushakov via gnats" <gnats-admin@NetBSD.org> writes:

 >  Yes, it's awkward and needs to be made less confusing.

 I just went and checked what OpenBSD is doing and there the manpage
 states

 "It does not matter to getopt() if a following argument has leading
 whitespace; except in the case where the argument is optional, denoted
 with two colons, no leading whitespace is permitted."

 That is less confusing (even if I think that UX might be better if the
 whitespace were allowed, but that is besides the point.

 Happy to rescind the submitted patch and create one to update the
 manpage to remove the confusion.

From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 12:31:21 +0300

 On Wed, Dec 10, 2025 at 18:05:01 +0000, Simon Wollwage via gnats wrote:

 >  That is less confusing (even if I think that UX might be better if the
 >  whitespace were allowed, but that is besides the point.

 The behaviour cannot be changed b/c that would break people's scripts
 that do things like (a random example off the top of my head):

     sed -i '/^constty/s/off/on/' $target/etc/ttys


 That also opens a can of worms with an optarg that starts with a dash,
 e.g.

     sed -i -OLD ...


 -uwe

From: Robert Elz <kre@munnari.OZ.AU>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 17:37:40 +0700

     Date:        Wed, 10 Dec 2025 18:05:01 +0000 (UTC)
     From:        "Simon Wollwage via gnats" <gnats-admin@NetBSD.org>
     Message-ID:  <20251210180501.BE59C1A923A@mollari.NetBSD.org>

   |  That is less confusing (even if I think that UX might be better if the
   |  whitespace were allowed, but that is besides the point.

 It is impossible, think of what happens if either:

 1) you want the optarg to -d to be -14 instead of 14, what would your
    proposed patch to with cmd -d -14 ... ?

 2) there are no more options after the -d (whose optional value is not
    wanted) and the next arg is a filename (or something - a positional arg).
     Is the plan to always require using the -- arg?   Or given 1 above
    would that -- just be treated as the optarg to -d  (cmd -d -- whatever)

 With single : options, where the optarg is required, it doesn't matter,
 if the optarg value doesn't immediately follow the option character
 (without intervening anything) then the next arg is the optarg value,
 whatever it looks like (if there is none it is an error).

 But with optional optargs (::) there needs to be a clear, obvious, way
 to determine if the optional optarg is there or not).  And it has to work
 in every conceivable case.

 kre

From: Simon Wollwage <rootnode@wollwage.com>
To: gnats-bugs@NetBSD.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 21:10:45 +0900

 "Robert Elz via gnats" <gnats-admin@NetBSD.org> writes:

 >  With single : options, where the optarg is required, it doesn't matter,
 >  if the optarg value doesn't immediately follow the option character
 >  (without intervening anything) then the next arg is the optarg value,
 >  whatever it looks like (if there is none it is an error).
 >  
 >  But with optional optargs (::) there needs to be a clear, obvious, way
 >  to determine if the optional optarg is there or not).  And it has to work
 >  in every conceivable case.

 Agreed.  But maybe at least the wording can be made clearer in the man
 page.  See the additional text in the OpenBSD version for example.

 If that is acceptable I'll be happy to make a patch for that.

From: "Valery Ushakov" <uwe@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/59828 CVS commit: src/lib/libc/stdlib
Date: Thu, 11 Dec 2025 14:07:23 +0000

 Module Name:	src
 Committed By:	uwe
 Date:		Thu Dec 11 14:07:23 UTC 2025

 Modified Files:
 	src/lib/libc/stdlib: getopt.3

 Log Message:
 getopt(3): be more explicit about :: extension

 Make it possible to search for literal two colons (::) and actually
 find something.  Make the "x"/"x:"/"x::" examples more explicit and
 more visibile.

 PR lib/59828


 To generate a diff of this commit:
 cvs rdiff -u -r1.36 -r1.37 src/lib/libc/stdlib/getopt.3

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 17:12:01 +0300

 On Thu, Dec 11, 2025 at 12:15:01 +0000, Simon Wollwage via gnats wrote:

 > If that is acceptable I'll be happy to make a patch for that.

 I don't like the openbsd wording either.  (E.g. pedantically speaking
 both -x' foo' and -x ' foo' are examples of "argument has leading
 whitespace").

 I've commited my own take.  Let me know if it could be improved.

 Thanks!

 -uwe

From: Simon Wollwage <rootnode+netbsd@wollwage.com>
To: gnats-bugs@NetBSD.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 23:51:58 +0900

 "Valery Ushakov via gnats" <gnats-admin@NetBSD.org> writes:

 >  I don't like the openbsd wording either.  (E.g. pedantically speaking
 >  both -x' foo' and -x ' foo' are examples of "argument has leading
 >  whitespace").
 >  
 >  I've commited my own take.  Let me know if it could be improved.

 I guess that looks ok.  I would have preferred to give it a shot first,
 but that is ok.

 The part of the OpenBSD wording I was referring to was the "except in
 the case where the argument is optional, denoted with two colons, no
 leading whitespace is permitted." part, which I personally think is
 quite concise as a small addendum and clears up the confusion.

 It keeps most of the existing wording intact and only covers the edge
 case of the whitespace for optional arguments for that original GNU
 extension.

 If the feeling is that the provided patch is clearer then I will yield
 to that. An improved wording is an improved wording.  Not a hill to
 fight on :D

From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: lib/59828: getopt(3) GNU extension wrong behavior
Date: Thu, 11 Dec 2025 20:36:30 +0300

 On Thu, Dec 11, 2025 at 14:55:01 +0000, Simon Wollwage via gnats wrote:

 >  The part of the OpenBSD wording I was referring to was the "except in
 >  the case where the argument is optional, denoted with two colons, no
 >  leading whitespace is permitted." part, which I personally think is
 >  quite concise as a small addendum and clears up the confusion.

 But that's exactly the problem, it's a run-on sentence that is also
 technically incorrect ("leading whitespace" is wrong notion here).  I
 wanted to provide a very explicit example that one can immediately
 see, not an anstract description that requres further exegesis.


 >  An improved wording is an improved wording.  Not a hill to
 >  fight on :D

 Right :)


 -uwe

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.