cp-stub.c (7066B)
- // 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 <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_metadata = false, opt_R = false;
- bool follow_src_symlinks = false;
- mode_t mode = 00755;
- char *argv0 = "cp";
- // Copied from utils-std/lib/path.c remove on de-stubbing
- static char *
- static_basename(char *path)
- {
- char *sep = strrchr(path, '/');
- return (sep == NULL) ? path : sep + 1;
- }
- static int
- do_copy(char *src, char *dest, bool multi_src)
- {
- assert(errno == 0);
- struct stat src_stat;
- if(fstatat(AT_FDCWD, src, &src_stat, follow_src_symlinks ? 0 : AT_SYMLINK_NOFOLLOW) < 0)
- {
- fprintf(
- stderr, "%s: Failed getting status for source '%s': %s\n", argv0, dest, strerror(errno));
- return -1;
- }
- struct stat dest_stat;
- if(stat(dest, &dest_stat) < 0)
- {
- if(errno == ENOENT)
- {
- errno = 0;
- }
- else
- {
- fprintf(stderr,
- "%s: Failed getting status for destination '%s': %s\n",
- argv0,
- dest,
- strerror(errno));
- return -1;
- }
- }
- char *dest_path = dest;
- char target[PATH_MAX] = "";
- if(multi_src || S_ISDIR(dest_stat.st_mode))
- {
- 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;
- }
- if(S_ISDIR(src_stat.st_mode))
- {
- if(!opt_R)
- {
- fprintf(
- stderr, "%s: Option -R not specified and source file '%s' is a directory\n", argv0, src);
- return -1;
- }
- if(mkdir(dest_path, mode | S_IRWXU) != 0)
- {
- if(errno != EEXIST)
- {
- fprintf(stderr,
- "%s: Failed creating target directory '%s': %s\n",
- argv0,
- dest,
- strerror(errno));
- return -1;
- }
- else
- errno = 0;
- }
- if(preserve_metadata)
- {
- struct timespec src_times[2] = {src_stat.st_atim, src_stat.st_mtim};
- if(utimensat(AT_FDCWD, dest_path, src_times, 0) != 0)
- {
- fprintf(stderr,
- "%s: Error while setting access/modification times on '%s': %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- if(chown(dest_path, src_stat.st_uid, src_stat.st_gid) < 0)
- {
- fprintf(stderr,
- "%s: Error: Failed setting ownership on file '%s': %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- }
- fprintf(stderr, "%s: FIXME: Copy the files inside '%s/'\n", argv0, dest);
- return -1;
- }
- else if(S_ISLNK(src_stat.st_mode))
- {
- char link[PATH_MAX] = "";
- if(readlink(src, link, sizeof(link)) < 0)
- {
- fprintf(stderr, "%s: Failed reading symlink of '%s': %s\n", argv0, src, strerror(errno));
- return -1;
- }
- if(symlink(link, dest_path) != 0)
- {
- fprintf(stderr,
- "%s: Failed copying symlink '%s' to '%s': %s\n",
- argv0,
- src,
- dest_path,
- strerror(errno));
- return -1;
- }
- if(preserve_metadata)
- {
- struct timespec src_times[2] = {src_stat.st_atim, src_stat.st_mtim};
- if(utimensat(AT_FDCWD, dest_path, src_times, AT_SYMLINK_NOFOLLOW) != 0)
- {
- fprintf(stderr,
- "%s: Error while setting access/modification times on '%s': %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- if(chown(dest, src_stat.st_uid, src_stat.st_gid) < 0)
- {
- fprintf(stderr,
- "%s: Error: Failed setting ownership on file '%s': %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- }
- }
- else if(S_ISREG(src_stat.st_mode))
- {
- // FIXME: Handle writing into block devices
- int src_fd = open(src, O_RDONLY | O_NOFOLLOW);
- if(src_fd < 0)
- {
- fprintf(
- stderr, "%s: Failed opening file '%s' for reading: %s\n", argv0, src, strerror(errno));
- return -1;
- }
- // FIXME: Handle -i (consent)
- int dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, mode);
- if(dest_fd < 0)
- {
- // FIXME: Handle -f (unlink on creation-opening fail)
- 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(preserve_metadata)
- {
- 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(fchown(dest_fd, src_stat.st_uid, src_stat.st_gid) < 0)
- {
- fprintf(stderr,
- "%s: Error: Failed setting ownership on file '%s': %s\n",
- argv0,
- dest_path,
- strerror(errno));
- return -1;
- }
- }
- assert(errno == 0);
- 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;
- }
- }
- else
- {
- fprintf(stderr, "%s: Unhandled file '%s' of type-mode %o\n", argv0, dest, src_stat.st_mode);
- return -1;
- }
- assert(errno == 0);
- return 0;
- }
- static void
- usage()
- {
- fprintf(stderr, "Usage: cp [-p] [-rR [-H|-L|-P]] source... destination\n");
- }
- int
- main(int argc, char *argv[])
- {
- int c = -1;
- while((c = getopt(argc, argv, ":pRHLP")) != -1)
- {
- switch(c)
- {
- case 'p':
- preserve_metadata = true;
- break;
- case 'R':
- case 'r':
- opt_R = true;
- break;
- case 'H':
- // FIXME: Only first level aka arguments, not traversal
- follow_src_symlinks = true;
- break;
- case 'L':
- follow_src_symlinks = true;
- break;
- case 'P':
- follow_src_symlinks = false;
- break;
- case '?':
- fprintf(stderr, "install: Unknown option '-%c'\n", optopt);
- usage();
- break;
- }
- }
- assert(errno == 0);
- argc -= optind;
- argv += optind;
- if(argc == 2)
- {
- if(do_copy(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_copy(src, dest, true) < 0) return 1;
- }
- }
- return 0;
- }