blob: d61e48c3cd9f1a3575f64c7d133accda74acb750 [file] [log] [blame]
/* 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 [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);
}