blob: f2768e79baa8ccd5cc9a424d3f68580e373d5447 [file] [log] [blame]
// 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 <map>
#include <memory>
#include <vector>
//#undef NOLOG
#include "log.h"
#include "string_piece.h"
#include "strutil.h"
#include "timeutil.h"
namespace {
class Cond {
public:
virtual ~Cond() = default;
virtual bool IsTrue(const string& name, unsigned char type) const = 0;
protected:
Cond() = default;
};
class NameCond : public Cond {
public:
explicit NameCond(const string& n)
: name_(n) {
}
virtual bool IsTrue(const string& name, unsigned char) const {
return fnmatch(name_.c_str(), name.c_str(), 0) == 0;
}
private:
string name_;
};
class TypeCond : public Cond {
public:
explicit TypeCond(unsigned char t)
: type_(t) {
}
virtual bool IsTrue(const string&, unsigned char type) const {
return type == type_;
}
private:
unsigned char type_;
};
class NotCond : public Cond {
public:
NotCond(Cond* c)
: c_(c) {
}
virtual bool IsTrue(const string& name, unsigned char type) const {
return !c_->IsTrue(name, type);
}
private:
unique_ptr<Cond> c_;
};
class AndCond : public Cond {
public:
AndCond(Cond* c1, Cond* c2)
: c1_(c1), c2_(c2) {
}
virtual bool IsTrue(const string& name, unsigned char type) const {
if (c1_->IsTrue(name, type))
return c2_->IsTrue(name, type);
return false;
}
private:
unique_ptr<Cond> c1_, c2_;
};
class OrCond : public Cond {
public:
OrCond(Cond* c1, Cond* c2)
: c1_(c1), c2_(c2) {
}
virtual bool IsTrue(const string& name, unsigned char type) const {
if (!c1_->IsTrue(name, type))
return c2_->IsTrue(name, type);
return true;
}
private:
unique_ptr<Cond> c1_, c2_;
};
struct FindCommand {
FindCommand()
: follows_symlinks(false), depth(INT_MAX) {
}
~FindCommand() {
}
StringPiece chdir;
StringPiece testdir;
vector<StringPiece> finddirs;
bool follows_symlinks;
unique_ptr<Cond> print_cond;
unique_ptr<Cond> prune_cond;
int depth;
};
class DirentNode {
public:
virtual ~DirentNode() = default;
virtual const DirentNode* FindDir(StringPiece) const {
return NULL;
}
virtual bool RunFind(const FindCommand& fc, int d,
string* path, string* out) 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,
string* out) const {
if (fc.print_cond && !fc.print_cond->IsTrue(base_, type))
return;
*out += path;
*out += ' ';
}
string base_;
};
class DirentFileNode : public DirentNode {
public:
DirentFileNode(const string& name, unsigned char type)
: DirentNode(name), type_(type) {
}
virtual bool RunFind(const FindCommand& fc, int,
string* path, string* out) const {
PrintIfNecessary(fc, *path, type_, out);
return true;
}
private:
unsigned char type_;
};
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 {
if (d.empty() || d == ".")
return this;
size_t index = d.find('/');
const string& p = d.substr(0, index).as_string();
auto found = children_.find(p);
if (found == children_.end())
return NULL;
if (index == string::npos)
return found->second;
StringPiece nd = d.substr(index + 1);
return found->second->FindDir(nd);
}
virtual bool RunFind(const FindCommand& fc, int d,
string* path, string* out) const {
if (fc.prune_cond && fc.prune_cond->IsTrue(base_, DT_DIR)) {
*out += *path;
*out += ' ';
return true;
}
PrintIfNecessary(fc, *path, DT_DIR, out);
if (d >= fc.depth)
return true;
size_t orig_path_size = path->size();
for (const auto& p : children_) {
DirentNode* c = p.second;
if ((*path)[path->size()-1] != '/')
*path += '/';
*path += c->base();
if (!c->RunFind(fc, d + 1, path, out))
return false;
path->resize(orig_path_size);
}
return true;
}
void Add(const string& name, DirentNode* c) {
auto p = children_.emplace(name, c);
CHECK(p.second);
}
private:
map<string, DirentNode*> children_;
};
class DirentSymlinkNode : public DirentNode {
public:
explicit DirentSymlinkNode(const string& name)
: DirentNode(name) {
}
virtual bool RunFind(const FindCommand& fc, int,
string* path, string* out) const {
unsigned char type = DT_LNK;
if (fc.follows_symlinks) {
// TODO
LOG("FindEmulator: symlink is hard");
return false;
char buf[PATH_MAX+1];
buf[PATH_MAX] = 0;
LOG("path=%s", path->c_str());
ssize_t len = readlink(path->c_str(), buf, PATH_MAX);
if (len > 0) {
buf[len] = 0;
string oldpath;
if (buf[0] != '/') {
Dirname(*path).AppendToString(&oldpath);
oldpath += '/';
}
oldpath += buf;
LOG("buf=%s old=%s", buf, oldpath.c_str());
struct stat st;
if (stat(oldpath.c_str(), &st) == 0) {
LOG("st OK");
if (S_ISREG(st.st_mode)) {
type = DT_REG;
} else if (S_ISDIR(st.st_mode)) {
type = DT_DIR;
} else if (S_ISCHR(st.st_mode)) {
type = DT_CHR;
} else if (S_ISBLK(st.st_mode)) {
type = DT_BLK;
} else if (S_ISFIFO(st.st_mode)) {
type = DT_FIFO;
} else if (S_ISLNK(st.st_mode)) {
type = DT_LNK;
} else if (S_ISSOCK(st.st_mode)) {
type = DT_SOCK;
} else {
return false;
}
}
}
}
PrintIfNecessary(fc, *path, type, out);
return true;
}
};
static FindEmulator* g_instance;
class FindEmulatorImpl : public FindEmulator {
public:
FindEmulatorImpl()
: node_cnt_(0) {
ScopedTimeReporter tr("init find emulator time");
root_.reset(ConstructDirectoryTree(""));
LOG_STAT("%d find nodes", node_cnt_);
g_instance = this;
}
virtual ~FindEmulatorImpl() = default;
static string ConcatDir(StringPiece b, StringPiece n) {
string r;
if (!b.empty()) {
b.AppendToString(&r);
r += '/';
}
n.AppendToString(&r);
NormalizePath(&r);
return r;
}
virtual bool HandleFind(const string& cmd, string* out) override {
FindCommand fc;
if (!ParseFindCommand(cmd, &fc))
return false;
if (HasPrefix(fc.chdir, "/")) {
LOG("FindEmulator: Cannot handle abspath: %s", cmd.c_str());
return false;
}
for (StringPiece finddir : fc.finddirs) {
if (HasPrefix(finddir, "/")) {
LOG("FindEmulator: Cannot handle abspath: %s", cmd.c_str());
return false;
}
}
if (!fc.testdir.empty() && !root_->FindDir(fc.testdir)) {
LOG("FindEmulator: Test dir (%.*s) not found: %s",
SPF(fc.testdir), cmd.c_str());
return false;
}
const size_t orig_out_size = out->size();
for (StringPiece finddir : fc.finddirs) {
const DirentNode* base = root_->FindDir(ConcatDir(fc.chdir, finddir));
if (!base) {
LOG("FindEmulator: Find dir (%s) not found: %s",
ConcatDir(fc.chdir, finddir).c_str(), cmd.c_str());
out->resize(orig_out_size);
return false;
}
string path = finddir.as_string();
if (!base->RunFind(fc, 0, &path, out)) {
LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
out->resize(orig_out_size);
return false;
}
}
if (!out->empty() && (*out)[out->size()-1] == ' ')
out->resize(out->size()-1);
LOG("FindEmulator: OK");
return true;
}
private:
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;
}
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;
return true;
}
Cond* ParseFact(StringPiece tok) {
if (tok == "-not" || tok == "\\!") {
if (!GetNextToken(&tok) || tok.empty())
return NULL;
unique_ptr<Cond> 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<Cond> 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;
}
}
Cond* ParseTerm(StringPiece tok) {
unique_ptr<Cond> c(ParseFact(tok));
if (!c.get())
return NULL;
while (true) {
if (!GetNextToken(&tok))
return NULL;
if (tok != "-and" && tok != "-a") {
UngetToken(tok);
return c.release();
}
if (!GetNextToken(&tok) || tok.empty())
return NULL;
unique_ptr<Cond> r(ParseFact(tok));
if (!r.get()) {
return NULL;
}
c.reset(new AndCond(c.release(), r.release()));
}
}
Cond* ParseExpr(StringPiece tok) {
unique_ptr<Cond> 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<Cond> 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
Cond* ParseFindCond(StringPiece tok) {
return ParseExpr(tok);
}
bool ParseFind() {
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 == "\\(") {
if (fc_->print_cond.get())
return false;
Cond* c = ParseFindCond(tok);
if (!c)
return false;
fc_->print_cond.reset(c);
} else {
fc_->finddirs.push_back(tok);
}
}
}
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;
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;
}
}
}
StringPiece cmd_;
StringPiece cur_;
FindCommand* fc_;
bool has_if_;
StringPiece unget_tok_;
};
DirentNode* ConstructDirectoryTree(const string& path) {
DIR* dir = opendir(path.empty() ? "." : path.c_str());
if (!dir)
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") ||
!strcmp(ent->d_name, "out"))
continue;
string npath = path;
if (!path.empty())
npath += '/';
npath += ent->d_name;
DirentNode* c = NULL;
if (ent->d_type == DT_DIR) {
c = ConstructDirectoryTree(npath);
} else if (ent->d_type == DT_LNK) {
c = new DirentSymlinkNode(npath);
} else {
c = new DirentFileNode(npath, ent->d_type);
}
node_cnt_++;
n->Add(ent->d_name, c);
}
closedir(dir);
return n;
}
bool ParseFindCommand(StringPiece cmd, FindCommand* fc) {
FindCommandParser fcp(cmd, fc);
if (cmd.find("find ") == string::npos)
return false;
if (!fcp.Parse())
return false;
if (fc->finddirs.empty())
fc->finddirs.push_back(".");
return true;
}
unique_ptr<DirentNode> root_;
int node_cnt_;
};
} // namespace
FindEmulator* FindEmulator::Get() {
return g_instance;
}
void InitFindEmulator() {
new FindEmulatorImpl();
}