From: Nick Bowler Date: Sun, 12 May 2024 18:05:47 +0000 (-0400) Subject: Import getline helper from cdecl99. X-Git-Url: https://git.draconx.ca/gitweb/dxcommon.git/commitdiff_plain/refs/heads/master Import getline helper from cdecl99. This is pretty generally useful. The common function is altered from the original version to allow the caller to manage the error conditions, so not completely a drop in replacement but pretty close. --- 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/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..b0f4999 --- /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_ERROR; + } + + 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..912e571 --- /dev/null +++ b/t/getline.c @@ -0,0 +1,123 @@ +/* + * 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; + + 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 */ + tap_result(dx_getline(&line, &n, f) != 0, "dx_getline"); + 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) */ + tap_result(dx_getline(&line, &n, f) != 0, "dx_getline"); + 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; + tap_result(dx_getline(&line, &n, f) != 0, "dx_getline"); + 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; + tap_result(dx_getline(&line, &n, f) != 0, "dx_getline"); + 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 */ + tap_result(dx_getline(&line, &n, f) != 0, "dx_getline"); + 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\""); + } + + 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 <