// 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 #include #include #include #include #include #include #include #include //#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 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 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 c1_, c2_; }; struct FindCommand { FindCommand() : follows_symlinks(false), depth(INT_MAX) { } ~FindCommand() { } StringPiece chdir; StringPiece testdir; vector finddirs; bool follows_symlinks; unique_ptr print_cond; unique_ptr 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 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 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 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 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 r(ParseFact(tok)); if (!r.get()) { return NULL; } c.reset(new AndCond(c.release(), r.release())); } } Cond* ParseExpr(StringPiece tok) { unique_ptr 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 r(ParseTerm(tok)); if (!r.get()) { return NULL; } c.reset(new OrCond(c.release(), r.release())); } } // ::= { } // ::= { } // ::= | '\(' '\)' | // ::= '-not' | '\!' // ::= '-and' | '-a' // ::= '-or' | '-o' // ::= | | // ::= '-name' NAME // ::= '-type' TYPE // ::= '-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 root_; int node_cnt_; }; } // namespace FindEmulator* FindEmulator::Get() { return g_instance; } void InitFindEmulator() { new FindEmulatorImpl(); }