From b997ddf3df6535765f7eab0683eae4703933a703 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 1 Feb 2016 17:41:55 -0500 Subject: Add -[acm]{min,time} support. --- bfs.h | 39 +++++++++++++++--- eval.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- parse.c | 92 +++++++++++++++++++++++++++++++++++------- 3 files changed, 242 insertions(+), 30 deletions(-) diff --git a/bfs.h b/bfs.h index 441993b..e4dffdd 100644 --- a/bfs.h +++ b/bfs.h @@ -15,6 +15,7 @@ #include "color.h" #include #include +#include /** * The parsed command line. @@ -64,8 +65,23 @@ struct cmdline { /** The command line expression. */ expression *expr; + + /** The current time. */ + struct timespec now; }; +/** + * Possible types of numeric comparison. + */ +typedef enum { + /** Exactly n. */ + CMP_EXACT, + /** Less than n. */ + CMP_LESS, + /** Greater than n. */ + CMP_GREATER, +} cmpflag; + struct expression { /** The left hand side of the expression. */ expression *lhs; @@ -73,6 +89,8 @@ struct expression { expression *rhs; /** The function that evaluates this expression. */ eval_fn *eval; + /** The comparison flag. */ + cmpflag cmp; /** Optional integer data for this expression. */ int idata; /** Optional string data for this expression. */ @@ -95,20 +113,31 @@ int eval_cmdline(cmdline *cl); void free_cmdline(cmdline *cl); // Predicate evaluation functions +bool eval_true(const expression *expr, eval_state *state); +bool eval_false(const expression *expr, eval_state *state); + bool eval_access(const expression *expr, eval_state *state); -bool eval_delete(const expression *expr, eval_state *state); + +bool eval_amin(const expression *expr, eval_state *state); +bool eval_atime(const expression *expr, eval_state *state); +bool eval_cmin(const expression *expr, eval_state *state); +bool eval_ctime(const expression *expr, eval_state *state); +bool eval_mmin(const expression *expr, eval_state *state); +bool eval_mtime(const expression *expr, eval_state *state); + bool eval_empty(const expression *expr, eval_state *state); -bool eval_false(const expression *expr, eval_state *state); bool eval_hidden(const expression *expr, eval_state *state); +bool eval_type(const expression *expr, eval_state *state); + bool eval_name(const expression *expr, eval_state *state); -bool eval_nohidden(const expression *expr, eval_state *state); bool eval_path(const expression *expr, eval_state *state); + +bool eval_delete(const expression *expr, eval_state *state); +bool eval_nohidden(const expression *expr, eval_state *state); bool eval_print(const expression *expr, eval_state *state); bool eval_print0(const expression *expr, eval_state *state); bool eval_prune(const expression *expr, eval_state *state); bool eval_quit(const expression *expr, eval_state *state); -bool eval_true(const expression *expr, eval_state *state); -bool eval_type(const expression *expr, eval_state *state); // Operator evaluation functions bool eval_not(const expression *expr, eval_state *state); diff --git a/eval.c b/eval.c index 417bfc3..243546e 100644 --- a/eval.c +++ b/eval.c @@ -8,6 +8,7 @@ #include #include #include +#include #include struct eval_state { @@ -24,17 +25,57 @@ struct eval_state { /** * Perform a stat() call if necessary. */ -static void fill_statbuf(eval_state *state) { +static const struct stat *fill_statbuf(eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; - if (ftwbuf->statbuf) { - return; + if (!ftwbuf->statbuf) { + if (fstatat(ftwbuf->at_fd, ftwbuf->at_path, &state->statbuf, AT_SYMLINK_NOFOLLOW) == 0) { + ftwbuf->statbuf = &state->statbuf; + } else { + perror("fstatat()"); + } } + return ftwbuf->statbuf; +} - if (fstatat(ftwbuf->at_fd, ftwbuf->at_path, &state->statbuf, AT_SYMLINK_NOFOLLOW) == 0) { - ftwbuf->statbuf = &state->statbuf; - } else { - perror("fstatat()"); +/** + * Get the difference (in seconds) between two struct timespecs. + */ +static time_t timespec_diff(const struct timespec *lhs, const struct timespec *rhs) { + time_t ret = lhs->tv_sec - rhs->tv_sec; + if (lhs->tv_nsec < rhs->tv_nsec) { + --ret; + } + return ret; +} + +/** + * Convert a second count to minutes. + */ +static time_t to_minutes(time_t seconds) { + return seconds/60; +} + +/** + * Convert a second count to days. + */ +static time_t to_days(time_t seconds) { + return seconds/60/60/24; +} + +/** + * Perform a comparison. + */ +static bool do_cmp(const expression *expr, int n) { + switch (expr->cmp) { + case CMP_EXACT: + return n == expr->idata; + case CMP_LESS: + return n < expr->idata; + case CMP_GREATER: + return n > expr->idata; } + + return false; } /** @@ -52,13 +93,91 @@ bool eval_false(const expression *expr, eval_state *state) { } /** - * -executable, -readable, -writable action. + * -executable, -readable, -writable tests. */ bool eval_access(const expression *expr, eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; return faccessat(ftwbuf->at_fd, ftwbuf->at_path, expr->idata, AT_SYMLINK_NOFOLLOW) == 0; } +/** + * -amin test. + */ +bool eval_amin(const expression *expr, eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + time_t diff = timespec_diff(&state->cl->now, &statbuf->st_atim); + return do_cmp(expr, to_minutes(diff)); +} + +/** + * -atime test. + */ +bool eval_atime(const expression *expr, eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + time_t diff = timespec_diff(&state->cl->now, &statbuf->st_atim); + return do_cmp(expr, to_days(diff)); +} + +/** + * -cmin test. + */ +bool eval_cmin(const expression *expr, eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + time_t diff = timespec_diff(&state->cl->now, &statbuf->st_ctim); + return do_cmp(expr, to_minutes(diff)); +} + +/** + * -ctime test. + */ +bool eval_ctime(const expression *expr, eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + time_t diff = timespec_diff(&state->cl->now, &statbuf->st_ctim); + return do_cmp(expr, to_days(diff)); +} + +/** + * -mmin test. + */ +bool eval_mmin(const expression *expr, eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + time_t diff = timespec_diff(&state->cl->now, &statbuf->st_mtim); + return do_cmp(expr, to_minutes(diff)); +} + +/** + * -mtime test. + */ +bool eval_mtime(const expression *expr, eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + time_t diff = timespec_diff(&state->cl->now, &statbuf->st_mtim); + return do_cmp(expr, to_days(diff)); +} + /** * -delete action. */ @@ -111,9 +230,9 @@ bool eval_empty(const expression *expr, eval_state *state) { closedir(dir); } else { - fill_statbuf(state); - if (ftwbuf->statbuf) { - ret = ftwbuf->statbuf->st_size == 0; + const struct stat *statbuf = fill_statbuf(state); + if (statbuf) { + ret = statbuf->st_size == 0; } } diff --git a/parse.c b/parse.c index 5425afa..6983f16 100644 --- a/parse.c +++ b/parse.c @@ -18,6 +18,7 @@ static expression *new_expression(eval_fn *eval) { expr->lhs = NULL; expr->rhs = NULL; expr->eval = eval; + expr->cmp = CMP_EXACT; expr->idata = 0; expr->sdata = NULL; return expr; @@ -163,6 +164,50 @@ static const char *skip_paths(parser_state *state) { } } +/** + * Parse an integer. + */ +static bool parse_int(const char *str, int *value) { + char *endptr; + long result = strtol(str, &endptr, 10); + + if (*str == '\0' || *endptr != '\0') { + goto bad; + } + + if (result < INT_MIN || result > INT_MAX) { + goto bad; + } + + *value = result; + return true; + +bad: + fprintf(stderr, "'%s' is not a valid integer.\n", str); + return false; +} + +/** + * Parse an integer and a comparison flag. + */ +static bool parse_icmp(const char *str, expression *expr) { + switch (str[0]) { + case '-': + expr->cmp = CMP_LESS; + ++str; + break; + case '+': + expr->cmp = CMP_GREATER; + ++str; + break; + default: + expr->cmp = CMP_EXACT; + break; + } + + return parse_int(str, &expr->idata); +} + /** * Create a new option expression. */ @@ -228,22 +273,25 @@ static expression *new_action(parser_state *state, eval_fn *eval) { } /** - * Parse an integer. + * Parse a test expression with integer data and a comparison flag. */ -static bool parse_int(const char *str, int *value) { - char *endptr; - long result = strtol(str, &endptr, 10); - - if (*str == '\0' || *endptr != '\0') { - return false; +static expression *parse_test_icmp(parser_state *state, const char *test, eval_fn *eval) { + const char *arg = state->argv[state->i]; + if (!arg) { + fprintf(stderr, "%s needs a value.\n", test); + return NULL; } - if (result < INT_MIN || result > INT_MAX) { - return false; - } + ++state->i; - *value = result; - return true; + expression *expr = new_test(state, eval); + if (expr) { + if (!parse_icmp(arg, expr)) { + free_expression(expr); + expr = NULL; + } + } + return expr; } /** @@ -274,7 +322,6 @@ static expression *parse_depth(parser_state *state, const char *option, int *dep ++state->i; if (!parse_int(arg, depth)) { - fprintf(stderr, "%s is not a valid integer.\n", arg); return NULL; } @@ -336,7 +383,15 @@ static expression *parse_literal(parser_state *state) { // Paths are already skipped at this point const char *arg = state->argv[state->i++]; - if (strcmp(arg, "-color") == 0) { + if (strcmp(arg, "-amin") == 0) { + return parse_test_icmp(state, arg, eval_amin); + } else if (strcmp(arg, "-atime") == 0) { + return parse_test_icmp(state, arg, eval_atime); + } else if (strcmp(arg, "-cmin") == 0) { + return parse_test_icmp(state, arg, eval_cmin); + } else if (strcmp(arg, "-ctime") == 0) { + return parse_test_icmp(state, arg, eval_ctime); + } else if (strcmp(arg, "-color") == 0) { state->cl->color = true; return new_option(state, arg); } else if (strcmp(arg, "-nocolor") == 0) { @@ -362,6 +417,10 @@ static expression *parse_literal(parser_state *state) { return parse_depth(state, arg, &state->cl->mindepth); } else if (strcmp(arg, "-maxdepth") == 0) { return parse_depth(state, arg, &state->cl->maxdepth); + } else if (strcmp(arg, "-mmin") == 0) { + return parse_test_icmp(state, arg, eval_mmin); + } else if (strcmp(arg, "-mtime") == 0) { + return parse_test_icmp(state, arg, eval_mtime); } else if (strcmp(arg, "-name") == 0) { return parse_test_sdata(state, arg, eval_name); } else if (strcmp(arg, "-path") == 0 || strcmp(arg, "-wholename") == 0) { @@ -613,6 +672,11 @@ cmdline *parse_cmdline(int argc, char *argv[]) { cl->flags = BFTW_RECOVER; cl->expr = &expr_true; + if (clock_gettime(CLOCK_REALTIME, &cl->now) != 0) { + perror("clock_gettime()"); + goto fail; + } + parser_state state = { .cl = cl, .argv = argv, -- cgit v1.2.3