git: 17dc7017d737 - main - install: Simplify path construction.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Fri, 12 Apr 2024 17:31:47 UTC
The branch main has been updated by des:

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

commit 17dc7017d7375b3463d65adffe1eb980b0f86795
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2024-04-12 17:30:52 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2024-04-12 17:31:35 +0000

    install: Simplify path construction.
    
    There's no need to copy the path twice to split it into base and dir.
    We simply call `basename()` first, then handle the two trivial cases in
    which it isn't safe to call `dirname()`.
    
    While here, add an early check that the destination is not an empty
    string.  This would always fail eventually, so it may as well fail
    right away.  Also add a test case for this shortcut.
    
    MFC after:      1 week
    Sponsored by:   Klara, Inc.
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D44743
---
 usr.bin/xinstall/tests/install_test.sh |  8 ++++++++
 usr.bin/xinstall/xinstall.c            | 34 +++++++++++++++++++++-------------
 2 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/usr.bin/xinstall/tests/install_test.sh b/usr.bin/xinstall/tests/install_test.sh
index cbe95f0dfbf3..b35706521ec3 100755
--- a/usr.bin/xinstall/tests/install_test.sh
+++ b/usr.bin/xinstall/tests/install_test.sh
@@ -25,6 +25,13 @@
 #
 #
 
+atf_test_case copy_to_empty
+copy_to_empty_body() {
+	printf 'test\n123\r456\r\n789\0z' >testf
+	atf_check -s not-exit:0 -e match:"empty string" \
+	    install testf ""
+}
+
 copy_to_nonexistent_with_opts() {
 	printf 'test\n123\r456\r\n789\0z' >testf
 	atf_check install "$@" testf copyf
@@ -497,6 +504,7 @@ set_optional_exec_body()
 }
 
 atf_init_test_cases() {
+	atf_add_test_case copy_to_empty
 	atf_add_test_case copy_to_nonexistent
 	atf_add_test_case copy_to_nonexistent_safe
 	atf_add_test_case copy_to_nonexistent_comparing
diff --git a/usr.bin/xinstall/xinstall.c b/usr.bin/xinstall/xinstall.c
index 6d2f05d64e2c..6ab0a88d5cd7 100644
--- a/usr.bin/xinstall/xinstall.c
+++ b/usr.bin/xinstall/xinstall.c
@@ -657,8 +657,10 @@ static void
 makelink(const char *from_name, const char *to_name,
     const struct stat *target_sb)
 {
-	char	src[MAXPATHLEN], dst[MAXPATHLEN], lnk[MAXPATHLEN];
-	struct stat	to_sb;
+	char src[MAXPATHLEN], dst[MAXPATHLEN], lnk[MAXPATHLEN];
+	char *to_name_copy, *d, *ld, *ls, *s;
+	const char *base, *dir;
+	struct stat to_sb;
 
 	/* Try hard links first. */
 	if (dolink & (LN_HARD|LN_MIXED)) {
@@ -719,8 +721,6 @@ makelink(const char *from_name, const char *to_name,
 	}
 
 	if (dolink & LN_RELATIVE) {
-		char *to_name_copy, *cp, *d, *ld, *ls, *s;
-
 		if (*from_name != '/') {
 			/* this is already a relative link */
 			do_symlink(from_name, to_name, target_sb);
@@ -740,17 +740,23 @@ makelink(const char *from_name, const char *to_name,
 		to_name_copy = strdup(to_name);
 		if (to_name_copy == NULL)
 			err(EX_OSERR, "%s: strdup", to_name);
-		cp = dirname(to_name_copy);
-		if (realpath(cp, dst) == NULL)
-			err(EX_OSERR, "%s: realpath", cp);
-		/* .. and add the last component. */
-		if (strcmp(dst, "/") != 0) {
-			if (strlcat(dst, "/", sizeof(dst)) > sizeof(dst))
+		base = basename(to_name_copy);
+		if (base == to_name_copy) {
+			/* destination is a file in cwd */
+			(void)strlcpy(dst, "./", sizeof(dst));
+		} else if (base == to_name_copy + 1) {
+			/* destination is a file in the root */
+			(void)strlcpy(dst, "/", sizeof(dst));
+		} else {
+			/* all other cases: safe to call dirname() */
+			dir = dirname(to_name_copy);
+			if (realpath(dir, dst) == NULL)
+				err(EX_OSERR, "%s: realpath", dir);
+			if (strcmp(dst, "/") != 0 &&
+			    strlcat(dst, "/", sizeof(dst)) > sizeof(dst))
 				errx(1, "resolved pathname too long");
 		}
-		strcpy(to_name_copy, to_name);
-		cp = basename(to_name_copy);
-		if (strlcat(dst, cp, sizeof(dst)) > sizeof(dst))
+		if (strlcat(dst, base, sizeof(dst)) > sizeof(dst))
 			errx(1, "resolved pathname too long");
 		free(to_name_copy);
 
@@ -834,6 +840,8 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
 	} else {
 		devnull = 1;
 	}
+	if (*to_name == '\0')
+		errx(EX_USAGE, "destination cannot be an empty string");
 
 	target = (lstat(to_name, &to_sb) == 0);