[git commit] less: improve O_NONBLOCK manipulations on stdin

Denys Vlasenko vda.linux at googlemail.com
Fri Feb 27 10:01:04 UTC 2026


commit: https://git.busybox.net/busybox/commit/?id=ca3f4c6639b0cd638f5f8d08a76a5c231f543800
branch: https://git.busybox.net/busybox/log/?h=master

"less FILE" case needs it to be set just once. In other cases,
manipulate it less often.

function                                             old     new   delta
read_lines                                           702     783     +81
reinitialize                                         198     199      +1
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 82/0)               Total: 82 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 miscutils/less.c | 76 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 44 insertions(+), 32 deletions(-)

diff --git a/miscutils/less.c b/miscutils/less.c
index 65a8c4bed..12bb31537 100644
--- a/miscutils/less.c
+++ b/miscutils/less.c
@@ -201,8 +201,8 @@ struct globals {
 #if ENABLE_FEATURE_LESS_WINCH
 	unsigned winch_counter;
 #endif
-	int stdin_GETFL_flags;
-	ssize_t eof_error; /* eof if 0, error if < 0, > 0 if last read got some chars */
+	smallint ndelay_set;
+	ssize_t eof_error_ok; /* eof if 0, error if < 0, > 0 if last read did not indicate either */
 	size_t readpos;
 	size_t read_size;
 	const char **buffer;
@@ -252,7 +252,6 @@ struct globals {
 #define winch_counter       (G.winch_counter     )
 /* This one is 100% not cached by compiler on read access */
 #define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
-#define eof_error           (G.eof_error         )
 #define readpos             (G.readpos           )
 #define read_size           (G.read_size         )
 #define buffer              (G.buffer            )
@@ -282,7 +281,7 @@ struct globals {
 	less_gets_pos = -1; \
 	empty_line_marker = "~"; \
 	current_file = 1; \
-	eof_error = 1; \
+	G.eof_error_ok = 1; \
 	terminated = 1; \
 	IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \
 } while (0)
@@ -463,13 +462,14 @@ static int at_end(void)
  * readbuf[readpos] - next character to add to current line.
  * last_line_pos - screen line position of next char to be read
  *      (takes into account tabs and backspaces)
- * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
+ * G.eof_error_ok - < 0 error, == 0 EOF, > 0 not EOF/error
  *
  * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs,
  * "/search on very long input" and "reaching max line count" corner cases.
  */
 static void read_lines(void)
 {
+	int ndelay_set, fdflags;
 	char *current_line, *p;
 	int w = width;
 	char last_terminated = terminated;
@@ -498,6 +498,12 @@ static void read_lines(void)
 		last_line_pos = 0;
 	}
 
+	// Consider these cases:
+	// "less FILE": can set O_NONBLOCK on open.
+	// "true | less": can't.
+	// " { less; cat; } <FILE": can't. And must not confuse cat.
+	ndelay_set = -1; // "don't know whetherstdin is nonblocking"
+
 	while (1) { /* read lines until we reach cur_fline or wanted_match */
 		*p = '\0';
 		terminated = 0;
@@ -505,22 +511,29 @@ static void read_lines(void)
 			char c;
 			/* if no unprocessed chars left, eat more */
 			if (readpos >= read_size) {
-				// Read stdin, temporarily making it nonblocking (if it's not already).
-				// NB: we do NOT check eof_error status before reading.
+				// Read stdin, temporarily make it nonblocking (if it's not already)
+				if (ndelay_set < 0) {
+					ndelay_set = G.ndelay_set; // can be only 0 or 1
+					if (ndelay_set == 0) {
+						fdflags = ndelay_on(STDIN_FILENO);
+						if (fdflags & O_NONBLOCK)
+							ndelay_set = 2; // it _was_ nonblocking, do NOT restore later
+					} //else: G.ndelay_set=1: set nonblocking at open(FILE) time
+				} //else: we were here already, stdin is already nonblocking
+
+				// NB: we do NOT check last eof_error_ok before reading.
 				// This has the effect that e.g. PageDown on a regular file
 				// where we already reached EOF *will try reading anyway*,
 				// if the file is a growing log file, less *will* show the new data.
-				int flags = G.stdin_GETFL_flags;
-				if (!(flags & O_NONBLOCK))
-					fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK);
-				eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
-				if (!(flags & O_NONBLOCK))
-					fcntl(STDIN_FILENO, F_SETFL, flags);
-
+				G.eof_error_ok = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
 				readpos = 0;
-				read_size = (eof_error < 0 ? 0 : eof_error);
-				if (eof_error <= 0)
+				read_size = G.eof_error_ok;
+				if (G.eof_error_ok <= 0) {
+					read_size = 0; // -1 would be seen as UNIT_MAX, prevent
+					if (G.eof_error_ok < 0 && errno == EAGAIN)
+						G.eof_error_ok = 1; // "neither EOF nor error"
 					goto reached_eof;
+				}
 			}
 			c = readbuf[readpos];
 			/* backspace? [needed for manpages] */
@@ -598,7 +611,7 @@ static void read_lines(void)
 			max_lineno++;
 
 		if (max_fline >= MAXLINES) {
-			eof_error = 0; /* Pretend we saw EOF */
+			G.eof_error_ok = 0; /* Pretend we saw EOF */
 			break;
 		}
 		if (!at_end()) {
@@ -613,24 +626,21 @@ static void read_lines(void)
 				break;
 #endif
 		}
-		if (eof_error <= 0) {
+		if (G.eof_error_ok <= 0) /* EOF or error? */
 			break;
-		}
 		max_fline++;
 		current_line = ((char*)xmalloc(w + 5)) + 4;
 		p = current_line;
 		last_line_pos = 0;
 	} /* end of "read lines until we reach cur_fline" loop */
 
-	if (eof_error < 0) {
-		if (errno == EAGAIN) {
-			eof_error = 1; /* "neither EOF nor error" */
-		} else {
-			print_statusline(bb_msg_read_error);
-		}
-	}
+	if (ndelay_set == 0) // stdin was not nonblocking, restore that
+		fcntl(STDIN_FILENO, F_SETFL, fdflags);
+
+	if (G.eof_error_ok < 0) // error?
+		print_statusline(bb_msg_read_error);
 #if ENABLE_FEATURE_LESS_FLAGS
-	else if (eof_error == 0)
+	else if (G.eof_error_ok == 0) // EOF?
 		num_lines = max_lineno;
 #endif
 
@@ -904,7 +914,7 @@ static void buffer_print(void)
 			print_ascii(buffer[i]);
 	}
 	if ((option_mask32 & (FLAG_E|FLAG_F))
-	 && eof_error <= 0
+	 && G.eof_error_ok <= 0
 	) {
 		i = (option_mask32 & FLAG_F) ? 0 : cur_fline;
 		if (max_fline - i <= max_displayed_line)
@@ -959,7 +969,7 @@ static void goto_lineno(int target)
 		while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
 			++cur_fline;
 		/* target not reached but more input is available */
-		if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
+		if (LINENO(flines[cur_fline]) != target && G.eof_error_ok > 0) {
 			read_lines();
 			goto retry;
 		}
@@ -1042,8 +1052,11 @@ static void buffer_lineno(int lineno)
 
 static void open_file_and_read_lines(void)
 {
+	G.ndelay_set = 0;
 	if (filename) {
 		xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
+		ndelay_on(STDIN_FILENO);
+		G.ndelay_set = 1;
 #if ENABLE_FEATURE_LESS_FLAGS
 		num_lines = REOPEN_AND_COUNT;
 #endif
@@ -1055,7 +1068,6 @@ static void open_file_and_read_lines(void)
 		num_lines = REOPEN_STDIN;
 #endif
 	}
-	G.stdin_GETFL_flags = fcntl(STDIN_FILENO, F_GETFL);
 	readpos = 0;
 	read_size = 0;
 	last_line_pos = 0;
@@ -1111,7 +1123,7 @@ static int64_t getch_nowait(void)
 	dont_poll_stdin = 1;
 	/* Are we interested in stdin? */
 	if (at_end()) {
-		if (eof_error > 0) /* did NOT reach eof yet */
+		if (G.eof_error_ok > 0) /* did NOT reach EOF/error yet */
 			dont_poll_stdin = 0; /* yes, we are interested in stdin */
 	}
 	/* Position cursor if line input is done */
@@ -1325,7 +1337,7 @@ static void goto_match(int match)
 	if (match < 0)
 		match = 0;
 	/* Try to find next match if eof isn't reached yet */
-	if (match >= num_matches && eof_error > 0) {
+	if (match >= num_matches && G.eof_error_ok > 0) {
 		wanted_match = match; /* "I want to read until I see N'th match" */
 		read_lines();
 	}


More information about the busybox-cvs mailing list