/* Test for additional fgets error handling. Copyright (C) 2024 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* This avoids compiler warnings about passing NULL where a valid pointer is expected. */ static void *volatile null = NULL; /* Implementation of our FILE stream backend. */ static int bytes_read; static int cookie_valid = 0; struct Cookie { const char *buffer; int bufptr; int bufsz; }; #define VALIDATE_COOKIE() if (! cookie_valid) { \ FAIL ("call to %s after file closed", __FUNCTION__); \ return -1; \ } static ssize_t io_read (void *vcookie, char *buf, size_t size) { struct Cookie *cookie = (struct Cookie *) vcookie; VALIDATE_COOKIE (); if (size > cookie->bufsz - cookie->bufptr) size = cookie->bufsz - cookie->bufptr; memcpy (buf, cookie->buffer + cookie->bufptr, size); cookie->bufptr += size; bytes_read += size; return size; } static ssize_t io_write (void *vcookie, const char *buf, size_t size) { VALIDATE_COOKIE (); FAIL_EXIT1 ("io_write called"); } static int io_seek (void *vcookie, off64_t *position, int whence) { VALIDATE_COOKIE (); FAIL_EXIT1 ("io_seek called"); } static int io_clean (void *vcookie) { struct Cookie *cookie = (struct Cookie *) vcookie; VALIDATE_COOKIE (); cookie->buffer = NULL; cookie->bufsz = 0; cookie->bufptr = 0; cookie_valid = 0; free (cookie); return 0; } cookie_io_functions_t io_funcs = { .read = io_read, .write = io_write, .seek = io_seek, .close = io_clean }; FILE * io_open (const char *buffer, int buflen, const char *mode, void **vcookie) { FILE *f; struct Cookie *cookie; cookie = (struct Cookie *) xcalloc (1, sizeof (struct Cookie)); *vcookie = cookie; cookie_valid = 1; cookie->buffer = buffer; cookie->bufsz = buflen; bytes_read = 0; f = fopencookie (cookie, mode, io_funcs); if (f == NULL) FAIL_EXIT1 ("fopencookie failed"); clearerr (f); return f; } /* The test cases. */ #define my_open(s,l,m) io_open (s, l, m, (void *) &cookie) #define TEST_COMPARE_0x11(buf, len) \ TEST_COMPARE_BLOB (buf + (len), sizeof (buf) - (len), \ buf2, sizeof (buf) - (len)); #define check_flags(f, expected_eof, expected_err) \ { \ if (expected_eof) \ TEST_VERIFY (feof (f) != 0); \ else \ TEST_VERIFY (feof (f) == 0); \ if (expected_err) \ TEST_VERIFY (ferror (f) != 0); \ else \ TEST_VERIFY (ferror (f) == 0); \ } static int do_test (void) { FILE *f; struct Cookie *cookie; char buf [10]; char buf2 [10]; char *returned_string; memset (buf2, 0x11, sizeof (buf2)); printf ("testing base operation...\n"); f = my_open ("hello\n", 6, "r"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, f); TEST_VERIFY (returned_string == buf); TEST_COMPARE_BLOB (buf, bytes_read + 1, "hello\n\0", 7); TEST_COMPARE_0x11 (buf, bytes_read + 1); check_flags (f, 0, 0); fclose (f); printf ("testing zero size file...\n"); f = my_open ("hello\n", 0, "r"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, f); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); check_flags (f, 1, 0); fclose (f); printf ("testing zero size buffer...\n"); f = my_open ("hello\n", 6, "r"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, 0, f); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); check_flags (f, 0, 0); fclose (f); printf ("testing NULL buffer with empty stream...\n"); f = my_open ("hello\n", 0, "r"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (null, sizeof (buf), f); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); check_flags (f, 1, 0); fclose (f); printf ("testing embedded NUL...\n"); f = my_open ("hel\0lo\n", 7, "r"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, f); TEST_VERIFY (returned_string == buf); TEST_COMPARE_BLOB (buf, bytes_read + 1, "hel\0lo\n\0", 8); TEST_COMPARE_0x11 (buf, bytes_read + 1); check_flags (f, 0, 0); fclose (f); printf ("testing writable stream...\n"); f = my_open ("hel\0lo\n", 7, "w"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, f); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); check_flags (f, 0, 1); fclose (f); printf ("testing closed fd stream...\n"); int fd = open ("/dev/null", O_RDONLY); f = fdopen (fd, "r"); close (fd); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, f); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); check_flags (f, 0, 1); fclose (f); #ifdef IO_DEBUG /* These tests only pass if glibc is built with -DIO_DEBUG, but are included for reference. */ printf ("testing NULL descriptor...\n"); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, null); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); printf ("testing closed descriptor...\n"); f = my_open ("hello\n", 7, "r"); fclose (f); memset (buf, 0x11, sizeof (buf)); returned_string = fgets (buf, sizeof (buf) - 1, f); TEST_VERIFY (returned_string == NULL); TEST_VERIFY (bytes_read == 0); #endif return 0; } #include