From b30242bd400462a74e98bcd79ab5e69831590198 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 28 Mar 2019 19:43:26 -0400 Subject: color: Implement ln=target With ln=target in LS_COLORS, links should be colored according to their target's stat() info, not their own. --- color.c | 75 ++++++++++++++++++++++++++++++++---------- tests.sh | 5 +++ tests/test_color_ln_target.out | 20 +++++++++++ 3 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 tests/test_color_ln_target.out diff --git a/color.c b/color.c index 8bcd69b..c6e58cd 100644 --- a/color.c +++ b/color.c @@ -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; diff --git a/tests.sh b/tests.sh index 187daff..3b6c030 100755 --- a/tests.sh +++ b/tests.sh @@ -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 @@ +rainbow +rainbow/broken +rainbow/exec.sh +rainbow/chardev_link +rainbow/socket +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky +rainbow/file.dat +rainbow/file.txt +rainbow/link.txt +rainbow/mh1 +rainbow/mh2 +rainbow/star.gz +rainbow/star.tar +rainbow/star.tar.gz -- cgit v1.2.3