diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt index 276d0196..28f7f574 100644 --- a/userspace/CMakeLists.txt +++ b/userspace/CMakeLists.txt @@ -5,6 +5,7 @@ project(userspace CXX) set(USERSPACE_PROJECTS cat cat-mmap + cp dd echo id diff --git a/userspace/cp/CMakeLists.txt b/userspace/cp/CMakeLists.txt new file mode 100644 index 00000000..89f6e2af --- /dev/null +++ b/userspace/cp/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.26) + +project(cp CXX) + +set(SOURCES + main.cpp +) + +add_executable(cp ${SOURCES}) +target_compile_options(cp PUBLIC -O2 -g) +target_link_libraries(cp PUBLIC libc ban) + +add_custom_target(cp-install + COMMAND sudo cp ${CMAKE_CURRENT_BINARY_DIR}/cp ${BANAN_BIN}/ + DEPENDS cp + USES_TERMINAL +) diff --git a/userspace/cp/main.cpp b/userspace/cp/main.cpp new file mode 100644 index 00000000..2e0c263c --- /dev/null +++ b/userspace/cp/main.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include +#include + +#define STR_STARTS_WITH(str, arg) (strncmp(str, arg, sizeof(arg) - 1) == 0) +#define STR_EQUAL(str, arg) (strcmp(str, arg) == 0) + +bool copy_file(const BAN::String& source, BAN::String destination) +{ + struct stat st; + if (stat(source.data(), &st) == -1) + { + fprintf(stderr, "%s: ", source.data()); + perror("stat"); + return false; + } + if (!S_ISREG(st.st_mode)) + { + fprintf(stderr, "%s: not a directory\n", source.data()); + return false; + } + + if (stat(destination.data(), &st) != -1 && S_ISDIR(st.st_mode)) + { + MUST(destination.push_back('/')); + MUST(destination.append(MUST(source.sv().split('/')).back())); + } + + int src_fd = open(source.data(), O_RDONLY); + if (src_fd == -1) + { + fprintf(stderr, "%s: ", source.data()); + perror("open"); + return false; + } + + int dest_fd = open(destination.data(), O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (dest_fd == -1) + { + fprintf(stderr, "%s: ", destination.data()); + perror("open"); + close(src_fd); + return false; + } + + bool ret = true; + char buffer[1024]; + while (ssize_t nread = read(src_fd, buffer, sizeof(buffer))) + { + if (nread < 0) + { + fprintf(stderr, "%s: ", source.data()); + perror("read"); + ret = false; + break; + } + + size_t written = 0; + while (written < nread) + { + ssize_t nwrite = write(dest_fd, buffer, nread - written); + if (nwrite < 0) + { + fprintf(stderr, "%s: ", destination.data()); + perror("write"); + ret = false; + } + if (nwrite == 0) + break; + written += nwrite; + } + + if (written < nread) + break; + } + + close(src_fd); + close(dest_fd); + return ret; +} + +bool copy_file_to_directory(const BAN::String& source, const BAN::String& destination) +{ + auto temp = destination; + MUST(temp.append(MUST(source.sv().split('/')).back())); + return copy_file(source, destination); +} + +void usage(const char* argv0, int ret) +{ + FILE* out = (ret == 0) ? stdout : stderr; + fprintf(out, "usage: %s [OPTIONS]... SOURCE... DEST\n", argv0); + fprintf(out, "Copies files SOURCE... to DEST\n"); + fprintf(out, "OPTIONS:\n"); + fprintf(out, " -h, --help\n"); + fprintf(out, " Show this message and exit\n"); + exit(ret); +} + +int main(int argc, char** argv) +{ + BAN::Vector src; + BAN::StringView dest; + + int i = 1; + for (; i < argc; i++) + { + if (STR_EQUAL(argv[i], "-h") || STR_EQUAL(argv[i], "--help")) + { + usage(argv[0], 0); + } + else if (argv[i][0] == '-') + { + fprintf(stderr, "Unknown argument %s\n", argv[i]); + usage(argv[0], 1); + } + else + { + break; + } + } + + for (; i < argc - 1; i++) + MUST(src.push_back(argv[i])); + dest = argv[argc - 1]; + + if (src.empty()) + { + fprintf(stderr, "Missing destination operand\n"); + usage(argv[0], 1); + } + + int ret = 0; + for (auto file_path : src) + if (!copy_file(file_path, dest)) + ret = 1; + + return ret; +}