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
This commit is contained in:
Dag-Erling Smørgrav committed 2026-07-21 08:03:44 +00:00
1 parent bd30d1ad78
commit d34870708d
3 files changed
+73 -18

No files matched your search

+6 -1
View File
@@ -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
+32
View File
@@ -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
}
+35 -17
View File
@@ -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) {