]> Sergey Matveev's repositories - bfs.git/blob - src/ctx.c
Skip mtab
[bfs.git] / src / ctx.c
1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
3
4 #include "ctx.h"
5 #include "alloc.h"
6 #include "color.h"
7 #include "darray.h"
8 #include "diag.h"
9 #include "expr.h"
10 #include "mtab.h"
11 #include "pwcache.h"
12 #include "stat.h"
13 #include "trie.h"
14 #include "xtime.h"
15 #include <errno.h>
16 #include <limits.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19
20 struct bfs_ctx *bfs_ctx_new(void) {
21         struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx);
22         if (!ctx) {
23                 return NULL;
24         }
25
26         ctx->maxdepth = INT_MAX;
27         ctx->flags = BFTW_RECOVER;
28         ctx->strategy = BFTW_BFS;
29         ctx->optlevel = 3;
30
31         trie_init(&ctx->files);
32
33         struct rlimit rl;
34         if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
35                 goto fail;
36         }
37         ctx->nofile_soft = rl.rlim_cur;
38         ctx->nofile_hard = rl.rlim_max;
39
40         ctx->users = bfs_users_new();
41         if (!ctx->users) {
42                 goto fail;
43         }
44
45         ctx->groups = bfs_groups_new();
46         if (!ctx->groups) {
47                 goto fail;
48         }
49
50         if (xgettime(&ctx->now) != 0) {
51                 goto fail;
52         }
53
54         return ctx;
55
56 fail:
57         bfs_ctx_free(ctx);
58         return NULL;
59 }
60
61 const struct bfs_mtab *bfs_ctx_mtab(const struct bfs_ctx *ctx) {
62         struct bfs_ctx *mut = (struct bfs_ctx *)ctx;
63         return mut->mtab;
64
65         if (mut->mtab_error) {
66                 errno = mut->mtab_error;
67         } else if (!mut->mtab) {
68                 mut->mtab = bfs_mtab_parse();
69                 if (!mut->mtab) {
70                         mut->mtab_error = errno;
71                 }
72         }
73
74         return mut->mtab;
75 }
76
77 /**
78  * An open file tracked by the bfs context.
79  */
80 struct bfs_ctx_file {
81         /** The file itself. */
82         CFILE *cfile;
83         /** The path to the file (for diagnostics). */
84         const char *path;
85         /** Remembers I/O errors, to propagate them to the exit status. */
86         int error;
87 };
88
89 CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) {
90         struct bfs_stat sb;
91         if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) {
92                 return NULL;
93         }
94
95         bfs_file_id id;
96         bfs_stat_id(&sb, &id);
97
98         struct trie_leaf *leaf = trie_insert_mem(&ctx->files, id, sizeof(id));
99         if (!leaf) {
100                 return NULL;
101         }
102
103         struct bfs_ctx_file *ctx_file = leaf->value;
104         if (ctx_file) {
105                 ctx_file->path = path;
106                 return ctx_file->cfile;
107         }
108
109         leaf->value = ctx_file = ALLOC(struct bfs_ctx_file);
110         if (!ctx_file) {
111                 trie_remove(&ctx->files, leaf);
112                 return NULL;
113         }
114
115         ctx_file->cfile = cfile;
116         ctx_file->path = path;
117         ctx_file->error = 0;
118
119         if (cfile != ctx->cout && cfile != ctx->cerr) {
120                 ++ctx->nfiles;
121         }
122
123         return cfile;
124 }
125
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) {
135                         continue;
136                 }
137
138                 ctx_file->error = errno;
139                 clearerr(cfile->file);
140
141                 const char *path = ctx_file->path;
142                 if (path) {
143                         bfs_error(ctx, "'%s': %m.\n", path);
144                 } else if (cfile == ctx->cout) {
145                         bfs_error(ctx, "(standard output): %m.\n");
146                 }
147         }
148
149         // Flush the user/group caches, in case the executed command edits the
150         // user/group tables
151         bfs_users_flush(ctx->users);
152         bfs_groups_flush(ctx->groups);
153 }
154
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)) {
159                 ret = -1;
160                 error = EIO;
161         }
162         if (fflush(cfile->file) != 0) {
163                 ret = -1;
164                 error = errno;
165         }
166
167         errno = error;
168         return ret;
169 }
170
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;
174
175         if (cfile == ctx->cout) {
176                 // Will be checked later
177                 return 0;
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);
183                 } else {
184                         return 0;
185                 }
186         }
187
188         int ret = 0, error = 0;
189         if (ferror(cfile->file)) {
190                 ret = -1;
191                 error = EIO;
192         }
193         if (cfclose(cfile) != 0) {
194                 ret = -1;
195                 error = errno;
196         }
197
198         errno = error;
199         return ret;
200 }
201
202 int bfs_ctx_free(struct bfs_ctx *ctx) {
203         int ret = 0;
204
205         if (ctx) {
206                 CFILE *cout = ctx->cout;
207                 CFILE *cerr = ctx->cerr;
208
209                 bfs_expr_free(ctx->exclude);
210                 bfs_expr_free(ctx->expr);
211
212                 bfs_mtab_free(ctx->mtab);
213
214                 bfs_groups_free(ctx->groups);
215                 bfs_users_free(ctx->users);
216
217                 for_trie (leaf, &ctx->files) {
218                         struct bfs_ctx_file *ctx_file = leaf->value;
219
220                         if (ctx_file->error) {
221                                 // An error was previously reported during bfs_ctx_flush()
222                                 ret = -1;
223                         }
224
225                         if (bfs_ctx_fclose(ctx, ctx_file) != 0) {
226                                 if (cerr) {
227                                         bfs_error(ctx, "'%s': %m.\n", ctx_file->path);
228                                 }
229                                 ret = -1;
230                         }
231
232                         free(ctx_file);
233                 }
234                 trie_destroy(&ctx->files);
235
236                 if (cout && bfs_ctx_fflush(cout) != 0) {
237                         if (cerr) {
238                                 bfs_error(ctx, "(standard output): %m.\n");
239                         }
240                         ret = -1;
241                 }
242
243                 cfclose(cout);
244                 cfclose(cerr);
245
246                 free_colors(ctx->colors);
247
248                 for (size_t i = 0; i < darray_length(ctx->paths); ++i) {
249                         free((char *)ctx->paths[i]);
250                 }
251                 darray_free(ctx->paths);
252
253                 free(ctx->argv);
254                 free(ctx);
255         }
256
257         return ret;
258 }