// Copyright 2015 Google Inc. All rights reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ignore
#include "find.h"
#include <dirent.h>
#include <fnmatch.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <memory>
#include <vector>
//#undef NOLOG
#include "fileutil.h"
#include "log.h"
#include "string_piece.h"
#include "strutil.h"
#include "timeutil.h"
#define FIND_WARN_LOC(...) \
do { \
if (g_flags.werror_find_emulator) { \
ERROR_LOC(__VA_ARGS__); \
} else { \
WARN_LOC(__VA_ARGS__); \
} \
} while (0)
class FindCond {
public:
virtual ~FindCond() = default;
virtual bool IsTrue(const string& path, unsigned char type) const = 0;
virtual bool Countable() const = 0;
virtual unsigned Count() const = 0;
protected:
FindCond() = default;
};
namespace {
class NameCond : public FindCond {
public:
explicit NameCond(const string& n) : name_(n) {
has_wildcard_ = (n.find_first_of("?*[") != string::npos);
}
virtual bool IsTrue(const string& path, unsigned char) const override {
return fnmatch(name_.c_str(), Basename(path).data(), 0) == 0;
}
virtual bool Countable() const override { return !has_wildcard_; }
virtual unsigned Count() const override { return 1; }
private:
string name_;
bool has_wildcard_;
};
class TypeCond : public FindCond {
public:
explicit TypeCond(unsigned char t) : type_(t) {}
virtual bool IsTrue(const string&, unsigned char type) const override {
return type == type_;
}
virtual bool Countable() const override { return false; }
virtual unsigned Count() const override { return 0; }
private:
unsigned char type_;
};
class NotCond : public FindCond {
public:
NotCond(FindCond* c) : c_(c) {}
virtual bool IsTrue(const string& path, unsigned char type) const override {
return !c_->IsTrue(path, type);
}
virtual bool Countable() const override { return false; }
virtual unsigned Count() const override { return 0; }
private:
unique_ptr<FindCond> c_;
};
class AndCond : public FindCond {
public:
AndCond(FindCond* c1, FindCond* c2) : c1_(c1), c2_(c2) {}
virtual bool IsTrue(const string& path, unsigned char type) const override {
if (c1_->IsTrue(path, type))
return c2_->IsTrue(path, type);
return false;
}
virtual bool Countable() const override { return false; }
virtual unsigned Count() const override { return 0; }
private:
unique_ptr<FindCond> c1_, c2_;
};
class OrCond : public FindCond {
public:
OrCond(FindCond* c1, FindCond* c2) : c1_(c1), c2_(c2) {}
virtual bool IsTrue(const string& path, unsigned char type) const override {
if (!c1_->IsTrue(path, type))
return c2_->IsTrue(path, type);
return true;
}
virtual bool Countable() const override {
return c1_->Countable() && c2_->Countable();
;
}
virtual unsigned Count() const override {
return c1_->Count() + c2_->Count();
}
private:
unique_ptr<FindCond> c1_, c2_;
};
class DirentNode {
public:
virtual ~DirentNode() = default;
virtual const DirentNode* FindDir(StringPiece) const { return NULL; }
virtual bool RunFind(const FindCommand& fc,
const Loc& loc,
int d,
string* path,
unordered_map<const DirentNode*, string>* cur_read_dirs,
vector<string>& out) const = 0;
virtual bool IsDirectory() const = 0;
const string& base() const { return base_; }
protected:
explicit DirentNode(const string& name) {
base_ = Basename(name).as_string();
}
void PrintIfNecessary(const FindCommand& fc,
const string& path,
unsigned char type,
int d,
vector<string>& out) const {
if (fc.print_cond && !fc.print_cond->IsTrue(path, type))
return;
if (d < fc.mindepth)
return;
out.push_back(path);
}
string base_;
};
class DirentFileNode : public DirentNode {
public:
DirentFileNode(const string& name, unsigned char type)
: DirentNode(name), type_(type) {}
virtual bool RunFind(const FindCommand& fc,
const Loc&,
int d,
string* path,
unordered_map<const DirentNode*, string>*,
vector<string>& out) const override {
PrintIfNecessary(fc, *path, type_, d, out);
return true;
}
virtual bool IsDirectory() const override { return false; }
private:
unsigned char type_;
};
struct ScopedReadDirTracker {
public:
ScopedReadDirTracker(const DirentNode* n,
const string& path,
unordered_map<const DirentNode*, string>* cur_read_dirs)
: n_(NULL), cur_read_dirs_(cur_read_dirs) {
const auto& p = cur_read_dirs->emplace(n, path);
if (p.second) {
n_ = n;
} else {
conflicted_ = p.first->second;
}
}
~ScopedReadDirTracker() {
if (n_)
cur_read_dirs_->erase(n_);
}
bool ok() const { return conflicted_.empty(); }
const string& conflicted() const { return conflicted_; }
private:
string conflicted_;
const DirentNode* n_;
unordered_map<const DirentNode*, string>* cur_read_dirs_;
};
class DirentDirNode : public DirentNode {
public:
explicit DirentDirNode(const string& name) : DirentNode(name) {}
~DirentDirNode() {
for (auto& p : children_) {
delete p.second;
}
}
virtual const DirentNode* FindDir(StringPiece d) const override {
if (d.empty() || d == ".")
return this;
size_t index = d.find('/');
const string& p = d.substr(0, index).as_string();
if (p.empty() || p == ".")
return FindDir(d.substr(index + 1));
;
for (auto& child : children_) {
if (p == child.first) {
if (index == string::npos)
return child.second;
StringPiece nd = d.substr(index + 1);
return child.second->FindDir(nd);
}
}
return NULL;
}
virtual bool RunFind(const FindCommand& fc,
const Loc& loc,
int d,
string* path,
unordered_map<const DirentNode*, string>* cur_read_dirs,
vector<string>& out) const override {
ScopedReadDirTracker srdt(this, *path, cur_read_dirs);
if (!srdt.ok()) {
FIND_WARN_LOC(loc,
"FindEmulator: find: File system loop detected; `%s' "
"is part of the same file system loop as `%s'.",
path->c_str(), srdt.conflicted().c_str());
return true;
}
fc.read_dirs->insert(*path);
if (fc.prune_cond && fc.prune_cond->IsTrue(*path, DT_DIR)) {
if (fc.type != FindCommandType::FINDLEAVES) {
out.push_back(*path);
}
return true;
}
PrintIfNecessary(fc, *path, DT_DIR, d, out);
if (d >= fc.depth)
return true;
size_t orig_path_size = path->size();
if (fc.type == FindCommandType::FINDLEAVES) {
size_t orig_out_size = out.size();
for (const auto& p : children_) {
DirentNode* c = p.second;
// We will handle directories later.
if (c->IsDirectory())
continue;
if ((*path)[path->size() - 1] != '/')
*path += '/';
*path += c->base();
if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
return false;
path->resize(orig_path_size);
}
// Found a leaf, stop the search.
if (orig_out_size != out.size()) {
// If we've found all possible files in this directory, we don't need
// to add a regen dependency on the directory, we just need to ensure
// that the files are not removed.
if (fc.print_cond->Countable() &&
fc.print_cond->Count() == out.size() - orig_out_size) {
fc.read_dirs->erase(*path);
for (unsigned i = orig_out_size; i < out.size(); i++) {
fc.found_files->push_back(out[i]);
}
}
return true;
}
for (const auto& p : children_) {
DirentNode* c = p.second;
if (!c->IsDirectory())
continue;
if ((*path)[path->size() - 1] != '/')
*path += '/';
*path += c->base();
if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
return false;
path->resize(orig_path_size);
}
} else {
for (const auto& p : children_) {
DirentNode* c = p.second;
if ((*path)[path->size() - 1] != '/')
*path += '/';
*path += c->base();
if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
return false;
path->resize(orig_path_size);
}
}
return true;
}
virtual bool IsDirectory() const override { return true; }
void Add(const string& name, DirentNode* c) {
children_.emplace(children_.end(), name, c);
}
private:
vector<pair<string, DirentNode*>> children_;
};
class DirentSymlinkNode : public DirentNode {
public:
explicit DirentSymlinkNode(const string& name)
: DirentNode(name), to_(NULL), errno_(0) {}
virtual const DirentNode* FindDir(StringPiece d) const override {
if (errno_ == 0 && to_)
return to_->FindDir(d);
return NULL;
}
virtual bool RunFind(const FindCommand& fc,
const Loc& loc,
int d,
string* path,
unordered_map<const DirentNode*, string>* cur_read_dirs,
vector<string>& out) const override {
unsigned char type = DT_LNK;
if (fc.follows_symlinks && errno_ != ENOENT) {
if (errno_) {
if (fc.type != FindCommandType::FINDLEAVES) {
FIND_WARN_LOC(loc, "FindEmulator: find: `%s': %s", path->c_str(),
strerror(errno_));
}
return true;
}
if (!to_) {
LOG("FindEmulator does not support %s", path->c_str());
return false;
}
return to_->RunFind(fc, loc, d, path, cur_read_dirs, out);
}
PrintIfNecessary(fc, *path, type, d, out);
return true;
}
virtual bool IsDirectory() const override {
return errno_ == 0 && to_ && to_->IsDirectory();
}
void set_to(const DirentNode* to) { to_ = to; }
void set_errno(int e) { errno_ = e; }
private:
const DirentNode* to_;
int errno_;
};
class FindCommandParser {
public:
FindCommandParser(StringPiece cmd, FindCommand* fc)
: cmd_(cmd), fc_(fc), has_if_(false) {}
bool Parse() {
cur_ = cmd_;
if (!ParseImpl()) {
LOG("FindEmulator: Unsupported find command: %.*s", SPF(cmd_));
return false;
}
CHECK(TrimLeftSpace(cur_).empty());
return true;
}
private:
bool GetNextToken(StringPiece* tok) {
if (!unget_tok_.empty()) {
*tok = unget_tok_;
unget_tok_.clear();
return true;
}
cur_ = TrimLeftSpace(cur_);
if (cur_[0] == ';') {
*tok = cur_.substr(0, 1);
cur_ = cur_.substr(1);
return true;
}
if (cur_[0] == '&') {
if (cur_.get(1) != '&') {
return false;
}
*tok = cur_.substr(0, 2);
cur_ = cur_.substr(2);
return true;
}
size_t i = 0;
while (i < cur_.size() && !isspace(cur_[i]) && cur_[i] != ';' &&
cur_[i] != '&') {
i++;
}
*tok = cur_.substr(0, i);
cur_ = cur_.substr(i);
const char c = tok->get(0);
if (c == '\'' || c == '"') {
if (tok->size() < 2 || (*tok)[tok->size() - 1] != c)
return false;
*tok = tok->substr(1, tok->size() - 2);
return true;
} else {
// Support stripping off a leading backslash
if (c == '\\') {
*tok = tok->substr(1);
}
// But if there are any others, we can't support it, as unescaping would
// require allocation
if (tok->find("\\") != string::npos) {
return false;
}
}
return true;
}
void UngetToken(StringPiece tok) {
CHECK(unget_tok_.empty());
if (!tok.empty())
unget_tok_ = tok;
}
bool ParseTest() {
if (has_if_ || !fc_->testdir.empty())
return false;
StringPiece tok;
if (!GetNextToken(&tok) || tok != "-d")
return false;
if (!GetNextToken(&tok) || tok.empty())
return false;
fc_->testdir = tok.as_string();
return true;
}
FindCond* ParseFact(StringPiece tok) {
if (tok == "-not" || tok == "!") {
if (!GetNextToken(&tok) || tok.empty())
return NULL;
unique_ptr<FindCond> c(ParseFact(tok));
if (!c.get())
return NULL;
return new NotCond(c.release());
} else if (tok == "(") {
if (!GetNextToken(&tok) || tok.empty())
return NULL;
unique_ptr<FindCond> c(ParseExpr(tok));
if (!GetNextToken(&tok) || tok != ")") {
return NULL;
}
return c.release();
} else if (tok == "-name") {
if (!GetNextToken(&tok) || tok.empty())
return NULL;
return new NameCond(tok.as_string());
} else if (tok == "-type") {
if (!GetNextToken(&tok) || tok.empty())
return NULL;
char type;
if (tok == "b")
type = DT_BLK;
else if (tok == "c")
type = DT_CHR;
else if (tok == "d")
type = DT_DIR;
else if (tok == "p")
type = DT_FIFO;
else if (tok == "l")
type = DT_LNK;
else if (tok == "f")
type = DT_REG;
else if (tok == "s")
type = DT_SOCK;
else
return NULL;
return new TypeCond(type);
} else {
UngetToken(tok);
return NULL;
}
}
FindCond* ParseTerm(StringPiece tok) {
unique_ptr<FindCond> c(ParseFact(tok));
if (!c.get())
return NULL;
while (true) {
if (!GetNextToken(&tok))
return NULL;
if (tok == "-and" || tok == "-a") {
if (!GetNextToken(&tok) || tok.empty())
return NULL;
} else {
if (tok != "-not" && tok != "!" && tok != "(" && tok != "-name" &&
tok != "-type") {
UngetToken(tok);
return c.release();
}
}
unique_ptr<FindCond> r(ParseFact(tok));
if (!r.get()) {
return NULL;
}
c.reset(new AndCond(c.release(), r.release()));
}
}
FindCond* ParseExpr(StringPiece tok) {
unique_ptr<FindCond> c(ParseTerm(tok));
if (!c.get())
return NULL;
while (true) {
if (!GetNextToken(&tok))
return NULL;
if (tok != "-or" && tok != "-o") {
UngetToken(tok);
return c.release();
}
if (!GetNextToken(&tok) || tok.empty())
return NULL;
unique_ptr<FindCond> r(ParseTerm(tok));
if (!r.get()) {
return NULL;
}
c.reset(new OrCond(c.release(), r.release()));
}
}
// <expr> ::= <term> {<or> <term>}
// <term> ::= <fact> {[<and>] <fact>}
// <fact> ::= <not> <fact> | '(' <expr> ')' | <pred>
// <not> ::= '-not' | '!'
// <and> ::= '-and' | '-a'
// <or> ::= '-or' | '-o'
// <pred> ::= <name> | <type> | <maxdepth>
// <name> ::= '-name' NAME
// <type> ::= '-type' TYPE
// <maxdepth> ::= '-maxdepth' MAXDEPTH
FindCond* ParseFindCond(StringPiece tok) { return ParseExpr(tok); }
bool ParseFind() {
fc_->type = FindCommandType::FIND;
StringPiece tok;
while (true) {
if (!GetNextToken(&tok))
return false;
if (tok.empty() || tok == ";")
return true;
if (tok == "-L") {
fc_->follows_symlinks = true;
} else if (tok == "-prune") {
if (!fc_->print_cond || fc_->prune_cond)
return false;
if (!GetNextToken(&tok) || tok != "-o")
return false;
fc_->prune_cond.reset(fc_->print_cond.release());
} else if (tok == "-print") {
if (!GetNextToken(&tok) || !tok.empty())
return false;
return true;
} else if (tok == "-maxdepth") {
if (!GetNextToken(&tok) || tok.empty())
return false;
const string& depth_str = tok.as_string();
char* endptr;
long d = strtol(depth_str.c_str(), &endptr, 10);
if (endptr != depth_str.data() + depth_str.size() || d < 0 ||
d > INT_MAX) {
return false;
}
fc_->depth = d;
} else if (tok[0] == '-' || tok == "(" || tok == "!") {
if (fc_->print_cond.get())
return false;
FindCond* c = ParseFindCond(tok);
if (!c)
return false;
fc_->print_cond.reset(c);
} else if (tok == "2>") {
if (!GetNextToken(&tok) || tok != "/dev/null") {
return false;
}
fc_->redirect_to_devnull = true;
} else if (tok.find_first_of("|;&><*'\"") != string::npos) {
return false;
} else {
fc_->finddirs.push_back(tok.as_string());
}
}
}
bool ParseFindLeaves() {
fc_->type = FindCommandType::FINDLEAVES;
fc_->follows_symlinks = true;
StringPiece tok;
vector<string> findfiles;
while (true) {
if (!GetNextToken(&tok))
return false;
if (tok.empty()) {
if (fc_->finddirs.size() == 0) {
// backwards compatibility
if (findfiles.size() < 2)
return false;
fc_->finddirs.swap(findfiles);
fc_->print_cond.reset(new NameCond(fc_->finddirs.back()));
fc_->finddirs.pop_back();
} else {
if (findfiles.size() < 1)
return false;
for (auto& file : findfiles) {
FindCond* cond = new NameCond(file);
if (fc_->print_cond.get()) {
cond = new OrCond(fc_->print_cond.release(), cond);
}
CHECK(!fc_->print_cond.get());
fc_->print_cond.reset(cond);
}
}
return true;
}
if (HasPrefix(tok, "--prune=")) {
FindCond* cond =
new NameCond(tok.substr(strlen("--prune=")).as_string());
if (fc_->prune_cond.get()) {
cond = new OrCond(fc_->prune_cond.release(), cond);
}
CHECK(!fc_->prune_cond.get());
fc_->prune_cond.reset(cond);
} else if (HasPrefix(tok, "--mindepth=")) {
string mindepth_str = tok.substr(strlen("--mindepth=")).as_string();
char* endptr;
long d = strtol(mindepth_str.c_str(), &endptr, 10);
if (endptr != mindepth_str.data() + mindepth_str.size() ||
d < INT_MIN || d > INT_MAX) {
return false;
}
fc_->mindepth = d;
} else if (HasPrefix(tok, "--dir=")) {
StringPiece dir = tok.substr(strlen("--dir="));
fc_->finddirs.push_back(dir.as_string());
} else if (HasPrefix(tok, "--")) {
if (g_flags.werror_find_emulator) {
ERROR("Unknown flag in findleaves.py: %.*s", SPF(tok));
} else {
WARN("Unknown flag in findleaves.py: %.*s", SPF(tok));
}
return false;
} else {
findfiles.push_back(tok.as_string());
}
}
}
bool ParseImpl() {
while (true) {
StringPiece tok;
if (!GetNextToken(&tok))
return false;
if (tok.empty())
return true;
if (tok == "cd") {
if (!GetNextToken(&tok) || tok.empty() || !fc_->chdir.empty())
return false;
fc_->chdir = tok.as_string();
if (!GetNextToken(&tok) || (tok != ";" && tok != "&&"))
return false;
} else if (tok == "if") {
if (!GetNextToken(&tok) || tok != "[")
return false;
if (!ParseTest())
return false;
if (!GetNextToken(&tok) || tok != "]")
return false;
if (!GetNextToken(&tok) || tok != ";")
return false;
if (!GetNextToken(&tok) || tok != "then")
return false;
has_if_ = true;
} else if (tok == "test") {
if (!fc_->chdir.empty())
return false;
if (!ParseTest())
return false;
if (!GetNextToken(&tok) || tok != "&&")
return false;
} else if (tok == "find") {
if (!ParseFind())
return false;
if (has_if_) {
if (!GetNextToken(&tok) || tok != "fi")
return false;
}
if (!GetNextToken(&tok) || !tok.empty())
return false;
return true;
} else if (tok == "build/tools/findleaves.py" ||
tok == "build/make/tools/findleaves.py") {
if (!ParseFindLeaves())
return false;
return true;
} else {
return false;
}
}
}
StringPiece cmd_;
StringPiece cur_;
FindCommand* fc_;
bool has_if_;
StringPiece unget_tok_;
};
static FindEmulator* g_instance;
class FindEmulatorImpl : public FindEmulator {
public:
FindEmulatorImpl() : node_cnt_(0), is_initialized_(false) {
g_instance = this;
}
virtual ~FindEmulatorImpl() = default;
bool CanHandle(StringPiece s) const {
return (!HasPrefix(s, "../") && !HasPrefix(s, "/") &&
!HasPrefix(s, ".repo") && !HasPrefix(s, ".git"));
}
const DirentNode* FindDir(StringPiece d, bool* should_fallback) {
const DirentNode* r = root_->FindDir(d);
if (!r) {
*should_fallback = Exists(d);
}
return r;
}
virtual bool HandleFind(const string& cmd UNUSED,
const FindCommand& fc,
const Loc& loc,
string* out) override {
if (!CanHandle(fc.chdir)) {
LOG("FindEmulator: Cannot handle chdir (%.*s): %s", SPF(fc.chdir),
cmd.c_str());
return false;
}
if (!is_initialized_) {
ScopedTimeReporter tr("init find emulator time");
root_.reset(ConstructDirectoryTree(""));
if (!root_) {
ERROR("FindEmulator: Cannot open root directory");
}
ResolveSymlinks();
LOG_STAT("%d find nodes", node_cnt_);
is_initialized_ = true;
}
if (!fc.testdir.empty()) {
if (!CanHandle(fc.testdir)) {
LOG("FindEmulator: Cannot handle test dir (%.*s): %s", SPF(fc.testdir),
cmd.c_str());
return false;
}
bool should_fallback = false;
if (!FindDir(fc.testdir, &should_fallback)) {
LOG("FindEmulator: Test dir (%.*s) not found: %s", SPF(fc.testdir),
cmd.c_str());
return !should_fallback;
}
}
const DirentNode* root = root_.get();
if (!fc.chdir.empty()) {
if (!CanHandle(fc.chdir)) {
LOG("FindEmulator: Cannot handle chdir (%.*s): %s", SPF(fc.chdir),
cmd.c_str());
return false;
}
root = root->FindDir(fc.chdir);
if (!root) {
if (Exists(fc.chdir))
return false;
if (!fc.redirect_to_devnull) {
FIND_WARN_LOC(loc,
"FindEmulator: cd: %.*s: No such file or directory",
SPF(fc.chdir));
}
return true;
}
}
vector<string> results;
for (const string& finddir : fc.finddirs) {
if (!CanHandle(finddir)) {
LOG("FindEmulator: Cannot handle find dir (%s): %s", finddir.c_str(),
cmd.c_str());
return false;
}
const DirentNode* base;
base = root->FindDir(finddir);
if (!base) {
if (Exists(finddir)) {
return false;
}
if (!fc.redirect_to_devnull) {
FIND_WARN_LOC(loc,
"FindEmulator: find: `%s': No such file or directory",
ConcatDir(fc.chdir, finddir).c_str());
}
continue;
}
string path = finddir;
unordered_map<const DirentNode*, string> cur_read_dirs;
if (!base->RunFind(fc, loc, 0, &path, &cur_read_dirs, results)) {
LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
return false;
}
}
if (results.size() > 0) {
// Calculate and reserve necessary space in out
size_t new_length = 0;
for (const string& result : results) {
new_length += result.size() + 1;
}
out->reserve(out->size() + new_length - 1);
if (fc.type == FindCommandType::FINDLEAVES) {
sort(results.begin(), results.end());
}
WordWriter writer(out);
for (const string& result : results) {
writer.Write(result);
}
}
LOG("FindEmulator: OK");
return true;
}
private:
static unsigned char GetDtTypeFromStat(const struct stat& st) {
if (S_ISREG(st.st_mode)) {
return DT_REG;
} else if (S_ISDIR(st.st_mode)) {
return DT_DIR;
} else if (S_ISCHR(st.st_mode)) {
return DT_CHR;
} else if (S_ISBLK(st.st_mode)) {
return DT_BLK;
} else if (S_ISFIFO(st.st_mode)) {
return DT_FIFO;
} else if (S_ISLNK(st.st_mode)) {
return DT_LNK;
} else if (S_ISSOCK(st.st_mode)) {
return DT_SOCK;
} else {
return DT_UNKNOWN;
}
}
static unsigned char GetDtType(const string& path) {
struct stat st;
if (lstat(path.c_str(), &st)) {
PERROR("stat for %s", path.c_str());
}
return GetDtTypeFromStat(st);
}
DirentNode* ConstructDirectoryTree(const string& path) {
DIR* dir = opendir(path.empty() ? "." : path.c_str());
if (!dir) {
if (errno == ENOENT || errno == EACCES) {
LOG("opendir failed: %s", path.c_str());
return NULL;
} else {
PERROR("opendir failed: %s", path.c_str());
}
}
DirentDirNode* n = new DirentDirNode(path);
struct dirent* ent;
while ((ent = readdir(dir)) != NULL) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
!strcmp(ent->d_name, ".repo") || !strcmp(ent->d_name, ".git"))
continue;
string npath = path;
if (!path.empty())
npath += '/';
npath += ent->d_name;
DirentNode* c = NULL;
auto d_type = ent->d_type;
if (d_type == DT_UNKNOWN) {
d_type = GetDtType(npath);
CHECK(d_type != DT_UNKNOWN);
}
if (d_type == DT_DIR) {
c = ConstructDirectoryTree(npath);
if (c == NULL) {
continue;
}
} else if (d_type == DT_LNK) {
auto s = new DirentSymlinkNode(npath);
symlinks_.push_back(make_pair(npath, s));
c = s;
} else {
c = new DirentFileNode(npath, d_type);
}
node_cnt_++;
n->Add(ent->d_name, c);
}
closedir(dir);
return n;
}
void ResolveSymlinks() {
vector<pair<string, DirentSymlinkNode*>> symlinks;
symlinks.swap(symlinks_);
for (const auto& p : symlinks) {
const string& path = p.first;
DirentSymlinkNode* s = p.second;
char buf[PATH_MAX + 1];
buf[PATH_MAX] = 0;
ssize_t len = readlink(path.c_str(), buf, PATH_MAX);
if (len < 0) {
WARN("readlink failed: %s", path.c_str());
continue;
}
buf[len] = 0;
struct stat st;
unsigned char type = DT_UNKNOWN;
if (stat(path.c_str(), &st) == 0) {
type = GetDtTypeFromStat(st);
} else {
s->set_errno(errno);
LOG("stat failed: %s: %s", path.c_str(), strerror(errno));
}
if (*buf != '/') {
const string npath = ConcatDir(Dirname(path), buf);
bool should_fallback = false;
const DirentNode* to = FindDir(npath, &should_fallback);
if (to) {
s->set_to(to);
continue;
}
}
if (type == DT_DIR) {
if (path.find('/') == string::npos) {
DirentNode* dir = ConstructDirectoryTree(path);
if (dir != NULL) {
s->set_to(dir);
} else {
s->set_errno(errno);
}
}
} else if (type != DT_LNK && type != DT_UNKNOWN) {
s->set_to(new DirentFileNode(path, type));
}
}
if (!symlinks_.empty())
ResolveSymlinks();
}
unique_ptr<DirentNode> root_;
vector<pair<string, DirentSymlinkNode*>> symlinks_;
int node_cnt_;
bool is_initialized_;
};
} // namespace
FindCommand::FindCommand()
: follows_symlinks(false),
depth(INT_MAX),
mindepth(INT_MIN),
redirect_to_devnull(false),
found_files(new vector<string>()),
read_dirs(new unordered_set<string>()) {}
FindCommand::~FindCommand() {}
bool FindCommand::Parse(const string& cmd) {
FindCommandParser fcp(cmd, this);
if (!HasWord(cmd, "find") && !HasWord(cmd, "build/tools/findleaves.py") &&
!HasWord(cmd, "build/make/tools/findleaves.py"))
return false;
if (!fcp.Parse())
return false;
NormalizePath(&chdir);
NormalizePath(&testdir);
if (finddirs.empty())
finddirs.push_back(".");
return true;
}
FindEmulator* FindEmulator::Get() {
return g_instance;
}
void InitFindEmulator() {
new FindEmulatorImpl();
}