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. */
44struct 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
91static void
92log_method (FILE *fp, const char *name)
93{
94 if (test_verbose > 0)
95 printf (format: "info: %s (%p) called\n", name, fp);
96}
97
98static void
99method_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
107static int
108method_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
118static int
119method_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
128static int
129method_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
138static int
139method_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
149static size_t
150method_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
165static size_t
166method_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
175static off64_t
176method_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
185static off64_t
186method_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
195static FILE *
196method_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
205static int
206method_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
215static int
216method_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
225static ssize_t
226method_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
235static ssize_t
236method_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
245static off64_t
246method_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
255static int
256method_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
265static int
266method_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
275static int
276method_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
285static void
286method_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
296static 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
322struct my_file
323{
324 FILE f;
325 const struct _IO_jump_t *vtable;
326};
327
328struct my_file
329my_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. */
348typedef int (*fprintf_type) (FILE *, const char *, ...);
349static const volatile fprintf_type fprintf_ptr = &fprintf;
350
351static void
352without_compatibility_fprintf (void *closure)
353{
354 /* This call should abort. */
355 fprintf_ptr (shared->fp, " ");
356 _exit (1);
357}
358
359static void
360without_compatibility_fputc (void *closure)
361{
362 /* This call should abort. */
363 fputc (c: ' ', stream: shared->fp);
364 _exit (1);
365}
366
367static void
368without_compatibility_fgetc (void *closure)
369{
370 /* This call should abort. */
371 fgetc (stream: shared->fp);
372 _exit (1);
373}
374
375static void
376without_compatibility_fflush (void *closure)
377{
378 /* This call should abort. */
379 fflush (shared->fp);
380 _exit (1);
381}
382
383static void
384check_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). */
407void _IO_init (FILE *fp, int flags);
408
409static void
410with_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
419static void
420with_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
429static void
430with_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
438static void
439with_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. */
448static void
449check_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. */
470static int
471run_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

source code of glibc/libio/tst-vtables-common.c