summaryrefslogtreecommitdiffstats
path: root/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'eval.c')
-rw-r--r--eval.c1644
1 files changed, 0 insertions, 1644 deletions
diff --git a/eval.c b/eval.c
deleted file mode 100644
index 1d0a6f2..0000000
--- a/eval.c
+++ /dev/null
@@ -1,1644 +0,0 @@
-/****************************************************************************
- * bfs *
- * Copyright (C) 2015-2022 Tavian Barnes <tavianator@tavianator.com> *
- * *
- * Permission to use, copy, modify, and/or distribute this software for any *
- * purpose with or without fee is hereby granted. *
- * *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF *
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR *
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES *
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN *
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
- ****************************************************************************/
-
-/**
- * Implementation of all the literal expressions.
- */
-
-#include "eval.h"
-#include "bar.h"
-#include "bftw.h"
-#include "color.h"
-#include "ctx.h"
-#include "darray.h"
-#include "diag.h"
-#include "dir.h"
-#include "dstring.h"
-#include "exec.h"
-#include "expr.h"
-#include "fsade.h"
-#include "mtab.h"
-#include "printf.h"
-#include "pwcache.h"
-#include "stat.h"
-#include "trie.h"
-#include "util.h"
-#include "xregex.h"
-#include "xtime.h"
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <fnmatch.h>
-#include <grp.h>
-#include <pwd.h>
-#include <stdarg.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <unistd.h>
-#include <wchar.h>
-
-struct bfs_eval {
- /** Data about the current file. */
- const struct BFTW *ftwbuf;
- /** The bfs context. */
- const struct bfs_ctx *ctx;
- /** The bftw() callback return value. */
- enum bftw_action action;
- /** The bfs_eval() return value. */
- int *ret;
- /** Whether to quit immediately. */
- bool quit;
-};
-
-/**
- * Print an error message.
- */
-BFS_FORMATTER(2, 3)
-static void eval_error(struct bfs_eval *state, const char *format, ...) {
- // By POSIX, any errors should be accompanied by a non-zero exit status
- *state->ret = EXIT_FAILURE;
-
- int error = errno;
- const struct bfs_ctx *ctx = state->ctx;
- CFILE *cerr = ctx->cerr;
-
- bfs_error(ctx, "%pP: ", state->ftwbuf);
-
- va_list args;
- va_start(args, format);
- errno = error;
- cvfprintf(cerr, format, args);
- va_end(args);
-}
-
-/**
- * Check if an error should be ignored.
- */
-static bool eval_should_ignore(const struct bfs_eval *state, int error) {
- return state->ctx->ignore_races
- && is_nonexistence_error(error)
- && state->ftwbuf->depth > 0;
-}
-
-/**
- * Report an error that occurs during evaluation.
- */
-static void eval_report_error(struct bfs_eval *state) {
- if (!eval_should_ignore(state, errno)) {
- eval_error(state, "%m.\n");
- }
-}
-
-/**
- * Perform a bfs_stat() call if necessary.
- */
-static const struct bfs_stat *eval_stat(struct bfs_eval *state) {
- const struct BFTW *ftwbuf = state->ftwbuf;
- const struct bfs_stat *ret = bftw_stat(ftwbuf, ftwbuf->stat_flags);
- if (!ret) {
- eval_report_error(state);
- }
- return ret;
-}
-
-/**
- * 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;
-}
-
-bool bfs_expr_cmp(const struct bfs_expr *expr, long long n) {
- switch (expr->int_cmp) {
- case BFS_INT_EQUAL:
- return n == expr->num;
- case BFS_INT_LESS:
- return n < expr->num;
- case BFS_INT_GREATER:
- return n > expr->num;
- }
-
- assert(!"Invalid comparison mode");
- return false;
-}
-
-/**
- * -true test.
- */
-bool eval_true(const struct bfs_expr *expr, struct bfs_eval *state) {
- return true;
-}
-
-/**
- * -false test.
- */
-bool eval_false(const struct bfs_expr *expr, struct bfs_eval *state) {
- return false;
-}
-
-/**
- * -executable, -readable, -writable tests.
- */
-bool eval_access(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct BFTW *ftwbuf = state->ftwbuf;
- return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, expr->num) == 0;
-}
-
-/**
- * -acl test.
- */
-bool eval_acl(const struct bfs_expr *expr, struct bfs_eval *state) {
- int ret = bfs_check_acl(state->ftwbuf);
- if (ret >= 0) {
- return ret;
- } else {
- eval_report_error(state);
- return false;
- }
-}
-
-/**
- * -capable test.
- */
-bool eval_capable(const struct bfs_expr *expr, struct bfs_eval *state) {
- int ret = bfs_check_capabilities(state->ftwbuf);
- if (ret >= 0) {
- return ret;
- } else {
- eval_report_error(state);
- return false;
- }
-}
-
-/**
- * Get the given timespec field out of a stat buffer.
- */
-static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct bfs_eval *state) {
- const struct timespec *ret = bfs_stat_time(statbuf, field);
- if (!ret) {
- eval_error(state, "Couldn't get file %s: %m.\n", bfs_stat_field_name(field));
- }
- return ret;
-}
-
-/**
- * -[aBcm]?newer tests.
- */
-bool eval_newer(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state);
- if (!time) {
- return false;
- }
-
- return time->tv_sec > expr->reftime.tv_sec
- || (time->tv_sec == expr->reftime.tv_sec && time->tv_nsec > expr->reftime.tv_nsec);
-}
-
-/**
- * -[aBcm]{min,time} tests.
- */
-bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state);
- if (!time) {
- return false;
- }
-
- time_t diff = timespec_diff(&expr->reftime, time);
- switch (expr->time_unit) {
- case BFS_DAYS:
- diff /= 60*24;
- BFS_FALLTHROUGH;
- case BFS_MINUTES:
- diff /= 60;
- BFS_FALLTHROUGH;
- case BFS_SECONDS:
- break;
- }
-
- return bfs_expr_cmp(expr, diff);
-}
-
-/**
- * -used test.
- */
-bool eval_used(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- const struct timespec *atime = eval_stat_time(statbuf, BFS_STAT_ATIME, state);
- const struct timespec *ctime = eval_stat_time(statbuf, BFS_STAT_CTIME, state);
- if (!atime || !ctime) {
- return false;
- }
-
- long long diff = timespec_diff(atime, ctime);
- if (diff < 0) {
- return false;
- }
-
- long long day_seconds = 60*60*24;
- diff = (diff + day_seconds - 1) / day_seconds;
- return bfs_expr_cmp(expr, diff);
-}
-
-/**
- * -gid test.
- */
-bool eval_gid(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- return bfs_expr_cmp(expr, statbuf->gid);
-}
-
-/**
- * -uid test.
- */
-bool eval_uid(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- return bfs_expr_cmp(expr, statbuf->uid);
-}
-
-/**
- * -nogroup test.
- */
-bool eval_nogroup(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- const struct bfs_groups *groups = bfs_ctx_groups(state->ctx);
- if (!groups) {
- eval_report_error(state);
- return false;
- }
-
- return bfs_getgrgid(groups, statbuf->gid) == NULL;
-}
-
-/**
- * -nouser test.
- */
-bool eval_nouser(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- const struct bfs_users *users = bfs_ctx_users(state->ctx);
- if (!users) {
- eval_report_error(state);
- return false;
- }
-
- return bfs_getpwuid(users, statbuf->uid) == NULL;
-}
-
-/**
- * -delete action.
- */
-bool eval_delete(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct BFTW *ftwbuf = state->ftwbuf;
-
- // Don't try to delete the current directory
- if (strcmp(ftwbuf->path, ".") == 0) {
- return true;
- }
-
- int flag = 0;
-
- // We need to know the actual type of the path, not what it points to
- enum bfs_type type = bftw_type(ftwbuf, BFS_STAT_NOFOLLOW);
- if (type == BFS_DIR) {
- flag |= AT_REMOVEDIR;
- } else if (type == BFS_ERROR) {
- eval_report_error(state);
- return false;
- }
-
- if (unlinkat(ftwbuf->at_fd, ftwbuf->at_path, flag) != 0) {
- eval_report_error(state);
- return false;
- }
-
- return true;
-}
-
-/** Finish any pending -exec ... + operations. */
-static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *ctx) {
- int ret = 0;
-
- if (expr->eval_fn == eval_exec) {
- if (bfs_exec_finish(expr->exec) != 0) {
- if (errno != 0) {
- bfs_error(ctx, "%s %s: %m.\n", expr->argv[0], expr->argv[1]);
- }
- ret = -1;
- }
- } else if (bfs_expr_has_children(expr)) {
- if (expr->lhs && eval_exec_finish(expr->lhs, ctx) != 0) {
- ret = -1;
- }
- if (expr->rhs && eval_exec_finish(expr->rhs, ctx) != 0) {
- ret = -1;
- }
- }
-
- return ret;
-}
-
-/**
- * -exec[dir]/-ok[dir] actions.
- */
-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: %m.\n", expr->argv[0], expr->argv[1]);
- }
- return ret;
-}
-
-/**
- * -exit action.
- */
-bool eval_exit(const struct bfs_expr *expr, struct bfs_eval *state) {
- state->action = BFTW_STOP;
- *state->ret = expr->num;
- state->quit = true;
- return true;
-}
-
-/**
- * -depth N test.
- */
-bool eval_depth(const struct bfs_expr *expr, struct bfs_eval *state) {
- return bfs_expr_cmp(expr, state->ftwbuf->depth);
-}
-
-/**
- * -empty test.
- */
-bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) {
- bool ret = false;
- const struct BFTW *ftwbuf = state->ftwbuf;
-
- if (ftwbuf->type == BFS_DIR) {
- struct bfs_dir *dir = bfs_opendir(ftwbuf->at_fd, ftwbuf->at_path);
- if (!dir) {
- eval_report_error(state);
- goto done;
- }
-
- int did_read = bfs_readdir(dir, NULL);
- if (did_read < 0) {
- eval_report_error(state);
- } else {
- ret = !did_read;
- }
-
- bfs_closedir(dir);
- } else if (ftwbuf->type == BFS_REG) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (statbuf) {
- ret = statbuf->size == 0;
- }
- }
-
-done:
- return ret;
-}
-
-/**
- * -flags test.
- */
-bool eval_flags(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- if (!(statbuf->mask & BFS_STAT_ATTRS)) {
- eval_error(state, "Couldn't get file %s.\n", bfs_stat_field_name(BFS_STAT_ATTRS));
- return false;
- }
-
- unsigned long flags = statbuf->attrs;
- unsigned long set = expr->set_flags;
- unsigned long clear = expr->clear_flags;
-
- switch (expr->flags_cmp) {
- case BFS_MODE_EQUAL:
- return flags == set && !(flags & clear);
-
- case BFS_MODE_ALL:
- return (flags & set) == set && !(flags & clear);
-
- case BFS_MODE_ANY:
- return (flags & set) || (flags & clear) != clear;
- }
-
- assert(!"Invalid comparison mode");
- return false;
-}
-
-/**
- * -fstype test.
- */
-bool eval_fstype(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- const struct bfs_mtab *mtab = bfs_ctx_mtab(state->ctx);
- if (!mtab) {
- eval_report_error(state);
- return false;
- }
-
- const char *type = bfs_fstype(mtab, statbuf);
- return strcmp(type, expr->argv[1]) == 0;
-}
-
-/**
- * -hidden test.
- */
-bool eval_hidden(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct BFTW *ftwbuf = state->ftwbuf;
- const char *name = ftwbuf->path + ftwbuf->nameoff;
-
- // Don't treat "." or ".." as hidden directories. Otherwise we'd filter
- // out everything when given
- //
- // $ bfs . -nohidden
- // $ bfs .. -nohidden
- return name[0] == '.' && strcmp(name, ".") != 0 && strcmp(name, "..") != 0;
-}
-
-/**
- * -inum test.
- */
-bool eval_inum(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- return bfs_expr_cmp(expr, statbuf->ino);
-}
-
-/**
- * -links test.
- */
-bool eval_links(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- return bfs_expr_cmp(expr, statbuf->nlink);
-}
-
-/**
- * -i?lname test.
- */
-bool eval_lname(const struct bfs_expr *expr, struct bfs_eval *state) {
- bool ret = false;
- char *name = NULL;
-
- const struct BFTW *ftwbuf = state->ftwbuf;
- if (ftwbuf->type != BFS_LNK) {
- goto done;
- }
-
- const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW);
- size_t len = statbuf ? statbuf->size : 0;
-
- name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len);
- if (!name) {
- eval_report_error(state);
- goto done;
- }
-
- ret = fnmatch(expr->argv[1], name, expr->num) == 0;
-
-done:
- free(name);
- return ret;
-}
-
-/**
- * -i?name test.
- */
-bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct BFTW *ftwbuf = state->ftwbuf;
-
- const char *name = ftwbuf->path + ftwbuf->nameoff;
- char *copy = NULL;
- if (ftwbuf->depth == 0) {
- // Any trailing slashes are not part of the name. This can only
- // happen for the root path.
- const char *slash = strchr(name, '/');
- if (slash && slash > name) {
- copy = strndup(name, slash - name);
- if (!copy) {
- eval_report_error(state);
- return false;
- }
- name = copy;
- }
- }
-
- bool ret = fnmatch(expr->argv[1], name, expr->num) == 0;
- free(copy);
- return ret;
-}
-
-/**
- * -i?path test.
- */
-bool eval_path(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct BFTW *ftwbuf = state->ftwbuf;
- return fnmatch(expr->argv[1], ftwbuf->path, expr->num) == 0;
-}
-
-/**
- * -perm test.
- */
-bool eval_perm(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- mode_t mode = statbuf->mode;
- mode_t target;
- if (state->ftwbuf->type == BFS_DIR) {
- target = expr->dir_mode;
- } else {
- target = expr->file_mode;
- }
-
- switch (expr->mode_cmp) {
- case BFS_MODE_EQUAL:
- return (mode & 07777) == target;
-
- case BFS_MODE_ALL:
- return (mode & target) == target;
-
- case BFS_MODE_ANY:
- return !(mode & target) == !target;
- }
-
- assert(!"Invalid comparison mode");
- return false;
-}
-
-/**
- * -f?ls action.
- */
-bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) {
- CFILE *cfile = expr->cfile;
- FILE *file = cfile->file;
- const struct bfs_users *users = bfs_ctx_users(state->ctx);
- const struct bfs_groups *groups = bfs_ctx_groups(state->ctx);
- const struct BFTW *ftwbuf = state->ftwbuf;
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- goto done;
- }
-
- uintmax_t ino = statbuf->ino;
- uintmax_t block_size = state->ctx->posixly_correct ? 512 : 1024;
- uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + block_size - 1)/block_size;
- char mode[11];
- xstrmode(statbuf->mode, mode);
- char acl = bfs_check_acl(ftwbuf) > 0 ? '+' : ' ';
- uintmax_t nlink = statbuf->nlink;
- if (fprintf(file, "%9ju %6ju %s%c %2ju", ino, blocks, mode, acl, nlink) < 0) {
- goto error;
- }
-
- uintmax_t uid = statbuf->uid;
- const struct passwd *pwd = users ? bfs_getpwuid(users, uid) : NULL;
- if (pwd) {
- if (fprintf(file, " %-8s", pwd->pw_name) < 0) {
- goto error;
- }
- } else {
- if (fprintf(file, " %-8ju", uid) < 0) {
- goto error;
- }
- }
-
- uintmax_t gid = statbuf->gid;
- const struct group *grp = groups ? bfs_getgrgid(groups, gid) : NULL;
- if (grp) {
- if (fprintf(file, " %-8s", grp->gr_name) < 0) {
- goto error;
- }
- } else {
- if (fprintf(file, " %-8ju", gid) < 0) {
- goto error;
- }
- }
-
- if (ftwbuf->type == BFS_BLK || ftwbuf->type == BFS_CHR) {
- int ma = bfs_major(statbuf->rdev);
- int mi = bfs_minor(statbuf->rdev);
- if (fprintf(file, " %3d, %3d", ma, mi) < 0) {
- goto error;
- }
- } else {
- uintmax_t size = statbuf->size;
- if (fprintf(file, " %8ju", size) < 0) {
- goto error;
- }
- }
-
- time_t time = statbuf->mtime.tv_sec;
- time_t now = expr->reftime.tv_sec;
- time_t six_months_ago = now - 6*30*24*60*60;
- time_t tomorrow = now + 24*60*60;
- struct tm tm;
- if (xlocaltime(&time, &tm) != 0) {
- goto error;
- }
- char time_str[256];
- const char *time_format = "%b %e %H:%M";
- if (time <= six_months_ago || time >= tomorrow) {
- time_format = "%b %e %Y";
- }
- if (!strftime(time_str, sizeof(time_str), time_format, &tm)) {
- errno = EOVERFLOW;
- goto error;
- }
- if (fprintf(file, " %s", time_str) < 0) {
- goto error;
- }
-
- if (cfprintf(cfile, " %pP", ftwbuf) < 0) {
- goto error;
- }
-
- if (ftwbuf->type == BFS_LNK) {
- if (cfprintf(cfile, " -> %pL", ftwbuf) < 0) {
- goto error;
- }
- }
-
- if (fputc('\n', file) == EOF) {
- goto error;
- }
-
-done:
- return true;
-
-error:
- eval_report_error(state);
- return true;
-}
-
-/**
- * -f?print action.
- */
-bool eval_fprint(const struct bfs_expr *expr, struct bfs_eval *state) {
- if (cfprintf(expr->cfile, "%pP\n", state->ftwbuf) < 0) {
- eval_report_error(state);
- }
- return true;
-}
-
-/**
- * -f?print0 action.
- */
-bool eval_fprint0(const struct bfs_expr *expr, struct bfs_eval *state) {
- const char *path = state->ftwbuf->path;
- size_t length = strlen(path) + 1;
- if (fwrite(path, 1, length, expr->cfile->file) != length) {
- eval_report_error(state);
- }
- return true;
-}
-
-/**
- * -f?printf action.
- */
-bool eval_fprintf(const struct bfs_expr *expr, struct bfs_eval *state) {
- if (bfs_printf(expr->cfile, expr->printf, state->ftwbuf) != 0) {
- eval_report_error(state);
- }
-
- return true;
-}
-
-/**
- * -printx action.
- */
-bool eval_fprintx(const struct bfs_expr *expr, struct bfs_eval *state) {
- FILE *file = expr->cfile->file;
- const char *path = state->ftwbuf->path;
-
- while (true) {
- size_t span = strcspn(path, " \t\n\\$'\"`");
- if (fwrite(path, 1, span, file) != span) {
- goto error;
- }
- path += span;
-
- char c = path[0];
- if (!c) {
- break;
- }
-
- char escaped[] = {'\\', c};
- if (fwrite(escaped, 1, sizeof(escaped), file) != sizeof(escaped)) {
- goto error;
- }
- ++path;
- }
-
-
- if (fputc('\n', file) == EOF) {
- goto error;
- }
-
- return true;
-
-error:
- eval_report_error(state);
- return true;
-}
-
-/**
- * -prune action.
- */
-bool eval_prune(const struct bfs_expr *expr, struct bfs_eval *state) {
- state->action = BFTW_PRUNE;
- return true;
-}
-
-/**
- * -quit action.
- */
-bool eval_quit(const struct bfs_expr *expr, struct bfs_eval *state) {
- state->action = BFTW_STOP;
- state->quit = true;
- return true;
-}
-
-/**
- * -i?regex test.
- */
-bool eval_regex(const struct bfs_expr *expr, struct bfs_eval *state) {
- const char *path = state->ftwbuf->path;
-
- int ret = bfs_regexec(expr->regex, path, BFS_REGEX_ANCHOR);
- if (ret < 0) {
- char *str = bfs_regerror(expr->regex);
- if (str) {
- eval_error(state, "%s.\n", str);
- free(str);
- } else {
- eval_error(state, "bfs_regerror(): %m.\n");
- }
- }
-
- return ret > 0;
-}
-
-/**
- * -samefile test.
- */
-bool eval_samefile(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- return statbuf->dev == expr->dev && statbuf->ino == expr->ino;
-}
-
-/**
- * -size test.
- */
-bool eval_size(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- static const off_t scales[] = {
- [BFS_BLOCKS] = 512,
- [BFS_BYTES] = 1,
- [BFS_WORDS] = 2,
- [BFS_KB] = 1LL << 10,
- [BFS_MB] = 1LL << 20,
- [BFS_GB] = 1LL << 30,
- [BFS_TB] = 1LL << 40,
- [BFS_PB] = 1LL << 50,
- };
-
- off_t scale = scales[expr->size_unit];
- off_t size = (statbuf->size + scale - 1)/scale; // Round up
- return bfs_expr_cmp(expr, size);
-}
-
-/**
- * -sparse test.
- */
-bool eval_sparse(const struct bfs_expr *expr, struct bfs_eval *state) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- blkcnt_t expected = (statbuf->size + BFS_STAT_BLKSIZE - 1)/BFS_STAT_BLKSIZE;
- return statbuf->blocks < expected;
-}
-
-/**
- * -type test.
- */
-bool eval_type(const struct bfs_expr *expr, struct bfs_eval *state) {
- return (1 << state->ftwbuf->type) & expr->num;
-}
-
-/**
- * -xattr test.
- */
-bool eval_xattr(const struct bfs_expr *expr, struct bfs_eval *state) {
- int ret = bfs_check_xattrs(state->ftwbuf);
- if (ret >= 0) {
- return ret;
- } else {
- eval_report_error(state);
- return false;
- }
-}
-
-/**
- * -xattrname test.
- */
-bool eval_xattrname(const struct bfs_expr *expr, struct bfs_eval *state) {
- int ret = bfs_check_xattr_named(state->ftwbuf, expr->argv[1]);
- if (ret >= 0) {
- return ret;
- } else {
- eval_report_error(state);
- return false;
- }
-}
-
-/**
- * -xtype test.
- */
-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);
- if (type == BFS_ERROR) {
- eval_report_error(state);
- return false;
- } else {
- return (1 << type) & expr->num;
- }
-}
-
-#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.
- */
-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(): %m.\n", state->ftwbuf);
- }
- 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;
- }
-}
-
-/**
- * Evaluate an expression.
- */
-static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) {
- struct timespec start, end;
- bool time = state->ctx->debug & DEBUG_RATES;
- if (time) {
- if (eval_gettime(state, &start) != 0) {
- time = false;
- }
- }
-
- assert(!state->quit);
-
- bool ret = expr->eval_fn(expr, state);
-
- if (time) {
- if (eval_gettime(state, &end) == 0) {
- timespec_elapsed(&expr->elapsed, &start, &end);
- }
- }
-
- ++expr->evaluations;
- if (ret) {
- ++expr->successes;
- }
-
- if (bfs_expr_never_returns(expr)) {
- assert(state->quit);
- } else if (!state->quit) {
- assert(!expr->always_true || ret);
- assert(!expr->always_false || !ret);
- }
-
- return ret;
-}
-
-/**
- * Evaluate a negation.
- */
-bool eval_not(const struct bfs_expr *expr, struct bfs_eval *state) {
- return !eval_expr(expr->rhs, state);
-}
-
-/**
- * Evaluate a conjunction.
- */
-bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) {
- if (!eval_expr(expr->lhs, state)) {
- return false;
- }
-
- if (state->quit) {
- return false;
- }
-
- return eval_expr(expr->rhs, state);
-}
-
-/**
- * Evaluate a disjunction.
- */
-bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) {
- if (eval_expr(expr->lhs, state)) {
- return true;
- }
-
- if (state->quit) {
- return false;
- }
-
- return eval_expr(expr->rhs, state);
-}
-
-/**
- * Evaluate the comma operator.
- */
-bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) {
- eval_expr(expr->lhs, state);
-
- if (state->quit) {
- return false;
- }
-
- return eval_expr(expr->rhs, 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;
- }
- }
-
- size_t width = bfs_bar_width(bar);
- if (width < 3) {
- return;
- }
-
- const struct BFTW *ftwbuf = state->ftwbuf;
-
- char *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth);
- if (!rhs) {
- return;
- }
-
- size_t rhslen = dstrlen(rhs);
- if (3 + rhslen > width) {
- dstresize(&rhs, 0);
- rhslen = 0;
- }
-
- char *status = dstralloc(0);
- if (!status) {
- goto out_rhs;
- }
-
- const char *path = ftwbuf->path;
- size_t pathlen = ftwbuf->nameoff;
- if (ftwbuf->depth == 0) {
- pathlen = strlen(path);
- }
-
- // Try to make sure even wide characters fit in the status bar
- size_t pathmax = width - rhslen - 3;
- size_t pathwidth = 0;
- mbstate_t mb;
- memset(&mb, 0, sizeof(mb));
- while (pathlen > 0) {
- wchar_t wc;
- size_t len = mbrtowc(&wc, path, pathlen, &mb);
- int cwidth;
- if (len == (size_t)-1) {
- // Invalid byte sequence, assume a single-width '?'
- len = 1;
- cwidth = 1;
- memset(&mb, 0, sizeof(mb));
- } else if (len == (size_t)-2) {
- // Incomplete byte sequence, assume a single-width '?'
- len = pathlen;
- cwidth = 1;
- } else {
- cwidth = wcwidth(wc);
- if (cwidth < 0) {
- cwidth = 0;
- }
- }
-
- if (pathwidth + cwidth > pathmax) {
- break;
- }
-
- if (dstrncat(&status, path, len) != 0) {
- goto out_rhs;
- }
-
- path += len;
- pathlen -= len;
- pathwidth += cwidth;
- }
-
- if (dstrcat(&status, "...") != 0) {
- goto out_rhs;
- }
-
- while (pathwidth < pathmax) {
- if (dstrapp(&status, ' ') != 0) {
- goto out_rhs;
- }
- ++pathwidth;
- }
-
- if (dstrcat(&status, rhs) != 0) {
- goto out_rhs;
- }
-
- bfs_bar_update(bar, status);
-
- dstrfree(status);
-out_rhs:
- dstrfree(rhs);
-}
-
-/** Check if we've seen a file before. */
-static bool eval_file_unique(struct bfs_eval *state, struct trie *seen) {
- const struct bfs_stat *statbuf = eval_stat(state);
- if (!statbuf) {
- return false;
- }
-
- bfs_file_id id;
- bfs_stat_id(statbuf, &id);
-
- struct trie_leaf *leaf = trie_insert_mem(seen, id, sizeof(id));
- if (!leaf) {
- eval_report_error(state);
- return false;
- }
-
- if (leaf->value) {
- state->action = BFTW_PRUNE;
- return false;
- } else {
- leaf->value = leaf;
- return true;
- }
-}
-
-#define DEBUG_FLAG(flags, flag) \
- do { \
- if ((flags & flag) || flags == flag) { \
- fputs(#flag, stderr); \
- flags ^= flag; \
- if (flags) { \
- fputs(" | ", stderr); \
- } \
- } \
- } while (0)
-
-/**
- * Log a stat() call.
- */
-static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, const struct bftw_stat *cache, enum bfs_stat_flags flags) {
- bfs_debug_prefix(ctx, DEBUG_STAT);
-
- fprintf(stderr, "bfs_stat(");
- if (ftwbuf->at_fd == AT_FDCWD) {
- fprintf(stderr, "AT_FDCWD");
- } else {
- size_t baselen = strlen(ftwbuf->path) - strlen(ftwbuf->at_path);
- fprintf(stderr, "\"");
- fwrite(ftwbuf->path, 1, baselen, stderr);
- fprintf(stderr, "\"");
- }
-
- fprintf(stderr, ", \"%s\", ", ftwbuf->at_path);
-
- DEBUG_FLAG(flags, BFS_STAT_FOLLOW);
- DEBUG_FLAG(flags, BFS_STAT_NOFOLLOW);
- DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW);
-
- fprintf(stderr, ") == %d", cache->buf ? 0 : -1);
-
- if (cache->error) {
- fprintf(stderr, " [%d]", cache->error);
- }
-
- fprintf(stderr, "\n");
-}
-
-/**
- * Log any stat() calls that happened.
- */
-static void debug_stats(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf) {
- if (!(ctx->debug & DEBUG_STAT)) {
- return;
- }
-
- const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf;
- if (statbuf || ftwbuf->stat_cache.error) {
- debug_stat(ctx, ftwbuf, &ftwbuf->stat_cache, BFS_STAT_FOLLOW);
- }
-
- const struct bfs_stat *lstatbuf = ftwbuf->lstat_cache.buf;
- if ((lstatbuf && lstatbuf != statbuf) || ftwbuf->lstat_cache.error) {
- debug_stat(ctx, ftwbuf, &ftwbuf->lstat_cache, BFS_STAT_NOFOLLOW);
- }
-}
-
-#define DUMP_MAP(value) [value] = #value
-
-/**
- * Dump the bfs_type for -D search.
- */
-static const char *dump_bfs_type(enum bfs_type type) {
- static const char *types[] = {
- DUMP_MAP(BFS_UNKNOWN),
- DUMP_MAP(BFS_BLK),
- DUMP_MAP(BFS_CHR),
- DUMP_MAP(BFS_DIR),
- DUMP_MAP(BFS_DOOR),
- DUMP_MAP(BFS_FIFO),
- DUMP_MAP(BFS_LNK),
- DUMP_MAP(BFS_PORT),
- DUMP_MAP(BFS_REG),
- DUMP_MAP(BFS_SOCK),
- DUMP_MAP(BFS_WHT),
- };
-
- if (type == BFS_ERROR) {
- return "BFS_ERROR";
- } else {
- return types[type];
- }
-}
-
-/**
- * Dump the bftw_visit for -D search.
- */
-static const char *dump_bftw_visit(enum bftw_visit visit) {
- static const char *visits[] = {
- DUMP_MAP(BFTW_PRE),
- DUMP_MAP(BFTW_POST),
- };
- return visits[visit];
-}
-
-/**
- * Dump the bftw_action for -D search.
- */
-static const char *dump_bftw_action(enum bftw_action action) {
- static const char *actions[] = {
- DUMP_MAP(BFTW_CONTINUE),
- DUMP_MAP(BFTW_PRUNE),
- DUMP_MAP(BFTW_STOP),
- };
- return actions[action];
-}
-
-/**
- * Type passed as the argument to the bftw() callback.
- */
-struct callback_args {
- /** The bfs context. */
- const struct bfs_ctx *ctx;
-
- /** The status bar. */
- struct bfs_bar *bar;
- /** The time of the last status update. */
- struct timespec last_status;
- /** The number of files visited so far. */
- size_t count;
-
- /** The set of seen files. */
- struct trie *seen;
-
- /** Eventual return value from bfs_eval(). */
- int ret;
-};
-
-/**
- * bftw() callback.
- */
-static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
- struct callback_args *args = ptr;
- ++args->count;
-
- const struct bfs_ctx *ctx = args->ctx;
-
- struct bfs_eval state;
- state.ftwbuf = ftwbuf;
- state.ctx = ctx;
- state.action = BFTW_CONTINUE;
- state.ret = &args->ret;
- state.quit = false;
-
- if (args->bar) {
- eval_status(&state, args->bar, &args->last_status, args->count);
- }
-
- if (ftwbuf->type == BFS_ERROR) {
- if (!eval_should_ignore(&state, ftwbuf->error)) {
- eval_error(&state, "%s.\n", strerror(ftwbuf->error));
- }
- state.action = BFTW_PRUNE;
- goto done;
- }
-
- if (ctx->unique && ftwbuf->visit == BFTW_PRE) {
- if (!eval_file_unique(&state, args->seen)) {
- goto done;
- }
- }
-
- if (eval_expr(ctx->exclude, &state)) {
- state.action = BFTW_PRUNE;
- goto done;
- }
-
- if (ctx->xargs_safe && strpbrk(ftwbuf->path, " \t\n\'\"\\")) {
- eval_error(&state, "Path is not safe for xargs.\n");
- state.action = BFTW_PRUNE;
- goto done;
- }
-
- if (ctx->maxdepth < 0 || ftwbuf->depth >= (size_t)ctx->maxdepth) {
- state.action = BFTW_PRUNE;
- }
-
- // In -depth mode, only handle directories on the BFTW_POST visit
- enum bftw_visit expected_visit = BFTW_PRE;
- if ((ctx->flags & BFTW_POST_ORDER)
- && (ctx->strategy == BFTW_IDS || ftwbuf->type == BFS_DIR)
- && ftwbuf->depth < (size_t)ctx->maxdepth) {
- expected_visit = BFTW_POST;
- }
-
- if (ftwbuf->visit == expected_visit
- && ftwbuf->depth >= (size_t)ctx->mindepth
- && ftwbuf->depth <= (size_t)ctx->maxdepth) {
- eval_expr(ctx->expr, &state);
- }
-
-done:
- debug_stats(ctx, ftwbuf);
-
- if (bfs_debug(ctx, DEBUG_SEARCH, "eval_callback({\n")) {
- fprintf(stderr, "\t.path = \"%s\",\n", ftwbuf->path);
- fprintf(stderr, "\t.root = \"%s\",\n", ftwbuf->root);
- fprintf(stderr, "\t.depth = %zu,\n", ftwbuf->depth);
- fprintf(stderr, "\t.visit = %s,\n", dump_bftw_visit(ftwbuf->visit));
- fprintf(stderr, "\t.type = %s,\n", dump_bfs_type(ftwbuf->type));
- fprintf(stderr, "\t.error = %d,\n", ftwbuf->error);
- fprintf(stderr, "}) == %s\n", dump_bftw_action(state.action));
- }
-
- return state.action;
-}
-
-/** Check if an rlimit value is infinite. */
-static bool rlim_isinf(rlim_t r) {
- // Consider RLIM_{INFINITY,SAVED_{CUR,MAX}} all equally infinite
- if (r == RLIM_INFINITY) {
- return true;
- }
-
-#ifdef RLIM_SAVED_CUR
- if (r == RLIM_SAVED_CUR) {
- return true;
- }
-#endif
-
-#ifdef RLIM_SAVED_MAX
- if (r == RLIM_SAVED_MAX) {
- return true;
- }
-#endif
-
- return false;
-}
-
-/** Compare two rlimit values, accounting for RLIM_INFINITY etc. */
-static int rlim_cmp(rlim_t a, rlim_t b) {
- bool a_inf = rlim_isinf(a);
- bool b_inf = rlim_isinf(b);
- if (a_inf || b_inf) {
- return a_inf - b_inf;
- }
-
- return (a > b) - (a < b);
-}
-
-/** Raise RLIMIT_NOFILE if possible, and return the new limit. */
-static int raise_fdlimit(const struct bfs_ctx *ctx) {
- rlim_t target = 64 << 10;
- if (rlim_cmp(target, ctx->nofile_hard) > 0) {
- target = ctx->nofile_hard;
- }
-
- int ret = target;
-
- if (rlim_cmp(target, ctx->nofile_soft) > 0) {
- const struct rlimit rl = {
- .rlim_cur = target,
- .rlim_max = ctx->nofile_hard,
- };
- if (setrlimit(RLIMIT_NOFILE, &rl) != 0) {
- ret = ctx->nofile_soft;
- }
- }
-
- return ret;
-}
-
-/** Infer the number of file descriptors available to bftw(). */
-static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) {
- // 3 for std{in,out,err}
- int nopen = 3 + ctx->nfiles;
-
- // Check /proc/self/fd for the current number of open fds, if possible
- // (we may have inherited more than just the standard ones)
- struct bfs_dir *dir = bfs_opendir(AT_FDCWD, "/proc/self/fd");
- if (!dir) {
- dir = bfs_opendir(AT_FDCWD, "/dev/fd");
- }
- if (dir) {
- // Account for 'dir' itself
- nopen = -1;
-
- while (bfs_readdir(dir, NULL) > 0) {
- ++nopen;
- }
-
- bfs_closedir(dir);
- }
-
- int ret = limit - nopen;
- ret -= ctx->expr->persistent_fds;
- ret -= ctx->expr->ephemeral_fds;
-
- // bftw() needs at least 2 available fds
- if (ret < 2) {
- ret = 2;
- }
-
- return ret;
-}
-
-/**
- * Dump the bftw() flags for -D search.
- */
-static void dump_bftw_flags(enum bftw_flags flags) {
- DEBUG_FLAG(flags, 0);
- DEBUG_FLAG(flags, BFTW_STAT);
- DEBUG_FLAG(flags, BFTW_RECOVER);
- DEBUG_FLAG(flags, BFTW_POST_ORDER);
- DEBUG_FLAG(flags, BFTW_FOLLOW_ROOTS);
- DEBUG_FLAG(flags, BFTW_FOLLOW_ALL);
- DEBUG_FLAG(flags, BFTW_DETECT_CYCLES);
- DEBUG_FLAG(flags, BFTW_SKIP_MOUNTS);
- DEBUG_FLAG(flags, BFTW_PRUNE_MOUNTS);
- DEBUG_FLAG(flags, BFTW_SORT);
- DEBUG_FLAG(flags, BFTW_BUFFER);
-
- assert(!flags);
-}
-
-/**
- * Dump the bftw_strategy for -D search.
- */
-static const char *dump_bftw_strategy(enum bftw_strategy strategy) {
- static const char *strategies[] = {
- DUMP_MAP(BFTW_BFS),
- DUMP_MAP(BFTW_DFS),
- DUMP_MAP(BFTW_IDS),
- DUMP_MAP(BFTW_EDS),
- };
- return strategies[strategy];
-}
-
-/** Check if we need to enable BFTW_BUFFER. */
-static bool eval_must_buffer(const struct bfs_expr *expr) {
-#if __FreeBSD__
- // FreeBSD doesn't properly handle adding/removing directory entries
- // during readdir() on NFS mounts. Work around it by passing BFTW_BUFFER
- // whenever we could be mutating the directory ourselves through -delete
- // or -exec. We don't attempt to handle concurrent modification by other
- // processes, which are racey anyway.
- //
- // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=57696
- // https://github.com/tavianator/bfs/issues/67
-
- if (expr->eval_fn == eval_delete || expr->eval_fn == eval_exec) {
- return true;
- }
-
- if (bfs_expr_has_children(expr)) {
- if (expr->lhs && eval_must_buffer(expr->lhs)) {
- return true;
- }
-
- if (expr->rhs && eval_must_buffer(expr->rhs)) {
- return true;
- }
- }
-#endif // __FreeBSD__
-
- return false;
-}
-
-int bfs_eval(const struct bfs_ctx *ctx) {
- if (!ctx->expr) {
- return EXIT_SUCCESS;
- }
-
- struct callback_args args = {
- .ctx = ctx,
- .ret = EXIT_SUCCESS,
- };
-
- if (ctx->status) {
- args.bar = bfs_bar_show();
- if (!args.bar) {
- bfs_warning(ctx, "Couldn't show status bar: %m.\n\n");
- }
- }
-
- struct trie seen;
- if (ctx->unique) {
- trie_init(&seen);
- args.seen = &seen;
- }
-
- int fdlimit = raise_fdlimit(ctx);
- fdlimit = infer_fdlimit(ctx, fdlimit);
-
- struct bftw_args bftw_args = {
- .paths = ctx->paths,
- .npaths = darray_length(ctx->paths),
- .callback = eval_callback,
- .ptr = &args,
- .nopenfd = fdlimit,
- .flags = ctx->flags,
- .strategy = ctx->strategy,
- .mtab = bfs_ctx_mtab(ctx),
- };
-
- if (eval_must_buffer(ctx->expr)) {
- bftw_args.flags |= BFTW_BUFFER;
- }
-
- if (bfs_debug(ctx, DEBUG_SEARCH, "bftw({\n")) {
- fprintf(stderr, "\t.paths = {\n");
- for (size_t i = 0; i < bftw_args.npaths; ++i) {
- fprintf(stderr, "\t\t\"%s\",\n", bftw_args.paths[i]);
- }
- fprintf(stderr, "\t},\n");
- fprintf(stderr, "\t.npaths = %zu,\n", bftw_args.npaths);
- fprintf(stderr, "\t.callback = eval_callback,\n");
- fprintf(stderr, "\t.ptr = &args,\n");
- fprintf(stderr, "\t.nopenfd = %d,\n", bftw_args.nopenfd);
- fprintf(stderr, "\t.flags = ");
- dump_bftw_flags(bftw_args.flags);
- fprintf(stderr, ",\n\t.strategy = %s,\n", dump_bftw_strategy(bftw_args.strategy));
- fprintf(stderr, "\t.mtab = ");
- if (bftw_args.mtab) {
- fprintf(stderr, "ctx->mtab");
- } else {
- fprintf(stderr, "NULL");
- }
- fprintf(stderr, ",\n})\n");
- }
-
- if (bftw(&bftw_args) != 0) {
- args.ret = EXIT_FAILURE;
- bfs_perror(ctx, "bftw()");
- }
-
- if (eval_exec_finish(ctx->expr, ctx) != 0) {
- args.ret = EXIT_FAILURE;
- }
-
- bfs_ctx_dump(ctx, DEBUG_RATES);
-
- if (ctx->unique) {
- trie_destroy(&seen);
- }
-
- bfs_bar_hide(args.bar);
-
- return args.ret;
-}