2

i'm aiming the getopt() machinery at my own argv-like array, after having used it on the real argv. the interface is non-reentrant (holds state), and various implementations are reset in different ways.

  • in XPG3/SVID it's optreset = 1; (freebsd, macosx)
  • in XPG4/POSIX/SUS it's optind = 1; (suse tumbleweed)
  • in Linux/GLIBC it's also optind = 0; (debian)

i don't want to autoconf just for this.

what's a reliable set of #ifdef's?

Paul Vixie
  • 171
  • 1
  • 4
  • what's working at the moment is https://github.com/dnsdb/dnsdbq/commit/efa68c0499c3b5b4a1238318345e5e466a7fd99f – Paul Vixie Mar 02 '20 at 22:59

1 Answers1

0

The only standard thing is setting optind = 1.

The SUSv4 standard makes no mention of optreset and declares optind = 0 "unspecified":

If the application sets optind to zero before calling getopt(), the behavior is unspecified.

Otherwise, the standard it vague, and does not say anything at all about calling getopt() multiple times, neither allowing it nor declaring it undefined.

Also, there are no defines which say if optind = 0 or optreset are supported (fwiw the former is supported on OpenBSD, but not on FreeBSD, and the latter on Linux/musl, but not on Linux/glibc), so there can't be any reliable way to detect it via #ifdefs.

Weren't for bugs in fancy getopt/_long implementations (see below), just setting optind = 1 to restart getopt() should work fine as long as

a) you don't use any GNU extensions:

A program that scans multiple argument vectors, or rescans the same vector more than once, and wants to make use of GNU extensions such as + and - at the start of optstring, or changes the value of POSIXLY_CORRECT between scans, must reinitialize getopt() by resetting optind to 0,

b) you don't restart the getopt() in the middle, before it has returned -1. Eg. from FreeBSD's getopt:

#define BADCH   (int)'?'
#define BADARG  (int)':'
#define EMSG    ""
...
int
getopt(int nargc, char * const nargv[], const char *ostr)
{
        static char *place = EMSG;              /* option letter processing */
        ...
        if (optreset || *place == 0) {          /* update scanning pointer */
                optreset = 0;
                ...
                        place = EMSG;
                        return (-1);
                ...
                        place = EMSG;
                        return (-1);
                ...
                        place = EMSG;
                        if (strchr(ostr, '-') == NULL)
                                return (-1);
        ...
                return (BADCH);
        ...
                                return (BADARG);
        ...
                        return (BADCH);
        ...
        return (optopt);                        /* return option letter */
}

Bugs

The glibc implementation of getopt will cling to stale state even after returning -1. This may result in stupid bugs and crashes if the old strings are freed.

The OpenBSD implementation will do likewise, but only when a bogus - option is used, as in getopt("q", ["prog", "-q-", NULL]). Notice that OpenBSD's getopt_long implementation may have made its way as the default getopt() into other systems like Solaris and Android.

  • those approaches were not portable across systems were tested. what we found that happens to work everywhere we tried it is https://github.com/dnsdb/dnsdbq/commit/efa68c0499c3b5b4a1238318345e5e466a7fd99f – Paul Vixie Mar 02 '20 at 23:01
  • Nope, that's broken. `optreset` is not supported on Solaris. What system exactly did you try `optind = 1` on (with the conditions described in the answer) which did not work? –  Mar 03 '20 at 09:03
  • we installed a Solaris 11.4 VM, did a pkg install of git and cc and were then able to build using "CC=gcc make" and the changes shown in the github link above. – Paul Vixie Mar 04 '20 at 13:54
  • Great, maybe they have added `optreset` to very recent versions of Solaris. Or you were using some compat library. But, please, what EXACT system did you try "those approaches" (ie as described in my answer) on and did NOT work. Thanks. –  Mar 04 '20 at 14:07
  • mosvy, ty for your challenge. we're attempting to isolate a test case. (and no, there's no compat library in the solaris case.) – Paul Vixie Mar 08 '20 at 19:52
  • we have isolated a test case at [link](http://family.redbarn.org/~vixie/getopttest.c) which fails as follows on debian stretch, opensuse 15, and suse tumbleweed. `==2301== Invalid read of size 1 ==2301== at 0x4F08CAB: _getopt_internal_r (getopt.c:427) ==2301== by 0x4F09D00: _getopt_internal (getopt.c:1175) ==2301== by 0x4F09D42: getopt (getopt.c:1189) ==2301== by 0x108B92: getopttest` – Paul Vixie Mar 10 '20 at 00:18
  • @PaulVixie Thanks, I have reported it as a [bug in glibc](https://sourceware.org/bugzilla/show_bug.cgi?id=25658). That should teach me to trust the Linux manpages ;-) –  Mar 12 '20 at 05:25
  • ty, but can you ask them for a feature test as part of the fix, so that i can avoid setting optreset=1 via #ifdef once it is no longer nec'y? – Paul Vixie Mar 13 '20 at 07:49