mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
5029338c62
Summary: Now that the Overlay no longer serializes timestamps, remove all of the special-case migration logic. Reviewed By: strager Differential Revision: D13144764 fbshipit-source-id: 713a4bfcde9003a8d5a28837cb530b05a9017c22
389 lines
13 KiB
C++
389 lines
13 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 "eden/fs/inodes/InodeBase.h"
|
|
|
|
#include <folly/Likely.h>
|
|
#include <folly/logging/xlog.h>
|
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
#include "eden/fs/inodes/InodeMap.h"
|
|
#include "eden/fs/inodes/InodeTable.h"
|
|
#include "eden/fs/inodes/ParentInodeInfo.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
#include "eden/fs/utils/Clock.h"
|
|
|
|
using namespace folly;
|
|
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
InodeBase::InodeBase(EdenMount* mount)
|
|
: ino_{kRootNodeId},
|
|
initialMode_{S_IFDIR | 0755},
|
|
mount_{mount},
|
|
location_{
|
|
LocationInfo{nullptr,
|
|
PathComponentPiece{"", detail::SkipPathSanityCheck()}}} {
|
|
XLOG(DBG5) << "root inode " << this << " (" << ino_ << ") created for mount "
|
|
<< mount_->getPath();
|
|
// The root inode always starts with an implicit reference from FUSE.
|
|
incFuseRefcount();
|
|
|
|
mount->getInodeMetadataTable()->populateIfNotSet(
|
|
ino_, [&] { return mount->getInitialInodeMetadata(S_IFDIR | 0755); });
|
|
}
|
|
|
|
InodeBase::InodeBase(
|
|
InodeNumber ino,
|
|
mode_t initialMode,
|
|
const std::optional<InodeTimestamps>& initialTimestamps,
|
|
TreeInodePtr parent,
|
|
PathComponentPiece name)
|
|
: ino_{ino},
|
|
initialMode_{initialMode},
|
|
mount_{parent->mount_},
|
|
location_{LocationInfo{std::move(parent), name}} {
|
|
// Inode numbers generally shouldn't be 0.
|
|
// Older versions of glibc have bugs handling files with an inode number of 0
|
|
DCHECK(ino_.hasValue());
|
|
XLOG(DBG5) << "inode " << this << " (" << ino_
|
|
<< ") created: " << getLogPath();
|
|
|
|
mount_->getInodeMetadataTable()->populateIfNotSet(ino_, [&] {
|
|
auto metadata = mount_->getInitialInodeMetadata(initialMode);
|
|
if (initialTimestamps) {
|
|
metadata.timestamps = *initialTimestamps;
|
|
}
|
|
return metadata;
|
|
});
|
|
}
|
|
|
|
InodeBase::~InodeBase() {
|
|
XLOG(DBG5) << "inode " << this << " (" << ino_
|
|
<< ") destroyed: " << getLogPath();
|
|
}
|
|
|
|
// See Dispatcher::getattr
|
|
folly::Future<Dispatcher::Attr> InodeBase::getattr() {
|
|
FUSELL_NOT_IMPL();
|
|
}
|
|
|
|
folly::Future<folly::Unit> InodeBase::setxattr(
|
|
folly::StringPiece /*name*/,
|
|
folly::StringPiece /*value*/,
|
|
int /*flags*/) {
|
|
// setxattr is not supported for any type of inode. This instructs the kernel
|
|
// to automatically fail all future setxattr() syscalls with EOPNOTSUPP.
|
|
FUSELL_NOT_IMPL();
|
|
}
|
|
|
|
folly::Future<folly::Unit> InodeBase::removexattr(folly::StringPiece /*name*/) {
|
|
// removexattr is not supported for any type of inode. This instructs the
|
|
// kernel to automatically fail all future removexattr() syscalls with
|
|
// EOPNOTSUPP.
|
|
FUSELL_NOT_IMPL();
|
|
}
|
|
|
|
folly::Future<folly::Unit> InodeBase::access(int /*mask*/) {
|
|
// Returning ENOSYS instructs FUSE that access() will always succeed, so does
|
|
// not need to call back into the FUSE daemon.
|
|
FUSELL_NOT_IMPL();
|
|
}
|
|
|
|
bool InodeBase::isUnlinked() const {
|
|
auto loc = location_.rlock();
|
|
return loc->unlinked;
|
|
}
|
|
|
|
/**
|
|
* Helper function for getPath() and getLogPath()
|
|
*
|
|
* Populates the names vector with the list of PathComponents from the root
|
|
* down to this inode.
|
|
*
|
|
* This method should not be called on the root inode. The caller is
|
|
* responsible for checking that before calling getPathHelper().
|
|
*
|
|
* Returns true if the the file exists at the given path, or false if the file
|
|
* has been unlinked.
|
|
*
|
|
* If stopOnUnlinked is true, it breaks immediately when it finds that the file
|
|
* has been unlinked. The contents of the names vector are then undefined if
|
|
* the function returns false.
|
|
*
|
|
* If stopOnUnlinked is false it continues building the names vector even if
|
|
* the file is unlinked, which will then contain the path that the file used to
|
|
* exist at. (This path should be used only for logging purposes at that
|
|
* point.)
|
|
*/
|
|
bool InodeBase::getPathHelper(
|
|
std::vector<PathComponent>& names,
|
|
bool stopOnUnlinked) const {
|
|
TreeInodePtr parent;
|
|
bool unlinked = false;
|
|
{
|
|
auto loc = location_.rlock();
|
|
if (loc->unlinked) {
|
|
if (stopOnUnlinked) {
|
|
return false;
|
|
}
|
|
unlinked = true;
|
|
}
|
|
parent = loc->parent;
|
|
// Our caller should ensure that we are not the root
|
|
DCHECK(parent);
|
|
names.push_back(loc->name);
|
|
}
|
|
|
|
while (true) {
|
|
// Stop at the root inode.
|
|
// We check for this based on inode number so we can stop without having to
|
|
// acquire the root inode's location lock. (Otherwise all path lookups
|
|
// would have to acquire the root's lock, making it more likely to be
|
|
// contended.)
|
|
if (parent->ino_ == kRootNodeId) {
|
|
// Reverse the names vector, since we built it from bottom to top.
|
|
std::reverse(names.begin(), names.end());
|
|
return !unlinked;
|
|
}
|
|
|
|
auto loc = parent->location_.rlock();
|
|
// In general our parent should not be unlinked if we are not unlinked,
|
|
// which we checked above. However, we have since released our location
|
|
// lock, so it's possible (but unlikely) that someone unlinked us and our
|
|
// parent directories since we checked above.
|
|
if (UNLIKELY(loc->unlinked)) {
|
|
if (stopOnUnlinked) {
|
|
return false;
|
|
}
|
|
unlinked = true;
|
|
}
|
|
names.push_back(loc->name);
|
|
parent = loc->parent;
|
|
DCHECK(parent);
|
|
}
|
|
}
|
|
|
|
std::optional<RelativePath> InodeBase::getPath() const {
|
|
if (ino_ == kRootNodeId) {
|
|
return RelativePath();
|
|
}
|
|
|
|
std::vector<PathComponent> names;
|
|
if (!getPathHelper(names, true)) {
|
|
return std::nullopt;
|
|
}
|
|
return RelativePath(names);
|
|
}
|
|
|
|
std::string InodeBase::getLogPath() const {
|
|
if (ino_ == kRootNodeId) {
|
|
// We use "<root>" here instead of the empty string to make log messages
|
|
// more understandable. The empty string would likely be confusing, as it
|
|
// would appear if the file name were missing.
|
|
return "<root>";
|
|
}
|
|
|
|
std::vector<PathComponent> names;
|
|
bool unlinked = !getPathHelper(names, false);
|
|
auto path = RelativePath(names);
|
|
if (unlinked) {
|
|
return folly::to<std::string>("<deleted:", path.stringPiece(), ">");
|
|
}
|
|
return std::move(path).value();
|
|
}
|
|
|
|
void InodeBase::markUnlinkedAfterLoad() {
|
|
auto loc = location_.wlock();
|
|
DCHECK(!loc->unlinked);
|
|
loc->unlinked = true;
|
|
}
|
|
|
|
std::unique_ptr<InodeBase> InodeBase::markUnlinked(
|
|
TreeInode* parent,
|
|
PathComponentPiece name,
|
|
const RenameLock& renameLock) {
|
|
XLOG(DBG5) << "inode " << this << " unlinked: " << getLogPath();
|
|
DCHECK(renameLock.isHeld(mount_));
|
|
|
|
{
|
|
auto loc = location_.wlock();
|
|
DCHECK(!loc->unlinked);
|
|
DCHECK_EQ(loc->parent.get(), parent);
|
|
loc->unlinked = true;
|
|
}
|
|
|
|
// Grab the inode map lock, and check if we should unload
|
|
// ourself immediately.
|
|
auto* inodeMap = getMount()->getInodeMap();
|
|
auto inodeMapLock = inodeMap->lockForUnload();
|
|
if (isPtrAcquireCountZero() && getFuseRefcount() == 0) {
|
|
inodeMap->unloadInode(this, parent, name, true, inodeMapLock);
|
|
// We have to delete ourself now.
|
|
// Do this by returning a unique_ptr to ourself, so that our caller will
|
|
// destroy us. This ensures we get destroyed after releasing the InodeMap
|
|
// lock. Our calling TreeInode should wait to destroy us until they
|
|
// release their contents lock as well.
|
|
//
|
|
// (Technically it should probably be fine even if the caller deletes us
|
|
// before releasing their contents lock, it just seems safer to wait.
|
|
// The main area of concern is that deleting us will drop a reference count
|
|
// on our parent, which could require the code to acquire locks to destroy
|
|
// our parent. However, we are only ever invoked from unlink(), rmdir(),
|
|
// or rename() operations which must already be holding a reference on our
|
|
// parent. Therefore our parent should never be destroyed when our
|
|
// destructor gets invoked here, so we won't need to acquire our parent's
|
|
// contents lock in our destructor.)
|
|
return std::unique_ptr<InodeBase>(this);
|
|
}
|
|
// We don't need our caller to delete us, so return null.
|
|
return nullptr;
|
|
}
|
|
|
|
void InodeBase::updateLocation(
|
|
TreeInodePtr newParent,
|
|
PathComponentPiece newName,
|
|
const RenameLock& renameLock) {
|
|
XLOG(DBG5) << "inode " << this << " renamed: " << getLogPath() << " --> "
|
|
<< newParent->getLogPath() << " / \"" << newName << "\"";
|
|
DCHECK(renameLock.isHeld(mount_));
|
|
DCHECK_EQ(mount_, newParent->mount_);
|
|
|
|
auto loc = location_.wlock();
|
|
DCHECK(!loc->unlinked);
|
|
loc->parent = newParent;
|
|
loc->name = newName.copy();
|
|
}
|
|
|
|
void InodeBase::onPtrRefZero() const {
|
|
// onPtrRefZero() is const since we treat incrementing and decrementing the
|
|
// pointer refcount as a non-modifying operation. (The refcount is updated
|
|
// atomically so the operation is thread-safe.)
|
|
//
|
|
// However when the last reference goes to zero we destroy the inode object,
|
|
// which is a modifying operation. Cast ourself back to non-const in this
|
|
// case. We are guaranteed that no-one else has a reference to us anymore so
|
|
// this is safe.
|
|
//
|
|
// We could perhaps just make incrementPtrRef() and decrementPtrRef()
|
|
// non-const instead. InodePtr objects always point to non-const InodeBase
|
|
// objects; we do not currently ever use pointer-to-const InodePtrs.
|
|
getMount()->getInodeMap()->onInodeUnreferenced(
|
|
const_cast<InodeBase*>(this), getParentInfo());
|
|
}
|
|
|
|
ParentInodeInfo InodeBase::getParentInfo() const {
|
|
using ParentContentsPtr = folly::Synchronized<TreeInodeState>::LockedPtr;
|
|
|
|
// Grab our parent's contents_ lock.
|
|
//
|
|
// We need a retry loop here in case we get renamed or unlinked while trying
|
|
// to acquire our parent's lock.
|
|
//
|
|
// (We could acquire the mount point rename lock first, to ensure that we
|
|
// can't be renamed during this process. However it seems unlikely that we
|
|
// would get renamed or unlinked, so retrying seems probably better than
|
|
// holding a mountpoint-wide lock.)
|
|
size_t numTries = {0};
|
|
while (true) {
|
|
++numTries;
|
|
TreeInodePtr parent;
|
|
// Get our current parent.
|
|
{
|
|
auto loc = location_.rlock();
|
|
parent = loc->parent;
|
|
if (loc->unlinked) {
|
|
XLOG(DBG6) << "getParentInfo(): unlinked inode detected after "
|
|
<< numTries << " tries";
|
|
return ParentInodeInfo{
|
|
loc->name, loc->parent, loc->unlinked, ParentContentsPtr{}};
|
|
}
|
|
}
|
|
|
|
if (!parent) {
|
|
// We are the root inode.
|
|
DCHECK_EQ(numTries, 1);
|
|
return ParentInodeInfo{
|
|
PathComponentPiece{"", detail::SkipPathSanityCheck()},
|
|
nullptr,
|
|
false,
|
|
ParentContentsPtr{}};
|
|
}
|
|
// Now grab our parent's contents lock.
|
|
auto parentContents = parent->getContents().wlock();
|
|
|
|
// After acquiring our parent's contents lock we have to make sure it is
|
|
// actually still our parent. If it is we are done and can break out of
|
|
// this loop.
|
|
{
|
|
auto loc = location_.rlock();
|
|
if (loc->unlinked) {
|
|
// This file was unlinked since we checked earlier
|
|
XLOG(DBG6) << "getParentInfo(): file is newly unlinked on try "
|
|
<< numTries;
|
|
return ParentInodeInfo{
|
|
loc->name, loc->parent, loc->unlinked, ParentContentsPtr{}};
|
|
}
|
|
if (loc->parent == parent) {
|
|
// Our parent is still the same. We're done.
|
|
XLOG(DBG6) << "getParentInfo() acquired parent lock after " << numTries
|
|
<< " tries";
|
|
return ParentInodeInfo{
|
|
loc->name, loc->parent, loc->unlinked, std::move(parentContents)};
|
|
}
|
|
}
|
|
// Otherwise our parent changed, and we have to retry.
|
|
parent.reset();
|
|
parentContents.unlock();
|
|
}
|
|
}
|
|
|
|
InodeMetadata InodeBase::getMetadataLocked() const {
|
|
return getMount()->getInodeMetadataTable()->getOrThrow(getNodeId());
|
|
}
|
|
|
|
void InodeBase::updateAtime() {
|
|
// TODO: Is it worth implementing relatime-like logic?
|
|
auto now = getNow();
|
|
getMount()->getInodeMetadataTable()->modifyOrThrow(
|
|
getNodeId(), [&](auto& metadata) { metadata.timestamps.atime = now; });
|
|
}
|
|
|
|
InodeTimestamps InodeBase::updateMtimeAndCtime(timespec now) {
|
|
return getMount()
|
|
->getInodeMetadataTable()
|
|
->modifyOrThrow(
|
|
getNodeId(),
|
|
[&](auto& record) {
|
|
record.timestamps.ctime = now;
|
|
record.timestamps.mtime = now;
|
|
})
|
|
.timestamps;
|
|
}
|
|
|
|
timespec InodeBase::getNow() const {
|
|
return getClock().getRealtime();
|
|
}
|
|
|
|
const Clock& InodeBase::getClock() const {
|
|
return getMount()->getClock();
|
|
}
|
|
|
|
// Helper function to update Journal used by FileInode and TreeInode.
|
|
void InodeBase::updateJournal() {
|
|
auto path = getPath();
|
|
if (path.has_value()) {
|
|
getMount()->getJournal().addDelta(std::make_unique<JournalDelta>(
|
|
std::move(path.value()), JournalDelta::CHANGED));
|
|
}
|
|
}
|
|
} // namespace eden
|
|
} // namespace facebook
|