Serenity Operating System
at portability 193 lines 6.3 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/FileSystemPath.h> 28#include <AK/String.h> 29#include <AK/StringBuilder.h> 30#include <LibCore/ArgsParser.h> 31#include <LibCore/DirIterator.h> 32#include <assert.h> 33#include <fcntl.h> 34#include <stdio.h> 35#include <sys/stat.h> 36#include <unistd.h> 37 38bool copy_file_or_directory(String, String, bool); 39bool copy_file(String, String, struct stat, int); 40bool copy_directory(String, String); 41 42int main(int argc, char** argv) 43{ 44 if (pledge("stdio rpath wpath cpath fattr", nullptr) < 0) { 45 perror("pledge"); 46 return 1; 47 } 48 49 bool recursion_allowed = false; 50 Vector<const char*> sources; 51 const char* destination = nullptr; 52 53 Core::ArgsParser args_parser; 54 args_parser.add_option(recursion_allowed, "Copy directories recursively", "recursive", 'r'); 55 args_parser.add_positional_argument(sources, "Source file path", "source"); 56 args_parser.add_positional_argument(destination, "Destination file path", "destination"); 57 args_parser.parse(argc, argv); 58 59 for (auto& source : sources) { 60 bool ok = copy_file_or_directory(source, destination, recursion_allowed); 61 if (!ok) 62 return 1; 63 } 64 return 0; 65} 66 67/** 68 * Copy a file or directory to a new location. Returns true if successful, false 69 * otherwise. If there is an error, its description is output to stderr. 70 * 71 * Directories should only be copied if recursion_allowed is set. 72 */ 73bool copy_file_or_directory(String src_path, String dst_path, bool recursion_allowed) 74{ 75 int src_fd = open(src_path.characters(), O_RDONLY); 76 if (src_fd < 0) { 77 perror("open src"); 78 return false; 79 } 80 81 struct stat src_stat; 82 int rc = fstat(src_fd, &src_stat); 83 if (rc < 0) { 84 perror("stat src"); 85 return false; 86 } 87 88 if (S_ISDIR(src_stat.st_mode)) { 89 if (!recursion_allowed) { 90 fprintf(stderr, "cp: -r not specified; omitting directory '%s'\n", src_path.characters()); 91 return false; 92 } 93 return copy_directory(src_path, dst_path); 94 } 95 return copy_file(src_path, dst_path, src_stat, src_fd); 96} 97 98/** 99 * Copy a source file to a destination file. Returns true if successful, false 100 * otherwise. If there is an error, its description is output to stderr. 101 * 102 * To avoid repeated work, the source file's stat and file descriptor are required. 103 */ 104bool copy_file(String src_path, String dst_path, struct stat src_stat, int src_fd) 105{ 106 int dst_fd = creat(dst_path.characters(), 0666); 107 if (dst_fd < 0) { 108 if (errno != EISDIR) { 109 perror("open dst"); 110 return false; 111 } 112 StringBuilder builder; 113 builder.append(dst_path); 114 builder.append('/'); 115 builder.append(FileSystemPath(src_path).basename()); 116 dst_path = builder.to_string(); 117 dst_fd = creat(dst_path.characters(), 0666); 118 if (dst_fd < 0) { 119 perror("open dst"); 120 return false; 121 } 122 } 123 124 if (src_stat.st_size > 0) { 125 if (ftruncate(dst_fd, src_stat.st_size) < 0) { 126 perror("cp: ftruncate"); 127 return false; 128 } 129 } 130 131 for (;;) { 132 char buffer[32768]; 133 ssize_t nread = read(src_fd, buffer, sizeof(buffer)); 134 if (nread < 0) { 135 perror("read src"); 136 return false; 137 } 138 if (nread == 0) 139 break; 140 ssize_t remaining_to_write = nread; 141 char* bufptr = buffer; 142 while (remaining_to_write) { 143 ssize_t nwritten = write(dst_fd, bufptr, remaining_to_write); 144 if (nwritten < 0) { 145 perror("write dst"); 146 return false; 147 } 148 assert(nwritten > 0); 149 remaining_to_write -= nwritten; 150 bufptr += nwritten; 151 } 152 } 153 154 auto my_umask = umask(0); 155 umask(my_umask); 156 int rc = fchmod(dst_fd, src_stat.st_mode & ~my_umask); 157 if (rc < 0) { 158 perror("fchmod dst"); 159 return false; 160 } 161 162 close(src_fd); 163 close(dst_fd); 164 return true; 165} 166 167/** 168 * Copy the contents of a source directory into a destination directory. 169 */ 170bool copy_directory(String src_path, String dst_path) 171{ 172 int rc = mkdir(dst_path.characters(), 0755); 173 if (rc < 0) { 174 perror("cp: mkdir"); 175 return false; 176 } 177 Core::DirIterator di(src_path, Core::DirIterator::SkipDots); 178 if (di.has_error()) { 179 fprintf(stderr, "cp: CDirIterator: %s\n", di.error_string()); 180 return false; 181 } 182 while (di.has_next()) { 183 String filename = di.next_path(); 184 bool is_copied = copy_file_or_directory( 185 String::format("%s/%s", src_path.characters(), filename.characters()), 186 String::format("%s/%s", dst_path.characters(), filename.characters()), 187 true); 188 if (!is_copied) { 189 return false; 190 } 191 } 192 return true; 193}