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-2022 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
do_ftruncate_test(const char * filename)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
do_rewind_test(const char * filename)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
do_ftell_test(const char * filename)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
do_write_test(const char * filename)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
do_append_test(const char * filename)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
do_one_test(const char * filename)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
do_test(void)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 }
685