From: Joel Rosdahl Date: Sun, 20 Dec 2020 18:57:42 +0000 (+0100) Subject: Add Util::hard_link utility function X-Git-Tag: v4.2~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e09c9753d10ea779bfa34ab353738c1ff9de70b0;p=thirdparty%2Fccache.git Add Util::hard_link utility function --- diff --git a/src/Util.cpp b/src/Util.cpp index bac6b7d12..84f1265cf 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -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) { diff --git a/src/Util.hpp b/src/Util.hpp index 3fbab45a4..e353f4ce7 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -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: diff --git a/src/system.hpp b/src/system.hpp index 79d07effa..bf4a26a47 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -139,7 +139,6 @@ const mode_t S_IWUSR = mode_t(_S_IWRITE); # define NOMINMAX 1 # include # 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 diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp index 917c13783..d8c2ddba1 100644 --- a/unittest/test_Util.cpp +++ b/unittest/test_Util.cpp @@ -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];