2016-05-12 23:43:17 +03:00
|
|
|
/*
|
2017-01-21 09:02:33 +03:00
|
|
|
* Copyright (c) 2016-present, Facebook, Inc.
|
2016-05-12 23:43:17 +03:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
*/
|
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/experimental/logging/xlog.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>
|
2017-07-27 09:39:02 +03:00
|
|
|
#include <openssl/sha.h>
|
2018-03-19 22:51:10 +03:00
|
|
|
#include "eden/fs/inodes/EdenFileHandle.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/Overlay.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"
|
2017-12-12 23:23:57 +03:00
|
|
|
#include "eden/fs/utils/UnboundedQueueThreadPool.h"
|
2017-04-14 21:31:48 +03:00
|
|
|
#include "eden/fs/utils/XAttr.h"
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2017-11-04 01:58:04 +03:00
|
|
|
using folly::ByteRange;
|
2018-01-26 22:17:36 +03:00
|
|
|
using folly::checkUnixError;
|
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;
|
2018-03-23 22:34:59 +03:00
|
|
|
using folly::Synchronized;
|
2017-07-27 09:39:02 +03:00
|
|
|
using folly::Unit;
|
2017-03-11 05:28:13 +03:00
|
|
|
using std::shared_ptr;
|
2016-05-12 23:43:17 +03:00
|
|
|
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
|
|
|
|
* LockedPtr. However, it also is capable of managing a reference count
|
|
|
|
* to State::openCount, and decrementing this count when it is destroyed, or
|
|
|
|
* transferring this count to a new EdenFileHandle object.
|
|
|
|
*/
|
|
|
|
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();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unlock the state and create a new EdenFileHandle object.
|
|
|
|
*/
|
|
|
|
std::shared_ptr<EdenFileHandle> unlockAndCreateHandle(FileInodePtr inode);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an EdenFileHandle object.
|
|
|
|
*
|
|
|
|
* Beware that you must pass in an EdenFileHandle object that exists in a
|
|
|
|
* higher-level scope than the LockedState object itself. You must ensure
|
|
|
|
* that the LockedState object is destroyed before the EdenFileHandle
|
|
|
|
* object.
|
|
|
|
*/
|
|
|
|
void createHandleInOuterScope(
|
|
|
|
FileInodePtr inode,
|
|
|
|
std::shared_ptr<EdenFileHandle>* outHandle);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure that state->file is an open File object.
|
|
|
|
*
|
|
|
|
* This method may only be called when the state tag is
|
|
|
|
* MATERIALIZED_IN_OVERLAY.
|
|
|
|
*/
|
|
|
|
void ensureFileOpen(const FileInode* inode);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This moves the file into the MATERIALIZED_IN_OVERLAY state, setting
|
|
|
|
* state->file.
|
|
|
|
*
|
|
|
|
* This updates state->tag and state->file, and clears state->blob,
|
|
|
|
* state-hash, and state->sha1Valid.
|
|
|
|
*
|
|
|
|
* This also implicitly ensures that this LockedState has an open refcount.
|
|
|
|
*/
|
|
|
|
void setMaterialized(folly::File&& file);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Increment the state's open count.
|
|
|
|
*
|
|
|
|
* This should generally be called when setting the blob or file object in
|
|
|
|
* the state, to ensure that the blob or file is destroyed when the state
|
|
|
|
* lock is released if it is not still referenced by an EdenFileHandle
|
|
|
|
* object.
|
|
|
|
*
|
|
|
|
* This reference count will automatically be decremented again when the
|
|
|
|
* LockedState is destroyed. This can only be called at most once on a
|
|
|
|
* LockedState object--it is not valid to call incOpenCount() on a
|
|
|
|
* LockedState that already has a reference count.
|
|
|
|
*/
|
|
|
|
void incOpenCount();
|
|
|
|
|
|
|
|
bool hasOpenCount() const {
|
|
|
|
return hasOpenRefcount_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
folly::Synchronized<State>::LockedPtr ptr_;
|
|
|
|
bool hasOpenRefcount_{false};
|
|
|
|
};
|
|
|
|
|
|
|
|
FileInode::LockedState::~LockedState() {
|
|
|
|
if (!ptr_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (hasOpenRefcount_) {
|
|
|
|
ptr_->decOpenCount();
|
|
|
|
}
|
|
|
|
// Check the state invariants every time we release the lock
|
|
|
|
ptr_->checkInvariants();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::LockedState::unlock() {
|
|
|
|
if (hasOpenRefcount_) {
|
|
|
|
ptr_->decOpenCount();
|
|
|
|
}
|
|
|
|
ptr_->checkInvariants();
|
|
|
|
ptr_.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<EdenFileHandle> FileInode::LockedState::unlockAndCreateHandle(
|
|
|
|
FileInodePtr inode) {
|
|
|
|
std::shared_ptr<EdenFileHandle> handle;
|
|
|
|
createHandleInOuterScope(std::move(inode), &handle);
|
|
|
|
// Beware: creating the EdenFileHandle should be the very last thing we do
|
|
|
|
// before unlocking the state. If we throw an exception after creating the
|
|
|
|
// EdenFileHandle but while still holding the state lock we will deadlock in
|
|
|
|
// the EdenFileHandle destructor, which acquires the state lock.
|
|
|
|
ptr_.unlock();
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::LockedState::createHandleInOuterScope(
|
|
|
|
FileInodePtr inode,
|
|
|
|
std::shared_ptr<EdenFileHandle>* outHandle) {
|
|
|
|
if (!hasOpenRefcount_) {
|
|
|
|
ptr_->incOpenCount();
|
|
|
|
hasOpenRefcount_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ptr_->checkInvariants();
|
|
|
|
*outHandle =
|
|
|
|
std::make_shared<EdenFileHandle>(std::move(inode), &hasOpenRefcount_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::LockedState::incOpenCount() {
|
|
|
|
CHECK(!hasOpenRefcount_);
|
|
|
|
ptr_->incOpenCount();
|
|
|
|
hasOpenRefcount_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::LockedState::ensureFileOpen(const FileInode* inode) {
|
|
|
|
DCHECK(ptr_->isMaterialized())
|
|
|
|
<< "must only be called for materialized files";
|
|
|
|
|
|
|
|
if (!hasOpenRefcount_) {
|
|
|
|
ptr_->incOpenCount();
|
|
|
|
hasOpenRefcount_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ptr_->isFileOpen()) {
|
|
|
|
// When opening a file handle to the file, the openCount is incremented but
|
|
|
|
// the overlay file is not actually opened. Instead, it's opened lazily
|
|
|
|
// here.
|
2018-03-28 22:54:24 +03:00
|
|
|
ptr_->file =
|
|
|
|
inode->getMount()->getOverlay()->openFileNoVerify(inode->getNodeId());
|
2018-03-27 21:13:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::LockedState::setMaterialized(folly::File&& file) {
|
|
|
|
if (!hasOpenRefcount_) {
|
|
|
|
ptr_->incOpenCount();
|
|
|
|
hasOpenRefcount_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ptr_->file = std::move(file);
|
|
|
|
ptr_->hash.reset();
|
|
|
|
ptr_->blob.reset();
|
|
|
|
ptr_->tag = State::MATERIALIZED_IN_OVERLAY;
|
|
|
|
ptr_->sha1Valid = false;
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
********************************************************************/
|
|
|
|
|
|
|
|
template <typename Fn>
|
|
|
|
typename folly::futures::detail::callableResult<FileInode::LockedState, Fn>::
|
|
|
|
Return
|
|
|
|
FileInode::runWhileDataLoaded(LockedState state, Fn&& fn) {
|
|
|
|
auto future = Future<FileHandlePtr>::makeEmpty();
|
|
|
|
switch (state->tag) {
|
|
|
|
case State::BLOB_LOADED:
|
|
|
|
// We can run the function immediately
|
|
|
|
return folly::makeFutureWith(
|
|
|
|
[&] { return std::forward<Fn>(fn)(std::move(state)); });
|
2018-03-27 21:13:03 +03:00
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
|
|
|
// Open the file, then run the function
|
|
|
|
state.ensureFileOpen(this);
|
|
|
|
return folly::makeFutureWith(
|
|
|
|
[&] { return std::forward<Fn>(fn)(std::move(state)); });
|
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;
|
|
|
|
case State::NOT_LOADED:
|
|
|
|
future = startLoadingData(std::move(state));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return future.then([self = inodePtrFromThis(), fn = std::forward<Fn>(fn)](
|
|
|
|
FileHandlePtr /* handle */) mutable {
|
|
|
|
// Simply call runWhileDataLoaded() again when we we finish loading the blob
|
|
|
|
// data. The state should be BLOB_LOADED or MATERIALIZED_IN_OVERLAY this
|
|
|
|
// time around.
|
2018-03-27 21:13:03 +03:00
|
|
|
auto stateLock = LockedState{self};
|
2018-03-23 22:34:59 +03:00
|
|
|
DCHECK(
|
2018-03-26 22:38:57 +03:00
|
|
|
stateLock->tag == State::BLOB_LOADED ||
|
|
|
|
stateLock->tag == State::MATERIALIZED_IN_OVERLAY)
|
|
|
|
<< "unexpected FileInode state after loading: " << stateLock->tag;
|
|
|
|
return self->runWhileDataLoaded(std::move(stateLock), std::forward<Fn>(fn));
|
2018-03-23 22:34:59 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Fn>
|
|
|
|
typename folly::futures::detail::callableResult<FileInode::LockedState, Fn>::
|
|
|
|
Return
|
|
|
|
FileInode::runWhileMaterialized(LockedState state, Fn&& fn) {
|
|
|
|
auto future = Future<FileHandlePtr>::makeEmpty();
|
|
|
|
switch (state->tag) {
|
|
|
|
case State::BLOB_LOADED: {
|
|
|
|
// We have the blob data loaded.
|
|
|
|
// Materialize the file now.
|
|
|
|
materializeNow(state);
|
|
|
|
// 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)}); });
|
|
|
|
}
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
2018-03-27 21:13:03 +03:00
|
|
|
// Open the file, then run the function
|
|
|
|
state.ensureFileOpen(this);
|
2018-03-23 22:34:59 +03:00
|
|
|
return folly::makeFutureWith(
|
|
|
|
[&] { return std::forward<Fn>(fn)(LockedState{std::move(state)}); });
|
|
|
|
case State::BLOB_LOADING:
|
|
|
|
// If we're already loading, latch on to the in-progress load
|
|
|
|
future = state->blobLoadingPromise->getFuture();
|
|
|
|
state.unlock();
|
|
|
|
break;
|
|
|
|
case State::NOT_LOADED:
|
|
|
|
future = startLoadingData(std::move(state));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return future.then([self = inodePtrFromThis(), fn = std::forward<Fn>(fn)](
|
|
|
|
FileHandlePtr /* handle */) mutable {
|
|
|
|
// Simply call runWhileDataLoaded() again when we we finish loading the blob
|
|
|
|
// data. The state should be BLOB_LOADED or MATERIALIZED_IN_OVERLAY this
|
|
|
|
// time around.
|
2018-03-27 21:13:03 +03:00
|
|
|
auto stateLock = LockedState{self};
|
2018-03-23 22:34:59 +03:00
|
|
|
DCHECK(
|
2018-03-26 22:38:57 +03:00
|
|
|
stateLock->tag == State::BLOB_LOADED ||
|
|
|
|
stateLock->tag == State::MATERIALIZED_IN_OVERLAY)
|
|
|
|
<< "unexpected FileInode state after loading: " << stateLock->tag;
|
|
|
|
return self->runWhileMaterialized(
|
|
|
|
std::move(stateLock), std::forward<Fn>(fn));
|
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) {
|
|
|
|
auto future = Future<FileHandlePtr>::makeEmpty();
|
|
|
|
switch (state->tag) {
|
|
|
|
case State::NOT_LOADED:
|
|
|
|
case State::BLOB_LOADED:
|
|
|
|
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.
|
|
|
|
std::shared_ptr<EdenFileHandle> handle;
|
|
|
|
folly::Optional<folly::SharedPromise<FileHandlePtr>> loadingPromise;
|
2018-03-23 22:34:59 +03:00
|
|
|
SCOPE_EXIT {
|
|
|
|
if (loadingPromise) {
|
|
|
|
loadingPromise->setValue(std::move(handle));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
// If we are currently in the BLOB_LOADING state, we first need to create
|
|
|
|
// an EdenFileHandle object to use to fulfill the blobLoadingPromise.
|
|
|
|
// We do this early on so that we cannot fail to create the file handle
|
|
|
|
// after we have successfully materialized the file.
|
|
|
|
//
|
|
|
|
// We move the LockedState object into an inner scope when we do this, to
|
|
|
|
// ensure that the LockedState always gets destroyed before the
|
|
|
|
// EdenFileHandle object. The EdenFileHandle destructor requires
|
|
|
|
// acquiring the state lock itself, so the lock cannot still be held when
|
|
|
|
// the EdenFileHandle destructor runs.
|
|
|
|
{
|
|
|
|
LockedState innerState(std::move(state));
|
|
|
|
if (innerState->tag == State::BLOB_LOADING) {
|
|
|
|
innerState.createHandleInOuterScope(inodePtrFromThis(), &handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call materializeAndTruncate()
|
|
|
|
materializeAndTruncate(innerState);
|
|
|
|
|
|
|
|
// Now that materializeAndTruncate() has succeeded, extract the
|
|
|
|
// blobLoadingPromise so we can fulfill it as we exit.
|
|
|
|
loadingPromise = std::move(innerState->blobLoadingPromise);
|
|
|
|
// Also call materializeInParent() as we exit, before fulfilling the
|
|
|
|
// blobLoadingPromise.
|
|
|
|
SCOPE_EXIT {
|
|
|
|
CHECK(innerState.isNull());
|
|
|
|
materializeInParent();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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(innerState)});
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* FileInode::State methods
|
|
|
|
********************************************************************/
|
|
|
|
|
2017-03-11 05:28:13 +03:00
|
|
|
FileInode::State::State(
|
|
|
|
FileInode* inode,
|
|
|
|
mode_t m,
|
2017-07-27 21:48:19 +03:00
|
|
|
const folly::Optional<Hash>& h,
|
|
|
|
const timespec& lastCheckoutTime)
|
2017-08-15 09:07:54 +03:00
|
|
|
: mode(m), hash(h) {
|
2017-11-21 20:14:31 +03:00
|
|
|
if (!hash.hasValue()) {
|
|
|
|
// File is materialized; read out the timestamps but don't keep it open.
|
2018-03-28 22:54:24 +03:00
|
|
|
//
|
|
|
|
// TODO: This is inefficient for constructors that just created the inode
|
|
|
|
// and already have timestamp info.
|
|
|
|
(void)inode->getMount()->getOverlay()->openFile(
|
|
|
|
inode->getNodeId(), Overlay::kHeaderIdentifierFile, timeStamps);
|
2017-11-20 23:03:28 +03:00
|
|
|
tag = MATERIALIZED_IN_OVERLAY;
|
2017-08-05 06:14:18 +03:00
|
|
|
} else {
|
2018-02-01 23:21:03 +03:00
|
|
|
timeStamps.setAll(lastCheckoutTime);
|
2017-11-20 23:03:28 +03:00
|
|
|
tag = NOT_LOADED;
|
2017-07-17 23:17:42 +03:00
|
|
|
}
|
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
|
|
|
|
2017-03-31 09:38:42 +03:00
|
|
|
FileInode::State::State(
|
2018-03-23 01:17:40 +03:00
|
|
|
FileInode* /*inode*/,
|
2017-03-31 09:38:42 +03:00
|
|
|
mode_t m,
|
2018-01-25 00:17:27 +03:00
|
|
|
const timespec& creationTime)
|
|
|
|
: tag(MATERIALIZED_IN_OVERLAY), mode(m) {
|
2018-02-01 23:21:03 +03:00
|
|
|
timeStamps.setAll(creationTime);
|
2017-11-08 03:07:16 +03:00
|
|
|
checkInvariants();
|
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
/*
|
|
|
|
* Defined State Destructor explicitly to avoid including
|
|
|
|
* some header files in FileInode.h
|
|
|
|
*/
|
|
|
|
FileInode::State::~State() = default;
|
|
|
|
|
2017-11-08 03:07:16 +03:00
|
|
|
void FileInode::State::State::checkInvariants() {
|
2017-11-20 23:03:28 +03:00
|
|
|
switch (tag) {
|
|
|
|
case NOT_LOADED:
|
|
|
|
CHECK(hash);
|
|
|
|
CHECK(!blobLoadingPromise);
|
|
|
|
CHECK(!blob);
|
|
|
|
CHECK(!file);
|
|
|
|
CHECK(!sha1Valid);
|
|
|
|
return;
|
|
|
|
case BLOB_LOADING:
|
|
|
|
CHECK(hash);
|
|
|
|
CHECK(blobLoadingPromise);
|
|
|
|
CHECK(!blob);
|
|
|
|
CHECK(!file);
|
|
|
|
CHECK(!sha1Valid);
|
|
|
|
return;
|
|
|
|
case BLOB_LOADED:
|
|
|
|
CHECK(hash);
|
|
|
|
CHECK(!blobLoadingPromise);
|
|
|
|
CHECK(blob);
|
|
|
|
CHECK(!file);
|
|
|
|
CHECK(!sha1Valid);
|
|
|
|
DCHECK_EQ(blob->getHash(), hash.value());
|
|
|
|
return;
|
|
|
|
case MATERIALIZED_IN_OVERLAY:
|
|
|
|
// 'materialized'
|
|
|
|
CHECK(!hash);
|
|
|
|
CHECK(!blobLoadingPromise);
|
|
|
|
CHECK(!blob);
|
2018-01-04 04:18:30 +03:00
|
|
|
if (file) {
|
|
|
|
CHECK_GT(openCount, 0);
|
|
|
|
}
|
2017-11-21 20:14:31 +03:00
|
|
|
if (openCount == 0) {
|
|
|
|
// file is lazily set, so the only interesting assertion is
|
|
|
|
// that it's not open if openCount is zero.
|
|
|
|
CHECK(!file);
|
|
|
|
}
|
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-27 21:13:03 +03:00
|
|
|
void FileInode::State::incOpenCount() {
|
|
|
|
++openCount;
|
2017-11-21 20:14:31 +03:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
void FileInode::State::decOpenCount() {
|
|
|
|
DCHECK_GT(openCount, 0);
|
|
|
|
--openCount;
|
|
|
|
if (openCount == 0) {
|
|
|
|
switch (tag) {
|
|
|
|
case State::BLOB_LOADED:
|
|
|
|
blob.reset();
|
|
|
|
tag = State::NOT_LOADED;
|
|
|
|
break;
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
|
|
|
// TODO: Before closing the file handle, it might make sense to write
|
|
|
|
// in-memory timestamps into the overlay, even if the inode remains in
|
|
|
|
// memory. This would ensure timestamps persist even if the edenfs
|
|
|
|
// process crashes or otherwise exits without unloading all inodes.
|
|
|
|
file.close();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2017-11-21 20:14:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
/*********************************************************************
|
|
|
|
* FileInode methods
|
|
|
|
********************************************************************/
|
|
|
|
|
2018-01-04 04:18:26 +03:00
|
|
|
std::tuple<FileInodePtr, FileInode::FileHandlePtr> FileInode::create(
|
2018-03-20 03:01:15 +03:00
|
|
|
InodeNumber ino,
|
2017-11-16 11:09:32 +03:00
|
|
|
TreeInodePtr parentInode,
|
|
|
|
PathComponentPiece name,
|
|
|
|
mode_t mode,
|
|
|
|
folly::File&& file,
|
2018-04-25 04:18:37 +03:00
|
|
|
timespec timestamp) {
|
2017-11-20 23:03:28 +03:00
|
|
|
// The FileInode is in MATERIALIZED_IN_OVERLAY state.
|
2018-04-25 04:18:37 +03:00
|
|
|
auto inode = FileInodePtr::makeNew(ino, parentInode, name, mode, timestamp);
|
2018-03-27 21:13:03 +03:00
|
|
|
|
|
|
|
auto state = LockedState{inode};
|
|
|
|
state.incOpenCount();
|
|
|
|
state->file = std::move(file);
|
|
|
|
DCHECK_EQ(state->openCount, 1)
|
|
|
|
<< "open count cannot be anything other than 1";
|
|
|
|
return std::make_tuple(inode, state.unlockAndCreateHandle(inode));
|
2017-11-16 11:09:32 +03:00
|
|
|
}
|
|
|
|
|
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,
|
2017-03-11 05:28:13 +03:00
|
|
|
const folly::Optional<Hash>& hash)
|
2018-04-27 01:51:16 +03:00
|
|
|
: InodeBase(ino, initialMode, std::move(parentInode), name),
|
2017-07-27 21:48:19 +03:00
|
|
|
state_(
|
|
|
|
folly::in_place,
|
|
|
|
this,
|
2018-04-27 01:51:16 +03:00
|
|
|
initialMode,
|
2017-07-27 21:48:19 +03:00
|
|
|
hash,
|
|
|
|
getMount()->getLastCheckoutTime()) {}
|
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-04-25 04:18:37 +03:00
|
|
|
timespec timestamp)
|
2018-04-27 01:51:16 +03:00
|
|
|
: InodeBase(ino, initialMode, std::move(parentInode), name),
|
|
|
|
state_(folly::in_place, this, initialMode, timestamp) {}
|
2016-07-06 05:53:15 +03:00
|
|
|
|
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`.
|
2017-11-04 04:54:57 +03:00
|
|
|
return stat().then(
|
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-03-20 03:01:15 +03:00
|
|
|
folly::Future<Dispatcher::Attr> FileInode::setInodeAttr(
|
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-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-03-27 21:13:03 +03:00
|
|
|
DCHECK(state->isFileOpen());
|
2017-12-05 02:07:44 +03:00
|
|
|
|
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-03-27 21:13:03 +03:00
|
|
|
checkUnixError(
|
|
|
|
ftruncate(state->file.fd(), attr.size + Overlay::kHeaderLength));
|
2017-12-05 02:07:44 +03:00
|
|
|
}
|
2017-08-05 06:14:19 +03:00
|
|
|
|
2018-01-03 03:25:03 +03:00
|
|
|
if (attr.valid & FATTR_MODE) {
|
2017-12-05 02:07:44 +03:00
|
|
|
// The mode data is stored only in inode_->state_.
|
|
|
|
// (We don't set mode bits on the overlay file as that may incorrectly
|
|
|
|
// prevent us from reading or writing the overlay data).
|
|
|
|
// Make sure we preserve the file type bits, and only update
|
|
|
|
// permissions.
|
2018-01-03 03:25:03 +03:00
|
|
|
state->mode = (state->mode & S_IFMT) | (07777 & attr.mode);
|
2017-12-05 02:07:44 +03:00
|
|
|
}
|
2017-08-05 06:14:19 +03:00
|
|
|
|
2017-12-05 02:07:44 +03:00
|
|
|
// Set in-memory timeStamps
|
2018-02-01 23:21:03 +03:00
|
|
|
state->timeStamps.setattrTimes(self->getClock(), attr);
|
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.
|
2018-02-09 06:32:04 +03:00
|
|
|
struct stat overlayStat;
|
2018-03-27 21:13:03 +03:00
|
|
|
checkUnixError(fstat(state->file.fd(), &overlayStat));
|
2018-02-27 23:40:30 +03:00
|
|
|
result.st.st_ino = self->getNodeId().get();
|
2018-02-09 06:32:04 +03:00
|
|
|
result.st.st_size = overlayStat.st_size - Overlay::kHeaderLength;
|
2018-02-16 03:00:44 +03:00
|
|
|
result.st.st_atim = state->timeStamps.atime.toTimespec();
|
|
|
|
result.st.st_ctim = state->timeStamps.ctime.toTimespec();
|
|
|
|
result.st.st_mtim = state->timeStamps.mtime.toTimespec();
|
2017-12-05 02:07:44 +03:00
|
|
|
result.st.st_mode = state->mode;
|
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 {
|
|
|
|
return runWhileMaterialized(std::move(state), setAttrs);
|
|
|
|
}
|
2016-07-02 01:08:47 +03:00
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
folly::Future<std::string> FileInode::readlink() {
|
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!
|
2017-11-04 03:46:03 +03:00
|
|
|
return readAll();
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
void FileInode::fileHandleDidClose() {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
|
|
|
state->decOpenCount();
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
2017-11-08 03:07:16 +03:00
|
|
|
|
2018-02-20 22:16:00 +03:00
|
|
|
folly::Optional<bool> FileInode::isSameAsFast(
|
|
|
|
const Hash& blobID,
|
|
|
|
TreeEntryType entryType) {
|
2017-11-08 03:07:16 +03:00
|
|
|
auto state = state_.rlock();
|
2018-02-20 22:16:00 +03:00
|
|
|
if (entryType != treeEntryTypeFromMode(state->mode)) {
|
2017-07-27 09:39:02 +03:00
|
|
|
return false;
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
2017-03-11 05:28:13 +03:00
|
|
|
|
2017-05-02 04:45:31 +03:00
|
|
|
if (state->hash.hasValue()) {
|
|
|
|
// This file is not materialized, so we can just compare hashes
|
2017-07-27 09:39:02 +03:00
|
|
|
return state->hash.value() == blobID;
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
2017-07-27 09:39:02 +03:00
|
|
|
return folly::none;
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2018-02-20 22:16:00 +03:00
|
|
|
bool FileInode::isSameAs(const Blob& blob, TreeEntryType entryType) {
|
|
|
|
auto result = isSameAsFast(blob.getHash(), entryType);
|
2017-07-27 09:39:02 +03:00
|
|
|
if (result.hasValue()) {
|
|
|
|
return result.value();
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2017-11-10 02:51:44 +03:00
|
|
|
return getSha1().value() == Hash::sha1(&blob.getContents());
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2018-02-20 22:16:00 +03:00
|
|
|
folly::Future<bool> FileInode::isSameAs(
|
|
|
|
const Hash& blobID,
|
|
|
|
TreeEntryType entryType) {
|
|
|
|
auto result = isSameAsFast(blobID, entryType);
|
2017-07-27 09:39:02 +03:00
|
|
|
if (result.hasValue()) {
|
|
|
|
return makeFuture(result.value());
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:58:04 +03:00
|
|
|
return getMount()->getObjectStore()->getBlobMetadata(blobID).then(
|
|
|
|
[self = inodePtrFromThis()](const BlobMetadata& metadata) {
|
2017-11-10 02:51:44 +03:00
|
|
|
return self->getSha1().value() == metadata.sha1;
|
2017-05-02 04:45:31 +03:00
|
|
|
});
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
2017-03-03 01:20:34 +03:00
|
|
|
mode_t FileInode::getMode() const {
|
2017-03-11 05:28:13 +03:00
|
|
|
return state_.rlock()->mode;
|
2017-03-03 01:20:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
mode_t FileInode::getPermissions() const {
|
|
|
|
return (getMode() & 07777);
|
|
|
|
}
|
|
|
|
|
2017-03-11 05:28:13 +03:00
|
|
|
folly::Optional<Hash> FileInode::getBlobHash() const {
|
|
|
|
return state_.rlock()->hash;
|
|
|
|
}
|
|
|
|
|
2018-03-20 03:01:15 +03:00
|
|
|
folly::Future<std::shared_ptr<FileHandle>> FileInode::open(int flags) {
|
2018-02-13 04:35:47 +03:00
|
|
|
if (dtype_t::Symlink == getType()) {
|
|
|
|
// Linux reports ELOOP if you try to open a symlink with O_NOFOLLOW set.
|
|
|
|
// Since it isn't clear whether FUSE will allow this to happen, this
|
|
|
|
// is a speculative defense against that happening; the O_PATH flag
|
|
|
|
// does allow a file handle to be opened on a symlink on Linux,
|
|
|
|
// but does not allow it to be used for real IO operations. We're
|
|
|
|
// punting on handling those situations here for now.
|
|
|
|
throw InodeError(ELOOP, inodePtrFromThis(), "is a symlink");
|
2017-03-02 19:16:18 +03:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
std::shared_ptr<EdenFileHandle> fileHandle;
|
|
|
|
{
|
|
|
|
auto state = LockedState{this};
|
|
|
|
state.createHandleInOuterScope(inodePtrFromThis(), &fileHandle);
|
|
|
|
|
|
|
|
if (flags & O_TRUNC) {
|
|
|
|
// Use truncateAndRun() to truncate the file, materializing it first if
|
|
|
|
// necessary. We don't actually need to run anything, so we pass in a
|
|
|
|
// no-op lambda.
|
|
|
|
(void)truncateAndRun(std::move(state), [](LockedState&&) { return 0; });
|
|
|
|
} else if (flags & (O_RDWR | O_WRONLY | O_CREAT)) {
|
|
|
|
// Call runWhileMaterialized() to begin materializing the data into the
|
|
|
|
// overlay, since the caller will likely want to use it soon since they
|
|
|
|
// have just opened a file handle.
|
|
|
|
//
|
|
|
|
// We don't wait for this to return, though, and we return the file
|
|
|
|
// handle immediately.
|
|
|
|
//
|
|
|
|
// Since we just want to materialize the file and don't need to do
|
|
|
|
// anything else we pass in a no-op lambda function.
|
|
|
|
(void)runWhileMaterialized(
|
|
|
|
std::move(state), [](LockedState&&) { return 0; });
|
|
|
|
} else {
|
|
|
|
// Begin prefetching the data as it's likely to be needed soon.
|
|
|
|
//
|
|
|
|
// Since we just want to load the data and don't need to do anything else
|
|
|
|
// we pass in a no-op lambda function.
|
|
|
|
(void)runWhileDataLoaded(
|
|
|
|
std::move(state), [](LockedState&&) { return 0; });
|
|
|
|
}
|
2016-09-10 02:56:00 +03:00
|
|
|
}
|
2017-12-05 22:08:56 +03:00
|
|
|
|
|
|
|
return fileHandle;
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
Future<vector<string>> FileInode::listxattr() {
|
2016-05-12 23:43:17 +03:00
|
|
|
// Currently, we only return a non-empty vector for regular files, and we
|
|
|
|
// assume that the SHA-1 is present without checking the ObjectStore.
|
|
|
|
vector<string> attributes;
|
2018-02-13 04:35:47 +03:00
|
|
|
if (dtype_t::Regular == getType()) {
|
|
|
|
attributes.emplace_back(kXattrSha1.str());
|
2016-09-10 02:56:00 +03:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2017-11-10 02:51:44 +03:00
|
|
|
return getSha1().then([](Hash hash) { return hash.toString(); });
|
2016-05-28 04:16:30 +03:00
|
|
|
}
|
|
|
|
|
2018-01-31 00:06:52 +03:00
|
|
|
Future<Hash> FileInode::getSha1() {
|
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) {
|
|
|
|
case State::NOT_LOADED:
|
|
|
|
case State::BLOB_LOADING:
|
|
|
|
case State::BLOB_LOADED:
|
|
|
|
// If a file is not materialized it should have a hash value.
|
|
|
|
return getObjectStore()
|
|
|
|
->getBlobMetadata(state->hash.value())
|
|
|
|
.then([](const BlobMetadata& metadata) { return metadata.sha1; });
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY:
|
2018-03-27 21:13:03 +03:00
|
|
|
state.ensureFileOpen(this);
|
2017-11-20 23:03:28 +03:00
|
|
|
if (state->sha1Valid) {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto shaStr = fgetxattr(state->file.fd(), kXattrSha1);
|
2017-11-20 23:03:28 +03:00
|
|
|
if (!shaStr.empty()) {
|
|
|
|
return Hash(shaStr);
|
|
|
|
}
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
2018-03-27 21:13:03 +03:00
|
|
|
return recomputeAndStoreSha1(state);
|
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
|
|
|
}
|
|
|
|
|
2017-11-04 04:54:57 +03:00
|
|
|
folly::Future<struct stat> FileInode::stat() {
|
2018-03-23 22:34:59 +03:00
|
|
|
return runWhileDataLoaded(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState{this}, [self = inodePtrFromThis()](LockedState&& state) {
|
2018-03-23 22:34:59 +03:00
|
|
|
auto st = self->getMount()->initStatData();
|
|
|
|
st.st_nlink = 1;
|
|
|
|
st.st_ino = self->getNodeId().get();
|
|
|
|
|
|
|
|
if (state->tag == State::MATERIALIZED_IN_OVERLAY) {
|
|
|
|
// We are calling fstat only to get the size of the file.
|
|
|
|
struct stat overlayStat;
|
2018-03-27 21:13:03 +03:00
|
|
|
checkUnixError(fstat(state->file.fd(), &overlayStat));
|
2018-03-23 22:34:59 +03:00
|
|
|
|
|
|
|
if (overlayStat.st_size < Overlay::kHeaderLength) {
|
2018-03-28 22:54:24 +03:00
|
|
|
EDEN_BUG() << "Overlay file for " << self->getNodeId()
|
2018-03-23 22:34:59 +03:00
|
|
|
<< " is too short for header: size="
|
|
|
|
<< overlayStat.st_size;
|
|
|
|
}
|
|
|
|
st.st_size = overlayStat.st_size - Overlay::kHeaderLength;
|
|
|
|
} else {
|
|
|
|
// blob is guaranteed set because runWhileDataLoaded() guarantees that
|
2018-03-27 21:13:03 +03:00
|
|
|
// state->tag is either MATERIALIZED_IN_OVERLAY or BLOB_LOADED.
|
2018-03-23 22:34:59 +03:00
|
|
|
DCHECK_EQ(state->tag, State::BLOB_LOADED);
|
|
|
|
CHECK(state->blob);
|
|
|
|
auto buf = state->blob->getContents();
|
|
|
|
st.st_size = buf.computeChainDataLength();
|
|
|
|
|
|
|
|
// NOTE: we don't set rdev to anything special here because we
|
|
|
|
// don't support committing special device nodes.
|
|
|
|
}
|
2018-04-24 23:20:52 +03:00
|
|
|
|
|
|
|
state->timeStamps.applyToStat(st);
|
2018-03-23 22:34:59 +03:00
|
|
|
st.st_mode = state->mode;
|
|
|
|
updateBlockCount(st);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
return st;
|
|
|
|
});
|
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::flush(uint64_t /* lock_owner */) {
|
2017-12-05 22:08:56 +03:00
|
|
|
// This is called by FUSE when a file handle is closed.
|
|
|
|
// https://github.com/libfuse/libfuse/wiki/FAQ#which-method-is-called-on-the-close-system-call
|
2017-07-27 09:39:02 +03:00
|
|
|
// We have no write buffers, so there is nothing for us to flush,
|
|
|
|
// but let's take this opportunity to update the sha1 attribute.
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
2017-11-21 20:14:31 +03:00
|
|
|
if (state->isFileOpen() && !state->sha1Valid) {
|
2018-03-27 21:13:03 +03:00
|
|
|
recomputeAndStoreSha1(state);
|
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};
|
2017-11-21 20:14:31 +03:00
|
|
|
if (!state->isFileOpen()) {
|
2017-07-27 09:39:02 +03:00
|
|
|
// If we don't have an overlay file then we have nothing to sync.
|
|
|
|
return;
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
auto res =
|
|
|
|
#ifndef __APPLE__
|
|
|
|
datasync ? ::fdatasync(state->file.fd()) :
|
|
|
|
#endif
|
|
|
|
::fsync(state->file.fd());
|
|
|
|
checkUnixError(res);
|
|
|
|
|
|
|
|
// let's take this opportunity to update the sha1 attribute.
|
2017-11-20 23:03:28 +03:00
|
|
|
// TODO: A program that issues a series of write() and fsync() syscalls (for
|
|
|
|
// example, when logging to a file), would exhibit quadratic behavior here.
|
|
|
|
// This should either not recompute SHA-1 here or instead remember if the
|
|
|
|
// prior SHA-1 was actually used.
|
2017-07-27 09:39:02 +03:00
|
|
|
if (!state->sha1Valid) {
|
2018-03-27 21:13:03 +03:00
|
|
|
recomputeAndStoreSha1(state);
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
Future<string> FileInode::readAll() {
|
|
|
|
return runWhileDataLoaded(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState{this},
|
2018-03-23 22:34:59 +03:00
|
|
|
[self = inodePtrFromThis()](LockedState&& state) -> Future<string> {
|
|
|
|
std::string result;
|
|
|
|
switch (state->tag) {
|
|
|
|
case State::MATERIALIZED_IN_OVERLAY: {
|
|
|
|
// Note that this code requires a write lock on state_ because the
|
|
|
|
// lseek() call modifies the file offset of the file descriptor.
|
2018-03-27 21:13:03 +03:00
|
|
|
auto rc = lseek(state->file.fd(), Overlay::kHeaderLength, SEEK_SET);
|
2018-03-23 22:34:59 +03:00
|
|
|
folly::checkUnixError(
|
|
|
|
rc, "unable to seek in materialized FileInode");
|
2018-03-27 21:13:03 +03:00
|
|
|
folly::readFile(state->file.fd(), result);
|
2018-03-23 22:34:59 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case State::BLOB_LOADED: {
|
|
|
|
const auto& contentsBuf = state->blob->getContents();
|
|
|
|
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
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
// We want to update atime after the read operation.
|
|
|
|
state->timeStamps.atime = self->getNow();
|
|
|
|
return result;
|
|
|
|
});
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2018-03-22 08:21:16 +03:00
|
|
|
Future<BufVec> FileInode::read(size_t size, off_t off) {
|
2018-03-23 22:34:59 +03:00
|
|
|
return runWhileDataLoaded(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState{this},
|
2018-03-23 22:34:59 +03:00
|
|
|
[size, off, self = inodePtrFromThis()](LockedState&& state) {
|
|
|
|
SCOPE_SUCCESS {
|
|
|
|
state->timeStamps.atime = self->getNow();
|
|
|
|
};
|
|
|
|
|
|
|
|
if (state->tag == State::MATERIALIZED_IN_OVERLAY) {
|
|
|
|
auto buf = folly::IOBuf::createCombined(size);
|
|
|
|
auto res = ::pread(
|
2018-03-27 21:13:03 +03:00
|
|
|
state->file.fd(),
|
2018-03-23 22:34:59 +03:00
|
|
|
buf->writableBuffer(),
|
|
|
|
size,
|
|
|
|
off + Overlay::kHeaderLength);
|
|
|
|
|
|
|
|
checkUnixError(res);
|
|
|
|
buf->append(res);
|
|
|
|
return BufVec{std::move(buf)};
|
|
|
|
} else {
|
2018-03-27 21:13:03 +03:00
|
|
|
// runWhileDataLoaded() ensures that the state is either
|
|
|
|
// MATERIALIZED_IN_OVERLAY or BLOB_LOADED
|
|
|
|
DCHECK_EQ(state->tag, State::BLOB_LOADED);
|
2018-03-23 22:34:59 +03:00
|
|
|
auto buf = state->blob->getContents();
|
|
|
|
folly::io::Cursor cursor(&buf);
|
|
|
|
|
|
|
|
if (!cursor.canAdvance(off)) {
|
|
|
|
// Seek beyond EOF. Return an empty result.
|
|
|
|
return BufVec{folly::IOBuf::wrapBuffer("", 0)};
|
|
|
|
}
|
2018-01-04 04:18:28 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
cursor.skip(off);
|
2018-03-22 08:21:16 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
std::unique_ptr<folly::IOBuf> result;
|
|
|
|
cursor.cloneAtMost(result, size);
|
2018-03-22 08:21:16 +03:00
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
return BufVec{std::move(result)};
|
|
|
|
}
|
|
|
|
});
|
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);
|
|
|
|
DCHECK(state->isFileOpen());
|
2017-07-27 09:39:02 +03:00
|
|
|
|
|
|
|
state->sha1Valid = false;
|
2018-03-23 22:34:59 +03:00
|
|
|
auto xfer =
|
2018-03-27 21:13:03 +03:00
|
|
|
::pwritev(state->file.fd(), iov, numIovecs, off + Overlay::kHeaderLength);
|
2017-07-27 09:39:02 +03:00
|
|
|
checkUnixError(xfer);
|
2017-08-15 09:07:53 +03:00
|
|
|
|
|
|
|
// Update mtime and ctime on write systemcall.
|
2018-01-24 01:48:59 +03:00
|
|
|
const auto now = getNow();
|
2017-12-05 20:55:31 +03:00
|
|
|
state->timeStamps.mtime = now;
|
|
|
|
state->timeStamps.ctime = now;
|
2017-08-15 09:07:53 +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-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-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
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
Future<FileInode::FileHandlePtr> FileInode::startLoadingData(
|
2018-03-27 21:13:03 +03:00
|
|
|
LockedState state) {
|
2018-03-23 22:34:59 +03:00
|
|
|
DCHECK_EQ(state->tag, State::NOT_LOADED);
|
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.
|
|
|
|
auto blobFuture = getObjectStore()->getBlob(state->hash.value());
|
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
|
2017-12-22 23:27:48 +03:00
|
|
|
blobFuture
|
2018-03-27 21:13:03 +03:00
|
|
|
.then([self](folly::Try<std::shared_ptr<const Blob>> tryBlob) mutable {
|
|
|
|
auto state = LockedState{self};
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2017-12-22 23:27:48 +03:00
|
|
|
switch (state->tag) {
|
|
|
|
// 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);
|
2017-12-22 23:27:48 +03:00
|
|
|
state->blobLoadingPromise.clear();
|
|
|
|
|
|
|
|
if (tryBlob.hasValue()) {
|
|
|
|
// Transition to 'loaded' state.
|
2018-03-27 21:13:03 +03:00
|
|
|
state.incOpenCount();
|
2017-12-22 23:27:48 +03:00
|
|
|
state->blob = std::move(tryBlob.value());
|
|
|
|
state->tag = State::BLOB_LOADED;
|
2018-03-27 21:13:03 +03:00
|
|
|
promise.setValue(state.unlockAndCreateHandle(std::move(self)));
|
2017-12-22 23:27:48 +03:00
|
|
|
} else {
|
|
|
|
state->tag = State::NOT_LOADED;
|
|
|
|
// 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.
|
|
|
|
state.unlock();
|
|
|
|
promise.setException(tryBlob.exception());
|
|
|
|
}
|
|
|
|
break;
|
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
|
|
|
|
// it. Nothing left to do here.
|
2017-12-22 23:27:48 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
EDEN_BUG()
|
|
|
|
<< "Inode left in unexpected state after getBlob() completed";
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.onError([](const std::exception&) {
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
void FileInode::materializeNow(LockedState& state) {
|
2018-03-23 22:34:59 +03:00
|
|
|
// This function should only be called from the BLOB_LOADED state
|
|
|
|
DCHECK_EQ(state->tag, State::BLOB_LOADED);
|
|
|
|
CHECK(state->blob);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
// Look up the blob metadata so we can get the blob contents SHA1
|
|
|
|
// Since this uses state->hash we perform this before calling
|
|
|
|
// state.setMaterialized()
|
|
|
|
auto metadata = getObjectStore()->getBlobMetadata(state->hash.value());
|
|
|
|
|
2018-03-28 22:54:24 +03:00
|
|
|
auto file = getMount()->getOverlay()->createOverlayFile(
|
|
|
|
getNodeId(), state->timeStamps, state->blob->getContents());
|
2018-03-27 21:13:03 +03:00
|
|
|
state.setMaterialized(std::move(file));
|
2018-03-23 22:34:59 +03:00
|
|
|
|
|
|
|
// If we have a SHA-1 from the metadata, apply it to the new file. This
|
|
|
|
// saves us from recomputing it again in the case that something opens the
|
|
|
|
// file read/write and closes it without changing it.
|
|
|
|
if (metadata.isReady()) {
|
2018-03-27 21:13:03 +03:00
|
|
|
storeSha1(state, metadata.value().sha1);
|
2018-03-23 22:34:59 +03:00
|
|
|
} else {
|
|
|
|
// Leave the SHA-1 attribute dirty - it is not very likely that a file
|
|
|
|
// will be opened for writing, closed without changing, and then have
|
|
|
|
// its SHA-1 queried via Thrift or xattr. If so, the SHA-1 will be
|
|
|
|
// recomputed as needed. That said, it's perhaps cheaper to hash now
|
|
|
|
// (SHA-1 is hundreds of MB/s) while the data is accessible in the blob
|
|
|
|
// than to read the file out of the overlay later.
|
|
|
|
}
|
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-03-28 22:54:24 +03:00
|
|
|
auto file = getMount()->getOverlay()->createOverlayFile(
|
2018-03-28 22:54:25 +03:00
|
|
|
getNodeId(), state->timeStamps, ByteRange{});
|
2018-03-27 21:13:03 +03:00
|
|
|
state.setMaterialized(std::move(file));
|
|
|
|
storeSha1(state, Hash::sha1(ByteRange{}));
|
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);
|
|
|
|
CHECK(!state->blob);
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
state.ensureFileOpen(this);
|
|
|
|
checkUnixError(ftruncate(state->file.fd(), 0 + Overlay::kHeaderLength));
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
ObjectStore* FileInode::getObjectStore() const {
|
|
|
|
return getMount()->getObjectStore();
|
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
Hash FileInode::recomputeAndStoreSha1(const LockedState& state) {
|
|
|
|
DCHECK_EQ(state->tag, State::MATERIALIZED_IN_OVERLAY);
|
|
|
|
DCHECK(state->isFileOpen());
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
uint8_t buf[8192];
|
|
|
|
off_t off = Overlay::kHeaderLength;
|
|
|
|
SHA_CTX ctx;
|
|
|
|
SHA1_Init(&ctx);
|
|
|
|
|
|
|
|
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.
|
2018-03-27 21:13:03 +03:00
|
|
|
auto len = folly::preadNoInt(state->file.fd(), buf, sizeof(buf), off);
|
2017-07-27 09:39:02 +03:00
|
|
|
if (len == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (len == -1) {
|
|
|
|
folly::throwSystemError();
|
|
|
|
}
|
|
|
|
SHA1_Update(&ctx, buf, len);
|
|
|
|
off += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
|
|
|
SHA1_Final(digest, &ctx);
|
|
|
|
auto sha1 = Hash(folly::ByteRange(digest, sizeof(digest)));
|
2018-03-27 21:13:03 +03:00
|
|
|
storeSha1(state, sha1);
|
2017-07-27 09:39:02 +03:00
|
|
|
return sha1;
|
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:03 +03:00
|
|
|
void FileInode::storeSha1(const LockedState& state, Hash sha1) {
|
|
|
|
DCHECK_EQ(state->tag, State::MATERIALIZED_IN_OVERLAY);
|
|
|
|
DCHECK(state->isFileOpen());
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
try {
|
2018-03-27 21:13:03 +03:00
|
|
|
fsetxattr(state->file.fd(), kXattrSha1, sha1.toString());
|
2017-07-27 09:39:02 +03:00
|
|
|
state->sha1Valid = true;
|
|
|
|
} catch (const std::exception& ex) {
|
|
|
|
// If something goes wrong storing the attribute just log a warning
|
|
|
|
// and leave sha1Valid as false. We'll have to recompute the value
|
|
|
|
// next time we need it.
|
|
|
|
XLOG(WARNING) << "error setting SHA1 attribute in the overlay: "
|
|
|
|
<< folly::exceptionStr(ex);
|
|
|
|
}
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
2017-07-28 04:12:48 +03:00
|
|
|
|
2017-11-04 03:46:03 +03:00
|
|
|
// Gets the in-memory timestamps of the inode.
|
2018-02-01 23:21:03 +03:00
|
|
|
InodeTimestamps FileInode::getTimestamps() const {
|
|
|
|
return state_.rlock()->timeStamps;
|
2017-07-28 04:12:48 +03:00
|
|
|
}
|
2017-08-05 06:14:18 +03:00
|
|
|
|
2017-09-19 20:59:46 +03:00
|
|
|
folly::Future<folly::Unit> FileInode::prefetch() {
|
|
|
|
// Careful to only hold the lock while fetching a copy of the hash.
|
|
|
|
return folly::via(getMount()->getThreadPool().get()).then([this] {
|
|
|
|
if (auto hash = state_.rlock()->hash) {
|
|
|
|
getObjectStore()->getBlobMetadata(*hash);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:01 +03:00
|
|
|
void FileInode::updateOverlayHeader() {
|
2018-03-27 21:13:03 +03:00
|
|
|
auto state = LockedState{this};
|
2017-11-21 20:14:31 +03:00
|
|
|
if (state->isMaterialized()) {
|
2018-03-27 21:13:03 +03:00
|
|
|
state.ensureFileOpen(this);
|
|
|
|
Overlay::updateTimestampToHeader(state->file.fd(), state->timeStamps);
|
2017-08-05 06:14:18 +03:00
|
|
|
}
|
|
|
|
}
|
2017-11-04 01:58:04 +03:00
|
|
|
} // namespace eden
|
|
|
|
} // namespace facebook
|