@@ -218,6 +218,8 @@ tests := \
tst-fphex-wide \
tst-fseek \
tst-fwrite \
+ tst-getline \
+ tst-getline-enomem \
tst-gets \
tst-grouping \
tst-grouping2 \
@@ -320,6 +322,8 @@ tests-special += \
# tests-special
generated += \
+ tst-getline-enomem.mtrace \
+ tst-getline.mtrace \
tst-printf-bz18872-mem.out \
tst-printf-bz18872.c \
tst-printf-bz18872.mtrace \
@@ -424,6 +428,12 @@ tst-printf-fp-leak-ENV = \
tst-scanf-bz27650-ENV = \
MALLOC_TRACE=$(objpfx)tst-scanf-bz27650.mtrace \
LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
+tst-scanf-getline-ENV = \
+ MALLOC_TRACE=$(objpfx)tst-getline.mtrace \
+ LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
+tst-scanf-getline-enomem-ENV = \
+ MALLOC_TRACE=$(objpfx)tst-getline-enomem.mtrace \
+ LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
$(objpfx)tst-unbputc.out: tst-unbputc.sh $(objpfx)tst-unbputc
$(SHELL) $< $(common-objpfx) '$(test-program-prefix)'; \
new file mode 100644
@@ -0,0 +1,78 @@
+/* Test getline: ENOMEM on allocation failure.
+ 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <mcheck.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include <support/check.h>
+#include <support/test-driver.h>
+
+/* Produce a stream of test data based on data in COOKIE (ignored),
+ storing up to SIZE bytes in BUF. */
+
+static ssize_t
+io_read (void *cookie, char *buf, size_t size)
+{
+ memset (buf, 'x', size);
+ return size;
+}
+
+/* Set up a test stream with fopencookie. */
+
+static FILE *
+open_test_stream (void)
+{
+ static cookie_io_functions_t io_funcs = { .read = io_read };
+ static int cookie;
+ FILE *fp = fopencookie (&cookie, "r", io_funcs);
+ TEST_VERIFY_EXIT (fp != NULL);
+ return fp;
+}
+
+int
+do_test (void)
+{
+ FILE *fp;
+ char *lineptr = NULL;
+ size_t size = 0;
+ ssize_t ret;
+ mtrace ();
+ /* Test ENOMEM (and error indicator for stream set) for memory
+ allocation failure. */
+ verbose_printf ("Testing memory allocation failure\n");
+ fp = open_test_stream ();
+ struct rlimit limit;
+ TEST_VERIFY_EXIT (getrlimit (RLIMIT_AS, &limit) == 0);
+ limit.rlim_cur = 32 * 1024 * 1024;
+ TEST_VERIFY_EXIT (setrlimit (RLIMIT_AS, &limit) == 0);
+ errno = 0;
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, ENOMEM);
+ TEST_COMPARE (!!ferror (fp), 1);
+ TEST_COMPARE (feof (fp), 0);
+ free (lineptr);
+ fclose (fp);
+ return 0;
+}
+
+#include <support/test-driver.c>
new file mode 100644
@@ -0,0 +1,387 @@
+/* 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <mcheck.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <support/check.h>
+#include <support/test-driver.h>
+#include <support/support.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+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))
+
+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");
+ errno = 0;
+ ret = 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");
+ errno = 0;
+ ret = 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");
+ errno = 0;
+ ret = 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 4);
+ TEST_COMPARE_BLOB (lineptr, 5, "foo\n", 5);
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 5);
+ TEST_COMPARE_BLOB (lineptr, 6, "bar\0\n", 6);
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 5);
+ TEST_COMPARE_BLOB (lineptr, 6, "\0baz\n", 6);
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 6);
+ TEST_COMPARE_BLOB (lineptr, 7, "te\0st\n", 7);
+ 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 4);
+ TEST_COMPARE_BLOB (lineptr, 5, "foo\n", 5);
+ free (lineptr);
+ lineptr = NULL;
+ size = 0;
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 5);
+ TEST_COMPARE_BLOB (lineptr, 6, "bar\0\n", 6);
+ free (lineptr);
+ lineptr = NULL;
+ size = 0;
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 5);
+ TEST_COMPARE_BLOB (lineptr, 6, "\0baz\n", 6);
+ free (lineptr);
+ lineptr = NULL;
+ size = 0;
+ ret = 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 4);
+ TEST_COMPARE_BLOB (lineptr, 5, "foo\n", 5);
+ free (lineptr);
+ lineptr = xmalloc (1);
+ size = 1;
+ ret = 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 5);
+ TEST_COMPARE_BLOB (lineptr, 6, "\0baz\n", 6);
+ free (lineptr);
+ lineptr = xmalloc (1);
+ size = 1;
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, 6);
+ TEST_COMPARE_BLOB (lineptr, 7, "te\0st\n", 7);
+ 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (ferror (fp), 0);
+ TEST_COMPARE (!!feof (fp), 1);
+ ret = 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (ferror (fp), 0);
+ TEST_COMPARE (!!feof (fp), 1);
+ ret = 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
+ 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 = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, EINVAL);
+ TEST_COMPARE (!!ferror (fp), 1);
+ TEST_COMPARE (feof (fp), 0);
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, EINVAL);
+ 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 = 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);
+ fclose (fp);
+ /* Test EBADF error as a representative example of an fgetc error
+ resulting in an error from getline. We don't try to cover all
+ error cases for fgetc here. */
+ free (lineptr);
+ lineptr = NULL;
+ size = 0;
+ fp = xfopen ("/dev/null", "r");
+ xclose (fileno (fp));
+ ret = getline (&lineptr, &size, fp);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (!!ferror (fp), 1);
+ TEST_COMPARE (feof (fp), 0);
+ fclose (fp);
+ free (lineptr);
+ return 0;
+}
+
+#include <support/test-driver.c>