diff options
Diffstat (limited to 'color.c')
-rw-r--r-- | color.c | 931 |
1 files changed, 0 insertions, 931 deletions
diff --git a/color.c b/color.c deleted file mode 100644 index c397050..0000000 --- a/color.c +++ /dev/null @@ -1,931 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2019 Tavian Barnes <tavianator@tavianator.com> * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -#include "color.h" -#include "bftw.h" -#include "dstring.h" -#include "fsade.h" -#include "stat.h" -#include "trie.h" -#include "util.h" -#include <assert.h> -#include <errno.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> - -/** - * The parsed form of LS_COLORS. - */ -struct colors { - char *reset; - char *leftcode; - char *rightcode; - char *endcode; - char *clear_to_eol; - - char *bold; - char *gray; - char *red; - char *green; - char *yellow; - char *blue; - char *magenta; - char *cyan; - char *white; - - char *warning; - char *error; - - char *normal; - - char *file; - char *multi_hard; - char *executable; - char *capable; - char *setgid; - char *setuid; - - char *directory; - char *sticky; - char *other_writable; - char *sticky_other_writable; - - char *link; - char *orphan; - char *missing; - bool link_as_target; - - char *blockdev; - char *chardev; - char *door; - char *pipe; - char *socket; - - /** A mapping from color names (fi, di, ln, etc.) to struct fields. */ - struct trie names; - - /** A mapping from file extensions to colors. */ - struct trie ext_colors; -}; - -/** Initialize a color in the table. */ -static int init_color(struct colors *colors, const char *name, const char *value, char **field) { - if (value) { - *field = dstrdup(value); - if (!*field) { - return -1; - } - } else { - *field = NULL; - } - - struct trie_leaf *leaf = trie_insert_str(&colors->names, name); - if (leaf) { - leaf->value = field; - return 0; - } else { - return -1; - } -} - -/** Get a color from the table. */ -static char **get_color(const struct colors *colors, const char *name) { - const struct trie_leaf *leaf = trie_find_str(&colors->names, name); - if (leaf) { - return (char **)leaf->value; - } else { - return NULL; - } -} - -/** Set the value of a color. */ -static void set_color(struct colors *colors, const char *name, char *value) { - char **color = get_color(colors, name); - if (color) { - dstrfree(*color); - *color = value; - } -} - -/** - * Transform a file extension for fast lookups, by reversing and lowercasing it. - */ -static void extxfrm(char *ext) { - size_t len = strlen(ext); - for (size_t i = 0; i < len - i; ++i) { - char a = ext[i]; - char b = ext[len - i - 1]; - - // What's internationalization? Doesn't matter, this is what - // GNU ls does. Luckily, since there's no standard C way to - // casefold. Not using tolower() here since it respects the - // current locale, which GNU ls doesn't do. - if (a >= 'A' && a <= 'Z') { - a += 'a' - 'A'; - } - if (b >= 'A' && b <= 'Z') { - b += 'a' - 'A'; - } - - ext[i] = b; - ext[len - i - 1] = a; - } -} - -/** - * Set the color for an extension. - */ -static int set_ext_color(struct colors *colors, char *key, const char *value) { - extxfrm(key); - - // A later *.x should override any earlier *.x, *.y.x, etc. - struct trie_leaf *match; - while ((match = trie_find_postfix(&colors->ext_colors, key))) { - dstrfree(match->value); - trie_remove(&colors->ext_colors, match); - } - - struct trie_leaf *leaf = trie_insert_str(&colors->ext_colors, key); - if (leaf) { - leaf->value = (char *)value; - return 0; - } else { - return -1; - } -} - -/** - * Find a color by an extension. - */ -static const char *get_ext_color(const struct colors *colors, const char *filename) { - char *xfrm = strdup(filename); - if (!xfrm) { - return NULL; - } - extxfrm(xfrm); - - const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_colors, xfrm); - free(xfrm); - if (leaf) { - return leaf->value; - } else { - return NULL; - } -} - -/** - * Parse a chunk of LS_COLORS that may have escape sequences. The supported - * escapes are: - * - * \a, \b, \f, \n, \r, \t, \v: - * As in C - * \e: - * ESC (\033) - * \?: - * DEL (\177) - * \_: - * ' ' (space) - * \NNN: - * Octal - * \xNN: - * Hex - * ^C: - * Control character. - * - * See man dir_colors. - * - * @param value - * The value to parse. - * @param end - * The character that marks the end of the chunk. - * @param[out] next - * Will be set to the next chunk. - * @return - * The parsed chunk as a dstring. - */ -static char *unescape(const char *value, char end, const char **next) { - if (!value) { - goto fail; - } - - char *str = dstralloc(0); - if (!str) { - goto fail_str; - } - - const char *i; - for (i = value; *i && *i != end; ++i) { - unsigned char c = 0; - - switch (*i) { - case '\\': - switch (*++i) { - case 'a': - c = '\a'; - break; - case 'b': - c = '\b'; - break; - case 'e': - c = '\033'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - case '?': - c = '\177'; - break; - case '_': - c = ' '; - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - while (i[1] >= '0' && i[1] <= '7') { - c <<= 3; - c |= *i++ - '0'; - } - c <<= 3; - c |= *i - '0'; - break; - - case 'X': - case 'x': - while (true) { - if (i[1] >= '0' && i[1] <= '9') { - c <<= 4; - c |= i[1] - '0'; - } else if (i[1] >= 'A' && i[1] <= 'F') { - c <<= 4; - c |= i[1] - 'A' + 0xA; - } else if (i[1] >= 'a' && i[1] <= 'f') { - c <<= 4; - c |= i[1] - 'a' + 0xA; - } else { - break; - } - ++i; - } - break; - - case '\0': - goto fail_str; - - default: - c = *i; - break; - } - break; - - case '^': - switch (*++i) { - case '?': - c = '\177'; - break; - case '\0': - goto fail_str; - default: - // CTRL masks bits 6 and 7 - c = *i & 0x1F; - break; - } - break; - - default: - c = *i; - break; - } - - if (dstrapp(&str, c) != 0) { - goto fail_str; - } - } - - if (*i) { - *next = i + 1; - } else { - *next = NULL; - } - - return str; - -fail_str: - dstrfree(str); -fail: - *next = NULL; - return NULL; -} - -struct colors *parse_colors(const char *ls_colors) { - struct colors *colors = malloc(sizeof(struct colors)); - if (!colors) { - return NULL; - } - - trie_init(&colors->names); - trie_init(&colors->ext_colors); - - int ret = 0; - - // From man console_codes - - ret |= init_color(colors, "rs", "0", &colors->reset); - ret |= init_color(colors, "lc", "\033[", &colors->leftcode); - ret |= init_color(colors, "rc", "m", &colors->rightcode); - ret |= init_color(colors, "ec", NULL, &colors->endcode); - ret |= init_color(colors, "cl", "\033[K", &colors->clear_to_eol); - - ret |= init_color(colors, "bld", "01", &colors->bold); - ret |= init_color(colors, "gry", "01;30", &colors->gray); - ret |= init_color(colors, "red", "01;31", &colors->red); - ret |= init_color(colors, "grn", "01;32", &colors->green); - ret |= init_color(colors, "ylw", "01;33", &colors->yellow); - ret |= init_color(colors, "blu", "01;34", &colors->blue); - ret |= init_color(colors, "mag", "01;35", &colors->magenta); - ret |= init_color(colors, "cyn", "01;36", &colors->cyan); - ret |= init_color(colors, "wht", "01;37", &colors->white); - - ret |= init_color(colors, "wr", "01;33", &colors->warning); - ret |= init_color(colors, "er", "01;31", &colors->error); - - // Defaults from man dir_colors - - ret |= init_color(colors, "no", NULL, &colors->normal); - - ret |= init_color(colors, "fi", NULL, &colors->file); - ret |= init_color(colors, "mh", NULL, &colors->multi_hard); - ret |= init_color(colors, "ex", "01;32", &colors->executable); - ret |= init_color(colors, "ca", "30;41", &colors->capable); - ret |= init_color(colors, "sg", "30;43", &colors->setgid); - ret |= init_color(colors, "su", "37;41", &colors->setuid); - - ret |= init_color(colors, "di", "01;34", &colors->directory); - ret |= init_color(colors, "st", "37;44", &colors->sticky); - ret |= init_color(colors, "ow", "34;42", &colors->other_writable); - ret |= init_color(colors, "tw", "30;42", &colors->sticky_other_writable); - - 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); - ret |= init_color(colors, "do", "01;35", &colors->door); - ret |= init_color(colors, "pi", "33", &colors->pipe); - ret |= init_color(colors, "so", "01;35", &colors->socket); - - if (ret) { - free_colors(colors); - return NULL; - } - - for (const char *chunk = ls_colors, *next; chunk; chunk = next) { - if (chunk[0] == '*') { - char *key = unescape(chunk + 1, '=', &next); - if (!key) { - continue; - } - - char *value = unescape(next, ':', &next); - if (value) { - if (set_ext_color(colors, key, value) != 0) { - dstrfree(value); - } - } - - dstrfree(key); - } else { - const char *equals = strchr(chunk, '='); - if (!equals) { - break; - } - - char *value = unescape(equals + 1, ':', &next); - if (!value) { - continue; - } - - char *key = strndup(chunk, equals - chunk); - if (!key) { - dstrfree(value); - continue; - } - - // All-zero values should be treated like NULL, to fall - // back on any other relevant coloring for that file - if (strspn(value, "0") == strlen(value) - && strcmp(key, "rs") != 0 - && strcmp(key, "lc") != 0 - && strcmp(key, "rc") != 0 - && strcmp(key, "ec") != 0) { - dstrfree(value); - value = NULL; - } - - set_color(colors, key, value); - free(key); - } - } - - if (colors->link && strcmp(colors->link, "target") == 0) { - colors->link_as_target = true; - dstrfree(colors->link); - colors->link = NULL; - } - - return colors; -} - -void free_colors(struct colors *colors) { - if (colors) { - struct trie_leaf *leaf; - while ((leaf = trie_first_leaf(&colors->ext_colors))) { - dstrfree(leaf->value); - trie_remove(&colors->ext_colors, leaf); - } - trie_destroy(&colors->ext_colors); - - while ((leaf = trie_first_leaf(&colors->names))) { - char **field = leaf->value; - dstrfree(*field); - trie_remove(&colors->names, leaf); - } - trie_destroy(&colors->names); - - free(colors); - } -} - -CFILE *cfopen(const char *path, const struct colors *colors) { - CFILE *cfile = malloc(sizeof(*cfile)); - if (!cfile) { - return NULL; - } - - cfile->close = false; - cfile->file = fopen(path, "wb"); - if (!cfile->file) { - cfclose(cfile); - return NULL; - } - cfile->close = true; - - if (isatty(fileno(cfile->file))) { - cfile->colors = colors; - } else { - cfile->colors = NULL; - } - - return cfile; -} - -CFILE *cfdup(FILE *file, const struct colors *colors) { - CFILE *cfile = malloc(sizeof(*cfile)); - if (!cfile) { - return NULL; - } - - cfile->close = false; - cfile->file = file; - - if (isatty(fileno(file))) { - cfile->colors = colors; - } else { - cfile->colors = NULL; - } - - return cfile; -} - -int cfclose(CFILE *cfile) { - int ret = 0; - if (cfile) { - if (cfile->close) { - ret = fclose(cfile->file); - } - free(cfile); - } - return ret; -} - -/** Check if a symlink is broken. */ -static bool is_link_broken(const struct BFTW *ftwbuf) { - if (ftwbuf->stat_flags & BFS_STAT_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, enum bfs_stat_flag flags) { - enum bftw_typeflag typeflag = bftw_typeflag(ftwbuf, flags); - if (typeflag == BFTW_ERROR) { - goto error; - } - - const struct bfs_stat *statbuf = NULL; - const char *color = NULL; - - switch (typeflag) { - case BFTW_REG: - if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { - statbuf = bftw_stat(ftwbuf, flags); - if (!statbuf) { - goto error; - } - } - - if (colors->setuid && (statbuf->mode & 04000)) { - color = colors->setuid; - } else if (colors->setgid && (statbuf->mode & 02000)) { - color = colors->setgid; - } else if (colors->capable && bfs_check_capabilities(ftwbuf) > 0) { - color = colors->capable; - } else if (colors->executable && (statbuf->mode & 00111)) { - color = colors->executable; - } else if (colors->multi_hard && statbuf->nlink > 1) { - color = colors->multi_hard; - } - - if (!color) { - color = get_ext_color(colors, filename); - } - - if (!color) { - color = colors->file; - } - - break; - - case BFTW_DIR: - if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { - statbuf = bftw_stat(ftwbuf, flags); - if (!statbuf) { - goto error; - } - } - - if (colors->sticky_other_writable && (statbuf->mode & 01002) == 01002) { - color = colors->sticky_other_writable; - } else if (colors->other_writable && (statbuf->mode & 00002)) { - color = colors->other_writable; - } else if (colors->sticky && (statbuf->mode & 01000)) { - color = colors->sticky; - } else { - color = colors->directory; - } - - break; - - case BFTW_LNK: - if (colors->orphan && is_link_broken(ftwbuf)) { - color = colors->orphan; - } else { - color = colors->link; - } - break; - - case BFTW_BLK: - color = colors->blockdev; - break; - case BFTW_CHR: - color = colors->chardev; - break; - case BFTW_FIFO: - color = colors->pipe; - break; - case BFTW_SOCK: - color = colors->socket; - break; - case BFTW_DOOR: - color = colors->door; - break; - - default: - break; - } - - if (!color) { - color = colors->normal; - } - - return color; - -error: - if (colors->missing) { - return colors->missing; - } else { - return colors->orphan; - } -} - -/** Print a fixed-length string. */ -static int print_strn(const char *str, size_t len, FILE *file) { - if (fwrite(str, 1, len, file) == len) { - return 0; - } else { - return -1; - } -} - -/** Print a dstring. */ -static int print_dstr(const char *str, FILE *file) { - return print_strn(str, dstrlen(str), file); -} - -/** Print an ANSI escape sequence. */ -static int print_esc(const struct colors *colors, const char *esc, FILE *file) { - if (print_dstr(colors->leftcode, file) != 0) { - return -1; - } - if (print_dstr(esc, file) != 0) { - return -1; - } - if (print_dstr(colors->rightcode, file) != 0) { - return -1; - } - - return 0; -} - -/** Reset after an ANSI escape sequence. */ -static int print_reset(const struct colors *colors, FILE *file) { - if (colors->endcode) { - return print_dstr(colors->endcode, file); - } else { - return print_esc(colors, colors->reset, file); - } -} - -/** Print a string with an optional color. */ -static int print_colored(const struct colors *colors, const char *esc, const char *str, size_t len, FILE *file) { - if (esc) { - if (print_esc(colors, esc, file) != 0) { - return -1; - } - } - if (print_strn(str, len, file) != 0) { - return -1; - } - if (esc) { - if (print_reset(colors, file) != 0) { - return -1; - } - } - - return 0; -} - -/** Print a path with colors. */ -static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { - const struct colors *colors = cfile->colors; - FILE *file = cfile->file; - - size_t nameoff; - if (path == ftwbuf->path) { - nameoff = ftwbuf->nameoff; - } else { - nameoff = xbasename(path) - path; - } - - if (nameoff > 0) { - if (print_colored(colors, colors->directory, path, nameoff, file) != 0) { - return -1; - } - } - - const char *filename = path + nameoff; - const char *color = file_color(colors, filename, ftwbuf, flags); - return print_colored(colors, color, filename, strlen(filename), file); -} - -/** 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; - } - - enum bfs_stat_flag flags = ftwbuf->stat_flags; - if (colors && colors->link_as_target && ftwbuf->typeflag == BFTW_LNK) { - flags = BFS_STAT_TRYFOLLOW; - } - - return print_path_colored(cfile, ftwbuf->path, ftwbuf, flags); -} - -/** Print a link target with the appropriate colors. */ -static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { - int ret = -1; - - size_t len = 0; - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_NOFOLLOW); - if (statbuf) { - len = statbuf->size; - } - - char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len); - if (!target) { - goto done; - } - - if (!cfile->colors) { - ret = fputs(target, cfile->file) == EOF ? -1 : 0; - goto done; - } - - ret = print_path_colored(cfile, target, ftwbuf, BFS_STAT_FOLLOW); - -done: - free(target); - return ret; -} - -int cfprintf(CFILE *cfile, const char *format, ...) { - va_list args; - va_start(args, format); - int ret = cvfprintf(cfile, format, args); - va_end(args); - return ret; -} - -int cvfprintf(CFILE *cfile, const char *format, va_list args) { - const struct colors *colors = cfile->colors; - FILE *file = cfile->file; - int error = errno; - - for (const char *i = format; *i; ++i) { - size_t verbatim = strcspn(i, "%$"); - if (fwrite(i, 1, verbatim, file) != verbatim) { - return -1; - } - - i += verbatim; - switch (*i) { - case '%': - switch (*++i) { - case '%': - if (fputc('%', file) == EOF) { - return -1; - } - break; - - case 'c': - if (fputc(va_arg(args, int), file) == EOF) { - return -1; - } - break; - - case 'd': - if (fprintf(file, "%d", va_arg(args, int)) < 0) { - return -1; - } - break; - - case 'g': - if (fprintf(file, "%g", va_arg(args, double)) < 0) { - return -1; - } - break; - - case 's': - if (fputs(va_arg(args, const char *), file) == EOF) { - return -1; - } - break; - - case 'z': - ++i; - if (*i != 'u') { - goto invalid; - } - if (fprintf(file, "%zu", va_arg(args, size_t)) < 0) { - return -1; - } - break; - - case 'm': - if (fputs(strerror(error), file) == EOF) { - return -1; - } - break; - - case 'p': - switch (*++i) { - case 'P': - if (print_path(cfile, va_arg(args, const struct BFTW *)) != 0) { - return -1; - } - break; - - case 'L': - if (print_link_target(cfile, va_arg(args, const struct BFTW *)) != 0) { - return -1; - } - break; - - default: - goto invalid; - } - - break; - - default: - goto invalid; - } - break; - - case '$': - switch (*++i) { - case '$': - if (fputc('$', file) == EOF) { - return -1; - } - break; - - case '{': { - ++i; - const char *end = strchr(i, '}'); - if (!end) { - goto invalid; - } - if (!colors) { - i = end; - break; - } - - size_t len = end - i; - char name[len + 1]; - memcpy(name, i, len); - name[len] = '\0'; - - char **esc = get_color(colors, name); - if (!esc) { - goto invalid; - } - if (*esc) { - if (print_esc(colors, *esc, file) != 0) { - return -1; - } - } - - i = end; - break; - } - - default: - goto invalid; - } - break; - - default: - return 0; - } - - } - - return 0; - -invalid: - assert(false); - errno = EINVAL; - return -1; -} |