build: ensure sys/select.h is included
[coreutils.git] / src / dircolors.c
blobc325c6cbc12bf56963542d89f2b7b094d74e1701
1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2019 Free Software Foundation, Inc.
3 Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000 H. Peter Anvin
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
20 #include <sys/types.h>
21 #include <fnmatch.h>
22 #include <getopt.h>
24 #include "system.h"
25 #include "dircolors.h"
26 #include "c-strcase.h"
27 #include "die.h"
28 #include "error.h"
29 #include "obstack.h"
30 #include "quote.h"
31 #include "stdio--.h"
32 #include "xstrndup.h"
34 /* The official name of this program (e.g., no 'g' prefix). */
35 #define PROGRAM_NAME "dircolors"
37 #define AUTHORS proper_name ("H. Peter Anvin")
39 #define obstack_chunk_alloc malloc
40 #define obstack_chunk_free free
42 enum Shell_syntax
44 SHELL_SYNTAX_BOURNE,
45 SHELL_SYNTAX_C,
46 SHELL_SYNTAX_UNKNOWN
49 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
50 #define APPEND_TWO_CHAR_STRING(S) \
51 do \
52 { \
53 APPEND_CHAR (S[0]); \
54 APPEND_CHAR (S[1]); \
55 } \
56 while (0)
58 /* Accumulate in this obstack the value for the LS_COLORS environment
59 variable. */
60 static struct obstack lsc_obstack;
62 static const char *const slack_codes[] =
64 "NORMAL", "NORM", "FILE", "RESET", "DIR", "LNK", "LINK",
65 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
66 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
67 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
68 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
69 "MULTIHARDLINK", "CLRTOEOL", NULL
72 static const char *const ls_codes[] =
74 "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
75 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
76 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl", NULL
78 verify (ARRAY_CARDINALITY (slack_codes) == ARRAY_CARDINALITY (ls_codes));
80 static struct option const long_options[] =
82 {"bourne-shell", no_argument, NULL, 'b'},
83 {"sh", no_argument, NULL, 'b'},
84 {"csh", no_argument, NULL, 'c'},
85 {"c-shell", no_argument, NULL, 'c'},
86 {"print-database", no_argument, NULL, 'p'},
87 {GETOPT_HELP_OPTION_DECL},
88 {GETOPT_VERSION_OPTION_DECL},
89 {NULL, 0, NULL, 0}
92 void
93 usage (int status)
95 if (status != EXIT_SUCCESS)
96 emit_try_help ();
97 else
99 printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
100 fputs (_("\
101 Output commands to set the LS_COLORS environment variable.\n\
103 Determine format of output:\n\
104 -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
105 -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
106 -p, --print-database output defaults\n\
107 "), stdout);
108 fputs (HELP_OPTION_DESCRIPTION, stdout);
109 fputs (VERSION_OPTION_DESCRIPTION, stdout);
110 fputs (_("\
112 If FILE is specified, read it to determine which colors to use for which\n\
113 file types and extensions. Otherwise, a precompiled database is used.\n\
114 For details on the format of these files, run 'dircolors --print-database'.\n\
115 "), stdout);
116 emit_ancillary_info (PROGRAM_NAME);
119 exit (status);
122 /* If the SHELL environment variable is set to 'csh' or 'tcsh,'
123 assume C shell. Else Bourne shell. */
125 static enum Shell_syntax
126 guess_shell_syntax (void)
128 char *shell;
130 shell = getenv ("SHELL");
131 if (shell == NULL || *shell == '\0')
132 return SHELL_SYNTAX_UNKNOWN;
134 shell = last_component (shell);
136 if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
137 return SHELL_SYNTAX_C;
139 return SHELL_SYNTAX_BOURNE;
142 static void
143 parse_line (char const *line, char **keyword, char **arg)
145 char const *p;
146 char const *keyword_start;
147 char const *arg_start;
149 *keyword = NULL;
150 *arg = NULL;
152 for (p = line; isspace (to_uchar (*p)); ++p)
153 continue;
155 /* Ignore blank lines and shell-style comments. */
156 if (*p == '\0' || *p == '#')
157 return;
159 keyword_start = p;
161 while (!isspace (to_uchar (*p)) && *p != '\0')
163 ++p;
166 *keyword = xstrndup (keyword_start, p - keyword_start);
167 if (*p == '\0')
168 return;
172 ++p;
174 while (isspace (to_uchar (*p)));
176 if (*p == '\0' || *p == '#')
177 return;
179 arg_start = p;
181 while (*p != '\0' && *p != '#')
182 ++p;
184 for (--p; isspace (to_uchar (*p)); --p)
185 continue;
186 ++p;
188 *arg = xstrndup (arg_start, p - arg_start);
191 /* FIXME: Write a string to standard out, while watching for "dangerous"
192 sequences like unescaped : and = characters. */
194 static void
195 append_quoted (const char *str)
197 bool need_backslash = true;
199 while (*str != '\0')
201 switch (*str)
203 case '\'':
204 APPEND_CHAR ('\'');
205 APPEND_CHAR ('\\');
206 APPEND_CHAR ('\'');
207 need_backslash = true;
208 break;
210 case '\\':
211 case '^':
212 need_backslash = !need_backslash;
213 break;
215 case ':':
216 case '=':
217 if (need_backslash)
218 APPEND_CHAR ('\\');
219 FALLTHROUGH;
221 default:
222 need_backslash = true;
223 break;
226 APPEND_CHAR (*str);
227 ++str;
231 /* Read the file open on FP (with name FILENAME). First, look for a
232 'TERM name' directive where name matches the current terminal type.
233 Once found, translate and accumulate the associated directives onto
234 the global obstack LSC_OBSTACK. Give a diagnostic
235 upon failure (unrecognized keyword is the only way to fail here).
236 Return true if successful. */
238 static bool
239 dc_parse_stream (FILE *fp, const char *filename)
241 size_t line_number = 0;
242 char const *next_G_line = G_line;
243 char *input_line = NULL;
244 size_t input_line_size = 0;
245 char const *line;
246 char const *term;
247 bool ok = true;
249 /* State for the parser. */
250 enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
252 /* Get terminal type */
253 term = getenv ("TERM");
254 if (term == NULL || *term == '\0')
255 term = "none";
257 while (1)
259 char *keywd, *arg;
260 bool unrecognized;
262 ++line_number;
264 if (fp)
266 if (getline (&input_line, &input_line_size, fp) <= 0)
268 free (input_line);
269 break;
271 line = input_line;
273 else
275 if (next_G_line == G_line + sizeof G_line)
276 break;
277 line = next_G_line;
278 next_G_line += strlen (next_G_line) + 1;
281 parse_line (line, &keywd, &arg);
283 if (keywd == NULL)
284 continue;
286 if (arg == NULL)
288 error (0, 0, _("%s:%lu: invalid line; missing second token"),
289 quotef (filename), (unsigned long int) line_number);
290 ok = false;
291 free (keywd);
292 continue;
295 unrecognized = false;
296 if (c_strcasecmp (keywd, "TERM") == 0)
298 if (fnmatch (arg, term, 0) == 0)
299 state = ST_TERMSURE;
300 else if (state != ST_TERMSURE)
301 state = ST_TERMNO;
303 else
305 if (state == ST_TERMSURE)
306 state = ST_TERMYES; /* Another TERM can cancel */
308 if (state != ST_TERMNO)
310 if (keywd[0] == '.')
312 APPEND_CHAR ('*');
313 append_quoted (keywd);
314 APPEND_CHAR ('=');
315 append_quoted (arg);
316 APPEND_CHAR (':');
318 else if (keywd[0] == '*')
320 append_quoted (keywd);
321 APPEND_CHAR ('=');
322 append_quoted (arg);
323 APPEND_CHAR (':');
325 else if (c_strcasecmp (keywd, "OPTIONS") == 0
326 || c_strcasecmp (keywd, "COLOR") == 0
327 || c_strcasecmp (keywd, "EIGHTBIT") == 0)
329 /* Ignore. */
331 else
333 int i;
335 for (i = 0; slack_codes[i] != NULL; ++i)
336 if (c_strcasecmp (keywd, slack_codes[i]) == 0)
337 break;
339 if (slack_codes[i] != NULL)
341 APPEND_TWO_CHAR_STRING (ls_codes[i]);
342 APPEND_CHAR ('=');
343 append_quoted (arg);
344 APPEND_CHAR (':');
346 else
348 unrecognized = true;
352 else
354 unrecognized = true;
358 if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
360 error (0, 0, _("%s:%lu: unrecognized keyword %s"),
361 (filename ? quotef (filename) : _("<internal>")),
362 (unsigned long int) line_number, keywd);
363 ok = false;
366 free (keywd);
367 free (arg);
370 return ok;
373 static bool
374 dc_parse_file (const char *filename)
376 bool ok;
378 if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL)
380 error (0, errno, "%s", quotef (filename));
381 return false;
384 ok = dc_parse_stream (stdin, filename);
386 if (fclose (stdin) != 0)
388 error (0, errno, "%s", quotef (filename));
389 return false;
392 return ok;
396 main (int argc, char **argv)
398 bool ok = true;
399 int optc;
400 enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
401 bool print_database = false;
403 initialize_main (&argc, &argv);
404 set_program_name (argv[0]);
405 setlocale (LC_ALL, "");
406 bindtextdomain (PACKAGE, LOCALEDIR);
407 textdomain (PACKAGE);
409 atexit (close_stdout);
411 while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
412 switch (optc)
414 case 'b': /* Bourne shell syntax. */
415 syntax = SHELL_SYNTAX_BOURNE;
416 break;
418 case 'c': /* C shell syntax. */
419 syntax = SHELL_SYNTAX_C;
420 break;
422 case 'p':
423 print_database = true;
424 break;
426 case_GETOPT_HELP_CHAR;
428 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
430 default:
431 usage (EXIT_FAILURE);
434 argc -= optind;
435 argv += optind;
437 /* It doesn't make sense to use --print with either of
438 --bourne or --c-shell. */
439 if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
441 error (0, 0,
442 _("the options to output dircolors' internal database and\n"
443 "to select a shell syntax are mutually exclusive"));
444 usage (EXIT_FAILURE);
447 if ((!print_database) < argc)
449 error (0, 0, _("extra operand %s"), quote (argv[!print_database]));
450 if (print_database)
451 fprintf (stderr, "%s\n",
452 _("file operands cannot be combined with "
453 "--print-database (-p)"));
454 usage (EXIT_FAILURE);
457 if (print_database)
459 char const *p = G_line;
460 while (p - G_line < sizeof G_line)
462 puts (p);
463 p += strlen (p) + 1;
466 else
468 /* If shell syntax was not explicitly specified, try to guess it. */
469 if (syntax == SHELL_SYNTAX_UNKNOWN)
471 syntax = guess_shell_syntax ();
472 if (syntax == SHELL_SYNTAX_UNKNOWN)
474 die (EXIT_FAILURE, 0,
475 _("no SHELL environment variable, and no shell type option given"));
479 obstack_init (&lsc_obstack);
480 if (argc == 0)
481 ok = dc_parse_stream (NULL, NULL);
482 else
483 ok = dc_parse_file (argv[0]);
485 if (ok)
487 size_t len = obstack_object_size (&lsc_obstack);
488 char *s = obstack_finish (&lsc_obstack);
489 const char *prefix;
490 const char *suffix;
492 if (syntax == SHELL_SYNTAX_BOURNE)
494 prefix = "LS_COLORS='";
495 suffix = "';\nexport LS_COLORS\n";
497 else
499 prefix = "setenv LS_COLORS '";
500 suffix = "'\n";
502 fputs (prefix, stdout);
503 fwrite (s, 1, len, stdout);
504 fputs (suffix, stdout);
508 return ok ? EXIT_SUCCESS : EXIT_FAILURE;