mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
0687431924
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
253 lines
7.3 KiB
C++
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_;
|
|
}
|
|
}
|
|
}
|