git: eaf2b738b99c - stable/14 - install: Allow installing stdin

Dag-Erling Smørgrav <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a6877a4.18966.40e3e5ee__19312.6165935368$1785231359$gmane$org@gitrepo.freebsd.org>
The branch stable/14 has been updated by des:

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

commit eaf2b738b99caa531c467acc33ca8813fbd1b89b
Author:     Dag-Erling Smørgrav <[email protected]>
AuthorDate: 2026-07-21 08:03:44 +0000
Commit:     Dag-Erling Smørgrav <[email protected]>
CommitDate: 2026-07-28 09:33:01 +0000

    install: Allow installing stdin
    
    If from_name is "/dev/stdin" or "-" and the target is not a directory,
    skip the comparison and copy data from standard input to the target.
    
    MFC after:      1 week
    Reviewed by:    imp
    Differential Revision:  https://reviews.freebsd.org/D58348
    
    (cherry picked from commit d34870708db9fa1eb8e29b5e085b755de1189b1f)
---
 usr.bin/xinstall/install.1             |  7 ++++-
 usr.bin/xinstall/tests/install_test.sh | 32 +++++++++++++++++++++
 usr.bin/xinstall/xinstall.c            | 52 +++++++++++++++++++++++-----------
 3 files changed, 73 insertions(+), 18 deletions(-)

diff --git a/usr.bin/xinstall/install.1 b/usr.bin/xinstall/install.1
index f13c0c1ad9a8..1d78b4f22e71 100644
--- a/usr.bin/xinstall/install.1
+++ b/usr.bin/xinstall/install.1
@@ -27,7 +27,7 @@
 .\"
 .\"	From: @(#)install.1	8.1 (Berkeley) 6/6/93
 .\"
-.Dd June 3, 2026
+.Dd July 19, 2026
 .Dt INSTALL 1
 .Os
 .Sh NAME
@@ -288,6 +288,11 @@ utility attempts to prevent moving a file onto itself.
 Installing
 .Pa /dev/null
 creates an empty file.
+Installing
+.Pa /dev/stdin
+or
+.Sy -
+copies data from standard input to the target.
 .Sh ENVIRONMENT
 The
 .Nm
diff --git a/usr.bin/xinstall/tests/install_test.sh b/usr.bin/xinstall/tests/install_test.sh
index 377920200490..7acd02abbb80 100755
--- a/usr.bin/xinstall/tests/install_test.sh
+++ b/usr.bin/xinstall/tests/install_test.sh
@@ -557,6 +557,36 @@ digest_body() {
 	done
 }
 
+atf_test_case null
+null_head() {
+	atf_set "descr" "Install empty file"
+}
+null_body() {
+	atf_check mkdir dst
+	atf_check -s exit:71 -e not-empty install /dev/null dst
+	atf_check install /dev/null dst/file
+	atf_check test -f dst/file
+	atf_check test ! -s dst/file
+}
+
+atf_test_case stdin
+stdin_head() {
+	atf_set "descr" "Install stdin"
+}
+stdin_body() {
+	atf_check mkdir dst
+	echo "The Magic Words are Squeamish Ossifrage" >file
+	atf_check -s exit:71 -e not-empty install - dst <file
+	atf_check test ! -e dst/file
+	atf_check install - dst/file <file
+	atf_check cmp -s file dst/file
+	atf_check rm dst/file
+	atf_check -s exit:71 -e not-empty install /dev/stdin dst <file
+	atf_check test ! -e dst/file
+	atf_check install /dev/stdin dst/file <file
+	atf_check cmp -s file dst/file
+}
+
 atf_init_test_cases() {
 	atf_add_test_case incompatible_opts
 	atf_add_test_case copy_to_empty
@@ -605,4 +635,6 @@ atf_init_test_cases() {
 	atf_add_test_case set_optional_exec
 	atf_add_test_case metalog
 	atf_add_test_case digest
+	atf_add_test_case null
+	atf_add_test_case stdin
 }
diff --git a/usr.bin/xinstall/xinstall.c b/usr.bin/xinstall/xinstall.c
index 04801c590e06..ea07010c0abd 100644
--- a/usr.bin/xinstall/xinstall.c
+++ b/usr.bin/xinstall/xinstall.c
@@ -74,6 +74,13 @@ static char sccsid[] = "@(#)xinstall.c	8.1 (Berkeley) 7/21/93";
 
 #include "mtree.h"
 
+#ifndef _PATH_STDIN
+# ifndef _PATH_DEV
+#  defne _PATH_DEV "/dev/"
+# endif
+# define _PATH_STDIN _PATH_DEV "stdin"
+#endif
+
 /*
  * Memory strategy threshold, in pages: if physmem is larger then this, use a
  * large buffer.
@@ -820,18 +827,29 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
 {
 	struct stat from_sb, temp_sb, to_sb;
 	struct timespec tsb[2];
-	int devnull, files_match, from_fd, serrno, stripped, target;
+	int devnull, files_match, from_fd, ispipe, serrno, stripped, target;
 	int temp_fd, to_fd;
 	char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN];
 	char *digestresult;
 
+	devnull = ispipe = 0;
 	digestresult = NULL;
 	files_match = stripped = 0;
 	from_fd = -1;
 	to_fd = -1;
 
-	/* If try to install NULL file to a directory, fails. */
-	if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) {
+	if (strcmp(from_name, _PATH_DEVNULL) == 0) {
+		/* We can't create a new file without a name */
+		if ((flags & DIRECTORY) != 0)
+			errc(EX_OSERR, EFTYPE, "%s", from_name);
+		devnull = 1;
+	} else if (strcmp(from_name, _PATH_STDIN) == 0 ||
+	    strcmp(from_name, "-") == 0) {
+		/* We can't create a new file without a name */
+		if ((flags & DIRECTORY) != 0)
+			errc(EX_OSERR, EFTYPE, "%s", from_name);
+		ispipe = 1;
+	} else {
 		if (!dolink) {
 			if (stat(from_name, &from_sb))
 				err(EX_OSERR, "%s", from_name);
@@ -846,9 +864,6 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
 			    (p = strrchr(from_name, '/')) ? ++p : from_name);
 			to_name = pathbuf;
 		}
-		devnull = 0;
-	} else {
-		devnull = 1;
 	}
 	if (*to_name == '\0')
 		errx(EX_USAGE, "destination cannot be an empty string");
@@ -863,19 +878,28 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
 	if (target && !S_ISREG(to_sb.st_mode) && !S_ISLNK(to_sb.st_mode))
 		errc(EX_CANTCREAT, EFTYPE, "%s", to_name);
 
-	if (!devnull && (from_fd = open(from_name, O_RDONLY, 0)) < 0)
-		err(EX_OSERR, "%s", from_name);
+	if (devnull) {
+		/* No from_fd needed */
+	} else if (ispipe) {
+		from_fd = STDIN_FILENO;
+	} else {
+		if ((from_fd = open(from_name, O_RDONLY)) < 0)
+			err(EX_OSERR, "%s", from_name);
+	}
 
 	/* If we don't strip, we can compare first. */
 	if (docompare && !dostrip && target && S_ISREG(to_sb.st_mode)) {
-		if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
+		if ((to_fd = open(to_name, O_RDONLY)) < 0)
 			err(EX_OSERR, "%s", to_name);
 		if (devnull)
 			files_match = to_sb.st_size == 0;
-		else
+		else if (ispipe)
+			files_match = 0;
+		else {
 			files_match = !(compare(from_fd, from_name,
 			    (size_t)from_sb.st_size, to_fd,
 			    to_name, (size_t)to_sb.st_size, &digestresult));
+		}
 
 		/* Close "to" file unless we match. */
 		if (!files_match)
@@ -1082,7 +1106,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
 #endif
 
 	(void)close(to_fd);
-	if (!devnull)
+	if (!devnull && !ispipe)
 		(void)close(from_fd);
 
 	metadata_log(to_name, "file", tsb, NULL, digestresult, to_sb.st_size);
@@ -1200,12 +1224,6 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name)
 #endif
 	DIGEST_CTX ctx;
 
-	/* Rewind file descriptors. */
-	if (lseek(from_fd, 0, SEEK_SET) < 0)
-		err(EX_OSERR, "lseek: %s", from_name);
-	if (lseek(to_fd, 0, SEEK_SET) < 0)
-		err(EX_OSERR, "lseek: %s", to_name);
-
 #ifndef BOOTSTRAP_XINSTALL
 	/* Try copy_file_range() if no digest is requested */
 	if (digesttype == DIGEST_NONE) {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.