1 /****************************************************************************
2 * bfs *
3 * Copyright (C) 2017-2021 Tavian Barnes <tavianator@tavianator.com> *
4 * *
5 * Permission to use, copy, modify, and/or distribute this software for any *
6 * purpose with or without fee is hereby granted. *
7 * *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF *
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR *
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES *
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN *
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
15 ****************************************************************************/
16
17 #include "printf.h"
18 #include "bftw.h"
19 #include "color.h"
20 #include "ctx.h"
21 #include "diag.h"
22 #include "dir.h"
23 #include "dstring.h"
24 #include "mtab.h"
25 #include "pwcache.h"
26 #include "stat.h"
27 #include "time.h"
28 #include "util.h"
29 #include <assert.h>
30 #include <errno.h>
31 #include <grp.h>
32 #include <pwd.h>
33 #include <stdbool.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39
40 typedef int bfs_printf_fn(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf);
41
42 struct bfs_printf {
43 /** The printing function to invoke. */
44 bfs_printf_fn *fn;
45 /** String data associated with this directive. */
46 char *str;
47 /** The stat field to print. */
48 enum bfs_stat_field stat_field;
49 /** Character data associated with this directive. */
50 char c;
51 /** Some data used by the directive. */
52 const void *ptr;
53 /** The next printf directive in the chain. */
54 struct bfs_printf *next;
55 };
56
57 /** Print some text as-is. */
bfs_printf_literal(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)58 static int bfs_printf_literal(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
59 size_t len = dstrlen(directive->str);
60 if (fwrite(directive->str, 1, len, cfile->file) == len) {
61 return 0;
62 } else {
63 return -1;
64 }
65 }
66
67 /** \c: flush */
bfs_printf_flush(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)68 static int bfs_printf_flush(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
69 return fflush(cfile->file);
70 }
71
72 /** Check if we can safely colorize this directive. */
should_color(CFILE * cfile,const struct bfs_printf * directive)73 static bool should_color(CFILE *cfile, const struct bfs_printf *directive) {
74 return cfile->colors && strcmp(directive->str, "%s") == 0;
75 }
76
77 /**
78 * Print a value to a temporary buffer before formatting it.
79 */
80 #define BFS_PRINTF_BUF(buf, format, ...) \
81 char buf[256]; \
82 int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \
83 assert(ret >= 0 && (size_t)ret < sizeof(buf)); \
84 (void)ret
85
86 /** %a, %c, %t: ctime() */
bfs_printf_ctime(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)87 static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
88 // Not using ctime() itself because GNU find adds nanoseconds
89 static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
90 static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
91
92 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
93 if (!statbuf) {
94 return -1;
95 }
96
97 const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field);
98 if (!ts) {
99 return -1;
100 }
101
102 struct tm tm;
103 if (xlocaltime(&ts->tv_sec, &tm) != 0) {
104 return -1;
105 }
106
107 BFS_PRINTF_BUF(buf, "%s %s %2d %.2d:%.2d:%.2d.%09ld0 %4d",
108 days[tm.tm_wday],
109 months[tm.tm_mon],
110 tm.tm_mday,
111 tm.tm_hour,
112 tm.tm_min,
113 tm.tm_sec,
114 (long)ts->tv_nsec,
115 1900 + tm.tm_year);
116
117 return fprintf(cfile->file, directive->str, buf);
118 }
119
120 /** %A, %B/%W, %C, %T: strftime() */
bfs_printf_strftime(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)121 static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
122 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
123 if (!statbuf) {
124 return -1;
125 }
126
127 const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field);
128 if (!ts) {
129 return -1;
130 }
131
132 struct tm tm;
133 if (xlocaltime(&ts->tv_sec, &tm) != 0) {
134 return -1;
135 }
136
137 int ret;
138 char buf[256];
139 char format[] = "% ";
140 switch (directive->c) {
141 // Non-POSIX strftime() features
142 case '@':
143 ret = snprintf(buf, sizeof(buf), "%lld.%09ld0", (long long)ts->tv_sec, (long)ts->tv_nsec);
144 break;
145 case '+':
146 ret = snprintf(buf, sizeof(buf), "%4d-%.2d-%.2d+%.2d:%.2d:%.2d.%09ld0",
147 1900 + tm.tm_year,
148 tm.tm_mon + 1,
149 tm.tm_mday,
150 tm.tm_hour,
151 tm.tm_min,
152 tm.tm_sec,
153 (long)ts->tv_nsec);
154 break;
155 case 'k':
156 ret = snprintf(buf, sizeof(buf), "%2d", tm.tm_hour);
157 break;
158 case 'l':
159 ret = snprintf(buf, sizeof(buf), "%2d", (tm.tm_hour + 11)%12 + 1);
160 break;
161 case 's':
162 ret = snprintf(buf, sizeof(buf), "%lld", (long long)ts->tv_sec);
163 break;
164 case 'S':
165 ret = snprintf(buf, sizeof(buf), "%.2d.%09ld0", tm.tm_sec, (long)ts->tv_nsec);
166 break;
167 case 'T':
168 ret = snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d.%09ld0",
169 tm.tm_hour,
170 tm.tm_min,
171 tm.tm_sec,
172 (long)ts->tv_nsec);
173 break;
174
175 // POSIX strftime() features
176 default:
177 format[1] = directive->c;
178 ret = strftime(buf, sizeof(buf), format, &tm);
179 break;
180 }
181
182 assert(ret >= 0 && (size_t)ret < sizeof(buf));
183 (void)ret;
184
185 return fprintf(cfile->file, directive->str, buf);
186 }
187
188 /** %b: blocks */
bfs_printf_b(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)189 static int bfs_printf_b(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
190 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
191 if (!statbuf) {
192 return -1;
193 }
194
195 uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 511)/512;
196 BFS_PRINTF_BUF(buf, "%ju", blocks);
197 return fprintf(cfile->file, directive->str, buf);
198 }
199
200 /** %d: depth */
bfs_printf_d(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)201 static int bfs_printf_d(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
202 return fprintf(cfile->file, directive->str, (intmax_t)ftwbuf->depth);
203 }
204
205 /** %D: device */
bfs_printf_D(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)206 static int bfs_printf_D(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
207 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
208 if (!statbuf) {
209 return -1;
210 }
211
212 BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev);
213 return fprintf(cfile->file, directive->str, buf);
214 }
215
216 /** %f: file name */
bfs_printf_f(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)217 static int bfs_printf_f(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
218 if (should_color(cfile, directive)) {
219 return cfprintf(cfile, "%pF", ftwbuf);
220 } else {
221 return fprintf(cfile->file, directive->str, ftwbuf->path + ftwbuf->nameoff);
222 }
223 }
224
225 /** %F: file system type */
bfs_printf_F(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)226 static int bfs_printf_F(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
227 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
228 if (!statbuf) {
229 return -1;
230 }
231
232 const char *type = bfs_fstype(directive->ptr, statbuf);
233 return fprintf(cfile->file, directive->str, type);
234 }
235
236 /** %G: gid */
bfs_printf_G(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)237 static int bfs_printf_G(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
238 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
239 if (!statbuf) {
240 return -1;
241 }
242
243 BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid);
244 return fprintf(cfile->file, directive->str, buf);
245 }
246
247 /** %g: group name */
bfs_printf_g(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)248 static int bfs_printf_g(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
249 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
250 if (!statbuf) {
251 return -1;
252 }
253
254 const struct bfs_groups *groups = directive->ptr;
255 const struct group *grp = groups ? bfs_getgrgid(groups, statbuf->gid) : NULL;
256 if (!grp) {
257 return bfs_printf_G(cfile, directive, ftwbuf);
258 }
259
260 return fprintf(cfile->file, directive->str, grp->gr_name);
261 }
262
263 /** %h: leading directories */
bfs_printf_h(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)264 static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
265 char *copy = NULL;
266 const char *buf;
267
268 if (ftwbuf->nameoff > 0) {
269 size_t len = ftwbuf->nameoff;
270 if (len > 1) {
271 --len;
272 }
273
274 buf = copy = strndup(ftwbuf->path, len);
275 } else if (ftwbuf->path[0] == '/') {
276 buf = "/";
277 } else {
278 buf = ".";
279 }
280
281 if (!buf) {
282 return -1;
283 }
284
285 int ret;
286 if (should_color(cfile, directive)) {
287 ret = cfprintf(cfile, "${di}%s${rs}", buf);
288 } else {
289 ret = fprintf(cfile->file, directive->str, buf);
290 }
291
292 free(copy);
293 return ret;
294 }
295
296 /** %H: current root */
bfs_printf_H(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)297 static int bfs_printf_H(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
298 if (should_color(cfile, directive)) {
299 if (ftwbuf->depth == 0) {
300 return cfprintf(cfile, "%pP", ftwbuf);
301 } else {
302 return cfprintf(cfile, "${di}%s${rs}", ftwbuf->root);
303 }
304 } else {
305 return fprintf(cfile->file, directive->str, ftwbuf->root);
306 }
307 }
308
309 /** %i: inode */
bfs_printf_i(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)310 static int bfs_printf_i(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
311 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
312 if (!statbuf) {
313 return -1;
314 }
315
316 BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino);
317 return fprintf(cfile->file, directive->str, buf);
318 }
319
320 /** %k: 1K blocks */
bfs_printf_k(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)321 static int bfs_printf_k(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
322 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
323 if (!statbuf) {
324 return -1;
325 }
326
327 uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024;
328 BFS_PRINTF_BUF(buf, "%ju", blocks);
329 return fprintf(cfile->file, directive->str, buf);
330 }
331
332 /** %l: link target */
bfs_printf_l(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)333 static int bfs_printf_l(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
334 char *buf = NULL;
335 const char *target = "";
336
337 if (ftwbuf->type == BFS_LNK) {
338 if (should_color(cfile, directive)) {
339 return cfprintf(cfile, "%pL", ftwbuf);
340 }
341
342 const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW);
343 size_t len = statbuf ? statbuf->size : 0;
344
345 target = buf = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len);
346 if (!target) {
347 return -1;
348 }
349 }
350
351 int ret = fprintf(cfile->file, directive->str, target);
352 free(buf);
353 return ret;
354 }
355
356 /** %m: mode */
bfs_printf_m(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)357 static int bfs_printf_m(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
358 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
359 if (!statbuf) {
360 return -1;
361 }
362
363 return fprintf(cfile->file, directive->str, (unsigned int)(statbuf->mode & 07777));
364 }
365
366 /** %M: symbolic mode */
bfs_printf_M(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)367 static int bfs_printf_M(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
368 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
369 if (!statbuf) {
370 return -1;
371 }
372
373 char buf[11];
374 xstrmode(statbuf->mode, buf);
375 return fprintf(cfile->file, directive->str, buf);
376 }
377
378 /** %n: link count */
bfs_printf_n(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)379 static int bfs_printf_n(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
380 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
381 if (!statbuf) {
382 return -1;
383 }
384
385 BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink);
386 return fprintf(cfile->file, directive->str, buf);
387 }
388
389 /** %p: full path */
bfs_printf_p(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)390 static int bfs_printf_p(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
391 if (should_color(cfile, directive)) {
392 return cfprintf(cfile, "%pP", ftwbuf);
393 } else {
394 return fprintf(cfile->file, directive->str, ftwbuf->path);
395 }
396 }
397
398 /** %P: path after root */
bfs_printf_P(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)399 static int bfs_printf_P(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
400 size_t offset = strlen(ftwbuf->root);
401 if (ftwbuf->path[offset] == '/') {
402 ++offset;
403 }
404
405 if (should_color(cfile, directive)) {
406 if (ftwbuf->depth == 0) {
407 return 0;
408 }
409
410 struct BFTW copybuf = *ftwbuf;
411 copybuf.path += offset;
412 copybuf.nameoff -= offset;
413 return cfprintf(cfile, "%pP", ©buf);
414 } else {
415 return fprintf(cfile->file, directive->str, ftwbuf->path + offset);
416 }
417 }
418
419 /** %s: size */
bfs_printf_s(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)420 static int bfs_printf_s(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
421 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
422 if (!statbuf) {
423 return -1;
424 }
425
426 BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size);
427 return fprintf(cfile->file, directive->str, buf);
428 }
429
430 /** %S: sparseness */
bfs_printf_S(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)431 static int bfs_printf_S(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
432 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
433 if (!statbuf) {
434 return -1;
435 }
436
437 double sparsity;
438 if (statbuf->size == 0 && statbuf->blocks == 0) {
439 sparsity = 1.0;
440 } else {
441 sparsity = (double)BFS_STAT_BLKSIZE*statbuf->blocks/statbuf->size;
442 }
443 return fprintf(cfile->file, directive->str, sparsity);
444 }
445
446 /** %U: uid */
bfs_printf_U(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)447 static int bfs_printf_U(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
448 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
449 if (!statbuf) {
450 return -1;
451 }
452
453 BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid);
454 return fprintf(cfile->file, directive->str, buf);
455 }
456
457 /** %u: user name */
bfs_printf_u(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)458 static int bfs_printf_u(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
459 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
460 if (!statbuf) {
461 return -1;
462 }
463
464 const struct bfs_users *users = directive->ptr;
465 const struct passwd *pwd = users ? bfs_getpwuid(users, statbuf->uid) : NULL;
466 if (!pwd) {
467 return bfs_printf_U(cfile, directive, ftwbuf);
468 }
469
470 return fprintf(cfile->file, directive->str, pwd->pw_name);
471 }
472
bfs_printf_type(enum bfs_type type)473 static const char *bfs_printf_type(enum bfs_type type) {
474 switch (type) {
475 case BFS_BLK:
476 return "b";
477 case BFS_CHR:
478 return "c";
479 case BFS_DIR:
480 return "d";
481 case BFS_DOOR:
482 return "D";
483 case BFS_FIFO:
484 return "p";
485 case BFS_LNK:
486 return "l";
487 case BFS_REG:
488 return "f";
489 case BFS_SOCK:
490 return "s";
491 default:
492 return "U";
493 }
494 }
495
496 /** %y: type */
bfs_printf_y(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)497 static int bfs_printf_y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
498 const char *type = bfs_printf_type(ftwbuf->type);
499 return fprintf(cfile->file, directive->str, type);
500 }
501
502 /** %Y: target type */
bfs_printf_Y(CFILE * cfile,const struct bfs_printf * directive,const struct BFTW * ftwbuf)503 static int bfs_printf_Y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
504 int error = 0;
505
506 if (ftwbuf->type != BFS_LNK) {
507 return bfs_printf_y(cfile, directive, ftwbuf);
508 }
509
510 const char *type = "U";
511
512 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_FOLLOW);
513 if (statbuf) {
514 type = bfs_printf_type(bfs_mode_to_type(statbuf->mode));
515 } else {
516 switch (errno) {
517 case ELOOP:
518 type = "L";
519 break;
520 case ENOENT:
521 case ENOTDIR:
522 type = "N";
523 break;
524 default:
525 type = "?";
526 error = errno;
527 break;
528 }
529 }
530
531 int ret = fprintf(cfile->file, directive->str, type);
532 if (error != 0) {
533 ret = -1;
534 errno = error;
535 }
536 return ret;
537 }
538
539 /**
540 * Free a printf directive.
541 */
free_directive(struct bfs_printf * directive)542 static void free_directive(struct bfs_printf *directive) {
543 if (directive) {
544 dstrfree(directive->str);
545 free(directive);
546 }
547 }
548
549 /**
550 * Create a new printf directive.
551 */
new_directive(const struct bfs_ctx * ctx,bfs_printf_fn * fn)552 static struct bfs_printf *new_directive(const struct bfs_ctx *ctx, bfs_printf_fn *fn) {
553 struct bfs_printf *directive = malloc(sizeof(*directive));
554 if (!directive) {
555 bfs_perror(ctx, "malloc()");
556 goto error;
557 }
558
559 directive->fn = fn;
560 directive->str = dstralloc(2);
561 if (!directive->str) {
562 bfs_perror(ctx, "dstralloc()");
563 goto error;
564 }
565 directive->stat_field = 0;
566 directive->c = 0;
567 directive->ptr = NULL;
568 directive->next = NULL;
569 return directive;
570
571 error:
572 free_directive(directive);
573 return NULL;
574 }
575
576 /**
577 * Append a printf directive to the chain.
578 */
append_directive(struct bfs_printf ** tail,struct bfs_printf * directive)579 static struct bfs_printf **append_directive(struct bfs_printf **tail, struct bfs_printf *directive) {
580 assert(directive);
581 *tail = directive;
582 return &directive->next;
583 }
584
585 /**
586 * Append a literal string to the chain.
587 */
append_literal(struct bfs_printf ** tail,struct bfs_printf ** literal)588 static struct bfs_printf **append_literal(struct bfs_printf **tail, struct bfs_printf **literal) {
589 struct bfs_printf *directive = *literal;
590 if (directive && dstrlen(directive->str) > 0) {
591 *literal = NULL;
592 return append_directive(tail, directive);
593 } else {
594 return tail;
595 }
596 }
597
bfs_printf_parse(const struct bfs_ctx * ctx,const char * format)598 struct bfs_printf *bfs_printf_parse(const struct bfs_ctx *ctx, const char *format) {
599 struct bfs_printf *head = NULL;
600 struct bfs_printf **tail = &head;
601
602 struct bfs_printf *literal = new_directive(ctx, bfs_printf_literal);
603 if (!literal) {
604 goto error;
605 }
606
607 for (const char *i = format; *i; ++i) {
608 char c = *i;
609
610 if (c == '\\') {
611 c = *++i;
612
613 if (c >= '0' && c < '8') {
614 c = 0;
615 for (int j = 0; j < 3 && *i >= '0' && *i < '8'; ++i, ++j) {
616 c *= 8;
617 c += *i - '0';
618 }
619 --i;
620 goto one_char;
621 }
622
623 switch (c) {
624 case 'a': c = '\a'; break;
625 case 'b': c = '\b'; break;
626 case 'f': c = '\f'; break;
627 case 'n': c = '\n'; break;
628 case 'r': c = '\r'; break;
629 case 't': c = '\t'; break;
630 case 'v': c = '\v'; break;
631 case '\\': c = '\\'; break;
632
633 case 'c':
634 tail = append_literal(tail, &literal);
635 struct bfs_printf *directive = new_directive(ctx, bfs_printf_flush);
636 if (!directive) {
637 goto error;
638 }
639 tail = append_directive(tail, directive);
640 goto done;
641
642 case '\0':
643 bfs_error(ctx, "'%s': Incomplete escape sequence '\\'.\n", format);
644 goto error;
645
646 default:
647 bfs_error(ctx, "'%s': Unrecognized escape sequence '\\%c'.\n", format, c);
648 goto error;
649 }
650 } else if (c == '%') {
651 if (i[1] == '%') {
652 c = *++i;
653 goto one_char;
654 }
655
656 struct bfs_printf *directive = new_directive(ctx, NULL);
657 if (!directive) {
658 goto directive_error;
659 }
660 if (dstrapp(&directive->str, c) != 0) {
661 bfs_perror(ctx, "dstrapp()");
662 goto directive_error;
663 }
664
665 const char *specifier = "s";
666
667 // Parse any flags
668 bool must_be_numeric = false;
669 while (true) {
670 c = *++i;
671
672 switch (c) {
673 case '#':
674 case '0':
675 case '+':
676 must_be_numeric = true;
677 BFS_FALLTHROUGH;
678 case ' ':
679 case '-':
680 if (strchr(directive->str, c)) {
681 bfs_error(ctx, "'%s': Duplicate flag '%c'.\n", format, c);
682 goto directive_error;
683 }
684 if (dstrapp(&directive->str, c) != 0) {
685 bfs_perror(ctx, "dstrapp()");
686 goto directive_error;
687 }
688 continue;
689 }
690
691 break;
692 }
693
694 // Parse the field width
695 while (c >= '0' && c <= '9') {
696 if (dstrapp(&directive->str, c) != 0) {
697 bfs_perror(ctx, "dstrapp()");
698 goto directive_error;
699 }
700 c = *++i;
701 }
702
703 // Parse the precision
704 if (c == '.') {
705 do {
706 if (dstrapp(&directive->str, c) != 0) {
707 bfs_perror(ctx, "dstrapp()");
708 goto directive_error;
709 }
710 c = *++i;
711 } while (c >= '0' && c <= '9');
712 }
713
714 switch (c) {
715 case 'a':
716 directive->fn = bfs_printf_ctime;
717 directive->stat_field = BFS_STAT_ATIME;
718 break;
719 case 'b':
720 directive->fn = bfs_printf_b;
721 break;
722 case 'c':
723 directive->fn = bfs_printf_ctime;
724 directive->stat_field = BFS_STAT_CTIME;
725 break;
726 case 'd':
727 directive->fn = bfs_printf_d;
728 specifier = "jd";
729 break;
730 case 'D':
731 directive->fn = bfs_printf_D;
732 break;
733 case 'f':
734 directive->fn = bfs_printf_f;
735 break;
736 case 'F':
737 directive->ptr = bfs_ctx_mtab(ctx);
738 if (!directive->ptr) {
739 bfs_error(ctx, "Couldn't parse the mount table: %m.\n");
740 goto directive_error;
741 }
742 directive->fn = bfs_printf_F;
743 break;
744 case 'g':
745 directive->ptr = bfs_ctx_groups(ctx);
746 if (!directive->ptr) {
747 bfs_error(ctx, "Couldn't parse the group table: %m.\n");
748 goto directive_error;
749 }
750 directive->fn = bfs_printf_g;
751 break;
752 case 'G':
753 directive->fn = bfs_printf_G;
754 break;
755 case 'h':
756 directive->fn = bfs_printf_h;
757 break;
758 case 'H':
759 directive->fn = bfs_printf_H;
760 break;
761 case 'i':
762 directive->fn = bfs_printf_i;
763 break;
764 case 'k':
765 directive->fn = bfs_printf_k;
766 break;
767 case 'l':
768 directive->fn = bfs_printf_l;
769 break;
770 case 'm':
771 directive->fn = bfs_printf_m;
772 specifier = "o";
773 break;
774 case 'M':
775 directive->fn = bfs_printf_M;
776 break;
777 case 'n':
778 directive->fn = bfs_printf_n;
779 break;
780 case 'p':
781 directive->fn = bfs_printf_p;
782 break;
783 case 'P':
784 directive->fn = bfs_printf_P;
785 break;
786 case 's':
787 directive->fn = bfs_printf_s;
788 break;
789 case 'S':
790 directive->fn = bfs_printf_S;
791 specifier = "g";
792 break;
793 case 't':
794 directive->fn = bfs_printf_ctime;
795 directive->stat_field = BFS_STAT_MTIME;
796 break;
797 case 'u':
798 directive->ptr = bfs_ctx_users(ctx);
799 if (!directive->ptr) {
800 bfs_error(ctx, "Couldn't parse the user table: %m.\n");
801 goto directive_error;
802 }
803 directive->fn = bfs_printf_u;
804 break;
805 case 'U':
806 directive->fn = bfs_printf_U;
807 break;
808 case 'w':
809 directive->fn = bfs_printf_ctime;
810 directive->stat_field = BFS_STAT_BTIME;
811 break;
812 case 'y':
813 directive->fn = bfs_printf_y;
814 break;
815 case 'Y':
816 directive->fn = bfs_printf_Y;
817 break;
818
819 case 'A':
820 directive->stat_field = BFS_STAT_ATIME;
821 goto directive_strftime;
822 case 'B':
823 case 'W':
824 directive->stat_field = BFS_STAT_BTIME;
825 goto directive_strftime;
826 case 'C':
827 directive->stat_field = BFS_STAT_CTIME;
828 goto directive_strftime;
829 case 'T':
830 directive->stat_field = BFS_STAT_MTIME;
831 goto directive_strftime;
832
833 directive_strftime:
834 directive->fn = bfs_printf_strftime;
835 c = *++i;
836 if (!c) {
837 bfs_error(ctx, "'%s': Incomplete time specifier '%s%c'.\n", format, directive->str, i[-1]);
838 goto directive_error;
839 } else if (strchr("%+@aAbBcCdDeFgGhHIjklmMnprRsStTuUVwWxXyYzZ", c)) {
840 directive->c = c;
841 } else {
842 bfs_error(ctx, "'%s': Unrecognized time specifier '%%%c%c'.\n", format, i[-1], c);
843 goto directive_error;
844 }
845 break;
846
847 case '\0':
848 bfs_error(ctx, "'%s': Incomplete format specifier '%s'.\n", format, directive->str);
849 goto directive_error;
850
851 default:
852 bfs_error(ctx, "'%s': Unrecognized format specifier '%%%c'.\n", format, c);
853 goto directive_error;
854 }
855
856 if (must_be_numeric && strcmp(specifier, "s") == 0) {
857 bfs_error(ctx, "'%s': Invalid flags '%s' for string format '%%%c'.\n", format, directive->str + 1, c);
858 goto directive_error;
859 }
860
861 if (dstrcat(&directive->str, specifier) != 0) {
862 bfs_perror(ctx, "dstrcat()");
863 goto directive_error;
864 }
865
866 tail = append_literal(tail, &literal);
867 tail = append_directive(tail, directive);
868
869 if (!literal) {
870 literal = new_directive(ctx, bfs_printf_literal);
871 if (!literal) {
872 goto error;
873 }
874 }
875
876 continue;
877
878 directive_error:
879 free_directive(directive);
880 goto error;
881 }
882
883 one_char:
884 if (dstrapp(&literal->str, c) != 0) {
885 bfs_perror(ctx, "dstrapp()");
886 goto error;
887 }
888 }
889
890 done:
891 tail = append_literal(tail, &literal);
892 if (head) {
893 free_directive(literal);
894 return head;
895 } else {
896 return literal;
897 }
898
899 error:
900 free_directive(literal);
901 bfs_printf_free(head);
902 return NULL;
903 }
904
bfs_printf(CFILE * cfile,const struct bfs_printf * format,const struct BFTW * ftwbuf)905 int bfs_printf(CFILE *cfile, const struct bfs_printf *format, const struct BFTW *ftwbuf) {
906 int ret = 0, error = 0;
907
908 for (const struct bfs_printf *directive = format; directive; directive = directive->next) {
909 if (directive->fn(cfile, directive, ftwbuf) < 0) {
910 ret = -1;
911 error = errno;
912 }
913 }
914
915 errno = error;
916 return ret;
917 }
918
bfs_printf_free(struct bfs_printf * format)919 void bfs_printf_free(struct bfs_printf *format) {
920 while (format) {
921 struct bfs_printf *next = format->next;
922 free_directive(format);
923 format = next;
924 }
925 }
926