aboutsummaryrefslogtreecommitdiff
path: root/posix
diff options
context:
space:
mode:
Diffstat (limited to 'posix')
-rw-r--r--posix/Makefile5
-rw-r--r--posix/getopt.c362
-rw-r--r--posix/tst-getopt-cancel.c284
3 files changed, 342 insertions, 309 deletions
diff --git a/posix/Makefile b/posix/Makefile
index efcbeff..8e29eea 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -97,6 +97,9 @@ ifeq (yes,$(build-shared))
test-srcs := globtest
tests += wordexp-test tst-exec tst-spawn tst-spawn2 tst-spawn3
endif
+ifeq (yesyes,$(build-shared)$(have-thread-library))
+tests += tst-getopt-cancel
+endif
tests-static = tst-exec-static tst-spawn-static
tests += $(tests-static)
others := getconf
@@ -254,6 +257,8 @@ ptestcases.h: PTESTS PTESTS2C.sed
LC_ALL=C sed -f PTESTS2C.sed < $< > $@T
mv -f $@T $@
+$(objpfx)tst-getopt-cancel: $(shared-thread-library)
+
test-xfail-annexc = yes
$(objpfx)annexc.out: $(objpfx)annexc
$(dir $<)$(notdir $<) '$(CC)' \
diff --git a/posix/getopt.c b/posix/getopt.c
index e616aa6..248fe5b 100644
--- a/posix/getopt.c
+++ b/posix/getopt.c
@@ -27,14 +27,30 @@
#include <stdio.h>
#include <stdlib.h>
-#include <unistd.h>
#include <string.h>
+#include <unistd.h>
#ifdef _LIBC
+/* When used as part of glibc, error printing must be done differently
+ for standards compliance. getopt is not a cancellation point, so
+ it must not call functions that are, and it is specified by an
+ older standard than stdio locking, so it must not refer to
+ functions in the "user namespace" related to stdio locking.
+ Finally, it must use glibc's internal message translation so that
+ the messages are looked up in the proper text domain. */
# include <libintl.h>
+# define fprintf __fxprintf_nocancel
+# define flockfile(fp) _IO_flockfile (fp)
+# define funlockfile(fp) _IO_funlockfile (fp)
#else
# include "gettext.h"
# define _(msgid) gettext (msgid)
+/* When used standalone, flockfile and funlockfile might not be
+ available. */
+# ifndef _POSIX_THREAD_SAFE_FUNCTIONS
+# define flockfile(fp) /* nop */
+# define funlockfile(fp) /* nop */
+# endif
#endif
/* This implementation of 'getopt' has three modes for handling
@@ -98,7 +114,6 @@ int optopt = '?';
/* Keep a global copy of all internal members of getopt_data. */
static struct _getopt_data getopt_data;
-
/* Exchange two adjacent subsequences of ARGV.
One subsequence is elements [first_nonopt,last_nonopt)
@@ -271,7 +286,7 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
if (d->optind == 0 || !d->__initialized)
{
if (d->optind == 0)
- d->optind = 1; /* Don't scan ARGV[0], the program name. */
+ d->optind = 1; /* Don't scan ARGV[0], the program name. */
optstring = _getopt_initialize (argc, argv, optstring, d,
posixly_correct);
d->__initialized = 1;
@@ -441,42 +456,8 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
first.next = ambig_list;
ambig_list = &first;
-#if defined _LIBC
- char *buf = NULL;
- size_t buflen = 0;
-
- FILE *fp = __open_memstream (&buf, &buflen);
- if (fp != NULL)
- {
- fprintf (fp,
- _("%s: option '%s' is ambiguous; possibilities:"),
- argv[0], argv[d->optind]);
-
- do
- {
- fprintf (fp, " '--%s'", ambig_list->p->name);
- ambig_list = ambig_list->next;
- }
- while (ambig_list != NULL);
-
- fputc_unlocked ('\n', fp);
-
- if (__glibc_likely (fclose (fp) != EOF))
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+ flockfile (stderr);
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
- }
-#else
fprintf (stderr,
_("%s: option '%s' is ambiguous; possibilities:"),
argv[0], argv[d->optind]);
@@ -487,8 +468,11 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
}
while (ambig_list != NULL);
- fputc ('\n', stderr);
-#endif
+ /* This must use 'fprintf' even though it's only printing a
+ single character, so that it goes through __fxprintf_nocancel
+ when compiled as part of glibc. */
+ fprintf (stderr, "\n");
+ funlockfile (stderr);
}
d->__nextchar += strlen (d->__nextchar);
d->optind++;
@@ -510,57 +494,17 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
{
if (print_errors)
{
-#if defined _LIBC
- char *buf;
- int n;
-#endif
-
if (argv[d->optind - 1][1] == '-')
- {
- /* --option */
-#if defined _LIBC
- n = __asprintf (&buf, _("\
-%s: option '--%s' doesn't allow an argument\n"),
- argv[0], pfound->name);
-#else
- fprintf (stderr, _("\
+ /* --option */
+ fprintf (stderr, _("\
%s: option '--%s' doesn't allow an argument\n"),
- argv[0], pfound->name);
-#endif
- }
+ argv[0], pfound->name);
else
- {
- /* +option or -option */
-#if defined _LIBC
- n = __asprintf (&buf, _("\
-%s: option '%c%s' doesn't allow an argument\n"),
- argv[0], argv[d->optind - 1][0],
- pfound->name);
-#else
- fprintf (stderr, _("\
+ /* +option or -option */
+ fprintf (stderr, _("\
%s: option '%c%s' doesn't allow an argument\n"),
- argv[0], argv[d->optind - 1][0],
- pfound->name);
-#endif
- }
-
-#if defined _LIBC
- if (n >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2
- |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
-#endif
+ argv[0], argv[d->optind - 1][0],
+ pfound->name);
}
d->__nextchar += strlen (d->__nextchar);
@@ -576,33 +520,10 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
else
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
-
- if (__asprintf (&buf, _("\
-%s: option '--%s' requires an argument\n"),
- argv[0], pfound->name) >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2
- |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
+ fprintf (stderr,
+ _("%s: option '--%s' requires an argument\n"),
+ argv[0], pfound->name);
- free (buf);
- }
-#else
- fprintf (stderr,
- _("%s: option '--%s' requires an argument\n"),
- argv[0], pfound->name);
-#endif
- }
d->__nextchar += strlen (d->__nextchar);
d->optopt = pfound->val;
return optstring[0] == ':' ? ':' : '?';
@@ -628,50 +549,14 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
{
if (print_errors)
{
-#if defined _LIBC
- char *buf;
- int n;
-#endif
-
if (argv[d->optind][1] == '-')
- {
- /* --option */
-#if defined _LIBC
- n = __asprintf (&buf, _("%s: unrecognized option '--%s'\n"),
- argv[0], d->__nextchar);
-#else
- fprintf (stderr, _("%s: unrecognized option '--%s'\n"),
- argv[0], d->__nextchar);
-#endif
- }
+ /* --option */
+ fprintf (stderr, _("%s: unrecognized option '--%s'\n"),
+ argv[0], d->__nextchar);
else
- {
- /* +option or -option */
-#if defined _LIBC
- n = __asprintf (&buf, _("%s: unrecognized option '%c%s'\n"),
- argv[0], argv[d->optind][0], d->__nextchar);
-#else
- fprintf (stderr, _("%s: unrecognized option '%c%s'\n"),
- argv[0], argv[d->optind][0], d->__nextchar);
-#endif
- }
-
-#if defined _LIBC
- if (n >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
-#endif
+ /* +option or -option */
+ fprintf (stderr, _("%s: unrecognized option '%c%s'\n"),
+ argv[0], argv[d->optind][0], d->__nextchar);
}
d->__nextchar = (char *) "";
d->optind++;
@@ -693,36 +578,7 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
if (temp == NULL || c == ':' || c == ';')
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
- int n;
-#endif
-
-#if defined _LIBC
- n = __asprintf (&buf, _("%s: invalid option -- '%c'\n"),
- argv[0], c);
-#else
- fprintf (stderr, _("%s: invalid option -- '%c'\n"), argv[0], c);
-#endif
-
-#if defined _LIBC
- if (n >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
-#endif
- }
+ fprintf (stderr, _("%s: invalid option -- '%c'\n"), argv[0], c);
d->optopt = c;
return '?';
}
@@ -751,32 +607,10 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
else if (d->optind == argc)
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
-
- if (__asprintf (&buf,
- _("%s: option requires an argument -- '%c'\n"),
- argv[0], c) >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
+ fprintf (stderr,
+ _("%s: option requires an argument -- '%c'\n"),
+ argv[0], c);
- free (buf);
- }
-#else
- fprintf (stderr,
- _("%s: option requires an argument -- '%c'\n"),
- argv[0], c);
-#endif
- }
d->optopt = c;
if (optstring[0] == ':')
c = ':';
@@ -825,30 +659,9 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
if (ambig && !exact)
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
-
- if (__asprintf (&buf, _("%s: option '-W %s' is ambiguous\n"),
- argv[0], d->optarg) >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+ fprintf (stderr, _("%s: option '-W %s' is ambiguous\n"),
+ argv[0], d->optarg);
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
-#else
- fprintf (stderr, _("%s: option '-W %s' is ambiguous\n"),
- argv[0], d->optarg);
-#endif
- }
d->__nextchar += strlen (d->__nextchar);
return '?';
}
@@ -864,33 +677,9 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
else
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
-
- if (__asprintf (&buf, _("\
-%s: option '-W %s' doesn't allow an argument\n"),
- argv[0], pfound->name) >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2
- |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
-#else
- fprintf (stderr, _("\
+ fprintf (stderr, _("\
%s: option '-W %s' doesn't allow an argument\n"),
- argv[0], pfound->name);
-#endif
- }
+ argv[0], pfound->name);
d->__nextchar += strlen (d->__nextchar);
return '?';
@@ -903,33 +692,10 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
else
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
-
- if (__asprintf (&buf, _("\
+ fprintf (stderr, _("\
%s: option '-W %s' requires an argument\n"),
- argv[0], pfound->name) >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2
- |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
+ argv[0], pfound->name);
- free (buf);
- }
-#else
- fprintf (stderr, _("\
-%s: option '-W %s' requires an argument\n"),
- argv[0], pfound->name);
-#endif
- }
d->__nextchar += strlen (d->__nextchar);
return optstring[0] == ':' ? ':' : '?';
}
@@ -978,32 +744,10 @@ _getopt_internal_r (int argc, char *const *argv, const char *optstring,
else if (d->optind == argc)
{
if (print_errors)
- {
-#if defined _LIBC
- char *buf;
+ fprintf (stderr,
+ _("%s: option requires an argument -- '%c'\n"),
+ argv[0], c);
- if (__asprintf (&buf, _("\
-%s: option requires an argument -- '%c'\n"),
- argv[0], c) >= 0)
- {
- _IO_flockfile (stderr);
-
- int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
- ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
-
- __fxprintf (NULL, "%s", buf);
-
- ((_IO_FILE *) stderr)->_flags2 = old_flags2;
- _IO_funlockfile (stderr);
-
- free (buf);
- }
-#else
- fprintf (stderr,
- _("%s: option requires an argument -- '%c'\n"),
- argv[0], c);
-#endif
- }
d->optopt = c;
if (optstring[0] == ':')
c = ':';
diff --git a/posix/tst-getopt-cancel.c b/posix/tst-getopt-cancel.c
new file mode 100644
index 0000000..594596a
--- /dev/null
+++ b/posix/tst-getopt-cancel.c
@@ -0,0 +1,284 @@
+/* Copyright (C) 2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+/* fprintf is a cancellation point, but getopt is not supposed to be a
+ cancellation point, even when it prints error messages. */
+
+/* Note: getopt.h must be included first in this file, so we get the
+ GNU getopt rather than the POSIX one. */
+#include <getopt.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/xthread.h>
+
+static bool
+check_stderr (bool expect_errmsg, FILE *stderr_trapped)
+{
+ static char *lineptr = 0;
+ static size_t linesz = 0;
+
+ bool got_errmsg = false;
+ rewind (stderr_trapped);
+ while (getline (&lineptr, &linesz, stderr_trapped) > 0)
+ {
+ got_errmsg = true;
+ fputs (lineptr, stdout);
+ }
+ rewind (stderr_trapped);
+ ftruncate (fileno (stderr_trapped), 0);
+ return got_errmsg == expect_errmsg;
+}
+
+struct test_short
+{
+ const char *label;
+ const char *opts;
+ const char *const argv[8];
+ int argc;
+ bool expect_errmsg;
+};
+
+struct test_long
+{
+ const char *label;
+ const char *opts;
+ const struct option longopts[4];
+ const char *const argv[8];
+ int argc;
+ bool expect_errmsg;
+};
+
+#define DEFINE_TEST_DRIVER(test_type, getopt_call) \
+ struct test_type##_tdata \
+ { \
+ pthread_mutex_t *sync; \
+ const struct test_type *tcase; \
+ bool ok; \
+ }; \
+ \
+ static void * \
+ test_type##_threadproc (void *data) \
+ { \
+ struct test_type##_tdata *tdata = data; \
+ const struct test_type *tc = tdata->tcase; \
+ \
+ xpthread_mutex_lock (tdata->sync); \
+ xpthread_mutex_unlock (tdata->sync); \
+ \
+ /* At this point, this thread has a cancellation pending. \
+ We should still be able to get all the way through a getopt \
+ loop without being cancelled. \
+ Setting optind to 0 forces getopt to reinitialize itself. */ \
+ optind = 0; \
+ opterr = 1; \
+ optopt = 0; \
+ while (getopt_call != -1) \
+ ; \
+ tdata->ok = true; \
+ \
+ pthread_testcancel(); \
+ return 0; \
+ } \
+ \
+ static bool \
+ do_##test_type (const struct test_type *tcase, FILE *stderr_trapped) \
+ { \
+ pthread_mutex_t sync; \
+ struct test_type##_tdata tdata; \
+ \
+ printf("begin: %s\n", tcase->label); \
+ \
+ xpthread_mutex_init (&sync, 0); \
+ xpthread_mutex_lock (&sync); \
+ \
+ tdata.sync = &sync; \
+ tdata.tcase = tcase; \
+ tdata.ok = false; \
+ \
+ pthread_t thr = xpthread_create (0, test_type##_threadproc, \
+ (void *)&tdata); \
+ xpthread_cancel (thr); \
+ xpthread_mutex_unlock (&sync); \
+ void *rv = xpthread_join (thr); \
+ \
+ xpthread_mutex_destroy (&sync); \
+ \
+ bool ok = true; \
+ if (!check_stderr (tcase->expect_errmsg, stderr_trapped)) \
+ { \
+ ok = false; \
+ printf("FAIL: %s: stderr not as expected\n", tcase->label); \
+ } \
+ if (!tdata.ok) \
+ { \
+ ok = false; \
+ printf("FAIL: %s: did not complete loop\n", tcase->label); \
+ } \
+ if (rv != PTHREAD_CANCELED) \
+ { \
+ ok = false; \
+ printf("FAIL: %s: thread was not cancelled\n", tcase->label); \
+ } \
+ if (ok) \
+ printf ("pass: %s\n", tcase->label); \
+ return ok; \
+ }
+
+DEFINE_TEST_DRIVER (test_short,
+ getopt (tc->argc, (char *const *)tc->argv, tc->opts))
+DEFINE_TEST_DRIVER (test_long,
+ getopt_long (tc->argc, (char *const *)tc->argv,
+ tc->opts, tc->longopts, 0))
+
+/* Caution: all option strings must begin with a '+' or '-' so that
+ getopt does not attempt to permute the argument vector (which is in
+ read-only memory). */
+const struct test_short tests_short[] = {
+ { "no errors",
+ "+ab:c", { "program", "-ac", "-b", "x", 0 }, 4, false },
+ { "invalid option",
+ "+ab:c", { "program", "-d", 0 }, 2, true },
+ { "missing argument",
+ "+ab:c", { "program", "-b", 0 }, 2, true },
+ { 0 }
+};
+
+const struct test_long tests_long[] = {
+ { "no errors (long)",
+ "+ab:c", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "--charlie", "--bravo=x", 0 }, 4, false },
+
+ { "invalid option (long)",
+ "+ab:c", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "--charlie", "--dingo", 0 }, 4, true },
+
+ { "unwanted argument",
+ "+ab:c", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "--charlie=dingo", "--bravo=x", 0 }, 4, true },
+
+ { "missing argument",
+ "+ab:c", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "--charlie", "--bravo", 0 }, 4, true },
+
+ { "ambiguous options",
+ "+uvw", { { "veni", no_argument, 0, 'u' },
+ { "vedi", no_argument, 0, 'v' },
+ { "veci", no_argument, 0, 'w' } },
+ { "program", "--ve", 0 }, 2, true },
+
+ { "no errors (long W)",
+ "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "-W", "charlie", "-W", "bravo=x", 0 }, 6, false },
+
+ { "missing argument (W itself)",
+ "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "-W", "charlie", "-W", 0 }, 5, true },
+
+ { "missing argument (W longopt)",
+ "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "-W", "charlie", "-W", "bravo", 0 }, 6, true },
+
+ { "unwanted argument (W longopt)",
+ "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
+ { "bravo", required_argument, 0, 'b' },
+ { "charlie", no_argument, 0, 'c' },
+ { 0 } },
+ { "program", "-a", "-W", "charlie=dingo", "-W", "bravo=x", 0 }, 6, true },
+
+ { "ambiguous options (W)",
+ "+uvwW;", { { "veni", no_argument, 0, 'u' },
+ { "vedi", no_argument, 0, 'v' },
+ { "veci", no_argument, 0, 'w' } },
+ { "program", "-W", "ve", 0 }, 3, true },
+
+ { 0 }
+};
+
+static int
+do_test (void)
+{
+ int stderr_trap = create_temp_file ("stderr", 0);
+ if (stderr_trap < 0)
+ {
+ perror ("create_temp_file");
+ return 1;
+ }
+ FILE *stderr_trapped = fdopen(stderr_trap, "r+");
+ if (!stderr_trapped)
+ {
+ perror ("fdopen");
+ return 1;
+ }
+ int old_stderr = dup (fileno (stderr));
+ if (old_stderr < 0)
+ {
+ perror ("dup");
+ return 1;
+ }
+ if (dup2 (stderr_trap, 2) < 0)
+ {
+ perror ("dup2");
+ return 1;
+ }
+ rewind (stderr);
+
+ bool success = true;
+
+ for (const struct test_short *tcase = tests_short; tcase->label; tcase++)
+ success = do_test_short (tcase, stderr_trapped) && success;
+
+ for (const struct test_long *tcase = tests_long; tcase->label; tcase++)
+ success = do_test_long (tcase, stderr_trapped) && success;
+
+ dup2 (old_stderr, 2);
+ close (old_stderr);
+ fclose (stderr_trapped);
+
+ return success ? 0 : 1;
+}
+
+#include <support/test-driver.c>