[v3,0/7] Fix some libm static issues
[platform/upstream/glibc.git] / 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-2024 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 }