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.

Reported by:	cperciva
Discussed with:	jrtc27
Reviewed by:	des, jlduran
Fixes:	4472fd66d0 ("lam: fail on I/O errors")
MFC after:	3 days (tentative)
Sponsored by:	Klara, Inc.
Differential Revision:	https://reviews.freebsd.org/D53750
This commit is contained in:
Kyle Evans 2025-11-14 08:36:20 -06:00
parent 0eca7fa1c9
commit 6a9452c837
5 changed files with 72 additions and 4 deletions

View file

@ -1165,6 +1165,8 @@
..
jot
..
lam
..
lastcomm
..
limits

View file

@ -1,3 +1,8 @@
.include <src.opts.mk>
PROG= lam
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests
.include <bsd.prog.mk>

View file

@ -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));
}

View file

@ -0,0 +1,5 @@
PACKAGE= tests
ATF_TESTS_SH= lam_test
.include <bsd.test.mk>

59
usr.bin/lam/tests/lam_test.sh Executable file
View file

@ -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
}