sapling/eden/fs/utils/FileHash.cpp
Saul Gutierrez 62c802bfdc symlinks: handle symlinks with absolute paths
Summary:
This allows hg on EdenFS to be able to create symlinks with absolute paths on Windows as well as check out commits with absolute path symlinks.

There are a few caveats here:
- Symlinks with absolute paths will only be usable on the same type of OS (Windows vs. POSIX) they were created.
- Checking out commits with absolute paths created on POSIX (i.e., starting with `/`) aren't usable on Windows
- Symlinks with absolute paths created on Windows will keep the same drive letter across different checkouts.
  - E.g., if the root of the EdenFS checkout is somewhere at `C:\` and there is a symlink pointing to somewhere like `D:\foo\bar.txt`, and then that same commit is checked out in a different Windows machine with that same repo mounted at `E:\`, that symlink will still point to `D:\foo\bar.txt`

If there seems to be some UNC wonkiness in this commit, it is because UNC usage on symlinks is quite inconsistent on Windows:
- Things that use UNC for absolute path symlinks:
  - Windows' `mklink` command (creates symlinks with UNC paths)
  - Python's `os.symlink` (same as above)
  - Hg commits
  - EdenFS (when stored as Inodes; some functions like `realpath`)
- Things that don't use UNC for absolute path symlinks:
  - `PrjWritePlaceholderInfo2`
  - `PrjFillDirEntryBuffer2`
  - `std::filesystem::read_symlink`

Reviewed By: xavierd

Differential Revision: D47227590

fbshipit-source-id: b92c8462ad3f13c2724b76ee61f8c095695027cd
2023-08-01 13:59:20 -07:00

117 lines
3.2 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();
// On Windows we need to calculate the hash of symlinks for commands such as
// `hg status` and `hg goto`. In POSIX FileInode::isSameAsFast overlay info
// that is not available on Windows allows us to avoid comparing symlinks by
// hash, whereas on Windows we have to go through this somewhat slower step.
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;
});
if (std::isalpha(content[0]) && content[1] == ':') {
// Symlinks with absolute paths use UNC paths. However, std's read_symlink
// returns the target without its UNC prefix. If this is not converted
// back to an UNC path, we get hashing errors.
content = canonicalPath(content).asString();
}
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