(root)/
glibc-2.38/
libio/
tst-vtables-common.c
       1  /* Test for libio vtables and their validation.  Common code.
       2     Copyright (C) 2018-2023 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 ("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  
     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, name, -SIGABRT,
     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    /* A temporary staging buffer is used in the current fprintf
     413       implementation, which is why there is just one call to
     414       xsputn.  */
     415    TEST_COMPARE (fprintf_ptr (shared->fp, "A%sCD", "B"), 4);
     416    TEST_COMPARE (shared->calls, 1);
     417    TEST_COMPARE (shared->calls_xsputn, 1);
     418    TEST_COMPARE_BLOB (shared->buffer, shared->buffer_length,
     419                       "ABCD", 4);
     420  }
     421  
     422  static void
     423  with_compatibility_fputc (void *closure)
     424  {
     425    shared->return_value = '@';
     426    TEST_COMPARE (fputc ('@', shared->fp), '@');
     427    TEST_COMPARE (shared->calls, 1);
     428    TEST_COMPARE (shared->calls_overflow, 1);
     429    TEST_COMPARE (shared->value, '@');
     430  }
     431  
     432  static void
     433  with_compatibility_fgetc (void *closure)
     434  {
     435    shared->return_value = 'X';
     436    TEST_COMPARE (fgetc (shared->fp), 'X');
     437    TEST_COMPARE (shared->calls, 1);
     438    TEST_COMPARE (shared->calls_uflow, 1);
     439  }
     440  
     441  static void
     442  with_compatibility_fflush (void *closure)
     443  {
     444    TEST_COMPARE (fflush (shared->fp), 0);
     445    TEST_COMPARE (shared->calls, 1);
     446    TEST_COMPARE (shared->calls_sync, 1);
     447  }
     448  
     449  /* Call CALLBACK in a subprocess, after setting up a custom file
     450     object and updating shared->fp.  */
     451  static void
     452  check_call (const char *name, void (*callback) (void *),
     453              bool initially_disabled)
     454  {
     455    *shared = (struct shared)
     456      {
     457       .initially_disabled = initially_disabled,
     458      };
     459  
     460    /* Set up a custom file object.  */
     461    struct my_file file = my_file_create ();
     462    shared->fp = &file.f;
     463    if (shared->initially_disabled)
     464      _IO_init (shared->fp, file.f._flags);
     465  
     466    if (test_verbose > 0)
     467      printf ("info: calling test %s\n", name);
     468    support_isolate_in_subprocess (callback, NULL);
     469  }
     470  
     471  /* Run the tests.  INITIALLY_DISABLED indicates whether custom vtables
     472     are disabled when the test starts.  */
     473  static int
     474  run_tests (bool initially_disabled)
     475  {
     476    /* The test relies on fatal error messages being printed to standard
     477       error.  */
     478    setenv ("LIBC_FATAL_STDERR_", "1", 1);
     479  
     480    shared = support_shared_allocate (sizeof (*shared));
     481    shared->initially_disabled = initially_disabled;
     482  
     483    if (initially_disabled)
     484      {
     485        check_for_termination ("fprintf", without_compatibility_fprintf);
     486        check_for_termination ("fputc", without_compatibility_fputc);
     487        check_for_termination ("fgetc", without_compatibility_fgetc);
     488        check_for_termination ("fflush", without_compatibility_fflush);
     489      }
     490  
     491    check_call ("fprintf", with_compatibility_fprintf, initially_disabled);
     492    check_call ("fputc", with_compatibility_fputc, initially_disabled);
     493    check_call ("fgetc", with_compatibility_fgetc, initially_disabled);
     494    check_call ("fflush", with_compatibility_fflush, initially_disabled);
     495  
     496    support_shared_free (shared);
     497    shared = NULL;
     498  
     499    return 0;
     500  }