• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 Google Inc. All rights reserved
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // +build ignore
16 
17 #include "find.h"
18 
19 #include <dirent.h>
20 #include <fnmatch.h>
21 #include <limits.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 
26 #include <algorithm>
27 #include <memory>
28 #include <vector>
29 
30 //#undef NOLOG
31 
32 #include "fileutil.h"
33 #include "log.h"
34 #include "string_piece.h"
35 #include "strutil.h"
36 #include "timeutil.h"
37 
38 #define FIND_WARN_LOC(...)              \
39   do {                                  \
40     if (g_flags.werror_find_emulator) { \
41       ERROR_LOC(__VA_ARGS__);           \
42     } else {                            \
43       WARN_LOC(__VA_ARGS__);            \
44     }                                   \
45   } while (0)
46 
47 class FindCond {
48  public:
49   virtual ~FindCond() = default;
50   virtual bool IsTrue(const string& path, unsigned char type) const = 0;
51   virtual bool Countable() const = 0;
52   virtual unsigned Count() const = 0;
53 
54  protected:
55   FindCond() = default;
56 };
57 
58 namespace {
59 
60 class NameCond : public FindCond {
61  public:
NameCond(const string & n)62   explicit NameCond(const string& n) : name_(n) {
63     has_wildcard_ = (n.find_first_of("?*[") != string::npos);
64   }
IsTrue(const string & path,unsigned char) const65   virtual bool IsTrue(const string& path, unsigned char) const override {
66     return fnmatch(name_.c_str(), Basename(path).data(), 0) == 0;
67   }
Countable() const68   virtual bool Countable() const override { return !has_wildcard_; }
Count() const69   virtual unsigned Count() const override { return 1; }
70 
71  private:
72   string name_;
73   bool has_wildcard_;
74 };
75 
76 class TypeCond : public FindCond {
77  public:
TypeCond(unsigned char t)78   explicit TypeCond(unsigned char t) : type_(t) {}
IsTrue(const string &,unsigned char type) const79   virtual bool IsTrue(const string&, unsigned char type) const override {
80     return type == type_;
81   }
Countable() const82   virtual bool Countable() const override { return false; }
Count() const83   virtual unsigned Count() const override { return 0; }
84 
85  private:
86   unsigned char type_;
87 };
88 
89 class NotCond : public FindCond {
90  public:
NotCond(FindCond * c)91   NotCond(FindCond* c) : c_(c) {}
IsTrue(const string & path,unsigned char type) const92   virtual bool IsTrue(const string& path, unsigned char type) const override {
93     return !c_->IsTrue(path, type);
94   }
Countable() const95   virtual bool Countable() const override { return false; }
Count() const96   virtual unsigned Count() const override { return 0; }
97 
98  private:
99   unique_ptr<FindCond> c_;
100 };
101 
102 class AndCond : public FindCond {
103  public:
AndCond(FindCond * c1,FindCond * c2)104   AndCond(FindCond* c1, FindCond* c2) : c1_(c1), c2_(c2) {}
IsTrue(const string & path,unsigned char type) const105   virtual bool IsTrue(const string& path, unsigned char type) const override {
106     if (c1_->IsTrue(path, type))
107       return c2_->IsTrue(path, type);
108     return false;
109   }
Countable() const110   virtual bool Countable() const override { return false; }
Count() const111   virtual unsigned Count() const override { return 0; }
112 
113  private:
114   unique_ptr<FindCond> c1_, c2_;
115 };
116 
117 class OrCond : public FindCond {
118  public:
OrCond(FindCond * c1,FindCond * c2)119   OrCond(FindCond* c1, FindCond* c2) : c1_(c1), c2_(c2) {}
IsTrue(const string & path,unsigned char type) const120   virtual bool IsTrue(const string& path, unsigned char type) const override {
121     if (!c1_->IsTrue(path, type))
122       return c2_->IsTrue(path, type);
123     return true;
124   }
Countable() const125   virtual bool Countable() const override {
126     return c1_->Countable() && c2_->Countable();
127     ;
128   }
Count() const129   virtual unsigned Count() const override {
130     return c1_->Count() + c2_->Count();
131   }
132 
133  private:
134   unique_ptr<FindCond> c1_, c2_;
135 };
136 
137 class DirentNode {
138  public:
139   virtual ~DirentNode() = default;
140 
FindDir(StringPiece) const141   virtual const DirentNode* FindDir(StringPiece) const { return NULL; }
142   virtual bool RunFind(const FindCommand& fc,
143                        const Loc& loc,
144                        int d,
145                        string* path,
146                        unordered_map<const DirentNode*, string>* cur_read_dirs,
147                        vector<string>& out) const = 0;
148 
149   virtual bool IsDirectory() const = 0;
150 
base() const151   const string& base() const { return base_; }
152 
153  protected:
DirentNode(const string & name)154   explicit DirentNode(const string& name) {
155     base_ = Basename(name).as_string();
156   }
157 
PrintIfNecessary(const FindCommand & fc,const string & path,unsigned char type,int d,vector<string> & out) const158   void PrintIfNecessary(const FindCommand& fc,
159                         const string& path,
160                         unsigned char type,
161                         int d,
162                         vector<string>& out) const {
163     if (fc.print_cond && !fc.print_cond->IsTrue(path, type))
164       return;
165     if (d < fc.mindepth)
166       return;
167     out.push_back(path);
168   }
169 
170   string base_;
171 };
172 
173 class DirentFileNode : public DirentNode {
174  public:
DirentFileNode(const string & name,unsigned char type)175   DirentFileNode(const string& name, unsigned char type)
176       : DirentNode(name), type_(type) {}
177 
RunFind(const FindCommand & fc,const Loc &,int d,string * path,unordered_map<const DirentNode *,string> *,vector<string> & out) const178   virtual bool RunFind(const FindCommand& fc,
179                        const Loc&,
180                        int d,
181                        string* path,
182                        unordered_map<const DirentNode*, string>*,
183                        vector<string>& out) const override {
184     PrintIfNecessary(fc, *path, type_, d, out);
185     return true;
186   }
187 
IsDirectory() const188   virtual bool IsDirectory() const override { return false; }
189 
190  private:
191   unsigned char type_;
192 };
193 
194 struct ScopedReadDirTracker {
195  public:
ScopedReadDirTracker__anon336b8ed40111::ScopedReadDirTracker196   ScopedReadDirTracker(const DirentNode* n,
197                        const string& path,
198                        unordered_map<const DirentNode*, string>* cur_read_dirs)
199       : n_(NULL), cur_read_dirs_(cur_read_dirs) {
200     const auto& p = cur_read_dirs->emplace(n, path);
201     if (p.second) {
202       n_ = n;
203     } else {
204       conflicted_ = p.first->second;
205     }
206   }
207 
~ScopedReadDirTracker__anon336b8ed40111::ScopedReadDirTracker208   ~ScopedReadDirTracker() {
209     if (n_)
210       cur_read_dirs_->erase(n_);
211   }
212 
ok__anon336b8ed40111::ScopedReadDirTracker213   bool ok() const { return conflicted_.empty(); }
conflicted__anon336b8ed40111::ScopedReadDirTracker214   const string& conflicted() const { return conflicted_; }
215 
216  private:
217   string conflicted_;
218   const DirentNode* n_;
219   unordered_map<const DirentNode*, string>* cur_read_dirs_;
220 };
221 
222 class DirentDirNode : public DirentNode {
223  public:
DirentDirNode(const string & name)224   explicit DirentDirNode(const string& name) : DirentNode(name) {}
225 
~DirentDirNode()226   ~DirentDirNode() {
227     for (auto& p : children_) {
228       delete p.second;
229     }
230   }
231 
FindDir(StringPiece d) const232   virtual const DirentNode* FindDir(StringPiece d) const override {
233     if (d.empty() || d == ".")
234       return this;
235     size_t index = d.find('/');
236     const string& p = d.substr(0, index).as_string();
237     if (p.empty() || p == ".")
238       return FindDir(d.substr(index + 1));
239     ;
240     for (auto& child : children_) {
241       if (p == child.first) {
242         if (index == string::npos)
243           return child.second;
244         StringPiece nd = d.substr(index + 1);
245         return child.second->FindDir(nd);
246       }
247     }
248     return NULL;
249   }
250 
RunFind(const FindCommand & fc,const Loc & loc,int d,string * path,unordered_map<const DirentNode *,string> * cur_read_dirs,vector<string> & out) const251   virtual bool RunFind(const FindCommand& fc,
252                        const Loc& loc,
253                        int d,
254                        string* path,
255                        unordered_map<const DirentNode*, string>* cur_read_dirs,
256                        vector<string>& out) const override {
257     ScopedReadDirTracker srdt(this, *path, cur_read_dirs);
258     if (!srdt.ok()) {
259       FIND_WARN_LOC(loc,
260                     "FindEmulator: find: File system loop detected; `%s' "
261                     "is part of the same file system loop as `%s'.",
262                     path->c_str(), srdt.conflicted().c_str());
263       return true;
264     }
265 
266     fc.read_dirs->insert(*path);
267 
268     if (fc.prune_cond && fc.prune_cond->IsTrue(*path, DT_DIR)) {
269       if (fc.type != FindCommandType::FINDLEAVES) {
270         out.push_back(*path);
271       }
272       return true;
273     }
274 
275     PrintIfNecessary(fc, *path, DT_DIR, d, out);
276 
277     if (d >= fc.depth)
278       return true;
279 
280     size_t orig_path_size = path->size();
281     if (fc.type == FindCommandType::FINDLEAVES) {
282       size_t orig_out_size = out.size();
283       for (const auto& p : children_) {
284         DirentNode* c = p.second;
285         // We will handle directories later.
286         if (c->IsDirectory())
287           continue;
288         if ((*path)[path->size() - 1] != '/')
289           *path += '/';
290         *path += c->base();
291         if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
292           return false;
293         path->resize(orig_path_size);
294       }
295 
296       // Found a leaf, stop the search.
297       if (orig_out_size != out.size()) {
298         // If we've found all possible files in this directory, we don't need
299         // to add a regen dependency on the directory, we just need to ensure
300         // that the files are not removed.
301         if (fc.print_cond->Countable() &&
302             fc.print_cond->Count() == out.size() - orig_out_size) {
303           fc.read_dirs->erase(*path);
304           for (unsigned i = orig_out_size; i < out.size(); i++) {
305             fc.found_files->push_back(out[i]);
306           }
307         }
308 
309         return true;
310       }
311 
312       for (const auto& p : children_) {
313         DirentNode* c = p.second;
314         if (!c->IsDirectory())
315           continue;
316         if ((*path)[path->size() - 1] != '/')
317           *path += '/';
318         *path += c->base();
319         if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
320           return false;
321         path->resize(orig_path_size);
322       }
323     } else {
324       for (const auto& p : children_) {
325         DirentNode* c = p.second;
326         if ((*path)[path->size() - 1] != '/')
327           *path += '/';
328         *path += c->base();
329         if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
330           return false;
331         path->resize(orig_path_size);
332       }
333     }
334     return true;
335   }
336 
IsDirectory() const337   virtual bool IsDirectory() const override { return true; }
338 
Add(const string & name,DirentNode * c)339   void Add(const string& name, DirentNode* c) {
340     children_.emplace(children_.end(), name, c);
341   }
342 
343  private:
344   vector<pair<string, DirentNode*>> children_;
345 };
346 
347 class DirentSymlinkNode : public DirentNode {
348  public:
DirentSymlinkNode(const string & name)349   explicit DirentSymlinkNode(const string& name)
350       : DirentNode(name), to_(NULL), errno_(0) {}
351 
FindDir(StringPiece d) const352   virtual const DirentNode* FindDir(StringPiece d) const override {
353     if (errno_ == 0 && to_)
354       return to_->FindDir(d);
355     return NULL;
356   }
357 
RunFind(const FindCommand & fc,const Loc & loc,int d,string * path,unordered_map<const DirentNode *,string> * cur_read_dirs,vector<string> & out) const358   virtual bool RunFind(const FindCommand& fc,
359                        const Loc& loc,
360                        int d,
361                        string* path,
362                        unordered_map<const DirentNode*, string>* cur_read_dirs,
363                        vector<string>& out) const override {
364     unsigned char type = DT_LNK;
365     if (fc.follows_symlinks && errno_ != ENOENT) {
366       if (errno_) {
367         if (fc.type != FindCommandType::FINDLEAVES) {
368           FIND_WARN_LOC(loc, "FindEmulator: find: `%s': %s", path->c_str(),
369                         strerror(errno_));
370         }
371         return true;
372       }
373 
374       if (!to_) {
375         LOG("FindEmulator does not support %s", path->c_str());
376         return false;
377       }
378 
379       return to_->RunFind(fc, loc, d, path, cur_read_dirs, out);
380     }
381     PrintIfNecessary(fc, *path, type, d, out);
382     return true;
383   }
384 
IsDirectory() const385   virtual bool IsDirectory() const override {
386     return errno_ == 0 && to_ && to_->IsDirectory();
387   }
388 
set_to(const DirentNode * to)389   void set_to(const DirentNode* to) { to_ = to; }
390 
set_errno(int e)391   void set_errno(int e) { errno_ = e; }
392 
393  private:
394   const DirentNode* to_;
395   int errno_;
396 };
397 
398 class FindCommandParser {
399  public:
FindCommandParser(StringPiece cmd,FindCommand * fc)400   FindCommandParser(StringPiece cmd, FindCommand* fc)
401       : cmd_(cmd), fc_(fc), has_if_(false) {}
402 
Parse()403   bool Parse() {
404     cur_ = cmd_;
405     if (!ParseImpl()) {
406       LOG("FindEmulator: Unsupported find command: %.*s", SPF(cmd_));
407       return false;
408     }
409     CHECK(TrimLeftSpace(cur_).empty());
410     return true;
411   }
412 
413  private:
GetNextToken(StringPiece * tok)414   bool GetNextToken(StringPiece* tok) {
415     if (!unget_tok_.empty()) {
416       *tok = unget_tok_;
417       unget_tok_.clear();
418       return true;
419     }
420 
421     cur_ = TrimLeftSpace(cur_);
422 
423     if (cur_[0] == ';') {
424       *tok = cur_.substr(0, 1);
425       cur_ = cur_.substr(1);
426       return true;
427     }
428     if (cur_[0] == '&') {
429       if (cur_.get(1) != '&') {
430         return false;
431       }
432       *tok = cur_.substr(0, 2);
433       cur_ = cur_.substr(2);
434       return true;
435     }
436 
437     size_t i = 0;
438     while (i < cur_.size() && !isspace(cur_[i]) && cur_[i] != ';' &&
439            cur_[i] != '&') {
440       i++;
441     }
442 
443     *tok = cur_.substr(0, i);
444     cur_ = cur_.substr(i);
445 
446     const char c = tok->get(0);
447     if (c == '\'' || c == '"') {
448       if (tok->size() < 2 || (*tok)[tok->size() - 1] != c)
449         return false;
450       *tok = tok->substr(1, tok->size() - 2);
451       return true;
452     } else {
453       // Support stripping off a leading backslash
454       if (c == '\\') {
455         *tok = tok->substr(1);
456       }
457       // But if there are any others, we can't support it, as unescaping would
458       // require allocation
459       if (tok->find("\\") != string::npos) {
460         return false;
461       }
462     }
463 
464     return true;
465   }
466 
UngetToken(StringPiece tok)467   void UngetToken(StringPiece tok) {
468     CHECK(unget_tok_.empty());
469     if (!tok.empty())
470       unget_tok_ = tok;
471   }
472 
ParseTest()473   bool ParseTest() {
474     if (has_if_ || !fc_->testdir.empty())
475       return false;
476     StringPiece tok;
477     if (!GetNextToken(&tok) || tok != "-d")
478       return false;
479     if (!GetNextToken(&tok) || tok.empty())
480       return false;
481     fc_->testdir = tok.as_string();
482     return true;
483   }
484 
ParseFact(StringPiece tok)485   FindCond* ParseFact(StringPiece tok) {
486     if (tok == "-not" || tok == "!") {
487       if (!GetNextToken(&tok) || tok.empty())
488         return NULL;
489       unique_ptr<FindCond> c(ParseFact(tok));
490       if (!c.get())
491         return NULL;
492       return new NotCond(c.release());
493     } else if (tok == "(") {
494       if (!GetNextToken(&tok) || tok.empty())
495         return NULL;
496       unique_ptr<FindCond> c(ParseExpr(tok));
497       if (!GetNextToken(&tok) || tok != ")") {
498         return NULL;
499       }
500       return c.release();
501     } else if (tok == "-name") {
502       if (!GetNextToken(&tok) || tok.empty())
503         return NULL;
504       return new NameCond(tok.as_string());
505     } else if (tok == "-type") {
506       if (!GetNextToken(&tok) || tok.empty())
507         return NULL;
508       char type;
509       if (tok == "b")
510         type = DT_BLK;
511       else if (tok == "c")
512         type = DT_CHR;
513       else if (tok == "d")
514         type = DT_DIR;
515       else if (tok == "p")
516         type = DT_FIFO;
517       else if (tok == "l")
518         type = DT_LNK;
519       else if (tok == "f")
520         type = DT_REG;
521       else if (tok == "s")
522         type = DT_SOCK;
523       else
524         return NULL;
525       return new TypeCond(type);
526     } else {
527       UngetToken(tok);
528       return NULL;
529     }
530   }
531 
ParseTerm(StringPiece tok)532   FindCond* ParseTerm(StringPiece tok) {
533     unique_ptr<FindCond> c(ParseFact(tok));
534     if (!c.get())
535       return NULL;
536     while (true) {
537       if (!GetNextToken(&tok))
538         return NULL;
539       if (tok == "-and" || tok == "-a") {
540         if (!GetNextToken(&tok) || tok.empty())
541           return NULL;
542       } else {
543         if (tok != "-not" && tok != "!" && tok != "(" && tok != "-name" &&
544             tok != "-type") {
545           UngetToken(tok);
546           return c.release();
547         }
548       }
549       unique_ptr<FindCond> r(ParseFact(tok));
550       if (!r.get()) {
551         return NULL;
552       }
553       c.reset(new AndCond(c.release(), r.release()));
554     }
555   }
556 
ParseExpr(StringPiece tok)557   FindCond* ParseExpr(StringPiece tok) {
558     unique_ptr<FindCond> c(ParseTerm(tok));
559     if (!c.get())
560       return NULL;
561     while (true) {
562       if (!GetNextToken(&tok))
563         return NULL;
564       if (tok != "-or" && tok != "-o") {
565         UngetToken(tok);
566         return c.release();
567       }
568       if (!GetNextToken(&tok) || tok.empty())
569         return NULL;
570       unique_ptr<FindCond> r(ParseTerm(tok));
571       if (!r.get()) {
572         return NULL;
573       }
574       c.reset(new OrCond(c.release(), r.release()));
575     }
576   }
577 
578   // <expr> ::= <term> {<or> <term>}
579   // <term> ::= <fact> {[<and>] <fact>}
580   // <fact> ::= <not> <fact> | '(' <expr> ')' | <pred>
581   // <not> ::= '-not' | '!'
582   // <and> ::= '-and' | '-a'
583   // <or> ::= '-or' | '-o'
584   // <pred> ::= <name> | <type> | <maxdepth>
585   // <name> ::= '-name' NAME
586   // <type> ::= '-type' TYPE
587   // <maxdepth> ::= '-maxdepth' MAXDEPTH
ParseFindCond(StringPiece tok)588   FindCond* ParseFindCond(StringPiece tok) { return ParseExpr(tok); }
589 
ParseFind()590   bool ParseFind() {
591     fc_->type = FindCommandType::FIND;
592     StringPiece tok;
593     while (true) {
594       if (!GetNextToken(&tok))
595         return false;
596       if (tok.empty() || tok == ";")
597         return true;
598 
599       if (tok == "-L") {
600         fc_->follows_symlinks = true;
601       } else if (tok == "-prune") {
602         if (!fc_->print_cond || fc_->prune_cond)
603           return false;
604         if (!GetNextToken(&tok) || tok != "-o")
605           return false;
606         fc_->prune_cond.reset(fc_->print_cond.release());
607       } else if (tok == "-print") {
608         if (!GetNextToken(&tok) || !tok.empty())
609           return false;
610         return true;
611       } else if (tok == "-maxdepth") {
612         if (!GetNextToken(&tok) || tok.empty())
613           return false;
614         const string& depth_str = tok.as_string();
615         char* endptr;
616         long d = strtol(depth_str.c_str(), &endptr, 10);
617         if (endptr != depth_str.data() + depth_str.size() || d < 0 ||
618             d > INT_MAX) {
619           return false;
620         }
621         fc_->depth = d;
622       } else if (tok[0] == '-' || tok == "(" || tok == "!") {
623         if (fc_->print_cond.get())
624           return false;
625         FindCond* c = ParseFindCond(tok);
626         if (!c)
627           return false;
628         fc_->print_cond.reset(c);
629       } else if (tok == "2>") {
630         if (!GetNextToken(&tok) || tok != "/dev/null") {
631           return false;
632         }
633         fc_->redirect_to_devnull = true;
634       } else if (tok.find_first_of("|;&><*'\"") != string::npos) {
635         return false;
636       } else {
637         fc_->finddirs.push_back(tok.as_string());
638       }
639     }
640   }
641 
ParseFindLeaves()642   bool ParseFindLeaves() {
643     fc_->type = FindCommandType::FINDLEAVES;
644     fc_->follows_symlinks = true;
645     StringPiece tok;
646     vector<string> findfiles;
647     while (true) {
648       if (!GetNextToken(&tok))
649         return false;
650       if (tok.empty()) {
651         if (fc_->finddirs.size() == 0) {
652           // backwards compatibility
653           if (findfiles.size() < 2)
654             return false;
655           fc_->finddirs.swap(findfiles);
656           fc_->print_cond.reset(new NameCond(fc_->finddirs.back()));
657           fc_->finddirs.pop_back();
658         } else {
659           if (findfiles.size() < 1)
660             return false;
661           for (auto& file : findfiles) {
662             FindCond* cond = new NameCond(file);
663             if (fc_->print_cond.get()) {
664               cond = new OrCond(fc_->print_cond.release(), cond);
665             }
666             CHECK(!fc_->print_cond.get());
667             fc_->print_cond.reset(cond);
668           }
669         }
670         return true;
671       }
672 
673       if (HasPrefix(tok, "--prune=")) {
674         FindCond* cond =
675             new NameCond(tok.substr(strlen("--prune=")).as_string());
676         if (fc_->prune_cond.get()) {
677           cond = new OrCond(fc_->prune_cond.release(), cond);
678         }
679         CHECK(!fc_->prune_cond.get());
680         fc_->prune_cond.reset(cond);
681       } else if (HasPrefix(tok, "--mindepth=")) {
682         string mindepth_str = tok.substr(strlen("--mindepth=")).as_string();
683         char* endptr;
684         long d = strtol(mindepth_str.c_str(), &endptr, 10);
685         if (endptr != mindepth_str.data() + mindepth_str.size() ||
686             d < INT_MIN || d > INT_MAX) {
687           return false;
688         }
689         fc_->mindepth = d;
690       } else if (HasPrefix(tok, "--dir=")) {
691         StringPiece dir = tok.substr(strlen("--dir="));
692         fc_->finddirs.push_back(dir.as_string());
693       } else if (HasPrefix(tok, "--")) {
694         if (g_flags.werror_find_emulator) {
695           ERROR("Unknown flag in findleaves.py: %.*s", SPF(tok));
696         } else {
697           WARN("Unknown flag in findleaves.py: %.*s", SPF(tok));
698         }
699         return false;
700       } else {
701         findfiles.push_back(tok.as_string());
702       }
703     }
704   }
705 
ParseImpl()706   bool ParseImpl() {
707     while (true) {
708       StringPiece tok;
709       if (!GetNextToken(&tok))
710         return false;
711 
712       if (tok.empty())
713         return true;
714 
715       if (tok == "cd") {
716         if (!GetNextToken(&tok) || tok.empty() || !fc_->chdir.empty())
717           return false;
718         fc_->chdir = tok.as_string();
719         if (!GetNextToken(&tok) || (tok != ";" && tok != "&&"))
720           return false;
721       } else if (tok == "if") {
722         if (!GetNextToken(&tok) || tok != "[")
723           return false;
724         if (!ParseTest())
725           return false;
726         if (!GetNextToken(&tok) || tok != "]")
727           return false;
728         if (!GetNextToken(&tok) || tok != ";")
729           return false;
730         if (!GetNextToken(&tok) || tok != "then")
731           return false;
732         has_if_ = true;
733       } else if (tok == "test") {
734         if (!fc_->chdir.empty())
735           return false;
736         if (!ParseTest())
737           return false;
738         if (!GetNextToken(&tok) || tok != "&&")
739           return false;
740       } else if (tok == "find") {
741         if (!ParseFind())
742           return false;
743         if (has_if_) {
744           if (!GetNextToken(&tok) || tok != "fi")
745             return false;
746         }
747         if (!GetNextToken(&tok) || !tok.empty())
748           return false;
749         return true;
750       } else if (tok == "build/tools/findleaves.py" ||
751                  tok == "build/make/tools/findleaves.py") {
752         if (!ParseFindLeaves())
753           return false;
754         return true;
755       } else {
756         return false;
757       }
758     }
759   }
760 
761   StringPiece cmd_;
762   StringPiece cur_;
763   FindCommand* fc_;
764   bool has_if_;
765   StringPiece unget_tok_;
766 };
767 
768 static FindEmulator* g_instance;
769 
770 class FindEmulatorImpl : public FindEmulator {
771  public:
FindEmulatorImpl()772   FindEmulatorImpl() : node_cnt_(0), is_initialized_(false) {
773     g_instance = this;
774   }
775 
776   virtual ~FindEmulatorImpl() = default;
777 
CanHandle(StringPiece s) const778   bool CanHandle(StringPiece s) const {
779     return (!HasPrefix(s, "../") && !HasPrefix(s, "/") &&
780             !HasPrefix(s, ".repo") && !HasPrefix(s, ".git"));
781   }
782 
FindDir(StringPiece d,bool * should_fallback)783   const DirentNode* FindDir(StringPiece d, bool* should_fallback) {
784     const DirentNode* r = root_->FindDir(d);
785     if (!r) {
786       *should_fallback = Exists(d);
787     }
788     return r;
789   }
790 
HandleFind(const string & cmd UNUSED,const FindCommand & fc,const Loc & loc,string * out)791   virtual bool HandleFind(const string& cmd UNUSED,
792                           const FindCommand& fc,
793                           const Loc& loc,
794                           string* out) override {
795     if (!CanHandle(fc.chdir)) {
796       LOG("FindEmulator: Cannot handle chdir (%.*s): %s", SPF(fc.chdir),
797           cmd.c_str());
798       return false;
799     }
800 
801     if (!is_initialized_) {
802       ScopedTimeReporter tr("init find emulator time");
803       root_.reset(ConstructDirectoryTree(""));
804       if (!root_) {
805         ERROR("FindEmulator: Cannot open root directory");
806       }
807       ResolveSymlinks();
808       LOG_STAT("%d find nodes", node_cnt_);
809       is_initialized_ = true;
810     }
811 
812     if (!fc.testdir.empty()) {
813       if (!CanHandle(fc.testdir)) {
814         LOG("FindEmulator: Cannot handle test dir (%.*s): %s", SPF(fc.testdir),
815             cmd.c_str());
816         return false;
817       }
818       bool should_fallback = false;
819       if (!FindDir(fc.testdir, &should_fallback)) {
820         LOG("FindEmulator: Test dir (%.*s) not found: %s", SPF(fc.testdir),
821             cmd.c_str());
822         return !should_fallback;
823       }
824     }
825 
826     const DirentNode* root = root_.get();
827 
828     if (!fc.chdir.empty()) {
829       if (!CanHandle(fc.chdir)) {
830         LOG("FindEmulator: Cannot handle chdir (%.*s): %s", SPF(fc.chdir),
831             cmd.c_str());
832         return false;
833       }
834       root = root->FindDir(fc.chdir);
835       if (!root) {
836         if (Exists(fc.chdir))
837           return false;
838         if (!fc.redirect_to_devnull) {
839           FIND_WARN_LOC(loc,
840                         "FindEmulator: cd: %.*s: No such file or directory",
841                         SPF(fc.chdir));
842         }
843         return true;
844       }
845     }
846 
847     vector<string> results;
848     for (const string& finddir : fc.finddirs) {
849       if (!CanHandle(finddir)) {
850         LOG("FindEmulator: Cannot handle find dir (%s): %s", finddir.c_str(),
851             cmd.c_str());
852         return false;
853       }
854 
855       const DirentNode* base;
856       base = root->FindDir(finddir);
857       if (!base) {
858         if (Exists(finddir)) {
859           return false;
860         }
861         if (!fc.redirect_to_devnull) {
862           FIND_WARN_LOC(loc,
863                         "FindEmulator: find: `%s': No such file or directory",
864                         ConcatDir(fc.chdir, finddir).c_str());
865         }
866         continue;
867       }
868 
869       string path = finddir;
870       unordered_map<const DirentNode*, string> cur_read_dirs;
871       if (!base->RunFind(fc, loc, 0, &path, &cur_read_dirs, results)) {
872         LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
873         return false;
874       }
875     }
876 
877     if (results.size() > 0) {
878       // Calculate and reserve necessary space in out
879       size_t new_length = 0;
880       for (const string& result : results) {
881         new_length += result.size() + 1;
882       }
883       out->reserve(out->size() + new_length - 1);
884 
885       if (fc.type == FindCommandType::FINDLEAVES) {
886         sort(results.begin(), results.end());
887       }
888 
889       WordWriter writer(out);
890       for (const string& result : results) {
891         writer.Write(result);
892       }
893     }
894 
895     LOG("FindEmulator: OK");
896     return true;
897   }
898 
899  private:
GetDtTypeFromStat(const struct stat & st)900   static unsigned char GetDtTypeFromStat(const struct stat& st) {
901     if (S_ISREG(st.st_mode)) {
902       return DT_REG;
903     } else if (S_ISDIR(st.st_mode)) {
904       return DT_DIR;
905     } else if (S_ISCHR(st.st_mode)) {
906       return DT_CHR;
907     } else if (S_ISBLK(st.st_mode)) {
908       return DT_BLK;
909     } else if (S_ISFIFO(st.st_mode)) {
910       return DT_FIFO;
911     } else if (S_ISLNK(st.st_mode)) {
912       return DT_LNK;
913     } else if (S_ISSOCK(st.st_mode)) {
914       return DT_SOCK;
915     } else {
916       return DT_UNKNOWN;
917     }
918   }
919 
GetDtType(const string & path)920   static unsigned char GetDtType(const string& path) {
921     struct stat st;
922     if (lstat(path.c_str(), &st)) {
923       PERROR("stat for %s", path.c_str());
924     }
925     return GetDtTypeFromStat(st);
926   }
927 
ConstructDirectoryTree(const string & path)928   DirentNode* ConstructDirectoryTree(const string& path) {
929     DIR* dir = opendir(path.empty() ? "." : path.c_str());
930     if (!dir) {
931       if (errno == ENOENT || errno == EACCES) {
932         LOG("opendir failed: %s", path.c_str());
933         return NULL;
934       } else {
935         PERROR("opendir failed: %s", path.c_str());
936       }
937     }
938 
939     DirentDirNode* n = new DirentDirNode(path);
940 
941     struct dirent* ent;
942     while ((ent = readdir(dir)) != NULL) {
943       if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
944           !strcmp(ent->d_name, ".repo") || !strcmp(ent->d_name, ".git"))
945         continue;
946 
947       string npath = path;
948       if (!path.empty())
949         npath += '/';
950       npath += ent->d_name;
951 
952       DirentNode* c = NULL;
953       auto d_type = ent->d_type;
954       if (d_type == DT_UNKNOWN) {
955         d_type = GetDtType(npath);
956         CHECK(d_type != DT_UNKNOWN);
957       }
958       if (d_type == DT_DIR) {
959         c = ConstructDirectoryTree(npath);
960         if (c == NULL) {
961           continue;
962         }
963       } else if (d_type == DT_LNK) {
964         auto s = new DirentSymlinkNode(npath);
965         symlinks_.push_back(make_pair(npath, s));
966         c = s;
967       } else {
968         c = new DirentFileNode(npath, d_type);
969       }
970       node_cnt_++;
971       n->Add(ent->d_name, c);
972     }
973     closedir(dir);
974 
975     return n;
976   }
977 
ResolveSymlinks()978   void ResolveSymlinks() {
979     vector<pair<string, DirentSymlinkNode*>> symlinks;
980     symlinks.swap(symlinks_);
981     for (const auto& p : symlinks) {
982       const string& path = p.first;
983       DirentSymlinkNode* s = p.second;
984 
985       char buf[PATH_MAX + 1];
986       buf[PATH_MAX] = 0;
987       ssize_t len = readlink(path.c_str(), buf, PATH_MAX);
988       if (len < 0) {
989         WARN("readlink failed: %s", path.c_str());
990         continue;
991       }
992       buf[len] = 0;
993 
994       struct stat st;
995       unsigned char type = DT_UNKNOWN;
996       if (stat(path.c_str(), &st) == 0) {
997         type = GetDtTypeFromStat(st);
998       } else {
999         s->set_errno(errno);
1000         LOG("stat failed: %s: %s", path.c_str(), strerror(errno));
1001       }
1002 
1003       if (*buf != '/') {
1004         const string npath = ConcatDir(Dirname(path), buf);
1005         bool should_fallback = false;
1006         const DirentNode* to = FindDir(npath, &should_fallback);
1007         if (to) {
1008           s->set_to(to);
1009           continue;
1010         }
1011       }
1012 
1013       if (type == DT_DIR) {
1014         if (path.find('/') == string::npos) {
1015           DirentNode* dir = ConstructDirectoryTree(path);
1016           if (dir != NULL) {
1017             s->set_to(dir);
1018           } else {
1019             s->set_errno(errno);
1020           }
1021         }
1022       } else if (type != DT_LNK && type != DT_UNKNOWN) {
1023         s->set_to(new DirentFileNode(path, type));
1024       }
1025     }
1026 
1027     if (!symlinks_.empty())
1028       ResolveSymlinks();
1029   }
1030 
1031   unique_ptr<DirentNode> root_;
1032   vector<pair<string, DirentSymlinkNode*>> symlinks_;
1033   int node_cnt_;
1034   bool is_initialized_;
1035 };
1036 
1037 }  // namespace
1038 
FindCommand()1039 FindCommand::FindCommand()
1040     : follows_symlinks(false),
1041       depth(INT_MAX),
1042       mindepth(INT_MIN),
1043       redirect_to_devnull(false),
1044       found_files(new vector<string>()),
1045       read_dirs(new unordered_set<string>()) {}
1046 
~FindCommand()1047 FindCommand::~FindCommand() {}
1048 
Parse(const string & cmd)1049 bool FindCommand::Parse(const string& cmd) {
1050   FindCommandParser fcp(cmd, this);
1051   if (!HasWord(cmd, "find") && !HasWord(cmd, "build/tools/findleaves.py") &&
1052       !HasWord(cmd, "build/make/tools/findleaves.py"))
1053     return false;
1054 
1055   if (!fcp.Parse())
1056     return false;
1057 
1058   NormalizePath(&chdir);
1059   NormalizePath(&testdir);
1060   if (finddirs.empty())
1061     finddirs.push_back(".");
1062   return true;
1063 }
1064 
Get()1065 FindEmulator* FindEmulator::Get() {
1066   return g_instance;
1067 }
1068 
InitFindEmulator()1069 void InitFindEmulator() {
1070   new FindEmulatorImpl();
1071 }
1072