1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
20 struct bfs_ctx *bfs_ctx_new(void) {
21 struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx);
26 ctx->maxdepth = INT_MAX;
27 ctx->flags = BFTW_RECOVER;
28 ctx->strategy = BFTW_BFS;
31 trie_init(&ctx->files);
34 if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
37 ctx->nofile_soft = rl.rlim_cur;
38 ctx->nofile_hard = rl.rlim_max;
40 ctx->users = bfs_users_new();
45 ctx->groups = bfs_groups_new();
50 if (xgettime(&ctx->now) != 0) {
61 const struct bfs_mtab *bfs_ctx_mtab(const struct bfs_ctx *ctx) {
62 struct bfs_ctx *mut = (struct bfs_ctx *)ctx;
65 if (mut->mtab_error) {
66 errno = mut->mtab_error;
67 } else if (!mut->mtab) {
68 mut->mtab = bfs_mtab_parse();
70 mut->mtab_error = errno;
78 * An open file tracked by the bfs context.
81 /** The file itself. */
83 /** The path to the file (for diagnostics). */
85 /** Remembers I/O errors, to propagate them to the exit status. */
89 CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) {
91 if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) {
96 bfs_stat_id(&sb, &id);
98 struct trie_leaf *leaf = trie_insert_mem(&ctx->files, id, sizeof(id));
103 struct bfs_ctx_file *ctx_file = leaf->value;
105 ctx_file->path = path;
106 return ctx_file->cfile;
109 leaf->value = ctx_file = ALLOC(struct bfs_ctx_file);
111 trie_remove(&ctx->files, leaf);
115 ctx_file->cfile = cfile;
116 ctx_file->path = path;
119 if (cfile != ctx->cout && cfile != ctx->cerr) {
126 void bfs_ctx_flush(const struct bfs_ctx *ctx) {
127 // Before executing anything, flush all open streams. This ensures that
128 // - the user sees everything relevant before an -ok[dir] prompt
129 // - output from commands is interleaved consistently with bfs
130 // - executed commands can rely on I/O from other bfs actions
131 for_trie (leaf, &ctx->files) {
132 struct bfs_ctx_file *ctx_file = leaf->value;
133 CFILE *cfile = ctx_file->cfile;
134 if (fflush(cfile->file) == 0) {
138 ctx_file->error = errno;
139 clearerr(cfile->file);
141 const char *path = ctx_file->path;
143 bfs_error(ctx, "'%s': %m.\n", path);
144 } else if (cfile == ctx->cout) {
145 bfs_error(ctx, "(standard output): %m.\n");
149 // Flush the user/group caches, in case the executed command edits the
151 bfs_users_flush(ctx->users);
152 bfs_groups_flush(ctx->groups);
155 /** Flush a file and report any errors. */
156 static int bfs_ctx_fflush(CFILE *cfile) {
157 int ret = 0, error = 0;
158 if (ferror(cfile->file)) {
162 if (fflush(cfile->file) != 0) {
171 /** Close a file tracked by the bfs context. */
172 static int bfs_ctx_fclose(struct bfs_ctx *ctx, struct bfs_ctx_file *ctx_file) {
173 CFILE *cfile = ctx_file->cfile;
175 if (cfile == ctx->cout) {
176 // Will be checked later
178 } else if (cfile == ctx->cerr) {
179 // Writes to stderr are allowed to fail silently, unless the same file was used by
180 // -fprint, -fls, etc.
181 if (ctx_file->path) {
182 return bfs_ctx_fflush(cfile);
188 int ret = 0, error = 0;
189 if (ferror(cfile->file)) {
193 if (cfclose(cfile) != 0) {
202 int bfs_ctx_free(struct bfs_ctx *ctx) {
206 CFILE *cout = ctx->cout;
207 CFILE *cerr = ctx->cerr;
209 bfs_expr_free(ctx->exclude);
210 bfs_expr_free(ctx->expr);
212 bfs_mtab_free(ctx->mtab);
214 bfs_groups_free(ctx->groups);
215 bfs_users_free(ctx->users);
217 for_trie (leaf, &ctx->files) {
218 struct bfs_ctx_file *ctx_file = leaf->value;
220 if (ctx_file->error) {
221 // An error was previously reported during bfs_ctx_flush()
225 if (bfs_ctx_fclose(ctx, ctx_file) != 0) {
227 bfs_error(ctx, "'%s': %m.\n", ctx_file->path);
234 trie_destroy(&ctx->files);
236 if (cout && bfs_ctx_fflush(cout) != 0) {
238 bfs_error(ctx, "(standard output): %m.\n");
246 free_colors(ctx->colors);
248 for (size_t i = 0; i < darray_length(ctx->paths); ++i) {
249 free((char *)ctx->paths[i]);
251 darray_free(ctx->paths);