view toys/pending/find.c @ 848:feea7a8ecbb1

Redo find's indenting from tabs to two spaces.
author Rob Landley <rob@landley.net>
date Wed, 10 Apr 2013 19:58:21 -0500
parents 2506cbdccd20
children 917d6e051b82
line wrap: on
line source

/* 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);
}