diff options
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | dev.mk.in | 1 | ||||
-rw-r--r-- | src/CacheEntryReader.cpp | 1 | ||||
-rw-r--r-- | src/CacheEntryReader.hpp | 2 | ||||
-rw-r--r-- | src/CacheFile.cpp | 27 | ||||
-rw-r--r-- | src/CacheFile.hpp | 7 | ||||
-rw-r--r-- | src/Decompressor.hpp | 3 | ||||
-rw-r--r-- | src/Stat.cpp | 45 | ||||
-rw-r--r-- | src/Stat.hpp | 182 | ||||
-rw-r--r-- | src/Util.cpp | 18 | ||||
-rw-r--r-- | src/Util.hpp | 3 | ||||
-rw-r--r-- | src/ZstdCompressor.hpp | 2 | ||||
-rw-r--r-- | src/ZstdDecompressor.hpp | 2 | ||||
-rw-r--r-- | src/ccache.cpp | 218 | ||||
-rw-r--r-- | src/ccache.hpp | 20 | ||||
-rw-r--r-- | src/cleanup.cpp | 17 | ||||
-rw-r--r-- | src/compress.cpp | 18 | ||||
-rw-r--r-- | src/execute.cpp | 9 | ||||
-rw-r--r-- | src/legacy_util.cpp | 64 | ||||
-rw-r--r-- | src/manifest.cpp | 33 | ||||
-rw-r--r-- | src/result.cpp | 55 | ||||
-rw-r--r-- | src/stats.cpp | 9 | ||||
-rw-r--r-- | src/system.hpp | 17 | ||||
-rw-r--r-- | unittest/test_Stat.cpp | 144 | ||||
-rw-r--r-- | unittest/test_Util.cpp | 22 | ||||
-rw-r--r-- | unittest/test_lockfile.cpp | 3 | ||||
-rw-r--r-- | unittest/util.cpp | 8 |
27 files changed, 596 insertions, 336 deletions
diff --git a/Makefile.in b/Makefile.in index 225396dd..3f37c7d0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -42,6 +42,7 @@ non_third_party_sources = \ src/NullCompressor.cpp \ src/NullDecompressor.cpp \ src/ProgressBar.cpp \ + src/Stat.cpp \ src/Util.cpp \ src/ZstdCompressor.cpp \ src/ZstdDecompressor.cpp \ @@ -82,6 +83,7 @@ test_suites += unittest/test_Checksum.cpp test_suites += unittest/test_Compression.cpp test_suites += unittest/test_Config.cpp test_suites += unittest/test_NullCompression.cpp +test_suites += unittest/test_Stat.cpp test_suites += unittest/test_Util.cpp test_suites += unittest/test_ZstdCompression.cpp test_suites += unittest/test_args.cpp @@ -51,6 +51,7 @@ non_third_party_headers = \ src/NullCompressor.hpp \ src/NullDecompressor.hpp \ src/ProgressBar.hpp \ + src/Stat.hpp \ src/ThreadPool.hpp \ src/Util.hpp \ src/ZstdCompressor.hpp \ diff --git a/src/CacheEntryReader.cpp b/src/CacheEntryReader.cpp index 2670ed13..28090780 100644 --- a/src/CacheEntryReader.cpp +++ b/src/CacheEntryReader.cpp @@ -20,7 +20,6 @@ #include "Compressor.hpp" #include "Error.hpp" -#include "ccache.hpp" #include "third_party/fmt/core.h" diff --git a/src/CacheEntryReader.hpp b/src/CacheEntryReader.hpp index 150ce417..88028161 100644 --- a/src/CacheEntryReader.hpp +++ b/src/CacheEntryReader.hpp @@ -18,6 +18,8 @@ #pragma once +#include "system.hpp" + #include "Checksum.hpp" #include "Decompressor.hpp" #include "Util.hpp" diff --git a/src/CacheFile.cpp b/src/CacheFile.cpp index 8022b2fb..67b26a22 100644 --- a/src/CacheFile.cpp +++ b/src/CacheFile.cpp @@ -20,31 +20,14 @@ #include "Util.hpp" -const struct stat& -CacheFile::stat() const +const Stat& +CacheFile::lstat() const { - if (!m_stated) { -#ifdef _WIN32 - int result = ::stat(m_path.c_str(), &m_stat); -#else - int result = lstat(m_path.c_str(), &m_stat); -#endif - if (result != 0) { - if (errno != ENOENT && errno != ESTALE) { - throw Error( - fmt::format("lstat {} failed: {}", m_path, strerror(errno))); - } - - // The file is missing, so just zero fill the stat structure. This will - // make e.g. S_ISREG(stat().st_mode) return false and stat().st_mtime - // will be, etc. - memset(&m_stat, '\0', sizeof(m_stat)); - } - - m_stated = true; + if (!m_stat) { + m_stat = Stat::lstat(m_path); } - return m_stat; + return *m_stat; } CacheFile::Type diff --git a/src/CacheFile.hpp b/src/CacheFile.hpp index bcf8bf2c..696458fe 100644 --- a/src/CacheFile.hpp +++ b/src/CacheFile.hpp @@ -19,8 +19,10 @@ #pragma once #include "Error.hpp" +#include "Stat.hpp" #include "third_party/fmt/core.h" +#include "third_party/nonstd/optional.hpp" #include <cerrno> #include <cstring> @@ -39,14 +41,13 @@ public: CacheFile(const CacheFile&) = delete; CacheFile& operator=(const CacheFile&) = delete; + const Stat& lstat() const; const std::string& path() const; - const struct stat& stat() const; Type type() const; private: const std::string m_path; - mutable struct stat m_stat; - mutable bool m_stated = false; + mutable nonstd::optional<Stat> m_stat; }; inline CacheFile::CacheFile(const std::string& path) : m_path(path) diff --git a/src/Decompressor.hpp b/src/Decompressor.hpp index dd2a4e99..59558898 100644 --- a/src/Decompressor.hpp +++ b/src/Decompressor.hpp @@ -18,9 +18,10 @@ #pragma once +#include "system.hpp" + #include "Compression.hpp" -#include <cstdio> #include <memory> class Decompressor diff --git a/src/Stat.cpp b/src/Stat.cpp new file mode 100644 index 00000000..ebb47a09 --- /dev/null +++ b/src/Stat.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2019 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 + +#include "Stat.hpp" + +#include "ccache.hpp" + +#include "third_party/fmt/core.h" + +Stat::Stat(StatFunction stat_function, + const std::string& path, + Stat::OnError on_error) +{ + int result = stat_function(path.c_str(), &m_stat); + if (result == 0) { + m_errno = 0; + } else { + m_errno = errno; + if (on_error == OnError::throw_error) { + throw Error(fmt::format("failed to stat {}: {}", path, strerror(errno))); + } + if (on_error == OnError::log) { + cc_log("Failed to stat %s: %s", path.c_str(), strerror(errno)); + } + + // The file is missing, so just zero fill the stat structure. This will + // make e.g. the is_*() methods return false and mtime() will be 0, etc. + memset(&m_stat, '\0', sizeof(m_stat)); + } +} diff --git a/src/Stat.hpp b/src/Stat.hpp new file mode 100644 index 00000000..8f78a3d6 --- /dev/null +++ b/src/Stat.hpp @@ -0,0 +1,182 @@ +// Copyright (C) 2019 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 "system.hpp" + +#include "Error.hpp" + +#include <string> + +class Stat +{ +public: + enum class OnError { + // Ignore any error (including missing file) from the underlying stat call. + // On error, error_number() will return the error number (AKA errno) and + // the query functions will return 0 or false. + ignore, + // Like above but log an error message as well. + log, + // Throw Error on errors (including missing file). + throw_error, + }; + + // Run stat(2). + // + // Arguments: + // - path: Path to stat. + // - on_error: What to do on errors (including missing file). + static Stat stat(const std::string& path, OnError on_error = OnError::ignore); + + // Run lstat(2) if available, otherwise stat(2). + // + // Arguments: + // - path: Path to (l)stat. + // - on_error: What to do on errors (including missing file). + static Stat lstat(const std::string& path, + OnError on_error = OnError::ignore); + + // Return true if the file could be (l)stat-ed (i.e., the file exists), + // otherwise false. + operator bool() const; + + // Return errno from the (l)stat call (0 if successful). + int error_number() const; + + dev_t device() const; + ino_t inode() const; + mode_t mode() const; + time_t ctime() const; + time_t mtime() const; + uint64_t size() const; + + uint64_t size_on_disk() const; + + bool is_directory() const; + bool is_regular() const; + bool is_symlink() const; + +protected: + using StatFunction = int (*)(const char*, struct stat*); + + Stat(StatFunction stat_function, const std::string& path, OnError on_error); + +private: + struct stat m_stat; + int m_errno; +}; + +inline Stat +Stat::stat(const std::string& path, OnError on_error) +{ + return Stat(::stat, path, on_error); +} + +inline Stat +Stat::lstat(const std::string& path, OnError on_error) +{ + return Stat( +#ifdef _WIN32 + ::stat, +#else + ::lstat, +#endif + path, + on_error); +} + +inline Stat::operator bool() const +{ + return m_errno == 0; +} + +inline int +Stat::error_number() const +{ + return m_errno; +} + +inline dev_t +Stat::device() const +{ + return m_stat.st_dev; +} + +inline ino_t +Stat::inode() const +{ + return m_stat.st_ino; +} + +inline mode_t +Stat::mode() const +{ + return m_stat.st_mode; +} + +inline time_t +Stat::ctime() const +{ + return m_stat.st_ctime; +} + +inline time_t +Stat::mtime() const +{ + return m_stat.st_mtime; +} + +inline uint64_t +Stat::size() const +{ + return m_stat.st_size; +} + +inline uint64_t +Stat::size_on_disk() const +{ +#ifdef _WIN32 + return (size() + 1023) & ~1023; +#else + return m_stat.st_blocks * 512; +#endif +} + +inline bool +Stat::is_directory() const +{ + return S_ISDIR(mode()); +} + +inline bool +Stat::is_symlink() const +{ +#ifndef _WIN32 + return S_ISLNK(mode()); +#else + return false; +#endif +} + +inline bool +Stat::is_regular() const +{ + return S_ISREG(mode()); +} diff --git a/src/Util.cpp b/src/Util.cpp index 801291e0..591d9462 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -89,9 +89,9 @@ bool create_dir(nonstd::string_view dir) { std::string dir_str(dir); - struct stat st; - if (stat(dir_str.c_str(), &st) == 0) { - if (S_ISDIR(st.st_mode)) { + auto st = Stat::stat(dir_str); + if (st) { + if (st.is_directory()) { return true; } else { errno = ENOTDIR; @@ -161,18 +161,6 @@ for_each_level_1_subdir(const std::string& cache_dir, progress_receiver(1.0); } -bool -get_file_size(const std::string& path, uint64_t& size) -{ - struct stat st; - if (stat(path.c_str(), &st) == 0) { - size = st.st_size; - return true; - } else { - return false; - } -} - void get_level_1_files(const std::string& dir, const ProgressReceiver& progress_receiver, diff --git a/src/Util.hpp b/src/Util.hpp index 53a38801..1f3f266e 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -103,9 +103,6 @@ void for_each_level_1_subdir(const std::string& cache_dir, const SubdirVisitor& visitor, const ProgressReceiver& progress_receiver); -// Get file size. Returns true if file exists, otherwise false. -bool get_file_size(const std::string& path, uint64_t& size); - // Get a list of files in a level 1 subdirectory of the cache. // // The function works under the assumption that directory entries with one diff --git a/src/ZstdCompressor.hpp b/src/ZstdCompressor.hpp index 641c2b76..3c120323 100644 --- a/src/ZstdCompressor.hpp +++ b/src/ZstdCompressor.hpp @@ -18,6 +18,8 @@ #pragma once +#include "system.hpp" + #include "Compressor.hpp" #include "NonCopyable.hpp" diff --git a/src/ZstdDecompressor.hpp b/src/ZstdDecompressor.hpp index 45616280..b32f006f 100644 --- a/src/ZstdDecompressor.hpp +++ b/src/ZstdDecompressor.hpp @@ -18,6 +18,8 @@ #pragma once +#include "system.hpp" + #include "Decompressor.hpp" #include "ccache.hpp" diff --git a/src/ccache.cpp b/src/ccache.cpp index fdacea09..a4549719 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -471,9 +471,8 @@ static void clean_up_internal_tempdir(void) { time_t now = time(NULL); - struct stat st; - if (x_stat(g_config.cache_dir().c_str(), &st) != 0 - || st.st_mtime + 3600 >= now) { + auto st = Stat::stat(g_config.cache_dir(), Stat::OnError::log); + if (!st || st.mtime() + 3600 >= now) { // No cleanup needed. return; } @@ -492,7 +491,8 @@ clean_up_internal_tempdir(void) } char* path = format("%s/%s", temp_dir(), entry->d_name); - if (x_lstat(path, &st) == 0 && st.st_mtime + 3600 < now) { + st = Stat::lstat(path, Stat::OnError::log); + if (st && st.mtime() + 3600 < now) { tmp_unlink(path); } free(path); @@ -617,15 +617,15 @@ do_remember_include_file(std::string path, } #endif - struct stat st; - if (x_stat(path.c_str(), &st) != 0) { + auto st = Stat::stat(path, Stat::OnError::log); + if (!st) { return false; } - if (S_ISDIR(st.st_mode)) { + if (st.is_directory()) { // Ignore directory, typically $PWD. return true; } - if (!S_ISREG(st.st_mode)) { + if (!st.is_regular()) { // Device, pipe, socket or other strange creature. cc_log("Non-regular include file %s", path.c_str()); return false; @@ -659,14 +659,14 @@ do_remember_include_file(std::string path, // starting compilation and writing the include file. See also the notes // under "Performance" in doc/MANUAL.adoc. if (!(g_config.sloppiness() & SLOPPY_INCLUDE_FILE_MTIME) - && st.st_mtime >= time_of_compilation) { + && st.mtime() >= time_of_compilation) { cc_log("Include file %s too new", path.c_str()); return false; } // The same >= logic as above applies to the change time of the file. if (!(g_config.sloppiness() & SLOPPY_INCLUDE_FILE_CTIME) - && st.st_ctime >= time_of_compilation) { + && st.ctime() >= time_of_compilation) { cc_log("Include file %s ctime too new", path.c_str()); return false; } @@ -686,7 +686,7 @@ do_remember_include_file(std::string path, // hash pch.sum instead of pch when it exists // to prevent hashing a very large .pch file every time std::string pch_sum_path = fmt::format("{}.sum", path); - if (x_stat(pch_sum_path.c_str(), &st) == 0) { + if (Stat::stat(pch_sum_path, Stat::OnError::log)) { path = std::move(pch_sum_path); using_pch_sum = true; cc_log("Using pch.sum file %s", path.c_str()); @@ -706,8 +706,8 @@ do_remember_include_file(std::string path, if (!is_pch) { // else: the file has already been hashed. char* source = NULL; size_t size; - if (st.st_size > 0) { - if (!read_file(path.c_str(), st.st_size, &source, &size)) { + if (st.size() > 0) { + if (!read_file(path.c_str(), st.size(), &source, &size)) { return false; } } else { @@ -792,12 +792,11 @@ make_relative_path(char* path) // canonicalizing one of these two paths since a compiler path argument // typically only makes sense if path or x_dirname(path) exists. char* path_suffix = NULL; - struct stat st; - if (stat(path, &st) != 0) { + if (!Stat::stat(path)) { // path doesn't exist. char* dir = x_dirname(path); // find the nearest existing directory in path - while (stat(dir, &st) != 0) { + while (!Stat::stat(dir)) { char* parent_dir = x_dirname(dir); free(dir); dir = parent_dir; @@ -1195,20 +1194,17 @@ update_manifest_file(void) return; } - struct stat st; - uint64_t old_size = 0; // in bytes - if (stat(manifest_path, &st) == 0) { - old_size = file_size_on_disk(&st); - } + auto old_st = Stat::stat(manifest_path); MTR_BEGIN("manifest", "manifest_put"); cc_log("Adding result name to %s", manifest_path); if (!manifest_put(manifest_path, *cached_result_name, g_included_files)) { cc_log("Failed to add result name to %s", manifest_path); - } else if (x_stat(manifest_path, &st) == 0) { + } else { + auto st = Stat::stat(manifest_path, Stat::OnError::log); stats_update_size(manifest_stats_file, - file_size_on_disk(&st) - old_size, - old_size == 0 ? 1 : 0); + st.size_on_disk() - old_st.size_on_disk(), + !old_st && st ? 1 : 0); } MTR_END("manifest", "manifest_put"); } @@ -1234,10 +1230,10 @@ create_cachedir_tag(const std::string& dir) "#\thttp://www.brynosaurus.com/cachedir/\n"; std::string filename = fmt::format("{}/CACHEDIR.TAG", dir); - struct stat st; + auto st = Stat::stat(filename); - if (stat(filename.c_str(), &st) == 0) { - if (S_ISREG(st.st_mode)) { + if (st) { + if (st.is_regular()) { return true; } errno = EEXIST; @@ -1334,8 +1330,8 @@ to_cache(struct args* args, struct hash* depend_mode_hash) } MTR_END("execute", "compiler"); - struct stat st; - if (x_stat(tmp_stdout, &st) != 0) { + auto st = Stat::stat(tmp_stdout, Stat::OnError::log); + if (!st) { // The stdout file was removed - cleanup in progress? Better bail out. stats_update(STATS_MISSING); tmp_unlink(tmp_stdout); @@ -1345,7 +1341,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash) // distcc-pump outputs lines like this: // __________Using # distcc servers in pump mode - if (st.st_size != 0 && guessed_compiler != GUESSED_PUMP) { + if (st.size() != 0 && guessed_compiler != GUESSED_PUMP) { cc_log("Compiler produced stdout"); stats_update(STATS_STDOUT); tmp_unlink(tmp_stdout); @@ -1428,23 +1424,25 @@ to_cache(struct args* args, struct hash* depend_mode_hash) use_relative_paths_in_depfile(output_dep); } - if (stat(output_obj, &st) != 0) { + st = Stat::stat(output_obj); + if (!st) { cc_log("Compiler didn't produce an object file"); stats_update(STATS_NOOUTPUT); failed(); } - if (st.st_size == 0) { + if (st.size() == 0) { cc_log("Compiler produced an empty object file"); stats_update(STATS_EMPTYOUTPUT); failed(); } - if (x_stat(tmp_stderr, &st) != 0) { + st = Stat::stat(tmp_stderr, Stat::OnError::log); + if (!st) { stats_update(STATS_ERROR); failed(); } ResultFileMap result_file_map; - if (st.st_size > 0) { + if (st.size() > 0) { result_file_map.emplace(FileType::stderr_output, tmp_stderr); } result_file_map.emplace(FileType::object, output_obj); @@ -1460,26 +1458,26 @@ to_cache(struct args* args, struct hash* depend_mode_hash) if (generating_diagnostics) { result_file_map.emplace(FileType::diagnostic, output_dia); } - if (seen_split_dwarf && stat(output_dwo, &st) == 0) { + if (seen_split_dwarf && Stat::stat(output_dwo)) { // Only copy .dwo file if it was created by the compiler (GCC and Clang // behave differently e.g. for "-gsplit-dwarf -g1"). result_file_map.emplace(FileType::dwarf_object, output_dwo); } - struct stat orig_dest_st; - bool orig_dest_existed = stat(cached_result_path, &orig_dest_st) == 0; + + auto orig_dest_stat = Stat::stat(cached_result_path); result_put(cached_result_path, result_file_map); cc_log("Stored in cache: %s", cached_result_path); - if (x_stat(cached_result_path, &st) != 0) { + auto new_dest_stat = Stat::stat(cached_result_path, Stat::OnError::log); + if (!new_dest_stat) { stats_update(STATS_ERROR); failed(); } - stats_update_size( - stats_file, - file_size_on_disk(&st) - - (orig_dest_existed ? file_size_on_disk(&orig_dest_st) : 0), - orig_dest_existed ? 0 : 1); + stats_update_size(stats_file, + new_dest_stat.size_on_disk() + - orig_dest_stat.size_on_disk(), + orig_dest_stat ? 0 : 1); MTR_END("file", "file_put"); @@ -1632,7 +1630,7 @@ get_result_name_from_cpp(struct args* args, struct hash* hash) // the CCACHE_COMPILERCHECK setting. static void hash_compiler(struct hash* hash, - struct stat* st, + const Stat& st, const char* path, bool allow_command) { @@ -1640,8 +1638,8 @@ hash_compiler(struct hash* hash, // Do nothing. } else if (g_config.compiler_check() == "mtime") { hash_delimiter(hash, "cc_mtime"); - hash_int(hash, st->st_size); - hash_int(hash, st->st_mtime); + hash_int(hash, st.size()); + hash_int(hash, st.mtime()); } else if (Util::starts_with(g_config.compiler_check(), "string:")) { hash_delimiter(hash, "cc_hash"); hash_string(hash, g_config.compiler_check().c_str() + strlen("string:")); @@ -1665,7 +1663,7 @@ hash_compiler(struct hash* hash, // in PATH instead. static void hash_nvcc_host_compiler(struct hash* hash, - struct stat* ccbin_st, + const Stat* ccbin_st, const char* ccbin) { // From <http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html>: @@ -1680,7 +1678,7 @@ hash_nvcc_host_compiler(struct hash* hash, // Linux, clang and clang++ on Mac OS X, and cl.exe on Windows) found in // the current execution search path will be used". - if (!ccbin || S_ISDIR(ccbin_st->st_mode)) { + if (!ccbin || ccbin_st->is_directory()) { #if defined(__APPLE__) const char* compilers[] = {"clang", "clang++"}; #elif defined(_WIN32) @@ -1691,23 +1689,22 @@ hash_nvcc_host_compiler(struct hash* hash, for (size_t i = 0; i < ARRAY_SIZE(compilers); i++) { if (ccbin) { char* path = format("%s/%s", ccbin, compilers[i]); - struct stat st; - if (stat(path, &st) == 0) { - hash_compiler(hash, &st, path, false); + auto st = Stat::stat(path); + if (st) { + hash_compiler(hash, st, path, false); } free(path); } else { char* path = find_executable(compilers[i], MYNAME); if (path) { - struct stat st; - x_stat(path, &st); - hash_compiler(hash, &st, ccbin, false); + auto st = Stat::stat(path, Stat::OnError::log); + hash_compiler(hash, st, ccbin, false); free(path); } } } } else { - hash_compiler(hash, ccbin_st, ccbin, false); + hash_compiler(hash, *ccbin_st, ccbin, false); } } @@ -1732,14 +1729,14 @@ hash_common_info(struct args* args, struct hash* hash) const char* full_path = args->argv[0]; #endif - struct stat st; - if (x_stat(full_path, &st) != 0) { + auto st = Stat::stat(full_path, Stat::OnError::log); + if (!st) { stats_update(STATS_COMPILER); failed(); } // Hash information about the compiler. - hash_compiler(hash, &st, args->argv[0], true); + hash_compiler(hash, st, args->argv[0], true); // Also hash the compiler name as some compilers use hard links and behave // differently depending on the real name. @@ -1969,40 +1966,49 @@ calculate_result_name(struct args* args, struct hash* hash, int direct_mode) p = args->argv[i] + 8; } - struct stat st; - if (p && x_stat(p, &st) == 0) { - // If given an explicit specs file, then hash that file, but don't - // include the path to it in the hash. - hash_delimiter(hash, "specs"); - hash_compiler(hash, &st, p, false); - continue; + if (p) { + auto st = Stat::stat(p, Stat::OnError::log); + if (st) { + // If given an explicit specs file, then hash that file, but don't + // include the path to it in the hash. + hash_delimiter(hash, "specs"); + hash_compiler(hash, st, p, false); + continue; + } } - if (str_startswith(args->argv[i], "-fplugin=") - && x_stat(args->argv[i] + 9, &st) == 0) { - hash_delimiter(hash, "plugin"); - hash_compiler(hash, &st, args->argv[i] + 9, false); - continue; + if (str_startswith(args->argv[i], "-fplugin=")) { + auto st = Stat::stat(args->argv[i] + 9, Stat::OnError::log); + if (st) { + hash_delimiter(hash, "plugin"); + hash_compiler(hash, st, args->argv[i] + 9, false); + continue; + } } if (str_eq(args->argv[i], "-Xclang") && i + 3 < args->argc && str_eq(args->argv[i + 1], "-load") - && str_eq(args->argv[i + 2], "-Xclang") - && x_stat(args->argv[i + 3], &st) == 0) { - hash_delimiter(hash, "plugin"); - hash_compiler(hash, &st, args->argv[i + 3], false); - i += 3; - continue; + && str_eq(args->argv[i + 2], "-Xclang")) { + auto st = Stat::stat(args->argv[i + 3], Stat::OnError::log); + if (st) { + hash_delimiter(hash, "plugin"); + hash_compiler(hash, st, args->argv[i + 3], false); + i += 3; + continue; + } } if ((str_eq(args->argv[i], "-ccbin") || str_eq(args->argv[i], "--compiler-bindir")) - && i + 1 < args->argc && x_stat(args->argv[i + 1], &st) == 0) { - found_ccbin = true; - hash_delimiter(hash, "ccbin"); - hash_nvcc_host_compiler(hash, &st, args->argv[i + 1]); - i++; - continue; + && i + 1 < args->argc) { + auto st = Stat::stat(args->argv[i + 1], Stat::OnError::log); + if (st) { + found_ccbin = true; + hash_delimiter(hash, "ccbin"); + hash_nvcc_host_compiler(hash, &st, args->argv[i + 1]); + i++; + continue; + } } // All other arguments are included in the hash. @@ -2297,29 +2303,27 @@ color_output_possible(void) static bool detect_pch(const char* option, const char* arg, bool* found_pch) { - struct stat st; - // Try to be smart about detecting precompiled headers. char* pch_file = NULL; if (str_eq(option, "-include-pch") || str_eq(option, "-include-pth")) { - if (stat(arg, &st) == 0) { + if (Stat::stat(arg)) { cc_log("Detected use of precompiled header: %s", arg); pch_file = x_strdup(arg); } } else { char* gchpath = format("%s.gch", arg); - if (stat(gchpath, &st) == 0) { + if (Stat::stat(gchpath)) { cc_log("Detected use of precompiled header: %s", gchpath); pch_file = x_strdup(gchpath); } else { char* pchpath = format("%s.pch", arg); - if (stat(pchpath, &st) == 0) { + if (Stat::stat(pchpath)) { cc_log("Detected use of precompiled header: %s", pchpath); pch_file = x_strdup(pchpath); } else { // clang may use pretokenized headers. char* pthpath = format("%s.pth", arg); - if (stat(pthpath, &st) == 0) { + if (Stat::stat(pthpath)) { cc_log("Detected use of pretokenized header: %s", pthpath); pch_file = x_strdup(pthpath); } @@ -3066,13 +3070,14 @@ cc_process_args(struct args* args, // // Note that "/dev/null" is an exception that is sometimes used as an input // file when code is testing compiler flags. - struct stat st; - if (!str_eq(argv[i], "/dev/null") - && (stat(argv[i], &st) != 0 || !S_ISREG(st.st_mode))) { - cc_log("%s is not a regular file, not considering as input file", - argv[i]); - args_add(common_args, argv[i]); - continue; + if (!str_eq(argv[i], "/dev/null")) { + auto st = Stat::stat(argv[i]); + if (!st || !st.is_regular()) { + cc_log("%s is not a regular file, not considering as input file", + argv[i]); + args_add(common_args, argv[i]); + continue; + } } if (input_file) { @@ -3100,7 +3105,7 @@ cc_process_args(struct args* args, continue; } - if (is_symlink(argv[i])) { + if (Stat::lstat(argv[i], Stat::OnError::log).is_symlink()) { // Don't rewrite source file path if it's a symlink since // make_relative_path resolves symlinks using realpath(3) and this leads // to potentially choosing incorrect relative header files. See the @@ -3305,18 +3310,20 @@ cc_process_args(struct args* args, } // Cope with -o /dev/null. - struct stat st; - if (!str_eq(output_obj, "/dev/null") && stat(output_obj, &st) == 0 - && !S_ISREG(st.st_mode)) { - cc_log("Not a regular file: %s", output_obj); - stats_update(STATS_BADOUTPUTFILE); - result = false; - goto out; + if (!str_eq(output_obj, "/dev/null")) { + auto st = Stat::stat(output_obj); + if (st && !st.is_regular()) { + cc_log("Not a regular file: %s", output_obj); + stats_update(STATS_BADOUTPUTFILE); + result = false; + goto out; + } } { char* output_dir = x_dirname(output_obj); - if (stat(output_dir, &st) != 0 || !S_ISDIR(st.st_mode)) { + auto st = Stat::stat(output_dir); + if (!st || !st.is_directory()) { cc_log("Directory does not exist: %s", output_dir); stats_update(STATS_BADOUTPUTFILE); result = false; @@ -3463,8 +3470,7 @@ create_initial_config_file(const char* path) unsigned max_files; uint64_t max_size; char* stats_dir = format("%s/0", g_config.cache_dir().c_str()); - struct stat st; - if (stat(stats_dir, &st) == 0) { + if (Stat::stat(stats_dir)) { stats_get_obsolete_limits(stats_dir, &max_files, &max_size); // STATS_MAXFILES and STATS_MAXSIZE was stored for each top directory. max_files *= 16; diff --git a/src/ccache.hpp b/src/ccache.hpp index 608663dd..302296b7 100644 --- a/src/ccache.hpp +++ b/src/ccache.hpp @@ -174,13 +174,10 @@ void* x_malloc(size_t size); void* x_realloc(void* ptr, size_t size); void x_setenv(const char* name, const char* value); void x_unsetenv(const char* name); -int x_lstat(const char* pathname, struct stat* buf); -int x_stat(const char* pathname, struct stat* buf); char* x_basename(const char* path); char* x_dirname(const char* path); const char* get_extension(const char* path); char* remove_extension(const char* path); -uint64_t file_size_on_disk(const struct stat* st); char* format_human_readable_size(uint64_t size); char* format_parsable_size_with_suffix(uint64_t size); bool parse_size_with_suffix(const char* str, uint64_t* size); @@ -201,7 +198,6 @@ size_t common_dir_prefix_length(const char* s1, const char* s2); char* get_relative_path(const char* from, const char* to); bool is_absolute_path(const char* path); bool is_full_path(const char* path); -bool is_symlink(const char* path); void update_mtime(const char* path); void x_exit(int status) ATTR_NORETURN; int x_rename(const char* oldpath, const char* newpath); @@ -295,20 +291,6 @@ void add_exe_ext_if_no_to_fullpath(char* full_path_win_ext, size_t max_size, const char* ext, const char* path); -# ifndef _WIN32_WINNT -# define _WIN32_WINNT 0x0501 -# endif -# include <windows.h> -# define mkdir(a, b) mkdir(a) -# define link(src, dst) (CreateHardLink(dst, src, NULL) ? 0 : -1) -# define lstat(a, b) stat(a, b) -# define execv(a, b) win32execute(a, b, 0, -1, -1) + # define execute(a, b, c, d) win32execute(*(a), a, 1, b, c) -# define DIR_DELIM_CH '\\' -# define PATH_DELIM ";" -# define F_RDLCK 0 -# define F_WRLCK 0 -#else -# define DIR_DELIM_CH '/' -# define PATH_DELIM ":" #endif diff --git a/src/cleanup.cpp b/src/cleanup.cpp index 7f04e73a..b00ca2f9 100644 --- a/src/cleanup.cpp +++ b/src/cleanup.cpp @@ -66,19 +66,19 @@ clean_up_dir(const std::string& subdir, ++i, progress_receiver(1.0 / 3 + 1.0 * i / files.size() / 3)) { const auto& file = files[i]; - if (!S_ISREG(file->stat().st_mode)) { + if (!file->lstat().is_regular()) { // Not a file or missing file. continue; } // Delete any tmp files older than 1 hour right away. - if (file->stat().st_mtime + 3600 < current_time + if (file->lstat().mtime() + 3600 < current_time && Util::base_name(file->path()).find(".tmp.") != std::string::npos) { x_unlink(file->path().c_str()); continue; } - cache_size += file_size_on_disk(&file->stat()); + cache_size += file->lstat().size_on_disk(); files_in_cache += 1; } @@ -87,7 +87,7 @@ clean_up_dir(const std::string& subdir, files.end(), [](const std::shared_ptr<CacheFile>& f1, const std::shared_ptr<CacheFile>& f2) { - return f1->stat().st_mtime < f2->stat().st_mtime; + return f1->lstat().mtime() < f2->lstat().mtime(); }); cc_log("Before cleanup: %.0f KiB, %.0f files", @@ -99,8 +99,7 @@ clean_up_dir(const std::string& subdir, ++i, progress_receiver(2.0 / 3 + 1.0 * i / files.size() / 3)) { const auto& file = files[i]; - if (!S_ISREG(file->stat().st_mode)) { - // Not a file or missing file. + if (!file->lstat() || file->lstat().is_directory()) { continue; } @@ -128,10 +127,8 @@ clean_up_dir(const std::string& subdir, delete_file(o_file, 0, nullptr, nullptr); } - delete_file(file->path(), - file_size_on_disk(&file->stat()), - &cache_size, - &files_in_cache); + delete_file( + file->path(), file->lstat().size_on_disk(), &cache_size, &files_in_cache); cleaned = true; } diff --git a/src/compress.cpp b/src/compress.cpp index 240d7ed9..dd493e9f 100644 --- a/src/compress.cpp +++ b/src/compress.cpp @@ -24,7 +24,6 @@ #include "File.hpp" #include "StdMakeUnique.hpp" #include "ThreadPool.hpp" -#include "ccache.hpp" #include "manifest.hpp" #include "result.hpp" @@ -116,14 +115,11 @@ recompress_file(const std::string& stats_file, reader->finalize(); writer->finalize(); - struct stat st; - x_stat(cache_file.path().c_str(), &st); - uint64_t old_size = file_size_on_disk(&st); - + uint64_t old_size = + Stat::stat(cache_file.path(), Stat::OnError::log).size_on_disk(); atomic_new_file.commit(); - - x_stat(cache_file.path().c_str(), &st); - uint64_t new_size = file_size_on_disk(&st); + uint64_t new_size = + Stat::stat(cache_file.path(), Stat::OnError::log).size_on_disk(); stats_update_size(stats_file.c_str(), new_size - old_size, 0); } @@ -149,15 +145,15 @@ compress_stats(const Config& config, for (size_t i = 0; i < files.size(); ++i) { const auto& cache_file = files[i]; - on_disk_size += file_size_on_disk(&cache_file->stat()); + on_disk_size += cache_file->lstat().size_on_disk(); try { auto file = open_file(cache_file->path(), "rb"); auto reader = create_reader(*cache_file, file.get()); - compr_size += cache_file->stat().st_size; + compr_size += cache_file->lstat().size(); compr_orig_size += reader->content_size(); } catch (Error&) { - incompr_size += cache_file->stat().st_size; + incompr_size += cache_file->lstat().size(); } sub_progress_receiver(1.0 / 2 + 1.0 * i / files.size() / 2); diff --git a/src/execute.cpp b/src/execute.cpp index f69e6817..88b9b918 100644 --- a/src/execute.cpp +++ b/src/execute.cpp @@ -18,6 +18,7 @@ // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "Config.hpp" +#include "Stat.hpp" #include "ccache.hpp" static char* find_executable_in_path(const char* name, @@ -336,12 +337,12 @@ find_executable_in_path(const char* name, return x_strdup(namebuf); } #else - struct stat st1, st2; char* fname = format("%s/%s", tok, name); + auto st1 = Stat::lstat(fname); + auto st2 = Stat::stat(fname); // Look for a normal executable file. - if (access(fname, X_OK) == 0 && lstat(fname, &st1) == 0 - && stat(fname, &st2) == 0 && S_ISREG(st2.st_mode)) { - if (S_ISLNK(st1.st_mode)) { + if (st1 && st2 && st2.is_regular() && access(fname, X_OK) == 0) { + if (st1.is_symlink()) { char* buf = x_realpath(fname); if (buf) { char* p = x_basename(buf); diff --git a/src/legacy_util.cpp b/src/legacy_util.cpp index 79cee6aa..b818d5b4 100644 --- a/src/legacy_util.cpp +++ b/src/legacy_util.cpp @@ -710,28 +710,6 @@ x_unsetenv(const char* name) #endif } -// Like lstat() but also call cc_log on failure. -int -x_lstat(const char* pathname, struct stat* buf) -{ - int result = lstat(pathname, buf); - if (result != 0) { - cc_log("Failed to lstat %s: %s", pathname, strerror(errno)); - } - return result; -} - -// Like stat() but also call cc_log on failure. -int -x_stat(const char* pathname, struct stat* buf) -{ - int result = stat(pathname, buf); - if (result != 0) { - cc_log("Failed to stat %s: %s", pathname, strerror(errno)); - } - return result; -} - // Construct a string according to the format and store it in *ptr. The // original *ptr is then freed. void @@ -819,17 +797,6 @@ remove_extension(const char* path) return x_strndup(path, strlen(path) - strlen(get_extension(path))); } -// Return size on disk of a file. -uint64_t -file_size_on_disk(const struct stat* st) -{ -#ifdef _WIN32 - return (st->st_size + 1023) & ~1023; -#else - return st->st_blocks * 512; -#endif -} - // Format a size as a human-readable string. Caller frees. char* format_human_readable_size(uint64_t v) @@ -1169,24 +1136,22 @@ get_home_directory(void) char* get_cwd(void) { - struct stat st_pwd; - struct stat st_cwd; - char* cwd = gnu_getcwd(); if (!cwd) { return NULL; } + char* pwd = getenv("PWD"); if (!pwd) { return cwd; } - if (stat(pwd, &st_pwd) != 0) { - return cwd; - } - if (stat(cwd, &st_cwd) != 0) { + + auto st_pwd = Stat::stat(pwd); + auto st_cwd = Stat::stat(cwd); + if (!st_pwd || !st_cwd) { return cwd; } - if (st_pwd.st_dev == st_cwd.st_dev && st_pwd.st_ino == st_cwd.st_ino) { + if (st_pwd.device() == st_cwd.device() && st_pwd.inode() == st_cwd.inode()) { free(cwd); return x_strdup(pwd); } else { @@ -1313,18 +1278,6 @@ is_full_path(const char* path) return false; } -bool -is_symlink(const char* path) -{ -#ifdef _WIN32 - (void)path; - return false; -#else - struct stat st; - return x_lstat(path, &st) == 0 && ((st.st_mode & S_IFMT) == S_IFLNK); -#endif -} - // Update the modification time of a file in the cache to save it from LRU // cleanup. void @@ -1484,10 +1437,7 @@ bool read_file(const char* path, size_t size_hint, char** data, size_t* size) { if (size_hint == 0) { - struct stat st; - if (x_stat(path, &st) == 0) { - size_hint = st.st_size; - } + size_hint = Stat::stat(path, Stat::OnError::log).size(); } size_hint = (size_hint < 1024) ? 1024 : size_hint; diff --git a/src/manifest.cpp b/src/manifest.cpp index 07658ccb..1c88cb73 100644 --- a/src/manifest.cpp +++ b/src/manifest.cpp @@ -25,7 +25,6 @@ #include "Config.hpp" #include "File.hpp" #include "StdMakeUnique.hpp" -#include "ccache.hpp" #include "hash.hpp" #include "hashutil.hpp" @@ -215,24 +214,24 @@ private: fi.digest = digest; - // file_stat.st_{m,c}time have a resolution of 1 second, so we can cache - // the file's mtime and ctime only if they're at least one second older - // than time_of_compilation. + // file_stat.{m,c}time() have a resolution of 1 second, so we can cache the + // file's mtime and ctime only if they're at least one second older than + // time_of_compilation. // - // st->ctime may be 0, so we have to check time_of_compilation against - // MAX(mtime, ctime). + // file_stat.ctime() may be 0, so we have to check time_of_compilation + // against MAX(mtime, ctime). - struct stat file_stat; - if (stat(path.c_str(), &file_stat) != -1) { + auto file_stat = Stat::stat(path, Stat::OnError::log); + if (file_stat) { if (time_of_compilation - > std::max(file_stat.st_mtime, file_stat.st_ctime)) { - fi.mtime = file_stat.st_mtime; - fi.ctime = file_stat.st_ctime; + > std::max(file_stat.mtime(), file_stat.ctime())) { + fi.mtime = file_stat.mtime(); + fi.ctime = file_stat.ctime(); } else { fi.mtime = -1; fi.ctime = -1; } - fi.fsize = file_stat.st_size; + fi.fsize = file_stat.size(); } else { fi.mtime = -1; fi.ctime = -1; @@ -381,14 +380,14 @@ verify_result(const Config& config, auto stated_files_iter = stated_files.find(path); if (stated_files_iter == stated_files.end()) { - struct stat file_stat; - if (x_stat(path.c_str(), &file_stat) != 0) { + auto file_stat = Stat::stat(path, Stat::OnError::log); + if (!file_stat) { return false; } FileStats st; - st.size = file_stat.st_size; - st.mtime = file_stat.st_mtime; - st.ctime = file_stat.st_ctime; + st.size = file_stat.size(); + st.mtime = file_stat.mtime(); + st.ctime = file_stat.ctime(); stated_files_iter = stated_files.emplace(path, st).first; } const FileStats& fs = stated_files_iter->second; diff --git a/src/result.cpp b/src/result.cpp index 018a75b0..f74537ac 100644 --- a/src/result.cpp +++ b/src/result.cpp @@ -24,8 +24,8 @@ #include "Config.hpp" #include "Error.hpp" #include "File.hpp" +#include "Stat.hpp" #include "Util.hpp" -#include "ccache.hpp" // Result data format // ================== @@ -258,16 +258,12 @@ read_raw_file_entry(CacheEntryReader& reader, (unsigned long long)file_len); auto raw_path = get_raw_file_path(result_path_in_cache, entry_number); - struct stat st; - if (x_stat(raw_path.c_str(), &st) != 0) { - throw Error( - fmt::format("Failed to stat {}: {}", raw_path, strerror(errno))); - } - if ((uint64_t)st.st_size != file_len) { + auto st = Stat::stat(raw_path, Stat::OnError::throw_error); + if (st.size() != file_len) { throw Error( fmt::format("Bad file size of {} (actual {} bytes, expected {} bytes)", raw_path, - st.st_size, + st.size(), file_len)); } @@ -347,11 +343,8 @@ write_embedded_file_entry(CacheEntryWriter& writer, auto type = UnderlyingFileTypeInt(suffix_and_path.first); const auto& source_path = suffix_and_path.second; - uint64_t source_file_size; - if (!Util::get_file_size(source_path, source_file_size)) { - throw Error( - fmt::format("Failed to stat {}: {}", source_path, strerror(errno))); - } + uint64_t source_file_size = + Stat::stat(source_path, Stat::OnError::throw_error).size(); cc_log("Storing embedded file #%u %s (%llu bytes) from %s", entry_number, @@ -389,14 +382,8 @@ write_raw_file_entry(CacheEntryWriter& writer, auto type = UnderlyingFileTypeInt(suffix_and_path.first); const auto& source_path = suffix_and_path.second; - uint64_t source_file_size; - if (!Util::get_file_size(source_path, source_file_size)) { - throw Error( - fmt::format("Failed to stat {}: {}", source_path, strerror(errno))); - } - - uint64_t old_size; - uint64_t new_size; + uint64_t source_file_size = + Stat::stat(source_path, Stat::OnError::throw_error).size(); cc_log("Storing raw file #%u %s (%llu bytes) from %s", entry_number, @@ -409,20 +396,16 @@ write_raw_file_entry(CacheEntryWriter& writer, writer.write(source_file_size); auto raw_file = get_raw_file_path(result_path_in_cache, entry_number); - struct stat old_stat; - bool old_existed = stat(raw_file.c_str(), &old_stat) == 0; + auto old_stat = Stat::stat(raw_file); if (!copy_raw_file(source_path, raw_file, true)) { throw Error( fmt::format("Failed to store {} as raw file {}", source_path, raw_file)); } - struct stat new_stat; - bool new_exists = stat(raw_file.c_str(), &new_stat) == 0; + auto new_stat = Stat::stat(raw_file); - old_size = old_existed ? file_size_on_disk(&old_stat) : 0; - new_size = new_exists ? file_size_on_disk(&new_stat) : 0; stats_update_size(stats_file, - new_size - old_size, - (new_exists ? 1 : 0) - (old_existed ? 1 : 0)); + new_stat.size_on_disk() - old_stat.size_on_disk(), + (new_stat ? 1 : 0) - (old_stat ? 1 : 0)); } static bool @@ -456,15 +439,11 @@ write_result(const std::string& path, const ResultFileMap& result_file_map) payload_size += 1; // n_entries for (const auto& pair : result_file_map) { const auto& result_file = pair.second; - uint64_t source_file_size; - if (!Util::get_file_size(result_file, source_file_size)) { - throw Error( - fmt::format("Failed to stat {}: {}", result_file, strerror(errno))); - } - payload_size += 1; // embedded_file_marker - payload_size += 1; // embedded_file_type - payload_size += 8; // data_len - payload_size += source_file_size; // data + auto st = Stat::stat(result_file, Stat::OnError::throw_error); + payload_size += 1; // embedded_file_marker + payload_size += 1; // embedded_file_type + payload_size += 8; // data_len + payload_size += st.size(); // data } AtomicFile atomic_result_file(path, AtomicFile::Mode::binary); diff --git a/src/stats.cpp b/src/stats.cpp index 3c317deb..81544d14 100644 --- a/src/stats.cpp +++ b/src/stats.cpp @@ -257,7 +257,6 @@ stats_hit_rate(struct counters* counters) static void stats_collect(struct counters* counters, time_t* last_updated) { - struct stat st; unsigned zero_timestamp = 0; *last_updated = 0; @@ -276,8 +275,9 @@ stats_collect(struct counters* counters, time_t* last_updated) stats_read(fname, counters); zero_timestamp = std::max(counters->data[STATS_ZEROTIMESTAMP], zero_timestamp); - if (stat(fname, &st) == 0 && st.st_mtime > *last_updated) { - *last_updated = st.st_mtime; + auto st = Stat::stat(fname); + if (st && st.mtime() > *last_updated) { + *last_updated = st.mtime(); } free(fname); } @@ -525,9 +525,8 @@ stats_zero(void) for (int dir = 0; dir <= 0xF; dir++) { struct counters* counters = counters_init(STATS_END); - struct stat st; fname = format("%s/%1x/stats", g_config.cache_dir().c_str(), dir); - if (stat(fname, &st) != 0) { + if (!Stat::stat(fname)) { // No point in trying to reset the stats file if it doesn't exist. free(fname); continue; diff --git a/src/system.hpp b/src/system.hpp index cc19a3fa..3fff3e18 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -68,3 +68,20 @@ extern char** environ; #ifndef ESTALE # define ESTALE -1 #endif + +#ifdef _WIN32 +# ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0501 +# endif +# include <windows.h> +# define mkdir(a, b) mkdir(a) +# define link(src, dst) (CreateHardLink(dst, src, NULL) ? 0 : -1) +# define execv(a, b) win32execute(a, b, 0, -1, -1) +# define DIR_DELIM_CH '\\' +# define PATH_DELIM ";" +# define F_RDLCK 0 +# define F_WRLCK 0 +#else +# define DIR_DELIM_CH '/' +# define PATH_DELIM ":" +#endif diff --git a/unittest/test_Stat.cpp b/unittest/test_Stat.cpp new file mode 100644 index 00000000..31e8126a --- /dev/null +++ b/unittest/test_Stat.cpp @@ -0,0 +1,144 @@ +// Copyright (C) 2019 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 + +#include "../src/Stat.hpp" +#include "../src/Util.hpp" +#include "../src/ccache.hpp" + +#include "third_party/catch.hpp" + +#include <unistd.h> + +using Catch::Equals; + +TEST_CASE("Constructor") +{ + CHECK(!Stat::stat("does_not_exist")); + CHECK(!Stat::stat("does_not_exist", Stat::OnError::ignore)); + CHECK(!Stat::stat("does_not_exist", Stat::OnError::log)); + CHECK_THROWS_WITH( + Stat::stat("does_not_exist", Stat::OnError::throw_error), + Equals("failed to stat does_not_exist: No such file or directory")); +} + +TEST_CASE("Return values when file is missing") +{ + auto stat = Stat::stat("does_not_exist"); + CHECK(!stat); + CHECK(stat.error_number() == ENOENT); + CHECK(stat.device() == 0); + CHECK(stat.inode() == 0); + CHECK(stat.mode() == 0); + CHECK(stat.ctime() == 0); + CHECK(stat.mtime() == 0); + CHECK(stat.size() == 0); + CHECK(stat.size_on_disk() == 0); + CHECK(!stat.is_directory()); + CHECK(!stat.is_regular()); + CHECK(!stat.is_symlink()); +} + +TEST_CASE("Return values when file exists") +{ + Util::write_file("file", "1234567"); + + auto stat = Stat::stat("file"); + struct stat st; + CHECK(::stat("file", &st) == 0); + + CHECK(stat); + CHECK(stat.error_number() == 0); + CHECK(stat.device() == st.st_dev); + CHECK(stat.inode() == st.st_ino); + CHECK(stat.mode() == st.st_mode); + CHECK(stat.ctime() == st.st_ctime); + CHECK(stat.mtime() == st.st_mtime); + CHECK(stat.size() == st.st_size); +#ifdef _WIN32 + CHECK(stat.size_on_disk() == ((stat.size() + 1023) & ~1023)); +#else + CHECK(stat.size_on_disk() == st.st_blocks * 512); +#endif + CHECK(!stat.is_directory()); + CHECK(stat.is_regular()); + CHECK(!stat.is_symlink()); +} + +TEST_CASE("Directory") +{ + rmdir("directory"); + REQUIRE(mkdir("directory", 0456) == 0); + auto stat = Stat::stat("directory"); + + CHECK(stat); + CHECK(stat.error_number() == 0); + CHECK(stat.is_directory()); + CHECK(!stat.is_regular()); + CHECK(!stat.is_symlink()); +} + +#ifndef _WIN32 +TEST_CASE("Symlinks") +{ + Util::write_file("file", "1234567"); + + SECTION("file lstat") + { + auto stat = Stat::lstat("file", Stat::OnError::ignore); + CHECK(stat); + CHECK(!stat.is_directory()); + CHECK(stat.is_regular()); + CHECK(!stat.is_symlink()); + CHECK(stat.size() == 7); + } + + SECTION("file stat") + { + auto stat = Stat::stat("file", Stat::OnError::ignore); + CHECK(stat); + CHECK(!stat.is_directory()); + CHECK(stat.is_regular()); + CHECK(!stat.is_symlink()); + CHECK(stat.size() == 7); + } + + SECTION("symlink lstat") + { + unlink("symlink"); + REQUIRE(symlink("file", "symlink") == 0); + auto stat = Stat::lstat("symlink", Stat::OnError::ignore); + CHECK(stat); + CHECK(!stat.is_directory()); + CHECK(!stat.is_regular()); + CHECK(stat.is_symlink()); + CHECK(stat.size() == 4); + } + + SECTION("symlink stat") + { + unlink("symlink"); + REQUIRE(symlink("file", "symlink") == 0); + auto stat = Stat::stat("symlink", Stat::OnError::ignore); + CHECK(stat); + CHECK(!stat.is_directory()); + CHECK(stat.is_regular()); + CHECK(!stat.is_symlink()); + CHECK(stat.size() == 7); + } +} +#endif diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp index eaec54f3..42c72070 100644 --- a/unittest/test_Util.cpp +++ b/unittest/test_Util.cpp @@ -74,9 +74,7 @@ TEST_CASE("Util::create_dir") CHECK(Util::create_dir("/")); CHECK(Util::create_dir("create/dir")); - struct stat st; - CHECK(stat("create/dir", &st) == 0); - CHECK(S_ISDIR(st.st_mode)); + CHECK(Stat::stat("create/dir").is_directory()); Util::write_file("create/dir/file", ""); CHECK(!Util::create_dir("create/dir/file")); @@ -141,16 +139,6 @@ TEST_CASE("Util::for_each_level_1_subdir") CHECK(actual == expected); } -TEST_CASE("Util::get_file_size") -{ - uint64_t size; - CHECK(!Util::get_file_size("does not exist", size)); - - Util::write_file("foo", "foo"); - CHECK(Util::get_file_size("foo", size)); - CHECK(size == 3); -} - TEST_CASE("Util::get_level_1_files") { Util::create_dir("e/m/p/t/y"); @@ -192,13 +180,13 @@ TEST_CASE("Util::get_level_1_files") }); CHECK(files[0]->path() == "0/1/file_b"); - CHECK(files[0]->stat().st_size == 1); + CHECK(files[0]->lstat().size() == 1); CHECK(files[1]->path() == "0/1/file_c"); - CHECK(files[1]->stat().st_size == 2); + CHECK(files[1]->lstat().size() == 2); CHECK(files[2]->path() == "0/f/c/file_d"); - CHECK(files[2]->stat().st_size == 3); + CHECK(files[2]->lstat().size() == 3); CHECK(files[3]->path() == "0/file_a"); - CHECK(files[3]->stat().st_size == 0); + CHECK(files[3]->lstat().size() == 0); } } diff --git a/unittest/test_lockfile.cpp b/unittest/test_lockfile.cpp index 334b3388..1f81cf7a 100644 --- a/unittest/test_lockfile.cpp +++ b/unittest/test_lockfile.cpp @@ -18,6 +18,7 @@ // This file contains tests for functions in lockfile.c. +#include "../src/Stat.hpp" #include "../src/ccache.hpp" #include "framework.hpp" #include "util.hpp" @@ -31,7 +32,7 @@ TEST(acquire_should_create_symlink) #if defined(_WIN32) || defined(__CYGWIN__) CHECK(path_exists("test.lock")); #else - CHECK(is_symlink("test.lock")); + CHECK(Stat::lstat("test.lock").is_symlink()); #endif } diff --git a/unittest/util.cpp b/unittest/util.cpp index 0a5009fd..45a1014f 100644 --- a/unittest/util.cpp +++ b/unittest/util.cpp @@ -18,17 +18,13 @@ #include "util.hpp" +#include "../src/Stat.hpp" #include "../src/system.hpp" -#ifdef _WIN32 -# define lstat(a, b) stat(a, b) -#endif - bool path_exists(const char* path) { - struct stat st; - return lstat(path, &st) == 0; + return Stat::lstat(path); } void |