Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1x2bvS-005rr9-2F for pgsql-hackers@arkaria.postgresql.org; Fri, 04 Sep 2026 21:58:22 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1x2bvQ-0098yN-1I for pgsql-hackers@arkaria.postgresql.org; Fri, 04 Sep 2026 21:58:20 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1x2bvQ-0098xy-0D for pgsql-hackers@lists.postgresql.org; Fri, 04 Sep 2026 21:58:20 +0000 Received: from mail-ot1-x332.google.com ([2607:f8b0:4864:20::332]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1x2bvN-00000002ynh-1Krk for pgsql-hackers@postgresql.org; Fri, 04 Sep 2026 21:58:19 +0000 Received: by mail-ot1-x332.google.com with SMTP id 46e09a7af769-7f4df360cc9so1846353a34.1 for ; Fri, 04 Sep 2026 14:58:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788559094; x=1789163894; darn=postgresql.org; h=in-reply-to:content-disposition:content-type:mime-version :references:message-id:subject:cc:to:from:date:from:to:cc:subject :date:message-id:reply-to:content-type; bh=dz+ZucqPD4gP1/NWDy35EXq76pqBoc/JVaCllCdUJl0=; b=V6tZKz4SNljlzMjGA/eli0s6QsU7v5LRz5N7zQRXQ5WqLptd85dH4Idl4olT51vfNU CbWIy8ETzxsfXUHaFs/Q8NhwOfQqfkzLuY5iDeNIvvXv8BUfsTzjxUj3LT+wR9EmPGgs kuZdhh1+HRXQw0ymbG2gKaiNBSRXBLLrnh5hWYO3uesy0PdrYE4wf8kidDNu8hPxSaE9 cyha7RyInH0/ExQPflF4kdd1kRveOcTptnIuHwOs9Uy/JlfZb2LeIyzinXYVSM3Kp0Mz uycjjM99ASwGZ+D1sxQkvpDp7eVzgiweMcaPzLXP84xuGtDt2dgLa38MTy46C5cRt20j GSfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788559094; x=1789163894; h=in-reply-to:content-disposition:content-type:mime-version :references:message-id:subject:cc:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=dz+ZucqPD4gP1/NWDy35EXq76pqBoc/JVaCllCdUJl0=; b=XTNleqAbdV3bgDPgbxQ0Ho2WZIOqlufXeNKg0N3Rsl8tu+JrDPVSsAkUATTkdx45hu IziWz1Gn5SnIALE305PVcd7qTvkDkR+RdWVCsVsdSKL4gnTr4gVhjqmub9IBssFievpK I3Ze3dwEJMRI8EBGCz4Wlj2eh97JarEkzKT1gikKB/mEfC/1Jszr3FsPaUt0nievWR8I OlkFqhT0yNClXtBmQzALv4hw6vgDNXvUhD2MHv8DdwHeYOrBsWVHMJEqLPFvYT1TBFWJ JE5f23V0OORQodhItuc528CWUXDzed7JL9nNOI9EAF0bJAFGkEW8Avvb50VpYQff7toX 70qg== X-Gm-Message-State: AFuF++klS7ZRNVWEyqOmRokAr6zQ92Y/ik0bTMpP5NqRUSD5f2Y57Yf8 b0BdUyvFgPVV9WE9ylUZLkkEFxrIIkKfLx79yJ0gFOyg6UukMGLpjRQK X-Gm-Gg: AYBFou1FQix5S6/86rzmLhgMno5nSrnNK5I6CyYZ+HFNW0iabn4DyZdJAUGwxkZBZJk Optm0sVAkgRPm2shjlEVCNxzc/o08qaQuiGzxSyTnVNIH5MNZp+oQMnp1tuu43QMmyY+jwkWxOe k7y/3dg+3kMkUiHdFwfgeVl+OLWoCHbqw7jvyaqRqe2vMnepBgQpfsNF5mrp0CzDPDRFkCUpWhM l/w0gfE1RBdD4RYQhXY3ODh1KaBKXgOzkl4dMC4P3p8JuAOpmTPXdy76ARZHsrmtZ2EZk79FTHZ UvojH9WT8lVBzOQpNz05vxKgFLc6gFMKCo5yj7hJVP65fJlIHUMRKQb20koirDmV++vn2J9eKvN Y+QOQon1KHvuwWIh0WBaufeYfoHTjMubBy9+dvowntkzPW5qTDY9FwCwEBtggZa3ewkeMV3hc/N 9IKKgCrCyIRV1Ay8eHe2WaObcSFkYfeKLGg7kOToHfsYubHc/MF2HODU8AiqDS3+FLcnzEmPAYk Qx53kBSlsEzO4LZgq39jFHMNfK2V1sfkcvHu3+XJXJ1IwpQ4jQhV+/5tiiZohs5N2FPx+YJl8eb 3oYypO37tfqss6U= X-Received: by 2002:a05:6830:448e:b0:7e6:d7b3:76f4 with SMTP id 46e09a7af769-7fa347b90cbmr3530412a34.0.1788559093693; Fri, 04 Sep 2026 14:58:13 -0700 (PDT) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7f9f7bf0086sm4524833a34.27.2026.09.04.14.58.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 14:58:12 -0700 (PDT) Date: Fri, 4 Sep 2026 16:58:11 -0500 From: Nathan Bossart To: Sehrope Sarkuni Cc: Pg Hackers Subject: Re: [PATCH] Fix getopt_long() argument handling and add tests Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="Zv9Xg0nwk4TTIGFg" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Zv9Xg0nwk4TTIGFg Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Sat, Aug 29, 2026 at 12:57:00PM -0400, Sehrope Sarkuni wrote: > While reworking the pg_waldump TAP test [1] I ran "pg_waldump --stats > --limit 5" and it failed on Windows CI with 'could not locate WAL file > "5"'. For a long option with optional_argument and no "=", the port > getopt_long() increments optind in the missing-argument branch and > again at the end of the long option path, so the next option is > skipped and its value becomes a positional argument. The same branch > returns BADARG when optstring starts with a colon although nothing is > missing. > > [...] > > 0003 fixes a second problem the tests turned up. The port moves each > non-option to the end of argv as soon as it sees it, so by the time a > following option looks for its argument, a non-option that came before > it on the command line is already sitting after it and gets taken: > "pg_amcheck mydb --jobs" parses as --jobs=mydb rather than reporting > the missing argument. The system getopt_long() reports the missing > argument. The fix bounds the argument search by the start of the > moved block. 0004 adds the test cases for it, kept separate in case > 0003 is backpatched without the test module. Nice finds. I've attached what I have staged for commit. I've tried to simplify the diffs a bit, and I also got rid of the new test module in favor of adding cases to existing test files. I don't think we need to bother back-patching 0001; the bug went undiscovered for ~23 years. I can try back-patching 0002 to v17, though. -- nathan --Zv9Xg0nwk4TTIGFg Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0001-Fix-optional-argument-handling-in-in-tree-getopt_.patch From f04a179b4f24f79f54ce81c6060f45c86a803c34 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 4 Sep 2026 16:37:01 -0500 Subject: [PATCH v2 1/2] Fix optional-argument handling in in-tree getopt_long(). A long option with an optional argument that is given without "=" advances optind twice, so the following argument is skipped. For example, "pg_waldump --stats --limit 5" complains that it cannot locate WAL file "5". The same path also returns BADARG when optstring starts with a colon, even though nothing is missing. To fix, handle optional arguments before the missing-argument code, which then only needs to deal with required arguments. This is a bug fix and could be back-patched, but since this issue went unnoticed for 23 years, I'm not going to bother. Author: Sehrope Sarkuni Discussion: https://postgr.es/m/CAH7T-arxDuVCSkorO%3Dk7%2BM-_JV0JFzMpN_EtKMyD2K0RDqZ2OA%40mail.gmail.com --- src/bin/pg_waldump/t/001_basic.pl | 3 +-- src/port/getopt_long.c | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 8beac19eaff..7b33efc6299 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -340,11 +340,10 @@ sub test_pg_waldump my ($stdout, $stderr); my $result = IPC::Run::run [ - 'pg_waldump', + 'pg_waldump', @opts, '--start' => $startlsn, '--end' => $endlsn, '--path' => $path, - @opts ], '>' => \$stdout, '2>' => \$stderr; diff --git a/src/port/getopt_long.c b/src/port/getopt_long.c index 2e869fed58b..0a9a50189f1 100644 --- a/src/port/getopt_long.c +++ b/src/port/getopt_long.c @@ -137,8 +137,9 @@ retry: { if (place[namelen] == '=') optarg = place + namelen + 1; - else if (optind < argc - 1 && - has_arg == required_argument) + else if (has_arg == optional_argument) + optarg = NULL; + else if (optind < argc - 1) { optind++; optarg = argv[optind]; @@ -152,16 +153,14 @@ retry: return BADARG; } - if (opterr && has_arg == required_argument) + if (opterr) fprintf(stderr, "%s: option requires an argument -- %s\n", argv[0], place); place = EMSG; - if (has_arg == required_argument) - return BADCH; - optarg = NULL; + return BADCH; } } else -- 2.55.0 --Zv9Xg0nwk4TTIGFg Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-Fix-option-argument-lookup-in-in-tree-getopt_long.patch From b0fcd1a01b01e9e15d76dcadfae13f33f9ab8bb1 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 4 Sep 2026 16:47:53 -0500 Subject: [PATCH v2 2/2] Fix option argument lookup in in-tree getopt_long(). The in-tree getopt_long() moves each non-option to the end of argv as soon as it finds one, which puts a non-option that preceded an option right where the option's argument lookup expects to find it. For example, "vacuumdb postgres --jobs" takes "postgres" as the number of jobs instead of complaining that --jobs is missing its argument. To fix, stop the argument lookups at the start of the moved non-options, which we already track to know when to stop scanning. Oversight in commit 411b720343. Author: Sehrope Sarkuni Discussion: https://postgr.es/m/CAH7T-arxDuVCSkorO%3Dk7%2BM-_JV0JFzMpN_EtKMyD2K0RDqZ2OA%40mail.gmail.com Backpatch-through: 17 --- src/bin/scripts/t/100_vacuumdb.pl | 4 ++++ src/port/getopt_long.c | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl index 7c4e35a6717..b78fa2a38de 100644 --- a/src/bin/scripts/t/100_vacuumdb.pl +++ b/src/bin/scripts/t/100_vacuumdb.pl @@ -240,6 +240,10 @@ $node->command_fails_like( [ 'vacuumdb', '--all', 'postgres' ], qr/cannot vacuum all databases and a specific one at the same time/, 'cannot use option --all and a dbname as argument at the same time'); +$node->command_fails_like( + [ 'vacuumdb', 'postgres', '--jobs' ], + qr/requires an argument/, + 'option missing its argument after a non-option'); $node->safe_psql( 'postgres', q| diff --git a/src/port/getopt_long.c b/src/port/getopt_long.c index 0a9a50189f1..f2edadb59d5 100644 --- a/src/port/getopt_long.c +++ b/src/port/getopt_long.c @@ -66,6 +66,9 @@ getopt_long(int argc, char *const argv[], static int nonopt_start = -1; static bool force_nonopt = false; + if (nonopt_start == -1) + nonopt_start = argc; + if (!*place) { /* update scanning pointer */ char **args = (char **) argv; @@ -75,7 +78,7 @@ retry: /* * If we are out of arguments or only non-options remain, return -1. */ - if (optind >= argc || optind == nonopt_start) + if (optind >= nonopt_start) { place = EMSG; nonopt_start = -1; @@ -99,10 +102,7 @@ retry: args[i] = args[i + 1]; args[argc - 1] = place; - if (nonopt_start == -1) - nonopt_start = argc - 1; - else - nonopt_start--; + nonopt_start--; goto retry; } @@ -139,7 +139,7 @@ retry: optarg = place + namelen + 1; else if (has_arg == optional_argument) optarg = NULL; - else if (optind < argc - 1) + else if (optind < nonopt_start - 1) { optind++; optarg = argv[optind]; @@ -222,7 +222,7 @@ retry: { /* need an argument */ if (*place) /* no white space */ optarg = place; - else if (argc <= ++optind) + else if (nonopt_start <= ++optind) { /* no arg */ place = EMSG; if (*optstring == ':') -- 2.55.0 --Zv9Xg0nwk4TTIGFg--