diff options
Diffstat (limited to 'stdio-common/tst-read-offset.c')
-rw-r--r-- | stdio-common/tst-read-offset.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/stdio-common/tst-read-offset.c b/stdio-common/tst-read-offset.c new file mode 100644 index 0000000..b870660 --- /dev/null +++ b/stdio-common/tst-read-offset.c @@ -0,0 +1,560 @@ +/* Test offsets in files being read, in particular with ungetc. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <dlfcn.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <support/check.h> +#include <support/temp_file.h> +#include <support/xstdio.h> +#include <support/xunistd.h> + +static volatile bool fail = false; + +/* Induce a malloc failure whenever FAIL is set. */ +void * +malloc (size_t sz) +{ + if (fail) + return NULL; + + static void *(*real_malloc) (size_t); + + if (real_malloc == NULL) + real_malloc = dlsym (RTLD_NEXT, "malloc"); + + return real_malloc (sz); +} + +/* The name of the temporary file used by all the tests. */ +static char *filename; + +/* st_blksize value for that file, or BUFSIZ if out of range. */ +static int blksize = BUFSIZ; + +/* Test data, both written to that file and used as an in-memory + stream. */ +char test_data[2 * BUFSIZ]; + +/* Ways to open a test stream for reading (that may use different code + paths in libio). */ +enum test_open_case + { + test_open_fopen, + test_open_fopen_m, + test_open_fopen64, + test_open_fopen64_m, + test_open_fmemopen, + test_open_max + }; + +static const char *const test_open_case_name[test_open_max] = + { + "fopen", "fopen(mmap)", "fopen64", "fopen64(mmap)", "fmemopen" + }; + +static FILE * +open_test_stream (enum test_open_case c) +{ + FILE *fp; + switch (c) + { + case test_open_fopen: + fp = fopen (filename, "r"); + break; + + case test_open_fopen_m: + fp = fopen (filename, "rm"); + break; + + case test_open_fopen64: + fp = fopen64 (filename, "r"); + break; + + case test_open_fopen64_m: + fp = fopen64 (filename, "rm"); + break; + + case test_open_fmemopen: + fp = fmemopen (test_data, 2 * BUFSIZ, "r"); + break; + + default: + abort (); + } + TEST_VERIFY_EXIT (fp != NULL); + return fp; +} + +/* Base locations at which the main test (ungetc calls then doing + something that clears ungetc characters, then checking offset) + starts. */ +enum test_base_loc + { + base_loc_start, + base_loc_blksize, + base_loc_half, + base_loc_bufsiz, + base_loc_eof, + base_loc_max + }; + +static int +base_loc_to_bytes (enum test_base_loc loc, int offset) +{ + switch (loc) + { + case base_loc_start: + return offset; + + case base_loc_blksize: + return blksize + offset; + + case base_loc_half: + return BUFSIZ / 2 + offset; + + case base_loc_bufsiz: + return BUFSIZ + offset; + + case base_loc_eof: + return 2 * BUFSIZ + offset; + + default: + abort (); + } +} + +/* Ways to clear data from ungetc. */ +enum clear_ungetc_case + { + clear_fseek, + clear_fseekm1, + clear_fseekp1, + clear_fseeko, + clear_fseekom1, + clear_fseekop1, + clear_fseeko64, + clear_fseeko64m1, + clear_fseeko64p1, + clear_fsetpos, + clear_fsetposu, + clear_fsetpos64, + clear_fsetpos64u, + clear_fflush, + clear_fflush_null, + clear_fclose, + clear_max + }; + +static const char *const clear_ungetc_case_name[clear_max] = + { + "fseek", "fseek(-1)", "fseek(1)", "fseeko", "fseeko(-1)", "fseeko(1)", + "fseeko64", "fseeko64(-1)", "fseeko64(1)", "fsetpos", "fsetpos(before)", + "fsetpos64", "fsetpos64(before)", "fflush", "fflush(NULL)", "fclose" + }; + +static int +clear_offset (enum clear_ungetc_case c, int num_ungetc) +{ + switch (c) + { + case clear_fseekm1: + case clear_fseekom1: + case clear_fseeko64m1: + return -1; + + case clear_fseekp1: + case clear_fseekop1: + case clear_fseeko64p1: + return 1; + + case clear_fsetposu: + case clear_fsetpos64u: + return num_ungetc; + + default: + return 0; + } +} + +/* The offsets used with fsetpos / fsetpos64. */ +static fpos_t pos; +static fpos64_t pos64; + +static int +do_clear_ungetc (FILE *fp, enum clear_ungetc_case c, int num_ungetc) +{ + int ret; + int offset = clear_offset (c, num_ungetc); + switch (c) + { + case clear_fseek: + case clear_fseekm1: + case clear_fseekp1: + ret = fseek (fp, offset, SEEK_CUR); + break; + + case clear_fseeko: + case clear_fseekom1: + case clear_fseekop1: + ret = fseeko (fp, offset, SEEK_CUR); + break; + + case clear_fseeko64: + case clear_fseeko64m1: + case clear_fseeko64p1: + ret = fseeko64 (fp, offset, SEEK_CUR); + break; + + case clear_fsetpos: + case clear_fsetposu: + ret = fsetpos (fp, &pos); + break; + + case clear_fsetpos64: + case clear_fsetpos64u: + ret = fsetpos64 (fp, &pos64); + break; + + case clear_fflush: + ret = fflush (fp); + break; + + case clear_fflush_null: + ret = fflush (NULL); + break; + + case clear_fclose: + ret = fclose (fp); + break; + + default: + abort(); + } + TEST_COMPARE (ret, 0); + return offset; +} + +static bool +clear_valid (enum test_open_case c, enum clear_ungetc_case cl) +{ + switch (c) + { + case test_open_fmemopen: + /* fflush is not valid for input memory streams, and fclose is + useless for this test for such streams because there is no + underlying open file description for which an offset could be + checked after fclose. */ + switch (cl) + { + case clear_fflush: + case clear_fflush_null: + case clear_fclose: + return false; + + default: + return true; + } + + default: + /* All ways of clearing ungetc state are valid for streams with + an underlying file. */ + return true; + } +} + +static bool +clear_closes_file (enum clear_ungetc_case cl) +{ + switch (cl) + { + case clear_fclose: + return true; + + default: + return false; + } +} + +static void +clear_getpos_before (FILE *fp, enum clear_ungetc_case c) +{ + switch (c) + { + case clear_fsetposu: + TEST_COMPARE (fgetpos (fp, &pos), 0); + break; + + case clear_fsetpos64u: + TEST_COMPARE (fgetpos64 (fp, &pos64), 0); + break; + + default: + break; + } +} + +static void +clear_getpos_after (FILE *fp, enum clear_ungetc_case c) +{ + switch (c) + { + case clear_fsetpos: + TEST_COMPARE (fgetpos (fp, &pos), 0); + break; + + case clear_fsetpos64: + TEST_COMPARE (fgetpos64 (fp, &pos64), 0); + break; + + default: + break; + } +} + +/* Ways to verify results of clearing ungetc data. */ +enum verify_case + { + verify_read, + verify_ftell, + verify_ftello, + verify_ftello64, + verify_fd, + verify_max + }; + +static const char *const verify_case_name[verify_max] = + { + "read", "ftell", "ftello", "ftello64", "fd" + }; + +static bool +valid_fd_offset (enum test_open_case c, enum clear_ungetc_case cl) +{ + switch (c) + { + case test_open_fmemopen: + /* No open file description. */ + return false; + + default: + /* fseek does not necessarily set the offset for the underlying + open file description ("If the most recent operation, other + than ftell(), on a given stream is fflush(), the file offset + in the underlying open file description shall be adjusted to + reflect the location specified by fseek()." in POSIX does not + include the case here where getc was the last operation). + Similarly, fsetpos does not necessarily set that offset + either. */ + switch (cl) + { + case clear_fflush: + case clear_fflush_null: + case clear_fclose: + return true; + + default: + return false; + } + } +} + +static bool +verify_valid (enum test_open_case c, enum clear_ungetc_case cl, + enum verify_case v) +{ + switch (v) + { + case verify_fd: + return valid_fd_offset (c, cl); + + default: + switch (cl) + { + case clear_fclose: + return false; + + default: + return true; + } + } +} + +static bool +verify_uses_fd (enum verify_case v) +{ + switch (v) + { + case verify_fd: + return true; + + default: + return false; + } +} + +static int +read_to_test_loc (FILE *fp, enum test_base_loc loc, int offset) +{ + int to_read = base_loc_to_bytes (loc, offset); + for (int i = 0; i < to_read; i++) + TEST_COMPARE (getc (fp), (unsigned char) i); + return to_read; +} + +static void +setup (void) +{ + int fd = create_temp_file ("tst-read-offset", &filename); + TEST_VERIFY_EXIT (fd != -1); + struct stat64 st; + xfstat64 (fd, &st); + if (st.st_blksize > 0 && st.st_blksize < BUFSIZ) + blksize = st.st_blksize; + printf ("BUFSIZ = %d, blksize = %d\n", BUFSIZ, blksize); + xclose (fd); + FILE *fp = xfopen (filename, "w"); + for (size_t i = 0; i < 2 * BUFSIZ; i++) + { + unsigned char c = i; + TEST_VERIFY_EXIT (fputc (c, fp) == c); + test_data[i] = c; + } + xfclose (fp); +} + +static void +test_one_case (enum test_open_case c, enum test_base_loc loc, int offset, + int num_ungetc, int num_ungetc_diff, bool ungetc_fallback, + enum clear_ungetc_case cl, enum verify_case v) +{ + int full_offset = base_loc_to_bytes (loc, offset); + printf ("Testing %s offset %d ungetc %d different %d %s%s %s\n", + test_open_case_name[c], full_offset, num_ungetc, num_ungetc_diff, + ungetc_fallback ? "fallback " : "", clear_ungetc_case_name[cl], + verify_case_name[v]); + FILE *fp = open_test_stream (c); + int cur_offset = read_to_test_loc (fp, loc, offset); + clear_getpos_before (fp, cl); + for (int i = 0; i < num_ungetc; i++) + { + unsigned char c = (i >= num_ungetc - num_ungetc_diff + ? cur_offset + : cur_offset - 1); + if (ungetc_fallback) + fail = true; + TEST_COMPARE (ungetc (c, fp), c); + fail = false; + cur_offset--; + } + clear_getpos_after (fp, cl); + int fd = -1; + bool done_dup = false; + if (verify_uses_fd (v)) + { + fd = fileno (fp); + TEST_VERIFY (fd != -1); + if (clear_closes_file (cl)) + { + fd = xdup (fd); + done_dup = true; + } + } + cur_offset += do_clear_ungetc (fp, cl, num_ungetc); + switch (v) + { + case verify_read: + for (; + cur_offset <= full_offset + 1 && cur_offset < 2 * BUFSIZ; + cur_offset++) + TEST_COMPARE (getc (fp), (unsigned char) cur_offset); + break; + + case verify_ftell: + TEST_COMPARE (ftell (fp), cur_offset); + break; + + case verify_ftello: + TEST_COMPARE (ftello (fp), cur_offset); + break; + + case verify_ftello64: + TEST_COMPARE (ftello64 (fp), cur_offset); + break; + + case verify_fd: + TEST_COMPARE (lseek (fd, 0, SEEK_CUR), cur_offset); + break; + + default: + abort (); + } + if (! clear_closes_file (cl)) + { + int ret = fclose (fp); + TEST_COMPARE (ret, 0); + } + if (done_dup) + xclose (fd); +} + +int +do_test (void) +{ + setup (); + for (enum test_open_case c = 0; c < test_open_max; c++) + for (enum test_base_loc loc = 0; loc < base_loc_max; loc++) + for (int offset = -2; offset <= 3; offset++) + for (int num_ungetc = 0; + num_ungetc <= 2 && num_ungetc <= base_loc_to_bytes (loc, offset); + num_ungetc++) + for (int num_ungetc_diff = 0; + num_ungetc_diff <= num_ungetc; + num_ungetc_diff++) + for (int ungetc_fallback = 0; + ungetc_fallback <= (num_ungetc == 1 ? 1 : 0); + ungetc_fallback++) + for (enum clear_ungetc_case cl = 0; cl < clear_max; cl++) + { + if (!clear_valid (c, cl)) + continue; + if (base_loc_to_bytes (loc, offset) > 2 * BUFSIZ) + continue; + if ((base_loc_to_bytes (loc, offset) + - num_ungetc + + clear_offset (cl, num_ungetc)) < 0) + continue; + if ((base_loc_to_bytes (loc, offset) + - num_ungetc + + clear_offset (cl, num_ungetc)) > 2 * BUFSIZ) + continue; + for (enum verify_case v = 0; v < verify_max; v++) + { + if (!verify_valid (c, cl, v)) + continue; + test_one_case (c, loc, offset, num_ungetc, + num_ungetc_diff, ungetc_fallback, cl, v); + } + } + return 0; +} + +#include <support/test-driver.c> |