// Copyright (C) 2019-2023 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program; if not, write to the Free Software Foundation, Inc., 51 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #pragma once #include #include #include #include #include #include #include #include #include #include #include #include class Config; class Context; namespace Util { using TraverseVisitor = std::function; enum class UnlinkLog { log_failure, ignore_failure }; // Get base name of path. std::string_view base_name(std::string_view path); // Get an integer value from bytes in big endian order. // // Parameters: // - buffer: Bytes to read. // - count: Number of bytes to read. template void big_endian_to_int(const uint8_t* buffer, T& value) { value = 0; for (size_t i = 0; i < sizeof(T); ++i) { value <<= 8; value |= buffer[i]; } } template<> inline void big_endian_to_int(const uint8_t* buffer, int8_t& value) { value = buffer[0]; } template<> inline void big_endian_to_int(const uint8_t* buffer, uint8_t& value) { value = buffer[0]; } // Remove the extension via `remove_extension()`, then add `new_ext`. `new_ext` // should start with a dot, no extra dot is inserted. std::string change_extension(std::string_view path, std::string_view new_ext); // Clone a file from `src` to `dest`. If `via_tmp_file` is true, `src` is cloned // to a temporary file and then renamed to `dest`. Throws `core::Error` on // error. void clone_file(const std::string& src, const std::string& dest, bool via_tmp_file = false); // Clone, hard link or copy a file from `source` to `dest` depending on settings // in `ctx`. If cloning or hard linking cannot and should not be done the file // will be copied instead. Throws `core::Error` on error. void clone_hard_link_or_copy_file(const Config& config, const std::string& source, const std::string& dest, bool via_tmp_file = false); // Compute the length of the longest directory path that is common to paths // `dir` (a directory) and `path` (any path). size_t common_dir_prefix_length(std::string_view dir, std::string_view path); // Copy all data from `fd_in` to `fd_out`. Throws `core::Error` on error. void copy_fd(int fd_in, int fd_out); // Copy a file from `src` to `dest`. If via_tmp_file is true, `src` is copied to // a temporary file and then renamed to dest. Throws `core::Error` on error. void copy_file(const std::string& src, const std::string& dest, bool via_tmp_file = false); // Create a directory if needed, including its parents if needed. // // Returns true if the directory exists or could be created, otherwise false. bool create_dir(std::string_view dir); // Get directory name of path. std::string_view dir_name(std::string_view path); // Like create_dir but throws Fatal on error. void ensure_dir_exists(std::string_view dir); // Expand all instances of $VAR or ${VAR}, where VAR is an environment variable, // in `str`. Throws `core::Error` if one of the environment variables. [[nodiscard]] std::string expand_environment_variables(const std::string& str); // Extends file size to at least new_size by calling posix_fallocate() if // supported, otherwise by writing zeros last to the file. // // Note that existing holes are not filled in case posix_fallocate() is not // supported. // // Returns 0 on success, an error number otherwise. int fallocate(int fd, long new_size); // Format `argv` as a simple string for logging purposes. That is, the result is // not intended to be machine parsable. `argv` must be terminated by a nullptr. std::string format_argv_for_logging(const char* const* argv); // Format a hexadecimal string representing `size` bytes of `data`. The returned // string will be `2 * size` long. std::string format_base16(const uint8_t* data, size_t size); // Format a lowercase base32hex string representing `size` bytes of `data`. No // padding characters will be added. std::string format_base32hex(const uint8_t* data, size_t size); // Return current working directory (CWD) as returned from getcwd(3) (i.e., // normalized path without symlink parts). Returns the empty string on error. std::string get_actual_cwd(); // Return current working directory (CWD) by reading the environment variable // PWD (thus keeping any symlink parts in the path and potentially ".." or "//" // parts). If PWD does not resolve to the same i-node as `actual_cwd` then // `actual_cwd` is returned instead. std::string get_apparent_cwd(const std::string& actual_cwd); // Return the file extension (including the dot) as a view into `path`. If // `path` has no file extension, an empty string_view is returned. std::string_view get_extension(std::string_view path); // Return the current user's home directory, or throw `Fatal` if it can't // be determined. std::string get_home_directory(); // Return a static string with the current hostname. const char* get_hostname(); // Compute a relative path from `dir` (an absolute path to a directory) to // `path` (an absolute path). Assumes that both `dir` and `path` are normalized. // The algorithm does *not* follow symlinks, so the result may not actually // resolve to the same file as `path`. std::string get_relative_path(std::string_view dir, std::string_view path); // Get process umask. mode_t get_umask(); // Hard-link `oldpath` to `newpath`. Throws `core::Error` on error. void hard_link(const std::string& oldpath, const std::string& newpath); // Write bytes in big endian order from an integer value. // // Parameters: // - value: Integer value to read. // - buffer: Buffer to write bytes to. template void int_to_big_endian(T value, uint8_t* buffer) { for (size_t i = 0; i < sizeof(T); ++i) { buffer[sizeof(T) - i - 1] = value & 0xFF; value >>= 8; } } template<> inline void int_to_big_endian(uint8_t value, uint8_t* buffer) { buffer[0] = value; } template<> inline void int_to_big_endian(int8_t value, uint8_t* buffer) { buffer[0] = value; } // Determine if `path` is an absolute path with prefix, returning the split // point. std::optional is_absolute_path_with_prefix(std::string_view path); // Detmine if `path` refers to a ccache executable. bool is_ccache_executable(std::string_view path); // Return whether `ch` is a directory separator, i.e. '/' on POSIX systems and // '/' or '\\' on Windows systems. inline bool is_dir_separator(char ch) { return ch == '/' #ifdef _WIN32 || ch == '\\' #endif ; } // Return whether `path` represents a precompiled header (see "Precompiled // Headers" in GCC docs). bool is_precompiled_header(std::string_view path); // Thread-safe version of `localtime(3)`. If `time` is not specified the current // time of day is used. std::optional localtime(std::optional time = {}); // Construct a normalized native path. // // Example: // // std::string path = Util::make_path("usr", "local", "bin"); template std::string make_path(const T&... args) { return (std::filesystem::path{} / ... / args).lexically_normal().string(); } // Make a relative path from current working directory (either `actual_cwd` or // `apparent_cwd`) to `path` if `path` is under `base_dir`. std::string make_relative_path(const std::string& base_dir, const std::string& actual_cwd, const std::string& apparent_cwd, std::string_view path); // Like above but with base directory and apparent/actual CWD taken from `ctx`. std::string make_relative_path(const Context& ctx, std::string_view path); // Return whether `path` is equal to `dir_prefix_or_file` or if // `dir_prefix_or_file` is a directory prefix of `path`. bool matches_dir_prefix_or_file(std::string_view dir_prefix_or_file, std::string_view path); // Normalize absolute path `path`, not taking symlinks into account. // // Normalization here means syntactically removing redundant slashes and // resolving "." and ".." parts. The algorithm does however *not* follow // symlinks, so the result may not actually resolve to the same filesystem entry // as `path` (nor to any existing file system entry for that matter). // // On Windows: Backslashes are replaced with forward slashes. std::string normalize_abstract_absolute_path(std::string_view path); // Like normalize_abstract_absolute_path, but returns `path` unchanged if the // normalized result doesn't resolve to the same file system entry as `path`. std::string normalize_concrete_absolute_path(const std::string& path); // Parse `duration`, an unsigned integer with d (days) or s (seconds) suffix, // into seconds. Throws `core::Error` on error. uint64_t parse_duration(std::string_view duration); #ifndef _WIN32 // Like readlink(2) but returns the string (or the empty string on failure). std::string read_link(const std::string& path); #endif // Return a normalized absolute path of `path`. On error (e.g. if the `path` // doesn't exist) the empty string is returned if return_empty_on_error is true, // otherwise `path` unmodified. std::string real_path(const std::string& path, bool return_empty_on_error = false); // Return a view into `path` containing the given path without the filename // extension as determined by `get_extension()`. std::string_view remove_extension(std::string_view path); // Rename `oldpath` to `newpath` (deleting `newpath`). Throws `core::Error` on // error. void rename(const std::string& oldpath, const std::string& newpath); // Send `text` to file descriptor `fd`, optionally stripping ANSI color // sequences if `ctx.args_info.strip_diagnostics_colors` is true and rewriting // paths to absolute if `ctx.config.absolute_paths_in_stderr` is true. Throws // `core::Error` on error. void send_to_fd(const Context& ctx, std::string_view text, int fd); // Set the FD_CLOEXEC on file descriptor `fd`. This is a NOP on Windows. void set_cloexec_flag(int fd); // Set process umask. Returns the previous mask. mode_t set_umask(mode_t mask); // Set environment variable `name` to `value`. void setenv(const std::string& name, const std::string& value); // Return size change in KiB between `old_stat` and `new_stat`. inline int64_t size_change_kibibyte(const Stat& old_stat, const Stat& new_stat) { return (static_cast(new_stat.size_on_disk()) - static_cast(old_stat.size_on_disk())) / 1024; } // Split `string` into tokens at any of the characters in `separators`. These // tokens are views into `string`. `separators` must neither be the empty string // nor a nullptr. std::vector split_into_views(std::string_view string, const char* separators, util::Tokenizer::Mode mode = util::Tokenizer::Mode::skip_empty, util::Tokenizer::IncludeDelimiter include_delimiter = util::Tokenizer::IncludeDelimiter::no); // Same as `split_into_views` but the tokens are copied from `string`. std::vector split_into_strings( std::string_view string, const char* separators, util::Tokenizer::Mode mode = util::Tokenizer::Mode::skip_empty, util::Tokenizer::IncludeDelimiter include_delimiter = util::Tokenizer::IncludeDelimiter::no); // Returns a copy of string with the specified ANSI CSI sequences removed. [[nodiscard]] std::string strip_ansi_csi_seqs(std::string_view string); // Convert a string to lowercase. [[nodiscard]] std::string to_lowercase(std::string_view string); // Traverse `path` recursively (postorder, i.e. files are visited before their // parent directory). // // Throws core::Error on error. void traverse(const std::string& path, const TraverseVisitor& visitor); // Remove `path` (non-directory), NFS safe. Logs according to `unlink_log`. // // Returns whether removal was successful. A nonexistent `path` is considered a // failure. bool unlink_safe(const std::string& path, UnlinkLog unlink_log = UnlinkLog::log_failure); // Remove `path` (non-directory), NFS hazardous. Use only for files that will // not exist on other systems. Logs according to `unlink_log`. // // Returns whether removal was successful. A nonexistent `path` is considered // successful. bool unlink_tmp(const std::string& path, UnlinkLog unlink_log = UnlinkLog::log_failure); // Unset environment variable `name`. void unsetenv(const std::string& name); // Remove `path` (and its contents if it's a directory). A nonexistent path is // not considered an error. // // Throws core::Error on error. void wipe_path(const std::string& path); } // namespace Util