diff options
Diffstat (limited to 'posix')
-rw-r--r-- | posix/Makefile | 5 | ||||
-rw-r--r-- | posix/getopt.c | 362 | ||||
-rw-r--r-- | posix/tst-getopt-cancel.c | 284 |
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> |