From 6e9f52c9a8d51cac7db3b62e799fc32072c86443 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 5 Nov 2017 12:03:31 -0500 Subject: Add support for file birth/creation times on platforms that have it Fixes #19 --- bfs.1 | 21 +++++++++++-- eval.c | 56 ++++++++++++++++++++++------------ eval.h | 4 +-- expr.h | 2 ++ parse.c | 105 ++++++++++++++++++++++++++++++++++++++++++--------------------- printf.c | 12 ++++++-- util.h | 5 +++ 7 files changed, 145 insertions(+), 60 deletions(-) diff --git a/bfs.1 b/bfs.1 index f49e505..a6ff153 100644 --- a/bfs.1 +++ b/bfs.1 @@ -391,8 +391,8 @@ time of and .I Y can be any of -.RI [ acm ] -.RI ( a ccess/ c hange/ m odification). +.RI [ aBcm ] +.RI ( a ccess/ B irth/ c hange/ m odification). .TP \fB\-regex \fIREGEX\fR Find files whose entire path matches the regular expression @@ -523,6 +523,23 @@ Treat as a path to search (useful if it begins with a dash). .LP Tests: +.PP +\fB\-Bmin\fR [\fI\-+\fR]\fIN\fR +.br +\fB\-Btime\fR [\fI\-+\fR]\fIN\fR +.RS +Find files +.BR B irthed +.I N +minutes/days ago. +.RE +.TP +\fB\-Bnewer \fIFILE\fR +Find files +.BR B irthed +more recently than +.I FILE +was modified. .TP \fB\-depth\fR [\fI\-+\fR]\fIN\fR Find files with depth diff --git a/eval.c b/eval.c index 62dbf55..fe89225 100644 --- a/eval.c +++ b/eval.c @@ -135,15 +135,15 @@ bool eval_access(const struct expr *expr, struct eval_state *state) { } /** - * -[acm]{min,time} tests. + * -[aBcm]?newer tests. */ -bool eval_acmtime(const struct expr *expr, struct eval_state *state) { +bool eval_newer(const struct expr *expr, struct eval_state *state) { const struct stat *statbuf = fill_statbuf(state); if (!statbuf) { return false; } - const struct timespec *time = NULL; + const struct timespec *time; switch (expr->time_field) { case ATIME: time = &statbuf->st_atim; @@ -154,32 +154,32 @@ bool eval_acmtime(const struct expr *expr, struct eval_state *state) { case MTIME: time = &statbuf->st_mtim; break; - } - assert(time); - time_t diff = timespec_diff(&expr->reftime, time); - switch (expr->time_unit) { - case MINUTES: - diff /= 60; - break; - case DAYS: - diff /= 60*60*24; +#if BFS_HAVE_ST_BIRTHTIM + case BTIME: + time = &statbuf->st_birthtim; break; +#endif + + default: + assert(false); + return false; } - return expr_cmp(expr, diff); + return time->tv_sec > expr->reftime.tv_sec + || (time->tv_sec == expr->reftime.tv_sec && time->tv_nsec > expr->reftime.tv_nsec); } /** - * -[ac]?newer tests. + * -[aBcm]{min,time} tests. */ -bool eval_acnewer(const struct expr *expr, struct eval_state *state) { +bool eval_time(const struct expr *expr, struct eval_state *state) { const struct stat *statbuf = fill_statbuf(state); if (!statbuf) { return false; } - const struct timespec *time = NULL; + const struct timespec *time; switch (expr->time_field) { case ATIME: time = &statbuf->st_atim; @@ -190,11 +190,29 @@ bool eval_acnewer(const struct expr *expr, struct eval_state *state) { case MTIME: time = &statbuf->st_mtim; break; + +#if BFS_HAVE_ST_BIRTHTIM + case BTIME: + time = &statbuf->st_birthtim; + break; +#endif + + default: + assert(false); + return false; } - assert(time); - return time->tv_sec > expr->reftime.tv_sec - || (time->tv_sec == expr->reftime.tv_sec && time->tv_nsec > expr->reftime.tv_nsec); + time_t diff = timespec_diff(&expr->reftime, time); + switch (expr->time_unit) { + case MINUTES: + diff /= 60; + break; + case DAYS: + diff /= 60*60*24; + break; + } + + return expr_cmp(expr, diff); } /** diff --git a/eval.h b/eval.h index 356f332..f190703 100644 --- a/eval.h +++ b/eval.h @@ -26,8 +26,8 @@ bool eval_false(const struct expr *expr, struct eval_state *state); bool eval_access(const struct expr *expr, struct eval_state *state); bool eval_perm(const struct expr *expr, struct eval_state *state); -bool eval_acmtime(const struct expr *expr, struct eval_state *state); -bool eval_acnewer(const struct expr *expr, struct eval_state *state); +bool eval_newer(const struct expr *expr, struct eval_state *state); +bool eval_time(const struct expr *expr, struct eval_state *state); bool eval_used(const struct expr *expr, struct eval_state *state); bool eval_gid(const struct expr *expr, struct eval_state *state); diff --git a/expr.h b/expr.h index 0eba0da..5581bf4 100644 --- a/expr.h +++ b/expr.h @@ -82,6 +82,8 @@ enum time_field { CTIME, /** Modification time. */ MTIME, + /** Birth time. */ + BTIME, }; /** diff --git a/parse.c b/parse.c index 6492412..9d533ab 100644 --- a/parse.c +++ b/parse.c @@ -893,38 +893,57 @@ static struct expr *parse_access(struct parser_state *state, int flag, int arg2) } /** - * Parse -[acm]{min,time}. + * Parse -[aBcm]?newer. */ -static struct expr *parse_acmtime(struct parser_state *state, int field, int unit) { - struct expr *expr = parse_test_icmp(state, eval_acmtime); - if (expr) { - expr->cost = STAT_COST; - expr->reftime = state->now; - expr->time_field = field; - expr->time_unit = unit; +static struct expr *parse_newer(struct parser_state *state, int field, int arg2) { + struct expr *expr = parse_unary_test(state, eval_newer); + if (!expr) { + return NULL; + } + +#if !BFS_HAVE_ST_BIRTHTIM + if (field == BTIME) { + cfprintf(state->cmdline->cerr, "%{er}error: %s: File birth times are not supported on this platform.%{rs}\n", expr->argv[0]); + goto fail; + } +#endif + + struct stat sb; + if (stat_arg(state, expr, &sb) != 0) { + goto fail; } + + expr->cost = STAT_COST; + expr->reftime = sb.st_mtim; + expr->time_field = field; return expr; + +fail: + free_expr(expr); + return NULL; } /** - * Parse -[ac]?newer. + * Parse -[aBcm]{min,time}. */ -static struct expr *parse_acnewer(struct parser_state *state, int field, int arg2) { - struct expr *expr = parse_unary_test(state, eval_acnewer); +static struct expr *parse_time(struct parser_state *state, int field, int unit) { + struct expr *expr = parse_test_icmp(state, eval_time); if (!expr) { return NULL; } - struct stat sb; - if (stat_arg(state, expr, &sb) != 0) { +#if !BFS_HAVE_ST_BIRTHTIM + if (field == BTIME) { + cfprintf(state->cmdline->cerr, "%{er}error: %s: File birth times are not supported on this platform.%{rs}\n", expr->argv[0]); free_expr(expr); return NULL; } +#endif expr->cost = STAT_COST; - expr->reftime = sb.st_mtim; + expr->reftime = state->now; expr->time_field = field; - + expr->time_unit = unit; return expr; } @@ -1445,13 +1464,15 @@ static struct expr *parse_lname(struct parser_state *state, int casefold, int ar * Parse -newerXY. */ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { + CFILE *cerr = state->cmdline->cerr; + const char *arg = state->argv[0]; if (strlen(arg) != 8) { - cfprintf(state->cmdline->cerr, "%{er}error: Expected -newerXY; found %s.%{rs}\n", arg); + cfprintf(cerr, "%{er}error: Expected -newerXY; found %s.%{rs}\n", arg); return NULL; } - struct expr *expr = parse_unary_test(state, eval_acnewer); + struct expr *expr = parse_unary_test(state, eval_newer); if (!expr) { goto fail; } @@ -1468,16 +1489,21 @@ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2 break; case 'B': - cfprintf(state->cmdline->cerr, "%{er}error: %s: File birth times ('B') are not supported.%{rs}\n", arg); +#if BFS_HAVE_ST_BIRTHTIM + expr->time_field = BTIME; + break; +#else + cfprintf(cerr, "%{er}error: %s: File birth times ('B') are not supported on this platform.%{rs}\n", arg); goto fail; +#endif default: - cfprintf(state->cmdline->cerr, "%{er}error: %s: For -newerXY, X should be 'a', 'c', 'm', or 'B'.%{rs}\n", arg); + cfprintf(cerr, "%{er}error: %s: For -newerXY, X should be 'a', 'c', 'm', or 'B'.%{rs}\n", arg); goto fail; } if (arg[7] == 't') { - cfprintf(state->cmdline->cerr, "%{er}error: %s: Explicit reference times ('t') are not supported.%{rs}\n", arg); + cfprintf(cerr, "%{er}error: %s: Explicit reference times ('t') are not supported.%{rs}\n", arg); goto fail; } else { struct stat sb; @@ -1497,12 +1523,15 @@ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2 break; case 'B': - cfprintf(state->cmdline->cerr, "%{er}error: %s: File birth times ('B') are not supported.%{rs}\n", arg); +#if BFS_HAVE_ST_BIRTHTIM + expr->reftime = sb.st_birthtim; +#else + cfprintf(cerr, "%{er}error: %s: File birth times ('B') are not supported on this platform.%{rs}\n", arg); goto fail; +#endif default: - cfprintf(state->cmdline->cerr, - "%{er}error: %s: For -newerXY, Y should be 'a', 'c', 'm', 'B', or 't'.%{rs}\n", arg); + cfprintf(cerr, "%{er}error: %s: For -newerXY, Y should be 'a', 'c', 'm', 'B', or 't'.%{rs}\n", arg); goto fail; } } @@ -2310,7 +2339,7 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { cfprintf(cout, " %{blu}-newer%{rs}%{bld}XY%{rs} %{bld}REFERENCE%{rs}\n"); cfprintf(cout, " Find files whose %{bld}X%{rs} time is newer than the %{bld}Y%{rs} time of" " %{bld}REFERENCE%{rs}. %{bld}X%{rs} and %{bld}Y%{rs}\n"); - cfprintf(cout, " can be any of [acm].\n"); + cfprintf(cout, " can be any of [aBcm].\n"); cfprintf(cout, " %{blu}-regex%{rs} %{bld}REGEX%{rs}\n"); cfprintf(cout, " Find files whose entire path matches the regular expression %{bld}REGEX%{rs}\n"); cfprintf(cout, " %{blu}-samefile%{rs} %{bld}FILE%{rs}\n"); @@ -2375,6 +2404,11 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { cfprintf(cout, " %{cyn}-f%{rs} %{mag}PATH%{rs}\n"); cfprintf(cout, " Treat %{mag}PATH%{rs} as a path to search (useful if begins with a dash)\n\n"); + cfprintf(cout, " %{blu}-Bmin%{rs} %{bld}[-+]N%{rs}\n"); + cfprintf(cout, " %{blu}-Btime%{rs} %{bld}[-+]N%{rs}\n"); + cfprintf(cout, " Find files Birthed %{bld}N%{rs} minutes/days ago\n"); + cfprintf(cout, " %{blu}-Bnewer%{rs} %{bld}FILE%{rs}\n"); + cfprintf(cout, " Find files Birthed more recently than %{bld}FILEa%{rs} was modified\n"); cfprintf(cout, " %{blu}-depth%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files with depth %{bld}N%{rs}\n"); cfprintf(cout, " %{blu}-gid%{rs} %{bld}NAME%{rs}\n"); @@ -2451,6 +2485,9 @@ struct table_entry { * The parse table for literals. */ static const struct table_entry parse_table[] = { + {"-Bmin", false, parse_time, BTIME, MINUTES}, + {"-Bnewer", false, parse_newer, BTIME}, + {"-Btime", false, parse_time, BTIME, DAYS}, {"-D", false, parse_debug}, {"-E", false, parse_regex_extended}, {"-O", true, parse_optlevel}, @@ -2459,13 +2496,13 @@ static const struct table_entry parse_table[] = { {"-L", false, parse_follow, BFTW_LOGICAL | BFTW_DETECT_CYCLES, false}, {"-X", false, parse_xargs_safe}, {"-a"}, - {"-amin", false, parse_acmtime, ATIME, MINUTES}, + {"-amin", false, parse_time, ATIME, MINUTES}, {"-and"}, - {"-atime", false, parse_acmtime, ATIME, DAYS}, - {"-anewer", false, parse_acnewer, ATIME}, - {"-cmin", false, parse_acmtime, CTIME, MINUTES}, - {"-ctime", false, parse_acmtime, CTIME, DAYS}, - {"-cnewer", false, parse_acnewer, CTIME}, + {"-anewer", false, parse_newer, ATIME}, + {"-atime", false, parse_time, ATIME, DAYS}, + {"-cmin", false, parse_time, CTIME, MINUTES}, + {"-cnewer", false, parse_newer, CTIME}, + {"-ctime", false, parse_time, CTIME, DAYS}, {"-color", false, parse_color, true}, {"-d", false, parse_depth}, {"-daystart", false, parse_daystart}, @@ -2500,12 +2537,12 @@ static const struct table_entry parse_table[] = { {"-ls", false, parse_ls}, {"-maxdepth", false, parse_depth_limit, false}, {"-mindepth", false, parse_depth_limit, true}, - {"-mmin", false, parse_acmtime, MTIME, MINUTES}, - {"-mnewer", false, parse_acnewer, MTIME}, + {"-mmin", false, parse_time, MTIME, MINUTES}, + {"-mnewer", false, parse_newer, MTIME}, {"-mount", false, parse_mount}, - {"-mtime", false, parse_acmtime, MTIME, DAYS}, + {"-mtime", false, parse_time, MTIME, DAYS}, {"-name", false, parse_name, false}, - {"-newer", false, parse_acnewer, MTIME}, + {"-newer", false, parse_newer, MTIME}, {"-newer", true, parse_newerxy}, {"-nocolor", false, parse_color, false}, {"-nogroup", false, parse_nogroup}, diff --git a/printf.c b/printf.c index 3c32108..9157ccb 100644 --- a/printf.c +++ b/printf.c @@ -87,10 +87,16 @@ static const struct timespec *get_time_field(const struct stat *statbuf, enum ti return &statbuf->st_ctim; case MTIME: return &statbuf->st_mtim; - } - assert(false); - return NULL; +#ifdef BFS_HAVE_ST_BIRTHTIM + case BTIME: + return &statbuf->st_birthtim; +#endif + + default: + assert(false); + return NULL; + } } /** %c, %c, and %t: ctime() */ diff --git a/util.h b/util.h index 8f285d3..50f476e 100644 --- a/util.h +++ b/util.h @@ -32,6 +32,11 @@ # define st_atim st_atimespec # define st_ctim st_ctimespec # define st_mtim st_mtimespec +# define st_birthtim st_birthtimespec +#endif + +#if __APPLE__ || __FreeBSD__ || __NetBSD__ +# define BFS_HAVE_ST_BIRTHTIM true #endif #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) -- cgit v1.2.3