maint: prefer C23-style nullptr
[coreutils.git] / src / dircolors.c
blob1185b79a3817226d4c6fcb09840f34743c3650ce
1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2023 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"
33 /* The official name of this program (e.g., no 'g' prefix). */
34 #define PROGRAM_NAME "dircolors"
36 #define AUTHORS proper_name ("H. Peter Anvin")
38 #define obstack_chunk_alloc malloc
39 #define obstack_chunk_free free
41 enum Shell_syntax
43 SHELL_SYNTAX_BOURNE,
44 SHELL_SYNTAX_C,
45 SHELL_SYNTAX_UNKNOWN
48 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
50 /* Accumulate in this obstack the value for the LS_COLORS environment
51 variable. */
52 static struct obstack lsc_obstack;
54 static char const *const slack_codes[] =
56 "NORMAL", "NORM", "FILE", "RESET", "DIR", "LNK", "LINK",
57 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
58 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
59 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
60 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
61 "MULTIHARDLINK", "CLRTOEOL", nullptr
64 static char const *const ls_codes[] =
66 "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
67 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
68 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl",
69 nullptr
71 static_assert (ARRAY_CARDINALITY (slack_codes) == ARRAY_CARDINALITY (ls_codes));
73 /* Whether to output escaped ls color codes for display. */
74 static bool print_ls_colors;
76 /* For long options that have no equivalent short option, use a
77 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
78 enum
80 PRINT_LS_COLORS_OPTION = CHAR_MAX + 1,
83 static struct option const long_options[] =
85 {"bourne-shell", no_argument, nullptr, 'b'},
86 {"sh", no_argument, nullptr, 'b'},
87 {"csh", no_argument, nullptr, 'c'},
88 {"c-shell", no_argument, nullptr, 'c'},
89 {"print-database", no_argument, nullptr, 'p'},
90 {"print-ls-colors", no_argument, nullptr, PRINT_LS_COLORS_OPTION},
91 {GETOPT_HELP_OPTION_DECL},
92 {GETOPT_VERSION_OPTION_DECL},
93 {nullptr, 0, nullptr, 0}
96 void
97 usage (int status)
99 if (status != EXIT_SUCCESS)
100 emit_try_help ();
101 else
103 printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
104 fputs (_("\
105 Output commands to set the LS_COLORS environment variable.\n\
107 Determine format of output:\n\
108 -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
109 -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
110 -p, --print-database output defaults\n\
111 --print-ls-colors output fully escaped colors for display\n\
112 "), stdout);
113 fputs (HELP_OPTION_DESCRIPTION, stdout);
114 fputs (VERSION_OPTION_DESCRIPTION, stdout);
115 fputs (_("\
117 If FILE is specified, read it to determine which colors to use for which\n\
118 file types and extensions. Otherwise, a precompiled database is used.\n\
119 For details on the format of these files, run 'dircolors --print-database'.\n\
120 "), stdout);
121 emit_ancillary_info (PROGRAM_NAME);
124 exit (status);
127 /* If the SHELL environment variable is set to 'csh' or 'tcsh,'
128 assume C shell. Else Bourne shell. */
130 static enum Shell_syntax
131 guess_shell_syntax (void)
133 char *shell;
135 shell = getenv ("SHELL");
136 if (shell == nullptr || *shell == '\0')
137 return SHELL_SYNTAX_UNKNOWN;
139 shell = last_component (shell);
141 if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
142 return SHELL_SYNTAX_C;
144 return SHELL_SYNTAX_BOURNE;
147 static void
148 parse_line (char const *line, char **keyword, char **arg)
150 char const *p;
151 char const *keyword_start;
152 char const *arg_start;
154 *keyword = nullptr;
155 *arg = nullptr;
157 for (p = line; isspace (to_uchar (*p)); ++p)
158 continue;
160 /* Ignore blank lines and shell-style comments. */
161 if (*p == '\0' || *p == '#')
162 return;
164 keyword_start = p;
166 while (!isspace (to_uchar (*p)) && *p != '\0')
168 ++p;
171 *keyword = ximemdup0 (keyword_start, p - keyword_start);
172 if (*p == '\0')
173 return;
177 ++p;
179 while (isspace (to_uchar (*p)));
181 if (*p == '\0' || *p == '#')
182 return;
184 arg_start = p;
186 while (*p != '\0' && *p != '#')
187 ++p;
189 for (--p; isspace (to_uchar (*p)); --p)
190 continue;
191 ++p;
193 *arg = ximemdup0 (arg_start, p - arg_start);
196 /* Accumulate STR to LS_COLORS data.
197 If outputting shell syntax, then escape appropriately. */
199 static void
200 append_quoted (char const *str)
202 bool need_backslash = true;
204 while (*str != '\0')
206 if (! print_ls_colors)
207 switch (*str)
209 case '\'':
210 APPEND_CHAR ('\'');
211 APPEND_CHAR ('\\');
212 APPEND_CHAR ('\'');
213 need_backslash = true;
214 break;
216 case '\\':
217 case '^':
218 need_backslash = !need_backslash;
219 break;
221 case ':':
222 case '=':
223 if (need_backslash)
224 APPEND_CHAR ('\\');
225 FALLTHROUGH;
227 default:
228 need_backslash = true;
229 break;
232 APPEND_CHAR (*str);
233 ++str;
237 /* Accumulate entry to LS_COLORS data.
238 Use shell syntax unless PRINT_LS_COLORS is set. */
240 static void
241 append_entry (char prefix, char const *item, char const *arg)
243 if (print_ls_colors)
245 append_quoted ("\x1B[");
246 append_quoted (arg);
247 APPEND_CHAR ('m');
249 if (prefix)
250 APPEND_CHAR (prefix);
251 append_quoted (item);
252 APPEND_CHAR (print_ls_colors ? '\t' : '=');
253 append_quoted (arg);
254 if (print_ls_colors)
255 append_quoted ("\x1B[0m");
256 APPEND_CHAR (print_ls_colors ? '\n' : ':');
259 /* Read the file open on FP (with name FILENAME). First, look for a
260 'TERM name' directive where name matches the current terminal type.
261 Once found, translate and accumulate the associated directives onto
262 the global obstack LSC_OBSTACK. Give a diagnostic
263 upon failure (unrecognized keyword is the only way to fail here).
264 Return true if successful. */
266 static bool
267 dc_parse_stream (FILE *fp, char const *filename)
269 size_t line_number = 0;
270 char const *next_G_line = G_line;
271 char *input_line = nullptr;
272 size_t input_line_size = 0;
273 char const *line;
274 char const *term;
275 char const *colorterm;
276 bool ok = true;
278 /* State for the parser. */
279 enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
281 /* Get terminal type */
282 term = getenv ("TERM");
283 if (term == nullptr || *term == '\0')
284 term = "none";
286 /* Also match $COLORTERM. */
287 colorterm = getenv ("COLORTERM");
288 if (colorterm == nullptr)
289 colorterm = ""; /* Doesn't match default "?*" */
291 while (true)
293 char *keywd, *arg;
294 bool unrecognized;
296 ++line_number;
298 if (fp)
300 if (getline (&input_line, &input_line_size, fp) <= 0)
302 if (ferror (fp))
304 error (0, errno, _("%s: read error"), quotef (filename));
305 ok = false;
307 free (input_line);
308 break;
310 line = input_line;
312 else
314 if (next_G_line == G_line + sizeof G_line)
315 break;
316 line = next_G_line;
317 next_G_line += strlen (next_G_line) + 1;
320 parse_line (line, &keywd, &arg);
322 if (keywd == nullptr)
323 continue;
325 if (arg == nullptr)
327 error (0, 0, _("%s:%lu: invalid line; missing second token"),
328 quotef (filename), (unsigned long int) line_number);
329 ok = false;
330 free (keywd);
331 continue;
334 unrecognized = false;
335 if (c_strcasecmp (keywd, "TERM") == 0)
337 if (state != ST_TERMSURE)
338 state = fnmatch (arg, term, 0) == 0 ? ST_TERMSURE : ST_TERMNO;
340 else if (c_strcasecmp (keywd, "COLORTERM") == 0)
342 if (state != ST_TERMSURE)
343 state = fnmatch (arg, colorterm, 0) == 0 ? ST_TERMSURE : ST_TERMNO;
345 else
347 if (state == ST_TERMSURE)
348 state = ST_TERMYES; /* Another {COLOR,}TERM can cancel. */
350 if (state != ST_TERMNO)
352 if (keywd[0] == '.')
353 append_entry ('*', keywd, arg);
354 else if (keywd[0] == '*')
355 append_entry (0, keywd, arg);
356 else if (c_strcasecmp (keywd, "OPTIONS") == 0
357 || c_strcasecmp (keywd, "COLOR") == 0
358 || c_strcasecmp (keywd, "EIGHTBIT") == 0)
360 /* Ignore. */
362 else
364 int i;
366 for (i = 0; slack_codes[i] != nullptr; ++i)
367 if (c_strcasecmp (keywd, slack_codes[i]) == 0)
368 break;
370 if (slack_codes[i] != nullptr)
371 append_entry (0, ls_codes[i], arg);
372 else
373 unrecognized = true;
376 else
377 unrecognized = true;
380 if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
382 error (0, 0, _("%s:%lu: unrecognized keyword %s"),
383 (filename ? quotef (filename) : _("<internal>")),
384 (unsigned long int) line_number, keywd);
385 ok = false;
388 free (keywd);
389 free (arg);
392 return ok;
395 static bool
396 dc_parse_file (char const *filename)
398 bool ok;
400 if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == nullptr)
402 error (0, errno, "%s", quotef (filename));
403 return false;
406 ok = dc_parse_stream (stdin, filename);
408 if (fclose (stdin) != 0)
410 error (0, errno, "%s", quotef (filename));
411 return false;
414 return ok;
418 main (int argc, char **argv)
420 bool ok = true;
421 int optc;
422 enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
423 bool print_database = false;
425 initialize_main (&argc, &argv);
426 set_program_name (argv[0]);
427 setlocale (LC_ALL, "");
428 bindtextdomain (PACKAGE, LOCALEDIR);
429 textdomain (PACKAGE);
431 atexit (close_stdout);
433 while ((optc = getopt_long (argc, argv, "bcp", long_options, nullptr)) != -1)
434 switch (optc)
436 case 'b': /* Bourne shell syntax. */
437 syntax = SHELL_SYNTAX_BOURNE;
438 break;
440 case 'c': /* C shell syntax. */
441 syntax = SHELL_SYNTAX_C;
442 break;
444 case 'p':
445 print_database = true;
446 break;
448 case PRINT_LS_COLORS_OPTION:
449 print_ls_colors = true;
450 break;
452 case_GETOPT_HELP_CHAR;
454 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
456 default:
457 usage (EXIT_FAILURE);
460 argc -= optind;
461 argv += optind;
463 /* It doesn't make sense to use --print with either of
464 --bourne or --c-shell. */
465 if ((print_database | print_ls_colors) && syntax != SHELL_SYNTAX_UNKNOWN)
467 error (0, 0,
468 _("the options to output non shell syntax,\n"
469 "and to select a shell syntax are mutually exclusive"));
470 usage (EXIT_FAILURE);
473 if (print_database && print_ls_colors)
475 error (0, 0,
476 _("options --print-database and --print-ls-colors "
477 "are mutually exclusive"));
478 usage (EXIT_FAILURE);
481 if ((!print_database) < argc)
483 error (0, 0, _("extra operand %s"),
484 quote (argv[!print_database]));
485 if (print_database)
486 fprintf (stderr, "%s\n",
487 _("file operands cannot be combined with "
488 "--print-database (-p)"));
489 usage (EXIT_FAILURE);
492 if (print_database)
494 char const *p = G_line;
495 while (p - G_line < sizeof G_line)
497 puts (p);
498 p += strlen (p) + 1;
501 else
503 /* If shell syntax was not explicitly specified, try to guess it. */
504 if (syntax == SHELL_SYNTAX_UNKNOWN && ! print_ls_colors)
506 syntax = guess_shell_syntax ();
507 if (syntax == SHELL_SYNTAX_UNKNOWN)
509 die (EXIT_FAILURE, 0,
510 _("no SHELL environment variable, and no shell type option given"));
514 obstack_init (&lsc_obstack);
515 if (argc == 0)
516 ok = dc_parse_stream (nullptr, nullptr);
517 else
518 ok = dc_parse_file (argv[0]);
520 if (ok)
522 size_t len = obstack_object_size (&lsc_obstack);
523 char *s = obstack_finish (&lsc_obstack);
524 char const *prefix;
525 char const *suffix;
527 if (syntax == SHELL_SYNTAX_BOURNE)
529 prefix = "LS_COLORS='";
530 suffix = "';\nexport LS_COLORS\n";
532 else
534 prefix = "setenv LS_COLORS '";
535 suffix = "'\n";
537 if (! print_ls_colors)
538 fputs (prefix, stdout);
539 fwrite (s, 1, len, stdout);
540 if (! print_ls_colors)
541 fputs (suffix, stdout);
545 return ok ? EXIT_SUCCESS : EXIT_FAILURE;