2016-05-12 23:43:17 +03:00
|
|
|
/*
|
2019-06-20 02:58:25 +03:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2016-05-12 23:43:17 +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.
|
2016-05-12 23:43:17 +03:00
|
|
|
*/
|
2019-10-11 15:26:59 +03:00
|
|
|
|
2017-05-02 04:45:31 +03:00
|
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
#include <folly/FileUtil.h>
|
|
|
|
#include <folly/io/Cursor.h>
|
|
|
|
#include <folly/io/IOBuf.h>
|
2017-09-19 20:59:46 +03:00
|
|
|
#include <folly/io/async/EventBase.h>
|
2018-05-01 07:20:51 +03:00
|
|
|
#include <folly/logging/xlog.h>
|
2017-05-02 04:45:31 +03:00
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
|
|
#include "eden/fs/inodes/InodeError.h"
|
|
|
|
#include "eden/fs/inodes/TreeInode.h"
|
2016-06-09 04:59:51 +03:00
|
|
|
#include "eden/fs/model/Blob.h"
|
2016-05-28 04:16:30 +03:00
|
|
|
#include "eden/fs/model/Hash.h"
|
2017-05-02 04:45:31 +03:00
|
|
|
#include "eden/fs/store/BlobMetadata.h"
|
2016-06-09 04:59:51 +03:00
|
|
|
#include "eden/fs/store/ObjectStore.h"
|
2017-07-27 09:39:02 +03:00
|
|
|
#include "eden/fs/utils/Bug.h"
|
2017-12-05 20:55:31 +03:00
|
|
|
#include "eden/fs/utils/Clock.h"
|
2017-12-15 03:36:38 +03:00
|
|
|
#include "eden/fs/utils/DirType.h"
|
2020-04-29 04:57:12 +03:00
|
|
|
#include "eden/fs/utils/EnumValue.h"
|
2018-08-03 23:08:37 +03:00
|
|
|
#include "eden/fs/utils/UnboundedQueueExecutor.h"
|
2020-04-02 00:51:06 +03:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "eden/fs/win/utils/FileUtils.h" // @manual
|
|
|
|
#else
|
|
|
|
#include "eden/fs/inodes/InodeTable.h"
|
|
|
|
#include "eden/fs/inodes/Overlay.h"
|
|
|
|
#include "eden/fs/store/BlobAccess.h"
|
2017-04-14 21:31:48 +03:00
|
|
|
#include "eden/fs/utils/XAttr.h"
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2016-05-12 23:43:17 +03:00
|
|
|
|
|
|
|
using folly::Future;
|
2018-01-26 22:17:36 +03:00
|
|
|
using folly::makeFuture;
|
2016-05-12 23:43:17 +03:00
|
|
|
using folly::StringPiece;
|
|
|
|
using std::string;
|
|
|
|
using std::vector;
|
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
/*********************************************************************
|
|
|
|
* FileInode::LockedState
|
|
|
|
********************************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* LockedState is a helper class that wraps
|
|
|
|
* folly::Synchronized<State>::LockedPtr
|
|
|
|
*
|
|
|
|
* It implements operator->() and operator*() so it can be used just like
|
2018-12-13 03:48:11 +03:00
|
|
|
* LockedPtr.
|
2018-03-27 21:13:03 +03:00
|
|
|
*/
|
|
|
|
class FileInode::LockedState {
|
|
|
|
public:
|
|
|
|
explicit LockedState(FileInode* inode) : ptr_{inode->state_.wlock()} {}
|
|
|
|
explicit LockedState(const FileInodePtr& inode)
|
|
|
|
: ptr_{inode->state_.wlock()} {}
|
|
|
|
|
|
|
|
LockedState(LockedState&&) = default;
|
|
|
|
LockedState& operator=(LockedState&&) = default;
|
|
|
|
|
|
|
|
~LockedState();
|
|
|
|
|
|
|
|
State* operator->() const {
|
|
|
|
return ptr_.operator->();
|
|
|
|
}
|
|
|
|
State& operator*() const {
|
|
|
|
return ptr_.operator*();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isNull() const {
|
|
|
|
return ptr_.isNull();
|
|
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
|
|
return !ptr_.isNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Explicitly unlock the LockedState object before it is destroyed.
|
|
|
|
*/
|
|
|
|
void unlock();
|
|
|
|
|
|
|
|
/**
|
2018-12-13 03:48:09 +03:00
|
|
|
* Move the file into the MATERIALIZED_IN_OVERLAY state.
|
2018-03-27 21:13:03 +03:00
|
|
|
*
|
2018-12-13 03:48:11 +03:00
|
|
|
* This updates state->tag and state->hash.
|
2018-03-27 21:13:03 +03:00
|
|
|
*/
|
2018-12-13 03:48:09 +03:00
|
|
|
void setMaterialized();
|
2018-03-27 21:13:03 +03:00
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
/**
|
|
|
|
* If this inode still has access to a cached blob, return it.
|
|
|
|
*
|
|
|
|
* Can only be called when not materialized.
|
|
|
|
*/
|
|
|
|
std::shared_ptr<const Blob> getCachedBlob(
|
|
|
|
EdenMount* mount,
|
|
|
|
BlobCache::Interest interest);
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
private:
|
|
|
|
folly::Synchronized<State>::LockedPtr ptr_;
|
|
|
|
};
|
|
|
|
|
|
|
|
FileInode::LockedState::~LockedState() {
|
|
|
|
if (!ptr_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check the state invariants every time we release the lock
|
|
|
|
ptr_->checkInvariants();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::LockedState::unlock() {
|
|
|
|
ptr_->checkInvariants();
|
|
|
|
ptr_.unlock();
|
|
|
|
}
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
std::shared_ptr<const Blob> FileInode::LockedState::getCachedBlob(
|
|
|
|
EdenMount* mount,
|
|
|
|
BlobCache::Interest interest) {
|
|
|
|
CHECK(!ptr_->isMaterialized())
|
|
|
|
<< "getCachedBlob can only be called when not materialized";
|
|
|
|
|
|
|
|
// Is the previous handle still valid? If so, return it.
|
|
|
|
if (auto blob = ptr_->interestHandle.getBlob()) {
|
|
|
|
return blob;
|
|
|
|
}
|
|
|
|
// Otherwise, does the cache have one?
|
|
|
|
//
|
|
|
|
// The BlobAccess::getBlob call in startLoadingData on a cache miss will also
|
|
|
|
// check the BlobCache, but by checking it here, we can avoid a transition to
|
|
|
|
// BLOB_LOADING and back, and also avoid allocating some futures and closures.
|
|
|
|
auto result = mount->getBlobCache()->get(ptr_->hash.value(), interest);
|
|
|
|
if (result.blob) {
|
|
|
|
ptr_->interestHandle = std::move(result.interestHandle);
|
|
|
|
return std::move(result.blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we received a read and missed cache because the blob was
|
|
|
|
// already evicted, assume the existing readByteRanges CoverageSet
|
|
|
|
// doesn't accurately reflect how much data is in the kernel's
|
|
|
|
// caches.
|
|
|
|
ptr_->interestHandle.reset();
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-12-11 06:28:14 +03:00
|
|
|
ptr_->readByteRanges.clear();
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif // !_WIN32
|
2018-12-11 06:28:14 +03:00
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-12-13 03:48:09 +03:00
|
|
|
void FileInode::LockedState::setMaterialized() {
|
2018-03-27 21:13:03 +03:00
|
|
|
ptr_->hash.reset();
|
|
|
|
ptr_->tag = State::MATERIALIZED_IN_OVERLAY;
|
2018-12-06 22:33:06 +03:00
|
|
|
|
|
|
|
ptr_->interestHandle.reset();
|
2020-04-02 00:51:06 +03:00
|
|
|
|
|
|
|
#ifndef _WIN32
|
2018-12-06 22:33:06 +03:00
|
|
|
ptr_->readByteRanges.clear();
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2018-03-27 21:13:03 +03:00
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
/*********************************************************************
|
|
|
|
* Implementations of FileInode private template methods
|
|
|
|
* These definitions need to appear before any functions that use them.
|
|
|
|
********************************************************************/
|
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
template <typename ReturnType, typename Fn>
|
|
|
|
ReturnType FileInode::runWhileDataLoaded(
|
|
|
|
LockedState state,
|
|
|
|
BlobCache::Interest interest,
|
2020-02-06 00:13:11 +03:00
|
|
|
ObjectFetchContext& fetchContext,
|
2020-03-17 12:29:57 +03:00
|
|
|
ImportPriority priority,
|
2018-11-29 04:31:38 +03:00
|
|
|
std::shared_ptr<const Blob> blob,
|
|
|
|
Fn&& fn) {
|
|
|
|
auto future = Future<std::shared_ptr<const Blob>>::makeEmpty();
|
2018-03-23 22:34:59 +03:00
|
|
|
switch (state->tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING:
|
|
|
|
if (!blob) {
|
2018-12-11 06:28:14 +03:00
|
|
|
// If no blob is given, check cache.
|
|
|
|
blob = state.getCachedBlob(getMount(), interest);
|
2018-11-29 04:31:38 +03:00
|
|
|
}
|
|
|
|
if (blob) {
|
|
|
|
// The blob was still in cache, so we can run the function immediately.
|
|
|
|
return folly::makeFutureWith([&] {
|
|
|
|
return std::forward<Fn>(fn)(std::move(state), std::move(blob));
|
|
|
|
});
|
|
|
|
} else {
|
2020-03-17 12:29:57 +03:00
|
|
|
future = startLoadingData(
|
|
|
|
std::move(state), interest, fetchContext, priority);
|
2018-11-29 04:31:38 +03:00
|
|
|
}
|
|
|
|
break;
|
2018-03-23 22:34:59 +03:00
|
|
|
case State::BLOB_LOADING:
|
|
|
|
// If we're already loading, latch on to the in-progress load
|
|
|
|
future = state->blobLoadingPromise->getFuture();
|
|
|
|
state.unlock();
|
|
|
|
break;
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
|
|
|
return folly::makeFutureWith(
|
|
|
|
[&] { return std::forward<Fn>(fn)(std::move(state), nullptr); });
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
return std::move(future).thenValue(
|
2020-02-06 00:13:11 +03:00
|
|
|
[self = inodePtrFromThis(),
|
|
|
|
fn = std::forward<Fn>(fn),
|
|
|
|
interest,
|
2020-03-17 12:29:57 +03:00
|
|
|
&fetchContext,
|
|
|
|
priority](std::shared_ptr<const Blob> blob) mutable {
|
2018-11-29 04:31:38 +03:00
|
|
|
// Simply call runWhileDataLoaded() again when we we finish loading the
|
|
|
|
// blob data. The state should be BLOB_NOT_LOADING or
|
|
|
|
// MATERIALIZED_IN_OVERLAY this time around.
|
|
|
|
auto stateLock = LockedState{self};
|
|
|
|
DCHECK(
|
|
|
|
stateLock->tag == State::BLOB_NOT_LOADING ||
|
|
|
|
stateLock->tag == State::MATERIALIZED_IN_OVERLAY)
|
|
|
|
<< "unexpected FileInode state after loading: " << stateLock->tag;
|
|
|
|
return self->runWhileDataLoaded<ReturnType>(
|
|
|
|
std::move(stateLock),
|
|
|
|
interest,
|
2020-02-06 00:13:11 +03:00
|
|
|
fetchContext,
|
2020-03-17 12:29:57 +03:00
|
|
|
priority,
|
2018-11-29 04:31:38 +03:00
|
|
|
std::move(blob),
|
|
|
|
std::forward<Fn>(fn));
|
|
|
|
});
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-03-23 22:34:59 +03:00
|
|
|
template <typename Fn>
|
|
|
|
typename folly::futures::detail::callableResult<FileInode::LockedState, Fn>::
|
|
|
|
Return
|
2018-11-29 04:31:38 +03:00
|
|
|
FileInode::runWhileMaterialized(
|
|
|
|
LockedState state,
|
|
|
|
std::shared_ptr<const Blob> blob,
|
|
|
|
Fn&& fn) {
|
|
|
|
auto future = Future<std::shared_ptr<const Blob>>::makeEmpty();
|
2018-03-23 22:34:59 +03:00
|
|
|
switch (state->tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING:
|
|
|
|
if (!blob) {
|
2018-12-11 06:28:14 +03:00
|
|
|
// If no blob is given, check cache.
|
|
|
|
blob = state.getCachedBlob(
|
|
|
|
getMount(), BlobCache::Interest::UnlikelyNeededAgain);
|
2018-11-29 04:31:38 +03:00
|
|
|
}
|
|
|
|
if (blob) {
|
|
|
|
// We have the blob data loaded.
|
|
|
|
// Materialize the file now.
|
|
|
|
materializeNow(state, blob);
|
|
|
|
// Call materializeInParent before we return, after we are
|
|
|
|
// sure the state lock has been released. This does mean that our
|
|
|
|
// parent won't have updated our state until after the caller's function
|
|
|
|
// runs, but this is okay. There is always a brief gap between when we
|
|
|
|
// materialize ourself and when our parent gets updated to indicate
|
|
|
|
// this. If we do crash during this period it is not too unreasonable
|
|
|
|
// that recent change right before the crash might be reverted to their
|
|
|
|
// non-materialized state.
|
|
|
|
SCOPE_EXIT {
|
|
|
|
CHECK(state.isNull());
|
|
|
|
materializeInParent();
|
|
|
|
};
|
|
|
|
// Note that we explicitly create a temporary LockedState object
|
|
|
|
// to pass to the caller to ensure that the state lock will be released
|
|
|
|
// when they return, even if the caller's function accepts the state as
|
|
|
|
// an rvalue-reference and does not release it themselves.
|
|
|
|
return folly::makeFutureWith([&] {
|
|
|
|
return std::forward<Fn>(fn)(LockedState{std::move(state)});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// The blob must be loaded, so kick that off. There's no point in caching
|
|
|
|
// it in memory - the blob will immediately be written into the overlay
|
|
|
|
// and then dropped.
|
|
|
|
future = startLoadingData(
|
2020-02-06 00:13:11 +03:00
|
|
|
std::move(state),
|
|
|
|
BlobCache::Interest::UnlikelyNeededAgain,
|
2020-03-17 12:29:57 +03:00
|
|
|
ObjectFetchContext::getNullContext(),
|
2020-03-20 20:53:03 +03:00
|
|
|
ImportPriority::kNormal());
|
2018-11-29 04:31:38 +03:00
|
|
|
break;
|
2018-03-23 22:34:59 +03:00
|
|
|
case State::BLOB_LOADING:
|
|
|
|
// If we're already loading, latch on to the in-progress load
|
|
|
|
future = state->blobLoadingPromise->getFuture();
|
|
|
|
state.unlock();
|
|
|
|
break;
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
|
|
|
return folly::makeFutureWith(
|
|
|
|
[&] { return std::forward<Fn>(fn)(LockedState{std::move(state)}); });
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
|
2018-10-23 23:39:59 +03:00
|
|
|
return std::move(future).thenValue(
|
2018-10-02 20:01:06 +03:00
|
|
|
[self = inodePtrFromThis(),
|
2018-11-29 04:31:38 +03:00
|
|
|
fn = std::forward<Fn>(fn)](std::shared_ptr<const Blob> blob) mutable {
|
|
|
|
// Simply call runWhileMaterialized() again when we we are finished
|
|
|
|
// loading the blob data.
|
2018-10-02 20:01:06 +03:00
|
|
|
auto stateLock = LockedState{self};
|
|
|
|
DCHECK(
|
2018-11-29 04:31:38 +03:00
|
|
|
stateLock->tag == State::BLOB_NOT_LOADING ||
|
2018-10-02 20:01:06 +03:00
|
|
|
stateLock->tag == State::MATERIALIZED_IN_OVERLAY)
|
|
|
|
<< "unexpected FileInode state after loading: " << stateLock->tag;
|
|
|
|
return self->runWhileMaterialized(
|
2018-11-29 04:31:38 +03:00
|
|
|
std::move(stateLock), std::move(blob), std::forward<Fn>(fn));
|
2018-10-02 20:01:06 +03:00
|
|
|
});
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Fn>
|
|
|
|
typename std::result_of<Fn(FileInode::LockedState&&)>::type
|
|
|
|
FileInode::truncateAndRun(LockedState state, Fn&& fn) {
|
|
|
|
switch (state->tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING:
|
2018-03-23 22:34:59 +03:00
|
|
|
case State::BLOB_LOADING: {
|
2018-03-27 21:13:03 +03:00
|
|
|
// We are not materialized yet. We need to materialize the file now.
|
|
|
|
//
|
|
|
|
// Note that we have to be pretty careful about ordering of operations
|
|
|
|
// here and how we behave if an exception is thrown at any point. We
|
|
|
|
// want to:
|
|
|
|
// - Truncate the file.
|
|
|
|
// - Invoke the input function with the state lock still held.
|
|
|
|
// - Release the state lock
|
|
|
|
// - Assuming we successfully materialized the file, mark ourself
|
|
|
|
// materialized in our parent TreeInode.
|
|
|
|
// - If we successfully materialized the file and were in the
|
|
|
|
// BLOB_LOADING state, fulfill the blobLoadingPromise.
|
2018-11-29 04:31:38 +03:00
|
|
|
std::optional<folly::SharedPromise<std::shared_ptr<const Blob>>>
|
|
|
|
loadingPromise;
|
2018-03-23 22:34:59 +03:00
|
|
|
SCOPE_EXIT {
|
|
|
|
if (loadingPromise) {
|
2018-11-29 04:31:38 +03:00
|
|
|
// If transitioning from the loading state to materialized, fulfill
|
|
|
|
// the loading promise will null. Callbacks will have to handle the
|
|
|
|
// case that the state is now materialized.
|
|
|
|
loadingPromise->setValue(nullptr);
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
// Call materializeAndTruncate()
|
|
|
|
materializeAndTruncate(state);
|
2018-03-27 21:13:03 +03:00
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
// Now that materializeAndTruncate() has succeeded, extract the
|
|
|
|
// blobLoadingPromise so we can fulfill it as we exit.
|
|
|
|
loadingPromise = std::move(state->blobLoadingPromise);
|
|
|
|
state->blobLoadingPromise.reset();
|
|
|
|
// Also call materializeInParent() as we exit, before fulfilling the
|
|
|
|
// blobLoadingPromise.
|
|
|
|
SCOPE_EXIT {
|
|
|
|
CHECK(state.isNull());
|
|
|
|
materializeInParent();
|
|
|
|
};
|
2018-03-27 21:13:03 +03:00
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
// Now invoke the input function.
|
|
|
|
// Note that we explicitly create a temporary LockedState object
|
|
|
|
// to pass to the caller to ensure that the state lock will be released
|
|
|
|
// when they return, even if the caller's function accepts the state as
|
|
|
|
// an rvalue-reference and does not release it themselves.
|
|
|
|
return std::forward<Fn>(fn)(LockedState{std::move(state)});
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
|
|
|
// We are already materialized.
|
|
|
|
// Truncate the file in the overlay, then call the function.
|
2018-03-27 21:13:03 +03:00
|
|
|
truncateInOverlay(state);
|
|
|
|
return std::forward<Fn>(fn)(std::move(state));
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
XLOG(FATAL) << "unexpected FileInode state " << state->tag;
|
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif // !_WIN32
|
2018-03-23 22:34:59 +03:00
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* FileInode::State methods
|
|
|
|
********************************************************************/
|
|
|
|
|
2018-10-24 04:48:38 +03:00
|
|
|
FileInodeState::FileInodeState(const std::optional<Hash>& h) : hash(h) {
|
2018-11-29 04:31:38 +03:00
|
|
|
tag = hash ? BLOB_NOT_LOADING : MATERIALIZED_IN_OVERLAY;
|
2017-11-08 03:07:16 +03:00
|
|
|
|
|
|
|
checkInvariants();
|
2017-07-17 23:17:42 +03:00
|
|
|
}
|
2017-03-11 05:28:13 +03:00
|
|
|
|
2018-06-01 19:29:32 +03:00
|
|
|
FileInodeState::FileInodeState() : tag(MATERIALIZED_IN_OVERLAY) {
|
2017-11-08 03:07:16 +03:00
|
|
|
checkInvariants();
|
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
/*
|
2018-06-01 19:29:32 +03:00
|
|
|
* Define FileInodeState destructor explicitly to avoid including
|
2018-03-27 21:13:03 +03:00
|
|
|
* some header files in FileInode.h
|
|
|
|
*/
|
2018-06-01 19:29:32 +03:00
|
|
|
FileInodeState::~FileInodeState() = default;
|
2018-03-27 21:13:03 +03:00
|
|
|
|
2018-06-01 19:29:32 +03:00
|
|
|
void FileInodeState::checkInvariants() {
|
2017-11-20 23:03:28 +03:00
|
|
|
switch (tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case BLOB_NOT_LOADING:
|
2017-11-20 23:03:28 +03:00
|
|
|
CHECK(hash);
|
|
|
|
CHECK(!blobLoadingPromise);
|
|
|
|
return;
|
|
|
|
case BLOB_LOADING:
|
|
|
|
CHECK(hash);
|
|
|
|
CHECK(blobLoadingPromise);
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-12-06 22:33:06 +03:00
|
|
|
CHECK(readByteRanges.empty());
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2017-11-20 23:03:28 +03:00
|
|
|
return;
|
|
|
|
case MATERIALIZED_IN_OVERLAY:
|
|
|
|
// 'materialized'
|
|
|
|
CHECK(!hash);
|
|
|
|
CHECK(!blobLoadingPromise);
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-12-06 22:33:06 +03:00
|
|
|
CHECK(readByteRanges.empty());
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2017-11-20 23:03:28 +03:00
|
|
|
return;
|
2017-11-08 03:07:16 +03:00
|
|
|
}
|
2017-11-20 23:03:28 +03:00
|
|
|
|
|
|
|
XLOG(FATAL) << "Unexpected tag value: " << tag;
|
2017-07-27 21:48:19 +03:00
|
|
|
}
|
2017-11-20 23:03:28 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
/*********************************************************************
|
|
|
|
* FileInode methods
|
|
|
|
********************************************************************/
|
|
|
|
|
2017-11-20 23:03:28 +03:00
|
|
|
// The FileInode is in NOT_LOADED or MATERIALIZED_IN_OVERLAY state.
|
2016-12-01 02:48:04 +03:00
|
|
|
FileInode::FileInode(
|
2018-03-20 03:01:15 +03:00
|
|
|
InodeNumber ino,
|
2016-12-13 04:48:45 +03:00
|
|
|
TreeInodePtr parentInode,
|
2016-12-13 04:48:43 +03:00
|
|
|
PathComponentPiece name,
|
2018-04-27 01:51:16 +03:00
|
|
|
mode_t initialMode,
|
2018-11-29 02:42:38 +03:00
|
|
|
const std::optional<InodeTimestamps>& initialTimestamps,
|
2018-10-24 04:48:38 +03:00
|
|
|
const std::optional<Hash>& hash)
|
2018-11-29 02:42:38 +03:00
|
|
|
: Base(ino, initialMode, initialTimestamps, std::move(parentInode), name),
|
2018-05-22 20:46:26 +03:00
|
|
|
state_(folly::in_place, hash) {}
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2017-11-20 23:03:28 +03:00
|
|
|
// The FileInode is in MATERIALIZED_IN_OVERLAY state.
|
2016-12-01 02:48:04 +03:00
|
|
|
FileInode::FileInode(
|
2018-03-20 03:01:15 +03:00
|
|
|
InodeNumber ino,
|
2016-12-13 04:48:45 +03:00
|
|
|
TreeInodePtr parentInode,
|
2016-12-13 04:48:43 +03:00
|
|
|
PathComponentPiece name,
|
2018-04-27 01:51:16 +03:00
|
|
|
mode_t initialMode,
|
2018-11-29 02:42:38 +03:00
|
|
|
const InodeTimestamps& initialTimestamps)
|
2018-06-01 21:16:44 +03:00
|
|
|
: Base(ino, initialMode, initialTimestamps, std::move(parentInode), name),
|
2018-05-22 20:46:26 +03:00
|
|
|
state_(folly::in_place) {}
|
2016-07-06 05:53:15 +03:00
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-03-20 03:01:15 +03:00
|
|
|
folly::Future<Dispatcher::Attr> FileInode::getattr() {
|
2016-05-17 00:48:30 +03:00
|
|
|
// Future optimization opportunity: right now, if we have not already
|
2017-03-11 05:28:13 +03:00
|
|
|
// materialized the data from the entry, we have to materialize it
|
2016-05-17 00:48:30 +03:00
|
|
|
// from the store. If we augmented our metadata we could avoid this,
|
|
|
|
// and this would speed up operations like `ls`.
|
2018-07-25 09:29:25 +03:00
|
|
|
return stat().thenValue(
|
2018-03-20 03:01:15 +03:00
|
|
|
[](const struct stat& st) { return Dispatcher::Attr{st}; });
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
|
2018-05-24 09:59:21 +03:00
|
|
|
folly::Future<Dispatcher::Attr> FileInode::setattr(
|
2018-01-03 03:25:03 +03:00
|
|
|
const fuse_setattr_in& attr) {
|
2018-04-27 06:41:38 +03:00
|
|
|
// If this file is inside of .eden it cannot be reparented, so getParentRacy()
|
|
|
|
// is okay.
|
|
|
|
auto parent = getParentRacy();
|
|
|
|
if (parent && parent->getNodeId() == getMount()->getDotEdenInodeNumber()) {
|
|
|
|
return folly::makeFuture<Dispatcher::Attr>(
|
|
|
|
InodeError(EPERM, inodePtrFromThis()));
|
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
auto setAttrs = [self = inodePtrFromThis(), attr](LockedState&& state) {
|
2018-12-13 03:48:09 +03:00
|
|
|
auto ino = self->getNodeId();
|
2018-03-20 03:01:15 +03:00
|
|
|
auto result = Dispatcher::Attr{self->getMount()->initStatData()};
|
2017-08-05 06:14:19 +03:00
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
DCHECK_EQ(State::MATERIALIZED_IN_OVERLAY, state->tag)
|
2017-12-05 02:07:44 +03:00
|
|
|
<< "Must have a file in the overlay at this point";
|
|
|
|
|
2018-01-03 03:25:03 +03:00
|
|
|
// Set the size of the file when FATTR_SIZE is set
|
|
|
|
if (attr.valid & FATTR_SIZE) {
|
2018-12-13 03:48:09 +03:00
|
|
|
// Throws upon error.
|
2019-11-07 05:21:39 +03:00
|
|
|
self->getOverlayFileAccess(state)->truncate(*self, attr.size);
|
2017-12-05 02:07:44 +03:00
|
|
|
}
|
2017-08-05 06:14:19 +03:00
|
|
|
|
2018-05-22 20:46:24 +03:00
|
|
|
auto metadata = self->getMount()->getInodeMetadataTable()->modifyOrThrow(
|
2018-12-13 03:48:09 +03:00
|
|
|
ino, [&](auto& metadata) {
|
2018-05-22 20:46:28 +03:00
|
|
|
metadata.updateFromAttr(self->getClock(), attr);
|
2018-05-22 20:46:24 +03:00
|
|
|
});
|
2018-05-22 20:46:26 +03:00
|
|
|
|
2017-12-05 02:07:44 +03:00
|
|
|
// We need to call fstat function here to get the size of the overlay
|
|
|
|
// file. We might update size in the result while truncating the file
|
2018-01-03 03:25:03 +03:00
|
|
|
// when FATTR_SIZE flag is set but when the flag is not set we
|
2017-12-05 02:07:44 +03:00
|
|
|
// have to return the correct size of the file even if some size is sent
|
|
|
|
// in attr.st.st_size.
|
2019-11-07 05:21:39 +03:00
|
|
|
off_t size = self->getOverlayFileAccess(state)->getFileSize(*self);
|
2018-12-13 03:48:09 +03:00
|
|
|
result.st.st_ino = ino.get();
|
|
|
|
result.st.st_size = size;
|
2018-05-22 20:46:24 +03:00
|
|
|
metadata.applyToStat(result.st);
|
2018-02-09 06:32:04 +03:00
|
|
|
result.st.st_nlink = 1;
|
|
|
|
updateBlockCount(result.st);
|
2016-09-19 22:48:11 +03:00
|
|
|
|
2017-12-05 02:07:44 +03:00
|
|
|
// Update the Journal
|
|
|
|
self->updateJournal();
|
|
|
|
return result;
|
2018-03-23 22:34:59 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// Minor optimization: if we know that the file is being completely truncated
|
|
|
|
// as part of this operation, there's no need to fetch the underlying data,
|
|
|
|
// so use truncateAndRun() rather than runWhileMaterialized()
|
|
|
|
bool truncate = (attr.valid & FATTR_SIZE) && attr.size == 0;
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
2018-03-23 22:34:59 +03:00
|
|
|
if (truncate) {
|
|
|
|
return truncateAndRun(std::move(state), setAttrs);
|
|
|
|
} else {
|
2018-11-29 04:31:38 +03:00
|
|
|
return runWhileMaterialized(std::move(state), nullptr, setAttrs);
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
2016-07-02 01:08:47 +03:00
|
|
|
}
|
|
|
|
|
2020-03-06 22:53:33 +03:00
|
|
|
folly::Future<std::string> FileInode::readlink(
|
|
|
|
ObjectFetchContext& fetchContext,
|
|
|
|
CacheHint cacheHint) {
|
2018-02-13 04:35:47 +03:00
|
|
|
if (dtype_t::Symlink != getType()) {
|
|
|
|
// man 2 readlink says: EINVAL The named file is not a symbolic link.
|
|
|
|
throw InodeError(EINVAL, inodePtrFromThis(), "not a symlink");
|
2016-05-17 00:48:28 +03:00
|
|
|
}
|
|
|
|
|
2017-03-02 19:16:18 +03:00
|
|
|
// The symlink contents are simply the file contents!
|
2020-03-06 22:53:33 +03:00
|
|
|
return readAll(fetchContext, cacheHint);
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif // !_WIN32
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2018-10-24 04:48:38 +03:00
|
|
|
std::optional<bool> FileInode::isSameAsFast(
|
2018-02-20 22:16:00 +03:00
|
|
|
const Hash& blobID,
|
|
|
|
TreeEntryType entryType) {
|
2017-11-08 03:07:16 +03:00
|
|
|
auto state = state_.rlock();
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-05-22 20:46:26 +03:00
|
|
|
if (entryType != treeEntryTypeFromMode(getMetadataLocked(*state).mode)) {
|
2017-07-27 09:39:02 +03:00
|
|
|
return false;
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#else
|
|
|
|
// Note: the Windows-specific version of getMode() is safe to call here even
|
|
|
|
// though we are holding the state_ lock. On non-Windows getMetadataLocked()
|
|
|
|
// must be used instead when holding the lock.
|
|
|
|
if (entryType != treeEntryTypeFromMode(getMode())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif // !_WIN32
|
2017-03-11 05:28:13 +03:00
|
|
|
|
2018-10-24 04:48:38 +03:00
|
|
|
if (state->hash.has_value()) {
|
2018-05-01 08:02:14 +03:00
|
|
|
// This file is not materialized, so we can compare blob hashes.
|
|
|
|
// If the hashes are the same then assume the contents are the same.
|
|
|
|
//
|
|
|
|
// Unfortunately we cannot assume that the file contents are different if
|
|
|
|
// the hashes are different: Mercurial's blob hashes also include history
|
|
|
|
// metadata, so there may be multiple different blob hashes for the same
|
|
|
|
// file contents.
|
|
|
|
if (state->hash.value() == blobID) {
|
|
|
|
return true;
|
|
|
|
}
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
2018-10-24 04:48:38 +03:00
|
|
|
return std::nullopt;
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2018-06-13 11:14:32 +03:00
|
|
|
folly::Future<bool> FileInode::isSameAs(
|
|
|
|
const Blob& blob,
|
2020-02-06 00:13:11 +03:00
|
|
|
TreeEntryType entryType,
|
|
|
|
ObjectFetchContext& fetchContext) {
|
2018-02-20 22:16:00 +03:00
|
|
|
auto result = isSameAsFast(blob.getHash(), entryType);
|
2018-10-24 04:48:38 +03:00
|
|
|
if (result.has_value()) {
|
2017-07-27 09:39:02 +03:00
|
|
|
return result.value();
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 03:07:04 +03:00
|
|
|
auto blobSha1 = Hash::sha1(blob.getContents());
|
2020-02-06 00:13:11 +03:00
|
|
|
return getSha1(fetchContext).thenValue([blobSha1](const Hash& sha1) {
|
|
|
|
return sha1 == blobSha1;
|
|
|
|
});
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2018-02-20 22:16:00 +03:00
|
|
|
folly::Future<bool> FileInode::isSameAs(
|
|
|
|
const Hash& blobID,
|
2020-02-06 00:13:11 +03:00
|
|
|
TreeEntryType entryType,
|
|
|
|
ObjectFetchContext& fetchContext) {
|
2018-02-20 22:16:00 +03:00
|
|
|
auto result = isSameAsFast(blobID, entryType);
|
2018-10-24 04:48:38 +03:00
|
|
|
if (result.has_value()) {
|
2017-07-27 09:39:02 +03:00
|
|
|
return makeFuture(result.value());
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
2020-02-06 00:13:11 +03:00
|
|
|
auto f1 = getSha1(fetchContext);
|
|
|
|
auto f2 = getMount()->getObjectStore()->getBlobSha1(blobID, fetchContext);
|
2020-03-10 21:31:05 +03:00
|
|
|
return folly::collectUnsafe(f1, f2).thenValue(
|
|
|
|
[](std::tuple<Hash, Hash>&& result) {
|
|
|
|
return std::get<0>(result) == std::get<1>(result);
|
|
|
|
});
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2017-03-03 01:20:34 +03:00
|
|
|
mode_t FileInode::getMode() const {
|
2018-05-22 20:46:26 +03:00
|
|
|
return getMetadata().mode;
|
2017-03-03 01:20:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
mode_t FileInode::getPermissions() const {
|
|
|
|
return (getMode() & 07777);
|
|
|
|
}
|
|
|
|
|
2018-05-22 20:46:24 +03:00
|
|
|
InodeMetadata FileInode::getMetadata() const {
|
|
|
|
auto lock = state_.rlock();
|
|
|
|
return getMetadataLocked(*lock);
|
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#else
|
|
|
|
mode_t FileInode::getMode() const {
|
|
|
|
// On Windows we only store the dir type info and no permissions bits here.
|
|
|
|
// For file it will always be a regular file.
|
|
|
|
return _S_IFREG;
|
|
|
|
}
|
|
|
|
#endif // !_WIN32
|
|
|
|
|
2018-10-24 04:48:38 +03:00
|
|
|
std::optional<Hash> FileInode::getBlobHash() const {
|
2017-03-11 05:28:13 +03:00
|
|
|
return state_.rlock()->hash;
|
|
|
|
}
|
|
|
|
|
2017-03-11 05:28:08 +03:00
|
|
|
void FileInode::materializeInParent() {
|
|
|
|
auto renameLock = getMount()->acquireRenameLock();
|
|
|
|
auto loc = getLocationInfo(renameLock);
|
|
|
|
if (loc.parent && !loc.unlinked) {
|
2018-03-24 04:17:05 +03:00
|
|
|
loc.parent->childMaterialized(renameLock, loc.name);
|
2017-03-11 05:28:08 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2016-12-01 02:48:04 +03:00
|
|
|
Future<vector<string>> FileInode::listxattr() {
|
2016-05-12 23:43:17 +03:00
|
|
|
vector<string> attributes;
|
2019-09-03 18:02:16 +03:00
|
|
|
// We used to return kXattrSha1 here for regular files, but
|
|
|
|
// that caused some annoying behavior with appledouble
|
|
|
|
// metadata files being created by various tools that wanted
|
|
|
|
// to preserve all of these attributes across copy on macos.
|
|
|
|
// So now we just return an empty set on all systems.
|
2016-05-12 23:43:17 +03:00
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
Future<string> FileInode::getxattr(StringPiece name) {
|
2016-05-12 23:43:17 +03:00
|
|
|
// Currently, we only support the xattr for the SHA-1 of a regular file.
|
2016-05-26 18:22:22 +03:00
|
|
|
if (name != kXattrSha1) {
|
2017-03-11 05:28:13 +03:00
|
|
|
return makeFuture<string>(InodeError(kENOATTR, inodePtrFromThis()));
|
2016-05-26 18:22:22 +03:00
|
|
|
}
|
|
|
|
|
2020-02-06 00:13:11 +03:00
|
|
|
return getSha1(ObjectFetchContext::getNullContext()).thenValue([](Hash hash) {
|
|
|
|
return hash.toString();
|
|
|
|
});
|
2016-05-28 04:16:30 +03:00
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#else
|
|
|
|
|
|
|
|
AbsolutePath FileInode::getMaterializedFilePath() {
|
|
|
|
auto filePath = getPath();
|
|
|
|
if (!filePath.has_value()) {
|
|
|
|
throw InodeError(
|
|
|
|
EINVAL, inodePtrFromThis(), "File is unlinked", getLogPath());
|
|
|
|
}
|
|
|
|
return getMount()->getPath() + filePath.value();
|
|
|
|
}
|
|
|
|
#endif
|
2016-05-28 04:16:30 +03:00
|
|
|
|
2020-02-06 00:13:11 +03:00
|
|
|
Future<Hash> FileInode::getSha1(ObjectFetchContext& fetchContext) {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
2017-11-08 03:07:16 +03:00
|
|
|
|
2017-11-20 23:03:28 +03:00
|
|
|
switch (state->tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING:
|
2017-11-20 23:03:28 +03:00
|
|
|
case State::BLOB_LOADING:
|
2018-11-29 04:31:38 +03:00
|
|
|
// If a file is not materialized, it should have a hash value.
|
2020-02-06 00:13:11 +03:00
|
|
|
return getObjectStore()->getBlobSha1(state->hash.value(), fetchContext);
|
2017-11-20 23:03:28 +03:00
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifdef _WIN32
|
|
|
|
// TODO(puneetk): We should convert the following code to Future based by
|
|
|
|
// offloading the function call.
|
|
|
|
AbsolutePath pathToFile = getMaterializedFilePath();
|
|
|
|
return makeFuture(getFileSha1(pathToFile.c_str()));
|
|
|
|
#else
|
2019-11-07 05:21:39 +03:00
|
|
|
return getOverlayFileAccess(state)->getSha1(*this);
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif // _WIN32
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
2017-11-20 23:03:28 +03:00
|
|
|
|
|
|
|
XLOG(FATAL) << "FileInode in illegal state: " << state->tag;
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2017-11-04 04:54:57 +03:00
|
|
|
folly::Future<struct stat> FileInode::stat() {
|
2018-10-18 23:59:34 +03:00
|
|
|
auto st = getMount()->initStatData();
|
|
|
|
st.st_nlink = 1; // Eden does not support hard links yet.
|
|
|
|
st.st_ino = getNodeId().get();
|
|
|
|
// NOTE: we don't set rdev to anything special here because we
|
|
|
|
// don't support committing special device nodes.
|
|
|
|
|
|
|
|
auto state = LockedState{this};
|
2018-04-24 23:20:52 +03:00
|
|
|
|
2018-10-18 23:59:34 +03:00
|
|
|
getMetadataLocked(*state).applyToStat(st);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-10-18 23:59:34 +03:00
|
|
|
switch (state->tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING:
|
2018-10-18 23:59:34 +03:00
|
|
|
case State::BLOB_LOADING:
|
|
|
|
CHECK(state->hash.has_value());
|
2019-06-26 06:25:14 +03:00
|
|
|
// While getBlobSize will sometimes need to fetch a blob to compute the
|
|
|
|
// size, if it's already known, return the cached size. This is especially
|
|
|
|
// a win after restarting Eden - size can be loaded from the local cache
|
|
|
|
// more cheaply than deserializing an entire blob.
|
2018-10-18 23:59:34 +03:00
|
|
|
return getObjectStore()
|
2020-02-06 00:13:11 +03:00
|
|
|
->getBlobSize(*state->hash, ObjectFetchContext::getNullContext())
|
2019-06-26 06:25:14 +03:00
|
|
|
.thenValue([st](const uint64_t size) mutable {
|
|
|
|
st.st_size = size;
|
2018-10-18 23:59:34 +03:00
|
|
|
updateBlockCount(st);
|
|
|
|
return st;
|
|
|
|
});
|
|
|
|
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
2019-11-07 05:21:39 +03:00
|
|
|
st.st_size = getOverlayFileAccess(state)->getFileSize(*this);
|
2018-10-18 23:59:34 +03:00
|
|
|
updateBlockCount(st);
|
|
|
|
return st;
|
|
|
|
}
|
2019-09-11 21:09:13 +03:00
|
|
|
|
2019-11-23 00:26:28 +03:00
|
|
|
return EDEN_BUG_FUTURE(struct stat)
|
2020-04-29 04:57:12 +03:00
|
|
|
<< "unexpected FileInode state tag " << enumValue(state->tag);
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2018-02-09 06:32:04 +03:00
|
|
|
void FileInode::updateBlockCount(struct stat& st) {
|
|
|
|
// Compute a value to store in st_blocks based on st_size.
|
|
|
|
// Note that st_blocks always refers to 512 byte blocks, regardless of the
|
|
|
|
// value we report in st.st_blksize.
|
|
|
|
static constexpr off_t kBlockSize = 512;
|
|
|
|
st.st_blocks = ((st.st_size + kBlockSize - 1) / kBlockSize);
|
|
|
|
}
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
void FileInode::fsync(bool datasync) {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
2018-12-13 03:48:09 +03:00
|
|
|
if (state->isMaterialized()) {
|
2019-11-07 05:21:39 +03:00
|
|
|
getOverlayFileAccess(state)->fsync(*this, datasync);
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2020-02-06 00:13:11 +03:00
|
|
|
Future<string> FileInode::readAll(
|
|
|
|
ObjectFetchContext& fetchContext,
|
|
|
|
CacheHint cacheHint) {
|
2018-11-29 04:31:38 +03:00
|
|
|
auto interest = BlobCache::Interest::LikelyNeededAgain;
|
|
|
|
switch (cacheHint) {
|
|
|
|
case CacheHint::NotNeededAgain:
|
|
|
|
interest = BlobCache::Interest::UnlikelyNeededAgain;
|
|
|
|
break;
|
|
|
|
case CacheHint::LikelyNeededAgain:
|
|
|
|
// readAll() with LikelyNeededAgain is primarily called for files read
|
|
|
|
// by Eden itself, like .gitignore, and for symlinks on kernels that don't
|
|
|
|
// cache readlink. At least keep the blob around while the inode is
|
|
|
|
// loaded.
|
|
|
|
interest = BlobCache::Interest::WantHandle;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return runWhileDataLoaded<Future<string>>(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState{this},
|
2018-11-29 04:31:38 +03:00
|
|
|
interest,
|
2020-02-06 00:13:11 +03:00
|
|
|
fetchContext,
|
2020-03-20 20:53:03 +03:00
|
|
|
ImportPriority::kNormal(),
|
2018-11-29 04:31:38 +03:00
|
|
|
nullptr,
|
|
|
|
[self = inodePtrFromThis()](
|
|
|
|
LockedState&& state, std::shared_ptr<const Blob> blob) -> string {
|
2018-03-23 22:34:59 +03:00
|
|
|
std::string result;
|
|
|
|
switch (state->tag) {
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY: {
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifdef _WIN32
|
|
|
|
AbsolutePath pathToFile = self->getMaterializedFilePath();
|
|
|
|
readFile(pathToFile.c_str(), result);
|
|
|
|
#else
|
2018-11-29 04:31:38 +03:00
|
|
|
DCHECK(!blob);
|
2019-11-07 05:21:39 +03:00
|
|
|
result = self->getOverlayFileAccess(state)->readAllContents(*self);
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2018-03-23 22:34:59 +03:00
|
|
|
break;
|
|
|
|
}
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING: {
|
|
|
|
const auto& contentsBuf = blob->getContents();
|
2018-03-23 22:34:59 +03:00
|
|
|
folly::io::Cursor cursor(&contentsBuf);
|
|
|
|
result =
|
|
|
|
cursor.readFixedString(contentsBuf.computeChainDataLength());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
EDEN_BUG() << "neither materialized nor loaded during "
|
|
|
|
"runWhileDataLoaded() call";
|
|
|
|
}
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-03-23 22:34:59 +03:00
|
|
|
// We want to update atime after the read operation.
|
2018-06-01 21:16:44 +03:00
|
|
|
self->updateAtimeLocked(*state);
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif // !_WIN32
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
return result;
|
|
|
|
});
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifdef _WIN32
|
|
|
|
void FileInode::materialize() {
|
|
|
|
{
|
|
|
|
auto state = LockedState{this};
|
|
|
|
state.setMaterialized();
|
|
|
|
}
|
|
|
|
|
|
|
|
materializeInParent();
|
|
|
|
auto path = getPath();
|
|
|
|
if (path.has_value()) {
|
|
|
|
getMount()->getJournal().recordChanged(std::move(path.value()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
|
2018-03-22 08:21:16 +03:00
|
|
|
Future<BufVec> FileInode::read(size_t size, off_t off) {
|
2018-12-06 22:33:06 +03:00
|
|
|
DCHECK_GE(off, 0);
|
2018-11-29 04:31:38 +03:00
|
|
|
return runWhileDataLoaded<Future<BufVec>>(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState{this},
|
2018-11-29 04:31:38 +03:00
|
|
|
BlobCache::Interest::WantHandle,
|
2020-02-06 00:13:11 +03:00
|
|
|
// This function is only called by FUSE.
|
|
|
|
ObjectFetchContext::getNullContext(),
|
2020-03-20 20:53:03 +03:00
|
|
|
ImportPriority::kHigh(),
|
2018-11-29 04:31:38 +03:00
|
|
|
nullptr,
|
|
|
|
[size, off, self = inodePtrFromThis()](
|
|
|
|
LockedState&& state, std::shared_ptr<const Blob> blob) -> BufVec {
|
2018-03-23 22:34:59 +03:00
|
|
|
SCOPE_SUCCESS {
|
2018-06-01 21:16:44 +03:00
|
|
|
self->updateAtimeLocked(*state);
|
2018-03-23 22:34:59 +03:00
|
|
|
};
|
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
// Materialized either before or during blob load.
|
2018-03-23 22:34:59 +03:00
|
|
|
if (state->tag == State::MATERIALIZED_IN_OVERLAY) {
|
2019-11-07 05:21:39 +03:00
|
|
|
return self->getOverlayFileAccess(state)->read(*self, size, off);
|
2018-11-29 04:31:38 +03:00
|
|
|
}
|
2018-03-22 08:21:16 +03:00
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
// runWhileDataLoaded() ensures that the state is either
|
|
|
|
// MATERIALIZED_IN_OVERLAY or BLOB_NOT_LOADING
|
|
|
|
DCHECK_EQ(state->tag, State::BLOB_NOT_LOADING);
|
|
|
|
DCHECK(blob) << "blob missing after load completed";
|
2018-12-06 22:33:06 +03:00
|
|
|
|
|
|
|
state->readByteRanges.add(off, off + size);
|
|
|
|
if (state->readByteRanges.covers(0, blob->getSize())) {
|
|
|
|
XLOG(DBG4) << "Inode " << self->getNodeId()
|
|
|
|
<< " dropping interest for blob " << blob->getHash()
|
|
|
|
<< " because it's been fully read.";
|
|
|
|
state->interestHandle.reset();
|
|
|
|
state->readByteRanges.clear();
|
|
|
|
}
|
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
auto buf = blob->getContents();
|
|
|
|
folly::io::Cursor cursor(&buf);
|
2018-03-22 08:21:16 +03:00
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
if (!cursor.canAdvance(off)) {
|
|
|
|
// Seek beyond EOF. Return an empty result.
|
|
|
|
return BufVec{folly::IOBuf::wrapBuffer("", 0)};
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
2018-11-29 04:31:38 +03:00
|
|
|
|
|
|
|
cursor.skip(off);
|
|
|
|
|
|
|
|
std::unique_ptr<folly::IOBuf> result;
|
|
|
|
cursor.cloneAtMost(result, size);
|
|
|
|
|
|
|
|
return BufVec{std::move(result)};
|
2018-03-23 22:34:59 +03:00
|
|
|
});
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
size_t FileInode::writeImpl(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState& state,
|
2018-03-23 22:34:59 +03:00
|
|
|
const struct iovec* iov,
|
|
|
|
size_t numIovecs,
|
|
|
|
off_t off) {
|
2018-03-27 21:13:03 +03:00
|
|
|
DCHECK_EQ(state->tag, State::MATERIALIZED_IN_OVERLAY);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
auto xfer = getOverlayFileAccess(state)->write(*this, iov, numIovecs, off);
|
2017-08-15 09:07:53 +03:00
|
|
|
|
2018-06-01 21:16:44 +03:00
|
|
|
updateMtimeAndCtimeLocked(*state, getNow());
|
2017-08-15 09:07:53 +03:00
|
|
|
|
2018-10-08 21:11:34 +03:00
|
|
|
state.unlock();
|
|
|
|
|
|
|
|
auto myname = getPath();
|
2018-10-24 04:48:38 +03:00
|
|
|
if (myname.has_value()) {
|
2019-06-20 01:12:50 +03:00
|
|
|
getMount()->getJournal().recordChanged(std::move(myname.value()));
|
2018-10-08 21:11:34 +03:00
|
|
|
}
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
return xfer;
|
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
folly::Future<size_t> FileInode::write(BufVec&& buf, off_t off) {
|
|
|
|
return runWhileMaterialized(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState{this},
|
2018-11-29 04:31:38 +03:00
|
|
|
nullptr,
|
2018-03-23 22:34:59 +03:00
|
|
|
[buf = std::move(buf), off, self = inodePtrFromThis()](
|
|
|
|
LockedState&& state) {
|
|
|
|
auto vec = buf.getIov();
|
|
|
|
return self->writeImpl(state, vec.data(), vec.size(), off);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-05 22:08:56 +03:00
|
|
|
folly::Future<size_t> FileInode::write(folly::StringPiece data, off_t off) {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
2017-08-15 09:07:53 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
// If we are currently materialized we don't need to copy the input data.
|
|
|
|
if (state->tag == State::MATERIALIZED_IN_OVERLAY) {
|
|
|
|
struct iovec iov;
|
|
|
|
iov.iov_base = const_cast<char*>(data.data());
|
|
|
|
iov.iov_len = data.size();
|
|
|
|
return writeImpl(state, &iov, 1, off);
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
2017-08-15 09:07:53 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
return runWhileMaterialized(
|
|
|
|
std::move(state),
|
2018-11-29 04:31:38 +03:00
|
|
|
nullptr,
|
2018-03-26 22:38:57 +03:00
|
|
|
[data = data.str(), off, self = inodePtrFromThis()](
|
|
|
|
LockedState&& stateLock) {
|
2018-03-23 22:34:59 +03:00
|
|
|
struct iovec iov;
|
|
|
|
iov.iov_base = const_cast<char*>(data.data());
|
|
|
|
iov.iov_len = data.size();
|
2018-03-26 22:38:57 +03:00
|
|
|
return self->writeImpl(stateLock, &iov, 1, off);
|
2018-03-23 22:34:59 +03:00
|
|
|
});
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
Future<std::shared_ptr<const Blob>> FileInode::startLoadingData(
|
|
|
|
LockedState state,
|
2020-02-06 00:13:11 +03:00
|
|
|
BlobCache::Interest interest,
|
2020-03-17 12:29:57 +03:00
|
|
|
ObjectFetchContext& fetchContext,
|
|
|
|
ImportPriority priority) {
|
2018-11-29 04:31:38 +03:00
|
|
|
DCHECK_EQ(state->tag, State::BLOB_NOT_LOADING);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
// Start the blob load first in case this throws an exception.
|
|
|
|
// Ideally the state transition is no-except in tandem with the
|
|
|
|
// Future's .then call.
|
2020-02-06 00:13:11 +03:00
|
|
|
auto getBlobFuture = getMount()->getBlobAccess()->getBlob(
|
2020-03-17 12:29:57 +03:00
|
|
|
state->hash.value(), fetchContext, interest, priority);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
// Everything from here through blobFuture.then should be noexcept.
|
|
|
|
state->blobLoadingPromise.emplace();
|
|
|
|
auto resultFuture = state->blobLoadingPromise->getFuture();
|
|
|
|
state->tag = State::BLOB_LOADING;
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
// Unlock state_ while we wait on the blob data to load
|
|
|
|
state.unlock();
|
2018-01-04 04:18:32 +03:00
|
|
|
|
2017-11-11 00:23:26 +03:00
|
|
|
auto self = inodePtrFromThis(); // separate line for formatting
|
2018-11-29 04:31:38 +03:00
|
|
|
std::move(getBlobFuture)
|
|
|
|
.thenTry([self](folly::Try<BlobCache::GetResult> tryResult) mutable {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{self};
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2017-12-22 23:27:48 +03:00
|
|
|
switch (state->tag) {
|
2018-11-29 04:31:38 +03:00
|
|
|
case State::BLOB_NOT_LOADING:
|
|
|
|
EDEN_BUG()
|
|
|
|
<< "A blob load finished when the inode was in BLOB_NOT_LOADING state";
|
|
|
|
|
2017-12-22 23:27:48 +03:00
|
|
|
// Since the load doesn't hold the state lock for its duration,
|
|
|
|
// sanity check that the inode is still in loading state.
|
|
|
|
//
|
2018-03-23 22:34:59 +03:00
|
|
|
// Note that someone else may have grabbed the lock before us and
|
|
|
|
// materialized the FileInode, so we may already be
|
|
|
|
// MATERIALIZED_IN_OVERLAY at this point.
|
2018-01-04 04:18:32 +03:00
|
|
|
case State::BLOB_LOADING: {
|
|
|
|
auto promise = std::move(*state->blobLoadingPromise);
|
2018-10-24 04:48:38 +03:00
|
|
|
state->blobLoadingPromise.reset();
|
2018-11-29 04:31:38 +03:00
|
|
|
state->tag = State::BLOB_NOT_LOADING;
|
2017-12-22 23:27:48 +03:00
|
|
|
|
2018-11-29 04:31:38 +03:00
|
|
|
// Call the Future's subscribers while the state_ lock is not
|
|
|
|
// held. Even if the FileInode has transitioned to a materialized
|
|
|
|
// state, any pending loads must be unblocked.
|
|
|
|
if (tryResult.hasValue()) {
|
|
|
|
state->interestHandle = std::move(tryResult->interestHandle);
|
|
|
|
state.unlock();
|
|
|
|
promise.setValue(std::move(tryResult->blob));
|
2017-12-22 23:27:48 +03:00
|
|
|
} else {
|
|
|
|
state.unlock();
|
2018-11-29 04:31:38 +03:00
|
|
|
promise.setException(std::move(tryResult).exception());
|
2017-12-22 23:27:48 +03:00
|
|
|
}
|
2018-11-29 04:31:38 +03:00
|
|
|
return;
|
2018-01-04 04:18:32 +03:00
|
|
|
}
|
2017-12-22 23:27:48 +03:00
|
|
|
|
2018-01-04 04:18:32 +03:00
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
2018-03-23 22:34:59 +03:00
|
|
|
// The load raced with a someone materializing the file to truncate
|
2018-11-29 04:31:38 +03:00
|
|
|
// it. Nothing left to do here. The truncation completed the
|
|
|
|
// promise with a null blob.
|
|
|
|
CHECK_EQ(false, state->blobLoadingPromise.has_value());
|
|
|
|
return;
|
2017-12-22 23:27:48 +03:00
|
|
|
}
|
|
|
|
})
|
2018-10-23 23:39:59 +03:00
|
|
|
.thenError([](folly::exception_wrapper&&) {
|
2017-12-22 23:27:48 +03:00
|
|
|
// We get here if EDEN_BUG() didn't terminate the process, or if we
|
2018-01-04 04:18:32 +03:00
|
|
|
// threw in the preceding block. Both are bad because we won't
|
2017-12-22 23:27:48 +03:00
|
|
|
// automatically propagate the exception to resultFuture and we
|
|
|
|
// can't trust the state of anything if we get here.
|
|
|
|
// Rather than leaving something hanging, we suicide.
|
|
|
|
// We could probably do a bit better with the error handling here :-/
|
|
|
|
XLOG(FATAL)
|
|
|
|
<< "Failed to propagate failure in getBlob(), no choice but to die";
|
|
|
|
});
|
2018-03-23 22:34:59 +03:00
|
|
|
return resultFuture;
|
2017-11-11 00:23:26 +03:00
|
|
|
}
|
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-11-29 04:31:38 +03:00
|
|
|
void FileInode::materializeNow(
|
|
|
|
LockedState& state,
|
|
|
|
std::shared_ptr<const Blob> blob) {
|
|
|
|
// This function should only be called from the BLOB_NOT_LOADING state
|
|
|
|
DCHECK_EQ(state->tag, State::BLOB_NOT_LOADING);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-12-13 03:48:09 +03:00
|
|
|
// If the blob metadata is immediately available, use it to populate the SHA-1
|
|
|
|
// value in the overlay for this file.
|
2018-03-27 21:13:03 +03:00
|
|
|
// Since this uses state->hash we perform this before calling
|
2018-12-13 03:48:09 +03:00
|
|
|
// state.setMaterialized().
|
2020-02-06 00:13:11 +03:00
|
|
|
auto blobSha1Future = getObjectStore()->getBlobSha1(
|
|
|
|
state->hash.value(), ObjectFetchContext::getNullContext());
|
2018-12-13 03:48:09 +03:00
|
|
|
std::optional<Hash> blobSha1;
|
|
|
|
if (blobSha1Future.isReady()) {
|
|
|
|
blobSha1 = blobSha1Future.value();
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
2018-12-13 03:48:09 +03:00
|
|
|
|
|
|
|
getOverlayFileAccess(state)->createFile(getNodeId(), *blob, blobSha1);
|
|
|
|
|
|
|
|
state.setMaterialized();
|
2017-11-11 00:23:26 +03:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
void FileInode::materializeAndTruncate(LockedState& state) {
|
2018-03-23 22:34:59 +03:00
|
|
|
CHECK_NE(state->tag, State::MATERIALIZED_IN_OVERLAY);
|
2018-12-13 03:48:09 +03:00
|
|
|
getOverlayFileAccess(state)->createEmptyFile(getNodeId());
|
|
|
|
state.setMaterialized();
|
2018-03-23 22:34:59 +03:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
void FileInode::truncateInOverlay(LockedState& state) {
|
2018-03-23 22:34:59 +03:00
|
|
|
CHECK_EQ(state->tag, State::MATERIALIZED_IN_OVERLAY);
|
|
|
|
CHECK(!state->hash);
|
|
|
|
|
2019-11-07 05:21:39 +03:00
|
|
|
getOverlayFileAccess(state)->truncate(*this);
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2018-12-13 03:48:09 +03:00
|
|
|
OverlayFileAccess* FileInode::getOverlayFileAccess(LockedState&) const {
|
|
|
|
return getMount()->getOverlayFileAccess();
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif // !_WIN32
|
|
|
|
|
|
|
|
ObjectStore* FileInode::getObjectStore() const {
|
|
|
|
return getMount()->getObjectStore();
|
|
|
|
}
|
2017-07-28 04:12:48 +03:00
|
|
|
|
2017-11-04 01:58:04 +03:00
|
|
|
} // namespace eden
|
|
|
|
} // namespace facebook
|