/* vi: set sw=4 ts=4: * * find.c - find files matching a criteria * * Copyright 2012 Tim Bird * * See http://opengroup.org/onlinepubs/9699919799/utilities/find.html USE_FIND(NEWTOY(find, "?", TOYFLAG_USR|TOYFLAG_BIN)) config FIND bool "find" default n help usage: find [] [ match pattern -type [fcdl] match file type !, -a, -o not, and , or (, ) group expressions -mtime [-+]n match file modification time (to within 24 hours) \t\t +=greater (older) than, -=less (younger) than */ /* To Do: * -exec action */ #define FOR_find #include "toys.h" struct filter_node { struct filter_node *next; int op; union { char *name_regex; struct { int sign; time_t time; } t; mode_t type; struct { int arg_path_index; char **exec_args; } e; } data; }; GLOBALS( char *dir; struct filter_node *filter_root; time_t itime; ) /* filter operation types */ #define OP_UNKNOWN 0 #define OP_OR 2 #define OP_AND 3 #define OP_NOT 4 #define LPAREN 1 #define RPAREN 5 #define MAX_OP 5 #define CHECK_NAME 7 #define CHECK_MTIME 8 #define CHECK_TYPE 9 #define ACTION_PRINT 20 #define ACTION_PRINT0 21 #define ACTION_EXEC 22 #define IS_ACTION(x) (x >= 20) /* executes the command for a filter node returns 0 for failure or 1 for success */ static int do_exec(struct filter_node *filter, struct dirtree *node) { char *path = NULL; char **arg_array; pid_t pid; int status; arg_array = filter->data.e.exec_args; if (filter->data.e.arg_path_index) { int plen; path = dirtree_path(node, &plen); arg_array[filter->data.e.arg_path_index] = path; } if (!(pid = fork())) xexec(arg_array); free(path); waitpid(pid, &status, 0); return !WEXITSTATUS(status); } /* prefix evaluator */ /* returns 0 for failure or 1 for success */ static int evaluate(struct filter_node *filter, struct dirtree *node, struct filter_node **fnext) { int result; int op; static int skip = 0; /* if no filters, success */ if (!filter) { *fnext = NULL; return 1; } op = filter->op; if (op==OP_NOT) return !evaluate(filter->next, node, fnext); if (op==OP_OR || op==OP_AND) { result = evaluate(filter->next, node, fnext); if(!skip) { if (op==OP_OR) { skip = result; result = evaluate(*fnext, node, fnext) || result; } else { skip = !result; result = evaluate(*fnext, node, fnext) && result; } skip = 0; } else result = evaluate(*fnext, node, fnext); return result; } // we're down to single operations, mark our position *fnext = filter->next; if(skip) return 0; // TODO: Do a regex comparison if (op==CHECK_NAME) return !strcmp(filter->data.name_regex, node->name); if (op==CHECK_MTIME) { long delta = (long)(((double)TT.itime - node->st.st_mtime) / 86400) -filter->data.t.time; return filter->data.t.sign == (delta > 0) - (delta < 0); } if (op==CHECK_TYPE) return (node->st.st_mode & S_IFMT) == filter->data.type; if (op==ACTION_PRINT || op==ACTION_PRINT0) { char *path; int plen = 0; char terminator = (op==ACTION_PRINT)*'\n'; path = dirtree_path(node, &plen); printf("%s%c", path, terminator); free(path); return 1; } if (op==ACTION_EXEC) return !do_exec(filter, node); error_msg("Ran out of operations in filter list!"); return 1; } static int check_node_callback(struct dirtree *node) { struct filter_node *junk; /* only recurse on "." at the root */ /* so, don't recurse on 1) non-root "." and 2) any ".." */ if (node->name[0] == '.' && ((!node->name[1] && node->parent) || (node->name[1]=='.' && !node->name[2]))) return 0; evaluate(TT.filter_root, node, &junk); return DIRTREE_RECURSE; } static void build_filter_list(void) { struct filter_node *node_list, *op_stack, *node, *op_node, *next; char **arg; int j; int prevop = 0; int have_action = 0; /* part optargs here and build a filter list in prefix format */ TT.dir = "."; node_list = NULL; for (arg = toys.optargs; *arg; arg++) { struct { char *arg; int op; int extrarg; } arg_map[] = {{"-o", OP_OR, 0}, {"-a", OP_AND, 0}, {"!", OP_NOT, 0}, {"(", LPAREN, 0}, {")", RPAREN, 0}, {"-name", CHECK_NAME, 1}, {"-mtime", CHECK_MTIME, 1}, {"-type", CHECK_TYPE, 1}, {"-print", ACTION_PRINT, 0}, {"-print0", ACTION_PRINT0, 0}, {"-exec", ACTION_EXEC, 1} }; mode_t types[]={S_IFREG,S_IFDIR,S_IFCHR,S_IFBLK,S_IFLNK,S_IFSOCK,S_IFIFO}; char **arg_array; node = (struct filter_node *) xmalloc(sizeof(struct filter_node)); node->op = OP_UNKNOWN; for (j=0; j < sizeof(arg_map)/sizeof(*arg_map); j++) { if (!strcmp(*arg, arg_map[j].arg)) { node->op = arg_map[j].op; if (arg_map[j].extrarg && !*(++arg)) error_exit("Missing argument to %s", arg_map[j].arg); break; } } switch(node->op) { case CHECK_NAME: node->data.name_regex = *arg; break; case CHECK_MTIME: switch(**arg) { case '+': node->data.t.sign=+1; (*arg)++; break; case '-': node->data.t.sign=-1; (*arg)++; break; default: node->data.t.sign=0; break; } node->data.t.time = atoi(*arg); break; case CHECK_TYPE: if (-1 == (j = stridx("fdcblsp", **arg))) error_exit("bad type '%s'", *arg); else node->data.type = types[j]; break; case ACTION_EXEC: arg_array = xmalloc(sizeof(char *)); for (j = 0; *arg && strcmp(*arg, ";"); j++) { /* new method */ arg_array = xrealloc(arg_array, sizeof(char *) * (j+2)); arg_array[j] = *arg; if (!strcmp(*arg, "{}")) node->data.e.arg_path_index = j; arg++; } if (!*arg) error_exit("need ';' in exec"); arg_array[j] = 0; node->data.e.exec_args = arg_array; break; } have_action |= IS_ACTION(node->op); if (node->op == OP_UNKNOWN) { if (**arg == '-') error_exit("bad option '%s'", *arg); else TT.dir = *arg; } else { // add OP_AND where necessary if (node_list) { int o1 = node_list->op, o2 = node->op; if ((o1>MAX_OP && o2>MAX_OP) || (o1==RPAREN && o2>=OP_NOT && o2!=RPAREN) || (o1>=OP_NOT && o1!=LPAREN && o2==LPAREN)) { struct filter_node *n = (struct filter_node *) xmalloc(sizeof(struct filter_node)); n->op = OP_AND; n->next = node_list; node_list = n; } } /* push node */ node->next = node_list;; node_list = node; } } /* now convert from infix to prefix */ if(have_action) TT.filter_root = NULL; else TT.filter_root = &(struct filter_node){0, ACTION_PRINT}; op_stack = NULL; node = node_list; while( node ) { int op = node->op; next = node->next; if (op==LPAREN || op==RPAREN) { free(node); node = 0; } if (op<=MAX_OP) { if (prevop > op) { /* pop opstack to output */ op_node = op_stack; while (op_node) { /* remove from op_stack */ op_stack = op_node->next; /* push to output */ op_node->next = TT.filter_root; TT.filter_root = op_node; /* get next node */ op_node = op_stack; } } if (node) { /* push to opstack */ node->next = op_stack; op_stack = node; } prevop = op*(op!=RPAREN); } else { /* push to output */ node->next = TT.filter_root; TT.filter_root = node; } node = next; } /* end of input - push opstack to output */ /* pop opstack to output till empty */ op_node = op_stack; while (op_node) { op_stack = op_node->next; op_node->next = TT.filter_root; TT.filter_root = op_node; op_node = op_stack; } if(!have_action) { node = TT.filter_root; TT.filter_root = (struct filter_node*) xmalloc(sizeof(struct filter_node)); TT.filter_root->next = node; TT.filter_root->op = OP_AND; } } void find_main(void) { TT.itime = time(NULL); /* parse filters, if present */ build_filter_list(); /* FIXTHIS - parse actions, if present */ dirtree_read(TT.dir, check_node_callback); }