/* 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 */ #include "toys.h" #define SECONDS_PER_DAY (24*60*60) int have_action; struct filter_node { struct filter_node *next; int op; union { char *name_regex; struct { char time_op; time_t time; } t; mode_t type; struct { int arg_path_index; char **exec_args; } e; } data; }; static struct filter_node *filter_root; /* filter operation types */ #define OP_UNKNOWN 0 #define OP_NOT 1 #define OP_OR 2 #define OP_AND 3 #define LPAREN 4 #define RPAREN 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 TEST_LT 0 #define TEST_EQ 1 #define TEST_GT 2 /* executes the command for a filter node * return the program return value (0=success) */ static int do_exec(struct filter_node *filter, struct dirtree *node) { char *path; int plen; char **arg_array; pid_t pid; int ccode; int status; arg_array = filter->data.e.exec_args; if (filter->data.e.arg_path_index) { path = dirtree_path(node, &plen); arg_array[filter->data.e.arg_path_index] = path; } else { path = NULL; } pid = fork(); if (!pid) { /* child */ xexec(arg_array); } else { /* parent */ /* wait for child */ waitpid(pid, &status, 0); ccode = WEXITSTATUS(status); } free(path); return ccode; } /* 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; char *path; int plen = 0; struct stat st_buf; time_t node_time; char terminator; /* if no filters, success */ if (!filter) { *fnext = NULL; return 1; } if (filter->op==OP_NOT) { return !evaluate(filter->next, node, fnext); } if (filter->op==OP_OR) { return evaluate(filter->next, node, fnext) || evaluate(*fnext, node, fnext); } if (filter->op==OP_AND) { return evaluate(filter->next, node, fnext) && evaluate(*fnext, node, fnext); } /* we're down to single operations, mark our position */ *fnext = filter->next; if (filter->op==CHECK_NAME) { // TODO: Do a regex comparison return !strcmp(filter->data.name_regex, node->name); } if (filter->op==CHECK_MTIME) { /* do mtime check */ path = dirtree_path(node, &plen); result = stat(path, &st_buf); free(path); if (result<0) { return 0; } node_time = st_buf.st_mtime/SECONDS_PER_DAY; result = 1; switch (filter->data.t.time_op) { /* do time compare here */ case TEST_LT: /* FIXTHIS - should result be < or <= ?*/ if (node_time > filter->data.t.time) result = 0; break; case TEST_GT: if (node_time < filter->data.t.time) result = 0; break; case TEST_EQ: if (node_time != filter->data.t.time) result = 0; break; default: /* how'd I get here? */ result = 0; break; } return result; } if (filter->op==CHECK_TYPE) { path = dirtree_path(node, &plen); result = lstat(path, &st_buf); free(path); if (result<0) { return 0; } return (st_buf.st_mode & S_IFMT) == filter->data.type; } if (filter->op==ACTION_PRINT || filter->op==ACTION_PRINT0) { if (filter->op==ACTION_PRINT) { terminator = '\n'; } else { terminator = 0; } path = dirtree_path(node, &plen); printf("%s%c", path, terminator); free(path); return 1; } if (filter->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) { char *path; int plen = 0; int result; 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; result = evaluate(filter_root, node, &junk); if (result & !have_action) { /* default action is just print the path */ path = dirtree_path(node, &plen); printf("%s\n", path); free(path); } return DIRTREE_RECURSE; } static void build_filter_list(void) { struct filter_node *node_list; struct filter_node *op_stack; struct filter_node *node, *op_node; struct filter_node *next; char *arg, **arg_array; int i, j; /* part optargs here and build a filter list in prefix format */ node_list = NULL; toybuf[0] = 0; have_action = 0; for(i=0; toys.optargs[i]; i++) { node = (struct filter_node *) xmalloc(sizeof(struct filter_node)); node->op = OP_UNKNOWN; arg = toys.optargs[i]; if (!strcmp(arg, "-o")) { node->op = OP_OR; } if (!strcmp(arg, "-a")) { node->op = OP_AND; } if (!strcmp(arg, "!")) { node->op = OP_NOT; } if (!strcmp(arg, "(")) { node->op = LPAREN; } if (!strcmp(arg, ")")) { node->op = RPAREN; } if (!strcmp(arg, "-name")) { node->op = CHECK_NAME; arg = toys.optargs[++i]; if (!arg) error_exit("Missing argument to -name\n"); node->data.name_regex = arg; } if (!strcmp(arg, "-mtime")) { node->op = CHECK_MTIME; arg = toys.optargs[++i]; if (!arg) error_exit("Missing argument to -mtime\n"); switch(arg[0]) { case '+': node->data.t.time_op=TEST_GT; arg++; break; case '-': node->data.t.time_op=TEST_LT; arg++; break; default: node->data.t.time_op=TEST_EQ; break; } /* convert to days (very crudely) */ node->data.t.time = atoi(arg)/SECONDS_PER_DAY; } if (!strcmp(arg, "-type")) { node->op = CHECK_TYPE; arg = toys.optargs[++i]; if (!arg) error_exit("Missing argument to -type\n"); switch(arg[0]) { case 'f': node->data.type = S_IFREG; break; case 'd': node->data.type = S_IFDIR; break; case 'c': node->data.type = S_IFCHR; break; case 'b': node->data.type = S_IFBLK; break; case 'l': node->data.type = S_IFLNK; break; case 's': node->data.type = S_IFSOCK; break; case 'p': /* named pipe */ node->data.type = S_IFIFO; break; default: error_exit("Unknown file type '%s'\n", arg); } } if (!strcmp(arg, "-print")) { node->op = ACTION_PRINT; have_action = 1; } if (!strcmp(arg, "-print0")) { node->op = ACTION_PRINT0; have_action = 1; } if (!strcmp(arg, "-exec")) { node->op = ACTION_EXEC; have_action = 1; arg_array = xmalloc(sizeof(char *)); arg = toys.optargs[++i]; 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 = toys.optargs[++i]; } if (!arg) error_exit("Missing ';' in exec command\n"); arg_array[j] = 0; node->data.e.exec_args = arg_array; } if (node->op == OP_UNKNOWN) { if (arg[0] == '-') error_exit("Unknown option '%s'\n", arg); else { /* use toybuf as start directory */ strcpy(toybuf, arg); } } else { /* push node */ node->next = node_list;; node_list = node; } } /* now convert from infix to prefix */ filter_root = NULL; op_stack = NULL; node = node_list; while( node ) { next = node->next; switch( node->op ) { case OP_AND: case OP_OR: case OP_NOT: case RPAREN: /* push to opstack */ node->next = op_stack; op_stack = node; break; case LPAREN: free(node); /* pop opstack to output (up to rparen) */ op_node = op_stack; while (op_node && op_node->op != RPAREN) { /* remove from op_stack */ op_stack = op_node->next; /* push to output */ op_node->next = filter_root; filter_root = op_node; /* get next node */ op_node = op_stack; } /* rparen should be on op_stack */ if (!op_stack) error_exit("Missing right paren\n"); /* remove rparen from op_stack */ op_stack = op_stack->next; free(op_node); break; default: /* push to output */ node->next = filter_root; filter_root = node; break; } node = next; } /* end of input - push opstack to output */ /* pop opstack to output till empty */ op_node = op_stack; while (op_node) { /*if (op_node->op == RPAREN || op_node->op == LPAREN) { error_exit("Error: extra paren found\n"); } */ op_stack = op_node->next; op_node->next = filter_root; filter_root = op_node; op_node = op_stack; } } void find_main(void) { /* parse filters, if present */ /* also, fill toybuf with the directory to start in, if present */ build_filter_list(); /* FIXTHIS - parse actions, if present */ if (!toybuf[0]) { strcpy(toybuf, "."); } dirtree_read(toybuf, check_node_callback); }