sapling/eden/fs/service/GlobNode.cpp
Adam Simpkins 6f60e9318e fix crashes triggered by the new TreeInode::Entry checks
Summary:
D5483953 added a check to ensure that getMode() can only be called on entries
that do not have a loaded inode.  However, a few places in the code were still
calling getMode() on tree entries without checking if the inode was loaded or
not.

These crashes were caught in the integration tests run by `buck test eden/...`,
but were not caught by sandcastle tests on the original diff since sandcastle
only runs the eden unit tests, and not the integration tests.

All of these locations only needed to check the file type, which is safe to do
even if the inode is loaded, since the file type portion of the mode can never
change on an existing inode.  Only the permissions bits are unsafe to access
once an inode has been loaded (since we need to ask the inode itself for the
latest permissions bits).

I updated these call sites to stop using getMode() and instead use functions
that explicitly check only the file type bits.

Reviewed By: bolinfest

Differential Revision: D5501256

fbshipit-source-id: c989dab2fdacb5b9cdecb6f5101795298f57e78b
2017-07-26 13:21:13 -07:00

236 lines
7.0 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 "GlobNode.h"
#include "EdenError.h"
#include "eden/fs/inodes/TreeInode.h"
using std::string;
using std::unique_ptr;
using std::vector;
using folly::Future;
using folly::makeFuture;
using std::make_unique;
using folly::StringPiece;
using std::unordered_set;
namespace facebook {
namespace eden {
GlobNode::GlobNode(StringPiece pattern, bool hasSpecials)
: pattern_(pattern), hasSpecials_(hasSpecials) {
if (pattern_ == "**" || pattern_ == "*") {
alwaysMatch_ = true;
} else {
auto compiled = GlobMatcher::create(pattern);
if (compiled.hasError()) {
throw newEdenError(
EINVAL,
"failed to compile pattern `{}` to GlobMatcher: {}",
pattern,
compiled.error());
}
matcher_ = std::move(compiled.value());
}
}
void GlobNode::parse(StringPiece pattern) {
GlobNode* parent = this;
while (!pattern.empty()) {
StringPiece token;
auto* container = &parent->children_;
bool hasSpecials;
if (pattern.startsWith("**")) {
// Recursive match defeats most optimizations; we have to stop
// tokenizing here.
token = pattern;
pattern = StringPiece();
container = &parent->recursiveChildren_;
hasSpecials = true;
} else {
token = tokenize(pattern, &hasSpecials);
}
auto node = lookupToken(container, token);
if (!node) {
container->emplace_back(std::make_unique<GlobNode>(token, hasSpecials));
node = container->back().get();
}
// If there are no more tokens remaining then we have a leaf node
// that will emit results. Update the node to reflect this.
// Note that this may convert a pre-existing node from an earlier
// glob specification to a leaf node.
if (pattern.empty()) {
node->isLeaf_ = true;
}
// Continue parsing the remainder of the pattern using this
// (possibly new) node as the parent.
parent = node;
}
}
Future<unordered_set<RelativePath>> GlobNode::evaluate(
RelativePathPiece rootPath,
TreeInodePtr root) {
unordered_set<RelativePath> results =
evaluateRecursiveComponent(rootPath, root).get();
vector<std::pair<PathComponent, GlobNode*>> recurse;
{
auto contents = root->getContents().rlock();
for (auto& node : children_) {
if (!node->hasSpecials_) {
// We can try a lookup for the exact name
auto it = contents->entries.find(PathComponentPiece(node->pattern_));
if (it != contents->entries.end()) {
// Matched!
if (node->isLeaf_) {
results.emplace((rootPath + it->first));
continue;
}
// Not the leaf of a pattern; if this is a dir, we need to recurse
if (it->second->isDirectory()) {
recurse.emplace_back(std::make_pair(it->first, node.get()));
}
}
} else {
// We need to match it out of the entries in this inode
for (auto& entry : contents->entries) {
if (node->alwaysMatch_ ||
node->matcher_.match(entry.first.stringPiece())) {
if (node->isLeaf_) {
results.emplace((rootPath + entry.first));
continue;
}
// Not the leaf of a pattern; if this is a dir, we need to
// recurse
if (entry.second->isDirectory()) {
recurse.emplace_back(std::make_pair(entry.first, node.get()));
}
}
}
}
}
}
// Recursively load child inodes and evaluate matches
std::vector<Future<unordered_set<RelativePath>>> futures;
for (auto& item : recurse) {
auto candidateName = rootPath + item.first;
futures.emplace_back(root->getOrLoadChildTree(item.first).then([
candidateName,
node = item.second
](TreeInodePtr dir) { return node->evaluate(candidateName, dir); }));
}
return folly::collect(futures).then([results = std::move(results)](
std::vector<std::unordered_set<RelativePath>> && matchVector) mutable {
for (auto& matches : matchVector) {
results.insert(matches.begin(), matches.end());
}
return results;
});
}
StringPiece GlobNode::tokenize(StringPiece& pattern, bool* hasSpecials) {
*hasSpecials = false;
for (auto it = pattern.begin(); it != pattern.end(); ++it) {
switch (*it) {
case '*':
case '?':
case '[':
case '\\':
*hasSpecials = true;
break;
case '/':
// token is the input up-to-but-not-including the current position,
// which is a '/' character
StringPiece token(pattern.begin(), it);
// update the pattern to be the text after the slash
pattern = StringPiece(it + 1, pattern.end());
return token;
}
}
// No slash found, so the the rest of the pattern is the token
StringPiece token = pattern;
pattern = StringPiece();
return token;
}
GlobNode* GlobNode::lookupToken(
vector<unique_ptr<GlobNode>>* container,
StringPiece token) {
for (auto& child : *container) {
if (child->pattern_ == token) {
return child.get();
}
}
return nullptr;
}
Future<unordered_set<RelativePath>> GlobNode::evaluateRecursiveComponent(
RelativePathPiece rootPath,
TreeInodePtr root) {
unordered_set<RelativePath> results;
if (recursiveChildren_.empty()) {
return results;
}
vector<RelativePath> subDirNames;
{
auto contents = root->getContents().rlock();
for (auto& entry : contents->entries) {
auto candidateName = rootPath + entry.first;
for (auto& node : recursiveChildren_) {
if (node->alwaysMatch_ ||
node->matcher_.match(candidateName.stringPiece())) {
results.emplace(candidateName);
// No sense running multiple matches for this same file.
break;
}
}
// Remember to recurse through child dirs after we've released
// the lock on the contents.
if (entry.second->isDirectory()) {
subDirNames.emplace_back(candidateName);
}
}
}
// Recursively load child inodes and evaluate matches
std::vector<Future<unordered_set<RelativePath>>> futures;
for (auto& candidateName : subDirNames) {
futures.emplace_back(root->getOrLoadChildTree(candidateName.basename())
.then([candidateName, this](TreeInodePtr dir) {
return evaluateRecursiveComponent(
candidateName, dir);
}));
}
return folly::collect(futures).then([results = std::move(results)](
std::vector<std::unordered_set<RelativePath>> && matchVector) mutable {
for (auto& matches : matchVector) {
results.insert(matches.begin(), matches.end());
}
return results;
});
}
}
}