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>
|
|
|
|
#include <openssl/sha.h>
|
|
|
|
#include "eden/fs/fuse/MountPoint.h"
|
2017-05-02 04:45:31 +03:00
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
|
|
#include "eden/fs/inodes/FileHandle.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-04-14 21:31:48 +03:00
|
|
|
#include "eden/fs/utils/XAttr.h"
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2017-03-11 05:28:13 +03:00
|
|
|
using folly::checkUnixError;
|
2016-05-12 23:43:17 +03:00
|
|
|
using folly::Future;
|
2017-03-11 05:28:13 +03:00
|
|
|
using folly::makeFuture;
|
2016-05-12 23:43:17 +03:00
|
|
|
using folly::StringPiece;
|
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;
|
2017-07-27 09:39:02 +03:00
|
|
|
using folly::ByteRange;
|
2016-05-12 23:43:17 +03:00
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
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-07-17 23:17:42 +03:00
|
|
|
if (!h.hasValue()) {
|
2017-08-05 06:14:18 +03:00
|
|
|
// File is materialized
|
2017-07-17 23:17:42 +03:00
|
|
|
auto filePath = inode->getLocalPath();
|
2017-08-11 21:34:52 +03:00
|
|
|
file = Overlay::openFile(
|
|
|
|
filePath.c_str(), Overlay::kHeaderIdentifierFile, timeStamps);
|
2017-08-05 06:14:18 +03:00
|
|
|
} else {
|
2017-08-11 21:34:52 +03:00
|
|
|
timeStamps.setTimestampValues(lastCheckoutTime);
|
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(
|
|
|
|
FileInode* inode,
|
|
|
|
mode_t m,
|
|
|
|
folly::File&& file,
|
2017-07-27 21:48:19 +03:00
|
|
|
const timespec& lastCheckoutTime,
|
2017-03-31 09:38:42 +03:00
|
|
|
dev_t rdev)
|
2017-07-27 09:39:02 +03:00
|
|
|
: mode(m),
|
2017-04-04 01:47:54 +03:00
|
|
|
rdev(rdev),
|
2017-07-27 21:48:19 +03:00
|
|
|
file(std::move(file)) {
|
2017-08-11 21:34:52 +03:00
|
|
|
timeStamps.setTimestampValues(lastCheckoutTime);
|
2017-07-27 21:48:19 +03:00
|
|
|
}
|
2017-07-17 23:17:42 +03:00
|
|
|
/*
|
|
|
|
* Defined State Destructor explicitly to avoid including
|
|
|
|
* some header files in FileInode.h
|
|
|
|
*/
|
|
|
|
FileInode::State::~State() = default;
|
2017-03-11 05:28:13 +03:00
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
FileInode::FileInode(
|
2016-05-12 23:43:17 +03:00
|
|
|
fuse_ino_t ino,
|
2016-12-13 04:48:45 +03:00
|
|
|
TreeInodePtr parentInode,
|
2016-12-13 04:48:43 +03:00
|
|
|
PathComponentPiece name,
|
2017-03-11 05:28:13 +03:00
|
|
|
mode_t mode,
|
|
|
|
const folly::Optional<Hash>& hash)
|
2016-12-23 02:35:01 +03:00
|
|
|
: InodeBase(ino, std::move(parentInode), name),
|
2017-07-27 21:48:19 +03:00
|
|
|
state_(
|
|
|
|
folly::in_place,
|
|
|
|
this,
|
|
|
|
mode,
|
|
|
|
hash,
|
|
|
|
getMount()->getLastCheckoutTime()) {}
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
FileInode::FileInode(
|
2016-07-06 05:53:15 +03:00
|
|
|
fuse_ino_t ino,
|
2016-12-13 04:48:45 +03:00
|
|
|
TreeInodePtr parentInode,
|
2016-12-13 04:48:43 +03:00
|
|
|
PathComponentPiece name,
|
2017-03-11 05:28:13 +03:00
|
|
|
mode_t mode,
|
2017-03-31 09:38:42 +03:00
|
|
|
folly::File&& file,
|
|
|
|
dev_t rdev)
|
2016-12-23 02:35:01 +03:00
|
|
|
: InodeBase(ino, std::move(parentInode), name),
|
2017-07-27 21:48:19 +03:00
|
|
|
state_(
|
|
|
|
folly::in_place,
|
|
|
|
this,
|
|
|
|
mode,
|
|
|
|
std::move(file),
|
|
|
|
getMount()->getLastCheckoutTime(),
|
|
|
|
rdev) {}
|
2016-07-06 05:53:15 +03:00
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
folly::Future<fusell::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-07-27 09:39:02 +03:00
|
|
|
return ensureDataLoaded().then([self = inodePtrFromThis()]() {
|
2017-03-11 05:28:13 +03:00
|
|
|
auto attr = fusell::Dispatcher::Attr{self->getMount()->getMountPoint()};
|
2017-07-27 09:39:02 +03:00
|
|
|
attr.st = self->stat();
|
2017-03-11 05:28:13 +03:00
|
|
|
attr.st.st_ino = self->getNodeId();
|
|
|
|
return attr;
|
|
|
|
});
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
|
2017-08-14 23:34:14 +03:00
|
|
|
folly::Future<fusell::Dispatcher::Attr> FileInode::setInodeAttr(
|
2016-07-02 01:08:47 +03:00
|
|
|
const struct stat& attr,
|
|
|
|
int to_set) {
|
2017-03-11 05:28:13 +03:00
|
|
|
int openFlags = O_RDWR;
|
2016-07-02 01:08:47 +03:00
|
|
|
|
|
|
|
// Minor optimization: if we know that the file is being completed truncated
|
|
|
|
// as part of this operation, there's no need to fetch the underlying data,
|
|
|
|
// so pass on the truncate flag our underlying open call
|
2016-07-06 05:53:16 +03:00
|
|
|
if ((to_set & FUSE_SET_ATTR_SIZE) && attr.st_size == 0) {
|
2017-03-11 05:28:13 +03:00
|
|
|
openFlags |= O_TRUNC;
|
2016-07-02 01:08:47 +03:00
|
|
|
}
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
return materializeForWrite(openFlags).then(
|
|
|
|
[ self = inodePtrFromThis(), attr, to_set ]() {
|
2017-03-11 05:28:13 +03:00
|
|
|
self->materializeInParent();
|
2016-07-02 01:08:47 +03:00
|
|
|
|
2017-03-11 05:28:13 +03:00
|
|
|
auto result =
|
|
|
|
fusell::Dispatcher::Attr{self->getMount()->getMountPoint()};
|
2017-08-05 06:14:19 +03:00
|
|
|
|
|
|
|
auto state = self->state_.wlock();
|
|
|
|
CHECK(state->file) << "MUST have a materialized file at this point";
|
|
|
|
|
|
|
|
// Set the size of the file when FUSE_SET_ATTR_SIZE is set
|
|
|
|
if (to_set & FUSE_SET_ATTR_SIZE) {
|
|
|
|
checkUnixError(ftruncate(
|
|
|
|
state->file.fd(), attr.st_size + Overlay::kHeaderLength));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (to_set & FUSE_SET_ATTR_MODE) {
|
|
|
|
// 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.
|
|
|
|
state->mode = (state->mode & S_IFMT) | (07777 & attr.st_mode);
|
|
|
|
}
|
|
|
|
|
2017-08-14 23:34:14 +03:00
|
|
|
// Set in-memory timeStamps
|
|
|
|
self->setattrTimes(attr, to_set, state->timeStamps);
|
2017-08-05 06:14:19 +03:00
|
|
|
|
2017-08-14 23:34:14 +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
|
|
|
|
// when FUSE_SET_ATTR_SIZE flag is set but when the flag is not set we
|
|
|
|
// have to return the correct size of the file even if some size is sent
|
|
|
|
// in attr.st.st_size.
|
2017-08-05 06:14:19 +03:00
|
|
|
checkUnixError(fstat(state->file.fd(), &result.st));
|
2017-03-11 05:28:13 +03:00
|
|
|
result.st.st_ino = self->getNodeId();
|
2017-08-14 23:34:14 +03:00
|
|
|
result.st.st_size -= Overlay::kHeaderLength;
|
|
|
|
result.st.st_atim = state->timeStamps.atime;
|
|
|
|
result.st.st_ctim = state->timeStamps.ctime;
|
|
|
|
result.st.st_mtim = state->timeStamps.mtime;
|
|
|
|
result.st.st_mode = state->mode;
|
2016-09-19 22:48:11 +03:00
|
|
|
|
2017-08-14 23:34:14 +03:00
|
|
|
// Update the Journal
|
|
|
|
self->updateJournal();
|
2017-03-11 05:28:13 +03:00
|
|
|
return result;
|
|
|
|
});
|
2016-07-02 01:08:47 +03:00
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
folly::Future<std::string> FileInode::readlink() {
|
2017-03-02 19:16:18 +03:00
|
|
|
{
|
2017-03-11 05:28:13 +03:00
|
|
|
auto state = state_.wlock();
|
|
|
|
if (!S_ISLNK(state->mode)) {
|
2017-03-02 19:16:18 +03:00
|
|
|
// 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-07-27 09:39:02 +03:00
|
|
|
return ensureDataLoaded().then([self = inodePtrFromThis()]() {
|
|
|
|
return self->readAll();
|
|
|
|
});
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
void FileInode::fileHandleDidClose() {
|
2017-03-11 05:28:13 +03:00
|
|
|
{
|
2017-07-27 09:39:02 +03:00
|
|
|
// TODO(T20329170): We might need this function in the Future if we decide
|
|
|
|
// to write in memory timestamps to overlay file on
|
|
|
|
// file handle close.
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
}
|
2016-12-01 02:48:04 +03:00
|
|
|
AbsolutePath FileInode::getLocalPath() const {
|
2017-02-11 01:16:00 +03:00
|
|
|
return getMount()->getOverlay()->getFilePath(getNodeId());
|
2016-05-17 00:48:28 +03:00
|
|
|
}
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
folly::Optional<bool> FileInode::isSameAsFast(const Hash& blobID, mode_t mode) {
|
2017-02-16 07:31:48 +03:00
|
|
|
// When comparing mode bits, we only care about the
|
|
|
|
// file type and owner permissions.
|
|
|
|
auto relevantModeBits = [](mode_t m) { return (m & (S_IFMT | S_IRWXU)); };
|
|
|
|
|
2017-05-02 04:45:31 +03:00
|
|
|
auto state = state_.wlock();
|
|
|
|
if (relevantModeBits(state->mode) != relevantModeBits(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
|
|
|
}
|
|
|
|
|
|
|
|
bool FileInode::isSameAs(const Blob& blob, mode_t mode) {
|
|
|
|
auto result = isSameAsFast(blob.getHash(), mode);
|
2017-07-27 09:39:02 +03:00
|
|
|
if (result.hasValue()) {
|
|
|
|
return result.value();
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
return getSHA1().value() == Hash::sha1(&blob.getContents());
|
2017-05-02 04:45:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
folly::Future<bool> FileInode::isSameAs(const Hash& blobID, mode_t mode) {
|
|
|
|
auto result = isSameAsFast(blobID, mode);
|
2017-07-27 09:39:02 +03:00
|
|
|
if (result.hasValue()) {
|
|
|
|
return makeFuture(result.value());
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
2017-05-02 04:45:31 +03:00
|
|
|
return getMount()
|
|
|
|
->getObjectStore()
|
|
|
|
->getBlobMetadata(blobID)
|
|
|
|
.then([self = inodePtrFromThis()](const BlobMetadata& metadata) {
|
2017-07-27 09:39:02 +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;
|
|
|
|
}
|
|
|
|
|
2016-12-01 02:48:04 +03:00
|
|
|
folly::Future<std::shared_ptr<fusell::FileHandle>> FileInode::open(
|
2016-05-12 23:43:17 +03:00
|
|
|
const struct fuse_file_info& fi) {
|
2017-03-11 05:28:13 +03:00
|
|
|
// TODO: We currently should ideally call fileHandleDidClose() if we fail
|
|
|
|
// to create a FileHandle. It's currently slightly tricky to do this right
|
|
|
|
// on all code paths.
|
|
|
|
//
|
|
|
|
// I think it will be better in the long run to just refactor how we do this.
|
|
|
|
// fileHandleDidClose() currently uses std::shared_ptr::unique(), which is
|
|
|
|
// deprecated in future versions of C++.
|
|
|
|
#if 0
|
2016-05-18 03:22:22 +03:00
|
|
|
SCOPE_EXIT {
|
|
|
|
fileHandleDidClose();
|
|
|
|
};
|
2017-03-11 05:28:13 +03:00
|
|
|
#endif
|
2017-03-02 19:16:18 +03:00
|
|
|
|
|
|
|
{
|
2017-03-11 05:28:13 +03:00
|
|
|
auto state = state_.wlock();
|
2017-03-02 19:16:18 +03:00
|
|
|
|
2017-03-11 05:28:13 +03:00
|
|
|
if (S_ISLNK(state->mode)) {
|
2017-03-02 19:16:18 +03:00
|
|
|
// 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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-10 02:56:00 +03:00
|
|
|
if (fi.flags & (O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
|
2017-07-27 09:39:02 +03:00
|
|
|
return materializeForWrite(fi.flags).then(
|
|
|
|
[ self = inodePtrFromThis(), flags = fi.flags ]() {
|
2017-03-11 05:28:13 +03:00
|
|
|
self->materializeInParent();
|
|
|
|
return shared_ptr<fusell::FileHandle>{
|
2017-07-27 09:39:02 +03:00
|
|
|
std::make_shared<FileHandle>(self, flags)};
|
2017-03-11 05:28:13 +03:00
|
|
|
});
|
2016-09-10 02:56:00 +03:00
|
|
|
} else {
|
2017-07-27 09:39:02 +03:00
|
|
|
return ensureDataLoaded().then(
|
|
|
|
[ self = inodePtrFromThis(), flags = fi.flags ]() {
|
2017-03-11 05:28:13 +03:00
|
|
|
return shared_ptr<fusell::FileHandle>{
|
2017-07-27 09:39:02 +03:00
|
|
|
std::make_shared<FileHandle>(self, flags)};
|
2017-03-11 05:28:13 +03:00
|
|
|
});
|
2016-09-10 02:56:00 +03:00
|
|
|
}
|
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) {
|
|
|
|
loc.parent->childMaterialized(renameLock, loc.name, getNodeId());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-02 04:49:43 +03:00
|
|
|
std::shared_ptr<FileHandle> FileInode::finishCreate() {
|
2016-09-10 02:56:00 +03:00
|
|
|
SCOPE_EXIT {
|
|
|
|
fileHandleDidClose();
|
|
|
|
};
|
2017-07-27 09:39:02 +03:00
|
|
|
return std::make_shared<FileHandle>(inodePtrFromThis(), 0);
|
2016-09-10 02:56:00 +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;
|
|
|
|
|
2017-03-11 05:28:13 +03:00
|
|
|
{
|
|
|
|
auto state = state_.rlock();
|
|
|
|
if (S_ISREG(state->mode)) {
|
|
|
|
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-03-11 05:28:13 +03:00
|
|
|
return getSHA1().then([](Hash hash) { return hash.toString(); });
|
2016-05-28 04:16:30 +03:00
|
|
|
}
|
|
|
|
|
2017-03-21 21:53:15 +03:00
|
|
|
Future<Hash> FileInode::getSHA1(bool failIfSymlink) {
|
2017-07-27 09:39:02 +03:00
|
|
|
auto state = state_.wlock();
|
|
|
|
if (failIfSymlink && !S_ISREG(state->mode)) {
|
|
|
|
// We only define a SHA-1 value for regular files
|
|
|
|
return makeFuture<Hash>(InodeError(kENOATTR, inodePtrFromThis()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->hash.hasValue()) {
|
|
|
|
// If a file is not materialized it should have a hash value.
|
|
|
|
return getObjectStore()->getSha1ForBlob(state->hash.value());
|
|
|
|
} else if (state->file) {
|
|
|
|
// If the file is materialized.
|
|
|
|
if (state->sha1Valid) {
|
|
|
|
auto shaStr = fgetxattr(state->file.fd(), kXattrSha1);
|
|
|
|
if (!shaStr.empty()) {
|
|
|
|
return Hash(shaStr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return recomputeAndStoreSha1(state);
|
|
|
|
} else {
|
|
|
|
auto bug = EDEN_BUG()
|
|
|
|
<< "One of state->hash and state->file must be set for the Inode :: "
|
|
|
|
<< getNodeId() << " :Blob is " << (state->blob ? "not " : "") << "Null";
|
|
|
|
return folly::makeFuture<Hash>(bug.toException());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct stat FileInode::stat() {
|
|
|
|
auto st = getMount()->getMountPoint()->initStatData();
|
|
|
|
st.st_nlink = 1;
|
|
|
|
|
|
|
|
auto state = state_.rlock();
|
|
|
|
|
|
|
|
if (state->file) {
|
2017-08-15 09:07:53 +03:00
|
|
|
// We are calling fstat only to get the size of the file.
|
2017-07-27 09:39:02 +03:00
|
|
|
checkUnixError(fstat(state->file.fd(), &st));
|
|
|
|
|
|
|
|
if (st.st_size < Overlay::kHeaderLength) {
|
|
|
|
auto filePath = getLocalPath();
|
|
|
|
EDEN_BUG() << "Overlay file " << getLocalPath()
|
|
|
|
<< " is too short for header: size=" << st.st_size;
|
2017-03-11 05:28:13 +03:00
|
|
|
}
|
2017-07-27 09:39:02 +03:00
|
|
|
st.st_size -= Overlay::kHeaderLength;
|
|
|
|
st.st_rdev = state->rdev;
|
2017-08-15 09:07:53 +03:00
|
|
|
} else {
|
|
|
|
CHECK(state->blob);
|
|
|
|
auto buf = state->blob->getContents();
|
|
|
|
st.st_size = buf.computeChainDataLength();
|
2017-07-27 09:39:02 +03:00
|
|
|
|
2017-08-15 09:07:53 +03:00
|
|
|
// NOTE: we don't set rdev to anything special here because we
|
|
|
|
// don't support committing special device nodes.
|
2016-09-10 02:56:00 +03:00
|
|
|
}
|
2017-07-27 09:39:02 +03:00
|
|
|
#if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || \
|
|
|
|
_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700
|
2017-08-15 09:07:53 +03:00
|
|
|
st.st_atim = state->timeStamps.atime;
|
|
|
|
st.st_ctim = state->timeStamps.ctime;
|
|
|
|
st.st_mtim = state->timeStamps.mtime;
|
|
|
|
#else
|
|
|
|
st.st_atime = state->timeStamps.atime.tv_sec;
|
|
|
|
st.st_mtime = state->timeStamps.mtime.tv_sec;
|
|
|
|
st.st_ctime = state->timeStamps.ctime.tv_sec;
|
2017-07-27 09:39:02 +03:00
|
|
|
#endif
|
2017-08-15 09:07:53 +03:00
|
|
|
st.st_mode = state->mode;
|
2017-07-27 09:39:02 +03:00
|
|
|
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::flush(uint64_t /* lock_owner */) {
|
|
|
|
// We have no write buffers, so there is nothing for us to flush,
|
|
|
|
// but let's take this opportunity to update the sha1 attribute.
|
|
|
|
auto state = state_.wlock();
|
|
|
|
if (state->file && !state->sha1Valid) {
|
|
|
|
recomputeAndStoreSha1(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::fsync(bool datasync) {
|
|
|
|
auto state = state_.wlock();
|
|
|
|
if (!state->file) {
|
|
|
|
// 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.
|
|
|
|
if (!state->sha1Valid) {
|
|
|
|
recomputeAndStoreSha1(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<folly::IOBuf> FileInode::readIntoBuffer(
|
|
|
|
size_t size,
|
|
|
|
off_t off) {
|
2017-08-15 09:07:53 +03:00
|
|
|
SCOPE_SUCCESS {
|
|
|
|
// We do not want to have a write lock on state_ during the entire io
|
|
|
|
// operation(read) for updating in memory timeStamps as it will block
|
|
|
|
// concurrent reads. Also, we want to update the timeStamps after the read
|
|
|
|
// operation, so in order to achieve the above two, we are using
|
|
|
|
// SCOPE_SUCCESS macro here
|
|
|
|
|
|
|
|
// Update atime on read systemcall.
|
|
|
|
auto state = state_.wlock();
|
|
|
|
clock_gettime(CLOCK_REALTIME, &state->timeStamps.atime);
|
|
|
|
};
|
2017-07-27 09:39:02 +03:00
|
|
|
auto state = state_.rlock();
|
|
|
|
|
|
|
|
if (state->file) {
|
|
|
|
auto buf = folly::IOBuf::createCombined(size);
|
|
|
|
auto res = ::pread(
|
|
|
|
state->file.fd(),
|
|
|
|
buf->writableBuffer(),
|
|
|
|
size,
|
|
|
|
off + Overlay::kHeaderLength);
|
|
|
|
|
|
|
|
checkUnixError(res);
|
|
|
|
buf->append(res);
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto buf = state->blob->getContents();
|
|
|
|
folly::io::Cursor cursor(&buf);
|
|
|
|
|
|
|
|
if (!cursor.canAdvance(off)) {
|
|
|
|
// Seek beyond EOF. Return an empty result.
|
|
|
|
return folly::IOBuf::wrapBuffer("", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.skip(off);
|
|
|
|
|
|
|
|
std::unique_ptr<folly::IOBuf> result;
|
|
|
|
cursor.cloneAtMost(result, size);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string FileInode::readAll() {
|
2017-08-01 05:04:55 +03:00
|
|
|
// We need to take the wlock instead of the rlock because the lseek() call
|
|
|
|
// modifies the file offset of the file descriptor.
|
|
|
|
auto state = state_.wlock();
|
2017-08-15 09:07:53 +03:00
|
|
|
std::string result;
|
2017-07-27 09:39:02 +03:00
|
|
|
if (state->file) {
|
|
|
|
auto rc = lseek(state->file.fd(), Overlay::kHeaderLength, SEEK_SET);
|
|
|
|
folly::checkUnixError(rc, "unable to seek in materialized FileData");
|
|
|
|
folly::readFile(state->file.fd(), result);
|
2017-08-15 09:07:53 +03:00
|
|
|
} else {
|
|
|
|
const auto& contentsBuf = state->blob->getContents();
|
|
|
|
folly::io::Cursor cursor(&contentsBuf);
|
|
|
|
result = cursor.readFixedString(contentsBuf.computeChainDataLength());
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
2017-08-15 09:07:53 +03:00
|
|
|
// We want to update atime after the read operation.
|
|
|
|
clock_gettime(CLOCK_REALTIME, &state->timeStamps.atime);
|
|
|
|
return result;
|
2017-07-27 09:39:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fusell::BufVec FileInode::read(size_t size, off_t off) {
|
|
|
|
auto buf = readIntoBuffer(size, off);
|
|
|
|
return fusell::BufVec(std::move(buf));
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FileInode::write(fusell::BufVec&& buf, off_t off) {
|
|
|
|
auto state = state_.wlock();
|
2017-08-15 09:07:53 +03:00
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
if (!state->file) {
|
|
|
|
// Not open for write
|
|
|
|
folly::throwSystemErrorExplicit(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
state->sha1Valid = false;
|
|
|
|
auto vec = buf.getIov();
|
|
|
|
auto xfer = ::pwritev(
|
|
|
|
state->file.fd(), vec.data(), vec.size(), off + Overlay::kHeaderLength);
|
|
|
|
checkUnixError(xfer);
|
2017-08-15 09:07:53 +03:00
|
|
|
|
|
|
|
// Update mtime and ctime on write systemcall.
|
|
|
|
clock_gettime(CLOCK_REALTIME, &state->timeStamps.mtime);
|
|
|
|
state->timeStamps.ctime = state->timeStamps.mtime;
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
return xfer;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FileInode::write(folly::StringPiece data, off_t off) {
|
|
|
|
auto state = state_.wlock();
|
2017-08-15 09:07:53 +03:00
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
if (!state->file) {
|
|
|
|
// Not open for write
|
|
|
|
folly::throwSystemErrorExplicit(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
state->sha1Valid = false;
|
|
|
|
auto xfer = ::pwrite(
|
|
|
|
state->file.fd(), data.data(), data.size(), off + Overlay::kHeaderLength);
|
|
|
|
checkUnixError(xfer);
|
2017-08-15 09:07:53 +03:00
|
|
|
|
|
|
|
// Update mtime and ctime on write systemcall.
|
|
|
|
clock_gettime(CLOCK_REALTIME, &state->timeStamps.mtime);
|
|
|
|
state->timeStamps.ctime = state->timeStamps.mtime;
|
|
|
|
|
2017-07-27 09:39:02 +03:00
|
|
|
return xfer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Unit> FileInode::ensureDataLoaded() {
|
|
|
|
auto state = state_.wlock();
|
|
|
|
|
|
|
|
if (!state->hash.hasValue()) {
|
|
|
|
// We should always have the file open if we are materialized.
|
|
|
|
CHECK(state->file);
|
|
|
|
return makeFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->blob) {
|
|
|
|
DCHECK_EQ(state->blob->getHash(), state->hash.value());
|
|
|
|
return makeFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the blob data.
|
|
|
|
auto blobFuture = getObjectStore()->getBlob(state->hash.value());
|
|
|
|
|
|
|
|
// TODO: We really should defer this using a Future rather than calling get()
|
|
|
|
// here and blocking until the load completes. However, for that to work we
|
|
|
|
// will need to add some extra data tracking whether or not we are already in
|
|
|
|
// the process of loading the data. We need to avoid multiple threads all
|
|
|
|
// trying to load the data at the same time.
|
|
|
|
//
|
|
|
|
// For now doing a blocking load with the inode_->state_ lock held ensures
|
|
|
|
// that only one thread can load the data at a time. It's pretty unfortunate
|
|
|
|
// to block with the lock held, though :-(
|
|
|
|
state->blob = blobFuture.get();
|
|
|
|
return makeFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Unit> FileInode::materializeForWrite(int openFlags) {
|
|
|
|
auto state = state_.wlock();
|
|
|
|
|
|
|
|
// If we already have a materialized overlay file then we don't
|
|
|
|
// need to do much
|
|
|
|
if (state->file) {
|
|
|
|
CHECK(!state->hash.hasValue());
|
|
|
|
if ((openFlags & O_TRUNC) != 0) {
|
|
|
|
// truncating a file that we already have open
|
|
|
|
state->sha1Valid = false;
|
|
|
|
checkUnixError(ftruncate(state->file.fd(), Overlay::kHeaderLength));
|
|
|
|
auto emptySha1 = Hash::sha1(ByteRange{});
|
|
|
|
storeSha1(state, emptySha1);
|
|
|
|
} else {
|
|
|
|
// no truncate option,overlay file contain old header
|
|
|
|
// we have to update only header but not contents
|
|
|
|
}
|
|
|
|
return makeFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add header to the overlay File.
|
|
|
|
auto header = Overlay::createHeader(
|
|
|
|
Overlay::kHeaderIdentifierFile,
|
|
|
|
Overlay::kHeaderVersion,
|
2017-08-11 21:34:52 +03:00
|
|
|
state->timeStamps.atime,
|
|
|
|
state->timeStamps.ctime,
|
|
|
|
state->timeStamps.mtime);
|
2017-07-27 09:39:02 +03:00
|
|
|
auto iov = header.getIov();
|
|
|
|
|
|
|
|
// We must not be materialized yet
|
|
|
|
CHECK(state->hash.hasValue());
|
|
|
|
|
|
|
|
Hash sha1;
|
|
|
|
auto filePath = getLocalPath();
|
|
|
|
|
|
|
|
if ((openFlags & O_TRUNC) != 0) {
|
|
|
|
folly::writeFileAtomic(filePath.stringPiece(), iov.data(), iov.size());
|
2017-08-05 06:14:18 +03:00
|
|
|
// We don't want to set the in-memory timestamps to the timestamps returned
|
|
|
|
// by the below openFile function as we just wrote these timestamps in to
|
|
|
|
// overlay using writeFileAtomic.
|
2017-08-11 21:34:52 +03:00
|
|
|
InodeTimestamps timeStamps;
|
2017-08-05 06:14:18 +03:00
|
|
|
state->file = Overlay::openFile(
|
2017-08-11 21:34:52 +03:00
|
|
|
filePath.stringPiece(), Overlay::kHeaderIdentifierFile, timeStamps);
|
2017-07-27 09:39:02 +03:00
|
|
|
sha1 = Hash::sha1(ByteRange{});
|
|
|
|
} else {
|
|
|
|
if (!state->blob) {
|
|
|
|
// TODO: Load the blob using the non-blocking Future APIs.
|
|
|
|
// However, just as in ensureDataLoaded() above we will also need
|
|
|
|
// to add a mechanism to wait for already in-progress loads.
|
|
|
|
auto blobFuture = getObjectStore()->getBlob(state->hash.value());
|
|
|
|
state->blob = blobFuture.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the blob contents out to the overlay
|
|
|
|
auto contents = state->blob->getContents().getIov();
|
|
|
|
iov.insert(iov.end(), contents.begin(), contents.end());
|
|
|
|
|
|
|
|
folly::writeFileAtomic(
|
|
|
|
filePath.stringPiece(), iov.data(), iov.size(), 0600);
|
2017-08-11 21:34:52 +03:00
|
|
|
InodeTimestamps timeStamps;
|
2017-08-05 06:14:18 +03:00
|
|
|
state->file = Overlay::openFile(
|
2017-08-11 21:34:52 +03:00
|
|
|
filePath.stringPiece(), Overlay::kHeaderIdentifierFile, timeStamps);
|
2017-07-27 09:39:02 +03:00
|
|
|
|
|
|
|
sha1 = getObjectStore()->getSha1ForBlob(state->hash.value());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy and apply the sha1 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.
|
|
|
|
storeSha1(state, sha1);
|
|
|
|
|
|
|
|
// Update the FileInode to indicate that we are materialized now
|
|
|
|
state->blob.reset();
|
|
|
|
state->hash = folly::none;
|
|
|
|
|
|
|
|
return makeFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
ObjectStore* FileInode::getObjectStore() const {
|
|
|
|
return getMount()->getObjectStore();
|
|
|
|
}
|
|
|
|
|
|
|
|
Hash FileInode::recomputeAndStoreSha1(
|
|
|
|
const folly::Synchronized<FileInode::State>::LockedPtr& state) {
|
|
|
|
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.
|
|
|
|
auto len = folly::preadNoInt(state->file.fd(), buf, sizeof(buf), off);
|
|
|
|
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)));
|
|
|
|
storeSha1(state, sha1);
|
|
|
|
return sha1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileInode::storeSha1(
|
|
|
|
const folly::Synchronized<FileInode::State>::LockedPtr& state,
|
|
|
|
Hash sha1) {
|
|
|
|
try {
|
|
|
|
fsetxattr(state->file.fd(), kXattrSha1, sha1.toString());
|
|
|
|
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
|
|
|
|
|
|
|
// Gets the immemory timestamps of the inode.
|
2017-08-11 21:34:52 +03:00
|
|
|
InodeBase::InodeTimestamps FileInode::getTimestamps() const {
|
2017-07-28 04:12:48 +03:00
|
|
|
auto state = state_.rlock();
|
2017-08-11 21:34:52 +03:00
|
|
|
return state->timeStamps;
|
2017-07-28 04:12:48 +03:00
|
|
|
}
|
2017-08-05 06:14:18 +03:00
|
|
|
|
|
|
|
void FileInode::updateOverlayHeader() const {
|
|
|
|
auto state = state_.wlock();
|
|
|
|
if (state->file) {
|
|
|
|
// File is a materialized file
|
2017-08-11 21:34:52 +03:00
|
|
|
Overlay::updateTimestampToHeader(state->file.fd(), state->timeStamps);
|
2017-08-05 06:14:18 +03:00
|
|
|
}
|
|
|
|
}
|
2016-05-12 23:43:17 +03:00
|
|
|
}
|
|
|
|
}
|