From: Nick Bowler Date: Sat, 18 May 2024 20:14:36 +0000 (-0400) Subject: dx_getline: Fix EOF handling in standard C fallback. X-Git-Url: https://git.draconx.ca/gitweb/dxcommon.git/commitdiff_plain/HEAD?hp=f5df895ced92ac539cafd825efee98835d8bf0d3 dx_getline: Fix EOF handling in standard C fallback. The return value of the function at end of input was not tested so of course it does not work: in the standard C fallback, a mistake was introduced during the import and it returns DX_GETLINE_ERROR instead of the expected DX_GETLINE_EOF. Easy enough to fix and update the test program to check this. --- diff --git a/Makefile.am b/Makefile.am index be73056..377ca02 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ -# Copyright © 2015, 2019, 2021-2023 Nick Bowler +# Copyright © 2015, 2019, 2021-2024 Nick Bowler # -# License WTFPL2: Do What The Fuck You Want To Public License, version 2. -# This is free software: you are free to do what the fuck you want to. +# License GPLv3+: GNU General Public License version 3 or any later version. +# This is free software: you are free to change and redistribute it. # There is NO WARRANTY, to the extent permitted by law. ACLOCAL_AMFLAGS = -I m4 @@ -11,9 +11,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/src $(STUB_INCLUDES) EXTRA_DIST = scripts/fix-gnulib.pl scripts/fix-ltdl.pl \ scripts/bake-config.awk scripts/gen-options.awk \ scripts/gen-strtab.awk scripts/gen-tree.awk scripts/join.awk \ - scripts/pe-subsys.awk src/copysym.h src/help.h src/pack.h \ - src/tap.h t/tapcheck.sh t/getopt/getopt.h t/nls/gettext.h \ - t/nls/mbswidth.h tests/data/gnulib.mk + scripts/pe-subsys.awk src/copysym.h src/getline.h src/help.h \ + src/pack.h src/tap.h t/tapcheck.sh t/getopt/getopt.h \ + t/nls/gettext.h t/nls/mbswidth.h tests/data/gnulib.mk check_LIBRARIES = t/libdummy.a t/libempty.a @@ -60,6 +60,9 @@ libnlscopysym_a_SOURCES = src/copysym.c libnlscopysym_a_CPPFLAGS = $(AM_CPPFLAGS) -DENABLE_NLS libnlscopysym_a_SHORTNAME = nls +check_PROGRAMS += t/getline +t_getline_SOURCES = t/getline.c src/tap.c + DISTCLEANFILES = SUFFIXES = diff --git a/m4/align.m4 b/m4/align.m4 index 945beff..a69e640 100644 --- a/m4/align.m4 +++ b/m4/align.m4 @@ -59,7 +59,7 @@ AC_DEFUN([DX_C_ALIGNAS], [no/no|*/c89|*/c99], [AC_CACHE_CHECK([if $CC supports _Alignas], [dx_cv_have_alignas], [dx_cv_have_alignas=no -for _dx_alignas +for _dx_alignas dnl Eat newline to work around bash-5 parsing bug. in '_Alignas(X)' '__attribute__((__aligned__(X)))' '__declspec(align(X))' do AC_COMPUTE_INT([_dx_tmp], diff --git a/m4/getline.m4 b/m4/getline.m4 new file mode 100644 index 0000000..8356a62 --- /dev/null +++ b/m4/getline.m4 @@ -0,0 +1,31 @@ +# Copyright © 2024 Nick Bowler +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# DX_CHECK_GETLINE +# +# Check whether or not the getline function is available. If it is, the macro +# HAVE_GETLINE is defined to 1 and the cache variable dx_cv_have_getline is set +# to "yes". Otherwise, dx_cv_have_getline is set to "no". +AC_DEFUN([DX_CHECK_GETLINE], +[AC_CACHE_CHECK([for getline], [dx_cv_have_getline], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +], [ssize_t (*x)() = getline; +char *p = 0; +size_t n = 0; +return getline(&p, &n, stdin); +])], [dx_cv_have_getline=yes], [dx_cv_have_getline=no])]) +AS_CASE([$dx_cv_have_getline], [yes], + [AC_DEFINE([HAVE_GETLINE], [1], + [Define to 1 if the getline function is available.])])]) diff --git a/src/getline.h b/src/getline.h new file mode 100644 index 0000000..f659c60 --- /dev/null +++ b/src/getline.h @@ -0,0 +1,121 @@ +/* + * Copyright © 2024 Nick Bowler + * + * getline-like function which removes trailing newline (if any). + * + * If HAVE_GETLINE is not defined (or defined to 0) then a standard C + * implementation is used. Othewrise, the POSIX getline function + * is called. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef DX_GETLINE_H_ +#define DX_GETLINE_H_ + +#include +#include +#include +#include + +/* + * Size of the initial buffer allocated internally by the fallback + * implementation when *linebuf is NULL. + */ +#ifndef DX_GETLINE_INITIAL_ALLOC +# define DX_GETLINE_INITIAL_ALLOC 75 +#endif + +enum { + DX_GETLINE_OK = 1, + DX_GETLINE_EOF = 0, + DX_GETLINE_ERROR = -1, + DX_GETLINE_ENOMEM = -2 +}; + +/* + * Wrapper around getline with standard C fallback. + * + * Note that for portability to some getline implementations (e.g., FreeBSD) + * both *linebuf and *n should be set to zero on the initial call. + * + * If pre-allocating a buffer, ensure that its size is more than 1 byte, + * otherwise AIX 7.2 getline fails to work correctly. + * + * Returns 1 (DX_GETLINE_OK) if a line was read or 0 (DX_GETLINE_EOF) if + * no line could be read because the end of file was reached. + * + * On failure, returns a negative value. If the C library input call failed + * then the return value is DX_GETLINE_ERROR and the reason for the failure + * should be available in errno. + * + * For the standard C fallback only, a return value of DX_GETLINE_ENOMEM + * indicates that the buffer allocation could not be expanded to fit the + * input line. + */ +static inline int dx_getline(char **linebuf, size_t *n, FILE *f) +{ +#if HAVE_GETLINE + ssize_t rc; + + if ((rc = getline(linebuf, n, f)) < 0) { + if (ferror(f)) + return DX_GETLINE_ERROR; + return DX_GETLINE_EOF; + } + + if (rc-- && (*linebuf)[rc] == '\n') + (*linebuf)[rc] = '\0'; + + return DX_GETLINE_OK; +#else + char *work = *linebuf; + size_t pos = 0; + size_t sz; + + if (!work) { + sz = DX_GETLINE_INITIAL_ALLOC; + goto initial_alloc; + } + + for (sz = *n;;) { + if (!fgets(&work[pos], sz - pos, f)) { + if (ferror(f)) + return DX_GETLINE_ERROR; + + return pos ? DX_GETLINE_OK : DX_GETLINE_EOF; + } + + pos += strlen(&work[pos]); + if (work[pos-1] == '\n') { + work[pos-1] = '\0'; + return DX_GETLINE_OK; + } + + if (sz > INT_MAX/2 || sz > ((size_t)-1)/4) + break; + + sz = ((sz*4) + 2) / 3; +initial_alloc: + work = realloc(work, sz); + if (!work) + break; + *linebuf = work; + *n = sz; + } + + return DX_GETLINE_ENOMEM; +#endif +} + +#endif diff --git a/t/.gitignore b/t/.gitignore index 39c659a..862ed4f 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -1,4 +1,5 @@ /copysym +/getline /helpdesc /helpopt /helpopt2 diff --git a/t/getline.c b/t/getline.c new file mode 100644 index 0000000..f7024ea --- /dev/null +++ b/t/getline.c @@ -0,0 +1,162 @@ +/* + * Copyright © 2024 Nick Bowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +void tap_diag(const char *fmt, ...); + +#undef HAVE_GETLINE +#define DX_GETLINE_INITIAL_ALLOC 2 +#include "getline.h" +#include "tap.h" + +#include + +#define LONGLINE "this line is pretty long and may require scaling the buffer multiple times" + +static const char testdata[] = + "\n" + LONGLINE "\n" + LONGLINE "\n" + LONGLINE "\n" + "hellorld\n"; + +int main(void) +{ + char *line = NULL; + size_t long_size; + size_t n = 0; + FILE *f; + int rc; + + if (!(f = tmpfile())) + tap_bail_out("tmpfile failed: %s\n", strerror(errno)); + + if (fwrite(testdata, sizeof testdata-1, 1, f) != 1) + tap_bail_out("fwrite failed: %s\n", strerror(errno)); + + if (fflush(f) != 0) + tap_bail_out("fflush failed: %s\n", strerror(errno)); + + if (fseek(f, 0, SEEK_SET) != 0) + tap_bail_out("fseek failed: %s\n", strerror(errno)); + + /* First line: empty */ + rc = dx_getline(&line, &n, f); + if (!tap_result(rc == DX_GETLINE_OK, "dx_getline")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %d", rc); + tap_diag(" Expected: %d (DX_GETLINE_OK)", DX_GETLINE_OK); + } + if (!tap_result(n == 2, "alloc size")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %lu", (unsigned long)n); + tap_diag(" Expected: 2"); + } + if (!tap_result(line[0] == 0, "returned string")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: \"%.*s\"", (int)n, line); + tap_diag(" Expected: \"\""); + } + + /* Next line: long line (buffer needs expanding) */ + rc = dx_getline(&line, &n, f); + if (!tap_result(rc == DX_GETLINE_OK, "dx_getline")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %d", rc); + tap_diag(" Expected: %d (DX_GETLINE_OK)", DX_GETLINE_OK); + } + if (!tap_result(n > sizeof LONGLINE, "alloc size")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %lu", (unsigned long)n); + tap_diag(" Expected: >%u", (unsigned)(sizeof LONGLINE)); + } + if (!tap_result(!strcmp(line, LONGLINE), "returned string")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: \"%.*s\"", (int)n, line); + tap_diag(" Expected: \"" LONGLINE "\""); + } + + /* Next line: long line (buffer does not need expanding) */ + long_size = n; + rc = dx_getline(&line, &n, f); + if (!tap_result(rc == DX_GETLINE_OK, "dx_getline")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %d", rc); + tap_diag(" Expected: %d (DX_GETLINE_OK)", DX_GETLINE_OK); + } + if (!tap_result(n == long_size, "alloc size")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %lu", (unsigned long)n); + tap_diag(" Expected: %lu", (unsigned long)long_size); + } + if (!tap_result(!strcmp(line, LONGLINE), "returned string")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: \"%.*s\"", (int)n, line); + tap_diag(" Expected: \"" LONGLINE "\""); + } + + /* Next line: long line (new buffer allocation) */ + free(line); line = NULL; n = 0; + rc = dx_getline(&line, &n, f); + if (!tap_result(rc == DX_GETLINE_OK, "dx_getline")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %d", rc); + tap_diag(" Expected: %d (DX_GETLINE_OK)", DX_GETLINE_OK); + } + if (!tap_result(n == long_size, "alloc size")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %lu", (unsigned long)n); + tap_diag(" Expected: %lu", (unsigned long)long_size); + } + if (!tap_result(!strcmp(line, LONGLINE), "returned string")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: \"%.*s\"", (int)n, line); + tap_diag(" Expected: \"" LONGLINE "\""); + } + + /* Next line: short line */ + rc = dx_getline(&line, &n, f); + if (!tap_result(rc == DX_GETLINE_OK, "dx_getline")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %d", rc); + tap_diag(" Expected: %d (DX_GETLINE_OK)", DX_GETLINE_OK); + } + if (!tap_result(n == long_size, "alloc size")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %lu", (unsigned long)n); + tap_diag(" Expected: %lu", (unsigned long)long_size); + } + if (!tap_result(!strcmp(line, "hellorld"), "returned string")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: \"%.*s\"", (int)n, line); + tap_diag(" Expected: \"hellorld\""); + } + + /* Next line: end of file */ + rc = dx_getline(&line, &n, f); + if (!tap_result(rc == DX_GETLINE_EOF, "dx_getline (end of file)")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %d", rc); + tap_diag(" Expected: %d (DX_GETLINE_EOF)", DX_GETLINE_EOF); + } + if (!tap_result(n == long_size, "alloc size")) { + tap_diag("Failed, unexpected result"); + tap_diag(" Received: %lu", (unsigned long)n); + tap_diag(" Expected: %lu", (unsigned long)long_size); + } + + tap_done(); +} diff --git a/t/libdummy.c b/t/libdummy.c index 1bfa48a..e51fbc7 100644 --- a/t/libdummy.c +++ b/t/libdummy.c @@ -15,4 +15,4 @@ struct MEVENT; int getmouse_nc_(struct MEVENT *mev) { return 0; } int getmouse_bogus_(void *p) { return 0; } -void dx_closelog(void) { } +void dx_link_stub(void) { } diff --git a/tests/functions.at b/tests/functions.at index 01a96a7..bede78e 100644 --- a/tests/functions.at +++ b/tests/functions.at @@ -1,8 +1,8 @@ -dnl Copyright © 2015, 2021-2024 Nick Bowler -dnl -dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. -dnl This is free software: you are free to do what the fuck you want to. -dnl There is NO WARRANTY, to the extent permitted by law. +# Copyright © 2015, 2021-2024 Nick Bowler +# +# License GPLv3+: GNU General Public License version 3 or any later version. +# This is free software: you are free to change and redistribute it. +# There is NO WARRANTY, to the extent permitted by law. AT_BANNER([Binary packing functions]) @@ -175,3 +175,4 @@ AT_CLEANUP AT_BANNER([Miscellaneous functions]) TEST_TAP_SIMPLE([copyright_symbol], [copysym], [], []) +TEST_TAP_SIMPLE([do_getline], [getline], [], [getline]) diff --git a/tests/libs.at b/tests/libs.at index f7cf534..8a7be8d 100644 --- a/tests/libs.at +++ b/tests/libs.at @@ -1,17 +1,17 @@ -dnl Copyright © 2019-2020, 2022-2023 Nick Bowler -dnl -dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. -dnl This is free software: you are free to do what the fuck you want to. -dnl There is NO WARRANTY, to the extent permitted by law. +# Copyright © 2019-2020, 2022-2024 Nick Bowler +# +# License GPLv3+: GNU General Public License version 3 or any later version. +# This is free software: you are free to change and redistribute it. +# There is NO WARRANTY, to the extent permitted by law. AT_BANNER([Library tests]) -dnl TEST_DUMMY_PKGCONFIG([cflags], [libs]) -dnl -dnl Create a hack pkg-config script in the current working directory which -dnl responds to --cflags and --libs with the provided values. The macro -dnl arguments should each be a single shell word, suitable for the right -dnl hand side of a shell assignment. +# TEST_DUMMY_PKGCONFIG([cflags], [libs]) +# +# Create a hack pkg-config script in the current working directory which +# responds to --cflags and --libs with the provided values. The macro +# arguments should each be a single shell word, suitable for the right +# hand side of a shell assignment. m4_define([TEST_DUMMY_PKGCONFIG], [[cat >pkg-config <