]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Improve functions related to (l)stat-ing
authorJoel Rosdahl <joel@rosdahl.net>
Sat, 19 Oct 2019 10:43:39 +0000 (12:43 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sat, 19 Oct 2019 10:50:46 +0000 (12:50 +0200)
Introduced a Stat class that represents a “struct stat”. The class
replaces the utility functions x_lstat, x_stat, file_size_on_disk and
is_symlink, and it provides an easier to use interface than the macros
associated with stat structs.

27 files changed:
Makefile.in
dev.mk.in
src/CacheEntryReader.cpp
src/CacheEntryReader.hpp
src/CacheFile.cpp
src/CacheFile.hpp
src/Decompressor.hpp
src/Stat.cpp [new file with mode: 0644]
src/Stat.hpp [new file with mode: 0644]
src/Util.cpp
src/Util.hpp
src/ZstdCompressor.hpp
src/ZstdDecompressor.hpp
src/ccache.cpp
src/ccache.hpp
src/cleanup.cpp
src/compress.cpp
src/execute.cpp
src/legacy_util.cpp
src/manifest.cpp
src/result.cpp
src/stats.cpp
src/system.hpp
unittest/test_Stat.cpp [new file with mode: 0644]
unittest/test_Util.cpp
unittest/test_lockfile.cpp
unittest/util.cpp

index 225396dd08a962543e5e565250f5b4517d82cefc..3f37c7d0b7b5337841731d58992662014968da5c 100644 (file)
@@ -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
index 79ec28f7489c07242e066627a66b6e4b3677c789..8c937f6edf993543c5e1e56dadbb5d056390caf5 100644 (file)
--- 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 \
index 2670ed13c210587a76146e064738150cb6d283b9..28090780b455c73412c2308bfd052d233f4fb538 100644 (file)
@@ -20,7 +20,6 @@
 
 #include "Compressor.hpp"
 #include "Error.hpp"
-#include "ccache.hpp"
 
 #include "third_party/fmt/core.h"
 
index 150ce417dfb0be1f8ce77588e217220c4fa6e003..880281617a457915fe9cb9a856b1e2b14709217d 100644 (file)
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include "system.hpp"
+
 #include "Checksum.hpp"
 #include "Decompressor.hpp"
 #include "Util.hpp"
index 8022b2fb56ea0169fd2f3b3ca1f598b8ee4e2207..67b26a2206273e656b90b3bbc6ca14b1080519ad 100644 (file)
 
 #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
index bcf8bf2c6b25a079995c0d2440e5a9f8908e159f..696458fe99cd1a006e6600344840d32fd0c9dd1f 100644 (file)
 #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)
index dd2a4e997a2430659c512f2a1594ca27dfd978d1..59558898b8dd8f394fc3e2b8ddb2ffb02aa28fca 100644 (file)
 
 #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 (file)
index 0000000..ebb47a0
--- /dev/null
@@ -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 (file)
index 0000000..8f78a3d
--- /dev/null
@@ -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());
+}
index 801291e02cf9768e4b5c6c8c4cf6799c61d0e84b..591d9462268c94ff6641377284166f8bdde61912 100644 (file)
@@ -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,
index 53a3880144f2eee6629c946c9178b37e5e90ece1..1f3f266e097a439a305060e37106b3955ea4294a 100644 (file)
@@ -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
index 641c2b76145d048c2be2a37da475f4b35391f4d1..3c12032354beeb5a6db9aae48fca6535ea3eeae3 100644 (file)
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include "system.hpp"
+
 #include "Compressor.hpp"
 #include "NonCopyable.hpp"
 
index 45616280f42e0752233b883e1f287b2d2085017c..b32f006f3d36a49e117a1157d99dff68cf8f59f5 100644 (file)
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include "system.hpp"
+
 #include "Decompressor.hpp"
 #include "ccache.hpp"
 
index fdacea09ee994888a29b9321d17a52c00948b41e..a4549719ed10e6cdc8b3be7815afbcbf69f92140 100644 (file)
@@ -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;
index 608663ddc17bc2957ae206d158633b3f932ab2e2..302296b70d88b60263bd21608bd37dfc42ba6356 100644 (file)
@@ -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
index 7f04e73a019b79d17170c034a8ccec33bba49764..b00ca2f9afe48929a1efa0d22f1affabc387ec6e 100644 (file)
@@ -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;
   }
 
index 240d7ed985425c89a477204c954aa2fab0cf2d20..dd493e9f3c8acea6743af6aa478d4d6cfae4016d 100644 (file)
@@ -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);
index f69e681768d158d73256b9dd1a712b0edee0dbb2..88b9b9182c08745078425e9fdfc3610b2fa3c5cd 100644 (file)
@@ -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);
index 79cee6aa0782f4c97707e4e88117386d7eca6d60..b818d5b418429c95f94e393fd938025c5b3bc0dc 100644 (file)
@@ -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;
 
index 07658ccbeb02687f081976782b444fb2acda4232..1c88cb73919510f58324ddbf26cb6c215a531a93 100644 (file)
@@ -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;
index 018a75b0f27d438194ebd0d37f16dd60432ce68b..f74537ac07ef113a30469f121e31e394b650f085 100644 (file)
@@ -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);
index 3c317deb9650fb0a8029631be031340ea5a54162..81544d14853b11331a81604045bb11182e9f5256 100644 (file)
@@ -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;
index cc19a3fa4789ed6195009d76802f0863c977d7f6..3fff3e186f36c6ec90ae3cbae2c8ac78e8e77242 100644 (file)
@@ -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 (file)
index 0000000..31e8126
--- /dev/null
@@ -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
index eaec54f345c396b034c3ac325ccddf6a6e7fc5ff..42c7207037952758c79e7ddef19c8b15c191af1a 100644 (file)
@@ -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);
   }
 }
 
index 334b338872a10ef540dc65aefd84f0c0386794c7..1f81cf7a8f7627567b9fa9f18849e6aa4285e85a 100644 (file)
@@ -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
 }
 
index 0a5009fd4ef2f605d45b2bace665c28852aec550..45a1014fd8f3bbe50b589030fc4785ae99f31a57 100644 (file)
 
 #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