diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2019-03-28 19:43:26 -0400 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2019-03-28 19:43:26 -0400 |
commit | b30242bd400462a74e98bcd79ab5e69831590198 (patch) | |
tree | a0d0e9db47a87d0640567910d5f8361c5b94859a | |
parent | 40ddb23b103eb1d29d2aa0ae3e5bc6633bae9c93 (diff) | |
download | bfs-b30242bd400462a74e98bcd79ab5e69831590198.tar.xz |
color: Implement ln=target
With ln=target in LS_COLORS, links should be colored according to their
target's stat() info, not their own.
-rw-r--r-- | color.c | 75 | ||||
-rwxr-xr-x | tests.sh | 5 | ||||
-rw-r--r-- | tests/test_color_ln_target.out | 20 |
3 files changed, 82 insertions, 18 deletions
@@ -71,6 +71,7 @@ struct colors { char *link; char *orphan; char *missing; + bool link_as_target; char *blockdev; char *chardev; @@ -397,6 +398,7 @@ struct colors *parse_colors(const char *ls_colors) { ret |= init_color(colors, "ln", "01;36", &colors->link); ret |= init_color(colors, "or", NULL, &colors->orphan); ret |= init_color(colors, "mi", NULL, &colors->missing); + colors->link_as_target = false; ret |= init_color(colors, "bd", "01;33", &colors->blockdev); ret |= init_color(colors, "cd", "01;33", &colors->chardev); @@ -457,6 +459,12 @@ struct colors *parse_colors(const char *ls_colors) { } } + if (colors->link && strcmp(colors->link, "target") == 0) { + colors->link_as_target = true; + dstrfree(colors->link); + colors->link = NULL; + } + return colors; } @@ -532,6 +540,15 @@ int cfclose(CFILE *cfile) { return ret; } +/** Check if a symlink is broken. */ +static bool is_link_broken(const struct BFTW *ftwbuf) { + if (ftwbuf->at_flags & AT_SYMLINK_NOFOLLOW) { + return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) != 0; + } else { + return true; + } +} + /** Get the color for a file. */ static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf) { const struct bfs_stat *sb = ftwbuf->statbuf; @@ -583,7 +600,7 @@ static const char *file_color(const struct colors *colors, const char *filename, break; case S_IFLNK: - if (colors->orphan && xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) != 0) { + if (colors->orphan && is_link_broken(ftwbuf)) { color = colors->orphan; } else { color = colors->link; @@ -674,16 +691,12 @@ static int print_colored(const struct colors *colors, const char *esc, const cha return 0; } -/** Print the path to a file with the appropriate colors. */ -static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) { +/** Print a path with colors. */ +static int print_path_colored(CFILE *cfile, const struct BFTW *ftwbuf) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; const char *path = ftwbuf->path; - if (!colors) { - return fputs(path, file) == EOF ? -1 : 0; - } - if (ftwbuf->nameoff > 0) { if (print_colored(colors, colors->directory, path, ftwbuf->nameoff, file) != 0) { return -1; @@ -692,15 +705,38 @@ static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) { const char *filename = path + ftwbuf->nameoff; const char *color = file_color(colors, filename, ftwbuf); - if (print_colored(colors, color, filename, strlen(filename), file) != 0) { - return -1; + return print_colored(colors, color, filename, strlen(filename), file); +} + +/** Call stat() again to resolve a link target. */ +static void restat(struct BFTW *ftwbuf, struct bfs_stat *statbuf) { + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, 0, statbuf) == 0) { + ftwbuf->statbuf = statbuf; + } +} + +/** Print the path to a file with the appropriate colors. */ +static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) { + const struct colors *colors = cfile->colors; + if (!colors) { + return fputs(ftwbuf->path, cfile->file) == EOF ? -1 : 0; } - return 0; + if (colors && colors->link_as_target) { + if (ftwbuf->typeflag == BFTW_LNK && (ftwbuf->at_flags & AT_SYMLINK_NOFOLLOW)) { + struct BFTW altbuf = *ftwbuf; + altbuf.at_flags = 0; + struct bfs_stat statbuf; + restat(&altbuf, &statbuf); + return print_path_colored(cfile, &altbuf); + } + } + + return print_path_colored(cfile, ftwbuf); } /** Print a link target with the appropriate colors. */ -static int print_link(CFILE *cfile, const struct BFTW *ftwbuf) { +static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { int ret = -1; char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, 0); @@ -708,18 +744,21 @@ static int print_link(CFILE *cfile, const struct BFTW *ftwbuf) { goto done; } + if (!cfile->colors) { + ret = fputs(target, cfile->file) == EOF ? -1 : 0; + goto done; + } + struct BFTW altbuf = *ftwbuf; altbuf.path = target; altbuf.nameoff = xbasename(target) - target; + altbuf.at_flags = 0; + altbuf.statbuf = NULL; struct bfs_stat statbuf; - if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, 0, 0, &statbuf) == 0) { - altbuf.statbuf = &statbuf; - } else { - altbuf.statbuf = NULL; - } + restat(&altbuf, &statbuf); - ret = print_path(cfile, &altbuf); + ret = print_path_colored(cfile, &altbuf); done: free(target); @@ -804,7 +843,7 @@ int cvfprintf(CFILE *cfile, const char *format, va_list args) { break; case 'L': - if (print_link(cfile, va_arg(args, const struct BFTW *)) != 0) { + if (print_link_target(cfile, va_arg(args, const struct BFTW *)) != 0) { return -1; } break; @@ -628,6 +628,7 @@ bfs_tests=( test_color_L test_color_rs_lc_rc_ec test_color_escapes + test_color_ln_target test_color_mh test_color_mh0 test_color_or @@ -1857,6 +1858,10 @@ function test_color_escapes() { LS_COLORS="lc=\e[:rc=\x6d\::ec=^[[\x6D\0:" bfs_diff rainbow -color } +function test_color_ln_target() { + LS_COLORS="ln=target:or=01;31:mi=01;33:" bfs_diff rainbow -color +} + function test_color_mh() { LS_COLORS="mh=01:" bfs_diff rainbow -color } diff --git a/tests/test_color_ln_target.out b/tests/test_color_ln_target.out new file mode 100644 index 0000000..cd4ec5e --- /dev/null +++ b/tests/test_color_ln_target.out @@ -0,0 +1,20 @@ +[01;34mrainbow[0m +[01;34mrainbow/[0m[01;31mbroken[0m +[01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;33mchardev_link[0m +[01;34mrainbow/[0m[01;35msocket[0m +[01;34mrainbow/[0m[30;42msticky_ow[0m +[01;34mrainbow/[0m[30;43msgid[0m +[01;34mrainbow/[0m[33mpipe[0m +[01;34mrainbow/[0m[34;42mow[0m +[01;34mrainbow/[0m[37;41msugid[0m +[01;34mrainbow/[0m[37;41msuid[0m +[01;34mrainbow/[0m[37;44msticky[0m +[01;34mrainbow/[0mfile.dat +[01;34mrainbow/[0mfile.txt +[01;34mrainbow/[0mlink.txt +[01;34mrainbow/[0mmh1 +[01;34mrainbow/[0mmh2 +[01;34mrainbow/[0mstar.gz +[01;34mrainbow/[0mstar.tar +[01;34mrainbow/[0mstar.tar.gz |