commit: bdb79c48e9ad473123a5ab6b32e14639c110dedd
parent 18976a7de983fa266a2b4b3ddd0b8962a9406269
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Wed, 1 May 2024 01:40:46 +0200
cmd/install: new
Diffstat:
5 files changed, 319 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -138,3 +138,7 @@ cmd/expr.tab.c: cmd/expr.y Makefile
cmd/expr: cmd/expr.tab.c Makefile
rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
$(CC) -std=c99 $(CFLAGS) -o $@ cmd/expr.tab.c $(LDFLAGS) $(LDSTATIC)
+
+cmd/install: cmd/install.c lib/mode.c lib/user_group_parse.c lib/user_group_parse.h lib/path.c lib/path.h Makefile
+ rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
+ $(CC) -std=c99 $(CFLAGS) -o $@ cmd/install.c lib/mode.c lib/user_group_parse.c lib/path.c $(LDFLAGS) $(LDSTATIC)
diff --git a/cmd/install.1 b/cmd/install.1
@@ -0,0 +1,57 @@
+.\" utils-std: Collection of commonly available Unix tools
+.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2024-05-01
+.Dt INSTALL 1
+.Os
+.Sh NAME
+.Nm install
+.Nd install binairies
+.Sh SYNOPSIS
+.Nm
+.Op Fl cp
+.Op Fl g Ar group
+.Op Fl m Ar mode
+.Op Fl o Ar owner
+.Op Ar source...
+.Op Ar destination
+.Sh DESCRIPTION
+.Nm
+copies the given
+.Ar source
+files to
+.Ar destination .
+If
+.Ar destination
+is a directory then source is copied into destination with its original filename,
+otherwise if it already exists, it is removed.
+.Pp
+The mode of
+.Ar destination
+is set to 755 unless
+.Fl m Ar mode
+is specified.
+.Sh OPTIONS
+.Bl -tag -width _o_owner
+.It Fl c
+Copy the file, default behavior in all modern implementations.
+.It Fl g Ar group
+Sets group ownership.
+.It Fl m Ar mode
+Sets alternative mode.
+The default mode is set to
+.Ql 0755/rwr-xr-x .
+.It Fl o Ar owner
+Sets user ownership.
+.It Fl p
+Preserve file access and modification times.
+.El
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh STANDARDS
+None known.
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me
diff --git a/cmd/install.c b/cmd/install.c
@@ -0,0 +1,250 @@
+// utils-std: Collection of commonly available Unix tools
+// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#define _GNU_SOURCE // copy_file_range
+
+#include "../lib/mode.h"
+#include "../lib/path.h"
+#include "../lib/user_group_parse.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
+#include <limits.h> // PATH_MAX
+#include <stdbool.h>
+#include <stdio.h> // fprintf
+#include <string.h> // strerror
+#include <sys/stat.h>
+#include <unistd.h> // getopt, copy_file_range
+
+bool preserve_times = false;
+
+mode_t mode = 00755;
+uid_t user = (uid_t)-1;
+gid_t group = (gid_t)-1;
+char *argv0 = "install";
+
+static int
+do_install(char *src, char *dest, bool is_dir)
+{
+ assert(errno == 0);
+ int src_fd = open(src, O_RDONLY);
+ if(src_fd < 0)
+ {
+ fprintf(stderr, "%s: Failed opening file '%s' for reading: %s\n", argv0, src, strerror(errno));
+ return -1;
+ }
+
+ struct stat src_stat;
+ if(fstat(src_fd, &src_stat) < 0)
+ {
+ fprintf(
+ stderr, "%s: Failed getting status for source '%s': %s\n", argv0, dest, strerror(errno));
+ return -1;
+ }
+
+ if(!is_dir)
+ {
+ assert(errno == 0);
+ struct stat dest_stat;
+ if(stat(dest, &dest_stat) < 0)
+ {
+ if(errno != ENOENT)
+ {
+ fprintf(stderr,
+ "%s: Failed getting status for destination '%s': %s\n",
+ argv0,
+ dest,
+ strerror(errno));
+ return -1;
+ }
+ else
+ errno = 0;
+ }
+ else
+ {
+ if(S_ISDIR(dest_stat.st_mode))
+ {
+ is_dir = true;
+ }
+ else
+ {
+ if(unlink(dest) < 0)
+ {
+ fprintf(stderr,
+ "%s: Failed removing existing file at destination '%s': %s\n",
+ argv0,
+ dest,
+ strerror(errno));
+ return -1;
+ }
+ }
+ }
+ }
+
+ assert(errno == 0);
+
+ char *dest_path = dest;
+
+ if(is_dir)
+ {
+ char target[PATH_MAX] = "";
+ char *src_basename = static_basename(src);
+
+ if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
+ {
+ fprintf(stderr,
+ "%s: Failed joining destination '%s' and source '%s'\n",
+ argv0,
+ dest,
+ src_basename);
+ return -1;
+ }
+
+ dest_path = target;
+ }
+
+ assert(errno == 0);
+
+ int dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, mode);
+ if(dest_fd < 0)
+ {
+ fprintf(stderr,
+ "%s: Failed create-opening file '%s' for writing: %s\n",
+ argv0,
+ dest_path,
+ strerror(errno));
+ return -1;
+ }
+
+ off_t len = src_stat.st_size;
+ off_t ret = -1;
+ do
+ {
+ ret = copy_file_range(src_fd, NULL, dest_fd, NULL, len, 0);
+ if(ret == -1)
+ {
+ fprintf(stderr,
+ "%s: Error: Failed copying data from '%s' to '%s': %s\n",
+ argv0,
+ src,
+ dest_path,
+ strerror(errno));
+ return -1;
+ }
+ len -= ret;
+ } while(len > 0 && ret > 0);
+
+ assert(errno == 0);
+
+ if(user != (uid_t)-1 || group != (gid_t)-1)
+ if(fchown(dest_fd, user, group) < 0)
+ {
+ fprintf(stderr,
+ "%s: Error: Failed changing ownership of '%s': %s\n",
+ argv0,
+ dest_path,
+ strerror(errno));
+ return -1;
+ }
+
+ assert(errno == 0);
+
+ if(preserve_times)
+ {
+ struct timespec src_times[2] = {src_stat.st_atim, src_stat.st_mtim};
+ if(futimens(dest_fd, src_times) != 0)
+ {
+ fprintf(stderr,
+ "%s: Error while setting access/modification times on '%s': %s\n",
+ argv0,
+ dest_path,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ if(close(src_fd) < 0)
+ {
+ fprintf(stderr, "%s: Error closing '%s'\n", argv0, src);
+ return -1;
+ }
+
+ if(close(dest_fd) < 0)
+ {
+ fprintf(stderr, "%s: Error closing '%s'\n", argv0, dest_path);
+ return -1;
+ }
+
+ assert(errno == 0);
+
+ return 0;
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: install [-cp] [-g group] [-m mode] [-o owner] source... destination\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *errstr = NULL;
+
+ int c = -1;
+ while((c = getopt(argc, argv, ":cpg:m:o:")) != -1)
+ {
+ switch(c)
+ {
+ case 'c':
+ // ignore, modern default behavior
+ break;
+ case 'p':
+ preserve_times = true;
+ break;
+ case 'g':
+ if(parse_group(optarg, &group) != 0) return 1;
+ break;
+ case 'o':
+ if(parse_group(optarg, &user) != 0) return 1;
+ break;
+ case 'm':
+ mode = new_mode(optarg, 0755, &errstr);
+ if(errstr != NULL)
+ {
+ fprintf(stderr, "install: Failed parsing mode '%s': %s\n", optarg, errstr);
+ return 1;
+ }
+ break;
+ case '?':
+ fprintf(stderr, "install: Unknown option '-%c'\n", optopt);
+ usage();
+ break;
+ }
+ }
+
+ assert(errno == 0);
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc == 2)
+ {
+ if(do_install(argv[0], argv[1], false) < 0) return 1;
+ }
+ else
+ {
+ char *dest = argv[argc - 1];
+
+ for(int i = 0; i < argc - 1; i++)
+ {
+ char *src = argv[i];
+ if(do_install(src, dest, true) < 0) return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/configure b/configure
@@ -239,7 +239,13 @@ fi
check_conftest configure.d/splice.c && CFLAGS="${CFLAGS} -DHAS_SPLICE"
-check_conftest configure.d/copy_file_range.c && CFLAGS="${CFLAGS} -DCOPY_FILE_RANGE"
+if check_conftest configure.d/copy_file_range.c
+then
+ CFLAGS="${CFLAGS} -DCOPY_FILE_RANGE"
+else
+ echo 'Disabling cmd/install'
+ echo 'cmd/install.' >> target_filter
+fi
if ! check_conftest configure.d/reallocarray.c; then
echo 'Disabling cmd/tr'
diff --git a/makeless.sh b/makeless.sh
@@ -24,6 +24,7 @@ $CC -std=c99 $CFLAGS -o cmd/echo cmd/echo.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/env cmd/env.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/false cmd/false.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/id cmd/id.c $LDFLAGS $LDSTATIC
+$CC -std=c99 $CFLAGS -o cmd/install cmd/install.c lib/mode.c lib/user_group_parse.c lib/path.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/link cmd/link.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/ln cmd/ln.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/logname cmd/logname.c $LDFLAGS $LDSTATIC