1 | /* Test for libio vtables and their validation. Common code. |
2 | Copyright (C) 2018-2022 Free Software Foundation, Inc. |
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 |
17 | <https://www.gnu.org/licenses/>. */ |
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 (format: "info: %s (%p) called\n" , name, fp); |
96 | } |
97 | |
98 | static void |
99 | method_finish (FILE *fp, int dummy) |
100 | { |
101 | log_method (fp, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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, name: __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 (c: ' ', stream: shared->fp); |
364 | _exit (1); |
365 | } |
366 | |
367 | static void |
368 | without_compatibility_fgetc (void *closure) |
369 | { |
370 | /* This call should abort. */ |
371 | fgetc (stream: 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 | |
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); |
392 | support_capture_subprocess_check (&proc, context: name, status_or_signal: -SIGABRT, |
393 | allowed: 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 (fp: shared->fp, flags: file.f._flags); |
462 | |
463 | if (test_verbose > 0) |
464 | printf (format: "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 (name: "LIBC_FATAL_STDERR_" , value: "1" , replace: 1); |
476 | |
477 | shared = support_shared_allocate (size: sizeof (*shared)); |
478 | shared->initially_disabled = initially_disabled; |
479 | |
480 | if (initially_disabled) |
481 | { |
482 | check_for_termination (name: "fprintf" , callback: without_compatibility_fprintf); |
483 | check_for_termination (name: "fputc" , callback: without_compatibility_fputc); |
484 | check_for_termination (name: "fgetc" , callback: without_compatibility_fgetc); |
485 | check_for_termination (name: "fflush" , callback: without_compatibility_fflush); |
486 | } |
487 | |
488 | check_call (name: "fprintf" , callback: with_compatibility_fprintf, initially_disabled); |
489 | check_call (name: "fputc" , callback: with_compatibility_fputc, initially_disabled); |
490 | check_call (name: "fgetc" , callback: with_compatibility_fgetc, initially_disabled); |
491 | check_call (name: "fflush" , callback: with_compatibility_fflush, initially_disabled); |
492 | |
493 | support_shared_free (shared); |
494 | shared = NULL; |
495 | |
496 | return 0; |
497 | } |
498 | |