The branch main has been updated by des:

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

commit d34870708db9fa1eb8e29b5e085b755de1189b1f
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-21 08:03:44 +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
---
 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) {

Reply via email to