git: 993df5472e52 - stable/15 - install: Allow installing stdin

Dag-Erling Smørgrav <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a687796.1c794.7b992d8c__11461.4381150872$1785231280$gmane$org@gitrepo.freebsd.org>
The branch stable/15 has been updated by des:

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

commit 993df5472e52f1fce7d527fcd35a5d0a7d51dde0
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:32:28 +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 c6a55632891c..c4a5fbe68950 100644
--- a/usr.bin/xinstall/install.1
+++ b/usr.bin/xinstall/install.1
@@ -25,7 +25,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd June 3, 2026
+.Dd July 19, 2026
 .Dt INSTALL 1
 .Os
 .Sh NAME
@@ -286,6 +286,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 2c88e080bb74..38aa793d2492 100644
--- a/usr.bin/xinstall/xinstall.c
+++ b/usr.bin/xinstall/xinstall.c
@@ -62,6 +62,13 @@
 
 #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 than this, use a
  * large buffer.
@@ -808,18 +815,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);
@@ -834,9 +852,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");
@@ -851,19 +866,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)
@@ -1070,7 +1094,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);
@@ -1188,12 +1212,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.