/* * Copyright (C) 2021 Riyyi * * SPDX-License-Identifier: MIT */ #include // tolower #include // size_t #include // fprintf, printf, stderr #include #include // function #include // getpwnam #include #include // error_code #include // geteuid, getlogin, setegid, seteuid #include #include "dotfile.h" namespace Util { std::vector Dotfile::s_excludePaths; std::vector Dotfile::s_systemDirectories; std::filesystem::path Dotfile::s_workingDirectory; Dotfile::Dotfile() { } 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 noExistPaths; std::vector homePaths; std::vector systemPaths; // Separate home and system targets 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))) { noExistPaths.push_back(i); continue; } if (isSystemTarget(targets.at(i))) { systemPaths.push_back(i); } else { homePaths.push_back(i); } } // Print non-existing targets and exit if (!noExistPaths.empty()) { for (size_t i : noExistPaths) { fprintf(stderr, "\033[31;1mDotfile:\033[0m '%s': no such file or directory\n", targets.at(i).c_str()); } return; } sync( targets, homePaths, systemPaths, [](std::string* paths, const std::string& homePath, const std::string& homeDirectory) { paths[0] = homePath; paths[1] = homePath.substr(homeDirectory.size() + 1); }, [](std::string* paths, const std::string& systemPath) { paths[0] = systemPath; paths[1] = systemPath.substr(1); }); } void Dotfile::list(const std::vector& targets) { if (s_workingDirectory.empty()) { fprintf(stderr, "\033[31;1mDotfile:\033[0m working directory is unset\n"); return; } if (!std::filesystem::is_directory(s_workingDirectory)) { fprintf(stderr, "\033[31;1mDotfile:\033[0m working directory is not a directory\n"); return; } forEachDotfile(targets, [](std::filesystem::directory_entry path, size_t, size_t workingDirectory) { printf("%s\n", path.path().c_str() + workingDirectory + 1); }); } void Dotfile::pull(const std::vector& targets) { std::vector dotfiles; std::vector homeFiles; std::vector systemFiles; // Separate home and system targets forEachDotfile(targets, [&](const std::filesystem::directory_entry& path, size_t index, size_t) { dotfiles.push_back(path.path().string()); if (isSystemTarget(path.path().string())) { systemFiles.push_back(index); } else { homeFiles.push_back(index); } }); size_t workingDirectory = s_workingDirectory.string().size(); sync( dotfiles, homeFiles, systemFiles, [&](std::string* paths, const std::string& homeFile, const std::string& homeDirectory) { // homeFile = /home//dotfiles/ // copy: /home// -> /home//dotfiles/ paths[0] = homeDirectory + homeFile.substr(workingDirectory); paths[1] = homeFile; }, [&](std::string* paths, const std::string& systemFile) { // systemFile = /home//dotfiles/ // copy: -> /home//dotfiles/ paths[0] = systemFile.substr(workingDirectory); paths[1] = systemFile; }); } // ----------------------------------------- void Dotfile::sync(const std::vector& files, const std::vector& homeIndices, const std::vector& systemIndices, const std::function& generateHomePaths, const std::function& generateSystemPaths) { bool root = !geteuid() ? true : false; if (!systemIndices.empty() && !root) { for (size_t i : systemIndices) { fprintf(stderr, "\033[31;1mDotfile:\033[0m you cannot copy system file '%s' unless you are root\n", files.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()); 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); } }; const auto copyOptions = std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive | std::filesystem::copy_options::copy_symlinks; auto copy = [&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 homeDirectory = "/home/" + std::string(user->pw_name); for (size_t i : homeIndices) { std::string paths[2]; generateHomePaths(paths, files.at(i), homeDirectory); copy(paths[0], paths[1], true); } // / for (size_t i : systemIndices) { std::string paths[2]; generateSystemPaths(paths, files.at(i)); copy(paths[0], paths[1], false); } } void Dotfile::forEachDotfile(const std::vector& targets, const std::function& callback) { size_t workingDirectory = s_workingDirectory.string().size(); size_t index = 0; for (const auto& path : std::filesystem::recursive_directory_iterator { s_workingDirectory }) { if (path.is_directory() || filter(path)) { continue; } if (!targets.empty() && !include(path.path().string(), targets)) { continue; } callback(path, index++, workingDirectory); } } bool Dotfile::filter(const std::filesystem::path& path) { for (auto& excludePath : s_excludePaths) { if (excludePath.type == ExcludeType::File) { if (path.string() == s_workingDirectory / excludePath.path) { return true; } } else if (excludePath.type == ExcludeType::Directory) { if (path.string().find(s_workingDirectory / excludePath.path) == 0) { return true; } } else if (excludePath.type == ExcludeType::EndsWith) { if (path.string().find(excludePath.path) == path.string().size() - excludePath.path.size()) { return true; } } } return false; } bool Dotfile::include(const std::filesystem::path& path, const std::vector& targets) { for (const auto& target : targets) { if (path.string().find(s_workingDirectory / target) == 0) { return true; } } return false; } bool Dotfile::isSystemTarget(const std::string& target) { for (const auto& systemDirectory : s_systemDirectories) { if (target.find(systemDirectory) == 0) { return true; } } return false; } } // namespace Util