2018-12-13 03:48:09 +03:00
|
|
|
/*
|
2019-06-20 02:58:25 +03:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2018-12-13 03:48:09 +03:00
|
|
|
*
|
2019-06-20 02:58:25 +03:00
|
|
|
* This software may be used and distributed according to the terms of the
|
|
|
|
* GNU General Public License version 2.
|
2018-12-13 03:48:09 +03:00
|
|
|
*/
|
2019-10-11 15:26:59 +03:00
|
|
|
|
2018-12-13 03:48:09 +03:00
|
|
|
#include "eden/fs/inodes/OverlayFileAccess.h"
|
2019-11-07 05:21:39 +03:00
|
|
|
|
|
|
|
#include <folly/Expected.h>
|
2018-12-13 03:48:09 +03:00
|
|
|
#include <folly/Range.h>
|
|
|
|
#include <folly/logging/xlog.h>
|
2019-03-09 01:39:53 +03:00
|
|
|
#include <gflags/gflags.h>
|
2018-12-13 03:48:09 +03:00
|
|
|
#include <openssl/sha.h>
|
2019-11-07 05:21:39 +03:00
|
|
|
|
2018-12-13 03:48:09 +03:00
|
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
|
|
#include "eden/fs/inodes/InodeError.h"
|
|
|
|
#include "eden/fs/inodes/InodePtr.h"
|
|
|
|
#include "eden/fs/inodes/Overlay.h"
|
2019-11-07 05:21:39 +03:00
|
|
|
#include "eden/fs/inodes/OverlayFile.h"
|
2018-12-13 03:48:09 +03:00
|
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
|
|
#include "eden/fs/model/Blob.h"
|
|
|
|
#include "eden/fs/utils/Bug.h"
|
|
|
|
#include "folly/FileUtil.h"
|
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* OverlayFileAccess should be careful not to perform overlay IO operations
|
|
|
|
* while its own state lock is held. Doing so serializes IO operations to the
|
|
|
|
* overlay which impacts throughput under concurrent operations.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DEFINE_uint64(overlayFileCacheSize, 100, "");
|
|
|
|
|
|
|
|
void OverlayFileAccess::Entry::Info::invalidateMetadata() {
|
|
|
|
++version;
|
|
|
|
size = std::nullopt;
|
|
|
|
sha1 = std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
OverlayFileAccess::State::State(size_t cacheSize) : entries{cacheSize} {
|
|
|
|
if (cacheSize == 0) {
|
|
|
|
throw std::range_error{"overlayFileCacheSize must be at least 1"};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-07 07:30:45 +03:00
|
|
|
OverlayFileAccess::OverlayFileAccess(Overlay* overlay)
|
|
|
|
: overlay_{overlay}, state_{folly::in_place, FLAGS_overlayFileCacheSize} {}
|
2018-12-13 03:48:09 +03:00
|
|
|
|
|
|
|
OverlayFileAccess::~OverlayFileAccess() = default;
|
|
|
|
|
|
|
|
void OverlayFileAccess::createEmptyFile(InodeNumber ino) {
|
|
|
|
auto file = overlay_->createOverlayFile(ino, folly::ByteRange{});
|
|
|
|
auto state = state_.wlock();
|
|
|
|
CHECK(!state->entries.exists(ino))
|
|
|
|
<< "Cannot create overlay file " << ino << " when it's already open!";
|
|
|
|
state->entries.set(
|
|
|
|
ino, std::make_shared<Entry>(std::move(file), size_t{0}, kEmptySha1));
|
|
|
|
}
|
|
|
|
|
|
|
|
void OverlayFileAccess::createFile(
|
|
|
|
InodeNumber ino,
|
|
|
|
const Blob& blob,
|
|
|
|
const std::optional<Hash>& sha1) {
|
|
|
|
auto file = overlay_->createOverlayFile(ino, blob.getContents());
|
|
|
|
auto state = state_.wlock();
|
|
|
|
CHECK(!state->entries.exists(ino))
|
|
|
|
<< "Cannot create overlay file " << ino << " when it's already open!";
|
|
|
|
state->entries.set(
|
|
|
|
ino, std::make_shared<Entry>(std::move(file), blob.getSize(), sha1));
|
|
|
|
}
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
off_t OverlayFileAccess::getFileSize(FileInode& inode) {
|
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
2018-12-13 03:48:09 +03:00
|
|
|
uint64_t version;
|
|
|
|
{
|
|
|
|
auto info = entry->info.rlock();
|
|
|
|
if (info->size.has_value()) {
|
|
|
|
return *info->size;
|
|
|
|
}
|
|
|
|
version = info->version;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size is not known, so fstat the file. Do so while the lock is not held to
|
|
|
|
// improve concurrency.
|
2019-11-07 05:21:39 +03:00
|
|
|
auto ret = entry->file.fstat();
|
|
|
|
if (ret.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
ret.error(), inode.inodePtrFromThis(), "unable to fstat overlay file");
|
|
|
|
}
|
|
|
|
auto st = ret.value();
|
2019-03-12 03:26:06 +03:00
|
|
|
if (st.st_size < static_cast<off_t>(FsOverlay::kHeaderLength)) {
|
2018-12-13 03:48:09 +03:00
|
|
|
// Truncated overlay files can sometimes occur after a hard reboot
|
|
|
|
// where the overlay file data was not flushed to disk before the
|
|
|
|
// system powered off.
|
2019-11-07 05:21:39 +03:00
|
|
|
XLOG(ERR) << "overlay file for " << inode.getNodeId()
|
2018-12-13 03:48:09 +03:00
|
|
|
<< " is too short for header: size=" << st.st_size;
|
|
|
|
throw InodeError(EIO, inode.inodePtrFromThis(), "corrupt overlay file");
|
|
|
|
}
|
|
|
|
|
2019-03-12 03:26:06 +03:00
|
|
|
auto size = st.st_size - static_cast<off_t>(FsOverlay::kHeaderLength);
|
2018-12-13 03:48:09 +03:00
|
|
|
|
|
|
|
// Update the cache if the version still matches.
|
|
|
|
auto info = entry->info.wlock();
|
|
|
|
if (version == info->version) {
|
|
|
|
info->size = size;
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
Hash OverlayFileAccess::getSha1(FileInode& inode) {
|
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
2018-12-13 03:48:09 +03:00
|
|
|
uint64_t version;
|
|
|
|
{
|
|
|
|
auto info = entry->info.rlock();
|
|
|
|
if (info->sha1.has_value()) {
|
|
|
|
return *info->sha1;
|
|
|
|
}
|
|
|
|
version = info->version;
|
|
|
|
}
|
|
|
|
|
|
|
|
// SHA-1 is not known, so recompute it. Do so while the lock is not held to
|
|
|
|
// improve concurrency.
|
|
|
|
|
|
|
|
SHA_CTX ctx;
|
|
|
|
SHA1_Init(&ctx);
|
|
|
|
|
2019-03-12 03:26:06 +03:00
|
|
|
off_t off = FsOverlay::kHeaderLength;
|
2018-12-13 03:48:09 +03:00
|
|
|
while (true) {
|
|
|
|
// Using pread here so that we don't move the file position;
|
|
|
|
// the file descriptor is shared between multiple file handles
|
|
|
|
// and while we serialize the requests to FileData, it seems
|
|
|
|
// like a good property of this function to avoid changing that
|
|
|
|
// state.
|
|
|
|
uint8_t buf[8192];
|
2019-11-07 05:21:39 +03:00
|
|
|
auto ret = entry->file.preadNoInt(&buf, sizeof(buf), off);
|
|
|
|
if (ret.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
ret.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"pread failed during SHA-1 calculation");
|
|
|
|
}
|
|
|
|
auto len = ret.value();
|
2018-12-13 03:48:09 +03:00
|
|
|
if (len == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
SHA1_Update(&ctx, buf, len);
|
|
|
|
off += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static_assert(Hash::RAW_SIZE == SHA_DIGEST_LENGTH);
|
|
|
|
Hash sha1;
|
|
|
|
SHA1_Final(sha1.mutableBytes().begin(), &ctx);
|
|
|
|
|
|
|
|
// Update the cache if the version still matches.
|
|
|
|
auto info = entry->info.wlock();
|
|
|
|
if (version == info->version) {
|
|
|
|
info->sha1 = sha1;
|
|
|
|
}
|
|
|
|
return sha1;
|
|
|
|
}
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
std::string OverlayFileAccess::readAllContents(FileInode& inode) {
|
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
2018-12-13 03:48:09 +03:00
|
|
|
|
|
|
|
// Note that this code requires a write lock on the entry because the lseek()
|
|
|
|
// call modifies the file offset of the file descriptor. Otherwise, concurrent
|
|
|
|
// readAllContents() calls would step on each other.
|
|
|
|
//
|
|
|
|
// This violates our rule of not doing IO while locks are held, but
|
|
|
|
// readAllContents() is rare, primarily for files like .gitignore that Eden
|
|
|
|
// must read.
|
|
|
|
//
|
|
|
|
// TODO: implement readFile with pread instead of lseek.
|
|
|
|
auto info = entry->info.wlock();
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
auto rc = entry->file.lseek(FsOverlay::kHeaderLength, SEEK_SET);
|
2019-11-07 05:21:39 +03:00
|
|
|
if (rc.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
rc.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"unable to seek in materialized FileInode");
|
2018-12-13 03:48:09 +03:00
|
|
|
}
|
2019-11-07 05:21:39 +03:00
|
|
|
auto result = entry->file.readFile();
|
|
|
|
|
|
|
|
if (result.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
result.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"unable to read overlay file");
|
|
|
|
}
|
|
|
|
return result.value();
|
2018-12-13 03:48:09 +03:00
|
|
|
}
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
BufVec OverlayFileAccess::read(FileInode& inode, size_t size, off_t off) {
|
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
2018-12-13 03:48:09 +03:00
|
|
|
|
|
|
|
auto buf = folly::IOBuf::createCombined(size);
|
2019-11-07 05:21:39 +03:00
|
|
|
auto res = entry->file.preadNoInt(
|
|
|
|
buf->writableBuffer(), size, off + FsOverlay::kHeaderLength);
|
2018-12-13 03:48:09 +03:00
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
if (res.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
res.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"pread failed during overlay file read");
|
|
|
|
}
|
|
|
|
|
|
|
|
buf->append(res.value());
|
2018-12-13 03:48:09 +03:00
|
|
|
return BufVec{std::move(buf)};
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t OverlayFileAccess::write(
|
2019-11-07 05:21:39 +03:00
|
|
|
FileInode& inode,
|
2018-12-13 03:48:09 +03:00
|
|
|
const struct iovec* iov,
|
|
|
|
size_t iovcnt,
|
|
|
|
off_t off) {
|
2019-11-07 05:21:39 +03:00
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
2018-12-13 03:48:09 +03:00
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
auto xfer = entry->file.pwritev(iov, iovcnt, off + FsOverlay::kHeaderLength);
|
2019-11-07 05:21:39 +03:00
|
|
|
if (xfer.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
xfer.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"pwritev failed during file write");
|
|
|
|
}
|
2018-12-13 03:48:09 +03:00
|
|
|
auto info = entry->info.wlock();
|
|
|
|
info->invalidateMetadata();
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
return xfer.value();
|
2018-12-13 03:48:09 +03:00
|
|
|
}
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
void OverlayFileAccess::truncate(FileInode& inode, off_t size) {
|
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
|
|
|
auto result = entry->file.ftruncate(size + FsOverlay::kHeaderLength);
|
|
|
|
if (result.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
result.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"unable to ftruncate overlay file");
|
|
|
|
}
|
2018-12-13 03:48:09 +03:00
|
|
|
|
|
|
|
auto info = entry->info.wlock();
|
|
|
|
info->invalidateMetadata();
|
|
|
|
}
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
void OverlayFileAccess::fsync(FileInode& inode, bool datasync) {
|
2018-12-13 03:48:09 +03:00
|
|
|
// TODO: If the inode is not currently in cache, we could avoid calling fsync.
|
|
|
|
// That said, close() does not ensure data is synced, so it's safest to
|
|
|
|
// reopen.
|
2019-11-07 05:21:39 +03:00
|
|
|
auto entry = getEntryForInode(inode.getNodeId());
|
|
|
|
auto result = datasync ? entry->file.fdatasync() : entry->file.fsync();
|
|
|
|
if (result.hasError()) {
|
|
|
|
throw InodeError(
|
|
|
|
result.error(),
|
|
|
|
inode.inodePtrFromThis(),
|
|
|
|
"unable to fsync overlay file");
|
|
|
|
}
|
2018-12-13 03:48:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
OverlayFileAccess::EntryPtr OverlayFileAccess::getEntryForInode(
|
|
|
|
InodeNumber ino) {
|
|
|
|
{
|
|
|
|
auto state = state_.wlock();
|
|
|
|
auto iter = state->entries.find(ino);
|
|
|
|
if (iter != state->entries.end()) {
|
|
|
|
return iter->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No entry found. Open one while the lock is not held.
|
|
|
|
// TODO: A possible future optimization here is, if a SHA-1 is known when
|
|
|
|
// the blob is evicted, write it into an xattr when the blob is closed. When
|
|
|
|
// reopened, if the xattr exists, read it back out (and clear).
|
|
|
|
auto entry = std::make_shared<Entry>(
|
|
|
|
overlay_->openFileNoVerify(ino), std::nullopt, std::nullopt);
|
|
|
|
|
|
|
|
{
|
|
|
|
auto state = state_.wlock();
|
|
|
|
state->entries.set(ino, entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace eden
|
|
|
|
} // namespace facebook
|