/**************************************************************************** * 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. * ****************************************************************************/ #include "ctx.h" #include "color.h" #include "darray.h" #include "diag.h" #include "expr.h" #include "mtab.h" #include "pwcache.h" #include "stat.h" #include "trie.h" #include <assert.h> #include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> const char *debug_flag_name(enum debug_flags flag) { switch (flag) { case DEBUG_COST: return "cost"; case DEBUG_EXEC: return "exec"; case DEBUG_OPT: return "opt"; case DEBUG_RATES: return "rates"; case DEBUG_SEARCH: return "search"; case DEBUG_STAT: return "stat"; case DEBUG_TREE: return "tree"; case DEBUG_ALL: break; } assert(!"Unrecognized debug flag"); return "???"; } struct bfs_ctx *bfs_ctx_new(void) { struct bfs_ctx *ctx = malloc(sizeof(*ctx)); if (!ctx) { return NULL; } ctx->argv = NULL; ctx->paths = NULL; ctx->expr = NULL; ctx->exclude = NULL; ctx->mindepth = 0; ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; ctx->optlevel = 3; ctx->debug = 0; ctx->ignore_races = false; ctx->posixly_correct = false; ctx->status = false; ctx->unique = false; ctx->warn = false; ctx->xargs_safe = false; ctx->colors = NULL; ctx->colors_error = 0; ctx->cout = NULL; ctx->cerr = NULL; ctx->users = NULL; ctx->groups = NULL; ctx->mtab = NULL; ctx->mtab_error = 0; trie_init(&ctx->files); ctx->nfiles = 0; struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { goto fail; } ctx->nofile_soft = rl.rlim_cur; ctx->nofile_hard = rl.rlim_max; ctx->users = bfs_users_new(); if (!ctx->users) { goto fail; } ctx->groups = bfs_groups_new(); if (!ctx->groups) { goto fail; } return ctx; fail: bfs_ctx_free(ctx); return NULL; } const struct bfs_mtab *bfs_ctx_mtab(const struct bfs_ctx *ctx) { struct bfs_ctx *mut = (struct bfs_ctx *)ctx; if (mut->mtab_error) { errno = mut->mtab_error; } else if (!mut->mtab) { mut->mtab = bfs_mtab_parse(); if (!mut->mtab) { mut->mtab_error = errno; } } return mut->mtab; } /** * An open file tracked by the bfs context. */ struct bfs_ctx_file { /** The file itself. */ CFILE *cfile; /** The path to the file (for diagnostics). */ const char *path; /** Remembers I/O errors, to propagate them to the exit status. */ int error; }; CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) { struct bfs_stat sb; if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) { return NULL; } bfs_file_id id; bfs_stat_id(&sb, &id); struct trie_leaf *leaf = trie_insert_mem(&ctx->files, id, sizeof(id)); if (!leaf) { return NULL; } struct bfs_ctx_file *ctx_file = leaf->value; if (ctx_file) { ctx_file->path = path; return ctx_file->cfile; } leaf->value = ctx_file = malloc(sizeof(*ctx_file)); if (!ctx_file) { trie_remove(&ctx->files, leaf); return NULL; } ctx_file->cfile = cfile; ctx_file->path = path; ctx_file->error = 0; if (cfile != ctx->cout && cfile != ctx->cerr) { ++ctx->nfiles; } return cfile; } void bfs_ctx_flush(const struct bfs_ctx *ctx) { // Before executing anything, flush all open streams. This ensures that // - the user sees everything relevant before an -ok[dir] prompt // - output from commands is interleaved consistently with bfs // - executed commands can rely on I/O from other bfs actions TRIE_FOR_EACH(&ctx->files, leaf) { struct bfs_ctx_file *ctx_file = leaf->value; CFILE *cfile = ctx_file->cfile; if (fflush(cfile->file) == 0) { continue; } ctx_file->error = errno; clearerr(cfile->file); const char *path = ctx_file->path; if (path) { bfs_error(ctx, "'%s': %m.\n", path); } else if (cfile == ctx->cout) { bfs_error(ctx, "(standard output): %m.\n"); } } // Flush the user/group caches, in case the executed command edits the // user/group tables bfs_users_flush(ctx->users); bfs_groups_flush(ctx->groups); } /** Flush a file and report any errors. */ static int bfs_ctx_fflush(CFILE *cfile) { int ret = 0, error = 0; if (ferror(cfile->file)) { ret = -1; error = EIO; } if (fflush(cfile->file) != 0) { ret = -1; error = errno; } errno = error; return ret; } /** Close a file tracked by the bfs context. */ static int bfs_ctx_fclose(struct bfs_ctx *ctx, struct bfs_ctx_file *ctx_file) { CFILE *cfile = ctx_file->cfile; if (cfile == ctx->cout) { // Will be checked later return 0; } else if (cfile == ctx->cerr) { // Writes to stderr are allowed to fail silently, unless the same file was used by // -fprint, -fls, etc. if (ctx_file->path) { return bfs_ctx_fflush(cfile); } else { return 0; } } int ret = 0, error = 0; if (ferror(cfile->file)) { ret = -1; error = EIO; } if (cfclose(cfile) != 0) { ret = -1; error = errno; } errno = error; return ret; } int bfs_ctx_free(struct bfs_ctx *ctx) { int ret = 0; if (ctx) { CFILE *cout = ctx->cout; CFILE *cerr = ctx->cerr; bfs_expr_free(ctx->exclude); bfs_expr_free(ctx->expr); bfs_mtab_free(ctx->mtab); bfs_groups_free(ctx->groups); bfs_users_free(ctx->users); TRIE_FOR_EACH(&ctx->files, leaf) { struct bfs_ctx_file *ctx_file = leaf->value; if (ctx_file->error) { // An error was previously reported during bfs_ctx_flush() ret = -1; } if (bfs_ctx_fclose(ctx, ctx_file) != 0) { if (cerr) { bfs_error(ctx, "'%s': %m.\n", ctx_file->path); } ret = -1; } free(ctx_file); } trie_destroy(&ctx->files); if (cout && bfs_ctx_fflush(cout) != 0) { if (cerr) { bfs_error(ctx, "(standard output): %m.\n"); } ret = -1; } cfclose(cout); cfclose(cerr); free_colors(ctx->colors); for (size_t i = 0; i < darray_length(ctx->paths); ++i) { free((char *)ctx->paths[i]); } darray_free(ctx->paths); free(ctx->argv); free(ctx); } return ret; }