| /* vi: set sw=4 ts=4: |
| * |
| * find.c - find files matching a criteria |
| * |
| * Copyright 2012 Tim Bird <tbird20d@gmail.com> |
| * |
| * 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 [DIR] [<options>] |
| |
| -name <pattern> match pattern |
| -type [bcdflps] 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 |
| |
| File types: |
| b block device d directory l symlink s socket |
| c char device f regular file p FIFO |
| */ |
| |
| /* 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); |
| } |