sapling/eden/fs/inodes/FileInode.cpp
Adam Simpkins 0687431924 implement EdenMount::checkout()
Summary:
This is the initial code for implementing checkout.

This isn't quite 100% implemented yet, but I think it's worth checking in this
code as-is, and getting the remaining functionality done in separate diffs.
In particular, a few operations aren't implemented:
- Removing a directory that was deleted in the new revision
- Replacing a directory that was replaced with a file or symlink in the new
  revision
- When doing a forced update, replacing a file or directory that did not exist
  in the old revision, but that was created locally in the working directory,
  and also exists in the new revision.

Reviewed By: wez

Differential Revision: D4538516

fbshipit-source-id: 5bb4889b02f23ab2048fcae2c8b7614340181aa6
2017-02-15 20:33:31 -08:00

253 lines
7.3 KiB
C++

/*
* Copyright (c) 2016-present, Facebook, Inc.
* 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.
*
*/
#include "FileInode.h"
#include "EdenMount.h"
#include "FileData.h"
#include "FileHandle.h"
#include "InodeError.h"
#include "Overlay.h"
#include "eden/fs/model/Blob.h"
#include "eden/fs/model/Hash.h"
#include "eden/fs/store/ObjectStore.h"
#include "eden/utils/XAttr.h"
using folly::Future;
using folly::StringPiece;
using folly::checkUnixError;
using std::string;
using std::vector;
namespace facebook {
namespace eden {
FileInode::FileInode(
fuse_ino_t ino,
TreeInodePtr parentInode,
PathComponentPiece name,
TreeInode::Entry* entry)
: InodeBase(ino, std::move(parentInode), name),
entry_(entry),
data_(std::make_shared<FileData>(this, mutex_, entry)) {}
FileInode::FileInode(
fuse_ino_t ino,
TreeInodePtr parentInode,
PathComponentPiece name,
TreeInode::Entry* entry,
folly::File&& file)
: InodeBase(ino, std::move(parentInode), name),
entry_(entry),
data_(std::make_shared<FileData>(this, mutex_, entry_, std::move(file))) {
}
folly::Future<fusell::Dispatcher::Attr> FileInode::getattr() {
auto data = getOrLoadData();
// Future optimization opportunity: right now, if we have not already
// materialized the data from the entry_, we have to materialize it
// from the store. If we augmented our metadata we could avoid this,
// and this would speed up operations like `ls`.
data->materializeForRead(O_RDONLY);
fusell::Dispatcher::Attr attr(getMount()->getMountPoint());
attr.st = data->stat();
attr.st.st_ino = getNodeId();
return attr;
}
folly::Future<fusell::Dispatcher::Attr> FileInode::setattr(
const struct stat& attr,
int to_set) {
auto data = getOrLoadData();
int open_flags = O_RDWR;
// 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
if ((to_set & FUSE_SET_ATTR_SIZE) && attr.st_size == 0) {
open_flags |= O_TRUNC;
}
getParentBuggy()->materializeDirAndParents();
data->materializeForWrite(open_flags);
fusell::Dispatcher::Attr result(getMount()->getMountPoint());
result.st = data->setAttr(attr, to_set);
result.st.st_ino = getNodeId();
auto path = getPath();
if (path.hasValue()) {
getMount()->getJournal().wlock()->addDelta(
std::make_unique<JournalDelta>(JournalDelta{path.value()}));
}
return result;
}
folly::Future<std::string> FileInode::readlink() {
std::unique_lock<std::mutex> lock(mutex_);
DCHECK_NOTNULL(entry_);
if (!S_ISLNK(entry_->mode)) {
// man 2 readlink says: EINVAL The named file is not a symbolic link.
throw InodeError(EINVAL, inodePtrFromThis(), "not a symlink");
}
if (entry_->isMaterialized()) {
struct stat st;
auto localPath = getLocalPath();
// Figure out how much space we need to hold the symlink target.
checkUnixError(lstat(localPath.c_str(), &st));
// Allocate a string of the appropriate size.
std::string buf;
buf.resize(st.st_size, 0 /* filled with zeroes */);
// Read the link into the string buffer.
auto res = ::readlink(
localPath.c_str(), &buf[0], buf.size() + 1 /* for nul terminator */);
checkUnixError(res);
CHECK_EQ(st.st_size, res) << "symlink size TOCTOU";
return buf;
}
// Load the symlink contents from the store
auto blob = getMount()->getObjectStore()->getBlob(entry_->getHash());
auto buf = blob->getContents();
return buf.moveToFbString().toStdString();
}
std::shared_ptr<FileData> FileInode::getOrLoadData() {
std::unique_lock<std::mutex> lock(mutex_);
if (!data_) {
data_ = std::make_shared<FileData>(this, mutex_, entry_);
}
return data_;
}
void FileInode::fileHandleDidClose() {
std::unique_lock<std::mutex> lock(mutex_);
if (data_.unique()) {
// We're the only remaining user, no need to keep it around
data_.reset();
}
}
AbsolutePath FileInode::getLocalPath() const {
return getMount()->getOverlay()->getFilePath(getNodeId());
}
bool FileInode::isSameAs(const Blob& blob, mode_t mode) {
// 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)); };
if (relevantModeBits(entry_->mode) != relevantModeBits(mode)) {
return false;
}
if (!entry_->isMaterialized()) {
return entry_->getHash() == blob.getHash();
}
return getOrLoadData()->getSha1() == Hash::sha1(&blob.getContents());
}
folly::Future<std::shared_ptr<fusell::FileHandle>> FileInode::open(
const struct fuse_file_info& fi) {
auto data = getOrLoadData();
SCOPE_EXIT {
data.reset();
fileHandleDidClose();
};
if (fi.flags & (O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
getParentBuggy()->materializeDirAndParents();
data->materializeForWrite(fi.flags);
} else {
data->materializeForRead(fi.flags);
}
return std::make_shared<FileHandle>(inodePtrFromThis(), data, fi.flags);
}
std::shared_ptr<FileHandle> FileInode::finishCreate() {
auto data = getOrLoadData();
SCOPE_EXIT {
data.reset();
fileHandleDidClose();
};
data->materializeForWrite(0);
return std::make_shared<FileHandle>(inodePtrFromThis(), data, 0);
}
Future<vector<string>> FileInode::listxattr() {
// 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;
if (S_ISREG(entry_->mode)) {
attributes.emplace_back(kXattrSha1.str());
}
return attributes;
}
Future<string> FileInode::getxattr(StringPiece name) {
// Currently, we only support the xattr for the SHA-1 of a regular file.
if (name != kXattrSha1) {
throw InodeError(kENOATTR, inodePtrFromThis());
}
return getSHA1().get().toString();
}
Future<Hash> FileInode::getSHA1() {
// Some ugly looking stuff to avoid materializing the file if we haven't
// done so already.
std::unique_lock<std::mutex> lock(mutex_);
if (data_) {
// We already have context, ask it to supply the results.
return data_->getSha1Locked(lock);
}
CHECK_NOTNULL(entry_);
if (!S_ISREG(entry_->mode)) {
// We only define a SHA-1 value for regular files
throw InodeError(kENOATTR, inodePtrFromThis());
}
if (entry_->isMaterialized()) {
// The O_NOFOLLOW here prevents us from attempting to read attributes
// from a symlink.
auto filePath = getLocalPath();
folly::File file(filePath.c_str(), O_RDONLY | O_NOFOLLOW);
// Return the property from the existing file.
// If it isn't set it means that someone was poking into the overlay and
// we'll return the standard kENOATTR back to the caller in that case.
return Hash(fgetxattr(file.fd(), kXattrSha1));
}
// TODO(mbolin): Make this more fault-tolerant. Currently, there is no logic
// to account for the case where we don't have the SHA-1 for the blob, the
// hash doesn't correspond to a blob, etc.
return getMount()->getObjectStore()->getSha1ForBlob(entry_->getHash());
}
const TreeInode::Entry* FileInode::getEntry() const {
return entry_;
}
}
}