]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/colors: add support for @term and /home/kzak
authorKarel Zak <kzak@redhat.com>
Wed, 30 Apr 2014 10:59:28 +0000 (12:59 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 13 May 2014 10:09:02 +0000 (12:09 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
include/pathnames.h
lib/colors.c

index 94cc412a7fc4d028222eaf8f9d1eefd1a223d3bd..b6efdb7dc6e5c61454e11e88bad1f31cf5fe658d 100644 (file)
@@ -50,8 +50,8 @@
 #define _PATH_SECURE           "/etc/securesingle"
 #define _PATH_USERTTY           "/etc/usertty"
 
-#define _PATH_TERMCOLORS_DIR   "/etc/terminal-colors.d/"
-#define _PATH_TERMCOLORS_DISABLE _PATH_TERMCOLORS_DIR "disable"
+#define _PATH_TERMCOLORS_DIRNAME "terminal-colors.d"
+#define _PATH_TERMCOLORS_DIR   "/etc/" _PATH_TERMCOLORS_DIRNAME
 
 /* used in login-utils/shutdown.c */
 
index 195027376fd0dd98c5ef2e118562cbe31f863993..4ad2bca53637ff0a410eab2fffbca4bfccd55f48 100644 (file)
  */
 #include <assert.h>
 #include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
 
 #include "c.h"
 #include "colors.h"
 #include "xalloc.h"
 #include "pathnames.h"
 
-static int ul_color_term_ok;
+enum {
+       UL_COLORFILE_DISABLE,           /* .disable */
+       UL_COLORFILE_ENABLE,            /* .enable */
+       UL_COLORFILE_SCHEME,            /* .scheme */
+
+       __UL_COLORFILE_COUNT
+};
+
+struct ul_color_ctl {
+       const char      *utilname;      /* util name */
+       const char      *termname;      /* terminal name ($TERM) */
+
+       char            *scheme;        /* path to scheme */
+
+       int             mode;           /* UL_COLORMODE_* */
+       int             use_colors;     /* based on mode and scores[] */
+       int             scores[__UL_COLORFILE_COUNT];
+};
+
+static struct ul_color_ctl ul_colors;
+
+
+static void colors_reset(struct ul_color_ctl *cc)
+{
+       if (!cc)
+               return;
+
+       free(cc->scheme);
+
+       cc->scheme = NULL;
+       cc->utilname = NULL;
+       cc->termname = NULL;
+       cc->mode = UL_COLORMODE_UNDEF;
+
+       memset(cc->scores, 0, sizeof(cc->scores));
+}
+
+#ifdef TEST_PROGRAM
+static void colors_debug(struct ul_color_ctl *cc)
+{
+       int i;
+
+       if (!cc)
+               return;
+
+       printf("Colors:\n");
+       printf("\tutilname = '%s'\n", cc->utilname);
+       printf("\ttermname = '%s'\n", cc->termname);
+       printf("\tscheme = '%s'\n", cc->scheme);
+       printf("\tmode = %s\n",
+                       cc->mode == UL_COLORMODE_UNDEF ? "undefined" :
+                       cc->mode == UL_COLORMODE_AUTO ?  "auto" :
+                       cc->mode == UL_COLORMODE_NEVER ? "never" :
+                       cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???");
+
+       for (i = 0; i < ARRAY_SIZE(cc->scores); i++)
+               printf("\tscore %s = %d\n",
+                               i == UL_COLORFILE_DISABLE ? "disable" :
+                               i == UL_COLORFILE_ENABLE ? "enable" :
+                               i == UL_COLORFILE_SCHEME ? "scheme" : "???",
+                               cc->scores[i]);
+}
+
+#endif
+
+static int filename_to_tokens(const char *str,
+                             const char **name, size_t *namesz,
+                             const char **term, size_t *termsz,
+                             int  *filetype)
+{
+       const char *type_start, *term_start, *p;
+
+       if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX)
+               return -EINVAL;
+
+       /* parse .type */
+       p = strrchr(str, '.');
+       type_start = p ? p + 1 : str;
+
+       if (strcmp(type_start, "disable") == 0)
+               *filetype = UL_COLORFILE_DISABLE;
+       else if (strcmp(type_start, "enable") == 0)
+               *filetype = UL_COLORFILE_ENABLE;
+       else if (strcmp(type_start, "scheme") == 0)
+               *filetype = UL_COLORFILE_SCHEME;
+       else
+               return 1;                               /* unknown type */
+
+       if (type_start == str)
+               return 0;                               /* "type" only */
+
+       /* parse @termname */
+       p = strchr(str, '@');
+       term_start = p ? p + 1 : NULL;
+       if (term_start) {
+               *term = term_start;
+               *termsz = type_start - term_start - 1;
+               if (term_start - 1 == str)
+                       return 0;                       /* "@termname.type" */
+       }
+
+       /* parse utilname */
+       p = term_start ? term_start : type_start;
+       *name =  str;
+       *namesz = p - str - 1;
+
+       return 0;
+}
+
+
+/*
+ * Checks for:
+ *
+ *  filename                    score
+ *  ---------------------------------
+ *  type                       1
+ *  @termname.type             10 + 1
+ *  utilname.type              20 + 1
+ *  utilname@termname.type     20 + 10 + 1
+ */
+static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
+{
+       DIR *dir;
+       int rc = 0;
+       struct dirent *d;
+       char scheme[PATH_MAX] = { '\0' };
+       size_t namesz, termsz;
+
+       if (!dirname || !cc || !cc->utilname || !*cc->utilname)
+               return -EINVAL;
+       dir = opendir(dirname);
+       if (!dir)
+               return -errno;
+
+       namesz = strlen(cc->utilname);
+       termsz = cc->termname ? strlen(cc->termname) : 0;
+
+       while ((d = readdir(dir))) {
+               int type, score = 1;
+               const char *tk_name = NULL, *tk_term = NULL;
+               size_t tk_namesz = 0, tk_termsz = 0;
+
+               if (*d->d_name == '.')
+                       continue;
+#ifdef _DIRENT_HAVE_D_TYPE
+               if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK &&
+                   d->d_type != DT_REG)
+                       continue;
+#endif
+               if (filename_to_tokens(d->d_name,
+                                      &tk_name, &tk_namesz,
+                                      &tk_term, &tk_termsz, &type) != 0)
+                       continue;
+
+               /* count teoretical score before we check names to avoid
+                * unnecessary strcmp() */
+               if (tk_name)
+                       score += 20;
+               if (tk_term)
+                       score += 10;
+               /*
+               fprintf(stderr, "%20s score: %2d [cur max: %2d]\n",
+                               d->d_name, score, cc->scores[type]);
+               */
+               if (score < cc->scores[type])
+                       continue;
+
+               /* filter out by names */
+               if (tk_namesz != namesz
+                   || strncmp(tk_name, cc->utilname, namesz) != 0)
+                       continue;
+               if (tk_termsz != termsz
+                   || termsz == 0
+                   || strncmp(tk_term, cc->termname, termsz) != 0)
+                       continue;
+
+               cc->scores[type] = score;
+               if (type == UL_COLORFILE_SCHEME)
+                       strncpy(scheme, d->d_name, sizeof(scheme));
+       }
+
+       if (*scheme) {
+               scheme[sizeof(scheme) - 1] = '\0';
+               if (asprintf(&cc->scheme, "%s/%s", dirname, scheme) <= 0)
+                       rc = -ENOMEM;
+       }
+
+       closedir(dir);
+       return rc;
+}
+
+static void colors_deinit(void)
+{
+       colors_reset(&ul_colors);
+}
+
+static char *colors_get_homedir(char *buf, size_t bufsz)
+{
+       char *p = getenv("XDG_CONFIG_HOME");
+
+       if (p) {
+               snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p);
+               return buf;
+       }
+
+       p = getenv("HOME");
+       if (p) {
+               snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p);
+               return buf;
+       }
+
+       return NULL;
+}
 
 int colors_init(int mode, const char *name)
 {
        int atty = -1;
+       struct ul_color_ctl *cc = &ul_colors;
+
+       cc->utilname = name;
+       cc->mode = mode;
 
        if (mode == UL_COLORMODE_UNDEF && (atty = isatty(STDOUT_FILENO))) {
-               char path[PATH_MAX];
+               int rc = -ENOENT;
+               char *dirname, buf[PATH_MAX];
 
-               snprintf(path, sizeof(path), "%s%s%s",
-                               _PATH_TERMCOLORS_DIR, name, ".enable");
+               cc->termname = getenv("TERM");
 
-               if (access(path, F_OK) == 0)
-                       mode = UL_COLORMODE_AUTO;
-               else {
-                       snprintf(path, sizeof(path), "%s%s%s",
-                               _PATH_TERMCOLORS_DIR, name, ".disable");
+               dirname = colors_get_homedir(buf, sizeof(buf));
+               if (dirname)
+                       rc = colors_readdir(cc, dirname);               /* ~/.config */
+               if (rc == -EPERM || rc == -EACCES || rc == -ENOENT)
+                       rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR);  /* /etc */
 
-                       if (access(_PATH_TERMCOLORS_DISABLE, F_OK) == 0 ||
-                           access(path, F_OK) == 0)
-                               mode = UL_COLORMODE_NEVER;
+               if (rc)
+                       cc->mode = UL_COLORMODE_AUTO;
+               else {
+                       /* evaluate scores */
+                       if (cc->scores[UL_COLORFILE_DISABLE] >
+                           cc->scores[UL_COLORFILE_ENABLE])
+                               cc->mode = UL_COLORMODE_NEVER;
                        else
-                               mode = UL_COLORMODE_AUTO;
+                               cc->mode = UL_COLORMODE_AUTO;
+
+                       atexit(colors_deinit);
                }
        }
 
-       switch (mode) {
+       switch (cc->mode) {
        case UL_COLORMODE_AUTO:
-               ul_color_term_ok = atty == -1 ? isatty(STDOUT_FILENO) : atty;
+               cc->use_colors = atty == -1 ? isatty(STDOUT_FILENO) : atty;
                break;
        case UL_COLORMODE_ALWAYS:
-               ul_color_term_ok = 1;
+               cc->use_colors = 1;
                break;
        case UL_COLORMODE_NEVER:
        default:
-               ul_color_term_ok = 0;
+               cc->use_colors = 0;
        }
-       return ul_color_term_ok;
+       return cc->use_colors;
 }
 
 int colors_wanted(void)
 {
-       return ul_color_term_ok;
+       return ul_colors.use_colors;
 }
 
 void color_fenable(const char *color_scheme, FILE *f)
 {
-       if (ul_color_term_ok && color_scheme)
+       if (ul_colors.use_colors && color_scheme)
                fputs(color_scheme, f);
 }
 
 void color_fdisable(FILE *f)
 {
-       if (ul_color_term_ok)
+       if (ul_colors.use_colors)
                fputs(UL_COLOR_RESET, f);
 }
 
@@ -150,31 +374,44 @@ const char *colorscheme_from_string(const char *str)
 int main(int argc, char *argv[])
 {
        static const struct option longopts[] = {
-               { "colors", optional_argument, 0, 'c' },
-               { "scheme", required_argument, 0, 's' },
+               { "mode",     optional_argument, 0, 'm' },
+               { "color",    required_argument, 0, 'c' },
+               { "read-dir", required_argument, 0, 'd' },
                { NULL, 0, 0, 0 }
        };
-       int c, mode = UL_COLORMODE_NEVER;       /* default */
-       const char *scheme = "red";
+       int c, mode = UL_COLORMODE_UNDEF;       /* default */
+       const char *color = "red", *dir = NULL;
 
-       while ((c = getopt_long(argc, argv, "c::s:", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "m::c:d:", longopts, NULL)) != -1) {
                switch (c) {
-               case 'c':
-                       mode = UL_COLORMODE_AUTO;
+               case 'm':
                        if (optarg)
                                mode = colormode_or_err(optarg, "unsupported color mode");
                        break;
-               case 's':
-                       scheme = optarg;
+               case 'c':
+                       color = optarg;
+                       break;
+               case 'd':
+                       dir = optarg;
                        break;
                }
        }
 
+       if (dir) {
+               struct ul_color_ctl cc = { .utilname = "foo",
+                                          .termname = "footerm" };
+
+               colors_readdir(&cc, dir);
+               colors_debug(&cc);
+               colors_reset(&cc);
+       }
+
        colors_init(mode, program_invocation_short_name);
-       color_enable(colorscheme_from_string(scheme));
 
+       color_enable(colorscheme_from_string(color));
        printf("Hello World!");
        color_disable();
+
        return EXIT_SUCCESS;
 }
 #endif