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
38static bool
39check_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
56struct 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
65struct 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
151DEFINE_TEST_DRIVER (test_short,
152 getopt (tc->argc, (char *const *)tc->argv, tc->opts))
153DEFINE_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). */
160const 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
170const 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
242static int
243do_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

source code of glibc/posix/tst-getopt-cancel.c