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