]>
Commit | Line | Data |
---|---|---|
29055464 | 1 | /* Test for libio vtables and their validation. Common code. |
d614a753 | 2 | Copyright (C) 2018-2020 Free Software Foundation, Inc. |
29055464 FW |
3 | This file is part of the GNU C Library. |
4 | ||
5 | The GNU C Library is free software; you can redistribute it and/or | |
6 | modify it under the terms of the GNU Lesser General Public | |
7 | License as published by the Free Software Foundation; either | |
8 | version 2.1 of the License, or (at your option) any later version. | |
9 | ||
10 | The GNU C Library is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | Lesser General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU Lesser General Public | |
16 | License along with the GNU C Library; if not, see | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
29055464 FW |
18 | |
19 | /* This test provides some coverage for how various stdio functions | |
20 | use the vtables in FILE * objects. The focus is mostly on which | |
21 | functions call which methods, not so much on validating data | |
22 | processing. An initial series of tests check that custom vtables | |
23 | do not work without activation through _IO_init. | |
24 | ||
25 | Note: libio vtables are deprecated feature. Do not use this test | |
26 | as a documentation source for writing custom vtables. See | |
27 | fopencookie for a different way of creating custom stdio | |
28 | streams. */ | |
29 | ||
30 | #include <stdbool.h> | |
31 | #include <string.h> | |
32 | #include <support/capture_subprocess.h> | |
33 | #include <support/check.h> | |
34 | #include <support/namespace.h> | |
35 | #include <support/support.h> | |
36 | #include <support/test-driver.h> | |
37 | #include <support/xunistd.h> | |
38 | ||
39 | #include "libioP.h" | |
40 | ||
41 | /* Data shared between the test subprocess and the test driver in the | |
42 | parent. Note that *shared is reset at the start of the check_call | |
43 | function. */ | |
44 | struct shared | |
45 | { | |
46 | /* Expected file pointer for method calls. */ | |
47 | FILE *fp; | |
48 | ||
49 | /* If true, assume that a call to _IO_init is needed to enable | |
50 | custom vtables. */ | |
51 | bool initially_disabled; | |
52 | ||
53 | /* Requested return value for the methods which have one. */ | |
54 | int return_value; | |
55 | ||
56 | /* A value (usually a character) recorded by some of the methods | |
57 | below. */ | |
58 | int value; | |
59 | ||
60 | /* Likewise, for some data. */ | |
61 | char buffer[16]; | |
62 | size_t buffer_length; | |
63 | ||
64 | /* Total number of method calls. */ | |
65 | unsigned int calls; | |
66 | ||
67 | /* Individual method call counts. */ | |
68 | unsigned int calls_finish; | |
69 | unsigned int calls_overflow; | |
70 | unsigned int calls_underflow; | |
71 | unsigned int calls_uflow; | |
72 | unsigned int calls_pbackfail; | |
73 | unsigned int calls_xsputn; | |
74 | unsigned int calls_xsgetn; | |
75 | unsigned int calls_seekoff; | |
76 | unsigned int calls_seekpos; | |
77 | unsigned int calls_setbuf; | |
78 | unsigned int calls_sync; | |
79 | unsigned int calls_doallocate; | |
80 | unsigned int calls_read; | |
81 | unsigned int calls_write; | |
82 | unsigned int calls_seek; | |
83 | unsigned int calls_close; | |
84 | unsigned int calls_stat; | |
85 | unsigned int calls_showmanyc; | |
86 | unsigned int calls_imbue; | |
87 | } *shared; | |
88 | ||
89 | /* Method implementations which increment the counters in *shared. */ | |
90 | ||
91 | static void | |
92 | log_method (FILE *fp, const char *name) | |
93 | { | |
94 | if (test_verbose > 0) | |
95 | printf ("info: %s (%p) called\n", name, fp); | |
96 | } | |
97 | ||
98 | static void | |
99 | method_finish (FILE *fp, int dummy) | |
100 | { | |
101 | log_method (fp, __func__); | |
102 | TEST_VERIFY (fp == shared->fp); | |
103 | ++shared->calls; | |
104 | ++shared->calls_finish; | |
105 | } | |
106 | ||
107 | static int | |
108 | method_overflow (FILE *fp, int ch) | |
109 | { | |
110 | log_method (fp, __func__); | |
111 | TEST_VERIFY (fp == shared->fp); | |
112 | ++shared->calls; | |
113 | ++shared->calls_overflow; | |
114 | shared->value = ch; | |
115 | return shared->return_value; | |
116 | } | |
117 | ||
118 | static int | |
119 | method_underflow (FILE *fp) | |
120 | { | |
121 | log_method (fp, __func__); | |
122 | TEST_VERIFY (fp == shared->fp); | |
123 | ++shared->calls; | |
124 | ++shared->calls_underflow; | |
125 | return shared->return_value; | |
126 | } | |
127 | ||
128 | static int | |
129 | method_uflow (FILE *fp) | |
130 | { | |
131 | log_method (fp, __func__); | |
132 | TEST_VERIFY (fp == shared->fp); | |
133 | ++shared->calls; | |
134 | ++shared->calls_uflow; | |
135 | return shared->return_value; | |
136 | } | |
137 | ||
138 | static int | |
139 | method_pbackfail (FILE *fp, int ch) | |
140 | { | |
141 | log_method (fp, __func__); | |
142 | TEST_VERIFY (fp == shared->fp); | |
143 | ++shared->calls; | |
144 | ++shared->calls_pbackfail; | |
145 | shared->value = ch; | |
146 | return shared->return_value; | |
147 | } | |
148 | ||
149 | static size_t | |
150 | method_xsputn (FILE *fp, const void *data, size_t n) | |
151 | { | |
152 | log_method (fp, __func__); | |
153 | TEST_VERIFY (fp == shared->fp); | |
154 | ++shared->calls; | |
155 | ++shared->calls_xsputn; | |
156 | ||
157 | size_t to_copy = n; | |
158 | if (n > sizeof (shared->buffer)) | |
159 | to_copy = sizeof (shared->buffer); | |
160 | memcpy (shared->buffer, data, to_copy); | |
161 | shared->buffer_length = to_copy; | |
162 | return to_copy; | |
163 | } | |
164 | ||
165 | static size_t | |
166 | method_xsgetn (FILE *fp, void *data, size_t n) | |
167 | { | |
168 | log_method (fp, __func__); | |
169 | TEST_VERIFY (fp == shared->fp); | |
170 | ++shared->calls; | |
171 | ++shared->calls_xsgetn; | |
172 | return 0; | |
173 | } | |
174 | ||
175 | static off64_t | |
176 | method_seekoff (FILE *fp, off64_t offset, int dir, int mode) | |
177 | { | |
178 | log_method (fp, __func__); | |
179 | TEST_VERIFY (fp == shared->fp); | |
180 | ++shared->calls; | |
181 | ++shared->calls_seekoff; | |
182 | return shared->return_value; | |
183 | } | |
184 | ||
185 | static off64_t | |
186 | method_seekpos (FILE *fp, off64_t offset, int mode) | |
187 | { | |
188 | log_method (fp, __func__); | |
189 | TEST_VERIFY (fp == shared->fp); | |
190 | ++shared->calls; | |
191 | ++shared->calls_seekpos; | |
192 | return shared->return_value; | |
193 | } | |
194 | ||
195 | static FILE * | |
196 | method_setbuf (FILE *fp, char *buffer, ssize_t length) | |
197 | { | |
198 | log_method (fp, __func__); | |
199 | TEST_VERIFY (fp == shared->fp); | |
200 | ++shared->calls; | |
201 | ++shared->calls_setbuf; | |
202 | return fp; | |
203 | } | |
204 | ||
205 | static int | |
206 | method_sync (FILE *fp) | |
207 | { | |
208 | log_method (fp, __func__); | |
209 | TEST_VERIFY (fp == shared->fp); | |
210 | ++shared->calls; | |
211 | ++shared->calls_sync; | |
212 | return shared->return_value; | |
213 | } | |
214 | ||
215 | static int | |
216 | method_doallocate (FILE *fp) | |
217 | { | |
218 | log_method (fp, __func__); | |
219 | TEST_VERIFY (fp == shared->fp); | |
220 | ++shared->calls; | |
221 | ++shared->calls_doallocate; | |
222 | return shared->return_value; | |
223 | } | |
224 | ||
225 | static ssize_t | |
226 | method_read (FILE *fp, void *data, ssize_t length) | |
227 | { | |
228 | log_method (fp, __func__); | |
229 | TEST_VERIFY (fp == shared->fp); | |
230 | ++shared->calls; | |
231 | ++shared->calls_read; | |
232 | return shared->return_value; | |
233 | } | |
234 | ||
235 | static ssize_t | |
236 | method_write (FILE *fp, const void *data, ssize_t length) | |
237 | { | |
238 | log_method (fp, __func__); | |
239 | TEST_VERIFY (fp == shared->fp); | |
240 | ++shared->calls; | |
241 | ++shared->calls_write; | |
242 | return shared->return_value; | |
243 | } | |
244 | ||
245 | static off64_t | |
246 | method_seek (FILE *fp, off64_t offset, int mode) | |
247 | { | |
248 | log_method (fp, __func__); | |
249 | TEST_VERIFY (fp == shared->fp); | |
250 | ++shared->calls; | |
251 | ++shared->calls_seek; | |
252 | return shared->return_value; | |
253 | } | |
254 | ||
255 | static int | |
256 | method_close (FILE *fp) | |
257 | { | |
258 | log_method (fp, __func__); | |
259 | TEST_VERIFY (fp == shared->fp); | |
260 | ++shared->calls; | |
261 | ++shared->calls_close; | |
262 | return shared->return_value; | |
263 | } | |
264 | ||
265 | static int | |
266 | method_stat (FILE *fp, void *buffer) | |
267 | { | |
268 | log_method (fp, __func__); | |
269 | TEST_VERIFY (fp == shared->fp); | |
270 | ++shared->calls; | |
271 | ++shared->calls_stat; | |
272 | return shared->return_value; | |
273 | } | |
274 | ||
275 | static int | |
276 | method_showmanyc (FILE *fp) | |
277 | { | |
278 | log_method (fp, __func__); | |
279 | TEST_VERIFY (fp == shared->fp); | |
280 | ++shared->calls; | |
281 | ++shared->calls_showmanyc; | |
282 | return shared->return_value; | |
283 | } | |
284 | ||
285 | static void | |
286 | method_imbue (FILE *fp, void *locale) | |
287 | { | |
288 | log_method (fp, __func__); | |
289 | TEST_VERIFY (fp == shared->fp); | |
290 | ++shared->calls; | |
291 | ++shared->calls_imbue; | |
292 | } | |
293 | ||
294 | /* Our custom vtable. */ | |
295 | ||
296 | static const struct _IO_jump_t jumps = | |
297 | { | |
298 | JUMP_INIT_DUMMY, | |
299 | JUMP_INIT (finish, method_finish), | |
300 | JUMP_INIT (overflow, method_overflow), | |
301 | JUMP_INIT (underflow, method_underflow), | |
302 | JUMP_INIT (uflow, method_uflow), | |
303 | JUMP_INIT (pbackfail, method_pbackfail), | |
304 | JUMP_INIT (xsputn, method_xsputn), | |
305 | JUMP_INIT (xsgetn, method_xsgetn), | |
306 | JUMP_INIT (seekoff, method_seekoff), | |
307 | JUMP_INIT (seekpos, method_seekpos), | |
308 | JUMP_INIT (setbuf, method_setbuf), | |
309 | JUMP_INIT (sync, method_sync), | |
310 | JUMP_INIT (doallocate, method_doallocate), | |
311 | JUMP_INIT (read, method_read), | |
312 | JUMP_INIT (write, method_write), | |
313 | JUMP_INIT (seek, method_seek), | |
314 | JUMP_INIT (close, method_close), | |
315 | JUMP_INIT (stat, method_stat), | |
316 | JUMP_INIT (showmanyc, method_showmanyc), | |
317 | JUMP_INIT (imbue, method_imbue) | |
318 | }; | |
319 | ||
320 | /* Our file implementation. */ | |
321 | ||
322 | struct my_file | |
323 | { | |
324 | FILE f; | |
325 | const struct _IO_jump_t *vtable; | |
326 | }; | |
327 | ||
328 | struct my_file | |
329 | my_file_create (void) | |
330 | { | |
331 | return (struct my_file) | |
332 | { | |
333 | /* Disable locking, so that we do not have to set up a lock | |
334 | pointer. */ | |
335 | .f._flags = _IO_USER_LOCK, | |
336 | ||
337 | /* Copy the offset from the an initialized handle, instead of | |
338 | figuring it out from scratch. */ | |
339 | .f._vtable_offset = stdin->_vtable_offset, | |
340 | ||
341 | .vtable = &jumps, | |
342 | }; | |
343 | } | |
344 | ||
345 | /* Initial tests which do not enable vtable compatibility. */ | |
346 | ||
347 | /* Inhibit GCC optimization of fprintf. */ | |
348 | typedef int (*fprintf_type) (FILE *, const char *, ...); | |
349 | static const volatile fprintf_type fprintf_ptr = &fprintf; | |
350 | ||
351 | static void | |
352 | without_compatibility_fprintf (void *closure) | |
353 | { | |
354 | /* This call should abort. */ | |
355 | fprintf_ptr (shared->fp, " "); | |
356 | _exit (1); | |
357 | } | |
358 | ||
359 | static void | |
360 | without_compatibility_fputc (void *closure) | |
361 | { | |
362 | /* This call should abort. */ | |
363 | fputc (' ', shared->fp); | |
364 | _exit (1); | |
365 | } | |
366 | ||
367 | static void | |
368 | without_compatibility_fgetc (void *closure) | |
369 | { | |
370 | /* This call should abort. */ | |
371 | fgetc (shared->fp); | |
372 | _exit (1); | |
373 | } | |
374 | ||
375 | static void | |
376 | without_compatibility_fflush (void *closure) | |
377 | { | |
378 | /* This call should abort. */ | |
379 | fflush (shared->fp); | |
380 | _exit (1); | |
381 | } | |
382 | ||
29055464 FW |
383 | static void |
384 | check_for_termination (const char *name, void (*callback) (void *)) | |
385 | { | |
386 | struct my_file file = my_file_create (); | |
387 | shared->fp = &file.f; | |
388 | shared->return_value = -1; | |
389 | shared->calls = 0; | |
390 | struct support_capture_subprocess proc | |
391 | = support_capture_subprocess (callback, NULL); | |
96cd0558 | 392 | support_capture_subprocess_check (&proc, name, -SIGABRT, |
29055464 FW |
393 | sc_allow_stderr); |
394 | const char *message | |
395 | = "Fatal error: glibc detected an invalid stdio handle\n"; | |
396 | TEST_COMPARE_BLOB (proc.err.buffer, proc.err.length, | |
397 | message, strlen (message)); | |
398 | TEST_COMPARE (shared->calls, 0); | |
399 | support_capture_subprocess_free (&proc); | |
400 | } | |
401 | ||
402 | /* The test with vtable validation disabled. */ | |
403 | ||
404 | /* This function does not have a prototype in libioP.h to prevent | |
405 | accidental use from within the library (which would disable vtable | |
406 | verification). */ | |
407 | void _IO_init (FILE *fp, int flags); | |
408 | ||
409 | static void | |
410 | with_compatibility_fprintf (void *closure) | |
411 | { | |
412 | TEST_COMPARE (fprintf_ptr (shared->fp, "A%sCD", "B"), 4); | |
413 | TEST_COMPARE (shared->calls, 3); | |
414 | TEST_COMPARE (shared->calls_xsputn, 3); | |
415 | TEST_COMPARE_BLOB (shared->buffer, shared->buffer_length, | |
416 | "CD", 2); | |
417 | } | |
418 | ||
419 | static void | |
420 | with_compatibility_fputc (void *closure) | |
421 | { | |
422 | shared->return_value = '@'; | |
423 | TEST_COMPARE (fputc ('@', shared->fp), '@'); | |
424 | TEST_COMPARE (shared->calls, 1); | |
425 | TEST_COMPARE (shared->calls_overflow, 1); | |
426 | TEST_COMPARE (shared->value, '@'); | |
427 | } | |
428 | ||
429 | static void | |
430 | with_compatibility_fgetc (void *closure) | |
431 | { | |
432 | shared->return_value = 'X'; | |
433 | TEST_COMPARE (fgetc (shared->fp), 'X'); | |
434 | TEST_COMPARE (shared->calls, 1); | |
435 | TEST_COMPARE (shared->calls_uflow, 1); | |
436 | } | |
437 | ||
438 | static void | |
439 | with_compatibility_fflush (void *closure) | |
440 | { | |
441 | TEST_COMPARE (fflush (shared->fp), 0); | |
442 | TEST_COMPARE (shared->calls, 1); | |
443 | TEST_COMPARE (shared->calls_sync, 1); | |
444 | } | |
445 | ||
446 | /* Call CALLBACK in a subprocess, after setting up a custom file | |
447 | object and updating shared->fp. */ | |
448 | static void | |
449 | check_call (const char *name, void (*callback) (void *), | |
450 | bool initially_disabled) | |
451 | { | |
452 | *shared = (struct shared) | |
453 | { | |
454 | .initially_disabled = initially_disabled, | |
455 | }; | |
456 | ||
457 | /* Set up a custom file object. */ | |
458 | struct my_file file = my_file_create (); | |
459 | shared->fp = &file.f; | |
460 | if (shared->initially_disabled) | |
461 | _IO_init (shared->fp, file.f._flags); | |
462 | ||
463 | if (test_verbose > 0) | |
464 | printf ("info: calling test %s\n", name); | |
465 | support_isolate_in_subprocess (callback, NULL); | |
466 | } | |
467 | ||
468 | /* Run the tests. INITIALLY_DISABLED indicates whether custom vtables | |
469 | are disabled when the test starts. */ | |
470 | static int | |
471 | run_tests (bool initially_disabled) | |
472 | { | |
473 | /* The test relies on fatal error messages being printed to standard | |
474 | error. */ | |
475 | setenv ("LIBC_FATAL_STDERR_", "1", 1); | |
476 | ||
477 | shared = support_shared_allocate (sizeof (*shared)); | |
478 | shared->initially_disabled = initially_disabled; | |
29055464 FW |
479 | |
480 | if (initially_disabled) | |
481 | { | |
482 | check_for_termination ("fprintf", without_compatibility_fprintf); | |
483 | check_for_termination ("fputc", without_compatibility_fputc); | |
484 | check_for_termination ("fgetc", without_compatibility_fgetc); | |
485 | check_for_termination ("fflush", without_compatibility_fflush); | |
486 | } | |
487 | ||
488 | check_call ("fprintf", with_compatibility_fprintf, initially_disabled); | |
489 | check_call ("fputc", with_compatibility_fputc, initially_disabled); | |
490 | check_call ("fgetc", with_compatibility_fgetc, initially_disabled); | |
491 | check_call ("fflush", with_compatibility_fflush, initially_disabled); | |
492 | ||
493 | support_shared_free (shared); | |
494 | shared = NULL; | |
495 | ||
496 | return 0; | |
497 | } |