git: 81f61990b28e - releng/15.0 - lam: fix using stdin more than once

From: Colin Percival <cperciva_at_FreeBSD.org>
Date: Sat, 15 Nov 2025 14:43:23 UTC
The branch releng/15.0 has been updated by cperciva:

URL: https://cgit.FreeBSD.org/src/commit/?id=81f61990b28e63b80007727be5a31db2226bb2c2

commit 81f61990b28e63b80007727be5a31db2226bb2c2
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2025-11-14 14:36:20 +0000
Commit:     Colin Percival <cperciva@FreeBSD.org>
CommitDate: 2025-11-15 14:43:05 +0000

    lam: fix using stdin more than once
    
    Historically, lam(1) closes stdin once we've hit EOF the first time,
    which would stop it from doing anything else on subsequent gatherline()
    calls with another openfile.  However, this doesn't seem to be strictly
    necessary- the EOF flag on FILEs is quite sticky, so we can assume that
    a subsequent fgetc(stdin) will flag EOF properly.
    
    This 'fixes' the below-referenced commit in the sense that it surfaced
    this problem as a fatal error, but the issue was pre-existing.  If we
    do `lam - -`, then one gatherline() will fclose(stdin) and set `ip->eof`
    for *that* openfile, while the next one will then observe that
    STDIN_FILENO has been closed and turn it into an EBADF.
    
    Add a few tests that were easy to snipe while I'm here, but I haven't
    aimed for anything close to exhaustive because I think re@ would prefer
    this fix go in sooner rather than later to land in 15.0.
    
    Minor style adjustment for the previous commit while we're here.
    
    Approved by:    re (cperciva)
    Reported by:    cperciva
    Discussed with: jrtc27
    Reviewed by:    des, jlduran
    Fixes:  4472fd66d006 ("lam: fail on I/O errors")
    Sponsored by:   Klara, Inc.
    
    (cherry picked from commit 6a9452c8378a1aa767708ad2d235f847c880a21c)
    (cherry picked from commit 1d678ba5756978240140beb5c1ebd38bb778ed66)
---
 etc/mtree/BSD.tests.dist      |  2 ++
 usr.bin/lam/Makefile          |  5 ++++
 usr.bin/lam/lam.c             |  5 +---
 usr.bin/lam/tests/Makefile    |  5 ++++
 usr.bin/lam/tests/lam_test.sh | 59 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 72 insertions(+), 4 deletions(-)

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 520b41c8b88f..41151d35e518 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -1163,6 +1163,8 @@
         ..
         jot
         ..
+        lam
+        ..
         lastcomm
         ..
         limits
diff --git a/usr.bin/lam/Makefile b/usr.bin/lam/Makefile
index e47ea0a98eaa..faad910f2202 100644
--- a/usr.bin/lam/Makefile
+++ b/usr.bin/lam/Makefile
@@ -1,3 +1,8 @@
+.include <src.opts.mk>
+
 PROG=	lam
 
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
 .include <bsd.prog.mk>
diff --git a/usr.bin/lam/lam.c b/usr.bin/lam/lam.c
index 2194ae7ab596..c1221ca92d0e 100644
--- a/usr.bin/lam/lam.c
+++ b/usr.bin/lam/lam.c
@@ -213,11 +213,8 @@ gatherline(struct openfile *ip)
 	*p = '\0';
 	if (c == EOF) {
 		ip->eof = 1;
-		if (ferror(ip->fp)) {
+		if (ferror(ip->fp))
 			err(EX_IOERR, NULL);
-		}
-		if (ip->fp == stdin)
-			fclose(stdin);
 		morefiles--;
 		return (pad(ip));
 	}
diff --git a/usr.bin/lam/tests/Makefile b/usr.bin/lam/tests/Makefile
new file mode 100644
index 000000000000..8d41af5e6e09
--- /dev/null
+++ b/usr.bin/lam/tests/Makefile
@@ -0,0 +1,5 @@
+PACKAGE=	tests
+
+ATF_TESTS_SH=	lam_test
+
+.include <bsd.test.mk>
diff --git a/usr.bin/lam/tests/lam_test.sh b/usr.bin/lam/tests/lam_test.sh
new file mode 100755
index 000000000000..bf3998a42d11
--- /dev/null
+++ b/usr.bin/lam/tests/lam_test.sh
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2025 Klara, Inc.
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+atf_test_case basic
+basic_head()
+{
+	atf_set "descr" "Test basic lam(1) functionality"
+}
+basic_body()
+{
+	printf '1\n2\n3\n' > a
+	printf '4\n5\n6\n' > b
+
+	atf_check -o inline:"14\n25\n36\n" lam a b
+}
+
+atf_test_case sep
+sep_head()
+{
+	atf_set "descr" "Test lam(1) -s and -S options"
+}
+sep_body()
+{
+	printf "1\n" > a
+	printf "0\n" > b
+
+	atf_check -o inline:"x1x0\n" lam -S x a b
+	atf_check -o inline:"1x0\n" lam a -S x b
+	atf_check -o inline:"x10\n" lam -S x a -s '' b
+
+	atf_check -o inline:"x10\n" lam -s x a b
+	atf_check -o inline:"x1y0\n" lam -s x a -s y b
+	atf_check -o inline:"1x0\n" lam a -s x b
+}
+
+atf_test_case stdin
+stdin_head()
+{
+	atf_set "descr" "Test lam(1) using stdin"
+}
+stdin_body()
+{
+	printf '1\n2\n3\n4\n' > a
+
+	atf_check -o inline:"11\n22\n33\n44\n" lam a - < a
+	atf_check -o inline:"11\n22\n33\n44\n" lam - a < a
+
+	atf_check -o inline:"12\n34\n" lam - - < a
+}
+
+atf_init_test_cases()
+{
+	atf_add_test_case basic
+	atf_add_test_case sep
+	atf_add_test_case stdin
+}