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 |
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: &lineptr, n: &linesz, stream: stderr_trapped) > 0) |
46 | { |
47 | got_errmsg = true; |
48 | fputs (lineptr, stdout); |
49 | } |
50 | rewind (stderr_trapped); |
51 | ftruncate (fd: fileno (stderr_trapped), length: 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 |
242 | do_test (void) |
243 | { |
244 | int stderr_trap = create_temp_file (base: "stderr" , filename: 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 (fd: fileno (stderr)); |
257 | if (old_stderr < 0) |
258 | { |
259 | perror ("dup" ); |
260 | return 1; |
261 | } |
262 | if (dup2 (fd: stderr_trap, fd2: 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 (fd: old_stderr, fd2: 2); |
278 | close (fd: old_stderr); |
279 | fclose (stderr_trapped); |
280 | |
281 | return success ? 0 : 1; |
282 | } |
283 | |
284 | #include <support/test-driver.c> |
285 | |