Skip to content

Conversation

@Kintaro
Copy link

@Kintaro Kintaro commented Dec 6, 2025

getopt had an issue when parsing optional arguments if they had a space between the opion and its value.

Request: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291374

Tested with bricoler run freebsd-src-regression-suite

@github-actions
Copy link

github-actions bot commented Dec 6, 2025

Thank you for taking the time to contribute to FreeBSD!
All issues resolved.

@Kintaro
Copy link
Author

Kintaro commented Dec 6, 2025

This probably needs to be backported, but as this is my first contribution I am not sure about the proper workflow on this._

Copy link
Member

@jlduran jlduran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should manually pass the following three tests (at least):

$ ./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
$ ./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 14 # XXX <- (Or is it NULL? I'll have to test on Linux)
ch is s optarg is /usr/local/admin/tftproot
ch is u optarg is admin
ch is U optarg is 0

and

$ ./getoptest -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

if (*place)
optarg = place;
else if (oli[2] == ':')
else if (oli[2] == ':') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style(9) fixes I was referring to in the bug report should be addressed, maybe using NetBSD style guide (https://cvsweb.netbsd.org/bsdweb.cgi/src/share/misc/style?rev=HEAD&content-type=text/x-cvsweb-markup).
Basically, use tabs, not spaces for indentation, and braces around single-line bodies are optional.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. I fixed the tabs. Not sure on the braces. It is a "single instruction" but it is another nested if/else. Not contesting any of it, just unsure which one would be preferred.

As for the tests:

rootnode@x220 ~/c/c/291374> ./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
rootnode@x220 ~/c/c/291374> ./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
ch is s optarg is /usr/local/admin/tftproot
ch is u optarg is admin
ch is U optarg is 0
rootnode@x220 ~/c/c/291374> ./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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, this patch does not match the one from the bug report, nor it passes the tests above.

Copy link
Author

@Kintaro Kintaro Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, this patch does not match the one from the bug report, nor it passes the tests above.

I mixed up the two branches between my 2 machines. One wasn't up to date. You refer to the missing && nargv[optind + 1][0] != '-'?

After some testing it turned out that this would for example segfault on ./getopttest -d -14

At that point it comes down to interpretation I guess. Is the - in the value argument allowed or should it be quoted to be allowed.

The version in this PR is not segfaulting and allows for - values.

$ ./getoptest -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  -s

Should the value 14 be missing as in the example above, then it would stop parsing at that point and return an error.

As I understand it, the man page description allows for the whitespace followed by any value.

But you are correct, the 3rd case would be broken.

I'll see if I can mitigate the segfault and get the 3rd case working. Values starting with a - might have to be quoted then.

Copy link
Member

@jlduran jlduran Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so let's add that 4th test:

./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
./getoptest: illegal option -- 1
ch is ? optarg is  NULL
./getoptest: illegal 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

EDIT: Match the expected FreeBSD output.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean as the expected behavior?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean as the expected behavior?

Yes, unless you have a different suggestion.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion makes sense. I will make the appropriate changes. Will take bit due to timezones.

@jlduran
Copy link
Member

jlduran commented Dec 8, 2025

So, I have the following patch you submitted in the Buzgilla Problem Report 291374 (adjusted to match style(9)):

--- a/lib/libc/stdlib/getopt.c
+++ b/lib/libc/stdlib/getopt.c
@@ -107,13 +107,16 @@ getopt(int nargc, char * const nargv[], const char *ostr)
                   entire next argument. */
                if (*place)
                        optarg = 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 */

Do you see any issues with it?

@Kintaro
Copy link
Author

Kintaro commented Dec 8, 2025

So, I have the following patch you submitted in the Buzgilla Problem Report 291374 (adjusted to match style(9)):

--- a/lib/libc/stdlib/getopt.c
+++ b/lib/libc/stdlib/getopt.c
@@ -107,13 +107,16 @@ getopt(int nargc, char * const nargv[], const char *ostr)
                   entire next argument. */
                if (*place)
                        optarg = 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 */

Do you see any issues with it?

Yes, in it's current state it would segfault on the ./getoptest -l -w -d -14 -s /usr/local/admin/tftproot -u admin -U 0 test. I will have to add a few checks and rethink the approach.

@jlduran
Copy link
Member

jlduran commented Dec 8, 2025

So, I have the following patch you submitted in the Buzgilla Problem Report 291374 (adjusted to match style(9)):

--- a/lib/libc/stdlib/getopt.c
+++ b/lib/libc/stdlib/getopt.c
@@ -107,13 +107,16 @@ getopt(int nargc, char * const nargv[], const char *ostr)
                   entire next argument. */
                if (*place)
                        optarg = 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 */

Do you see any issues with it?

Yes, in it's current state it would segfault on the ./getoptest -l -w -d -14 -s /usr/local/admin/tftproot -u admin -U 0 test. I will have to add a few checks and rethink the approach.

That is strange, I don't see any segmentation faults:

$ ./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: illegal option -- 1
ch is ? optarg is  NULL
getopttest: illegal 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

@jlduran jlduran added the changes-required Cannot land as is, change requested of submitter label Dec 8, 2025
@Kintaro
Copy link
Author

Kintaro commented Dec 8, 2025

L

So, I have the following patch you submitted in the Buzgilla Problem Report 291374 (adjusted to match style(9)):

--- a/lib/libc/stdlib/getopt.c
+++ b/lib/libc/stdlib/getopt.c
@@ -107,13 +107,16 @@ getopt(int nargc, char * const nargv[], const char *ostr)
                   entire next argument. */
                if (*place)
                        optarg = 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 */

Do you see any issues with it?

Yes, in it's current state it would segfault on the ./getoptest -l -w -d -14 -s /usr/local/admin/tftproot -u admin -U 0 test. I will have to add a few checks and rethink the approach.

That is strange, I don't see any segmentation faults:

$ ./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: illegal option -- 1
ch is ? optarg is  NULL
getopttest: illegal 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

I did a clean build. Yes, that seems to work. I will update the PR.

@jlduran
Copy link
Member

jlduran commented Dec 8, 2025

Please commit the patch attached in:

#1918 (comment)

And we'll start from there.

@jlduran jlduran removed the changes-required Cannot land as is, change requested of submitter label Dec 9, 2025
Copy link
Member

@jlduran jlduran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

The correct trailer should be:

PR:<tab><tab>291374

@jlduran jlduran added the ready Seems ready to go, subject to final sanity check label Dec 9, 2025
getopt had an issue when parsing optional arguments if
they had a space between the opion and its value.

Signed-off-by: Simon Wollwage <[email protected]>

PR:		291374
@Kintaro
Copy link
Author

Kintaro commented Dec 9, 2025

Thank you!

The correct trailer should be:

PR:<tab><tab>291374

Updated the commit message. Should hopefully be good to go now.

@Kintaro
Copy link
Author

Kintaro commented Dec 10, 2025

There is currently some discussion about the wording of the manpage and it's interpretation on the NetBSD mailiing list. See https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59828

@jlduran
Copy link
Member

jlduran commented Dec 11, 2025

There is currently some discussion about the wording of the manpage and it's interpretation on the NetBSD mailiing list. See https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59828

Thank you @Kintaro for opening a bug report "upstream" (NetBSD)!

My personal opinion is that this patch is correct, and this should be the right behavior. However, if "upstream" decides that a documentation fix is a better fix (similar to the wording found on OpenBSD's manual page), then I will rescind this change, and will kindly ask you to do the same for this pull request.

Thanks again!

@Kintaro
Copy link
Author

Kintaro commented Dec 11, 2025

Sure, that sounds reasonable. If upstream decides to only adjust the manpage, then I can update this PR to change it to a documentation change.

I think UX wise the whitespace should be allowed, but if upstream and OpenBSD don't allow it, I can see the consistency in that regard too.

@jlduran
Copy link
Member

jlduran commented Dec 11, 2025

I forgot to mention, that I base my opinion in the fact that it is indeed a GNU extension, as seen from the original commit:

NetBSD/src@78d38aa

If that is decided, then it should behave like GNU getopt(3), and the test for -d 14 should return ch is d optarg is NULL, and continue:

$ ./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 # XXX <- NULL, not 14
ch is s optarg is /usr/local/admin/tftproot
ch is u optarg is admin
ch is U optarg is 0

Two colons mean an option takes an optional arg; if there is text in the current argv-element (i.e., in the same word as the option name itself, for example, "-oarg"), then it is returned in optarg, otherwise optarg is set to zero. This is a GNU extension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Seems ready to go, subject to final sanity check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants