Commit a657938f authored by jan.koester's avatar jan.koester
Browse files

find renew

parent 2f40d2d0
Loading
Loading
Loading
Loading
+49 −83
Changes for commands/filesystem/find.cpp: 49 added lines, 83 removed lines.
Original line number Diff line number Diff line
@@ -48,15 +48,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

namespace fresh { namespace cmd {

    // POSIX find with GNU extensions build scripts rely on.
    // 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.
    class FindCommand : public ShellCommand {
    public:
        FindCommand():ShellCommand("find"){}

    private:
        struct Entry {
            std::string path;     // start path + relative part
            std::string name;     // last path component
            std::string path;     // as printed (start path + relative part)
            std::string name;     // last component
            struct stat st;
            int depth;
        };
@@ -71,21 +75,18 @@ 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;
            bool prune=false;     // set by -prune for the current entry
            int status=0;
            dev_t startDev=0;
            time_t now=0;
            std::vector<Exec*> batchExecs;
            std::vector<Exec*> batchExecs; // "-exec ... {} +" to flush at the end
        };

        // ---- expression nodes ----
@@ -94,22 +95,18 @@ 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){}
@@ -123,17 +120,20 @@ namespace fresh { namespace cmd {
            bool eval(Entry &e,State &) override {
                std::string subject=wholePath ? e.path : e.name;
                std::string p=pat;
                int flags = icase ? FNM_CASEFOLD : 0;
                return fnmatch(p.c_str(), subject.c_str(), flags) == 0;
                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;
            }
        };

        // 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){
                int flags = REG_EXTENDED | (icase ? REG_ICASE : 0);
                ok = (regcomp(&re, p.c_str(), flags) == 0);
                ok=regcomp(&re,p.c_str(),icase ? REG_ICASE : 0)==0;
            }
            ~Regex(){ if(ok) regfree(&re); }
            bool eval(Entry &e,State &) override {
@@ -145,7 +145,7 @@ namespace fresh { namespace cmd {
        };

        struct Type : Node {
            std::string types;
            std::string types; // "f", "d", "f,d" ...
            explicit Type(const std::string &t):types(t){}
            bool eval(Entry &e,State &) override {
                char t='?';
@@ -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); }
@@ -193,28 +193,21 @@ 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);
            }
        };

        enum TimeType { TIME_ACCESS, TIME_CHANGE, TIME_MODIFY };

        // -mtime N (days) / -mmin N (minutes), rounded down like GNU find
        struct Age : Node {
            Num n; 
            long long unit;
            TimeType type;
            Age(long long u, TimeType t = TIME_MODIFY):unit(u), type(t){}
            Num n; long long unit;
            explicit Age(long long u):unit(u){}
            bool eval(Entry &e,State &s) override {
                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);
                long long age=(long long)(s.now-e.st.st_mtime);
                if(age<0) age=0;
                return n.test(age/unit);
            }
@@ -244,9 +237,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;
@@ -256,8 +249,7 @@ 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; }
        };
@@ -290,10 +282,11 @@ namespace fresh { namespace cmd {
            }
        };

        // -exec cmd {} ;   -exec cmd {} +   -execdir ...   -ok ...
        struct Exec : Node {
            std::vector<std::string> argv;
            std::vector<std::string> argv; // "{}" marks the path
            bool batch=false,inDir=false,ask=false;
            std::vector<std::string> pending;
            std::vector<std::string> pending; // batched paths
            State *state=nullptr;

            static int run(const std::vector<std::string> &args,const std::string &dir){
@@ -348,8 +341,8 @@ namespace fresh { namespace cmd {
                    args.push_back(x);
                }
                if(ask){
                    std::cerr << "< " << args[0];
                    for(size_t i = 1; i < args.size(); ++i) std::cerr << " " << args[i];
                    std::cerr << "< ";
                    for(auto &a: args) std::cerr << a << " ";
                    std::cerr << "> ? ";
                    std::string answer;
                    if(!std::getline(std::cin,answer) || answer.empty() ||
@@ -373,7 +366,7 @@ namespace fresh { namespace cmd {
            bool more() const { return pos<toks.size(); }
            const std::string &peek() const { return toks[pos]; }

            NodePtr parseExpr(){
            NodePtr parseExpr(){ // lowest precedence: ","
                NodePtr l=parseOr();
                while(l && more() && peek()==","){
                    ++pos;
@@ -467,12 +460,8 @@ 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, 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=="-mtime") return num(std::make_unique<Age>(86400));
                if(t=="-mmin") return num(std::make_unique<Age>(60));
                if(t=="-size"){
                    auto n=std::make_unique<Size>();
                    if(!arg(v)) return nullptr;
@@ -480,7 +469,6 @@ 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;
@@ -540,6 +528,7 @@ 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());
@@ -551,6 +540,7 @@ 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());
@@ -558,7 +548,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.symlinkMode = DEREF_ALWAYS; return std::make_unique<Const>(true); }
                if(t=="-follow"){ s.followLinks=true; 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+"'";
@@ -575,9 +565,7 @@ 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.symlinkMode == DEREF_ALWAYS) || (s.symlinkMode == DEREF_COMMAND_LINE && depth == 0);
            
            bool follow=s.followLinks;
            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;
@@ -624,17 +612,14 @@ namespace fresh { namespace cmd {
            State s;
            s.now=time(nullptr);
            int i=1;

            // Handle positional symlink options
            // leading -H/-L/-P options
            for(; i<argc; ++i){
                std::string a=argv[i];
                if(a == "-L") s.symlinkMode = DEREF_ALWAYS;
                else if(a == "-H") s.symlinkMode = DEREF_COMMAND_LINE;
                else if(a == "-P") s.symlinkMode = DEREF_NEVER;
                if(a=="-L") s.followLinks=true;
                else if(a=="-H" || a=="-P") s.followLinks=false;
                else break;
            }

            std::vector paths;
            std::vector<std::string> paths;
            for(; i<argc; ++i){
                std::string a=argv[i];
                if(a=="!" || a=="(" || (a.size()>1 && a[0]=='-')) break;
@@ -643,30 +628,11 @@ namespace fresh { namespace cmd {
            if(paths.empty()) paths.push_back(".");

            Parser p(s);
            for(; i < argc; ++i) {
                std::string arg = argv[i];
                // Split concatenated leading '(' (e.g. "(-name" -> "(" "-name")
                while (arg.size() > 1 && arg[0] == '(') {
                    p.toks.push_back("(");
                    arg = arg.substr(1);
                }
                // Split concatenated trailing ')' (e.g. "*.S)" -> "*.S" ")")
                std::size_t trailing_cnt = 0;
                while (arg.size() > 1 && arg.back() == ')') {
                    trailing_cnt++;
                    arg.pop_back();
                }
                if (!arg.empty()) {
                    p.toks.push_back(arg);
                }
                while (trailing_cnt--) {
                    p.toks.push_back(")");
                }
            }
            for(; i<argc; ++i) p.toks.push_back(argv[i]);

            NodePtr expr;
            if(p.toks.empty()){
                expr = std::make_unique(true);
                expr=std::make_unique<Const>(true);
            }else{
                expr=p.parseExpr();
                if(expr && p.more()){ p.error="unexpected '"+p.peek()+"'"; expr.reset(); }
@@ -675,11 +641,11 @@ namespace fresh { namespace cmd {
                    return 1;
                }
            }

            // no action anywhere: ( expr ) -print
            if(!p.hasAction){
                auto a = std::make_unique();
                auto a=std::make_unique<And>();
                a->l=std::move(expr);
                a->r = std::make_unique('\n');
                a->r=std::make_unique<Print>('\n');
                expr=std::move(a);
            }