Loading commands/filesystem/find.cpp +59 −44 Changes for commands/filesystem/find.cpp: 59 added lines, 44 removed lines. Original line number Diff line number Diff line Loading @@ -48,19 +48,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace fresh { namespace cmd { // POSIX find with the GNU extensions build scripts rely on (rpm's // brp-* scripts: -print0, -inum, -regex, "!", "\( ... -o ... \)"). // The expression is parsed into a tree of Node once, then evaluated per // visited file with the usual short-circuit semantics; an expression // without any action gets an implicit "-print" around it. // POSIX find with GNU extensions build scripts rely on. class FindCommand : public ShellCommand { public: FindCommand():ShellCommand("find"){} private: struct Entry { std::string path; // as printed (start path + relative part) std::string name; // last component std::string path; // start path + relative part std::string name; // last path component struct stat st; int depth; }; Loading @@ -75,18 +71,21 @@ namespace fresh { namespace cmd { struct Exec; enum SymlinkMode { DEREF_NEVER, DEREF_COMMAND_LINE, DEREF_ALWAYS }; struct State { SymlinkMode symlinkMode = DEREF_NEVER; bool followLinks = false; bool depthFirst = false; bool xdev = false; int maxDepth = -1; int minDepth = 0; bool quit = false; bool prune=false; // set by -prune for the current entry bool prune = false; int status = 0; dev_t startDev = 0; time_t now = 0; std::vector<Exec*> batchExecs; // "-exec ... {} +" to flush at the end std::vector<Exec*> batchExecs; }; // ---- expression nodes ---- Loading @@ -95,18 +94,22 @@ namespace fresh { namespace cmd { NodePtr l, r; bool eval(Entry &e, State &s) override { return l->eval(e, s) && r->eval(e, s); } }; struct Or : Node { NodePtr l, r; bool eval(Entry &e, State &s) override { return l->eval(e, s) || r->eval(e, s); } }; struct Comma : Node { NodePtr l, r; bool eval(Entry &e, State &s) override { l->eval(e, s); return r->eval(e, s); } }; struct Not : Node { NodePtr n; bool eval(Entry &e, State &s) override { return !n->eval(e, s); } }; struct Const : Node { bool v; explicit Const(bool b):v(b){} Loading @@ -120,20 +123,17 @@ namespace fresh { namespace cmd { bool eval(Entry &e, State &) override { std::string subject = wholePath ? e.path : e.name; std::string p = pat; if(icase){ for(auto &c: subject) c=(char)tolower((unsigned char)c); for(auto &c: p) c=(char)tolower((unsigned char)c); } return fnmatch(p.c_str(),subject.c_str(),0)==0; int flags = icase ? FNM_CASEFOLD : 0; return fnmatch(p.c_str(), subject.c_str(), flags) == 0; } }; // GNU find matches a regex against the WHOLE path struct Regex : Node { regex_t re; bool ok = false; Regex(const std::string &p, bool icase) { ok=regcomp(&re,p.c_str(),icase ? REG_ICASE : 0)==0; int flags = REG_EXTENDED | (icase ? REG_ICASE : 0); ok = (regcomp(&re, p.c_str(), flags) == 0); } ~Regex() { if(ok) regfree(&re); } bool eval(Entry &e, State &) override { Loading @@ -145,7 +145,7 @@ namespace fresh { namespace cmd { }; struct Type : Node { std::string types; // "f", "d", "f,d" ... std::string types; explicit Type(const std::string &t):types(t){} bool eval(Entry &e, State &) override { char t = '?'; Loading @@ -161,9 +161,9 @@ namespace fresh { namespace cmd { } }; // numeric comparison: "N" exact, "+N" greater, "-N" less struct Num { int cmp=0; long long v=0; int cmp = 0; long long v = 0; bool parse(const std::string &s) { std::string t = s; if(!t.empty() && t[0] == '+'){ cmp = 1; t = t.substr(1); } Loading Loading @@ -193,21 +193,28 @@ namespace fresh { namespace cmd { bool eval(Entry &e, State &) override { return n.test((long long)e.st.st_gid); } }; // -size N[ckMGwb], default unit 512-byte blocks, rounded up struct Size : Node { Num n; long long unit=512; Num n; long long unit = 512; bool eval(Entry &e, State &) override { long long sz = (long long)e.st.st_size; return n.test((sz + unit - 1) / unit); } }; // -mtime N (days) / -mmin N (minutes), rounded down like GNU find enum TimeType { TIME_ACCESS, TIME_CHANGE, TIME_MODIFY }; struct Age : Node { Num n; long long unit; explicit Age(long long u):unit(u){} Num n; long long unit; TimeType type; Age(long long u, TimeType t = TIME_MODIFY):unit(u), type(t){} bool eval(Entry &e, State &s) override { long long age=(long long)(s.now-e.st.st_mtime); time_t target = e.st.st_mtime; if(type == TIME_ACCESS) target = e.st.st_atime; else if(type == TIME_CHANGE) target = e.st.st_ctime; long long age = (long long)(s.now - target); if(age < 0) age = 0; return n.test(age / unit); } Loading Loading @@ -237,9 +244,9 @@ namespace fresh { namespace cmd { } }; // -perm MODE (exact), -perm -MODE (all bits), -perm /MODE (any bit) struct Perm : Node { char kind=0; mode_t mode=0; char kind = 0; mode_t mode = 0; bool eval(Entry &e, State &) override { mode_t m = e.st.st_mode & 07777; if(kind == '-') return (m & mode) == mode; Loading @@ -249,7 +256,8 @@ namespace fresh { namespace cmd { }; struct Owner : Node { bool isUser; unsigned id; bool isUser; unsigned id; Owner(bool u, unsigned i):isUser(u), id(i){} bool eval(Entry &e, State &) override { return (isUser ? e.st.st_uid : e.st.st_gid) == id; } }; Loading Loading @@ -282,11 +290,10 @@ namespace fresh { namespace cmd { } }; // -exec cmd {} ; -exec cmd {} + -execdir ... -ok ... struct Exec : Node { std::vector<std::string> argv; // "{}" marks the path std::vector<std::string> argv; bool batch = false, inDir = false, ask = false; std::vector<std::string> pending; // batched paths std::vector<std::string> pending; State *state = nullptr; static int run(const std::vector<std::string> &args, const std::string &dir){ Loading Loading @@ -341,8 +348,8 @@ namespace fresh { namespace cmd { args.push_back(x); } if(ask){ std::cerr << "< "; for(auto &a: args) std::cerr << a << " "; std::cerr << "< " << args[0]; for(size_t i = 1; i < args.size(); ++i) std::cerr << " " << args[i]; std::cerr << " > ? "; std::string answer; if(!std::getline(std::cin, answer) || answer.empty() || Loading @@ -366,7 +373,7 @@ namespace fresh { namespace cmd { bool more() const { return pos < toks.size(); } const std::string &peek() const { return toks[pos]; } NodePtr parseExpr(){ // lowest precedence: "," NodePtr parseExpr(){ NodePtr l = parseOr(); while(l && more() && peek() == ","){ ++pos; Loading Loading @@ -460,8 +467,12 @@ namespace fresh { namespace cmd { if(t == "-links") return num(std::make_unique<Links>()); if(t == "-uid") return num(std::make_unique<Uid>()); if(t == "-gid") return num(std::make_unique<Gid>()); if(t=="-mtime") return num(std::make_unique<Age>(86400)); if(t=="-mmin") return num(std::make_unique<Age>(60)); if(t == "-mtime") return num(std::make_unique<Age>(86400, TIME_MODIFY)); if(t == "-atime") return num(std::make_unique<Age>(86400, TIME_ACCESS)); if(t == "-ctime") return num(std::make_unique<Age>(86400, TIME_CHANGE)); if(t == "-mmin") return num(std::make_unique<Age>(60, TIME_MODIFY)); if(t == "-amin") return num(std::make_unique<Age>(60, TIME_ACCESS)); if(t == "-cmin") return num(std::make_unique<Age>(60, TIME_CHANGE)); if(t == "-size"){ auto n = std::make_unique<Size>(); if(!arg(v)) return nullptr; Loading @@ -469,6 +480,7 @@ namespace fresh { namespace cmd { switch(v.back()){ case 'c': n->unit = 1; break; case 'w': n->unit = 2; break; case 'b': n->unit = 512; break; case 'k': n->unit = 1024; break; case 'M': n->unit = 1024 * 1024; break; case 'G': n->unit = 1024LL * 1024 * 1024; break; Loading Loading @@ -528,7 +540,6 @@ namespace fresh { namespace cmd { while(more() && peek() != ";" && peek() != "+") x->argv.push_back(toks[pos++]); if(!more()){ error = "missing argument to '" + t + "'"; return nullptr; } // "+" only terminates when right after a lone "{}" if(peek() == "+" && !x->argv.empty() && x->argv.back() == "{}" && !x->ask && !x->inDir){ x->batch = true; s.batchExecs.push_back(x.get()); Loading @@ -540,7 +551,6 @@ namespace fresh { namespace cmd { if(x->argv.empty()){ error = "missing command for '" + t + "'"; return nullptr; } return x; } // options: position-independent, always true if(t == "-maxdepth" || t == "-mindepth"){ if(!arg(v)) return nullptr; (t == "-maxdepth" ? s.maxDepth : s.minDepth) = atoi(v.c_str()); Loading @@ -548,7 +558,7 @@ namespace fresh { namespace cmd { } if(t == "-depth" || t == "-d"){ s.depthFirst = true; return std::make_unique<Const>(true); } if(t == "-xdev" || t == "-mount"){ s.xdev = true; return std::make_unique<Const>(true); } if(t=="-follow"){ s.followLinks=true; return std::make_unique<Const>(true); } if(t == "-follow"){ s.symlinkMode = DEREF_ALWAYS; return std::make_unique<Const>(true); } if(t == "-noleaf" || t == "-ignore_readdir_race" || t == "-nowarn" || t == "-warn") return std::make_unique<Const>(true); error = "unknown predicate '" + t + "'"; Loading @@ -565,7 +575,9 @@ namespace fresh { namespace cmd { e.depth = depth; size_t slash = path.find_last_of('/'); e.name = (slash == std::string::npos || path.size() == 1) ? path : path.substr(slash + 1); bool follow=s.followLinks; bool follow = (s.symlinkMode == DEREF_ALWAYS) || (s.symlinkMode == DEREF_COMMAND_LINE && depth == 0); if((follow ? stat(path.c_str(), &e.st) : lstat(path.c_str(), &e.st)) != 0){ if(lstat(path.c_str(), &e.st) != 0){ std::cerr << "find: '" << path << "': " << strerror(errno) << std::endl; Loading Loading @@ -612,13 +624,16 @@ namespace fresh { namespace cmd { State s; s.now = time(nullptr); int i = 1; // leading -H/-L/-P options // Handle positional symlink options for(; i < argc; ++i){ std::string a = argv[i]; if(a=="-L") s.followLinks=true; else if(a=="-H" || a=="-P") s.followLinks=false; if(a == "-L") s.symlinkMode = DEREF_ALWAYS; else if(a == "-H") s.symlinkMode = DEREF_COMMAND_LINE; else if(a == "-P") s.symlinkMode = DEREF_NEVER; else break; } std::vector<std::string> paths; for(; i < argc; ++i){ std::string a = argv[i]; Loading @@ -641,7 +656,7 @@ namespace fresh { namespace cmd { return 1; } } // no action anywhere: ( expr ) -print if(!p.hasAction){ auto a = std::make_unique<And>(); a->l = std::move(expr); Loading Loading
commands/filesystem/find.cpp +59 −44 Changes for commands/filesystem/find.cpp: 59 added lines, 44 removed lines. Original line number Diff line number Diff line Loading @@ -48,19 +48,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace fresh { namespace cmd { // POSIX find with the GNU extensions build scripts rely on (rpm's // brp-* scripts: -print0, -inum, -regex, "!", "\( ... -o ... \)"). // The expression is parsed into a tree of Node once, then evaluated per // visited file with the usual short-circuit semantics; an expression // without any action gets an implicit "-print" around it. // POSIX find with GNU extensions build scripts rely on. class FindCommand : public ShellCommand { public: FindCommand():ShellCommand("find"){} private: struct Entry { std::string path; // as printed (start path + relative part) std::string name; // last component std::string path; // start path + relative part std::string name; // last path component struct stat st; int depth; }; Loading @@ -75,18 +71,21 @@ namespace fresh { namespace cmd { struct Exec; enum SymlinkMode { DEREF_NEVER, DEREF_COMMAND_LINE, DEREF_ALWAYS }; struct State { SymlinkMode symlinkMode = DEREF_NEVER; bool followLinks = false; bool depthFirst = false; bool xdev = false; int maxDepth = -1; int minDepth = 0; bool quit = false; bool prune=false; // set by -prune for the current entry bool prune = false; int status = 0; dev_t startDev = 0; time_t now = 0; std::vector<Exec*> batchExecs; // "-exec ... {} +" to flush at the end std::vector<Exec*> batchExecs; }; // ---- expression nodes ---- Loading @@ -95,18 +94,22 @@ namespace fresh { namespace cmd { NodePtr l, r; bool eval(Entry &e, State &s) override { return l->eval(e, s) && r->eval(e, s); } }; struct Or : Node { NodePtr l, r; bool eval(Entry &e, State &s) override { return l->eval(e, s) || r->eval(e, s); } }; struct Comma : Node { NodePtr l, r; bool eval(Entry &e, State &s) override { l->eval(e, s); return r->eval(e, s); } }; struct Not : Node { NodePtr n; bool eval(Entry &e, State &s) override { return !n->eval(e, s); } }; struct Const : Node { bool v; explicit Const(bool b):v(b){} Loading @@ -120,20 +123,17 @@ namespace fresh { namespace cmd { bool eval(Entry &e, State &) override { std::string subject = wholePath ? e.path : e.name; std::string p = pat; if(icase){ for(auto &c: subject) c=(char)tolower((unsigned char)c); for(auto &c: p) c=(char)tolower((unsigned char)c); } return fnmatch(p.c_str(),subject.c_str(),0)==0; int flags = icase ? FNM_CASEFOLD : 0; return fnmatch(p.c_str(), subject.c_str(), flags) == 0; } }; // GNU find matches a regex against the WHOLE path struct Regex : Node { regex_t re; bool ok = false; Regex(const std::string &p, bool icase) { ok=regcomp(&re,p.c_str(),icase ? REG_ICASE : 0)==0; int flags = REG_EXTENDED | (icase ? REG_ICASE : 0); ok = (regcomp(&re, p.c_str(), flags) == 0); } ~Regex() { if(ok) regfree(&re); } bool eval(Entry &e, State &) override { Loading @@ -145,7 +145,7 @@ namespace fresh { namespace cmd { }; struct Type : Node { std::string types; // "f", "d", "f,d" ... std::string types; explicit Type(const std::string &t):types(t){} bool eval(Entry &e, State &) override { char t = '?'; Loading @@ -161,9 +161,9 @@ namespace fresh { namespace cmd { } }; // numeric comparison: "N" exact, "+N" greater, "-N" less struct Num { int cmp=0; long long v=0; int cmp = 0; long long v = 0; bool parse(const std::string &s) { std::string t = s; if(!t.empty() && t[0] == '+'){ cmp = 1; t = t.substr(1); } Loading Loading @@ -193,21 +193,28 @@ namespace fresh { namespace cmd { bool eval(Entry &e, State &) override { return n.test((long long)e.st.st_gid); } }; // -size N[ckMGwb], default unit 512-byte blocks, rounded up struct Size : Node { Num n; long long unit=512; Num n; long long unit = 512; bool eval(Entry &e, State &) override { long long sz = (long long)e.st.st_size; return n.test((sz + unit - 1) / unit); } }; // -mtime N (days) / -mmin N (minutes), rounded down like GNU find enum TimeType { TIME_ACCESS, TIME_CHANGE, TIME_MODIFY }; struct Age : Node { Num n; long long unit; explicit Age(long long u):unit(u){} Num n; long long unit; TimeType type; Age(long long u, TimeType t = TIME_MODIFY):unit(u), type(t){} bool eval(Entry &e, State &s) override { long long age=(long long)(s.now-e.st.st_mtime); time_t target = e.st.st_mtime; if(type == TIME_ACCESS) target = e.st.st_atime; else if(type == TIME_CHANGE) target = e.st.st_ctime; long long age = (long long)(s.now - target); if(age < 0) age = 0; return n.test(age / unit); } Loading Loading @@ -237,9 +244,9 @@ namespace fresh { namespace cmd { } }; // -perm MODE (exact), -perm -MODE (all bits), -perm /MODE (any bit) struct Perm : Node { char kind=0; mode_t mode=0; char kind = 0; mode_t mode = 0; bool eval(Entry &e, State &) override { mode_t m = e.st.st_mode & 07777; if(kind == '-') return (m & mode) == mode; Loading @@ -249,7 +256,8 @@ namespace fresh { namespace cmd { }; struct Owner : Node { bool isUser; unsigned id; bool isUser; unsigned id; Owner(bool u, unsigned i):isUser(u), id(i){} bool eval(Entry &e, State &) override { return (isUser ? e.st.st_uid : e.st.st_gid) == id; } }; Loading Loading @@ -282,11 +290,10 @@ namespace fresh { namespace cmd { } }; // -exec cmd {} ; -exec cmd {} + -execdir ... -ok ... struct Exec : Node { std::vector<std::string> argv; // "{}" marks the path std::vector<std::string> argv; bool batch = false, inDir = false, ask = false; std::vector<std::string> pending; // batched paths std::vector<std::string> pending; State *state = nullptr; static int run(const std::vector<std::string> &args, const std::string &dir){ Loading Loading @@ -341,8 +348,8 @@ namespace fresh { namespace cmd { args.push_back(x); } if(ask){ std::cerr << "< "; for(auto &a: args) std::cerr << a << " "; std::cerr << "< " << args[0]; for(size_t i = 1; i < args.size(); ++i) std::cerr << " " << args[i]; std::cerr << " > ? "; std::string answer; if(!std::getline(std::cin, answer) || answer.empty() || Loading @@ -366,7 +373,7 @@ namespace fresh { namespace cmd { bool more() const { return pos < toks.size(); } const std::string &peek() const { return toks[pos]; } NodePtr parseExpr(){ // lowest precedence: "," NodePtr parseExpr(){ NodePtr l = parseOr(); while(l && more() && peek() == ","){ ++pos; Loading Loading @@ -460,8 +467,12 @@ namespace fresh { namespace cmd { if(t == "-links") return num(std::make_unique<Links>()); if(t == "-uid") return num(std::make_unique<Uid>()); if(t == "-gid") return num(std::make_unique<Gid>()); if(t=="-mtime") return num(std::make_unique<Age>(86400)); if(t=="-mmin") return num(std::make_unique<Age>(60)); if(t == "-mtime") return num(std::make_unique<Age>(86400, TIME_MODIFY)); if(t == "-atime") return num(std::make_unique<Age>(86400, TIME_ACCESS)); if(t == "-ctime") return num(std::make_unique<Age>(86400, TIME_CHANGE)); if(t == "-mmin") return num(std::make_unique<Age>(60, TIME_MODIFY)); if(t == "-amin") return num(std::make_unique<Age>(60, TIME_ACCESS)); if(t == "-cmin") return num(std::make_unique<Age>(60, TIME_CHANGE)); if(t == "-size"){ auto n = std::make_unique<Size>(); if(!arg(v)) return nullptr; Loading @@ -469,6 +480,7 @@ namespace fresh { namespace cmd { switch(v.back()){ case 'c': n->unit = 1; break; case 'w': n->unit = 2; break; case 'b': n->unit = 512; break; case 'k': n->unit = 1024; break; case 'M': n->unit = 1024 * 1024; break; case 'G': n->unit = 1024LL * 1024 * 1024; break; Loading Loading @@ -528,7 +540,6 @@ namespace fresh { namespace cmd { while(more() && peek() != ";" && peek() != "+") x->argv.push_back(toks[pos++]); if(!more()){ error = "missing argument to '" + t + "'"; return nullptr; } // "+" only terminates when right after a lone "{}" if(peek() == "+" && !x->argv.empty() && x->argv.back() == "{}" && !x->ask && !x->inDir){ x->batch = true; s.batchExecs.push_back(x.get()); Loading @@ -540,7 +551,6 @@ namespace fresh { namespace cmd { if(x->argv.empty()){ error = "missing command for '" + t + "'"; return nullptr; } return x; } // options: position-independent, always true if(t == "-maxdepth" || t == "-mindepth"){ if(!arg(v)) return nullptr; (t == "-maxdepth" ? s.maxDepth : s.minDepth) = atoi(v.c_str()); Loading @@ -548,7 +558,7 @@ namespace fresh { namespace cmd { } if(t == "-depth" || t == "-d"){ s.depthFirst = true; return std::make_unique<Const>(true); } if(t == "-xdev" || t == "-mount"){ s.xdev = true; return std::make_unique<Const>(true); } if(t=="-follow"){ s.followLinks=true; return std::make_unique<Const>(true); } if(t == "-follow"){ s.symlinkMode = DEREF_ALWAYS; return std::make_unique<Const>(true); } if(t == "-noleaf" || t == "-ignore_readdir_race" || t == "-nowarn" || t == "-warn") return std::make_unique<Const>(true); error = "unknown predicate '" + t + "'"; Loading @@ -565,7 +575,9 @@ namespace fresh { namespace cmd { e.depth = depth; size_t slash = path.find_last_of('/'); e.name = (slash == std::string::npos || path.size() == 1) ? path : path.substr(slash + 1); bool follow=s.followLinks; bool follow = (s.symlinkMode == DEREF_ALWAYS) || (s.symlinkMode == DEREF_COMMAND_LINE && depth == 0); if((follow ? stat(path.c_str(), &e.st) : lstat(path.c_str(), &e.st)) != 0){ if(lstat(path.c_str(), &e.st) != 0){ std::cerr << "find: '" << path << "': " << strerror(errno) << std::endl; Loading Loading @@ -612,13 +624,16 @@ namespace fresh { namespace cmd { State s; s.now = time(nullptr); int i = 1; // leading -H/-L/-P options // Handle positional symlink options for(; i < argc; ++i){ std::string a = argv[i]; if(a=="-L") s.followLinks=true; else if(a=="-H" || a=="-P") s.followLinks=false; if(a == "-L") s.symlinkMode = DEREF_ALWAYS; else if(a == "-H") s.symlinkMode = DEREF_COMMAND_LINE; else if(a == "-P") s.symlinkMode = DEREF_NEVER; else break; } std::vector<std::string> paths; for(; i < argc; ++i){ std::string a = argv[i]; Loading @@ -641,7 +656,7 @@ namespace fresh { namespace cmd { return 1; } } // no action anywhere: ( expr ) -print if(!p.hasAction){ auto a = std::make_unique<And>(); a->l = std::move(expr); Loading