git: 93805b27eacc - stable/14 - asa: Rewrite to fix line termination issue.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Wed, 17 Jan 2024 18:28:36 UTC
The branch stable/14 has been updated by des:

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

commit 93805b27eacceeccccf545081fdc1c82c8473bff
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2024-01-09 14:09:41 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2024-01-17 18:28:22 +0000

    asa: Rewrite to fix line termination issue.
    
    The standard is somewhat unclear, but on the balance, I believe that the
    phrase “the rest of the input line” should be interpreted to mean the
    rest of the input line including the terminating newline if and only if
    there is one.  This means the current implementation is incorrect on two
    points:
    
    - First, it suppresses the previous line's newline in the '1' case.
    
    - Second, it unconditionally emits a newline at the end of the output
      for non-empty input, even if the input did not end with a newline.
    
    Resolve this by rewriting the main loop.  Instead of special-casing the
    first line and then assuming that every line ends with a newline, we
    remember how each line ends and emit that either at the beginning of
    the next line or at the end of the file except in the one case ('+')
    where the standard explicitly says not to.
    
    While here, try to reduce diff to upstream a little and update their
    RCS tag to reflect the fact that while we've diverged significantly
    from them, we've incorporated all their changes.  Remove the useless
    second RCS tag.
    
    We also update the tests to account for the change in interpretation
    of the '1' case and add a test case for unterminated input.
    
    MFC after:      1 week
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D43326
    
    (cherry picked from commit c2356a440db91c106867d45c94b3d6d7bc0e50f0)
---
 usr.bin/asa/asa.c             | 102 +++++++++++++++++++-----------------------
 usr.bin/asa/tests/asa_test.sh |  15 ++++++-
 2 files changed, 59 insertions(+), 58 deletions(-)

diff --git a/usr.bin/asa/asa.c b/usr.bin/asa/asa.c
index 757278442a52..9839dbf8a722 100644
--- a/usr.bin/asa/asa.c
+++ b/usr.bin/asa/asa.c
@@ -1,4 +1,4 @@
-/*	$NetBSD: asa.c,v 1.11 1997/09/20 14:55:00 lukem Exp $	*/
+/*	$NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-4-Clause
@@ -32,13 +32,8 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <sys/cdefs.h>
-#if 0
-#ifndef lint
-__RCSID("$NetBSD: asa.c,v 1.11 1997/09/20 14:55:00 lukem Exp $");
-#endif
-#endif
 #include <err.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -50,38 +45,33 @@ static void usage(void) __dead2;
 int
 main(int argc, char *argv[])
 {
-	int ch, exval;
 	FILE *fp;
-	const char *fn;
+	int ch, exval;
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
 		switch (ch) {
-		case '?':
 		default:
 			usage();
-			/*NOTREACHED*/
 		}
 	}
 	argc -= optind;
 	argv += optind;
 
 	exval = 0;
-	if (argc == 0)
+	if (*argv == NULL) {
 		asa(stdin);
-	else {
-		while ((fn = *argv++) != NULL) {
-			if (strcmp(fn, "-") == 0) {
+	} else {
+		do {
+			if (strcmp(*argv, "-") == 0) {
 				asa(stdin);
+			} else if ((fp = fopen(*argv, "r")) == NULL) {
+				warn("%s", *argv);
+				exval = 1;
 			} else {
-				if ((fp = fopen(fn, "r")) == NULL) {
-					warn("%s", fn);
-					exval = 1;
-					continue;
-				}
 				asa(fp);
 				fclose(fp);
 			}
-		}
+		} while (*++argv != NULL);
 	}
 
 	if (fflush(stdout) != 0)
@@ -93,7 +83,6 @@ main(int argc, char *argv[])
 static void
 usage(void)
 {
-
 	fprintf(stderr, "usage: asa [file ...]\n");
 	exit(1);
 }
@@ -101,52 +90,53 @@ usage(void)
 static void
 asa(FILE *f)
 {
-	size_t len;
 	char *buf;
+	size_t len;
+	bool eol = false;
 
-	if ((buf = fgetln(f, &len)) != NULL) {
-		if (buf[len - 1] == '\n')
-			buf[--len] = '\0';
-		/* special case the first line */
+	while ((buf = fgetln(f, &len)) != NULL) {
+		/* in all cases but '+', terminate previous line, if any */
+		if (buf[0] != '+' && eol)
+			putchar('\n');
+		/* examine and translate the control character */
 		switch (buf[0]) {
+		default:
+			/*
+			 * “It is suggested that implementations treat
+			 * characters other than 0, 1, and '+' as <space>
+			 * in the absence of any compelling reason to do
+			 * otherwise” (POSIX.1-2017)
+			 */
+		case ' ':
+			/* nothing */
+			break;
 		case '0':
 			putchar('\n');
 			break;
 		case '1':
 			putchar('\f');
 			break;
-		}
-
-		if (len > 1 && buf[0] && buf[1])
-			printf("%.*s", (int)(len - 1), buf + 1);
-
-		while ((buf = fgetln(f, &len)) != NULL) {
-			if (buf[len - 1] == '\n')
-				buf[--len] = '\0';
-			switch (buf[0]) {
-			default:
-			case ' ':
-				putchar('\n');
-				break;
-			case '0':
-				putchar('\n');
-				putchar('\n');
-				break;
-			case '1':
-				putchar('\f');
-				break;
-			case '+':
+		case '+':
+			/*
+			 * “If the '+' is the first character in the
+			 * input, it shall be equivalent to <space>.”
+			 * (POSIX.1-2017)
+			 */
+			if (eol)
 				putchar('\r');
-				break;
-			}
-
-			if (len > 1 && buf[0] && buf[1])
-				printf("%.*s", (int)(len - 1), buf + 1);
+			break;
 		}
-
-		putchar('\n');
+		/* trim newline if there is one */
+		if ((eol = (buf[len - 1] == '\n')))
+			--len;
+		/* print the rest of the input line */
+		if (len > 1 && buf[0] && buf[1])
+			fwrite(buf + 1, 1, len - 1, stdout);
 	}
-
+	/* terminate the last line, if any */
+	if (eol)
+		putchar('\n');
+	/* check for output errors */
 	if (ferror(stdout) != 0)
 		err(1, "stdout");
 }
diff --git a/usr.bin/asa/tests/asa_test.sh b/usr.bin/asa/tests/asa_test.sh
index 429342d530e4..91515bb55d95 100644
--- a/usr.bin/asa/tests/asa_test.sh
+++ b/usr.bin/asa/tests/asa_test.sh
@@ -38,8 +38,8 @@ one_head() {
 	atf_set descr "First character on line is '1'"
 }
 one_body() {
-	printf " %s\n1%s\n" "$a" "$b" >infile
-	printf "%s\f%s\n" "$a" "$b" >outfile
+	printf "1%s\n1%s\n" "$a" "$b" >infile
+	printf "\f%s\n\f%s\n" "$a" "$b" >outfile
 	atf_check_asa infile outfile
 }
 
@@ -87,6 +87,16 @@ dashdash_body() {
 	atf_check -o inline:"$a $b\n" asa -- -infile
 }
 
+atf_test_case unterminated
+unterminated_head() {
+	atf_set descr "Unterminated input"
+}
+unterminated_body() {
+	printf " %s\n %s" "$a" "$b" >infile
+	printf "%s\n%s" "$a" "$b" >outfile
+	atf_check_asa infile outfile
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case space
@@ -96,4 +106,5 @@ atf_init_test_cases()
 	atf_add_test_case plus_top
 	atf_add_test_case stdout
 	atf_add_test_case dashdash
+	atf_add_test_case unterminated
 }