git: d81c64d165d5 - main - tail: Fix -b, -c, -n options

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Wed, 06 Aug 2025 20:43:42 UTC
The branch main has been updated by des:

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

commit d81c64d165d52f8ebcafadda5012d3bb2bdd25a9
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-08-06 20:34:34 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-08-06 20:43:13 +0000

    tail: Fix -b, -c, -n options
    
    Switching from strtoll() to expand_number() was improper at the time as
    it only accepted positive numbers.  Now that it also accepts negative
    numbers, the -b option is still broken because the same commit that
    switched to expand_number() also dropped the multiplication by units.
    
    Fixes:          643ac419fafb
    Reviewed by:    delphij
    Differential Revision:  https://reviews.freebsd.org/D51757
---
 usr.bin/tail/tail.c             | 12 ++++++-----
 usr.bin/tail/tests/tail_test.sh | 48 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c
index fc60a82287df..a92eee3881b4 100644
--- a/usr.bin/tail/tail.c
+++ b/usr.bin/tail/tail.c
@@ -95,15 +95,17 @@ main(int argc, char *argv[])
 	 * -r is the entire file, not 10 lines.
 	 */
 #define	ARG(units, forward, backward) {					\
+	int64_t num;							\
 	if (style)							\
 		usage();						\
-	if (expand_number(optarg, &off))				\
+	if (expand_number(optarg, &num))				\
 		err(1, "illegal offset -- %s", optarg);			\
-	if (off > INT64_MAX / units || off < INT64_MIN / units )	\
+	if (num > INT64_MAX / units || num < INT64_MIN / units)		\
 		errx(1, "illegal offset -- %s", optarg);		\
-	switch(optarg[0]) {						\
+	off = num * units;						\
+	switch (optarg[0]) {						\
 	case '+':							\
-		if (off)						\
+		if (off != 0)						\
 			off -= (units);					\
 		style = (forward);					\
 		break;							\
@@ -121,7 +123,7 @@ main(int argc, char *argv[])
 	off = 0;
 	while ((ch = getopt_long(argc, argv, "+Fb:c:fn:qrv", long_opts, NULL)) !=
 	    -1)
-		switch(ch) {
+		switch (ch) {
 		case 'F':	/* -F is superset of (and implies) -f */
 			Fflag = fflag = 1;
 			break;
diff --git a/usr.bin/tail/tests/tail_test.sh b/usr.bin/tail/tests/tail_test.sh
index 9c941f8a2c2f..74d6908f7568 100755
--- a/usr.bin/tail/tests/tail_test.sh
+++ b/usr.bin/tail/tests/tail_test.sh
@@ -423,6 +423,51 @@ no_lf_at_eof_body()
 	atf_check -o inline:"a\nb\nc" tail -4 infile
 }
 
+atf_test_case tail_b
+tail_b_head()
+{
+	atf_set "descr" "Test -b option"
+}
+tail_b_body()
+{
+	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile
+	(jot -b b 256 ; jot -b c 256) >outfile
+	# infile is 3 blocks long, outfile contains the last two
+	atf_check -o file:outfile tail -b +2 infile # start at the 2nd block
+	atf_check -o file:outfile tail -b -2 infile # 2 blocks from the end
+	atf_check -o file:outfile tail -b  2 infile # 2 blocks from the end
+}
+
+atf_test_case tail_c
+tail_c_head()
+{
+	atf_set "descr" "Test -c option"
+}
+tail_c_body()
+{
+	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile
+	(jot -b b 256 ; jot -b c 256) >outfile
+	# infile is 1536 bytes long, outfile contains the last 1024
+	atf_check -o file:outfile tail -c  +513 infile # start at the 513th byte
+	atf_check -o file:outfile tail -c -1024 infile # 1024 bytes from the end
+	atf_check -o file:outfile tail -c  1024 infile # 1024 bytes from the end
+}
+
+atf_test_case tail_n
+tail_n_head()
+{
+	atf_set "descr" "Test -n option"
+}
+tail_n_body()
+{
+	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile
+	(jot -b b 256 ; jot -b c 256) >outfile
+	# infile is 768 lines long, outfile contains the last 512
+	atf_check -o file:outfile tail -n +257 infile # start at the 257th line
+	atf_check -o file:outfile tail -n -512 infile # 512 lines from the end
+	atf_check -o file:outfile tail -n  512 infile # 512 lines from the end
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case empty_r
@@ -448,4 +493,7 @@ atf_init_test_cases()
 	atf_add_test_case verbose_header
 	atf_add_test_case si_number
 	atf_add_test_case no_lf_at_eof
+	atf_add_test_case tail_b
+	atf_add_test_case tail_c
+	atf_add_test_case tail_n
 }