install.c (7658B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- // For copy_file_range
- #define _GNU_SOURCE // musl, glibc
- #define _DEFAULT_SOURCE // FreeBSD
- #include "../lib/fs.h"
- #include "../lib/lib_mkdir.h"
- #include "../lib/mode.h"
- #include "../lib/user_group_parse.h"
- #include <assert.h>
- #include <errno.h>
- #include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
- #include <libgen.h> // dirname
- #include <limits.h> // PATH_MAX
- #include <stdbool.h>
- #include <stdio.h> // fprintf
- #include <string.h> // strerror
- #include <sys/stat.h>
- #include <unistd.h> // getopt
- #ifdef HAS_GETOPT_LONG
- #include <getopt.h>
- #endif
- bool preserve_times = false, create_directories = false;
- // See lib/lib_mkdir.c
- bool mkdir_parents_verbose = false;
- mode_t mkdir_parents_filemask;
- mode_t mode = 00755;
- uid_t user = (uid_t)-1;
- gid_t group = (gid_t)-1;
- const char *argv0 = "install";
- bool opt_v = false;
- static int
- do_install(char *src, char *dest, bool is_dir)
- {
- int src_fd = open(src, O_RDONLY);
- if(src_fd < 0)
- {
- fprintf(stderr,
- "%s: error: 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: error: Failed getting status for source '%s': %s\n",
- argv0,
- dest,
- strerror(errno));
- return -1;
- }
- if(!is_dir)
- {
- struct stat dest_stat;
- if(stat(dest, &dest_stat) < 0)
- {
- if(errno != ENOENT)
- {
- fprintf(stderr,
- "%s: error: 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: error: Failed removing existing file at destination '%s': %s\n",
- argv0,
- dest,
- strerror(errno));
- return -1;
- }
- }
- }
- }
- char *dest_path = dest;
- if(is_dir)
- {
- static char target[PATH_MAX] = "";
- char *src_basename = static_basename(src);
- if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
- {
- fprintf(stderr,
- "%s: error: Failed joining destination '%s' and source '%s'\n",
- argv0,
- dest,
- src_basename);
- return -1;
- }
- dest_path = target;
- }
- int dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, mode);
- if(dest_fd < 0)
- {
- fprintf(stderr,
- "%s: error: Failed create-opening file '%s' for writing: %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- if(opt_v) fprintf(stderr, "%s: Made file: %s\n", argv0, dest);
- if(auto_file_copy(src_fd, dest_fd, src_stat.st_size, 0) < 0)
- {
- fprintf(stderr,
- "%s: error: Failed copying data from '%s' to '%s': %s\n",
- argv0,
- src,
- dest_path,
- strerror(errno));
- return -1;
- }
- 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;
- }
- 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: Failed setting access/modification times on '%s': %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- }
- if(close(src_fd) < 0)
- {
- fprintf(stderr, "%s: error: Failed closing '%s'\n", argv0, src);
- return -1;
- }
- if(close(dest_fd) < 0)
- {
- fprintf(stderr, "%s: error: Failed closing '%s'\n", argv0, dest_path);
- return -1;
- }
- return 0;
- }
- static void
- usage(void)
- {
- fprintf(stderr, "\
- Usage: install [-CcDpTv] [-g group] [-m mode] [-o owner] source... destination\n\
- install [-CcDpTv] [-g group] [-m mode] [-o owner] -t destination source...\n\
- install -d [-cv] [-g group] [-m mode] [-o owner] directory...\n\
- ");
- }
- int
- main(int argc, char *argv[])
- {
- const char *errstr = NULL;
- bool create_parents = false;
- bool opt_T = false;
- char *dest = NULL;
- mkdir_parents_filemask = umask(0);
- umask(mkdir_parents_filemask);
- int c = -1;
- #ifdef HAS_GETOPT_LONG
- // Strictly for GNUisms compatibility so no long-only options
- // clang-format off
- static struct option opts[] = {
- {"compare", no_argument, 0, 'c'},
- {"directory", no_argument, 0, 'd'},
- {"group", required_argument, 0, 'g'},
- {"mode", required_argument, 0, 'm'},
- {"owner", required_argument, 0, 'o'},
- {"preserve-timestamps", no_argument, 0, 'p'},
- {"target-directory", required_argument, 0, 't'},
- {"no-target-directory", no_argument, 0, 'T'},
- {"verbose", no_argument, 0, 'v'},
- {0, 0, 0, 0},
- };
- // clang-format on
- // Need + as first character to get POSIX-style option parsing
- while((c = getopt_long(argc, argv, "+:CcDdpTt:g:m:o:v", opts, NULL)) != -1)
- #else
- while((c = getopt(argc, argv, ":CcDdpTt:g:m:o:v")) != -1)
- #endif
- {
- switch(c)
- {
- case 'C':
- // ignore, for compatibility
- break;
- case 'c':
- // ignore, modern default behavior
- break;
- case 'D':
- create_parents = true;
- break;
- case 'd':
- create_directories = true;
- 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 'T':
- opt_T = true;
- break;
- case 't':
- dest = optarg;
- break;
- case 'm':
- mode = new_mode(optarg, 0755, &errstr);
- if(errstr != NULL)
- {
- fprintf(stderr, "install: error: Failed parsing mode '%s': %s\n", optarg, errstr);
- return 1;
- }
- break;
- case 'v':
- opt_v = true;
- mkdir_parents_verbose = true;
- break;
- case ':':
- fprintf(stderr, "install: error: Missing operand for option: '-%c'\n", optopt);
- usage();
- return 1;
- case '?':
- fprintf(stderr, "install: error: Unknown option '-%c'\n", optopt);
- usage();
- return 1;
- }
- }
- argc -= optind;
- argv += optind;
- if(create_directories)
- {
- for(int i = 0; i < argc; i++)
- {
- char *dest = argv[i];
- if(mkdir_parents(dest, mode) != 0) return -1;
- if(opt_v) fprintf(stderr, "%s: Made directory: %s\n", argv0, dest);
- if(user != (uid_t)-1 || group != (gid_t)-1)
- {
- if(chown(dest, user, group) < 0)
- {
- fprintf(stderr,
- "%s: error: Failed changing ownership of '%s': %s\n",
- argv0,
- dest,
- strerror(errno));
- return -1;
- }
- }
- }
- return 0;
- }
- if(dest == NULL) dest = argv[--argc];
- bool multi_src = argc > 1;
- if(opt_T)
- {
- if(argc != 1)
- {
- fprintf(stderr,
- "%s: error: Option -T passed, expected exactly 1 source operand, got %d\n",
- argv0,
- argc);
- return 1;
- }
- assert(!multi_src);
- }
- else if(argc < 1)
- {
- fprintf(stderr, "%s: error: Expected at least 1 source operand\n", argv0);
- return 1;
- }
- if(create_parents)
- {
- char *destdir = dest;
- // Same as in mkdir_parents
- mode_t parent_mode = (S_IWUSR | S_IXUSR | ~mkdir_parents_filemask) & 0777;
- if(!multi_src)
- {
- char path_dup[PATH_MAX] = "";
- strncpy(path_dup, dest, PATH_MAX);
- destdir = dirname(path_dup);
- }
- if(mkdir_parents(destdir, parent_mode) != 0) return 1;
- }
- for(int i = 0; i < argc; i++)
- {
- char *src = argv[i];
- if(do_install(src, dest, multi_src) < 0) return 1;
- }
- return 0;
- }