aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.in2
-rw-r--r--dev.mk.in1
-rw-r--r--src/CacheEntryReader.cpp1
-rw-r--r--src/CacheEntryReader.hpp2
-rw-r--r--src/CacheFile.cpp27
-rw-r--r--src/CacheFile.hpp7
-rw-r--r--src/Decompressor.hpp3
-rw-r--r--src/Stat.cpp45
-rw-r--r--src/Stat.hpp182
-rw-r--r--src/Util.cpp18
-rw-r--r--src/Util.hpp3
-rw-r--r--src/ZstdCompressor.hpp2
-rw-r--r--src/ZstdDecompressor.hpp2
-rw-r--r--src/ccache.cpp218
-rw-r--r--src/ccache.hpp20
-rw-r--r--src/cleanup.cpp17
-rw-r--r--src/compress.cpp18
-rw-r--r--src/execute.cpp9
-rw-r--r--src/legacy_util.cpp64
-rw-r--r--src/manifest.cpp33
-rw-r--r--src/result.cpp55
-rw-r--r--src/stats.cpp9
-rw-r--r--src/system.hpp17
-rw-r--r--unittest/test_Stat.cpp144
-rw-r--r--unittest/test_Util.cpp22
-rw-r--r--unittest/test_lockfile.cpp3
-rw-r--r--unittest/util.cpp8
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
diff --git a/dev.mk.in b/dev.mk.in
index 79ec28f7..8c937f6e 100644
--- a/dev.mk.in
+++ b/dev.mk.in
@@ -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