1 | /* Copyright (C) 2017-2024 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 | #include <support/xunistd.h> |
37 | |
38 | static bool |
39 | check_stderr (bool expect_errmsg, FILE *stderr_trapped) |
40 | { |
41 | static char *lineptr = 0; |
42 | static size_t linesz = 0; |
43 | |
44 | bool got_errmsg = false; |
45 | rewind (stderr_trapped); |
46 | while (getline (lineptr: &lineptr, n: &linesz, stream: stderr_trapped) > 0) |
47 | { |
48 | got_errmsg = true; |
49 | fputs (lineptr, stdout); |
50 | } |
51 | rewind (stderr_trapped); |
52 | xftruncate (fd: fileno (stderr_trapped), length: 0); |
53 | return got_errmsg == expect_errmsg; |
54 | } |
55 | |
56 | struct test_short |
57 | { |
58 | const char *label; |
59 | const char *opts; |
60 | const char *const argv[8]; |
61 | int argc; |
62 | bool expect_errmsg; |
63 | }; |
64 | |
65 | struct test_long |
66 | { |
67 | const char *label; |
68 | const char *opts; |
69 | const struct option longopts[4]; |
70 | const char *const argv[8]; |
71 | int argc; |
72 | bool expect_errmsg; |
73 | }; |
74 | |
75 | #define DEFINE_TEST_DRIVER(test_type, getopt_call) \ |
76 | struct test_type##_tdata \ |
77 | { \ |
78 | pthread_mutex_t *sync; \ |
79 | const struct test_type *tcase; \ |
80 | bool ok; \ |
81 | }; \ |
82 | \ |
83 | static void * \ |
84 | test_type##_threadproc (void *data) \ |
85 | { \ |
86 | struct test_type##_tdata *tdata = data; \ |
87 | const struct test_type *tc = tdata->tcase; \ |
88 | \ |
89 | xpthread_mutex_lock (tdata->sync); \ |
90 | xpthread_mutex_unlock (tdata->sync); \ |
91 | \ |
92 | /* At this point, this thread has a cancellation pending. \ |
93 | We should still be able to get all the way through a getopt \ |
94 | loop without being cancelled. \ |
95 | Setting optind to 0 forces getopt to reinitialize itself. */ \ |
96 | optind = 0; \ |
97 | opterr = 1; \ |
98 | optopt = 0; \ |
99 | while (getopt_call != -1) \ |
100 | ; \ |
101 | tdata->ok = true; \ |
102 | \ |
103 | pthread_testcancel(); \ |
104 | return 0; \ |
105 | } \ |
106 | \ |
107 | static bool \ |
108 | do_##test_type (const struct test_type *tcase, FILE *stderr_trapped) \ |
109 | { \ |
110 | pthread_mutex_t sync; \ |
111 | struct test_type##_tdata tdata; \ |
112 | \ |
113 | printf("begin: %s\n", tcase->label); \ |
114 | \ |
115 | xpthread_mutex_init (&sync, 0); \ |
116 | xpthread_mutex_lock (&sync); \ |
117 | \ |
118 | tdata.sync = &sync; \ |
119 | tdata.tcase = tcase; \ |
120 | tdata.ok = false; \ |
121 | \ |
122 | pthread_t thr = xpthread_create (0, test_type##_threadproc, \ |
123 | (void *)&tdata); \ |
124 | xpthread_cancel (thr); \ |
125 | xpthread_mutex_unlock (&sync); \ |
126 | void *rv = xpthread_join (thr); \ |
127 | \ |
128 | xpthread_mutex_destroy (&sync); \ |
129 | \ |
130 | bool ok = true; \ |
131 | if (!check_stderr (tcase->expect_errmsg, stderr_trapped)) \ |
132 | { \ |
133 | ok = false; \ |
134 | printf("FAIL: %s: stderr not as expected\n", tcase->label); \ |
135 | } \ |
136 | if (!tdata.ok) \ |
137 | { \ |
138 | ok = false; \ |
139 | printf("FAIL: %s: did not complete loop\n", tcase->label); \ |
140 | } \ |
141 | if (rv != PTHREAD_CANCELED) \ |
142 | { \ |
143 | ok = false; \ |
144 | printf("FAIL: %s: thread was not cancelled\n", tcase->label); \ |
145 | } \ |
146 | if (ok) \ |
147 | printf ("pass: %s\n", tcase->label); \ |
148 | return ok; \ |
149 | } |
150 | |
151 | DEFINE_TEST_DRIVER (test_short, |
152 | getopt (tc->argc, (char *const *)tc->argv, tc->opts)) |
153 | DEFINE_TEST_DRIVER (test_long, |
154 | getopt_long (tc->argc, (char *const *)tc->argv, |
155 | tc->opts, tc->longopts, 0)) |
156 | |
157 | /* Caution: all option strings must begin with a '+' or '-' so that |
158 | getopt does not attempt to permute the argument vector (which is in |
159 | read-only memory). */ |
160 | const struct test_short tests_short[] = { |
161 | { "no errors" , |
162 | "+ab:c" , { "program" , "-ac" , "-b" , "x" , 0 }, 4, false }, |
163 | { "invalid option" , |
164 | "+ab:c" , { "program" , "-d" , 0 }, 2, true }, |
165 | { "missing argument" , |
166 | "+ab:c" , { "program" , "-b" , 0 }, 2, true }, |
167 | { 0 } |
168 | }; |
169 | |
170 | const struct test_long tests_long[] = { |
171 | { "no errors (long)" , |
172 | "+ab:c" , { { "alpha" , no_argument, 0, 'a' }, |
173 | { "bravo" , required_argument, 0, 'b' }, |
174 | { "charlie" , no_argument, 0, 'c' }, |
175 | { 0 } }, |
176 | { "program" , "-a" , "--charlie" , "--bravo=x" , 0 }, 4, false }, |
177 | |
178 | { "invalid option (long)" , |
179 | "+ab:c" , { { "alpha" , no_argument, 0, 'a' }, |
180 | { "bravo" , required_argument, 0, 'b' }, |
181 | { "charlie" , no_argument, 0, 'c' }, |
182 | { 0 } }, |
183 | { "program" , "-a" , "--charlie" , "--dingo" , 0 }, 4, true }, |
184 | |
185 | { "unwanted argument" , |
186 | "+ab:c" , { { "alpha" , no_argument, 0, 'a' }, |
187 | { "bravo" , required_argument, 0, 'b' }, |
188 | { "charlie" , no_argument, 0, 'c' }, |
189 | { 0 } }, |
190 | { "program" , "-a" , "--charlie=dingo" , "--bravo=x" , 0 }, 4, true }, |
191 | |
192 | { "missing argument" , |
193 | "+ab:c" , { { "alpha" , no_argument, 0, 'a' }, |
194 | { "bravo" , required_argument, 0, 'b' }, |
195 | { "charlie" , no_argument, 0, 'c' }, |
196 | { 0 } }, |
197 | { "program" , "-a" , "--charlie" , "--bravo" , 0 }, 4, true }, |
198 | |
199 | { "ambiguous options" , |
200 | "+uvw" , { { "veni" , no_argument, 0, 'u' }, |
201 | { "vedi" , no_argument, 0, 'v' }, |
202 | { "veci" , no_argument, 0, 'w' } }, |
203 | { "program" , "--ve" , 0 }, 2, true }, |
204 | |
205 | { "no errors (long W)" , |
206 | "+ab:cW;" , { { "alpha" , no_argument, 0, 'a' }, |
207 | { "bravo" , required_argument, 0, 'b' }, |
208 | { "charlie" , no_argument, 0, 'c' }, |
209 | { 0 } }, |
210 | { "program" , "-a" , "-W" , "charlie" , "-W" , "bravo=x" , 0 }, 6, false }, |
211 | |
212 | { "missing argument (W itself)" , |
213 | "+ab:cW;" , { { "alpha" , no_argument, 0, 'a' }, |
214 | { "bravo" , required_argument, 0, 'b' }, |
215 | { "charlie" , no_argument, 0, 'c' }, |
216 | { 0 } }, |
217 | { "program" , "-a" , "-W" , "charlie" , "-W" , 0 }, 5, true }, |
218 | |
219 | { "missing argument (W longopt)" , |
220 | "+ab:cW;" , { { "alpha" , no_argument, 0, 'a' }, |
221 | { "bravo" , required_argument, 0, 'b' }, |
222 | { "charlie" , no_argument, 0, 'c' }, |
223 | { 0 } }, |
224 | { "program" , "-a" , "-W" , "charlie" , "-W" , "bravo" , 0 }, 6, true }, |
225 | |
226 | { "unwanted argument (W longopt)" , |
227 | "+ab:cW;" , { { "alpha" , no_argument, 0, 'a' }, |
228 | { "bravo" , required_argument, 0, 'b' }, |
229 | { "charlie" , no_argument, 0, 'c' }, |
230 | { 0 } }, |
231 | { "program" , "-a" , "-W" , "charlie=dingo" , "-W" , "bravo=x" , 0 }, 6, true }, |
232 | |
233 | { "ambiguous options (W)" , |
234 | "+uvwW;" , { { "veni" , no_argument, 0, 'u' }, |
235 | { "vedi" , no_argument, 0, 'v' }, |
236 | { "veci" , no_argument, 0, 'w' } }, |
237 | { "program" , "-W" , "ve" , 0 }, 3, true }, |
238 | |
239 | { 0 } |
240 | }; |
241 | |
242 | static int |
243 | do_test (void) |
244 | { |
245 | int stderr_trap = create_temp_file (base: "stderr" , filename: 0); |
246 | if (stderr_trap < 0) |
247 | { |
248 | perror ("create_temp_file" ); |
249 | return 1; |
250 | } |
251 | FILE *stderr_trapped = fdopen(stderr_trap, "r+" ); |
252 | if (!stderr_trapped) |
253 | { |
254 | perror ("fdopen" ); |
255 | return 1; |
256 | } |
257 | int old_stderr = dup (fd: fileno (stderr)); |
258 | if (old_stderr < 0) |
259 | { |
260 | perror ("dup" ); |
261 | return 1; |
262 | } |
263 | if (dup2 (fd: stderr_trap, fd2: 2) < 0) |
264 | { |
265 | perror ("dup2" ); |
266 | return 1; |
267 | } |
268 | rewind (stderr); |
269 | |
270 | bool success = true; |
271 | |
272 | for (const struct test_short *tcase = tests_short; tcase->label; tcase++) |
273 | success = do_test_short (tcase, stderr_trapped) && success; |
274 | |
275 | for (const struct test_long *tcase = tests_long; tcase->label; tcase++) |
276 | success = do_test_long (tcase, stderr_trapped) && success; |
277 | |
278 | dup2 (fd: old_stderr, fd2: 2); |
279 | close (fd: old_stderr); |
280 | fclose (stderr_trapped); |
281 | |
282 | return success ? 0 : 1; |
283 | } |
284 | |
285 | #include <support/test-driver.c> |
286 | |