diff --git a/Userland/cp.cpp b/Userland/cp.cpp index a2ddb8bda45..1a4a5d554ec 100644 --- a/Userland/cp.cpp +++ b/Userland/cp.cpp @@ -2,17 +2,21 @@ #include #include #include +#include #include #include #include #include #include -bool copy_file(String, String); +bool copy_file_or_directory(String, String, bool); +bool copy_file(String, String, struct stat, int); +bool copy_directory(String, String); int main(int argc, char** argv) { CArgsParser args_parser("cp"); + args_parser.add_arg("r", "copy directories recursively"); args_parser.add_required_single_value("source"); args_parser.add_required_single_value("destination"); @@ -22,16 +26,19 @@ int main(int argc, char** argv) args_parser.print_usage(); return 0; } + bool recursion_allowed = args.is_present("r"); String src_path = values[0]; String dst_path = values[1]; - return copy_file(src_path, dst_path) ? 0 : 1; + return copy_file_or_directory(src_path, dst_path, recursion_allowed) ? 0 : 1; } /** - * Copy a source file to a destination file. Returns true if successful, false + * Copy a file or directory to a new location. Returns true if successful, false * otherwise. If there is an error, its description is output to stderr. + * + * Directories should only be copied if recursion_allowed is set. */ -bool copy_file(String src_path, String dst_path) +bool copy_file_or_directory(String src_path, String dst_path, bool recursion_allowed) { int src_fd = open(src_path.characters(), O_RDONLY); if (src_fd < 0) { @@ -47,10 +54,23 @@ bool copy_file(String src_path, String dst_path) } if (S_ISDIR(src_stat.st_mode)) { - fprintf(stderr, "cp: FIXME: Copying directories is not yet supported\n"); - return false; + if (!recursion_allowed) { + fprintf(stderr, "cp: -r not specified; omitting directory '%s'\n", src_path.characters()); + return false; + } + return copy_directory(src_path, dst_path); } + return copy_file(src_path, dst_path, src_stat, src_fd); +} +/** + * Copy a source file to a destination file. Returns true if successful, false + * otherwise. If there is an error, its description is output to stderr. + * + * To avoid repeated work, the source file's stat and file descriptor are required. + */ +bool copy_file(String src_path, String dst_path, struct stat src_stat, int src_fd) +{ int dst_fd = creat(dst_path.characters(), 0666); if (dst_fd < 0) { if (errno != EISDIR) { @@ -94,7 +114,7 @@ bool copy_file(String src_path, String dst_path) auto my_umask = umask(0); umask(my_umask); - rc = fchmod(dst_fd, src_stat.st_mode & ~my_umask); + int rc = fchmod(dst_fd, src_stat.st_mode & ~my_umask); if (rc < 0) { perror("fchmod dst"); return false; @@ -104,3 +124,31 @@ bool copy_file(String src_path, String dst_path) close(dst_fd); return true; } + +/** + * Copy the contents of a source directory into a destination directory. + */ +bool copy_directory(String src_path, String dst_path) +{ + int rc = mkdir(dst_path.characters(), 0755); + if (rc < 0) { + perror("cp: mkdir"); + return false; + } + CDirIterator di(src_path, CDirIterator::SkipDots); + if (di.has_error()) { + fprintf(stderr, "cp: CDirIterator: %s\n", di.error_string()); + return false; + } + while (di.has_next()) { + String filename = di.next_path(); + bool is_copied = copy_file_or_directory( + String::format("%s/%s", src_path.characters(), filename.characters()), + String::format("%s/%s", dst_path.characters(), filename.characters()), + true); + if (!is_copied) { + return false; + } + } + return true; +} \ No newline at end of file