diff --git a/src/dotfile.cpp b/src/dotfile.cpp index ca8a8ff..272db8f 100644 --- a/src/dotfile.cpp +++ b/src/dotfile.cpp @@ -4,9 +4,14 @@ * SPDX-License-Identifier: MIT */ -#include // fprintf, printf +#include // tolower +#include // size_t +#include // fprintf, printf, stderr #include +#include // getpwnam #include +#include // error_code +#include // geteuid, getlogin, setegid, seteuid #include #include "dotfile.h" @@ -14,6 +19,7 @@ namespace Util { std::vector Dotfile::s_excludePaths; +std::vector Dotfile::s_systemDirectories; std::filesystem::path Dotfile::s_workingDirectory; Dotfile::Dotfile() @@ -26,6 +32,122 @@ Dotfile::~Dotfile() // ----------------------------------------- +void Dotfile::add(const std::vector& targets) +{ + if (targets.empty()) { + fprintf(stderr, "\033[31;1mDotfile:\033[0m no files or directories selected\n"); + return; + } + + std::vector noExistTargets; + std::vector homeTargets; + std::vector systemTargets; + + // Separate home and system targets + bool isSystem = false; + for (size_t i = 0; i < targets.size(); ++i) { + if (!std::filesystem::is_regular_file(targets.at(i)) + && !std::filesystem::is_directory(targets.at(i)) + && !std::filesystem::is_symlink(targets.at(i))) { + noExistTargets.push_back(i); + continue; + } + + isSystem = false; + for (const auto& systemDirectory : s_systemDirectories) { + if (targets.at(i).find(systemDirectory) == 0) { + isSystem = true; + } + } + + if (isSystem) { + systemTargets.push_back(i); + } + else { + homeTargets.push_back(i); + } + } + + // Print non-existing targets and exit + if (!noExistTargets.empty()) { + for (size_t i : noExistTargets) { + fprintf(stderr, "\033[31;1mDotfile:\033[0m '%s': no such file or directory\n", targets.at(i).c_str()); + } + return; + } + + // Print root-owned targets and exit + bool root = !geteuid() ? true : false; + if (!systemTargets.empty() && !root) { + for (size_t i : systemTargets) { + fprintf(stderr, "\033[31;1mDotfile:\033[0m you cannot copy system file '%s' unless you are root\n", targets.at(i).c_str()); + } + return; + } + + // Get the password database record (/etc/passwd) of the user logged in on + // the controlling terminal of the process + passwd* user = getpwnam(getlogin()); + + const auto copyOptions = std::filesystem::copy_options::overwrite_existing + | std::filesystem::copy_options::recursive + | std::filesystem::copy_options::copy_symlinks; + + auto printError = [](const std::filesystem::path& path, const std::error_code& error) -> void { + // std::filesystem::copy doesnt respect 'overwrite_existing' for symlinks + if (error.value() && error.message() != "File exists") { + fprintf(stderr, "\033[31;1mDotfile:\033[0m '%s': %c%s\n", + path.c_str(), + tolower(error.message().c_str()[0]), + error.message().c_str() + 1); + } + }; + + auto copyTarget = [&root, &user, &printError](const std::filesystem::path& from, + const std::filesystem::path& to, bool homePath) -> void { + if (homePath && root) { + seteuid(user->pw_uid); + setegid(user->pw_gid); + } + + // Create directory for the file + std::error_code error; + if (std::filesystem::is_regular_file(from)) { + auto directory = to.relative_path().parent_path(); + if (!directory.empty() && !std::filesystem::exists(directory) ) { + printf("created directory '%s'\n", directory.c_str()); + std::filesystem::create_directories(directory, error); + printError(to.relative_path().parent_path(), error); + } + } + + // Copy the file or directory + printf("'%s' -> '%s'\n", from.c_str(), to.c_str()); + std::filesystem::copy(from, to, copyOptions, error); + printError(to, error); + + if (homePath && root) { + seteuid(0); + setegid(0); + } + }; + + // /home// + std::string home = "/home/" + std::string(user->pw_name); + for (size_t index : homeTargets) { + copyTarget(std::filesystem::path(targets.at(index)), + std::filesystem::path(targets.at(index).substr(home.size() + 1)), + true); + } + // / + for (size_t index : systemTargets) { + auto path = std::filesystem::path(targets.at(index)); + copyTarget(path, + path.relative_path(), + false); + } +} + void Dotfile::list(const std::vector& targets) { if (s_workingDirectory.empty()) { diff --git a/src/dotfile.h b/src/dotfile.h index 17dd240..609f269 100644 --- a/src/dotfile.h +++ b/src/dotfile.h @@ -29,9 +29,11 @@ public: std::string path; }; + static void add(const std::vector& targets = {}); static void list(const std::vector& targets = {}); static void setWorkingDirectory(std::filesystem::path directory) { s_workingDirectory = directory; } + static void setSystemDirectories(const std::vector& systemDirectories) { s_systemDirectories = systemDirectories; } static void setExcludePaths(const std::vector& excludePaths) { s_excludePaths = excludePaths; } private: @@ -39,6 +41,7 @@ private: static bool include(const std::filesystem::path& path, const std::vector& targets); static std::vector s_excludePaths; + static std::vector s_systemDirectories; static std::filesystem::path s_workingDirectory; };