summaryrefslogtreecommitdiff
path: root/tests/lib/libc/stdio
diff options
context:
space:
mode:
authorchristos <christos@NetBSD.org>2021-07-08 09:07:46 +0000
committerchristos <christos@NetBSD.org>2021-07-08 09:07:46 +0000
commite9012683e9f9ca668dcf09a04da1394ff1f9c2c4 (patch)
tree1f7997838efd3d05a32569efb50340078ada553b /tests/lib/libc/stdio
parent13af2c069d75835bba87c0473804dc1f7a12919b (diff)
Add interrupted I/O tests (from RVP)
Diffstat (limited to 'tests/lib/libc/stdio')
-rw-r--r--tests/lib/libc/stdio/Makefile8
-rw-r--r--tests/lib/libc/stdio/h_intr.c421
-rw-r--r--tests/lib/libc/stdio/h_makenumbers.c19
-rw-r--r--tests/lib/libc/stdio/h_testnumbers.c16
-rw-r--r--tests/lib/libc/stdio/t_intr.sh81
5 files changed, 544 insertions, 1 deletions
diff --git a/tests/lib/libc/stdio/Makefile b/tests/lib/libc/stdio/Makefile
index bc54e02f9f3..ff3f08d0218 100644
--- a/tests/lib/libc/stdio/Makefile
+++ b/tests/lib/libc/stdio/Makefile
@@ -1,5 +1,6 @@
-# $NetBSD: Makefile,v 1.14 2020/06/29 14:22:11 jruoho Exp $
+# $NetBSD: Makefile,v 1.15 2021/07/08 09:07:46 christos Exp $
+NOMAN=
.include <bsd.own.mk>
TESTSDIR= ${TESTSBASE}/lib/libc/stdio
@@ -13,6 +14,11 @@ TESTS_C+= t_fputc
TESTS_C+= t_popen
TESTS_C+= t_printf
TESTS_C+= t_scanf
+
+TESTS_SH+= t_intr
+
COPTS.t_printf.c += -Wno-format-nonliteral
+PROGS+= h_intr h_makenumbers h_testnumbers
+
.include <bsd.test.mk>
diff --git a/tests/lib/libc/stdio/h_intr.c b/tests/lib/libc/stdio/h_intr.c
new file mode 100644
index 00000000000..e3ea227140b
--- /dev/null
+++ b/tests/lib/libc/stdio/h_intr.c
@@ -0,0 +1,421 @@
+/* $NetBSD: h_intr.c,v 1.1 2021/07/08 09:07:46 christos Exp $ */
+
+/**
+ * Test of interrupted writes to popen()'ed commands.
+ *
+ * Example 1:
+ * ./h_fwrite -c "gzip -t" *.gz
+ *
+ * Example 2:
+ * while :; do ./h_fwrite -b $((12*1024)) -t 10 -c "bzip2 -t" *.bz2; sleep 2; done
+ *
+ * Example 3:
+ * Create checksum file:
+ * find /mnt -type f -exec sha512 -n {} + >SHA512
+ *
+ * Check program:
+ * find /mnt -type f -exec ./h_fwrite -b 512 -c run.sh {} +
+ *
+ * ./run.sh:
+ #!/bin/sh
+ set -eu
+ grep -q "^$(sha512 -q)" SHA512
+ *
+ * Author: RVP at sdf.org
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_intr.c,v 1.1 2021/07/08 09:07:46 christos Exp $");
+
+#include <time.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int process(const char *fn);
+ssize_t maxread(FILE *fp, void *buf, size_t size);
+ssize_t smaxread(FILE *fp, void *buf, size_t size);
+ssize_t maxwrite(FILE *fp, const void *buf, size_t size);
+ssize_t smaxwrite(FILE *fp, const void *buf, size_t size);
+static sig_t xsignal(int signo, sig_t handler);
+static void alarmtimer(int wait);
+static void pr_star(int signo);
+static bool isvalid(const char *s);
+static int do_opts(int argc, char* argv[]);
+static void usage(FILE* fp);
+
+/* Globals */
+static struct options {
+ size_t bsize;
+ size_t ssize;
+ int btype;
+ int tmout;
+ const char *cmd;
+} opts;
+
+static const struct {
+ const char *name;
+ int value;
+} btypes[] = {
+ { "IONBF", _IONBF },
+ { "IOLBF", _IOLBF },
+ { "IOFBF", _IOFBF },
+};
+
+enum {
+ MB = 1024 * 1024,
+ BSIZE = 16 * 1024,
+ DEF_MS = 100,
+ MS = 1000,
+};
+
+
+
+int
+main(int argc, char* argv[])
+{
+ int i, rc = EXIT_SUCCESS;
+
+ i = do_opts(argc, argv);
+ argc -= i;
+ argv += i;
+
+ if (argc == 0) {
+ usage(stderr);
+ return rc;
+ }
+
+ xsignal(SIGPIPE, SIG_IGN);
+ for (i = 0; i < argc; i++) {
+ char *s = strdup(argv[i]);
+ printf("%s...", basename(s));
+ fflush(stdout);
+ free(s);
+
+ sig_t osig = xsignal(SIGALRM, pr_star);
+
+ if (process(argv[i]) == 0)
+ printf("ok\n");
+ else
+ rc = EXIT_FAILURE;
+
+ xsignal(SIGALRM, osig);
+ }
+
+ return rc;
+}
+
+static int
+process(const char *fn)
+{
+ FILE *ifp, *ofp;
+ char *buf;
+ size_t nw = 0;
+ int rc = EXIT_FAILURE;
+ ssize_t n;
+
+ if ((buf = malloc(opts.bsize)) == NULL)
+ err(rc, "buffer alloc failed");
+
+ if ((ifp = fopen(fn, "r")) == NULL) {
+ warn("fopen failed: %s", fn);
+ return rc;
+ }
+
+ if ((ofp = popen(opts.cmd, "w")) == NULL)
+ err(rc, "popen failed `%s'", opts.cmd);
+
+ setvbuf(ofp, NULL, opts.btype, opts.ssize);
+ setvbuf(ifp, NULL, opts.btype, opts.ssize);
+
+ alarmtimer(opts.tmout);
+ while ((n = maxread(ifp, buf, opts.bsize)) > 0) {
+ ssize_t i;
+ if ((i = maxwrite(ofp, buf, n)) == -1) {
+ warn("write failed");
+ break;
+ }
+ nw += i;
+ }
+ alarmtimer(0);
+ // printf("%lu\n", nw);
+
+ fclose(ifp);
+ if (pclose(ofp) != 0)
+ warn("command failed `%s'", opts.cmd);
+ else
+ rc = EXIT_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * maxread - syscall version
+ */
+ssize_t
+smaxread(FILE* fp, void* buf, size_t size)
+{
+ char* p = buf;
+ ssize_t nrd = 0;
+ ssize_t n;
+
+ while (size > 0) {
+ n = read(fileno(fp), p, size);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ return -1;
+ } else if (n == 0)
+ break;
+ p += n;
+ nrd += n;
+ size -= n;
+ }
+ return nrd;
+}
+
+/**
+ * maxread - stdio version
+ */
+ssize_t
+maxread(FILE* fp, void* buf, size_t size)
+{
+ char* p = buf;
+ ssize_t nrd = 0;
+ size_t n;
+
+ while (size > 0) {
+ errno = 0;
+ n = fread(p, 1, size, fp);
+ if (n == 0) {
+ printf("ir.");
+ fflush(stdout);
+ if (errno == EINTR)
+ continue;
+ if (feof(fp) || nrd > 0)
+ break;
+ return -1;
+ }
+ if (n != size)
+ clearerr(fp);
+ p += n;
+ nrd += n;
+ size -= n;
+ }
+ return nrd;
+}
+
+/**
+ * maxwrite - syscall version
+ */
+ssize_t
+smaxwrite(FILE* fp, const void* buf, size_t size)
+{
+ const char* p = buf;
+ ssize_t nwr = 0;
+ ssize_t n;
+
+ while (size > 0) {
+ n = write(fileno(fp), p, size);
+ if (n <= 0) {
+ if (errno == EINTR)
+ n = 0;
+ else
+ return -1;
+ }
+ p += n;
+ nwr += n;
+ size -= n;
+ }
+ return nwr;
+}
+
+/**
+ * maxwrite - stdio version (substrate is buggy)
+ */
+ssize_t
+maxwrite(FILE* fp, const void* buf, size_t size)
+{
+ const char* p = buf;
+ ssize_t nwr = 0;
+ size_t n;
+
+ while (size > 0) {
+ errno = 0;
+ n = fwrite(p, 1, size, fp);
+ if (n == 0) {
+ printf("iw.");
+ fflush(stdout);
+ if (errno == EINTR)
+ continue;
+ if (nwr > 0)
+ break;
+ return -1;
+ }
+ if (n != size)
+ clearerr(fp);
+ p += n;
+ nwr += n;
+ size -= n;
+ }
+ return nwr;
+}
+
+/**
+ * wrapper around sigaction() because we want POSIX semantics:
+ * no auto-restarting of interrupted slow syscalls.
+ */
+static sig_t
+xsignal(int signo, sig_t handler)
+{
+ struct sigaction sa, osa;
+
+ sa.sa_handler = handler;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(signo, &sa, &osa) < 0)
+ return SIG_ERR;
+ return osa.sa_handler;
+}
+
+static void
+alarmtimer(int wait)
+{
+ struct itimerval itv;
+
+ itv.it_value.tv_sec = wait / MS;
+ itv.it_value.tv_usec = (wait - itv.it_value.tv_sec * MS) * MS;
+ itv.it_interval = itv.it_value;
+ setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+/**
+ * Print a `*' each time an alarm signal occurs.
+ */
+static void
+pr_star(int signo)
+{
+ int oe = errno;
+ (void)signo;
+
+#if 0
+ write(1, "*", 1);
+#endif
+ errno = oe;
+}
+
+/**
+ * return true if not empty or blank; FAIL otherwise.
+ */
+static bool
+isvalid(const char *s)
+{
+ if (*s == '\0')
+ return false;
+ return strspn(s, " \t") != strlen(s);
+}
+
+static const char *
+getbtype(int val) {
+ for (size_t i = 0; i < __arraycount(btypes); i++)
+ if (btypes[i].value == val)
+ return btypes[i].name;
+ return "*invalid*";
+}
+
+/**
+ * Print usage information.
+ */
+static void
+usage(FILE* fp)
+{
+ fprintf(fp, "Usage: %s [-b SIZE] [-h] [-t TMOUT] -c CMD FILE...\n",
+ getprogname());
+ fprintf(fp, "%s: Test interrupted writes to popen()ed CMD.\n",
+ getprogname());
+ fprintf(fp, "\n");
+ fprintf(fp, " -b SIZE Buffer size (%lu)\n", opts.bsize);
+ fprintf(fp, " -c CMD Command to run on each FILE.\n");
+ fprintf(fp, " -h This message.\n");
+ fprintf(fp, " -p Buffering type %s.\n", getbtype(opts.btype));
+ fprintf(fp, " -s SIZE stdio buffer size (%lu)\n", opts.ssize);
+ fprintf(fp, " -t TMOUT Interrupt writing to CMD every (%d) ms\n",
+ opts.tmout);
+}
+
+/**
+ * Process program options.
+ */
+static int
+do_opts(int argc, char *argv[])
+{
+ int opt;
+ int i;
+ size_t j;
+
+ /* defaults */
+ opts.btype = _IONBF;
+ opts.ssize = BSIZE; /* 16K */
+ opts.bsize = BSIZE; /* 16K */
+ opts.tmout = DEF_MS; /* 100ms */
+ opts.cmd = "";
+
+ while ((opt = getopt(argc, argv, "b:c:hp:s:t:")) != -1) {
+ switch (opt) {
+ case 'b':
+ i = atoi(optarg);
+ if (i <= 0 || i > MB)
+ errx(EXIT_FAILURE,
+ "buffer size not in range (1 - %d): %d",
+ MB, i);
+ opts.bsize = i;
+ break;
+ case 'c':
+ opts.cmd = optarg;
+ break;
+ case 'h':
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ case 'p':
+ for (j = 0; j < __arraycount(btypes); j++)
+ if (strcmp(btypes[j].name, optarg) == 0) {
+ opts.btype = btypes[j].value;
+ break;
+ }
+ if (j == __arraycount(btypes))
+ errx(EXIT_FAILURE,
+ "unknown buffering type: `%s'", optarg);
+ break;
+ case 's':
+ i = atoi(optarg);
+ if (i <= 0 || i > MB)
+ errx(EXIT_FAILURE,
+ "buffer size not in range (1 - %d): %d",
+ MB, i);
+ opts.ssize = i;
+ break;
+ case 't':
+ i = atoi(optarg);
+ if ((i < 10 || i > 10000) && i != 0)
+ errx(EXIT_FAILURE,
+ "timeout not in range (10ms - 10s): %d", i);
+ opts.tmout = i;
+ break;
+ default:
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!isvalid(opts.cmd))
+ errx(EXIT_FAILURE, "Please specify a valid command with -c");
+
+ return optind;
+}
diff --git a/tests/lib/libc/stdio/h_makenumbers.c b/tests/lib/libc/stdio/h_makenumbers.c
new file mode 100644
index 00000000000..e5a9a38b9b2
--- /dev/null
+++ b/tests/lib/libc/stdio/h_makenumbers.c
@@ -0,0 +1,19 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+
+int
+main(int argc, char *argv[])
+{
+ size_t i = 0;
+ size_t maxi;
+
+ if (argc != 2)
+ errx(EXIT_FAILURE, "missing argument");
+
+ maxi = atoi(argv[1]);
+
+ while (i < maxi)
+ printf("%zu\n", i++);
+ return EXIT_SUCCESS;
+}
diff --git a/tests/lib/libc/stdio/h_testnumbers.c b/tests/lib/libc/stdio/h_testnumbers.c
new file mode 100644
index 00000000000..7148b728c01
--- /dev/null
+++ b/tests/lib/libc/stdio/h_testnumbers.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+
+int
+main(void)
+{
+ char line[1024];
+ size_t i = 0;
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ if ((size_t)atoi(line) != i)
+ errx(EXIT_FAILURE, "bad line %s\n", line);
+ i++;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/tests/lib/libc/stdio/t_intr.sh b/tests/lib/libc/stdio/t_intr.sh
new file mode 100644
index 00000000000..d0d216bd9b9
--- /dev/null
+++ b/tests/lib/libc/stdio/t_intr.sh
@@ -0,0 +1,81 @@
+# $NetBSD: t_intr.sh,v 1.1 2021/07/08 09:07:46 christos Exp $
+#
+# Copyright (c) 2021 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by Christos Zoulas.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+DIR=$(atf_get_srcdir)
+MAX=10000000
+LMAX=1000000
+BSIZE=128000
+SSIZE=256000
+TMOUT=20
+
+h_test() {
+ "${DIR}/h_makenumbers" "$1" > numbers.in
+ "${DIR}/h_intr" \
+ -p "$2" -b ${BSIZE} -s ${SSIZE} -t ${TMOUT} \
+ -c "dd of=numbers.out msgfmt=quiet" numbers.in
+ "${DIR}/h_testnumbers" < numbers.out
+}
+
+atf_test_case stdio_intr_ionbf
+stdio_intr_ionbf_head()
+{
+ atf_set "descr" "Checks stdio EINTR _IONBF"
+}
+stdio_intr_ionbf_body()
+{
+ h_test ${MAX} IONBF
+}
+
+atf_test_case stdio_intr_iolbf
+stdio_intr_iolbf_head()
+{
+ atf_set "descr" "Checks stdio EINTR _IOLBF"
+}
+stdio_intr_iolbf_body()
+{
+ h_test ${LMAX} IOLBF
+}
+
+atf_test_case stdio_intr_iofbf
+stdio_intr_iofbf_head()
+{
+ atf_set "descr" "Checks stdio EINTR _IOFBF"
+}
+stdio_intr_iofbf_body()
+{
+ h_test ${MAX} IOFBF
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case stdio_intr_ionbf
+ atf_add_test_case stdio_intr_iolbf
+ atf_add_test_case stdio_intr_iofbf
+}