1 /* Copyright (C) 2017-2022 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18 /* fprintf is a cancellation point, but getopt is not supposed to be a
19 cancellation point, even when it prints error messages. */
20
21 /* Note: getopt.h must be included first in this file, so we get the
22 GNU getopt rather than the POSIX one. */
23 #include <getopt.h>
24
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 #include <fcntl.h>
30 #include <pthread.h>
31 #include <unistd.h>
32
33 #include <support/support.h>
34 #include <support/temp_file.h>
35 #include <support/xthread.h>
36
37 static bool
check_stderr(bool expect_errmsg,FILE * stderr_trapped)38 check_stderr (bool expect_errmsg, FILE *stderr_trapped)
39 {
40 static char *lineptr = 0;
41 static size_t linesz = 0;
42
43 bool got_errmsg = false;
44 rewind (stderr_trapped);
45 while (getline (&lineptr, &linesz, stderr_trapped) > 0)
46 {
47 got_errmsg = true;
48 fputs (lineptr, stdout);
49 }
50 rewind (stderr_trapped);
51 ftruncate (fileno (stderr_trapped), 0);
52 return got_errmsg == expect_errmsg;
53 }
54
55 struct test_short
56 {
57 const char *label;
58 const char *opts;
59 const char *const argv[8];
60 int argc;
61 bool expect_errmsg;
62 };
63
64 struct test_long
65 {
66 const char *label;
67 const char *opts;
68 const struct option longopts[4];
69 const char *const argv[8];
70 int argc;
71 bool expect_errmsg;
72 };
73
74 #define DEFINE_TEST_DRIVER(test_type, getopt_call) \
75 struct test_type##_tdata \
76 { \
77 pthread_mutex_t *sync; \
78 const struct test_type *tcase; \
79 bool ok; \
80 }; \
81 \
82 static void * \
83 test_type##_threadproc (void *data) \
84 { \
85 struct test_type##_tdata *tdata = data; \
86 const struct test_type *tc = tdata->tcase; \
87 \
88 xpthread_mutex_lock (tdata->sync); \
89 xpthread_mutex_unlock (tdata->sync); \
90 \
91 /* At this point, this thread has a cancellation pending. \
92 We should still be able to get all the way through a getopt \
93 loop without being cancelled. \
94 Setting optind to 0 forces getopt to reinitialize itself. */ \
95 optind = 0; \
96 opterr = 1; \
97 optopt = 0; \
98 while (getopt_call != -1) \
99 ; \
100 tdata->ok = true; \
101 \
102 pthread_testcancel(); \
103 return 0; \
104 } \
105 \
106 static bool \
107 do_##test_type (const struct test_type *tcase, FILE *stderr_trapped) \
108 { \
109 pthread_mutex_t sync; \
110 struct test_type##_tdata tdata; \
111 \
112 printf("begin: %s\n", tcase->label); \
113 \
114 xpthread_mutex_init (&sync, 0); \
115 xpthread_mutex_lock (&sync); \
116 \
117 tdata.sync = &sync; \
118 tdata.tcase = tcase; \
119 tdata.ok = false; \
120 \
121 pthread_t thr = xpthread_create (0, test_type##_threadproc, \
122 (void *)&tdata); \
123 xpthread_cancel (thr); \
124 xpthread_mutex_unlock (&sync); \
125 void *rv = xpthread_join (thr); \
126 \
127 xpthread_mutex_destroy (&sync); \
128 \
129 bool ok = true; \
130 if (!check_stderr (tcase->expect_errmsg, stderr_trapped)) \
131 { \
132 ok = false; \
133 printf("FAIL: %s: stderr not as expected\n", tcase->label); \
134 } \
135 if (!tdata.ok) \
136 { \
137 ok = false; \
138 printf("FAIL: %s: did not complete loop\n", tcase->label); \
139 } \
140 if (rv != PTHREAD_CANCELED) \
141 { \
142 ok = false; \
143 printf("FAIL: %s: thread was not cancelled\n", tcase->label); \
144 } \
145 if (ok) \
146 printf ("pass: %s\n", tcase->label); \
147 return ok; \
148 }
149
150 DEFINE_TEST_DRIVER (test_short,
151 getopt (tc->argc, (char *const *)tc->argv, tc->opts))
152 DEFINE_TEST_DRIVER (test_long,
153 getopt_long (tc->argc, (char *const *)tc->argv,
154 tc->opts, tc->longopts, 0))
155
156 /* Caution: all option strings must begin with a '+' or '-' so that
157 getopt does not attempt to permute the argument vector (which is in
158 read-only memory). */
159 const struct test_short tests_short[] = {
160 { "no errors",
161 "+ab:c", { "program", "-ac", "-b", "x", 0 }, 4, false },
162 { "invalid option",
163 "+ab:c", { "program", "-d", 0 }, 2, true },
164 { "missing argument",
165 "+ab:c", { "program", "-b", 0 }, 2, true },
166 { 0 }
167 };
168
169 const struct test_long tests_long[] = {
170 { "no errors (long)",
171 "+ab:c", { { "alpha", no_argument, 0, 'a' },
172 { "bravo", required_argument, 0, 'b' },
173 { "charlie", no_argument, 0, 'c' },
174 { 0 } },
175 { "program", "-a", "--charlie", "--bravo=x", 0 }, 4, false },
176
177 { "invalid option (long)",
178 "+ab:c", { { "alpha", no_argument, 0, 'a' },
179 { "bravo", required_argument, 0, 'b' },
180 { "charlie", no_argument, 0, 'c' },
181 { 0 } },
182 { "program", "-a", "--charlie", "--dingo", 0 }, 4, true },
183
184 { "unwanted argument",
185 "+ab:c", { { "alpha", no_argument, 0, 'a' },
186 { "bravo", required_argument, 0, 'b' },
187 { "charlie", no_argument, 0, 'c' },
188 { 0 } },
189 { "program", "-a", "--charlie=dingo", "--bravo=x", 0 }, 4, true },
190
191 { "missing argument",
192 "+ab:c", { { "alpha", no_argument, 0, 'a' },
193 { "bravo", required_argument, 0, 'b' },
194 { "charlie", no_argument, 0, 'c' },
195 { 0 } },
196 { "program", "-a", "--charlie", "--bravo", 0 }, 4, true },
197
198 { "ambiguous options",
199 "+uvw", { { "veni", no_argument, 0, 'u' },
200 { "vedi", no_argument, 0, 'v' },
201 { "veci", no_argument, 0, 'w' } },
202 { "program", "--ve", 0 }, 2, true },
203
204 { "no errors (long W)",
205 "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
206 { "bravo", required_argument, 0, 'b' },
207 { "charlie", no_argument, 0, 'c' },
208 { 0 } },
209 { "program", "-a", "-W", "charlie", "-W", "bravo=x", 0 }, 6, false },
210
211 { "missing argument (W itself)",
212 "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
213 { "bravo", required_argument, 0, 'b' },
214 { "charlie", no_argument, 0, 'c' },
215 { 0 } },
216 { "program", "-a", "-W", "charlie", "-W", 0 }, 5, true },
217
218 { "missing argument (W longopt)",
219 "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
220 { "bravo", required_argument, 0, 'b' },
221 { "charlie", no_argument, 0, 'c' },
222 { 0 } },
223 { "program", "-a", "-W", "charlie", "-W", "bravo", 0 }, 6, true },
224
225 { "unwanted argument (W longopt)",
226 "+ab:cW;", { { "alpha", no_argument, 0, 'a' },
227 { "bravo", required_argument, 0, 'b' },
228 { "charlie", no_argument, 0, 'c' },
229 { 0 } },
230 { "program", "-a", "-W", "charlie=dingo", "-W", "bravo=x", 0 }, 6, true },
231
232 { "ambiguous options (W)",
233 "+uvwW;", { { "veni", no_argument, 0, 'u' },
234 { "vedi", no_argument, 0, 'v' },
235 { "veci", no_argument, 0, 'w' } },
236 { "program", "-W", "ve", 0 }, 3, true },
237
238 { 0 }
239 };
240
241 static int
do_test(void)242 do_test (void)
243 {
244 int stderr_trap = create_temp_file ("stderr", 0);
245 if (stderr_trap < 0)
246 {
247 perror ("create_temp_file");
248 return 1;
249 }
250 FILE *stderr_trapped = fdopen(stderr_trap, "r+");
251 if (!stderr_trapped)
252 {
253 perror ("fdopen");
254 return 1;
255 }
256 int old_stderr = dup (fileno (stderr));
257 if (old_stderr < 0)
258 {
259 perror ("dup");
260 return 1;
261 }
262 if (dup2 (stderr_trap, 2) < 0)
263 {
264 perror ("dup2");
265 return 1;
266 }
267 rewind (stderr);
268
269 bool success = true;
270
271 for (const struct test_short *tcase = tests_short; tcase->label; tcase++)
272 success = do_test_short (tcase, stderr_trapped) && success;
273
274 for (const struct test_long *tcase = tests_long; tcase->label; tcase++)
275 success = do_test_long (tcase, stderr_trapped) && success;
276
277 dup2 (old_stderr, 2);
278 close (old_stderr);
279 fclose (stderr_trapped);
280
281 return success ? 0 : 1;
282 }
283
284 #include <support/test-driver.c>
285