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