ls: fix spurious output with -Z
[coreutils.git] / src / dircolors.c
blob5722c49b003828e17edd66b346b187e4328bd947
1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2024 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-ctype.h"
27 #include "c-strcase.h"
28 #include "obstack.h"
29 #include "quote.h"
30 #include "stdio--.h"
32 /* The official name of this program (e.g., no 'g' prefix). */
33 #define PROGRAM_NAME "dircolors"
35 #define AUTHORS proper_name ("H. Peter Anvin")
37 #define obstack_chunk_alloc malloc
38 #define obstack_chunk_free free
40 enum Shell_syntax
42 SHELL_SYNTAX_BOURNE,
43 SHELL_SYNTAX_C,
44 SHELL_SYNTAX_UNKNOWN
47 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
49 /* Accumulate in this obstack the value for the LS_COLORS environment
50 variable. */
51 static struct obstack lsc_obstack;
53 static char const *const slack_codes[] =
55 "NORMAL", "NORM", "FILE", "RESET", "DIR", "LNK", "LINK",
56 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
57 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
58 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
59 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
60 "MULTIHARDLINK", "CLRTOEOL", nullptr
63 static char const *const ls_codes[] =
65 "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
66 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
67 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl",
68 nullptr
70 static_assert (ARRAY_CARDINALITY (slack_codes) == ARRAY_CARDINALITY (ls_codes));
72 /* Whether to output escaped ls color codes for display. */
73 static bool print_ls_colors;
75 /* For long options that have no equivalent short option, use a
76 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
77 enum
79 PRINT_LS_COLORS_OPTION = CHAR_MAX + 1,
82 static struct option const long_options[] =
84 {"bourne-shell", no_argument, nullptr, 'b'},
85 {"sh", no_argument, nullptr, 'b'},
86 {"csh", no_argument, nullptr, 'c'},
87 {"c-shell", no_argument, nullptr, 'c'},
88 {"print-database", no_argument, nullptr, 'p'},
89 {"print-ls-colors", no_argument, nullptr, PRINT_LS_COLORS_OPTION},
90 {GETOPT_HELP_OPTION_DECL},
91 {GETOPT_VERSION_OPTION_DECL},
92 {nullptr, 0, nullptr, 0}
95 void
96 usage (int status)
98 if (status != EXIT_SUCCESS)
99 emit_try_help ();
100 else
102 printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
103 fputs (_("\
104 Output commands to set the LS_COLORS environment variable.\n\
106 Determine format of output:\n\
107 -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
108 -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
109 -p, --print-database output defaults\n\
110 --print-ls-colors output fully escaped colors for display\n\
111 "), stdout);
112 fputs (HELP_OPTION_DESCRIPTION, stdout);
113 fputs (VERSION_OPTION_DESCRIPTION, stdout);
114 fputs (_("\
116 If FILE is specified, read it to determine which colors to use for which\n\
117 file types and extensions. Otherwise, a precompiled database is used.\n\
118 For details on the format of these files, run 'dircolors --print-database'.\n\
119 "), stdout);
120 emit_ancillary_info (PROGRAM_NAME);
123 exit (status);
126 /* If the SHELL environment variable is set to 'csh' or 'tcsh,'
127 assume C shell. Else Bourne shell. */
129 static enum Shell_syntax
130 guess_shell_syntax (void)
132 char *shell;
134 shell = getenv ("SHELL");
135 if (shell == nullptr || *shell == '\0')
136 return SHELL_SYNTAX_UNKNOWN;
138 shell = last_component (shell);
140 if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
141 return SHELL_SYNTAX_C;
143 return SHELL_SYNTAX_BOURNE;
146 static void
147 parse_line (char const *line, char **keyword, char **arg)
149 char const *p;
150 char const *keyword_start;
151 char const *arg_start;
153 *keyword = nullptr;
154 *arg = nullptr;
156 for (p = line; c_isspace (to_uchar (*p)); ++p)
157 continue;
159 /* Ignore blank lines and shell-style comments. */
160 if (*p == '\0' || *p == '#')
161 return;
163 keyword_start = p;
165 while (!c_isspace (to_uchar (*p)) && *p != '\0')
167 ++p;
170 *keyword = ximemdup0 (keyword_start, p - keyword_start);
171 if (*p == '\0')
172 return;
176 ++p;
178 while (c_isspace (to_uchar (*p)));
180 if (*p == '\0' || *p == '#')
181 return;
183 arg_start = p;
185 while (*p != '\0' && *p != '#')
186 ++p;
188 for (--p; c_isspace (to_uchar (*p)); --p)
189 continue;
190 ++p;
192 *arg = ximemdup0 (arg_start, p - arg_start);
195 /* Accumulate STR to LS_COLORS data.
196 If outputting shell syntax, then escape appropriately. */
198 static void
199 append_quoted (char const *str)
201 bool need_backslash = true;
203 while (*str != '\0')
205 if (! print_ls_colors)
206 switch (*str)
208 case '\'':
209 APPEND_CHAR ('\'');
210 APPEND_CHAR ('\\');
211 APPEND_CHAR ('\'');
212 need_backslash = true;
213 break;
215 case '\\':
216 case '^':
217 need_backslash = !need_backslash;
218 break;
220 case ':':
221 case '=':
222 if (need_backslash)
223 APPEND_CHAR ('\\');
224 FALLTHROUGH;
226 default:
227 need_backslash = true;
228 break;
231 APPEND_CHAR (*str);
232 ++str;
236 /* Accumulate entry to LS_COLORS data.
237 Use shell syntax unless PRINT_LS_COLORS is set. */
239 static void
240 append_entry (char prefix, char const *item, char const *arg)
242 if (print_ls_colors)
244 append_quoted ("\x1B[");
245 append_quoted (arg);
246 APPEND_CHAR ('m');
248 if (prefix)
249 APPEND_CHAR (prefix);
250 append_quoted (item);
251 APPEND_CHAR (print_ls_colors ? '\t' : '=');
252 append_quoted (arg);
253 if (print_ls_colors)
254 append_quoted ("\x1B[0m");
255 APPEND_CHAR (print_ls_colors ? '\n' : ':');
258 /* Read the file open on FP (with name FILENAME). First, look for a
259 'TERM name' directive where name matches the current terminal type.
260 Once found, translate and accumulate the associated directives onto
261 the global obstack LSC_OBSTACK. Give a diagnostic
262 upon failure (unrecognized keyword is the only way to fail here).
263 Return true if successful. */
265 static bool
266 dc_parse_stream (FILE *fp, char const *filename)
268 idx_t line_number = 0;
269 char const *next_G_line = G_line;
270 char *input_line = nullptr;
271 size_t input_line_size = 0;
272 char const *line;
273 char const *term;
274 char const *colorterm;
275 bool ok = true;
277 /* State for the parser. */
278 enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
280 /* Get terminal type */
281 term = getenv ("TERM");
282 if (term == nullptr || *term == '\0')
283 term = "none";
285 /* Also match $COLORTERM. */
286 colorterm = getenv ("COLORTERM");
287 if (colorterm == nullptr)
288 colorterm = ""; /* Doesn't match default "?*" */
290 while (true)
292 char *keywd, *arg;
293 bool unrecognized;
295 ++line_number;
297 if (fp)
299 if (getline (&input_line, &input_line_size, fp) <= 0)
301 if (ferror (fp))
303 error (0, errno, _("%s: read error"), quotef (filename));
304 ok = false;
306 free (input_line);
307 break;
309 line = input_line;
311 else
313 if (next_G_line == G_line + sizeof G_line)
314 break;
315 line = next_G_line;
316 next_G_line += strlen (next_G_line) + 1;
319 parse_line (line, &keywd, &arg);
321 if (keywd == nullptr)
322 continue;
324 if (arg == nullptr)
326 error (0, 0, _("%s:%td: invalid line; missing second token"),
327 quotef (filename), line_number);
328 ok = false;
329 free (keywd);
330 continue;
333 unrecognized = false;
334 if (c_strcasecmp (keywd, "TERM") == 0)
336 if (state != ST_TERMSURE)
337 state = fnmatch (arg, term, 0) == 0 ? ST_TERMSURE : ST_TERMNO;
339 else if (c_strcasecmp (keywd, "COLORTERM") == 0)
341 if (state != ST_TERMSURE)
342 state = fnmatch (arg, colorterm, 0) == 0 ? ST_TERMSURE : ST_TERMNO;
344 else
346 if (state == ST_TERMSURE)
347 state = ST_TERMYES; /* Another {COLOR,}TERM can cancel. */
349 if (state != ST_TERMNO)
351 if (keywd[0] == '.')
352 append_entry ('*', keywd, arg);
353 else if (keywd[0] == '*')
354 append_entry (0, keywd, arg);
355 else if (c_strcasecmp (keywd, "OPTIONS") == 0
356 || c_strcasecmp (keywd, "COLOR") == 0
357 || c_strcasecmp (keywd, "EIGHTBIT") == 0)
359 /* Ignore. */
361 else
363 int i;
365 for (i = 0; slack_codes[i] != nullptr; ++i)
366 if (c_strcasecmp (keywd, slack_codes[i]) == 0)
367 break;
369 if (slack_codes[i] != nullptr)
370 append_entry (0, ls_codes[i], arg);
371 else
372 unrecognized = true;
375 else
376 unrecognized = true;
379 if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
381 error (0, 0, _("%s:%td: unrecognized keyword %s"),
382 (filename ? quotef (filename) : _("<internal>")),
383 line_number, keywd);
384 ok = false;
387 free (keywd);
388 free (arg);
391 return ok;
394 static bool
395 dc_parse_file (char const *filename)
397 bool ok;
399 if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == nullptr)
401 error (0, errno, "%s", quotef (filename));
402 return false;
405 ok = dc_parse_stream (stdin, filename);
407 if (fclose (stdin) != 0)
409 error (0, errno, "%s", quotef (filename));
410 return false;
413 return ok;
417 main (int argc, char **argv)
419 bool ok = true;
420 int optc;
421 enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
422 bool print_database = false;
424 initialize_main (&argc, &argv);
425 set_program_name (argv[0]);
426 setlocale (LC_ALL, "");
427 bindtextdomain (PACKAGE, LOCALEDIR);
428 textdomain (PACKAGE);
430 atexit (close_stdout);
432 while ((optc = getopt_long (argc, argv, "bcp", long_options, nullptr)) != -1)
433 switch (optc)
435 case 'b': /* Bourne shell syntax. */
436 syntax = SHELL_SYNTAX_BOURNE;
437 break;
439 case 'c': /* C shell syntax. */
440 syntax = SHELL_SYNTAX_C;
441 break;
443 case 'p':
444 print_database = true;
445 break;
447 case PRINT_LS_COLORS_OPTION:
448 print_ls_colors = true;
449 break;
451 case_GETOPT_HELP_CHAR;
453 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
455 default:
456 usage (EXIT_FAILURE);
459 argc -= optind;
460 argv += optind;
462 /* It doesn't make sense to use --print with either of
463 --bourne or --c-shell. */
464 if ((print_database | print_ls_colors) && syntax != SHELL_SYNTAX_UNKNOWN)
466 error (0, 0,
467 _("the options to output non shell syntax,\n"
468 "and to select a shell syntax are mutually exclusive"));
469 usage (EXIT_FAILURE);
472 if (print_database && print_ls_colors)
474 error (0, 0,
475 _("options --print-database and --print-ls-colors "
476 "are mutually exclusive"));
477 usage (EXIT_FAILURE);
480 if ((!print_database) < argc)
482 error (0, 0, _("extra operand %s"),
483 quote (argv[!print_database]));
484 if (print_database)
485 fprintf (stderr, "%s\n",
486 _("file operands cannot be combined with "
487 "--print-database (-p)"));
488 usage (EXIT_FAILURE);
491 if (print_database)
493 char const *p = G_line;
494 while (p - G_line < sizeof G_line)
496 puts (p);
497 p += strlen (p) + 1;
500 else
502 /* If shell syntax was not explicitly specified, try to guess it. */
503 if (syntax == SHELL_SYNTAX_UNKNOWN && ! print_ls_colors)
505 syntax = guess_shell_syntax ();
506 if (syntax == SHELL_SYNTAX_UNKNOWN)
507 error (EXIT_FAILURE, 0,
508 _("no SHELL environment variable,"
509 " and no shell type option given"));
512 obstack_init (&lsc_obstack);
513 if (argc == 0)
514 ok = dc_parse_stream (nullptr, nullptr);
515 else
516 ok = dc_parse_file (argv[0]);
518 if (ok)
520 size_t len = obstack_object_size (&lsc_obstack);
521 char *s = obstack_finish (&lsc_obstack);
522 char const *prefix;
523 char const *suffix;
525 if (syntax == SHELL_SYNTAX_BOURNE)
527 prefix = "LS_COLORS='";
528 suffix = "';\nexport LS_COLORS\n";
530 else
532 prefix = "setenv LS_COLORS '";
533 suffix = "'\n";
535 if (! print_ls_colors)
536 fputs (prefix, stdout);
537 fwrite (s, 1, len, stdout);
538 if (! print_ls_colors)
539 fputs (suffix, stdout);
543 return ok ? EXIT_SUCCESS : EXIT_FAILURE;