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", &copybuf);
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