/** * file: cp.c * command: cp * author: Tyler Murphy */ #include "command.h" #include "lslib.h" #include #include #include #include #include #include #include #include /** * Command flags to be used with cp */ static struct { bool recurse; /* Recurse copy directorys */ bool preserve; /* Preserve file attributes */ bool sym_link; /* Symlink files instead of copy */ bool hard_link; /* Hardlink files instead of copy */ bool verbose; /* Verbose the output */ } flags; /** * Help function for cp */ 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"); } /** * Takes in each argument that has a single - and parses it * @param c the character after the - * @param next the next argument in argv that hasnt been parsed * @reutrn if the next arg was used or if the arg was invalid */ 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; } /** * Takes in two files paths and copies the data from one path to another * @param from the file to copy from * @param to the file to copy to * @returns if the file successfully coppied */ static bool copy_file(char* from, char* to) { #define BS 1024 /* The block size of copy with */ FILE *from_f, *to_f; /* file pointers to cope data from and to */ char buf[BS]; /* te file copy buffer */ int read; /* the amount of bytes read */ /* attempt to get a read stream from the src file */ from_f = get_file_s(from, "r"); if (from_f == NULL) { return false; } /* attempt to get a write streams from the dest file */ to_f = get_file_s(to, "w"); if (to_f == NULL) { fclose(from_f); return false; } /* while data is read copy the data to the dest */ while ((read = fread(buf, 1, BS, from_f)) > 0) { fwrite(buf, 1, read, to_f); } /* output if verbose */ if (flags.verbose) { output("copied '%s'", from); } return true; } /** * Takes in two file paths and symlinks from one to the other path * @param from the file to symlink * @param to the path to put the symlink * @returns if the file successfully symlinked */ static bool symlink_file(char* from, char* to) { /* symlink the file and error if failed */ if (symlink(from, to) < 0) { error_s("failed to symlink '%s'", from); return false; } else if (flags.verbose) { output("symlinked '%s'", from); /* output if verbose */ } return true; } /** * Takes in two file paths and hardlinks from one to the other path * @param from the file to hardlink * @param the path to put the hardlink * @returns fi the fule successfully hardlinked */ static bool hardlink_file(char* from, char* to) { /* hard link the file and error if failed */ if (link(from, to) < 0) { error_s("failed to hardlink '%s'", from); return false; } else if (flags.verbose) { output("hardlinked '%s'", from); /* output if verbose */ } return true; } /** * Copies data from one path to another using the set path buffers and * flags. Make sure to set these before running this function. * @param s the stat of the file being coppied */ static void run_copy(struct stat* s) { /* read file paths from the path buffers */ char* from = get_path_buffer(); char* to = get_path_buffer_2(); /* choose copy method based on given flags */ bool result; if (flags.sym_link) { result = symlink_file(from, to); /* symlink */ } else if (flags.hard_link) { result = hardlink_file(from, to); /* hardlink */ } else { result = copy_file(from, to); /* copy */ } /* if failed return */ if (!result) return; if (!flags.preserve) return; /* if set to preserve file attribs, copy over ownership and permissions */ if (chmod(to, s->st_mode) < 0) { error_s("cannot chmod '%s'", to); /* error if failed */ return; } if (chown(to, s->st_uid, s->st_gid) < 0) { error_s("cannot chown '%s'", to); /* error if failed */ return; } } /* predefine function definition */ static void cp_file(char* path); /** * Copies all files in a diretory specified in the path buffer, to the dest. * Make sure both the src and dest path buffers are set before calling. * @param s the stat of the directory */ static void cp_directory(struct stat* s) { DIR* d; struct dirent* file; /* if were not recursing ignore directory and error */ if (!flags.recurse) { error_s("-r not specified; omitting directory '%s'", get_path_buffer()); return; } /* if we failed to make the directory to copy data into error and return */ if (mkdir(get_path_buffer_2(), s->st_mode) < 0 && errno != EEXIST) { error_s("cannot create directory '%s'", get_path_buffer_2()); return; } /* open directory, if failed error and return */ d = opendir(get_path_buffer()); if (d == NULL) { error_s("cannot open directory '%s'", get_path_buffer()); return; } /* copy each file in the directory */ while ((file = readdir(d)) != NULL) { if (is_dot_dir(file->d_name)) continue; cp_file(file->d_name); } } /** * Copies a given file path to the destination given in the 2nd path buffer * Make sure path buffer 2 is set before running this function * @param path the file to copy */ static void cp_file(char* path) { /* load src and dest into path buffers */ int save = push_path_buffer(path); int save2 = push_path_buffer_2(get_last_component(path)); /* if we cannot get file info error and return */ struct stat s; if (lstat(get_path_buffer(), &s) < 0) { pop_path_buffer(save); error_s("cannot stat '%s'", get_path_buffer()); return; } /* if the file is a directory copy it as a directory, else copy it as a file */ if (S_ISDIR(s.st_mode)) { cp_directory(&s); } else { run_copy(&s); } /* cleanup */ pop_path_buffer(save); pop_path_buffer_2(save2); } /** * Copes file from a src path to a dest path */ COMMAND(cp_main) { int start, i; struct stat s; /* define default flags */ flags.hard_link = false; flags.sym_link = false; flags.preserve = false; flags.recurse = false; flags.verbose = false; /* parse arguments */ start = parse_args(argc, argv, help, short_arg, NULL); /* if not enough arguments passed show help message and quit */ 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) { /* if cannot read file info error and exit */ struct stat s; if (lstat(argv[start], &s) < 0) { error("cannot stat '%s'", argv[start]); } /* load src and dest into path buffers */ push_path_buffer(argv[argc-2]); push_path_buffer_2(argv[argc-1]); /* if the file is a directory copy it as a directory, else copy it as a file */ if (!S_ISDIR(s.st_mode)) { run_copy(&s); } else { cp_directory(&s); } /* no need to cleanup path buffers since command is done */ return EXIT_SUCCESS; } /* otherwise treat the dest argument as a directory */ /* push dest directory */ push_path_buffer_2(argv[argc-1]); /* if we cannot read dest info error and return */ if (lstat(get_path_buffer_2(), &s) < 0) { error("target: '%s'", get_path_buffer_2()); } /* copy each file into dest directory */ for (i = start; i < argc - 1; i++) { cp_file(argv[i]); } return EXIT_SUCCESS; }