summaryrefslogtreecommitdiffstats
path: root/color.c
diff options
context:
space:
mode:
Diffstat (limited to 'color.c')
-rw-r--r--color.c931
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;
-}