Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 28 Jul 2026 09:34:14 +0000
From:      Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav <des@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org
Subject:   git: 993df5472e52 - stable/15 - install: Allow installing stdin
Message-ID:  <6a687796.1c794.7b992d8c@gitrepo.freebsd.org>

index | next in thread | raw e-mail

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 <des@FreeBSD.org>
AuthorDate: 2026-07-21 08:03:44 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
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) {


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a687796.1c794.7b992d8c>