sapling/eden/fs/utils/FileHash.cpp
Saul Gutierrez 6ddfaf2fd2 symlinks: properly calculate the SHA1 of symlinks on Windows
Summary:
In some cases EdenFS would detect files as modified when it shouldn't, since it was reading the contents of symlink targets instead of hashing symlinks themselves. This diff the issues mentioned there.

Additionally, `hg status` have fewer issues on checkouts without symlinks enabled (hence the modified test)

Reviewed By: chadaustin

Differential Revision: D47107623

fbshipit-source-id: 9bee2e48b9855d71b22d6d8ec6723d94b6b8632a
2023-07-19 15:47:02 -07:00

107 lines
2.6 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#include <folly/portability/OpenSSL.h>
#include <filesystem>
#include "eden/common/utils/WinError.h"
#include "eden/fs/digest/Blake3.h"
#include "eden/fs/utils/FileHash.h"
namespace facebook::eden {
#ifdef _WIN32
namespace {
constexpr size_t kBufSize = 8192;
template <typename Hasher>
void hash(Hasher&& hasher, AbsolutePathPiece filePath) {
const auto widePath = filePath.wide();
std::error_code ec;
auto stdPath = std::filesystem::path(widePath);
auto lnk = std::filesystem::read_symlink(stdPath, ec);
if (ec.value() == 0) {
std::wstring lnkW = lnk.wstring();
std::string content;
std::transform(
lnkW.begin(), lnkW.end(), std::back_inserter(content), [](wchar_t c) {
return (char)c;
});
std::replace(content.begin(), content.end(), '\\', '/');
hasher(content.c_str(), content.size());
return;
}
HANDLE fileHandle = CreateFileW(
widePath.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (INVALID_HANDLE_VALUE == fileHandle) {
throw makeWin32ErrorExplicit(
GetLastError(), fmt::format(FMT_STRING("couldn't open {}"), filePath));
}
SCOPE_EXIT {
CloseHandle(fileHandle);
};
uint8_t buf[kBufSize];
while (true) {
DWORD bytesRead;
if (!ReadFile(fileHandle, buf, sizeof(buf), &bytesRead, nullptr)) {
throw makeWin32ErrorExplicit(
GetLastError(),
fmt::format(
FMT_STRING("Error while computing SHA1 of {}"), filePath));
}
if (bytesRead == 0) {
break;
}
hasher(buf, bytesRead);
}
}
} // namespace
Hash32 getFileBlake3(
AbsolutePathPiece filePath,
const std::optional<std::string>& maybeBlake3Key) {
auto hasher = Blake3::create(maybeBlake3Key);
hash(
[&hasher](const auto* buf, auto len) { hasher.update(buf, len); },
filePath);
static_assert(Hash32::RAW_SIZE == BLAKE3_OUT_LEN);
Hash32 blake3;
hasher.finalize(blake3.mutableBytes());
return blake3;
}
Hash20 getFileSha1(AbsolutePathPiece filePath) {
SHA_CTX ctx;
SHA1_Init(&ctx);
hash(
[&ctx](const auto* buf, auto len) { SHA1_Update(&ctx, buf, len); },
filePath);
static_assert(Hash20::RAW_SIZE == SHA_DIGEST_LENGTH);
Hash20 sha1;
SHA1_Final(sha1.mutableBytes().begin(), &ctx);
return sha1;
}
#endif
} // namespace facebook::eden