]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add Util::hard_link utility function
authorJoel Rosdahl <joel@rosdahl.net>
Sun, 20 Dec 2020 18:57:42 +0000 (19:57 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Sat, 26 Dec 2020 15:22:23 +0000 (16:22 +0100)
src/Util.cpp
src/Util.hpp
src/system.hpp
unittest/test_Util.cpp

index bac6b7d123c2803e937cc614fe7e5c94c0cc1342..84f1265cf776a970e193aedc2c4ce394726b16be 100644 (file)
@@ -290,16 +290,17 @@ clone_hard_link_or_copy_file(const Context& ctx,
 #endif
   }
   if (ctx.config.hard_link()) {
-    unlink(dest.c_str());
     LOG("Hard linking {} to {}", source, dest);
-    int ret = link(source.c_str(), dest.c_str());
-    if (ret == 0) {
+    try {
+      Util::hard_link(source, dest);
       if (chmod(dest.c_str(), 0444) != 0) {
         LOG("Failed to chmod: {}", strerror(errno));
       }
       return;
+    } catch (const Error& e) {
+      LOG_RAW(e.what());
+      // Fall back to copying.
     }
-    LOG("Failed to hard link: {}", strerror(errno));
   }
 
   LOG("Copying {} to {}", source, dest);
@@ -777,6 +778,30 @@ get_path_in_cache(string_view cache_dir, uint8_t level, string_view name)
   return path;
 }
 
+void
+hard_link(const std::string& oldpath, const std::string& newpath)
+{
+  // Assumption: newpath may already exist as a left-over file from a previous
+  // run, but it's only we who can create the file entry now so we don't try to
+  // handle a race between unlink() and link() below.
+  unlink(newpath.c_str());
+
+#ifndef _WIN32
+  if (link(oldpath.c_str(), newpath.c_str()) != 0) {
+    throw Error(
+      "failed to link {} to {}: {}", oldpath, newpath, strerror(errno));
+  }
+#else
+  if (!CreateHardLink(newpath.c_str(), oldpath.c_str(), nullptr)) {
+    DWORD error = GetLastError();
+    throw Error("failed to link {} to {}: {}",
+                oldpath,
+                newpath,
+                Win32Util::error_message(error));
+  }
+#endif
+}
+
 bool
 is_absolute_path(string_view path)
 {
index 3fbab45a4970f3d7b9c3d24ff220292f563e70d8..e353f4ce778bcc6d728c2be6721e72228c9be057 100644 (file)
@@ -234,6 +234,9 @@ std::string get_path_in_cache(nonstd::string_view cache_dir,
                               uint8_t level,
                               nonstd::string_view name);
 
+// Hard-link `oldpath` to `newpath`. Throws `Error` on error.
+void hard_link(const std::string& oldpath, const std::string& newpath);
+
 // Write bytes in big endian order from an integer value.
 //
 // Parameters:
index 79d07effa9b0d76b96c82f9ed16b9cf7fbd4d915..bf4a26a47bbd5db623eead69fc26e9ed6b4b6687 100644 (file)
@@ -139,7 +139,6 @@ const mode_t S_IWUSR = mode_t(_S_IWRITE);
 #  define NOMINMAX 1
 #  include <windows.h>
 #  define mkdir(a, b) _mkdir(a)
-#  define link(src, dst) (CreateHardLink(dst, src, nullptr) ? 0 : -1)
 #  define execv(a, b) win32execute(a, b, 0, -1, -1)
 #  define strncasecmp _strnicmp
 #  define strcasecmp _stricmp
index 917c13783675696a9a4f70c5c201bfa132f9bbaa..d8c2ddba1a4e3c91346d00a9d22207d0e32a8da5 100644 (file)
@@ -443,6 +443,31 @@ TEST_CASE("Util::get_path_in_cache")
         == "/zz/ccache/A/B/C/D/EF.suffix");
 }
 
+TEST_CASE("Util::hard_link")
+{
+  TestContext test_context;
+
+  SUBCASE("Link file to nonexistent destination")
+  {
+    Util::write_file("old", "content");
+    CHECK_NOTHROW(Util::hard_link("old", "new"));
+    CHECK(Util::read_file("new") == "content");
+  }
+
+  SUBCASE("Link file to existing destination")
+  {
+    Util::write_file("old", "content");
+    Util::write_file("new", "other content");
+    CHECK_NOTHROW(Util::hard_link("old", "new"));
+    CHECK(Util::read_file("new") == "content");
+  }
+
+  SUBCASE("Link nonexistent file")
+  {
+    CHECK_THROWS_AS(Util::hard_link("old", "new"), Error);
+  }
+}
+
 TEST_CASE("Util::int_to_big_endian")
 {
   uint8_t bytes[8];