diff mbox series

[3/3] input: Use tee(2) for stdin pipe

Message ID 45fc23774f1f20d430fa9228782b9faf73452ae5.1714900988.git.herbert@gondor.apana.org.au (mailing list archive)
State Changes Requested
Delegated to: Herbert Xu
Headers show
Series Improve performance when reading stdin | expand

Commit Message

Herbert Xu May 5, 2024, 9:24 a.m. UTC
use tee(2) to peek at pipes in order to avoid reading one byte at
a time.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
 configure.ac |  2 +-
 src/input.c  | 71 ++++++++++++++++++++++++++++++++++++++++++++++------
 src/system.h |  7 ++++++
 3 files changed, 72 insertions(+), 8 deletions(-)
diff mbox series

Patch

diff --git a/configure.ac b/configure.ac
index cb55c3f..50effc0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -89,7 +89,7 @@  dnl Checks for library functions.
 AC_CHECK_FUNCS(bsearch faccessat getpwnam getrlimit isalpha killpg \
 	       memfd_create mempcpy \
 	       sigsetmask stpcpy strchrnul strsignal strtod strtoimax \
-	       strtoumax sysconf)
+	       strtoumax sysconf tee)
 
 dnl Check whether it's worth working around FreeBSD PR kern/125009.
 dnl The traditional behavior of access/faccessat is crazy, but
diff --git a/src/input.c b/src/input.c
index b84ecec..8f8c173 100644
--- a/src/input.c
+++ b/src/input.c
@@ -57,14 +57,18 @@ 
 #include "redir.h"
 #include "shell.h"
 #include "syntax.h"
+#include "system.h"
 #include "trap.h"
 
 #define IBUFSIZ (BUFSIZ + PUNGETC_MAX + 1)
 
+MKINIT
 struct stdin_state {
 	tcflag_t canon;
 	off_t seekable;
 	struct termios tios;
+	int pip[2];
+	int pending;
 };
 
 MKINIT struct parsefile basepf;	/* top level input file */
@@ -85,6 +89,7 @@  static int preadbuffer(void);
 
 #ifdef mkinit
 INCLUDE <stdio.h>
+INCLUDE <string.h>
 INCLUDE <termios.h>
 INCLUDE <unistd.h>
 INCLUDE "input.h"
@@ -117,6 +122,11 @@  FORKRESET {
 		close(parsefile->fd);
 		parsefile->fd = 0;
 	}
+	if (stdin_state.pip[0]) {
+		close(stdin_state.pip[0]);
+		close(stdin_state.pip[1]);
+		memset(stdin_state.pip, 0, sizeof(stdin_state.pip));
+	}
 }
 
 POSTEXITRESET {
@@ -145,6 +155,43 @@  static bool stdin_bufferable(void)
 	return st->canon || st->seekable;
 }
 
+static void flush_tee(void *buf, int nr, int pending)
+{
+	while (pending > 0) {
+		int err;
+
+		err = read(0, buf, nr > pending ? pending : nr);
+		if (err > 0)
+			pending -= err;
+	}
+}
+
+static int stdin_tee(void *buf, int nr)
+{
+	int err;
+
+	if (stdin_istty)
+		return 0;
+
+	if (!stdin_state.pip[0]) {
+		err = pipe(stdin_state.pip);
+		if (err < 0)
+			return err;
+		if (stdin_state.pip[0] < 10)
+			stdin_state.pip[0] = savefd(stdin_state.pip[0],
+						    stdin_state.pip[0]);
+		if (stdin_state.pip[1] < 10)
+			stdin_state.pip[1] = savefd(stdin_state.pip[1],
+						    stdin_state.pip[1]);
+	}
+
+	flush_tee(buf, nr, stdin_state.pending);
+
+	err = tee(0, stdin_state.pip[1], nr, 0);
+	stdin_state.pending = err;
+	return err;
+}
+
 static void freestrings(struct strpush *sp)
 {
 	INTOFF;
@@ -280,10 +327,17 @@  retry:
 	}
 #endif
 
-	if (!fd && !stdin_bufferable())
-		nr = 1;
+	if (!fd && !stdin_bufferable()) {
+		nr = stdin_tee(buf, nr);
+		fd = stdin_state.pip[0];
+		if (nr <= 0) {
+			fd = 0;
+			nr = 1;
+		}
+	}
 
-	nr = read(fd, buf, nr);
+	if (nr >= 0)
+		nr = read(fd, buf, nr);
 
 	if (nr < 0) {
 		if (errno == EINTR && !(basepf.prev && pending_sig))
@@ -621,12 +675,15 @@  void __attribute__((noinline)) flush_input(void)
 {
 	int left = basepf.nleft + input_get_lleft(&basepf);
 
-	if (stdin_state.seekable && left) {
-		INTOFF;
+	INTOFF;
+	if (stdin_state.seekable && left)
 		lseek(0, -left, SEEK_CUR);
-		input_set_lleft(&basepf, basepf.nleft = 0);
-		INTON;
+	else if (stdin_state.pending > left) {
+		flush_tee(basebuf, BUFSIZ, stdin_state.pending - left);
+		stdin_state.pending = 0;
 	}
+	input_set_lleft(&basepf, basepf.nleft = 0);
+	INTON;
 }
 
 void reset_input(void)
diff --git a/src/system.h b/src/system.h
index 371c64b..371bb20 100644
--- a/src/system.h
+++ b/src/system.h
@@ -118,6 +118,13 @@  long sysconf(int) __attribute__((__noreturn__));
 int isblank(int c);
 #endif
 
+#ifndef HAVE_TEE
+static inline ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags)
+{
+	return -1;
+}
+#endif
+
 /*
  * A trick to suppress uninitialized variable warning without generating any
  * code