1 /****************************************************************************
2  * bfs                                                                      *
3  * Copyright (C) 2015-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 "color.h"
18 #include "bftw.h"
19 #include "dir.h"
20 #include "dstring.h"
21 #include "expr.h"
22 #include "fsade.h"
23 #include "stat.h"
24 #include "trie.h"
25 #include "util.h"
26 #include <assert.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <stdarg.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 #include <unistd.h>
36 
37 /**
38  * The parsed form of LS_COLORS.
39  */
40 struct colors {
41 	char *reset;
42 	char *leftcode;
43 	char *rightcode;
44 	char *endcode;
45 	char *clear_to_eol;
46 
47 	char *bold;
48 	char *gray;
49 	char *red;
50 	char *green;
51 	char *yellow;
52 	char *blue;
53 	char *magenta;
54 	char *cyan;
55 	char *white;
56 
57 	char *warning;
58 	char *error;
59 
60 	char *normal;
61 
62 	char *file;
63 	char *multi_hard;
64 	char *executable;
65 	char *capable;
66 	char *setgid;
67 	char *setuid;
68 
69 	char *directory;
70 	char *sticky;
71 	char *other_writable;
72 	char *sticky_other_writable;
73 
74 	char *link;
75 	char *orphan;
76 	char *missing;
77 	bool link_as_target;
78 
79 	char *blockdev;
80 	char *chardev;
81 	char *door;
82 	char *pipe;
83 	char *socket;
84 
85 	/** A mapping from color names (fi, di, ln, etc.) to struct fields. */
86 	struct trie names;
87 
88 	/** A mapping from file extensions to colors. */
89 	struct trie ext_colors;
90 };
91 
92 /** Initialize a color in the table. */
init_color(struct colors * colors,const char * name,const char * value,char ** field)93 static int init_color(struct colors *colors, const char *name, const char *value, char **field) {
94 	if (value) {
95 		*field = dstrdup(value);
96 		if (!*field) {
97 			return -1;
98 		}
99 	} else {
100 		*field = NULL;
101 	}
102 
103 	struct trie_leaf *leaf = trie_insert_str(&colors->names, name);
104 	if (leaf) {
105 		leaf->value = field;
106 		return 0;
107 	} else {
108 		return -1;
109 	}
110 }
111 
112 /** Get a color from the table. */
get_color(const struct colors * colors,const char * name)113 static char **get_color(const struct colors *colors, const char *name) {
114 	const struct trie_leaf *leaf = trie_find_str(&colors->names, name);
115 	if (leaf) {
116 		return (char **)leaf->value;
117 	} else {
118 		return NULL;
119 	}
120 }
121 
122 /** Set the value of a color. */
set_color(struct colors * colors,const char * name,char * value)123 static int set_color(struct colors *colors, const char *name, char *value) {
124 	char **color = get_color(colors, name);
125 	if (color) {
126 		dstrfree(*color);
127 		*color = value;
128 		return 0;
129 	} else {
130 		return -1;
131 	}
132 }
133 
134 /**
135  * Transform a file extension for fast lookups, by reversing and lowercasing it.
136  */
extxfrm(char * ext)137 static void extxfrm(char *ext) {
138 	size_t len = strlen(ext);
139 	for (size_t i = 0; i < len - i; ++i) {
140 		char a = ext[i];
141 		char b = ext[len - i - 1];
142 
143 		// What's internationalization?  Doesn't matter, this is what
144 		// GNU ls does.  Luckily, since there's no standard C way to
145 		// casefold.  Not using tolower() here since it respects the
146 		// current locale, which GNU ls doesn't do.
147 		if (a >= 'A' && a <= 'Z') {
148 			a += 'a' - 'A';
149 		}
150 		if (b >= 'A' && b <= 'Z') {
151 			b += 'a' - 'A';
152 		}
153 
154 		ext[i] = b;
155 		ext[len - i - 1] = a;
156 	}
157 }
158 
159 /**
160  * Set the color for an extension.
161  */
set_ext_color(struct colors * colors,char * key,const char * value)162 static int set_ext_color(struct colors *colors, char *key, const char *value) {
163 	extxfrm(key);
164 
165 	// A later *.x should override any earlier *.x, *.y.x, etc.
166 	struct trie_leaf *match;
167 	while ((match = trie_find_postfix(&colors->ext_colors, key))) {
168 		dstrfree(match->value);
169 		trie_remove(&colors->ext_colors, match);
170 	}
171 
172 	struct trie_leaf *leaf = trie_insert_str(&colors->ext_colors, key);
173 	if (leaf) {
174 		leaf->value = (char *)value;
175 		return 0;
176 	} else {
177 		return -1;
178 	}
179 }
180 
181 /**
182  * Find a color by an extension.
183  */
get_ext_color(const struct colors * colors,const char * filename)184 static const char *get_ext_color(const struct colors *colors, const char *filename) {
185 	char *xfrm = strdup(filename);
186 	if (!xfrm) {
187 		return NULL;
188 	}
189 	extxfrm(xfrm);
190 
191 	const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_colors, xfrm);
192 	free(xfrm);
193 	if (leaf) {
194 		return leaf->value;
195 	} else {
196 		return NULL;
197 	}
198 }
199 
200 /**
201  * Parse a chunk of LS_COLORS that may have escape sequences.  The supported
202  * escapes are:
203  *
204  * \a, \b, \f, \n, \r, \t, \v:
205  *     As in C
206  * \e:
207  *     ESC (\033)
208  * \?:
209  *     DEL (\177)
210  * \_:
211  *     ' ' (space)
212  * \NNN:
213  *     Octal
214  * \xNN:
215  *     Hex
216  * ^C:
217  *     Control character.
218  *
219  * See man dir_colors.
220  *
221  * @param value
222  *         The value to parse.
223  * @param end
224  *         The character that marks the end of the chunk.
225  * @param[out] next
226  *         Will be set to the next chunk.
227  * @return
228  *         The parsed chunk as a dstring.
229  */
unescape(const char * value,char end,const char ** next)230 static char *unescape(const char *value, char end, const char **next) {
231 	if (!value) {
232 		goto fail;
233 	}
234 
235 	char *str = dstralloc(0);
236 	if (!str) {
237 		goto fail_str;
238 	}
239 
240 	const char *i;
241 	for (i = value; *i && *i != end; ++i) {
242 		unsigned char c = 0;
243 
244 		switch (*i) {
245 		case '\\':
246 			switch (*++i) {
247 			case 'a':
248 				c = '\a';
249 				break;
250 			case 'b':
251 				c = '\b';
252 				break;
253 			case 'e':
254 				c = '\033';
255 				break;
256 			case 'f':
257 				c = '\f';
258 				break;
259 			case 'n':
260 				c = '\n';
261 				break;
262 			case 'r':
263 				c = '\r';
264 				break;
265 			case 't':
266 				c = '\t';
267 				break;
268 			case 'v':
269 				c = '\v';
270 				break;
271 			case '?':
272 				c = '\177';
273 				break;
274 			case '_':
275 				c = ' ';
276 				break;
277 
278 			case '0':
279 			case '1':
280 			case '2':
281 			case '3':
282 			case '4':
283 			case '5':
284 			case '6':
285 			case '7':
286 				while (i[1] >= '0' && i[1] <= '7') {
287 					c <<= 3;
288 					c |= *i++ - '0';
289 				}
290 				c <<= 3;
291 				c |= *i - '0';
292 				break;
293 
294 			case 'X':
295 			case 'x':
296 				while (true) {
297 					if (i[1] >= '0' && i[1] <= '9') {
298 						c <<= 4;
299 						c |= i[1] - '0';
300 					} else if (i[1] >= 'A' && i[1] <= 'F') {
301 						c <<= 4;
302 						c |= i[1] - 'A' + 0xA;
303 					} else if (i[1] >= 'a' && i[1] <= 'f') {
304 						c <<= 4;
305 						c |= i[1] - 'a' + 0xA;
306 					} else {
307 						break;
308 					}
309 					++i;
310 				}
311 				break;
312 
313 			case '\0':
314 				goto fail_str;
315 
316 			default:
317 				c = *i;
318 				break;
319 			}
320 			break;
321 
322 		case '^':
323 			switch (*++i) {
324 			case '?':
325 				c = '\177';
326 				break;
327 			case '\0':
328 				goto fail_str;
329 			default:
330 				// CTRL masks bits 6 and 7
331 				c = *i & 0x1F;
332 				break;
333 			}
334 			break;
335 
336 		default:
337 			c = *i;
338 			break;
339 		}
340 
341 		if (dstrapp(&str, c) != 0) {
342 			goto fail_str;
343 		}
344 	}
345 
346 	if (*i) {
347 		*next = i + 1;
348 	} else {
349 		*next = NULL;
350 	}
351 
352 	return str;
353 
354 fail_str:
355 	dstrfree(str);
356 fail:
357 	*next = NULL;
358 	return NULL;
359 }
360 
parse_colors(const char * ls_colors)361 struct colors *parse_colors(const char *ls_colors) {
362 	struct colors *colors = malloc(sizeof(struct colors));
363 	if (!colors) {
364 		return NULL;
365 	}
366 
367 	trie_init(&colors->names);
368 	trie_init(&colors->ext_colors);
369 
370 	int ret = 0;
371 
372 	// From man console_codes
373 
374 	ret |= init_color(colors, "rs", "0",      &colors->reset);
375 	ret |= init_color(colors, "lc", "\033[",  &colors->leftcode);
376 	ret |= init_color(colors, "rc", "m",      &colors->rightcode);
377 	ret |= init_color(colors, "ec", NULL,     &colors->endcode);
378 	ret |= init_color(colors, "cl", "\033[K", &colors->clear_to_eol);
379 
380 	ret |= init_color(colors, "bld", "01;39", &colors->bold);
381 	ret |= init_color(colors, "gry", "01;30", &colors->gray);
382 	ret |= init_color(colors, "red", "01;31", &colors->red);
383 	ret |= init_color(colors, "grn", "01;32", &colors->green);
384 	ret |= init_color(colors, "ylw", "01;33", &colors->yellow);
385 	ret |= init_color(colors, "blu", "01;34", &colors->blue);
386 	ret |= init_color(colors, "mag", "01;35", &colors->magenta);
387 	ret |= init_color(colors, "cyn", "01;36", &colors->cyan);
388 	ret |= init_color(colors, "wht", "01;37", &colors->white);
389 
390 	ret |= init_color(colors, "wr", "01;33", &colors->warning);
391 	ret |= init_color(colors, "er", "01;31", &colors->error);
392 
393 	// Defaults from man dir_colors
394 
395 	ret |= init_color(colors, "no", NULL, &colors->normal);
396 
397 	ret |= init_color(colors, "fi", NULL,    &colors->file);
398 	ret |= init_color(colors, "mh", NULL,    &colors->multi_hard);
399 	ret |= init_color(colors, "ex", "01;32", &colors->executable);
400 	ret |= init_color(colors, "ca", "30;41", &colors->capable);
401 	ret |= init_color(colors, "sg", "30;43", &colors->setgid);
402 	ret |= init_color(colors, "su", "37;41", &colors->setuid);
403 
404 	ret |= init_color(colors, "di", "01;34", &colors->directory);
405 	ret |= init_color(colors, "st", "37;44", &colors->sticky);
406 	ret |= init_color(colors, "ow", "34;42", &colors->other_writable);
407 	ret |= init_color(colors, "tw", "30;42", &colors->sticky_other_writable);
408 
409 	ret |= init_color(colors, "ln", "01;36", &colors->link);
410 	ret |= init_color(colors, "or", NULL,    &colors->orphan);
411 	ret |= init_color(colors, "mi", NULL,    &colors->missing);
412 	colors->link_as_target = false;
413 
414 	ret |= init_color(colors, "bd", "01;33", &colors->blockdev);
415 	ret |= init_color(colors, "cd", "01;33", &colors->chardev);
416 	ret |= init_color(colors, "do", "01;35", &colors->door);
417 	ret |= init_color(colors, "pi", "33",    &colors->pipe);
418 	ret |= init_color(colors, "so", "01;35", &colors->socket);
419 
420 	if (ret) {
421 		free_colors(colors);
422 		return NULL;
423 	}
424 
425 	for (const char *chunk = ls_colors, *next; chunk; chunk = next) {
426 		if (chunk[0] == '*') {
427 			char *key = unescape(chunk + 1, '=', &next);
428 			if (!key) {
429 				continue;
430 			}
431 
432 			char *value = unescape(next, ':', &next);
433 			if (value) {
434 				if (set_ext_color(colors, key, value) != 0) {
435 					dstrfree(value);
436 				}
437 			}
438 
439 			dstrfree(key);
440 		} else {
441 			const char *equals = strchr(chunk, '=');
442 			if (!equals) {
443 				break;
444 			}
445 
446 			char *value = unescape(equals + 1, ':', &next);
447 			if (!value) {
448 				continue;
449 			}
450 
451 			char *key = strndup(chunk, equals - chunk);
452 			if (!key) {
453 				dstrfree(value);
454 				continue;
455 			}
456 
457 			// All-zero values should be treated like NULL, to fall
458 			// back on any other relevant coloring for that file
459 			if (strspn(value, "0") == strlen(value)
460 			    && strcmp(key, "rs") != 0
461 			    && strcmp(key, "lc") != 0
462 			    && strcmp(key, "rc") != 0
463 			    && strcmp(key, "ec") != 0) {
464 				dstrfree(value);
465 				value = NULL;
466 			}
467 
468 			if (set_color(colors, key, value) != 0) {
469 				dstrfree(value);
470 			}
471 			free(key);
472 		}
473 	}
474 
475 	if (colors->link && strcmp(colors->link, "target") == 0) {
476 		colors->link_as_target = true;
477 		dstrfree(colors->link);
478 		colors->link = NULL;
479 	}
480 
481 	return colors;
482 }
483 
free_colors(struct colors * colors)484 void free_colors(struct colors *colors) {
485 	if (colors) {
486 		struct trie_leaf *leaf;
487 		while ((leaf = trie_first_leaf(&colors->ext_colors))) {
488 			dstrfree(leaf->value);
489 			trie_remove(&colors->ext_colors, leaf);
490 		}
491 		trie_destroy(&colors->ext_colors);
492 
493 		while ((leaf = trie_first_leaf(&colors->names))) {
494 			char **field = leaf->value;
495 			dstrfree(*field);
496 			trie_remove(&colors->names, leaf);
497 		}
498 		trie_destroy(&colors->names);
499 
500 		free(colors);
501 	}
502 }
503 
cfwrap(FILE * file,const struct colors * colors,bool close)504 CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) {
505 	CFILE *cfile = malloc(sizeof(*cfile));
506 	if (!cfile) {
507 		return NULL;
508 	}
509 
510 	cfile->buffer = dstralloc(128);
511 	if (!cfile->buffer) {
512 		free(cfile);
513 		return NULL;
514 	}
515 
516 	cfile->file = file;
517 	cfile->close = close;
518 
519 	if (isatty(fileno(file))) {
520 		cfile->colors = colors;
521 	} else {
522 		cfile->colors = NULL;
523 	}
524 
525 	return cfile;
526 }
527 
cfclose(CFILE * cfile)528 int cfclose(CFILE *cfile) {
529 	int ret = 0;
530 
531 	if (cfile) {
532 		dstrfree(cfile->buffer);
533 
534 		if (cfile->close) {
535 			ret = fclose(cfile->file);
536 		}
537 
538 		free(cfile);
539 	}
540 
541 	return ret;
542 }
543 
544 /** Check if a symlink is broken. */
is_link_broken(const struct BFTW * ftwbuf)545 static bool is_link_broken(const struct BFTW *ftwbuf) {
546 	if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) {
547 		return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) != 0;
548 	} else {
549 		return true;
550 	}
551 }
552 
553 /** Get the color for a file. */
file_color(const struct colors * colors,const char * filename,const struct BFTW * ftwbuf,enum bfs_stat_flags flags)554 static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
555 	enum bfs_type type = bftw_type(ftwbuf, flags);
556 	if (type == BFS_ERROR) {
557 		goto error;
558 	}
559 
560 	const struct bfs_stat *statbuf = NULL;
561 	const char *color = NULL;
562 
563 	switch (type) {
564 	case BFS_REG:
565 		if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) {
566 			statbuf = bftw_stat(ftwbuf, flags);
567 			if (!statbuf) {
568 				goto error;
569 			}
570 		}
571 
572 		if (colors->setuid && (statbuf->mode & 04000)) {
573 			color = colors->setuid;
574 		} else if (colors->setgid && (statbuf->mode & 02000)) {
575 			color = colors->setgid;
576 		} else if (colors->capable && bfs_check_capabilities(ftwbuf) > 0) {
577 			color = colors->capable;
578 		} else if (colors->executable && (statbuf->mode & 00111)) {
579 			color = colors->executable;
580 		} else if (colors->multi_hard && statbuf->nlink > 1) {
581 			color = colors->multi_hard;
582 		}
583 
584 		if (!color) {
585 			color = get_ext_color(colors, filename);
586 		}
587 
588 		if (!color) {
589 			color = colors->file;
590 		}
591 
592 		break;
593 
594 	case BFS_DIR:
595 		if (colors->sticky_other_writable || colors->other_writable || colors->sticky) {
596 			statbuf = bftw_stat(ftwbuf, flags);
597 			if (!statbuf) {
598 				goto error;
599 			}
600 		}
601 
602 		if (colors->sticky_other_writable && (statbuf->mode & 01002) == 01002) {
603 			color = colors->sticky_other_writable;
604 		} else if (colors->other_writable && (statbuf->mode & 00002)) {
605 			color = colors->other_writable;
606 		} else if (colors->sticky && (statbuf->mode & 01000)) {
607 			color = colors->sticky;
608 		} else {
609 			color = colors->directory;
610 		}
611 
612 		break;
613 
614 	case BFS_LNK:
615 		if (colors->orphan && is_link_broken(ftwbuf)) {
616 			color = colors->orphan;
617 		} else {
618 			color = colors->link;
619 		}
620 		break;
621 
622 	case BFS_BLK:
623 		color = colors->blockdev;
624 		break;
625 	case BFS_CHR:
626 		color = colors->chardev;
627 		break;
628 	case BFS_FIFO:
629 		color = colors->pipe;
630 		break;
631 	case BFS_SOCK:
632 		color = colors->socket;
633 		break;
634 	case BFS_DOOR:
635 		color = colors->door;
636 		break;
637 
638 	default:
639 		break;
640 	}
641 
642 	if (!color) {
643 		color = colors->normal;
644 	}
645 
646 	return color;
647 
648 error:
649 	if (colors->missing) {
650 		return colors->missing;
651 	} else {
652 		return colors->orphan;
653 	}
654 }
655 
656 /** Print an ANSI escape sequence. */
print_esc(CFILE * cfile,const char * esc)657 static int print_esc(CFILE *cfile, const char *esc) {
658 	const struct colors *colors = cfile->colors;
659 
660 	if (dstrdcat(&cfile->buffer, colors->leftcode) != 0) {
661 		return -1;
662 	}
663 	if (dstrdcat(&cfile->buffer, esc) != 0) {
664 		return -1;
665 	}
666 	if (dstrdcat(&cfile->buffer, colors->rightcode) != 0) {
667 		return -1;
668 	}
669 
670 	return 0;
671 }
672 
673 /** Reset after an ANSI escape sequence. */
print_reset(CFILE * cfile)674 static int print_reset(CFILE *cfile) {
675 	const struct colors *colors = cfile->colors;
676 
677 	if (colors->endcode) {
678 		return dstrdcat(&cfile->buffer, colors->endcode);
679 	} else {
680 		return print_esc(cfile, colors->reset);
681 	}
682 }
683 
684 /** Print a string with an optional color. */
print_colored(CFILE * cfile,const char * esc,const char * str,size_t len)685 static int print_colored(CFILE *cfile, const char *esc, const char *str, size_t len) {
686 	if (esc) {
687 		if (print_esc(cfile, esc) != 0) {
688 			return -1;
689 		}
690 	}
691 	if (dstrncat(&cfile->buffer, str, len) != 0) {
692 		return -1;
693 	}
694 	if (esc) {
695 		if (print_reset(cfile) != 0) {
696 			return -1;
697 		}
698 	}
699 
700 	return 0;
701 }
702 
703 /** Find the offset of the first broken path component. */
first_broken_offset(const char * path,const struct BFTW * ftwbuf,enum bfs_stat_flags flags,size_t max)704 static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, size_t max) {
705 	ssize_t ret = max;
706 	assert(ret >= 0);
707 
708 	if (bftw_type(ftwbuf, flags) != BFS_ERROR) {
709 		goto out;
710 	}
711 
712 	char *at_path;
713 	int at_fd;
714 	if (path == ftwbuf->path) {
715 		if (ftwbuf->depth == 0) {
716 			at_fd = AT_FDCWD;
717 			at_path = dstrndup(path, max);
718 		} else {
719 			// The parent must have existed to get here
720 			goto out;
721 		}
722 	} else {
723 		// We're in print_link_target(), so resolve relative to the link's parent directory
724 		at_fd = ftwbuf->at_fd;
725 		if (at_fd == AT_FDCWD && path[0] != '/') {
726 			at_path = dstrndup(ftwbuf->path, ftwbuf->nameoff);
727 			if (at_path && dstrncat(&at_path, path, max) != 0) {
728 				ret = -1;
729 				goto out_path;
730 			}
731 		} else {
732 			at_path = dstrndup(path, max);
733 		}
734 	}
735 
736 	if (!at_path) {
737 		ret = -1;
738 		goto out;
739 	}
740 
741 	while (ret > 0) {
742 		if (xfaccessat(at_fd, at_path, F_OK) == 0) {
743 			break;
744 		}
745 
746 		size_t len = dstrlen(at_path);
747 		while (ret && at_path[len - 1] == '/') {
748 			--len, --ret;
749 		}
750 		while (ret && at_path[len - 1] != '/') {
751 			--len, --ret;
752 		}
753 
754 		dstresize(&at_path, len);
755 	}
756 
757 out_path:
758 	dstrfree(at_path);
759 out:
760 	return ret;
761 }
762 
763 /** Print the directories leading up to a file. */
print_dirs_colored(CFILE * cfile,const char * path,const struct BFTW * ftwbuf,enum bfs_stat_flags flags,size_t nameoff)764 static int print_dirs_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, size_t nameoff) {
765 	const struct colors *colors = cfile->colors;
766 
767 	ssize_t broken = first_broken_offset(path, ftwbuf, flags, nameoff);
768 	if (broken < 0) {
769 		return -1;
770 	}
771 
772 	if (broken > 0) {
773 		if (print_colored(cfile, colors->directory, path, broken) != 0) {
774 			return -1;
775 		}
776 	}
777 
778 	if ((size_t)broken < nameoff) {
779 		const char *color = colors->missing;
780 		if (!color) {
781 			color = colors->orphan;
782 		}
783 		if (print_colored(cfile, color, path + broken, nameoff - broken) != 0) {
784 			return -1;
785 		}
786 	}
787 
788 	return 0;
789 }
790 
791 /** Print a file name with colors. */
print_name_colored(CFILE * cfile,const char * name,const struct BFTW * ftwbuf,enum bfs_stat_flags flags)792 static int print_name_colored(CFILE *cfile, const char *name, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
793 	const char *color = file_color(cfile->colors, name, ftwbuf, flags);
794 	return print_colored(cfile, color, name, strlen(name));
795 }
796 
797 /** Print a path with colors. */
print_path_colored(CFILE * cfile,const char * path,const struct BFTW * ftwbuf,enum bfs_stat_flags flags)798 static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
799 	size_t nameoff;
800 	if (path == ftwbuf->path) {
801 		nameoff = ftwbuf->nameoff;
802 	} else {
803 		nameoff = xbasename(path) - path;
804 	}
805 
806 	if (print_dirs_colored(cfile, path, ftwbuf, flags, nameoff) != 0) {
807 		return -1;
808 	}
809 
810 	return print_name_colored(cfile, path + nameoff, ftwbuf, flags);
811 }
812 
813 /** Print the name of a file with the appropriate colors. */
print_name(CFILE * cfile,const struct BFTW * ftwbuf)814 static int print_name(CFILE *cfile, const struct BFTW *ftwbuf) {
815 	const char *name = ftwbuf->path + ftwbuf->nameoff;
816 
817 	const struct colors *colors = cfile->colors;
818 	if (!colors) {
819 		return dstrcat(&cfile->buffer, name);
820 	}
821 
822 	enum bfs_stat_flags flags = ftwbuf->stat_flags;
823 	if (colors->link_as_target && ftwbuf->type == BFS_LNK) {
824 		flags = BFS_STAT_TRYFOLLOW;
825 	}
826 
827 	return print_name_colored(cfile, name, ftwbuf, flags);
828 }
829 
830 /** Print the path to a file with the appropriate colors. */
print_path(CFILE * cfile,const struct BFTW * ftwbuf)831 static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) {
832 	const struct colors *colors = cfile->colors;
833 	if (!colors) {
834 		return dstrcat(&cfile->buffer, ftwbuf->path);
835 	}
836 
837 	enum bfs_stat_flags flags = ftwbuf->stat_flags;
838 	if (colors->link_as_target && ftwbuf->type == BFS_LNK) {
839 		flags = BFS_STAT_TRYFOLLOW;
840 	}
841 
842 	return print_path_colored(cfile, ftwbuf->path, ftwbuf, flags);
843 }
844 
845 /** Print a link target with the appropriate colors. */
print_link_target(CFILE * cfile,const struct BFTW * ftwbuf)846 static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) {
847 	const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW);
848 	size_t len = statbuf ? statbuf->size : 0;
849 
850 	char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len);
851 	if (!target) {
852 		return -1;
853 	}
854 
855 	int ret;
856 	if (cfile->colors) {
857 		ret = print_path_colored(cfile, target, ftwbuf, BFS_STAT_FOLLOW);
858 	} else {
859 		ret = dstrcat(&cfile->buffer, target);
860 	}
861 
862 	free(target);
863 	return ret;
864 }
865 
866 /** Format some colored output to the buffer. */
867 BFS_FORMATTER(2, 3)
868 static int cbuff(CFILE *cfile, const char *format, ...);
869 
870 /** Dump a parsed expression tree, for debugging. */
print_expr(CFILE * cfile,const struct expr * expr,bool verbose)871 static int print_expr(CFILE *cfile, const struct expr *expr, bool verbose) {
872 	if (dstrcat(&cfile->buffer, "(") != 0) {
873 		return -1;
874 	}
875 
876 	if (expr->lhs || expr->rhs) {
877 		if (cbuff(cfile, "${red}%s${rs}", expr->argv[0]) < 0) {
878 			return -1;
879 		}
880 	} else {
881 		if (cbuff(cfile, "${blu}%s${rs}", expr->argv[0]) < 0) {
882 			return -1;
883 		}
884 	}
885 
886 	for (size_t i = 1; i < expr->argc; ++i) {
887 		if (cbuff(cfile, " ${bld}%s${rs}", expr->argv[i]) < 0) {
888 			return -1;
889 		}
890 	}
891 
892 	if (verbose) {
893 		double rate = 0.0, time = 0.0;
894 		if (expr->evaluations) {
895 			rate = 100.0*expr->successes/expr->evaluations;
896 			time = (1.0e9*expr->elapsed.tv_sec + expr->elapsed.tv_nsec)/expr->evaluations;
897 		}
898 		if (cbuff(cfile, " [${ylw}%zu${rs}/${ylw}%zu${rs}=${ylw}%g%%${rs}; ${ylw}%gns${rs}]",
899 		          expr->successes, expr->evaluations, rate, time)) {
900 			return -1;
901 		}
902 	}
903 
904 	if (expr->lhs) {
905 		if (dstrcat(&cfile->buffer, " ") != 0) {
906 			return -1;
907 		}
908 		if (print_expr(cfile, expr->lhs, verbose) != 0) {
909 			return -1;
910 		}
911 	}
912 
913 	if (expr->rhs) {
914 		if (dstrcat(&cfile->buffer, " ") != 0) {
915 			return -1;
916 		}
917 		if (print_expr(cfile, expr->rhs, verbose) != 0) {
918 			return -1;
919 		}
920 	}
921 
922 	if (dstrcat(&cfile->buffer, ")") != 0) {
923 		return -1;
924 	}
925 
926 	return 0;
927 }
928 
cvbuff(CFILE * cfile,const char * format,va_list args)929 static int cvbuff(CFILE *cfile, const char *format, va_list args) {
930 	const struct colors *colors = cfile->colors;
931 	int error = errno;
932 
933 	for (const char *i = format; *i; ++i) {
934 		size_t verbatim = strcspn(i, "%$");
935 		if (dstrncat(&cfile->buffer, i, verbatim) != 0) {
936 			return -1;
937 		}
938 
939 		i += verbatim;
940 		switch (*i) {
941 		case '%':
942 			switch (*++i) {
943 			case '%':
944 				if (dstrapp(&cfile->buffer, '%') != 0) {
945 					return -1;
946 				}
947 				break;
948 
949 			case 'c':
950 				if (dstrapp(&cfile->buffer, va_arg(args, int)) != 0) {
951 					return -1;
952 				}
953 				break;
954 
955 			case 'd':
956 				if (dstrcatf(&cfile->buffer, "%d", va_arg(args, int)) != 0) {
957 					return -1;
958 				}
959 				break;
960 
961 			case 'g':
962 				if (dstrcatf(&cfile->buffer, "%g", va_arg(args, double)) != 0) {
963 					return -1;
964 				}
965 				break;
966 
967 			case 's':
968 				if (dstrcat(&cfile->buffer, va_arg(args, const char *)) != 0) {
969 					return -1;
970 				}
971 				break;
972 
973 			case 'z':
974 				++i;
975 				if (*i != 'u') {
976 					goto invalid;
977 				}
978 				if (dstrcatf(&cfile->buffer, "%zu", va_arg(args, size_t)) != 0) {
979 					return -1;
980 				}
981 				break;
982 
983 			case 'm':
984 				if (dstrcat(&cfile->buffer, strerror(error)) != 0) {
985 					return -1;
986 				}
987 				break;
988 
989 			case 'p':
990 				switch (*++i) {
991 				case 'F':
992 					if (print_name(cfile, va_arg(args, const struct BFTW *)) != 0) {
993 						return -1;
994 					}
995 					break;
996 
997 				case 'P':
998 					if (print_path(cfile, va_arg(args, const struct BFTW *)) != 0) {
999 						return -1;
1000 					}
1001 					break;
1002 
1003 				case 'L':
1004 					if (print_link_target(cfile, va_arg(args, const struct BFTW *)) != 0) {
1005 						return -1;
1006 					}
1007 					break;
1008 
1009 				case 'e':
1010 					if (print_expr(cfile, va_arg(args, const struct expr *), false) != 0) {
1011 						return -1;
1012 					}
1013 					break;
1014 				case 'E':
1015 					if (print_expr(cfile, va_arg(args, const struct expr *), true) != 0) {
1016 						return -1;
1017 					}
1018 					break;
1019 
1020 				default:
1021 					goto invalid;
1022 				}
1023 
1024 				break;
1025 
1026 			default:
1027 				goto invalid;
1028 			}
1029 			break;
1030 
1031 		case '$':
1032 			switch (*++i) {
1033 			case '$':
1034 				if (dstrapp(&cfile->buffer, '$') != 0) {
1035 					return -1;
1036 				}
1037 				break;
1038 
1039 			case '{': {
1040 				++i;
1041 				const char *end = strchr(i, '}');
1042 				if (!end) {
1043 					goto invalid;
1044 				}
1045 				if (!colors) {
1046 					i = end;
1047 					break;
1048 				}
1049 
1050 				size_t len = end - i;
1051 				char name[len + 1];
1052 				memcpy(name, i, len);
1053 				name[len] = '\0';
1054 
1055 				char **esc = get_color(colors, name);
1056 				if (!esc) {
1057 					goto invalid;
1058 				}
1059 				if (*esc) {
1060 					if (print_esc(cfile, *esc) != 0) {
1061 						return -1;
1062 					}
1063 				}
1064 
1065 				i = end;
1066 				break;
1067 			}
1068 
1069 			default:
1070 				goto invalid;
1071 			}
1072 			break;
1073 
1074 		default:
1075 			return 0;
1076 		}
1077 	}
1078 
1079 	return 0;
1080 
1081 invalid:
1082 	assert(!"Invalid format string");
1083 	errno = EINVAL;
1084 	return -1;
1085 }
1086 
cbuff(CFILE * cfile,const char * format,...)1087 static int cbuff(CFILE *cfile, const char *format, ...) {
1088 	va_list args;
1089 	va_start(args, format);
1090 	int ret = cvbuff(cfile, format, args);
1091 	va_end(args);
1092 	return ret;
1093 }
1094 
cvfprintf(CFILE * cfile,const char * format,va_list args)1095 int cvfprintf(CFILE *cfile, const char *format, va_list args) {
1096 	assert(dstrlen(cfile->buffer) == 0);
1097 
1098 	int ret = -1;
1099 	if (cvbuff(cfile, format, args) == 0) {
1100 		size_t len = dstrlen(cfile->buffer);
1101 		if (fwrite(cfile->buffer, 1, len, cfile->file) == len) {
1102 			ret = 0;
1103 		}
1104 	}
1105 
1106 	dstresize(&cfile->buffer, 0);
1107 	return ret;
1108 }
1109 
cfprintf(CFILE * cfile,const char * format,...)1110 int cfprintf(CFILE *cfile, const char *format, ...) {
1111 	va_list args;
1112 	va_start(args, format);
1113 	int ret = cvfprintf(cfile, format, args);
1114 	va_end(args);
1115 	return ret;
1116 }
1117