2018-10-07 20:52:13 +02:00
|
|
|
/* `dir', `vdir' and `ls' directory listing programs for GNU.
|
|
|
|
|
|
|
|
Modified by Chet Ramey for Readline.
|
|
|
|
|
2019-08-12 18:24:03 +02:00
|
|
|
Copyright (C) 1985, 1988, 1990-1991, 1995-2010, 2012, 2017
|
|
|
|
Free Software Foundation, Inc.
|
2018-10-07 20:52:13 +02:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
/* Written by Richard Stallman and David MacKenzie. */
|
|
|
|
|
|
|
|
/* Color support by Peter Anvin <Peter.Anvin@linux.org> and Dennis
|
|
|
|
Flaherty <dennisf@denix.elk.miles.com> based on original patches by
|
|
|
|
Greg Lee <lee@uhunix.uhcc.hawaii.edu>. */
|
|
|
|
|
|
|
|
#define READLINE_LIBRARY
|
|
|
|
|
|
|
|
#if defined (HAVE_CONFIG_H)
|
|
|
|
# include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
// strdup() / strcpy()
|
|
|
|
#if defined (HAVE_STRING_H)
|
|
|
|
# include <string.h>
|
|
|
|
#else /* !HAVE_STRING_H */
|
|
|
|
# include <strings.h>
|
|
|
|
#endif /* !HAVE_STRING_H */
|
|
|
|
|
|
|
|
// abort()
|
|
|
|
#if defined (HAVE_STDLIB_H)
|
|
|
|
# include <stdlib.h>
|
|
|
|
#else
|
|
|
|
# include "ansi_stdlib.h"
|
|
|
|
#endif /* HAVE_STDLIB_H */
|
|
|
|
|
|
|
|
#include "rldefs.h" // STREQ, savestring
|
|
|
|
#include "readline.h"
|
|
|
|
#include "rlprivate.h"
|
|
|
|
#include "rlshell.h"
|
|
|
|
#include "xmalloc.h"
|
|
|
|
|
|
|
|
#include "colors.h"
|
|
|
|
#include "parse-colors.h"
|
|
|
|
|
|
|
|
#if defined (COLOR_SUPPORT)
|
|
|
|
|
|
|
|
static bool get_funky_string (char **dest, const char **src, bool equals_end, size_t *output_count);
|
|
|
|
|
|
|
|
struct bin_str _rl_color_indicator[] =
|
|
|
|
{
|
|
|
|
{ LEN_STR_PAIR ("\033[") }, // lc: Left of color sequence
|
|
|
|
{ LEN_STR_PAIR ("m") }, // rc: Right of color sequence
|
|
|
|
{ 0, NULL }, // ec: End color (replaces lc+no+rc)
|
|
|
|
{ LEN_STR_PAIR ("0") }, // rs: Reset to ordinary colors
|
|
|
|
{ 0, NULL }, // no: Normal
|
|
|
|
{ 0, NULL }, // fi: File: default
|
|
|
|
{ LEN_STR_PAIR ("01;34") }, // di: Directory: bright blue
|
|
|
|
{ LEN_STR_PAIR ("01;36") }, // ln: Symlink: bright cyan
|
|
|
|
{ LEN_STR_PAIR ("33") }, // pi: Pipe: yellow/brown
|
|
|
|
{ LEN_STR_PAIR ("01;35") }, // so: Socket: bright magenta
|
|
|
|
{ LEN_STR_PAIR ("01;33") }, // bd: Block device: bright yellow
|
|
|
|
{ LEN_STR_PAIR ("01;33") }, // cd: Char device: bright yellow
|
|
|
|
{ 0, NULL }, // mi: Missing file: undefined
|
|
|
|
{ 0, NULL }, // or: Orphaned symlink: undefined
|
|
|
|
{ LEN_STR_PAIR ("01;32") }, // ex: Executable: bright green
|
|
|
|
{ LEN_STR_PAIR ("01;35") }, // do: Door: bright magenta
|
|
|
|
{ LEN_STR_PAIR ("37;41") }, // su: setuid: white on red
|
|
|
|
{ LEN_STR_PAIR ("30;43") }, // sg: setgid: black on yellow
|
|
|
|
{ LEN_STR_PAIR ("37;44") }, // st: sticky: black on blue
|
|
|
|
{ LEN_STR_PAIR ("34;42") }, // ow: other-writable: blue on green
|
|
|
|
{ LEN_STR_PAIR ("30;42") }, // tw: ow w/ sticky: black on green
|
|
|
|
{ LEN_STR_PAIR ("30;41") }, // ca: black on red
|
|
|
|
{ 0, NULL }, // mh: disabled by default
|
|
|
|
{ LEN_STR_PAIR ("\033[K") }, // cl: clear to end of line
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Parse a string as part of the LS_COLORS variable; this may involve
|
|
|
|
decoding all kinds of escape characters. If equals_end is set an
|
|
|
|
unescaped equal sign ends the string, otherwise only a : or \0
|
|
|
|
does. Set *OUTPUT_COUNT to the number of bytes output. Return
|
|
|
|
true if successful.
|
|
|
|
|
|
|
|
The resulting string is *not* null-terminated, but may contain
|
|
|
|
embedded nulls.
|
|
|
|
|
|
|
|
Note that both dest and src are char **; on return they point to
|
|
|
|
the first free byte after the array and the character that ended
|
|
|
|
the input string, respectively. */
|
|
|
|
|
|
|
|
static bool
|
|
|
|
get_funky_string (char **dest, const char **src, bool equals_end, size_t *output_count) {
|
|
|
|
char num; /* For numerical codes */
|
|
|
|
size_t count; /* Something to count with */
|
|
|
|
enum {
|
|
|
|
ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR
|
|
|
|
} state;
|
|
|
|
const char *p;
|
|
|
|
char *q;
|
|
|
|
|
|
|
|
p = *src; /* We don't want to double-indirect */
|
|
|
|
q = *dest; /* the whole darn time. */
|
|
|
|
|
|
|
|
count = 0; /* No characters counted in yet. */
|
|
|
|
num = 0;
|
|
|
|
|
|
|
|
state = ST_GND; /* Start in ground state. */
|
|
|
|
while (state < ST_END)
|
|
|
|
{
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
case ST_GND: /* Ground state (no escapes) */
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case ':':
|
|
|
|
case '\0':
|
|
|
|
state = ST_END; /* End of string */
|
|
|
|
break;
|
|
|
|
case '\\':
|
|
|
|
state = ST_BACKSLASH; /* Backslash scape sequence */
|
|
|
|
++p;
|
|
|
|
break;
|
|
|
|
case '^':
|
|
|
|
state = ST_CARET; /* Caret escape */
|
|
|
|
++p;
|
|
|
|
break;
|
|
|
|
case '=':
|
|
|
|
if (equals_end)
|
|
|
|
{
|
|
|
|
state = ST_END; /* End */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* else fall through */
|
|
|
|
default:
|
|
|
|
*(q++) = *(p++);
|
|
|
|
++count;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ST_BACKSLASH: /* Backslash escaped character */
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
state = ST_OCTAL; /* Octal sequence */
|
|
|
|
num = *p - '0';
|
|
|
|
break;
|
|
|
|
case 'x':
|
|
|
|
case 'X':
|
|
|
|
state = ST_HEX; /* Hex sequence */
|
|
|
|
num = 0;
|
|
|
|
break;
|
|
|
|
case 'a': /* Bell */
|
|
|
|
num = '\a';
|
|
|
|
break;
|
|
|
|
case 'b': /* Backspace */
|
|
|
|
num = '\b';
|
|
|
|
break;
|
|
|
|
case 'e': /* Escape */
|
|
|
|
num = 27;
|
|
|
|
break;
|
|
|
|
case 'f': /* Form feed */
|
|
|
|
num = '\f';
|
|
|
|
break;
|
|
|
|
case 'n': /* Newline */
|
|
|
|
num = '\n';
|
|
|
|
break;
|
|
|
|
case 'r': /* Carriage return */
|
|
|
|
num = '\r';
|
|
|
|
break;
|
|
|
|
case 't': /* Tab */
|
|
|
|
num = '\t';
|
|
|
|
break;
|
|
|
|
case 'v': /* Vtab */
|
|
|
|
num = '\v';
|
|
|
|
break;
|
|
|
|
case '?': /* Delete */
|
|
|
|
num = 127;
|
|
|
|
break;
|
|
|
|
case '_': /* Space */
|
|
|
|
num = ' ';
|
|
|
|
break;
|
|
|
|
case '\0': /* End of string */
|
|
|
|
state = ST_ERROR; /* Error! */
|
|
|
|
break;
|
|
|
|
default: /* Escaped character like \ ^ : = */
|
|
|
|
num = *p;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (state == ST_BACKSLASH)
|
|
|
|
{
|
|
|
|
*(q++) = num;
|
|
|
|
++count;
|
|
|
|
state = ST_GND;
|
|
|
|
}
|
|
|
|
++p;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ST_OCTAL: /* Octal sequence */
|
|
|
|
if (*p < '0' || *p > '7')
|
|
|
|
{
|
|
|
|
*(q++) = num;
|
|
|
|
++count;
|
|
|
|
state = ST_GND;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
num = (num << 3) + (*(p++) - '0');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ST_HEX: /* Hex sequence */
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
num = (num << 4) + (*(p++) - '0');
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
case 'b':
|
|
|
|
case 'c':
|
|
|
|
case 'd':
|
|
|
|
case 'e':
|
|
|
|
case 'f':
|
|
|
|
num = (num << 4) + (*(p++) - 'a') + 10;
|
|
|
|
break;
|
|
|
|
case 'A':
|
|
|
|
case 'B':
|
|
|
|
case 'C':
|
|
|
|
case 'D':
|
|
|
|
case 'E':
|
|
|
|
case 'F':
|
|
|
|
num = (num << 4) + (*(p++) - 'A') + 10;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
*(q++) = num;
|
|
|
|
++count;
|
|
|
|
state = ST_GND;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ST_CARET: /* Caret escape */
|
|
|
|
state = ST_GND; /* Should be the next state... */
|
|
|
|
if (*p >= '@' && *p <= '~')
|
|
|
|
{
|
|
|
|
*(q++) = *(p++) & 037;
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
else if (*p == '?')
|
|
|
|
{
|
|
|
|
*(q++) = 127;
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = ST_ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* should we ? */
|
|
|
|
/* abort (); no, we should not */
|
|
|
|
state = ST_ERROR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*dest = q;
|
|
|
|
*src = p;
|
|
|
|
*output_count = count;
|
|
|
|
|
|
|
|
return state != ST_ERROR;
|
|
|
|
}
|
|
|
|
#endif /* COLOR_SUPPORT */
|
|
|
|
|
2019-08-12 18:24:03 +02:00
|
|
|
void _rl_parse_colors(void)
|
2018-10-07 20:52:13 +02:00
|
|
|
{
|
|
|
|
#if defined (COLOR_SUPPORT)
|
|
|
|
const char *p; /* Pointer to character being parsed */
|
|
|
|
char *buf; /* color_buf buffer pointer */
|
|
|
|
int state; /* State of parser */
|
|
|
|
int ind_no; /* Indicator number */
|
|
|
|
char label[3]; /* Indicator label */
|
|
|
|
COLOR_EXT_TYPE *ext; /* Extension we are working on */
|
|
|
|
|
|
|
|
p = sh_get_env_value ("LS_COLORS");
|
|
|
|
if (p == 0 || *p == '\0')
|
|
|
|
{
|
|
|
|
_rl_color_ext_list = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ext = NULL;
|
|
|
|
strcpy (label, "??");
|
|
|
|
|
|
|
|
/* This is an overly conservative estimate, but any possible
|
|
|
|
LS_COLORS string will *not* generate a color_buf longer than
|
|
|
|
itself, so it is a safe way of allocating a buffer in
|
|
|
|
advance. */
|
|
|
|
buf = color_buf = savestring (p);
|
|
|
|
|
|
|
|
state = 1;
|
|
|
|
while (state > 0)
|
|
|
|
{
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
case 1: /* First label character */
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case ':':
|
|
|
|
++p;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '*':
|
|
|
|
/* Allocate new extension block and add to head of
|
|
|
|
linked list (this way a later definition will
|
|
|
|
override an earlier one, which can be useful for
|
|
|
|
having terminal-specific defs override global). */
|
|
|
|
|
|
|
|
ext = (COLOR_EXT_TYPE *)xmalloc (sizeof *ext);
|
|
|
|
ext->next = _rl_color_ext_list;
|
|
|
|
_rl_color_ext_list = ext;
|
|
|
|
|
|
|
|
++p;
|
|
|
|
ext->ext.string = buf;
|
|
|
|
|
|
|
|
state = (get_funky_string (&buf, &p, true, &ext->ext.len)
|
|
|
|
? 4 : -1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '\0':
|
|
|
|
state = 0; /* Done! */
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: /* Assume it is file type label */
|
|
|
|
label[0] = *(p++);
|
|
|
|
state = 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2: /* Second label character */
|
|
|
|
if (*p)
|
|
|
|
{
|
|
|
|
label[1] = *(p++);
|
|
|
|
state = 3;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = -1; /* Error */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: /* Equal sign after indicator label */
|
|
|
|
state = -1; /* Assume failure... */
|
|
|
|
if (*(p++) == '=')/* It *should* be... */
|
|
|
|
{
|
|
|
|
for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no)
|
|
|
|
{
|
|
|
|
if (STREQ (label, indicator_name[ind_no]))
|
|
|
|
{
|
|
|
|
_rl_color_indicator[ind_no].string = buf;
|
|
|
|
state = (get_funky_string (&buf, &p, false,
|
|
|
|
&_rl_color_indicator[ind_no].len)
|
|
|
|
? 1 : -1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state == -1)
|
|
|
|
{
|
|
|
|
_rl_errmsg ("LS_COLORS: unrecognized prefix: %s", label);
|
|
|
|
/* recover from an unrecognized prefix */
|
|
|
|
while (p && *p && *p != ':')
|
|
|
|
p++;
|
|
|
|
if (p && *p == ':')
|
|
|
|
state = 1;
|
|
|
|
else if (p && *p == 0)
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4: /* Equal sign after *.ext */
|
|
|
|
if (*(p++) == '=')
|
|
|
|
{
|
|
|
|
ext->seq.string = buf;
|
|
|
|
state = (get_funky_string (&buf, &p, false, &ext->seq.len)
|
|
|
|
? 1 : -1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = -1;
|
|
|
|
/* XXX - recover here as with an unrecognized prefix? */
|
|
|
|
if (state == -1 && ext->ext.string)
|
|
|
|
_rl_errmsg ("LS_COLORS: syntax error: %s", ext->ext.string);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state < 0)
|
|
|
|
{
|
|
|
|
COLOR_EXT_TYPE *e;
|
|
|
|
COLOR_EXT_TYPE *e2;
|
|
|
|
|
|
|
|
_rl_errmsg ("unparsable value for LS_COLORS environment variable");
|
|
|
|
free (color_buf);
|
|
|
|
for (e = _rl_color_ext_list; e != NULL; /* empty */)
|
|
|
|
{
|
|
|
|
e2 = e;
|
|
|
|
e = e->next;
|
|
|
|
free (e2);
|
|
|
|
}
|
|
|
|
_rl_color_ext_list = NULL;
|
|
|
|
_rl_colored_stats = 0; /* can't have colored stats without colors */
|
|
|
|
}
|
|
|
|
#else /* !COLOR_SUPPORT */
|
|
|
|
;
|
|
|
|
#endif /* !COLOR_SUPPORT */
|
|
|
|
}
|