Module Name:    src
Committed By:   christos
Date:           Thu Jul  8 09:07:46 UTC 2021

Modified Files:
        src/tests/lib/libc/stdio: Makefile
Added Files:
        src/tests/lib/libc/stdio: h_intr.c h_makenumbers.c h_testnumbers.c
            t_intr.sh

Log Message:
Add interrupted I/O tests (from RVP)


To generate a diff of this commit:
cvs rdiff -u -r1.14 -r1.15 src/tests/lib/libc/stdio/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/lib/libc/stdio/h_intr.c \
    src/tests/lib/libc/stdio/h_makenumbers.c \
    src/tests/lib/libc/stdio/h_testnumbers.c \
    src/tests/lib/libc/stdio/t_intr.sh

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/tests/lib/libc/stdio/Makefile
diff -u src/tests/lib/libc/stdio/Makefile:1.14 src/tests/lib/libc/stdio/Makefile:1.15
--- src/tests/lib/libc/stdio/Makefile:1.14	Mon Jun 29 10:22:11 2020
+++ src/tests/lib/libc/stdio/Makefile	Thu Jul  8 05:07:46 2021
@@ -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>

Added files:

Index: src/tests/lib/libc/stdio/h_intr.c
diff -u /dev/null src/tests/lib/libc/stdio/h_intr.c:1.1
--- /dev/null	Thu Jul  8 05:07:46 2021
+++ src/tests/lib/libc/stdio/h_intr.c	Thu Jul  8 05:07:46 2021
@@ -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;
+}
Index: src/tests/lib/libc/stdio/h_makenumbers.c
diff -u /dev/null src/tests/lib/libc/stdio/h_makenumbers.c:1.1
--- /dev/null	Thu Jul  8 05:07:46 2021
+++ src/tests/lib/libc/stdio/h_makenumbers.c	Thu Jul  8 05:07:46 2021
@@ -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;
+}
Index: src/tests/lib/libc/stdio/h_testnumbers.c
diff -u /dev/null src/tests/lib/libc/stdio/h_testnumbers.c:1.1
--- /dev/null	Thu Jul  8 05:07:46 2021
+++ src/tests/lib/libc/stdio/h_testnumbers.c	Thu Jul  8 05:07:46 2021
@@ -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;
+}
Index: src/tests/lib/libc/stdio/t_intr.sh
diff -u /dev/null src/tests/lib/libc/stdio/t_intr.sh:1.1
--- /dev/null	Thu Jul  8 05:07:46 2021
+++ src/tests/lib/libc/stdio/t_intr.sh	Thu Jul  8 05:07:46 2021
@@ -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
+}

Reply via email to