/* Test getline. 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 static struct test_data { /* Input test data for fopencookie stream. */ const char *in_data; /* The amount of test data left. */ size_t in_data_left; /* Error number for forcing an error on next read. */ int in_error; /* Error number for forcing an error (rather than EOF) after all bytes read. */ int in_error_after; } the_cookie; /* Produce a stream of test data based on data in COOKIE, storing up to SIZE bytes in BUF. */ static ssize_t io_read (void *cookie, char *buf, size_t size) { struct test_data *p = cookie; if (p->in_error) { errno = p->in_error; return -1; } if (size > p->in_data_left) size = p->in_data_left; memcpy (buf, p->in_data, size); p->in_data += size; p->in_data_left -= size; if (p->in_data_left == 0) p->in_error = p->in_error_after; return size; } /* Set up a test stream with fopencookie. */ static FILE * open_test_stream (const char *in_data, size_t size) { static cookie_io_functions_t io_funcs = { .read = io_read }; the_cookie.in_data = in_data; the_cookie.in_data_left = size; the_cookie.in_error = 0; the_cookie.in_error_after = 0; FILE *fp = fopencookie (&the_cookie, "r", io_funcs); TEST_VERIFY_EXIT (fp != NULL); return fp; } /* Set up a test stream with fopencookie, using data from a string literal. */ #define OPEN_TEST_STREAM(IN_DATA) open_test_stream (IN_DATA, sizeof (IN_DATA)) /* Wrap getline to verify that (as per the glibc manual), *LINEPTR is returned as non-null and with at least *N bytes (even on error or EOF). Also clear errno for the benefit of tests that check the value of errno after the call. */ ssize_t wrap_getline (char **lineptr, size_t *n, FILE *stream) { errno = 0; ssize_t ret = getline (lineptr, n, stream); if (lineptr != NULL && n != NULL) { TEST_VERIFY (*lineptr != NULL); TEST_VERIFY (malloc_usable_size (*lineptr) >= *n); } return ret; } int do_test (void) { FILE *fp; char *lineptr = NULL; size_t size = 0; ssize_t ret; mtrace (); /* Test failure with EINVAL (and error indicator for stream set) if lineptr is a null pointer. */ verbose_printf ("Testing lineptr == NULL\n"); fp = OPEN_TEST_STREAM ("test"); ret = wrap_getline (NULL, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (errno, EINVAL); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); /* Test failure with EINVAL (and error indicator for stream set) if n is a null pointer. */ verbose_printf ("Testing n == NULL\n"); fp = OPEN_TEST_STREAM ("test"); ret = wrap_getline (&lineptr, NULL, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (errno, EINVAL); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); /* Test failure with EINVAL (and error indicator for stream set) if both lineptr and n are null pointers. */ verbose_printf ("Testing lineptr == NULL and n == NULL\n"); fp = OPEN_TEST_STREAM ("test"); ret = wrap_getline (NULL, NULL, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (errno, EINVAL); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); /* Test normal line, fitting in available space (including case with null bytes). */ verbose_printf ("Testing normal nonempty input\n"); lineptr = xmalloc (10); size = 10; fp = OPEN_TEST_STREAM ("foo\nbar\0\n\0baz\nte\0st\n"); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "foo\n", 5); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "bar\0\n", 6); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "\0baz\n", 6); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 6); TEST_COMPARE_BLOB (lineptr, 7, "te\0st\n", 7); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 1); TEST_COMPARE_BLOB (lineptr, 1, "", 1); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (ferror (fp), 0); TEST_COMPARE (!!feof (fp), 1); fclose (fp); /* Test normal line, with reallocation (including case with null bytes). */ verbose_printf ("Testing normal nonempty input with reallocation\n"); free (lineptr); lineptr = NULL; size = 0; fp = OPEN_TEST_STREAM ("foo\nbar\0\n\0baz\nte\0st\n" "foo\nbar\0\n\0baz\nte\0st\n"); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "foo\n", 5); free (lineptr); lineptr = NULL; size = 0; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "bar\0\n", 6); free (lineptr); lineptr = NULL; size = 0; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "\0baz\n", 6); free (lineptr); lineptr = NULL; size = 0; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 6); TEST_COMPARE_BLOB (lineptr, 7, "te\0st\n", 7); free (lineptr); lineptr = xmalloc (1); size = 1; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "foo\n", 5); free (lineptr); lineptr = xmalloc (1); size = 1; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "bar\0\n", 6); free (lineptr); lineptr = xmalloc (1); size = 1; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "\0baz\n", 6); free (lineptr); lineptr = xmalloc (1); size = 1; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 6); TEST_COMPARE_BLOB (lineptr, 7, "te\0st\n", 7); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 1); TEST_COMPARE_BLOB (lineptr, 1, "", 1); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (ferror (fp), 0); TEST_COMPARE (!!feof (fp), 1); fclose (fp); /* Test EOF before delimiter but after some bytes read, fitting in available space (including case with null bytes). */ verbose_printf ("Testing EOF before delimiter\n"); free (lineptr); lineptr = xmalloc (10); size = 10; fp = open_test_stream ("foo", 3); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 3); TEST_COMPARE_BLOB (lineptr, 4, "foo", 4); fclose (fp); free (lineptr); lineptr = xmalloc (10); size = 10; fp = open_test_stream ("bar\0", 4); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "bar\0", 5); fclose (fp); free (lineptr); lineptr = xmalloc (10); size = 10; fp = open_test_stream ("\0baz", 4); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "\0baz", 5); fclose (fp); free (lineptr); lineptr = xmalloc (10); size = 10; fp = open_test_stream ("te\0st", 5); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "te\0st", 6); fclose (fp); /* Test EOF before delimiter but after some bytes read, with reallocation (including case with null bytes). */ verbose_printf ("Testing EOF before delimiter with reallocation\n"); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("foo", 3); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 3); TEST_COMPARE_BLOB (lineptr, 4, "foo", 4); fclose (fp); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("bar\0", 4); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "bar\0", 5); fclose (fp); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("\0baz", 4); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "\0baz", 5); fclose (fp); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("te\0st", 5); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "te\0st", 6); fclose (fp); free (lineptr); lineptr = xmalloc (1); size = 1; fp = open_test_stream ("foo", 3); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 3); TEST_COMPARE_BLOB (lineptr, 4, "foo", 4); fclose (fp); free (lineptr); lineptr = xmalloc (1); size = 1; fp = open_test_stream ("bar\0", 4); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "bar\0", 5); fclose (fp); free (lineptr); lineptr = xmalloc (1); size = 1; fp = open_test_stream ("\0baz", 4); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 4); TEST_COMPARE_BLOB (lineptr, 5, "\0baz", 5); fclose (fp); free (lineptr); lineptr = xmalloc (1); size = 1; fp = open_test_stream ("te\0st", 5); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 5); TEST_COMPARE_BLOB (lineptr, 6, "te\0st", 6); fclose (fp); /* Test EOF with no bytes read (nothing is specified about anything written to the buffer), including EOF again when already at end of file. */ verbose_printf ("Testing EOF with no bytes read\n"); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("", 0); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (ferror (fp), 0); TEST_COMPARE (!!feof (fp), 1); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (ferror (fp), 0); TEST_COMPARE (!!feof (fp), 1); fclose (fp); free (lineptr); lineptr = xmalloc (1); size = 1; fp = open_test_stream ("", 0); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (ferror (fp), 0); TEST_COMPARE (!!feof (fp), 1); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (ferror (fp), 0); TEST_COMPARE (!!feof (fp), 1); fclose (fp); /* Test error occurring with no bytes read, including calling wrap_getline again while the file is in error state. */ verbose_printf ("Testing error with no bytes read\n"); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("", 0); the_cookie.in_error = EINVAL; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (errno, EINVAL); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); /* Make sure error state is sticky. */ the_cookie.in_error = 0; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); /* Test error occurring after some bytes read. Specifications are ambiguous here; at least in the fopencookie case used for testing, glibc returns the partial line (but with the error indicator on the stream set). */ verbose_printf ("Testing error after some bytes read\n"); free (lineptr); lineptr = NULL; size = 0; fp = open_test_stream ("foo", 3); the_cookie.in_error_after = EINVAL; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, 3); TEST_COMPARE_BLOB (lineptr, 4, "foo", 4); TEST_COMPARE (errno, EINVAL); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); /* Make sure error state is sticky. */ the_cookie.in_error = 0; ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); /* Test EBADF error as a representative example of an fgetc error resulting in an error from wrap_getline. We don't try to cover all error cases for fgetc here. */ verbose_printf ("Testing EBADF error\n"); free (lineptr); lineptr = NULL; size = 0; fp = xfopen ("/dev/null", "r"); xclose (fileno (fp)); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (errno, EBADF); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); /* Test EAGAIN error as an example of an fgetc error on a valid file descriptor. */ verbose_printf ("Testing EAGAIN error\n"); free (lineptr); lineptr = NULL; size = 0; int pipefd[2]; xpipe (pipefd); ret = fcntl (pipefd[0], F_SETFL, O_NONBLOCK); TEST_VERIFY_EXIT (ret == 0); fp = fdopen (pipefd[0], "r"); TEST_VERIFY_EXIT (fp != NULL); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (errno, EAGAIN); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); /* Make sure error state is sticky (even after more data is available to read). */ xwrite (pipefd[1], "x\n", 2); ret = wrap_getline (&lineptr, &size, fp); TEST_COMPARE (ret, -1); TEST_COMPARE (!!ferror (fp), 1); TEST_COMPARE (feof (fp), 0); fclose (fp); free (lineptr); return 0; } #include