blob: 4c7cd8fe186f3a14eae9ce75223b85c506848598 [file] [log] [blame]
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +09001// 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
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090026#include <memory>
27#include <vector>
28
29//#undef NOLOG
30
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +090031#include "fileutil.h"
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090032#include "log.h"
33#include "string_piece.h"
34#include "strutil.h"
Shinichiro Hamajicbd34cd2015-07-05 02:53:04 +090035#include "timeutil.h"
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090036
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090037class FindCond {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090038 public:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090039 virtual ~FindCond() = default;
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +090040 virtual bool IsTrue(const string& path, unsigned char type) const = 0;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090041 protected:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090042 FindCond() = default;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090043};
44
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090045namespace {
46
47class NameCond : public FindCond {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090048 public:
49 explicit NameCond(const string& n)
50 : name_(n) {
51 }
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +090052 virtual bool IsTrue(const string& path, unsigned char) const override {
53 return fnmatch(name_.c_str(), Basename(path).data(), 0) == 0;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090054 }
55 private:
56 string name_;
57};
58
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090059class TypeCond : public FindCond {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090060 public:
61 explicit TypeCond(unsigned char t)
62 : type_(t) {
63 }
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +090064 virtual bool IsTrue(const string&, unsigned char type) const override {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090065 return type == type_;
66 }
67 private:
68 unsigned char type_;
69};
70
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090071class NotCond : public FindCond {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090072 public:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090073 NotCond(FindCond* c)
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090074 : c_(c) {
75 }
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +090076 virtual bool IsTrue(const string& path, unsigned char type) const override {
77 return !c_->IsTrue(path, type);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090078 }
79 private:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090080 unique_ptr<FindCond> c_;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090081};
82
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090083class AndCond : public FindCond {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090084 public:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090085 AndCond(FindCond* c1, FindCond* c2)
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090086 : c1_(c1), c2_(c2) {
87 }
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +090088 virtual bool IsTrue(const string& path, unsigned char type) const override {
89 if (c1_->IsTrue(path, type))
90 return c2_->IsTrue(path, type);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090091 return false;
92 }
93 private:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090094 unique_ptr<FindCond> c1_, c2_;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090095};
96
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090097class OrCond : public FindCond {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +090098 public:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +090099 OrCond(FindCond* c1, FindCond* c2)
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900100 : c1_(c1), c2_(c2) {
101 }
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +0900102 virtual bool IsTrue(const string& path, unsigned char type) const override {
103 if (!c1_->IsTrue(path, type))
104 return c2_->IsTrue(path, type);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900105 return true;
106 }
107 private:
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +0900108 unique_ptr<FindCond> c1_, c2_;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900109};
110
111class DirentNode {
112 public:
113 virtual ~DirentNode() = default;
114
115 virtual const DirentNode* FindDir(StringPiece) const {
116 return NULL;
117 }
Shinichiro Hamajid45a09d2015-07-02 00:35:57 +0900118 virtual bool RunFind(const FindCommand& fc, int d,
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900119 string* path,
120 unordered_map<const DirentNode*, string>* cur_read_dirs,
121 string* out) const = 0;
122
123 virtual bool IsDirectory() const = 0;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900124
125 const string& base() const { return base_; }
126
127 protected:
128 explicit DirentNode(const string& name) {
129 base_ = Basename(name).as_string();
130 }
131
132 void PrintIfNecessary(const FindCommand& fc,
133 const string& path,
134 unsigned char type,
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900135 int d,
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900136 string* out) const {
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +0900137 if (fc.print_cond && !fc.print_cond->IsTrue(path, type))
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900138 return;
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900139 if (d < fc.mindepth)
140 return;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900141 *out += path;
142 *out += ' ';
143 }
144
145 string base_;
146};
147
148class DirentFileNode : public DirentNode {
149 public:
150 DirentFileNode(const string& name, unsigned char type)
151 : DirentNode(name), type_(type) {
152 }
153
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900154 virtual bool RunFind(const FindCommand& fc, int d,
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900155 string* path,
156 unordered_map<const DirentNode*, string>*,
157 string* out) const {
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900158 PrintIfNecessary(fc, *path, type_, d, out);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900159 return true;
160 }
161
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900162 virtual bool IsDirectory() const override { return false; }
163
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900164 private:
165 unsigned char type_;
166};
167
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900168struct ScopedReadDirTracker {
169 public:
170 ScopedReadDirTracker(const DirentNode* n,
171 const string& path,
172 unordered_map<const DirentNode*, string>* cur_read_dirs)
173 : n_(NULL), cur_read_dirs_(cur_read_dirs) {
174 const auto& p = cur_read_dirs->emplace(n, path);
175 if (p.second) {
176 n_ = n;
177 } else {
178 conflicted_ = p.first->second;
179 }
180 }
181
182 ~ScopedReadDirTracker() {
183 if (n_)
184 cur_read_dirs_->erase(n_);
185 }
186
187 bool ok() const { return conflicted_.empty(); }
188 const string& conflicted() const { return conflicted_; }
189
190 private:
191 string conflicted_;
192 const DirentNode* n_;
193 unordered_map<const DirentNode*, string>* cur_read_dirs_;
194};
195
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900196class DirentDirNode : public DirentNode {
197 public:
198 explicit DirentDirNode(const string& name)
199 : DirentNode(name) {
200 }
Shinichiro Hamaji5a71a8b2015-08-06 19:23:18 +0900201
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900202 ~DirentDirNode() {
203 for (auto& p : children_) {
204 delete p.second;
205 }
206 }
207
208 virtual const DirentNode* FindDir(StringPiece d) const {
209 if (d.empty() || d == ".")
210 return this;
211 size_t index = d.find('/');
212 const string& p = d.substr(0, index).as_string();
Dan Willemsen48d6e8c2015-08-05 14:38:34 -0700213 for (auto& child : children_) {
214 if (p == child.first) {
215 if (index == string::npos)
216 return child.second;
217 StringPiece nd = d.substr(index + 1);
218 return child.second->FindDir(nd);
219 }
220 }
221 return NULL;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900222 }
223
Shinichiro Hamajid45a09d2015-07-02 00:35:57 +0900224 virtual bool RunFind(const FindCommand& fc, int d,
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900225 string* path,
226 unordered_map<const DirentNode*, string>* cur_read_dirs,
227 string* out) const {
228 ScopedReadDirTracker srdt(this, *path, cur_read_dirs);
229 if (!srdt.ok()) {
230 fprintf(stderr, "FindEmulator: find: File system loop detected; `%s' is "
231 "part of the same file system loop as `%s'.\n",
232 path->c_str(), srdt.conflicted().c_str());
233 return true;
234 }
235
236 fc.read_dirs->insert(*path);
Shinichiro Hamaji5a71a8b2015-08-06 19:23:18 +0900237
Shinichiro Hamaji31505ba2015-10-15 17:44:51 +0900238 if (fc.prune_cond && fc.prune_cond->IsTrue(*path, DT_DIR)) {
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900239 if (fc.type != FindCommandType::FINDLEAVES) {
240 *out += *path;
241 *out += ' ';
242 }
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900243 return true;
244 }
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900245
246 PrintIfNecessary(fc, *path, DT_DIR, d, out);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900247
Shinichiro Hamajid45a09d2015-07-02 00:35:57 +0900248 if (d >= fc.depth)
249 return true;
250
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900251 size_t orig_path_size = path->size();
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900252 if (fc.type == FindCommandType::FINDLEAVES) {
253 size_t orig_out_size = out->size();
254 for (const auto& p : children_) {
255 DirentNode* c = p.second;
256 // We will handle directories later.
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900257 if (c->IsDirectory())
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900258 continue;
259 if ((*path)[path->size()-1] != '/')
260 *path += '/';
261 *path += c->base();
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900262 if (!c->RunFind(fc, d + 1, path, cur_read_dirs, out))
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900263 return false;
264 path->resize(orig_path_size);
265 // Found a leaf, stop the search.
266 if (orig_out_size != out->size())
267 return true;
268 }
269
270 for (const auto& p : children_) {
271 DirentNode* c = p.second;
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900272 if (!c->IsDirectory())
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900273 continue;
274 if ((*path)[path->size()-1] != '/')
275 *path += '/';
276 *path += c->base();
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900277 if (!c->RunFind(fc, d + 1, path, cur_read_dirs, out))
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900278 return false;
279 path->resize(orig_path_size);
280 }
281 } else {
282 for (const auto& p : children_) {
283 DirentNode* c = p.second;
284 if ((*path)[path->size()-1] != '/')
285 *path += '/';
286 *path += c->base();
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900287 if (!c->RunFind(fc, d + 1, path, cur_read_dirs, out))
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900288 return false;
289 path->resize(orig_path_size);
290 }
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900291 }
292 return true;
293 }
294
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900295 virtual bool IsDirectory() const override { return true; }
296
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900297 void Add(const string& name, DirentNode* c) {
Shinichiro Hamajia6960242015-08-06 18:15:27 +0900298 children_.emplace(children_.end(), name, c);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900299 }
300
301 private:
Dan Willemsen48d6e8c2015-08-05 14:38:34 -0700302 vector<pair<string, DirentNode*>> children_;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900303};
304
305class DirentSymlinkNode : public DirentNode {
306 public:
307 explicit DirentSymlinkNode(const string& name)
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900308 : DirentNode(name), to_(NULL), errno_(0) {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900309 }
310
Shinichiro Hamaji407d8d42015-10-15 16:51:09 +0900311 virtual const DirentNode* FindDir(StringPiece d) const {
312 if (errno_ == 0 && to_)
313 return to_->FindDir(d);
314 return NULL;
315 }
316
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900317 virtual bool RunFind(const FindCommand& fc, int d,
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900318 string* path,
319 unordered_map<const DirentNode*, string>* cur_read_dirs,
320 string* out) const {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900321 unsigned char type = DT_LNK;
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900322 if (fc.follows_symlinks && errno_ != ENOENT) {
323 if (errno_) {
324 if (fc.type != FindCommandType::FINDLEAVES) {
325 fprintf(stderr, "FindEmulator: find: `%s': %s\n",
326 path->c_str(), strerror(errno_));
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900327 }
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900328 return true;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900329 }
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900330
331 if (!to_) {
332 LOG("FindEmulator does not support %s", path->c_str());
333 return false;
334 }
335
336 return to_->RunFind(fc, d, path, cur_read_dirs, out);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900337 }
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900338 PrintIfNecessary(fc, *path, type, d, out);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900339 return true;
340 }
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900341
342 virtual bool IsDirectory() const override {
343 return errno_ == 0 && to_ && to_->IsDirectory();
344 }
345
346 void set_to(const DirentNode* to) {
347 to_ = to;
348 }
349
350 void set_errno(int e) {
351 errno_ = e;
352 }
353
354 private:
355 const DirentNode* to_;
356 int errno_;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900357};
358
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900359class FindCommandParser {
360 public:
361 FindCommandParser(StringPiece cmd, FindCommand* fc)
362 : cmd_(cmd), fc_(fc), has_if_(false) {
363 }
364
365 bool Parse() {
366 cur_ = cmd_;
367 if (!ParseImpl()) {
368 LOG("FindEmulator: Unsupported find command: %.*s", SPF(cmd_));
369 return false;
370 }
371 CHECK(TrimLeftSpace(cur_).empty());
372 return true;
373 }
374
375 private:
376 bool GetNextToken(StringPiece* tok) {
377 if (!unget_tok_.empty()) {
378 *tok = unget_tok_;
379 unget_tok_.clear();
380 return true;
381 }
382
383 cur_ = TrimLeftSpace(cur_);
384
385 if (cur_[0] == ';') {
386 *tok = cur_.substr(0, 1);
387 cur_ = cur_.substr(1);
388 return true;
389 }
390 if (cur_[0] == '&') {
391 if (cur_.get(1) != '&') {
392 return false;
393 }
394 *tok = cur_.substr(0, 2);
395 cur_ = cur_.substr(2);
396 return true;
397 }
398
399 size_t i = 0;
400 while (i < cur_.size() && !isspace(cur_[i]) &&
401 cur_[i] != ';' && cur_[i] != '&') {
402 i++;
403 }
404
405 *tok = cur_.substr(0, i);
406 cur_ = cur_.substr(i);
407
408 const char c = tok->get(0);
409 if (c == '\'' || c == '"') {
410 if (tok->size() < 2 || (*tok)[tok->size()-1] != c)
411 return false;
412 *tok = tok->substr(1, tok->size() - 2);
413 return true;
414 }
415
416 return true;
417 }
418
419 void UngetToken(StringPiece tok) {
420 CHECK(unget_tok_.empty());
421 if (!tok.empty())
422 unget_tok_ = tok;
423 }
424
425 bool ParseTest() {
426 if (has_if_ || !fc_->testdir.empty())
427 return false;
428 StringPiece tok;
429 if (!GetNextToken(&tok) || tok != "-d")
430 return false;
431 if (!GetNextToken(&tok) || tok.empty())
432 return false;
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900433 fc_->testdir = tok.as_string();
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900434 return true;
435 }
436
437 FindCond* ParseFact(StringPiece tok) {
438 if (tok == "-not" || tok == "\\!") {
439 if (!GetNextToken(&tok) || tok.empty())
440 return NULL;
441 unique_ptr<FindCond> c(ParseFact(tok));
442 if (!c.get())
443 return NULL;
444 return new NotCond(c.release());
445 } else if (tok == "\\(") {
446 if (!GetNextToken(&tok) || tok.empty())
447 return NULL;
448 unique_ptr<FindCond> c(ParseExpr(tok));
449 if (!GetNextToken(&tok) || tok != "\\)") {
450 return NULL;
451 }
452 return c.release();
453 } else if (tok == "-name") {
454 if (!GetNextToken(&tok) || tok.empty())
455 return NULL;
456 return new NameCond(tok.as_string());
457 } else if (tok == "-type") {
458 if (!GetNextToken(&tok) || tok.empty())
459 return NULL;
460 char type;
461 if (tok == "b")
462 type = DT_BLK;
463 else if (tok == "c")
464 type = DT_CHR;
465 else if (tok == "d")
466 type = DT_DIR;
467 else if (tok == "p")
468 type = DT_FIFO;
469 else if (tok == "l")
470 type = DT_LNK;
471 else if (tok == "f")
472 type = DT_REG;
473 else if (tok == "s")
474 type = DT_SOCK;
475 else
476 return NULL;
477 return new TypeCond(type);
478 } else {
479 UngetToken(tok);
480 return NULL;
481 }
482 }
483
484 FindCond* ParseTerm(StringPiece tok) {
485 unique_ptr<FindCond> c(ParseFact(tok));
486 if (!c.get())
487 return NULL;
488 while (true) {
489 if (!GetNextToken(&tok))
490 return NULL;
491 if (tok != "-and" && tok != "-a") {
492 UngetToken(tok);
493 return c.release();
494 }
495 if (!GetNextToken(&tok) || tok.empty())
496 return NULL;
497 unique_ptr<FindCond> r(ParseFact(tok));
498 if (!r.get()) {
499 return NULL;
500 }
501 c.reset(new AndCond(c.release(), r.release()));
502 }
503 }
504
505 FindCond* ParseExpr(StringPiece tok) {
506 unique_ptr<FindCond> c(ParseTerm(tok));
507 if (!c.get())
508 return NULL;
509 while (true) {
510 if (!GetNextToken(&tok))
511 return NULL;
512 if (tok != "-or" && tok != "-o") {
513 UngetToken(tok);
514 return c.release();
515 }
516 if (!GetNextToken(&tok) || tok.empty())
517 return NULL;
518 unique_ptr<FindCond> r(ParseTerm(tok));
519 if (!r.get()) {
520 return NULL;
521 }
522 c.reset(new OrCond(c.release(), r.release()));
523 }
524 }
525
526 // <expr> ::= <term> {<or> <term>}
527 // <term> ::= <fact> {<and> <fact>}
528 // <fact> ::= <not> <fact> | '\(' <expr> '\)' | <pred>
529 // <not> ::= '-not' | '\!'
530 // <and> ::= '-and' | '-a'
531 // <or> ::= '-or' | '-o'
532 // <pred> ::= <name> | <type> | <maxdepth>
533 // <name> ::= '-name' NAME
534 // <type> ::= '-type' TYPE
535 // <maxdepth> ::= '-maxdepth' MAXDEPTH
536 FindCond* ParseFindCond(StringPiece tok) {
537 return ParseExpr(tok);
538 }
539
540 bool ParseFind() {
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900541 fc_->type = FindCommandType::FIND;
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900542 StringPiece tok;
543 while (true) {
544 if (!GetNextToken(&tok))
545 return false;
546 if (tok.empty() || tok == ";")
547 return true;
548
549 if (tok == "-L") {
550 fc_->follows_symlinks = true;
551 } else if (tok == "-prune") {
552 if (!fc_->print_cond || fc_->prune_cond)
553 return false;
554 if (!GetNextToken(&tok) || tok != "-o")
555 return false;
556 fc_->prune_cond.reset(fc_->print_cond.release());
557 } else if (tok == "-print") {
558 if (!GetNextToken(&tok) || !tok.empty())
559 return false;
560 return true;
561 } else if (tok == "-maxdepth") {
562 if (!GetNextToken(&tok) || tok.empty())
563 return false;
564 const string& depth_str = tok.as_string();
565 char* endptr;
566 long d = strtol(depth_str.c_str(), &endptr, 10);
567 if (endptr != depth_str.data() + depth_str.size() ||
568 d < 0 || d > INT_MAX) {
569 return false;
570 }
571 fc_->depth = d;
572 } else if (tok[0] == '-' || tok == "\\(") {
573 if (fc_->print_cond.get())
574 return false;
575 FindCond* c = ParseFindCond(tok);
576 if (!c)
577 return false;
578 fc_->print_cond.reset(c);
Shinichiro Hamaji89e0c4f2015-08-11 15:25:24 +0900579 } else if (tok == "2>") {
580 if (!GetNextToken(&tok) || tok != "/dev/null") {
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900581 return false;
Shinichiro Hamaji89e0c4f2015-08-11 15:25:24 +0900582 }
583 fc_->redirect_to_devnull = true;
584 } else if (tok.find_first_of("|;&><*'\"") != string::npos) {
585 return false;
586 } else {
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900587 fc_->finddirs.push_back(tok);
588 }
589 }
590 }
591
592 bool ParseFindLeaves() {
593 fc_->type = FindCommandType::FINDLEAVES;
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900594 fc_->follows_symlinks = true;
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900595 StringPiece tok;
596 while (true) {
597 if (!GetNextToken(&tok))
598 return false;
599 if (tok.empty()) {
600 if (fc_->finddirs.size() < 2)
601 return false;
602 fc_->print_cond.reset(new NameCond(fc_->finddirs.back().as_string()));
603 fc_->finddirs.pop_back();
604 return true;
605 }
606
607 if (HasPrefix(tok, "--prune=")) {
608 FindCond* cond = new NameCond(
609 tok.substr(strlen("--prune=")).as_string());
610 if (fc_->prune_cond.get()) {
611 cond = new OrCond(fc_->prune_cond.release(), cond);
612 }
613 CHECK(!fc_->prune_cond.get());
614 fc_->prune_cond.reset(cond);
615 } else if (HasPrefix(tok, "--mindepth=")) {
616 string mindepth_str = tok.substr(strlen("--mindepth=")).as_string();
617 char* endptr;
618 long d = strtol(mindepth_str.c_str(), &endptr, 10);
619 if (endptr != mindepth_str.data() + mindepth_str.size() ||
620 d < INT_MIN || d > INT_MAX) {
621 return false;
622 }
623 fc_->mindepth = d;
624 } else if (HasPrefix(tok, "--")) {
625 WARN("Unknown flag in findleaves.py: %.*s", SPF(tok));
626 return false;
627 } else {
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900628 fc_->finddirs.push_back(tok);
629 }
630 }
631 }
632
633 bool ParseImpl() {
634 while (true) {
635 StringPiece tok;
636 if (!GetNextToken(&tok))
637 return false;
638
639 if (tok.empty())
640 return true;
641
642 if (tok == "cd") {
643 if (!GetNextToken(&tok) || tok.empty() || !fc_->chdir.empty())
644 return false;
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900645 fc_->chdir = tok.as_string();
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900646 if (!GetNextToken(&tok) || (tok != ";" && tok != "&&"))
647 return false;
648 } else if (tok == "if") {
649 if (!GetNextToken(&tok) || tok != "[")
650 return false;
651 if (!ParseTest())
652 return false;
653 if (!GetNextToken(&tok) || tok != "]")
654 return false;
655 if (!GetNextToken(&tok) || tok != ";")
656 return false;
657 if (!GetNextToken(&tok) || tok != "then")
658 return false;
659 has_if_ = true;
660 } else if (tok == "test") {
661 if (!fc_->chdir.empty())
662 return false;
663 if (!ParseTest())
664 return false;
665 if (!GetNextToken(&tok) || tok != "&&")
666 return false;
667 } else if (tok == "find") {
668 if (!ParseFind())
669 return false;
670 if (has_if_) {
671 if (!GetNextToken(&tok) || tok != "fi")
672 return false;
673 }
674 if (!GetNextToken(&tok) || !tok.empty())
675 return false;
676 return true;
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900677 } else if (tok == "build/tools/findleaves.py") {
678 if (!ParseFindLeaves())
679 return false;
680 return true;
Shinichiro Hamaji39970312015-07-31 16:10:34 +0900681 } else {
682 return false;
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900683 }
684 }
685 }
686
687 StringPiece cmd_;
688 StringPiece cur_;
689 FindCommand* fc_;
690 bool has_if_;
691 StringPiece unget_tok_;
692};
693
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900694static FindEmulator* g_instance;
695
696class FindEmulatorImpl : public FindEmulator {
697 public:
Shinichiro Hamaji802ef962015-07-06 15:37:40 +0900698 FindEmulatorImpl()
Shinichiro Hamajia87bd132015-07-31 12:48:22 +0900699 : node_cnt_(0), is_initialized_(false) {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900700 g_instance = this;
701 }
702
703 virtual ~FindEmulatorImpl() = default;
704
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900705 bool CanHandle(StringPiece s) const {
706 return (!HasPrefix(s, "../") &&
707 !HasPrefix(s, "/") &&
708 !HasPrefix(s, ".repo") &&
709 !HasPrefix(s, ".git") &&
710 !HasPrefix(s, "out"));
711 }
712
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +0900713 const DirentNode* FindDir(StringPiece d, bool* should_fallback) {
714 const DirentNode* r = root_->FindDir(d);
715 if (!r) {
716 *should_fallback = Exists(d);
717 }
718 return r;
719 }
720
Shinichiro Hamajic9b0aca2015-07-31 16:47:56 +0900721 virtual bool HandleFind(const string& cmd UNUSED, const FindCommand& fc,
722 string* out) override {
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900723 if (!CanHandle(fc.chdir)) {
724 LOG("FindEmulator: Cannot handle chdir (%.*s): %s",
725 SPF(fc.chdir), cmd.c_str());
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900726 return false;
727 }
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900728
Shinichiro Hamajia87bd132015-07-31 12:48:22 +0900729 if (!is_initialized_) {
730 ScopedTimeReporter tr("init find emulator time");
731 root_.reset(ConstructDirectoryTree(""));
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900732 ResolveSymlinks();
Shinichiro Hamajia87bd132015-07-31 12:48:22 +0900733 LOG_STAT("%d find nodes", node_cnt_);
734 is_initialized_ = true;
735 }
736
Shinichiro Hamaji9a802b02015-08-11 15:06:45 +0900737 if (!fc.testdir.empty()) {
738 if (!CanHandle(fc.testdir)) {
739 LOG("FindEmulator: Cannot handle test dir (%.*s): %s",
740 SPF(fc.testdir), cmd.c_str());
741 return false;
742 }
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +0900743 bool should_fallback = false;
744 if (!FindDir(fc.testdir, &should_fallback)) {
Shinichiro Hamaji9a802b02015-08-11 15:06:45 +0900745 LOG("FindEmulator: Test dir (%.*s) not found: %s",
746 SPF(fc.testdir), cmd.c_str());
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +0900747 return !should_fallback;
Shinichiro Hamaji9a802b02015-08-11 15:06:45 +0900748 }
749 }
750
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900751 if (!fc.chdir.empty()) {
752 if (!CanHandle(fc.chdir)) {
753 LOG("FindEmulator: Cannot handle chdir (%.*s): %s",
754 SPF(fc.chdir), cmd.c_str());
755 return false;
756 }
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +0900757 bool should_fallback = false;
758 if (!FindDir(fc.chdir, &should_fallback)) {
759 if (should_fallback)
760 return false;
Shinichiro Hamaji89e0c4f2015-08-11 15:25:24 +0900761 if (!fc.redirect_to_devnull) {
762 fprintf(stderr,
763 "FindEmulator: cd: %.*s: No such file or directory\n",
764 SPF(fc.chdir));
765 }
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900766 return true;
767 }
768 }
769
Shinichiro Hamajifb1fb2c2015-07-02 01:04:59 +0900770 const size_t orig_out_size = out->size();
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900771 for (StringPiece finddir : fc.finddirs) {
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900772 const string dir = ConcatDir(fc.chdir, finddir);
773
774 if (!CanHandle(dir)) {
775 LOG("FindEmulator: Cannot handle find dir (%s): %s",
776 dir.c_str(), cmd.c_str());
777 out->resize(orig_out_size);
778 return false;
779 }
780
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +0900781 bool should_fallback = false;
782 const DirentNode* base = FindDir(dir, &should_fallback);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900783 if (!base) {
Shinichiro Hamajibd3bb232015-10-09 16:35:54 +0900784 if (should_fallback) {
785 out->resize(orig_out_size);
786 return false;
787 }
Shinichiro Hamaji89e0c4f2015-08-11 15:25:24 +0900788 if (!fc.redirect_to_devnull) {
789 fprintf(stderr,
790 "FindEmulator: find: `%s': No such file or directory\n",
791 ConcatDir(fc.chdir, finddir).c_str());
792 }
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900793 continue;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900794 }
795
796 string path = finddir.as_string();
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900797 unordered_map<const DirentNode*, string> cur_read_dirs;
798 if (!base->RunFind(fc, 0, &path, &cur_read_dirs, out)) {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900799 LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
Shinichiro Hamajifb1fb2c2015-07-02 01:04:59 +0900800 out->resize(orig_out_size);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900801 return false;
802 }
803 }
804
805 if (!out->empty() && (*out)[out->size()-1] == ' ')
806 out->resize(out->size()-1);
Shinichiro Hamajia6960242015-08-06 18:15:27 +0900807
808 if (fc.type == FindCommandType::FINDLEAVES) {
809 *out = SortWordsInString(*out);
810 }
811
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900812 LOG("FindEmulator: OK");
813 return true;
814 }
815
816 private:
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900817 static unsigned char GetDtTypeFromStat(const struct stat& st) {
Shinichiro Hamaji2055fa52015-09-08 13:42:38 +0900818 if (S_ISREG(st.st_mode)) {
819 return DT_REG;
820 } else if (S_ISDIR(st.st_mode)) {
821 return DT_DIR;
822 } else if (S_ISCHR(st.st_mode)) {
823 return DT_CHR;
824 } else if (S_ISBLK(st.st_mode)) {
825 return DT_BLK;
826 } else if (S_ISFIFO(st.st_mode)) {
827 return DT_FIFO;
828 } else if (S_ISLNK(st.st_mode)) {
829 return DT_LNK;
830 } else if (S_ISSOCK(st.st_mode)) {
831 return DT_SOCK;
832 } else {
833 return DT_UNKNOWN;
834 }
835 }
836
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900837 static unsigned char GetDtType(const string& path) {
838 struct stat st;
839 if (lstat(path.c_str(), &st)) {
840 PERROR("stat for %s", path.c_str());
841 }
842 return GetDtTypeFromStat(st);
843 }
844
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900845 DirentNode* ConstructDirectoryTree(const string& path) {
846 DIR* dir = opendir(path.empty() ? "." : path.c_str());
847 if (!dir)
848 PERROR("opendir failed: %s", path.c_str());
849
850 DirentDirNode* n = new DirentDirNode(path);
851
852 struct dirent* ent;
853 while ((ent = readdir(dir)) != NULL) {
854 if (!strcmp(ent->d_name, ".") ||
855 !strcmp(ent->d_name, "..") ||
856 !strcmp(ent->d_name, ".repo") ||
857 !strcmp(ent->d_name, ".git") ||
858 !strcmp(ent->d_name, "out"))
859 continue;
860
861 string npath = path;
862 if (!path.empty())
863 npath += '/';
864 npath += ent->d_name;
865
866 DirentNode* c = NULL;
Shinichiro Hamaji2055fa52015-09-08 13:42:38 +0900867 auto d_type = ent->d_type;
868 if (d_type == DT_UNKNOWN) {
869 d_type = GetDtType(npath);
870 CHECK(d_type != DT_UNKNOWN);
871 }
872 if (d_type == DT_DIR) {
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900873 c = ConstructDirectoryTree(npath);
Shinichiro Hamaji2055fa52015-09-08 13:42:38 +0900874 } else if (d_type == DT_LNK) {
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900875 auto s = new DirentSymlinkNode(npath);
876 symlinks_.push_back(make_pair(npath, s));
877 c = s;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900878 } else {
Shinichiro Hamaji2055fa52015-09-08 13:42:38 +0900879 c = new DirentFileNode(npath, d_type);
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900880 }
Shinichiro Hamaji802ef962015-07-06 15:37:40 +0900881 node_cnt_++;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900882 n->Add(ent->d_name, c);
883 }
884 closedir(dir);
885
886 return n;
887 }
888
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900889 void ResolveSymlinks() {
Shinichiro Hamajif2d31722015-10-15 17:30:32 +0900890 vector<pair<string, DirentSymlinkNode*>> symlinks;
891 symlinks.swap(symlinks_);
892 for (const auto& p : symlinks) {
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900893 const string& path = p.first;
894 DirentSymlinkNode* s = p.second;
895
896 char buf[PATH_MAX+1];
897 buf[PATH_MAX] = 0;
898 ssize_t len = readlink(path.c_str(), buf, PATH_MAX);
899 if (len < 0) {
900 WARN("readlink failed: %s", path.c_str());
901 continue;
902 }
903 buf[len] = 0;
904
905 struct stat st;
Shinichiro Hamaji407d8d42015-10-15 16:51:09 +0900906 unsigned char type = DT_UNKNOWN;
907 if (stat(path.c_str(), &st) == 0) {
908 type = GetDtTypeFromStat(st);
909 } else {
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900910 s->set_errno(errno);
911 LOG("stat failed: %s: %s", path.c_str(), strerror(errno));
912 }
913
914 if (*buf != '/') {
915 const string npath = ConcatDir(Dirname(path), buf);
916 bool should_fallback = false;
917 const DirentNode* to = FindDir(npath, &should_fallback);
918 if (to) {
919 s->set_to(to);
920 continue;
921 }
922 }
923
Shinichiro Hamaji407d8d42015-10-15 16:51:09 +0900924 if (type == DT_DIR) {
925 if (path.find('/') == string::npos) {
926 s->set_to(ConstructDirectoryTree(path));
927 }
928 } else if (type != DT_LNK && type != DT_UNKNOWN) {
929 s->set_to(new DirentFileNode(path, type));
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900930 }
931 }
Shinichiro Hamaji407d8d42015-10-15 16:51:09 +0900932
933 if (!symlinks_.empty())
934 ResolveSymlinks();
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900935 }
936
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900937 unique_ptr<DirentNode> root_;
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900938 vector<pair<string, DirentSymlinkNode*>> symlinks_;
Shinichiro Hamaji802ef962015-07-06 15:37:40 +0900939 int node_cnt_;
Shinichiro Hamajia87bd132015-07-31 12:48:22 +0900940 bool is_initialized_;
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900941};
942
943} // namespace
944
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +0900945FindCommand::FindCommand()
Shinichiro Hamaji5a71a8b2015-08-06 19:23:18 +0900946 : follows_symlinks(false), depth(INT_MAX), mindepth(INT_MIN),
Shinichiro Hamaji89e0c4f2015-08-11 15:25:24 +0900947 redirect_to_devnull(false),
Shinichiro Hamajib7175612015-10-13 16:15:34 +0900948 read_dirs(new unordered_set<string>()) {
Shinichiro Hamajia5a5ef62015-07-31 14:45:41 +0900949}
950
Shinichiro Hamajic9b0aca2015-07-31 16:47:56 +0900951FindCommand::~FindCommand() {
952}
953
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900954bool FindCommand::Parse(const string& cmd) {
955 FindCommandParser fcp(cmd, this);
Shinichiro Hamajie7a68222015-08-06 17:07:29 +0900956 if (!HasWord(cmd, "find") && !HasWord(cmd, "build/tools/findleaves.py"))
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900957 return false;
958
959 if (!fcp.Parse())
960 return false;
961
Shinichiro Hamaji3498f342015-08-11 14:23:12 +0900962 NormalizePath(&chdir);
963 NormalizePath(&testdir);
Shinichiro Hamaji0876e092015-07-31 15:52:43 +0900964 if (finddirs.empty())
965 finddirs.push_back(".");
966 return true;
967}
968
Shinichiro Hamaji5f57a992015-06-30 19:39:39 +0900969FindEmulator* FindEmulator::Get() {
970 return g_instance;
971}
972
973void InitFindEmulator() {
974 new FindEmulatorImpl();
975}