summaryrefslogtreecommitdiffstats
path: root/src/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c279
1 files changed, 189 insertions, 90 deletions
diff --git a/src/eval.c b/src/eval.c
index 8863e34..0d1bf68 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -5,9 +5,11 @@
* Implementation of all the primary expressions.
*/
-#include "prelude.h"
#include "eval.h"
+
+#include "atomic.h"
#include "bar.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
@@ -22,14 +24,18 @@
#include "printf.h"
#include "pwcache.h"
#include "sanity.h"
+#include "sighook.h"
#include "stat.h"
#include "trie.h"
#include "xregex.h"
+#include "xtime.h"
+
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <grp.h>
#include <pwd.h>
+#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
@@ -37,6 +43,7 @@
#include <string.h>
#include <strings.h>
#include <sys/resource.h>
+#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -51,6 +58,8 @@ struct bfs_eval {
enum bftw_action action;
/** The bfs_eval() return value. */
int *ret;
+ /** The number of errors that have occurred. */
+ size_t *nerrors;
/** Whether to quit immediately. */
bool quit;
};
@@ -58,12 +67,18 @@ struct bfs_eval {
/**
* Print an error message.
*/
-attr(printf(2, 3))
+_printf(2, 3)
static void eval_error(struct bfs_eval *state, const char *format, ...) {
+ const struct bfs_ctx *ctx = state->ctx;
+
+ ++*state->nerrors;
+ if (ctx->ignore_errors) {
+ return;
+ }
+
// By POSIX, any errors should be accompanied by a non-zero exit status
*state->ret = EXIT_FAILURE;
- const struct bfs_ctx *ctx = state->ctx;
CFILE *cerr = ctx->cerr;
bfs_error(ctx, "%pP: ", state->ftwbuf);
@@ -122,11 +137,9 @@ static const struct bfs_stat *eval_stat(struct bfs_eval *state) {
* 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;
+ struct timespec diff = *lhs;
+ timespec_sub(&diff, rhs);
+ return diff.tv_sec;
}
bool bfs_expr_cmp(const struct bfs_expr *expr, long long n) {
@@ -245,8 +258,7 @@ bool eval_newer(const struct bfs_expr *expr, struct bfs_eval *state) {
return false;
}
- return time->tv_sec > expr->reftime.tv_sec
- || (time->tv_sec == expr->reftime.tv_sec && time->tv_nsec > expr->reftime.tv_nsec);
+ return timespec_cmp(time, &expr->reftime) > 0;
}
/**
@@ -267,10 +279,10 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) {
switch (expr->time_unit) {
case BFS_DAYS:
diff /= 60 * 24;
- fallthru;
+ _fallthrough;
case BFS_MINUTES:
diff /= 60;
- fallthru;
+ _fallthrough;
case BFS_SECONDS:
break;
}
@@ -396,7 +408,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c
if (expr->eval_fn == eval_exec) {
if (bfs_exec_finish(expr->exec) != 0) {
if (errno != 0) {
- bfs_error(ctx, "%s %s: %s.\n", expr->argv[0], expr->argv[1], errstr());
+ bfs_error(ctx, "${blu}%pq${rs} ${bld}%pq${rs}: %s.\n", expr->argv[0], expr->argv[1], errstr());
}
ret = -1;
}
@@ -417,7 +429,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c
bool eval_exec(const struct bfs_expr *expr, struct bfs_eval *state) {
bool ret = bfs_exec(expr->exec, state->ftwbuf) == 0;
if (errno != 0) {
- eval_error(state, "%s %s: %s.\n", expr->argv[0], expr->argv[1], errstr());
+ eval_error(state, "${blu}%pq${rs} ${bld}%pq${rs}: %s.\n", expr->argv[0], expr->argv[1], errstr());
}
return ret;
}
@@ -688,6 +700,34 @@ static int print_owner(FILE *file, const char *name, uintmax_t id, int *width) {
}
}
+/** Print a file's modification time. */
+static int print_time(FILE *file, time_t time, time_t now) {
+ struct tm tm;
+ if (!localtime_r(&time, &tm)) {
+ goto error;
+ }
+
+ char time_str[256];
+ size_t time_ret;
+
+ time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60;
+ time_t tomorrow = now + 24 * 60 * 60;
+ if (time <= six_months_ago || time >= tomorrow) {
+ time_ret = strftime(time_str, sizeof(time_str), "%b %e %Y", &tm);
+ } else {
+ time_ret = strftime(time_str, sizeof(time_str), "%b %e %H:%M", &tm);
+ }
+
+ if (time_ret == 0) {
+ goto error;
+ }
+
+ return fprintf(file, " %s", time_str);
+
+error:
+ return fprintf(file, " %jd", (intmax_t)time);
+}
+
/**
* -f?ls action.
*/
@@ -744,28 +784,11 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) {
time_t time = statbuf->mtime.tv_sec;
time_t now = ctx->now.tv_sec;
- time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60;
- time_t tomorrow = now + 24 * 60 * 60;
- struct tm tm;
- if (!localtime_r(&time, &tm)) {
- goto error;
- }
- char time_str[256];
- size_t time_ret;
- if (time <= six_months_ago || time >= tomorrow) {
- time_ret = strftime(time_str, sizeof(time_str), "%b %e %Y", &tm);
- } else {
- time_ret = strftime(time_str, sizeof(time_str), "%b %e %H:%M", &tm);
- }
- if (time_ret == 0) {
- errno = EOVERFLOW;
- goto error;
- }
- if (cfprintf(cfile, " %s${rs}", time_str) < 0) {
+ if (print_time(file, time, now) < 0) {
goto error;
}
- if (cfprintf(cfile, " %pP", ftwbuf) < 0) {
+ if (cfprintf(cfile, "${rs} %pP", ftwbuf) < 0) {
goto error;
}
@@ -997,6 +1020,13 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) {
const struct BFTW *ftwbuf = state->ftwbuf;
enum bfs_stat_flags flags = ftwbuf->stat_flags ^ (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW);
enum bfs_type type = bftw_type(ftwbuf, flags);
+
+ // GNU find treats ELOOP as a broken symbolic link for -xtype l
+ // (but not -L -type l)
+ if ((flags & BFS_STAT_TRYFOLLOW) && type == BFS_ERROR && errno == ELOOP) {
+ type = BFS_LNK;
+ }
+
if (type == BFS_ERROR) {
eval_report_error(state);
return false;
@@ -1005,40 +1035,23 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) {
}
}
-#if _POSIX_MONOTONIC_CLOCK > 0
-# define BFS_CLOCK CLOCK_MONOTONIC
-#elif _POSIX_TIMERS > 0
-# define BFS_CLOCK CLOCK_REALTIME
-#endif
-
/**
- * Call clock_gettime(), if available.
+ * clock_gettime() wrapper.
*/
static int eval_gettime(struct bfs_eval *state, struct timespec *ts) {
-#ifdef BFS_CLOCK
- int ret = clock_gettime(BFS_CLOCK, ts);
- if (ret != 0) {
- bfs_warning(state->ctx, "%pP: clock_gettime(): %s.\n", state->ftwbuf, errstr());
+ clockid_t clock = CLOCK_REALTIME;
+
+#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0
+ if (sysoption(MONOTONIC_CLOCK) > 0) {
+ clock = CLOCK_MONOTONIC;
}
- return ret;
-#else
- return -1;
#endif
-}
-/**
- * Record an elapsed time.
- */
-static void timespec_elapsed(struct timespec *elapsed, const struct timespec *start, const struct timespec *end) {
- elapsed->tv_sec += end->tv_sec - start->tv_sec;
- elapsed->tv_nsec += end->tv_nsec - start->tv_nsec;
- if (elapsed->tv_nsec < 0) {
- elapsed->tv_nsec += 1000000000L;
- --elapsed->tv_sec;
- } else if (elapsed->tv_nsec >= 1000000000L) {
- elapsed->tv_nsec -= 1000000000L;
- ++elapsed->tv_sec;
+ int ret = clock_gettime(clock, ts);
+ if (ret != 0) {
+ bfs_warning(state->ctx, "%pP: clock_gettime(): %s.\n", state->ftwbuf, errstr());
}
+ return ret;
}
/**
@@ -1059,7 +1072,8 @@ static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) {
if (time) {
if (eval_gettime(state, &end) == 0) {
- timespec_elapsed(&expr->elapsed, &start, &end);
+ timespec_sub(&end, &start);
+ timespec_add(&expr->elapsed, &end);
}
}
@@ -1128,20 +1142,7 @@ bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) {
}
/** Update the status bar. */
-static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct timespec *last_status, size_t count) {
- struct timespec now;
- if (eval_gettime(state, &now) == 0) {
- struct timespec elapsed = {0};
- timespec_elapsed(&elapsed, last_status, &now);
-
- // Update every 0.1s
- if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= 100000000L) {
- *last_status = now;
- } else {
- return;
- }
- }
-
+static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, size_t count) {
size_t width = bfs_bar_width(bar);
if (width < 3) {
return;
@@ -1157,7 +1158,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time
size_t rhslen = xstrwidth(rhs);
if (3 + rhslen > width) {
- dstresize(&rhs, 0);
+ dstrshrink(rhs, 0);
rhslen = 0;
}
@@ -1201,7 +1202,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time
}
pathwidth += cwidth;
}
- dstresize(&status, lhslen);
+ dstrshrink(status, lhslen);
if (dstrcat(&status, "...") != 0) {
goto out;
@@ -1284,7 +1285,7 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enu
DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW);
DEBUG_FLAG(flags, BFS_STAT_NOSYNC);
- fprintf(stderr, ") == %d", err ? 0 : -1);
+ fprintf(stderr, ") == %d", err == 0 ? 0 : -1);
if (err) {
fprintf(stderr, " [%d]", err);
@@ -1371,18 +1372,85 @@ struct callback_args {
/** The status bar. */
struct bfs_bar *bar;
- /** The time of the last status update. */
- struct timespec last_status;
+ /** The SIGALRM hook. */
+ struct sighook *alrm_hook;
+ /** The interval timer. */
+ struct timer *timer;
+ /** Flag set by SIGALRM. */
+ atomic bool alrm_flag;
+ /** Flag set by SIGINFO. */
+ atomic bool info_flag;
+
/** The number of files visited so far. */
size_t count;
/** The set of seen files. */
struct trie *seen;
+ /** The number of errors that have occurred. */
+ size_t nerrors;
/** Eventual return value from bfs_eval(). */
int ret;
};
+/** Update the status bar in response to SIGALRM. */
+static void eval_sigalrm(int sig, siginfo_t *info, void *ptr) {
+ struct callback_args *args = ptr;
+ store(&args->alrm_flag, true, relaxed);
+}
+
+/** Show/hide the bar in response to SIGINFO. */
+static void eval_siginfo(int sig, siginfo_t *info, void *ptr) {
+ struct callback_args *args = ptr;
+ store(&args->info_flag, true, relaxed);
+}
+
+/** Show the status bar. */
+static void eval_show_bar(struct callback_args *args) {
+ args->alrm_hook = sighook(SIGALRM, eval_sigalrm, args, SH_CONTINUE);
+ if (!args->alrm_hook) {
+ goto fail;
+ }
+
+ args->bar = bfs_bar_show();
+ if (!args->bar) {
+ goto fail;
+ }
+
+ // Update the bar every 0.1s
+ struct timespec ival = { .tv_nsec = 100 * 1000 * 1000 };
+ args->timer = xtimer_start(&ival);
+ if (!args->timer) {
+ goto fail;
+ }
+
+ // Update the bar immediately
+ store(&args->alrm_flag, true, relaxed);
+
+ return;
+
+fail:
+ bfs_warning(args->ctx, "Couldn't show status bar: %s.\n\n", errstr());
+
+ bfs_bar_hide(args->bar);
+ args->bar = NULL;
+
+ sigunhook(args->alrm_hook);
+ args->alrm_hook = NULL;
+}
+
+/** Hide the status bar. */
+static void eval_hide_bar(struct callback_args *args) {
+ xtimer_stop(args->timer);
+ args->timer = NULL;
+
+ sigunhook(args->alrm_hook);
+ args->alrm_hook = NULL;
+
+ bfs_bar_hide(args->bar);
+ args->bar = NULL;
+}
+
/**
* bftw() callback.
*/
@@ -1397,17 +1465,37 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
state.ctx = ctx;
state.action = BFTW_CONTINUE;
state.ret = &args->ret;
+ state.nerrors = &args->nerrors;
state.quit = false;
- if (args->bar) {
- eval_status(&state, args->bar, &args->last_status, args->count);
+ // Check whether SIGINFO was delivered and show/hide the bar
+ if (exchange(&args->info_flag, false, relaxed)) {
+ if (args->bar) {
+ eval_hide_bar(args);
+ } else {
+ eval_show_bar(args);
+ }
+ }
+
+ if (exchange(&args->alrm_flag, false, relaxed)) {
+ eval_status(&state, args->bar, args->count);
}
if (ftwbuf->type == BFS_ERROR) {
- if (!eval_should_ignore(&state, ftwbuf->error)) {
- eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
- }
state.action = BFTW_PRUNE;
+
+ if (ftwbuf->error == ELOOP && ftwbuf->loopoff > 0) {
+ char *loop = strndup(ftwbuf->path, ftwbuf->loopoff);
+ if (loop) {
+ eval_error(&state, "Filesystem loop back to ${di}%pq${rs}\n", loop);
+ free(loop);
+ goto done;
+ }
+ } else if (eval_should_ignore(&state, ftwbuf->error)) {
+ goto done;
+ }
+
+ eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
goto done;
}
@@ -1618,12 +1706,16 @@ int bfs_eval(struct bfs_ctx *ctx) {
};
if (ctx->status) {
- args.bar = bfs_bar_show();
- if (!args.bar) {
- bfs_warning(ctx, "Couldn't show status bar: %s.\n\n", errstr());
- }
+ eval_show_bar(&args);
}
+#ifdef SIGINFO
+ int siginfo = SIGINFO;
+#else
+ int siginfo = SIGUSR1;
+#endif
+ struct sighook *info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE);
+
struct trie seen;
if (ctx->unique) {
trie_init(&seen);
@@ -1691,7 +1783,14 @@ int bfs_eval(struct bfs_ctx *ctx) {
trie_destroy(&seen);
}
- bfs_bar_hide(args.bar);
+ sigunhook(info_hook);
+ if (args.bar) {
+ eval_hide_bar(&args);
+ }
+
+ if (ctx->ignore_errors && args.nerrors > 0) {
+ bfs_warning(ctx, "Suppressed errors: %zu\n", args.nerrors);
+ }
return args.ret;
}