#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);
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)
{
# 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
== "/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];