logo

utils-std

Collection of commonly available Unix tools
commit: bdb79c48e9ad473123a5ab6b32e14639c110dedd
parent 18976a7de983fa266a2b4b3ddd0b8962a9406269
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Wed,  1 May 2024 01:40:46 +0200

cmd/install: new

Diffstat:

MMakefile4++++
Acmd/install.157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/install.c250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfigure8+++++++-
Mmakeless.sh1+
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