diff options
Diffstat (limited to 'src/commands/cp.c')
-rw-r--r-- | src/commands/cp.c | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/src/commands/cp.c b/src/commands/cp.c new file mode 100644 index 0000000..37e3354 --- /dev/null +++ b/src/commands/cp.c @@ -0,0 +1,232 @@ +#include "../command.h" +#include <dirent.h> +#include <limits.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +static struct { + bool recurse; + bool preserve; + bool sym_link; + bool hard_link; + bool verbose; +} flags; + +static void help(void) { + printf("Usage: cp [-rplsv] SOURCE... DEST\n\n"); + printf("Copy SOURCEs to DEST\n\n"); + printf("\t-R,-r\tRecurse\n"); + printf("\t-p\tPreserve file attributes if possible\n"); + printf("\t-l,-s\tCreate (sym)links\n"); + printf("\t-v\tVerbose\n"); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'R': + case 'r': + flags.recurse = true; + break; + case 'p': + flags.preserve = true; + break; + case 'l': + flags.hard_link = true; + flags.sym_link = false; + break; + case 's': + flags.sym_link = true; + flags.hard_link = false; + break; + case 'v': + flags.verbose = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static bool copy_file(char* from, char* to) { + FILE* from_f = get_file_s(from, "r"); + if (from_f == NULL) { return false; } + + FILE* to_f = get_file_s(to, "w"); + if (to_f == NULL) { fclose(from_f); return false; } + + #define BS 1024 + + char buf[BS]; + int read; + + while ((read = fread(buf, 1, BS, from_f)) > 0) { + fwrite(buf, 1, read, to_f); + } + + if (flags.verbose) { + output("copied '%s'", from); + } + + return true; +} + +static bool symlink_file(char* from, char* to) { + if (symlink(from, to) < 0) { + error_s("failed to symlink '%s': %s", from, strerror(errno)); + return false; + } else if (flags.verbose) { + output("symlinked '%s'", from); + } + return true; +} + +static bool hardlink_file(char* from, char* to) { + if (link(from, to) < 0) { + error_s("failed to hardlink '%s': %s", from, strerror(errno)); + return false; + } else if (flags.verbose) { + output("hardlinked '%s'", from); + } + return true; +} + +static void run_copy(struct stat* s) { + + char* from = get_path_buffer(); + char* to = get_path_buffer_2(); + + bool result; + if (flags.sym_link) { + result = symlink_file(from, to); + } else if (flags.hard_link) { + result = hardlink_file(from, to); + } else { + result = copy_file(from, to); + } + + if (!result) return; + if (!flags.preserve) return; + + if (chmod(to, s->st_mode) < 0) { + error_s("cannot chmod '%s': %s", to, strerror(errno)); + return; + } + + if (chown(to, s->st_uid, s->st_gid) < 0) { + error_s("cannot chown '%s': %s", to, strerror(errno)); + return; + } +} + +static void cp_file(char* path); + +static void cp_directory(struct stat* s) { + if (!flags.recurse) { + error_s("-r not specified; omitting directory '%s'", get_path_buffer()); + return; + } + + if (mkdir(get_path_buffer_2(), s->st_mode) < 0 && errno != EEXIST) { + error_s("cannot create directory '%s': %s", get_path_buffer_2(), strerror(errno)); + return; + } + + DIR* d = opendir(get_path_buffer()); + + if (d == NULL) { + error_s("cannot open directory '%s': %s", get_path_buffer(), strerror(errno)); + return; + } + + struct dirent* file; + while ((file = readdir(d)) != NULL) { + if (is_dot_dir(file->d_name)) continue; + cp_file(file->d_name); + } +} + +static char* get_file_name(char* path) { + if (path[0] == '\0') return path; + + int last = 0; + int i = 0; + while (true) { + if (path[i+1] == '\0') break; + if (path[i] == '/') { + last = i; + } + i++; + } + + if (last == 0) return path; + else return path + last + 1; +} + +static void cp_file(char* path) { + + int save = push_path_buffer(path); + int save2 = push_path_buffer_2(get_file_name(path)); + + struct stat s; + if (lstat(get_path_buffer(), &s) < 0) { + pop_path_buffer(save); + error_s("cannot stat '%s': %s", get_path_buffer(), strerror(errno)); + return; + } + + if (S_ISDIR(s.st_mode)) { + cp_directory(&s); + } else { + run_copy(&s); + } + + pop_path_buffer(save); + pop_path_buffer_2(save2); +} + +COMMAND(cp) { + + flags.hard_link = false; + flags.sym_link = false; + flags.preserve = false; + flags.recurse = false; + flags.verbose = false; + + int start = parse_args(argc, argv, help, short_arg, NULL); + + if (argc - start < 2) { + global_help(help); + } + + // only when 2 args and first is a file, the 2nd will be a file + if (argc - start == 2) { + struct stat s; + if (lstat(argv[start], &s) < 0) { + error("cannot stat '%s': %s", argv[start], strerror(errno)); + } + push_path_buffer(argv[argc-2]); + push_path_buffer_2(argv[argc-1]); + if (!S_ISDIR(s.st_mode)) { + run_copy(&s); + } else { + cp_directory(&s); + } + return EXIT_SUCCESS; + } + + // push directory + push_path_buffer_2(argv[argc-1]); + + struct stat s; + if (lstat(get_path_buffer_2(), &s) < 0) { + error("target: '%s': %s", get_path_buffer_2(), strerror(errno)); + } + + for (int i = start; i < argc - 1; i++) { + cp_file(argv[i]); + } + + return EXIT_SUCCESS; +} |