Skip to content

Commit 411b720

Browse files
Teach in-tree getopt_long() to move non-options to the end of argv.
Unlike the other implementations of getopt_long() I could find, the in-tree implementation does not reorder non-options to the end of argv. Instead, it returns -1 as soon as the first non-option is found, even if there are other options listed afterwards. By moving non-options to the end of argv, getopt_long() can parse all specified options and return -1 when only non-options remain. This quirk is periodically missed by hackers (e.g., 869aa40, ffd3980, and d9ddc50). This commit introduces the aforementioned non-option reordering behavior to the in-tree getopt_long() implementation. Special thanks to Noah Misch for his help verifying behavior on AIX. Reviewed-by: Kyotaro Horiguchi, Michael Paquier Discussion: https://postgr.es/m/20230609232257.GA121461%40nathanxps13
1 parent b6e1157 commit 411b720

File tree

2 files changed

+44
-19
lines changed

2 files changed

+44
-19
lines changed

src/bin/scripts/t/040_createuser.pl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@
4242
'add a role as a member with admin option of the newly created role');
4343
$node->issues_sql_like(
4444
[
45-
'createuser', '-m',
46-
'regress_user3', '-m',
47-
'regress user #4', 'REGRESS_USER5'
45+
'createuser', 'REGRESS_USER5', '-m', 'regress_user3',
46+
'-m', 'regress user #4'
4847
],
4948
qr/statement: CREATE ROLE "REGRESS_USER5" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS ROLE regress_user3,"regress user #4";/,
5049
'add a role as a member of the newly created role');
@@ -73,11 +72,14 @@
7372
qr/statement: CREATE ROLE regress_user11 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS IN ROLE regress_user1;/,
7473
'--role');
7574
$node->issues_sql_like(
76-
[ 'createuser', '--member-of', 'regress_user1', 'regress_user12' ],
75+
[ 'createuser', 'regress_user12', '--member-of', 'regress_user1' ],
7776
qr/statement: CREATE ROLE regress_user12 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS IN ROLE regress_user1;/,
7877
'--member-of');
7978

8079
$node->command_fails([ 'createuser', 'regress_user1' ],
8180
'fails if role already exists');
81+
$node->command_fails(
82+
[ 'createuser', 'regress_user1', '-m', 'regress_user2', 'regress_user3' ],
83+
'fails for too many non-options');
8284

8385
done_testing();

src/port/getopt_long.c

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@
5050
* This implementation does not use optreset. Instead, we guarantee that
5151
* it can be restarted on a new argv array after a previous call returned -1,
5252
* if the caller resets optind to 1 before the first call of the new series.
53-
* (Internally, this means we must be sure to reset "place" to EMSG before
54-
* returning -1.)
53+
* (Internally, this means we must be sure to reset "place" to EMSG,
54+
* "nonopt_start" to -1, and "force_nonopt" to false before returning -1.)
55+
*
56+
* Note that this routine reorders the pointers in argv (despite the const
57+
* qualifier) so that all non-options will be at the end when -1 is returned.
5558
*/
5659
int
5760
getopt_long(int argc, char *const argv[],
@@ -60,38 +63,58 @@ getopt_long(int argc, char *const argv[],
6063
{
6164
static char *place = EMSG; /* option letter processing */
6265
char *oli; /* option letter list index */
66+
static int nonopt_start = -1;
67+
static bool force_nonopt = false;
6368

6469
if (!*place)
6570
{ /* update scanning pointer */
66-
if (optind >= argc)
71+
char **args = (char **) argv;
72+
73+
retry:
74+
75+
/*
76+
* If we are out of arguments or only non-options remain, return -1.
77+
*/
78+
if (optind >= argc || optind == nonopt_start)
6779
{
6880
place = EMSG;
81+
nonopt_start = -1;
82+
force_nonopt = false;
6983
return -1;
7084
}
7185

7286
place = argv[optind];
7387

74-
if (place[0] != '-')
88+
/*
89+
* An argument is a non-option if it meets any of the following
90+
* criteria: it follows an argument that is equivalent to the string
91+
* "--", it does not start with '-', or it is equivalent to the string
92+
* "-". When we encounter a non-option, we move it to the end of argv
93+
* (after shifting all remaining arguments over to make room), and
94+
* then we try again with the next argument.
95+
*/
96+
if (force_nonopt || place[0] != '-' || place[1] == '\0')
7597
{
76-
place = EMSG;
77-
return -1;
78-
}
98+
for (int i = optind; i < argc - 1; i++)
99+
args[i] = args[i + 1];
100+
args[argc - 1] = place;
79101

80-
place++;
102+
if (nonopt_start == -1)
103+
nonopt_start = argc - 1;
104+
else
105+
nonopt_start--;
81106

82-
if (!*place)
83-
{
84-
/* treat "-" as not being an option */
85-
place = EMSG;
86-
return -1;
107+
goto retry;
87108
}
88109

110+
place++;
111+
89112
if (place[0] == '-' && place[1] == '\0')
90113
{
91114
/* found "--", treat it as end of options */
92115
++optind;
93-
place = EMSG;
94-
return -1;
116+
force_nonopt = true;
117+
goto retry;
95118
}
96119

97120
if (place[0] == '-' && place[1])

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy