Commit 005c4760 authored by jan.koester's avatar jan.koester
Browse files

test

parent 75bdbab2
Loading
Loading
Loading
Loading
+59 −44
Changes for commands/filesystem/find.cpp: 59 added lines, 44 removed lines.
Original line number Diff line number Diff line
@@ -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;
        };
@@ -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 ----
@@ -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){}
@@ -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 {
@@ -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 = '?';
@@ -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,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);
            }
@@ -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;
@@ -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; }
        };
@@ -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){
@@ -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() ||
@@ -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;
@@ -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;
@@ -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;
@@ -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());
@@ -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());
@@ -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 + "'";
@@ -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;
@@ -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];
@@ -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);