(root)/
glibc-2.38/
libio/
tst-ftell-active-handler.c
       1  /* Verify that ftell returns the correct value at various points before and
       2     after the handler on which it is called becomes active.
       3     Copyright (C) 2014-2023 Free Software Foundation, Inc.
       4     This file is part of the GNU C Library.
       5  
       6     The GNU C Library is free software; you can redistribute it and/or
       7     modify it under the terms of the GNU Lesser General Public
       8     License as published by the Free Software Foundation; either
       9     version 2.1 of the License, or (at your option) any later version.
      10  
      11     The GNU C Library is distributed in the hope that it will be useful,
      12     but WITHOUT ANY WARRANTY; without even the implied warranty of
      13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14     Lesser General Public License for more details.
      15  
      16     You should have received a copy of the GNU Lesser General Public
      17     License along with the GNU C Library; if not, see
      18     <https://www.gnu.org/licenses/>.  */
      19  
      20  #include <stdio.h>
      21  #include <stdlib.h>
      22  #include <string.h>
      23  #include <errno.h>
      24  #include <unistd.h>
      25  #include <fcntl.h>
      26  #include <locale.h>
      27  #include <wchar.h>
      28  
      29  static int do_test (void);
      30  
      31  #define TEST_FUNCTION do_test ()
      32  #include "../test-skeleton.c"
      33  
      34  #define get_handles_fdopen(filename, fd, fp, fd_mode, mode) \
      35  ({									      \
      36    int ret = 0;								      \
      37    (fd) = open ((filename), (fd_mode), 0);				      \
      38    if ((fd) == -1)							      \
      39      {									      \
      40        printf ("open failed: %m\n");					      \
      41        ret = 1;								      \
      42      }									      \
      43    else									      \
      44      {									      \
      45        (fp) = fdopen ((fd), (mode));					      \
      46        if ((fp) == NULL)							      \
      47  	{								      \
      48  	  printf ("fdopen failed: %m\n");				      \
      49  	  close (fd);							      \
      50  	  ret = 1;							      \
      51  	}								      \
      52      }									      \
      53    ret;									      \
      54  })
      55  
      56  #define get_handles_fopen(filename, fd, fp, mode) \
      57  ({									      \
      58    int ret = 0;								      \
      59    (fp) = fopen ((filename), (mode));					      \
      60    if ((fp) == NULL)							      \
      61      {									      \
      62        printf ("fopen failed: %m\n");					      \
      63        ret = 1;								      \
      64      }									      \
      65    else									      \
      66      {									      \
      67        (fd) = fileno (fp);						      \
      68        if ((fd) == -1)							      \
      69  	{								      \
      70  	  printf ("fileno failed: %m\n");				      \
      71  	  ret = 1;							      \
      72  	}								      \
      73      }									      \
      74    ret;									      \
      75  })
      76  
      77  /* data points to either char_data or wide_data, depending on whether we're
      78     testing regular file mode or wide mode respectively.  Similarly,
      79     fputs_func points to either fputs or fputws.  data_len keeps track of the
      80     length of the current data and file_len maintains the current file
      81     length.  */
      82  static const void *data;
      83  static const char *char_data = "abcdef";
      84  static const wchar_t *wide_data = L"abcdef";
      85  static size_t data_len;
      86  static size_t file_len;
      87  
      88  typedef int (*fputs_func_t) (const void *data, FILE *fp);
      89  typedef void *(*fgets_func_t) (void *ws, int n, FILE *fp);
      90  fputs_func_t fputs_func;
      91  fgets_func_t fgets_func;
      92  
      93  /* This test verifies that the offset reported by ftell is correct after the
      94     file is truncated using ftruncate.  ftruncate does not change the file
      95     offset on truncation and hence, SEEK_CUR should continue to point to the
      96     old offset and not be changed to the new offset.  */
      97  static int
      98  do_ftruncate_test (const char *filename)
      99  {
     100    FILE *fp = NULL;
     101    int fd;
     102    int ret = 0;
     103    struct test
     104      {
     105        const char *mode;
     106        int fd_mode;
     107      } test_modes[] = {
     108  	  {"r+", O_RDWR},
     109  	  {"w", O_WRONLY | O_TRUNC},
     110  	  {"w+", O_RDWR | O_TRUNC},
     111  	  {"a", O_WRONLY},
     112  	  {"a+", O_RDWR}
     113      };
     114  
     115    for (int j = 0; j < 2; j++)
     116      {
     117        for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
     118  	{
     119  	  int fileret;
     120  	  printf ("\tftruncate: %s (file, \"%s\"): ",
     121  		  j == 0 ? "fopen" : "fdopen",
     122  		  test_modes[i].mode);
     123  
     124  	  if (j == 0)
     125  	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
     126  	  else
     127  	    fileret = get_handles_fdopen (filename, fd, fp,
     128  					  test_modes[i].fd_mode,
     129  					  test_modes[i].mode);
     130  
     131  	  if (fileret != 0)
     132  	    return fileret;
     133  
     134  	  /* Write some data.  */
     135  	  size_t written = fputs_func (data, fp);
     136  
     137  	  if (written == EOF)
     138  	    {
     139  	      printf ("fputs[1] failed to write data\n");
     140  	      ret |= 1;
     141  	    }
     142  
     143  	  /* Record the offset.  */
     144  	  long offset = ftell (fp);
     145  
     146  	  /* Flush data to allow switching active handles.  */
     147  	  if (fflush (fp))
     148  	    {
     149  	      printf ("Flush failed: %m\n");
     150  	      ret |= 1;
     151  	    }
     152  
     153  	  /* Now truncate the file.  */
     154  	  if (ftruncate (fd, 0) != 0)
     155  	    {
     156  	      printf ("Failed to truncate file: %m\n");
     157  	      ret |= 1;
     158  	    }
     159  
     160  	  /* ftruncate does not change the offset, so there is no need to call
     161  	     anything to be able to switch active handles.  */
     162  	  long new_offset = ftell (fp);
     163  
     164  	  /* The offset should remain unchanged since ftruncate does not update
     165  	     it.  */
     166  	  if (offset != new_offset)
     167  	    {
     168  	      printf ("Incorrect offset.  Expected %ld, but got %ld\n",
     169  		      offset, new_offset);
     170  
     171  	      ret |= 1;
     172  	    }
     173  	  else
     174  	    printf ("offset = %ld\n", offset);
     175  
     176  	  fclose (fp);
     177  	}
     178      }
     179  
     180    return ret;
     181  }
     182  /* Test that ftell output after a rewind is correct.  */
     183  static int
     184  do_rewind_test (const char *filename)
     185  {
     186    int ret = 0;
     187    struct test
     188      {
     189        const char *mode;
     190        int fd_mode;
     191        size_t old_off;
     192        size_t new_off;
     193      } test_modes[] = {
     194  	  {"w", O_WRONLY | O_TRUNC, 0, data_len},
     195  	  {"w+", O_RDWR | O_TRUNC, 0, data_len},
     196  	  {"r+", O_RDWR, 0, data_len},
     197  	  /* The new offsets for 'a' and 'a+' modes have to factor in the
     198  	     previous writes since they always append to the end of the
     199  	     file.  */
     200  	  {"a", O_WRONLY, 0, 3 * data_len},
     201  	  {"a+", O_RDWR, 0, 4 * data_len},
     202      };
     203  
     204    /* Empty the file before the test so that our offsets are simple to
     205       calculate.  */
     206    FILE *fp = fopen (filename, "w");
     207    if (fp == NULL)
     208      {
     209        printf ("Failed to open file for emptying\n");
     210        return 1;
     211      }
     212    fclose (fp);
     213  
     214    for (int j = 0; j < 2; j++)
     215      {
     216        for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
     217  	{
     218  	  FILE *fp;
     219  	  int fd;
     220  	  int fileret;
     221  
     222  	  printf ("\trewind: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen",
     223  		  test_modes[i].mode);
     224  
     225  	  if (j == 0)
     226  	    fileret = get_handles_fdopen (filename, fd, fp,
     227  					  test_modes[i].fd_mode,
     228  					  test_modes[i].mode);
     229  	  else
     230  	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
     231  
     232  	  if (fileret != 0)
     233  	    return fileret;
     234  
     235  	  /* Write some content to the file, rewind and ensure that the ftell
     236  	     output after the rewind is 0.  POSIX does not specify what the
     237  	     behavior is when a file is rewound in 'a' mode, so we retain
     238  	     current behavior, which is to keep the 0 offset.  */
     239  	  size_t written = fputs_func (data, fp);
     240  
     241  	  if (written == EOF)
     242  	    {
     243  	      printf ("fputs[1] failed to write data\n");
     244  	      ret |= 1;
     245  	    }
     246  
     247  	  rewind (fp);
     248  	  long offset = ftell (fp);
     249  
     250  	  if (offset != test_modes[i].old_off)
     251  	    {
     252  	      printf ("Incorrect old offset.  Expected %zu, but got %ld, ",
     253  		      test_modes[i].old_off, offset);
     254  	      ret |= 1;
     255  	    }
     256  	  else
     257  	    printf ("old offset = %ld, ", offset);
     258  
     259  	  written = fputs_func (data, fp);
     260  
     261  	  if (written == EOF)
     262  	    {
     263  	      printf ("fputs[1] failed to write data\n");
     264  	      ret |= 1;
     265  	    }
     266  
     267  	  /* After this write, the offset in append modes should factor in the
     268  	     implicit lseek to the end of file.  */
     269  	  offset = ftell (fp);
     270  	  if (offset != test_modes[i].new_off)
     271  	    {
     272  	      printf ("Incorrect new offset.  Expected %zu, but got %ld\n",
     273  		      test_modes[i].new_off, offset);
     274  	      ret |= 1;
     275  	    }
     276  	  else
     277  	    printf ("new offset = %ld\n", offset);
     278  	}
     279      }
     280    return ret;
     281  }
     282  
     283  /* Test that the value of ftell is not cached when the stream handle is not
     284     active.  */
     285  static int
     286  do_ftell_test (const char *filename)
     287  {
     288    int ret = 0;
     289    struct test
     290      {
     291        const char *mode;
     292        int fd_mode;
     293        size_t old_off;
     294        size_t new_off;
     295        size_t eof_off;
     296      } test_modes[] = {
     297  	  /* In w, w+ and r+ modes, the file position should be at the
     298  	     beginning of the file.  After the write, the offset should be
     299  	     updated to data_len.  We don't use eof_off in w and a modes since
     300  	     they don't allow reading.  */
     301  	  {"w", O_WRONLY | O_TRUNC, 0, data_len, 0},
     302  	  {"w+", O_RDWR | O_TRUNC, 0, data_len, 2 * data_len},
     303  	  {"r+", O_RDWR, 0, data_len, 3 * data_len},
     304  	  /* For the 'a' mode, the initial file position should be the
     305  	     current end of file. After the write, the offset has data_len
     306  	     added to the old value.  For a+ mode however, the initial file
     307  	     position is the file position of the underlying file descriptor,
     308  	     since it is initially assumed to be in read mode.  */
     309  	  {"a", O_WRONLY, 3 * data_len, 4 * data_len, 5 * data_len},
     310  	  {"a+", O_RDWR, 0, 5 * data_len, 6 * data_len},
     311      };
     312    for (int j = 0; j < 2; j++)
     313      {
     314        for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
     315  	{
     316  	  FILE *fp;
     317  	  int fd;
     318  	  int fileret;
     319  
     320  	  printf ("\tftell: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen",
     321  		  test_modes[i].mode);
     322  
     323  	  if (j == 0)
     324  	    fileret = get_handles_fdopen (filename, fd, fp,
     325  					  test_modes[i].fd_mode,
     326  					  test_modes[i].mode);
     327  	  else
     328  	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
     329  
     330  	  if (fileret != 0)
     331  	    return fileret;
     332  
     333  	  long off = ftell (fp);
     334  	  if (off != test_modes[i].old_off)
     335  	    {
     336  	      printf ("Incorrect old offset.  Expected %zu but got %ld, ",
     337  		      test_modes[i].old_off, off);
     338  	      ret |= 1;
     339  	    }
     340  	  else
     341  	    printf ("old offset = %ld, ", off);
     342  
     343  	  /* The effect of this write on the offset should be seen in the ftell
     344  	     call that follows it.  */
     345  	  int write_ret = write (fd, data, data_len);
     346  	  if (write_ret != data_len)
     347  	    {
     348  	      printf ("write failed (%m)\n");
     349  	      ret |= 1;
     350  	    }
     351  	  off = ftell (fp);
     352  
     353  	  if (off != test_modes[i].new_off)
     354  	    {
     355  	      printf ("Incorrect new offset.  Expected %zu but got %ld",
     356  		      test_modes[i].new_off, off);
     357  	      ret |= 1;
     358  	    }
     359  	  else
     360  	    printf ("new offset = %ld", off);
     361  
     362  	  /* Read to the end, write some data to the fd and check if ftell can
     363  	     see the new ofset.  Do this test only for files that allow
     364  	     reading.  */
     365  	  if (test_modes[i].fd_mode != O_WRONLY)
     366  	    {
     367  	      wchar_t tmpbuf[data_len];
     368  
     369  	      rewind (fp);
     370  
     371  	      while (fgets_func (tmpbuf, data_len, fp) && !feof (fp));
     372  
     373  	      write_ret = write (fd, data, data_len);
     374  	      if (write_ret != data_len)
     375  		{
     376  		  printf ("write failed (%m)\n");
     377  		  ret |= 1;
     378  		}
     379  	      off = ftell (fp);
     380  
     381  	      if (off != test_modes[i].eof_off)
     382  		{
     383  		  printf (", Incorrect offset after read EOF.  "
     384  			  "Expected %zu but got %ld\n",
     385  			  test_modes[i].eof_off, off);
     386  		  ret |= 1;
     387  		}
     388  	      else
     389  		printf (", offset after EOF = %ld\n", off);
     390  	    }
     391  	  else
     392  	    putc ('\n', stdout);
     393  
     394  	  fclose (fp);
     395  	}
     396      }
     397  
     398    return ret;
     399  }
     400  
     401  /* This test opens the file for writing, moves the file offset of the
     402     underlying file, writes out data and then checks if ftell trips on it.  */
     403  static int
     404  do_write_test (const char *filename)
     405  {
     406    FILE *fp = NULL;
     407    int fd;
     408    int ret = 0;
     409    struct test
     410      {
     411        const char *mode;
     412        int fd_mode;
     413      } test_modes[] = {
     414  	  {"w", O_WRONLY | O_TRUNC},
     415  	  {"w+", O_RDWR | O_TRUNC},
     416  	  {"r+", O_RDWR}
     417      };
     418  
     419    for (int j = 0; j < 2; j++)
     420      {
     421        for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
     422  	{
     423  	  int fileret;
     424  	  printf ("\twrite: %s (file, \"%s\"): ", j == 0 ? "fopen" : "fdopen",
     425  		  test_modes[i].mode);
     426  
     427  	  if (j == 0)
     428  	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
     429  	  else
     430  	    fileret = get_handles_fdopen (filename, fd, fp,
     431  					  test_modes[i].fd_mode,
     432  					  test_modes[i].mode);
     433  
     434  	  if (fileret != 0)
     435  	    return fileret;
     436  
     437  	  /* Move offset to just before the end of the file.  */
     438  	  off_t seek_ret = lseek (fd, file_len - 1, SEEK_SET);
     439  	  if (seek_ret == -1)
     440  	    {
     441  	      printf ("lseek failed: %m\n");
     442  	      ret |= 1;
     443  	    }
     444  
     445  	  /* Write some data.  */
     446  	  size_t written = fputs_func (data, fp);
     447  
     448  	  if (written == EOF)
     449  	    {
     450  	      printf ("fputs[1] failed to write data\n");
     451  	      ret |= 1;
     452  	    }
     453  
     454  	  /* Verify that the offset points to the end of the file.  The length
     455  	     of the file would be the original length + the length of data
     456  	     written to it - the amount by which we moved the offset using
     457  	     lseek.  */
     458  	  long offset = ftell (fp);
     459  	  file_len = file_len - 1 + data_len;
     460  
     461  	  if (offset != file_len)
     462  	    {
     463  	      printf ("Incorrect offset.  Expected %zu, but got %ld\n",
     464  		      file_len, offset);
     465  
     466  	      ret |= 1;
     467  	    }
     468  
     469  	  printf ("offset = %ld\n", offset);
     470  	  fclose (fp);
     471  	}
     472      }
     473  
     474    return ret;
     475  }
     476  
     477  /* This test opens a file in append mode, writes some data, and then verifies
     478     that ftell does not trip over it.  */
     479  static int
     480  do_append_test (const char *filename)
     481  {
     482    FILE *fp = NULL;
     483    int ret = 0;
     484    int fd;
     485  
     486    struct test
     487      {
     488        const char *mode;
     489        int fd_mode;
     490      } test_modes[] = {
     491  	  {"a", O_WRONLY},
     492  	  {"a+", O_RDWR}
     493      };
     494  
     495    for (int j = 0; j < 2; j++)
     496      {
     497        for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
     498  	{
     499  	  int fileret;
     500  
     501  	  printf ("\tappend: %s (file, \"%s\"): ", j == 0 ? "fopen" : "fdopen",
     502  		  test_modes[i].mode);
     503  
     504  	  if (j == 0)
     505  	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
     506  	  else
     507  	    fileret = get_handles_fdopen (filename, fd, fp,
     508  					  test_modes[i].fd_mode,
     509  					  test_modes[i].mode);
     510  
     511  	  if (fileret != 0)
     512  	    return fileret;
     513  
     514  	  /* Write some data.  */
     515  	  size_t written = fputs_func (data, fp);
     516  
     517  	  if (written == EOF)
     518  	    {
     519  	      printf ("fputs[1] failed to write all data\n");
     520  	      ret |= 1;
     521  	    }
     522  
     523  	  /* Verify that the offset points to the end of the file.  The file
     524  	     len is maintained by adding data_len each time to reflect the data
     525  	     written to it.  */
     526  	  long offset = ftell (fp);
     527  	  file_len += data_len;
     528  
     529  	  if (offset != file_len)
     530  	    {
     531  	      printf ("Incorrect offset.  Expected %zu, but got %ld\n",
     532  		      file_len, offset);
     533  
     534  	      ret |= 1;
     535  	    }
     536  
     537  	  printf ("offset = %ld\n", offset);
     538  	  fclose (fp);
     539  	}
     540      }
     541  
     542    /* For fdopen in 'a' mode, the file descriptor should not change if the file
     543       is already open with the O_APPEND flag set.  */
     544    fd = open (filename, O_WRONLY | O_APPEND, 0);
     545    if (fd == -1)
     546      {
     547        printf ("open(O_APPEND) failed: %m\n");
     548        return 1;
     549      }
     550  
     551    off_t seek_ret = lseek (fd, file_len - 1, SEEK_SET);
     552    if (seek_ret == -1)
     553      {
     554        printf ("lseek[O_APPEND][0] failed: %m\n");
     555        ret |= 1;
     556      }
     557  
     558    fp = fdopen (fd, "a");
     559    if (fp == NULL)
     560      {
     561        printf ("fdopen(O_APPEND) failed: %m\n");
     562        close (fd);
     563        return 1;
     564      }
     565  
     566    off_t new_seek_ret = lseek (fd, 0, SEEK_CUR);
     567    if (seek_ret == -1)
     568      {
     569        printf ("lseek[O_APPEND][1] failed: %m\n");
     570        ret |= 1;
     571      }
     572  
     573    printf ("\tappend: fdopen (file, \"a\"): O_APPEND: ");
     574  
     575    if (seek_ret != new_seek_ret)
     576      {
     577        printf ("incorrectly modified file offset to %jd, should be %jd",
     578  	      (intmax_t)  new_seek_ret, (intmax_t) seek_ret);
     579        ret |= 1;
     580      }
     581    else
     582      printf ("retained current file offset %jd", (intmax_t) seek_ret);
     583  
     584    new_seek_ret = ftello (fp);
     585  
     586    if (seek_ret != new_seek_ret)
     587      {
     588        printf (", ftello reported incorrect offset %jd, should be %jd\n",
     589  	      (intmax_t) new_seek_ret, (intmax_t) seek_ret);
     590        ret |= 1;
     591      }
     592    else
     593      printf (", ftello reported correct offset %jd\n", (intmax_t) seek_ret);
     594  
     595    fclose (fp);
     596  
     597    return ret;
     598  }
     599  
     600  static int
     601  do_one_test (const char *filename)
     602  {
     603    int ret = 0;
     604  
     605    ret |= do_ftell_test (filename);
     606    ret |= do_write_test (filename);
     607    ret |= do_append_test (filename);
     608    ret |= do_rewind_test (filename);
     609    ret |= do_ftruncate_test (filename);
     610  
     611    return ret;
     612  }
     613  
     614  /* Run a set of tests for ftell for regular files and wide mode files.  */
     615  static int
     616  do_test (void)
     617  {
     618    int ret = 0;
     619    FILE *fp = NULL;
     620    char *filename;
     621    size_t written;
     622    int fd = create_temp_file ("tst-active-handler-tmp.", &filename);
     623  
     624    if (fd == -1)
     625      {
     626        printf ("create_temp_file: %m\n");
     627        return 1;
     628      }
     629  
     630    fp = fdopen (fd, "w");
     631    if (fp == NULL)
     632      {
     633        printf ("fdopen[0]: %m\n");
     634        close (fd);
     635        return 1;
     636      }
     637  
     638    data = char_data;
     639    data_len = strlen (char_data);
     640    file_len = strlen (char_data);
     641    written = fputs (data, fp);
     642  
     643    if (written == EOF)
     644      {
     645        printf ("fputs[1] failed to write data\n");
     646        ret = 1;
     647      }
     648  
     649    fclose (fp);
     650    if (ret)
     651      return ret;
     652  
     653    /* Tests for regular files.  */
     654    puts ("Regular mode:");
     655    fputs_func = (fputs_func_t) fputs;
     656    fgets_func = (fgets_func_t) fgets;
     657    data = char_data;
     658    data_len = strlen (char_data);
     659    ret |= do_one_test (filename);
     660  
     661    /* Truncate the file before repeating the tests in wide mode.  */
     662    fp = fopen (filename, "w");
     663    if (fp == NULL)
     664      {
     665        printf ("fopen failed %m\n");
     666        return 1;
     667      }
     668    fclose (fp);
     669  
     670    /* Tests for wide files.  */
     671    puts ("Wide mode:");
     672    if (setlocale (LC_ALL, "en_US.UTF-8") == NULL)
     673      {
     674        printf ("Cannot set en_US.UTF-8 locale.\n");
     675        return 1;
     676      }
     677    fputs_func = (fputs_func_t) fputws;
     678    fgets_func = (fgets_func_t) fgetws;
     679    data = wide_data;
     680    data_len = wcslen (wide_data);
     681    ret |= do_one_test (filename);
     682  
     683    return ret;
     684  }