Fix optind handling inconsistency in getopt_long() for missing argument

Started by Japin Li2 months ago6 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

won't retrytests failedCI history

This thread has been committed, so CI has stopped here. Anything below is the last result it produced.

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253139
psql -h localhost -U postgres

Built from patchset v4 (message #4), July 30, 2026 at 10:21 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253139_4 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253139_4 && git checkout t253139_4

Patchset v4 (message #4) is on t253139_4

Jump to latest
#1Japin Li
japinli@hotmail.com

Hi, hackers

I noticed an inconsistency in how optind is handled when a required argument
is missing.

For short options, the current code handles the BADARG case as follows:

else if (argc <= ++optind)
{ /* no arg */
place = EMSG;
if (*optstring == ':')
return BADARG;
if (opterr)
fprintf(stderr,
"%s: option requires an argument -- %c\n",
argv[0], optopt);
return BADCH;
}

Here, place is set to EMSG and optind is incremented before returning BADARG.
However, for long options, the same logic currently sets place = EMSG and
increments optind after returning BADARG.

else
{
if (optstring[0] == ':')
return BADARG; <-- here, return before increasing optind

if (opterr && has_arg == required_argument)
fprintf(stderr,
"%s: option requires an argument -- %s\n",
argv[0], place);

place = EMSG;
optind++;

if (has_arg == required_argument)
return BADCH;
optarg = NULL;
}

The attached patch moves the place = EMSG and optind++ assignments to the
beginning of the long‑option error block, aligning the behavior with that of
short options.

Any thought?

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

Attachments:

v1-0001-Fix-optind-handling-inconsistency-in-getopt_long-.patchtext/x-patchDownload+3-4
#2Michael Paquier
michael@paquier.xyz
In reply to: Japin Li (#1)
Re: Fix optind handling inconsistency in getopt_long() for missing argument

On Tue, Jul 21, 2026 at 06:16:27PM +0800, Japin Li wrote:

+  place = EMSG;
+  optind++;
+
if (optstring[0] == ':')
return BADARG;

@@ -153,9 +156,6 @@ retry:
"%s: option requires an argument -- %s\n",
argv[0], place);

- place = EMSG;
- optind++;
-
if (has_arg == required_argument)
return BADCH;
optarg = NULL;

This also means that for long options we have an error message would
now use an empty string instead of an option name all the time. That
looks incorrect.

You are claiming that this makes the short option path more
consistent. That's true based on what your patch does, but it also
looks to me like the short option area is already wrong in setting
EMSG before we issue the error string "option requires an argument".
So, to me, you are making the situation worse in some cases while
claiming that it improves the situation in some other cases.

Our implementation of getopt_long() is only used on Windows for MSVC,
as far as I know. I'd also suggest to post one or more examples of
how this changes the implementation behavior. You should be able to
force your way through easily so as the Postgres getopt_long()
implementation is used with a quick hack, just to prove your point,
saving you the pain of deploying a WIN32 host.. (Like use a
pg_getopt_long() and patch one of the binaries, whatever you prefer.)
--
Michael

#3Japin Li
japinli@hotmail.com
In reply to: Michael Paquier (#2)
Re: Fix optind handling inconsistency in getopt_long() for missing argument

Hi, Michael

Thanks for reviewing the patch.

<#secure method=pgpmime mode=sign>
On Wed, 22 Jul 2026 at 10:47, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Jul 21, 2026 at 06:16:27PM +0800, Japin Li wrote:

+  place = EMSG;
+  optind++;
+
if (optstring[0] == ':')
return BADARG;

@@ -153,9 +156,6 @@ retry:
"%s: option requires an argument -- %s\n",
argv[0], place);

- place = EMSG;
- optind++;
-
if (has_arg == required_argument)
return BADCH;
optarg = NULL;

This also means that for long options we have an error message would
now use an empty string instead of an option name all the time. That
looks incorrect.

Right. I was mistaken about the option name. Fixed in v2-0002.

You are claiming that this makes the short option path more
consistent. That's true based on what your patch does, but it also
looks to me like the short option area is already wrong in setting
EMSG before we issue the error string "option requires an argument".
So, to me, you are making the situation worse in some cases while
claiming that it improves the situation in some other cases.

Our implementation of getopt_long() is only used on Windows for MSVC,
as far as I know. I'd also suggest to post one or more examples of
how this changes the implementation behavior. You should be able to
force your way through easily so as the Postgres getopt_long()
implementation is used with a quick hack, just to prove your point,
saving you the pain of deploying a WIN32 host.. (Like use a
pg_getopt_long() and patch one of the binaries, whatever you prefer.)

Here's a mini‑demo for getopt_long() in v2‑0001. Without v2‑0002, the output is:

$ ./getopt_long_test --log-filename
option './getopt_long_test' requires an argument
unrecognized option './getopt_long_test'
unrecognized option './getopt_long_test'
unrecognized option './getopt_long_test'
unrecognized option './getopt_long_test'
$ ./getopt_long_test -f
option '-f' requires an argument

With v2‑0002 applied, the output is:

$ ./getopt_long_test --log-filename
option '--log-filename' requires an argument
$ ./getopt_long_test -f
option '-f' requires an argument

--
Michael

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

#4Japin Li
japinli@hotmail.com
In reply to: Japin Li (#3)
Re: Fix optind handling inconsistency in getopt_long() for missing argument

On Wed, 22 Jul 2026 at 12:17, Japin Li <japinli@hotmail.com> wrote:

Hi, Michael

Thanks for reviewing the patch.

<#secure method=pgpmime mode=sign>
On Wed, 22 Jul 2026 at 10:47, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Jul 21, 2026 at 06:16:27PM +0800, Japin Li wrote:

+  place = EMSG;
+  optind++;
+
if (optstring[0] == ':')
return BADARG;

@@ -153,9 +156,6 @@ retry:
"%s: option requires an argument -- %s\n",
argv[0], place);

- place = EMSG;
- optind++;
-
if (has_arg == required_argument)
return BADCH;
optarg = NULL;

This also means that for long options we have an error message would
now use an empty string instead of an option name all the time. That
looks incorrect.

Right. I was mistaken about the option name. Fixed in v2-0002.

You are claiming that this makes the short option path more
consistent. That's true based on what your patch does, but it also
looks to me like the short option area is already wrong in setting
EMSG before we issue the error string "option requires an argument".
So, to me, you are making the situation worse in some cases while
claiming that it improves the situation in some other cases.

Our implementation of getopt_long() is only used on Windows for MSVC,
as far as I know. I'd also suggest to post one or more examples of
how this changes the implementation behavior. You should be able to
force your way through easily so as the Postgres getopt_long()
implementation is used with a quick hack, just to prove your point,
saving you the pain of deploying a WIN32 host.. (Like use a
pg_getopt_long() and patch one of the binaries, whatever you prefer.)

Here's a mini‑demo for getopt_long() in v2‑0001. Without v2‑0002, the output is:

$ ./getopt_long_test --log-filename
option './getopt_long_test' requires an argument
unrecognized option './getopt_long_test'
unrecognized option './getopt_long_test'
unrecognized option './getopt_long_test'
unrecognized option './getopt_long_test'
$ ./getopt_long_test -f
option '-f' requires an argument

With v2‑0002 applied, the output is:

$ ./getopt_long_test --log-filename
option '--log-filename' requires an argument
$ ./getopt_long_test -f
option '-f' requires an argument

--
Michael

Sorry, I forgot to include the patches in my previous email.

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

Attachments:

t253139_4
v2-0001-Add-a-binary-to-test-getopt_long.patchtext/x-patchDownload+52-1
v2-0002-Fix-optind-handling-inconsistency-in-getopt_long-.patchtext/x-patchDownload+4-2
#5Michael Paquier
michael@paquier.xyz
In reply to: Japin Li (#4)
Re: Fix optind handling inconsistency in getopt_long() for missing argument

On Wed, Jul 22, 2026 at 12:22:06PM +0800, Japin Li wrote:

On Wed, 22 Jul 2026 at 12:17, Japin Li <japinli@hotmail.com> wrote:

With v2‑0002 applied, the output is:

$ ./getopt_long_test --log-filename
option '--log-filename' requires an argument
$ ./getopt_long_test -f
option '-f' requires an argument

Thanks, that saves time. I have been also checking how libc behaves,
debugging more information with optind, argv, optarg, opterr and
optopt with the short and long cases.

One side note: optopt still remains inconsistent compared to our port
version. Not sure if this is worth caring about.. Mentioning
in passing.

Sorry, I forgot to include the patches in my previous email.

No problem. That happens.

Adjusted on HEAD.
--
Michael

#6Japin Li
japinli@hotmail.com
In reply to: Michael Paquier (#5)
Re: Fix optind handling inconsistency in getopt_long() for missing argument

References: <SY7PR01MB10921AF81F18A8BCFA06388BEB6C22@SY7PR01MB10921.ausprd01.prod.outlook.com>
<amAhNvNM4I4ATevL@paquier.xyz>
<SY7PR01MB10921B103CBDC8A0128D156B5B6C12@SY7PR01MB10921.ausprd01.prod.outlook.com>
<SY7PR01MB109210E2A46D09049F98D185AB6C12@SY7PR01MB10921.ausprd01.prod.outlook.com>
<amwa9nvUdE5QneAD@paquier.xyz>
User-Agent: mu4e 1.14.1; emacs 30.2
Date: Fri, 31 Jul 2026 13:27:31 +0800

<#secure method=pgpmime mode=sign>
On Fri, 31 Jul 2026 at 12:48, Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Jul 22, 2026 at 12:22:06PM +0800, Japin Li wrote:

On Wed, 22 Jul 2026 at 12:17, Japin Li <japinli@hotmail.com> wrote:

With v2‑0002 applied, the output is:

$ ./getopt_long_test --log-filename
option '--log-filename' requires an argument
$ ./getopt_long_test -f
option '-f' requires an argument

Thanks, that saves time. I have been also checking how libc behaves,
debugging more information with optind, argv, optarg, opterr and
optopt with the short and long cases.

One side note: optopt still remains inconsistent compared to our port
version. Not sure if this is worth caring about.. Mentioning
in passing.

Sorry, I forgot to include the patches in my previous email.

No problem. That happens.

Adjusted on HEAD.

Thank you!

--
Michael

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.