maint: cleanup up various uses of __attribute__
[coreutils.git] / src / dircolors.c
blob5610f08392fb88068d7839b9d1e88bcb0ad4da16
1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2013 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 <http://www.gnu.org/licenses/>. */
18 #include <config.h>
20 #include <sys/types.h>
21 #include <getopt.h>
23 #include "system.h"
24 #include "dircolors.h"
25 #include "c-strcase.h"
26 #include "error.h"
27 #include "obstack.h"
28 #include "quote.h"
29 #include "stdio--.h"
30 #include "xstrndup.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)
48 #define APPEND_TWO_CHAR_STRING(S) \
49 do \
50 { \
51 APPEND_CHAR (S[0]); \
52 APPEND_CHAR (S[1]); \
53 } \
54 while (0)
56 /* Accumulate in this obstack the value for the LS_COLORS environment
57 variable. */
58 static struct obstack lsc_obstack;
60 static const char *const slack_codes[] =
62 "NORMAL", "NORM", "FILE", "RESET", "DIR", "LNK", "LINK",
63 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
64 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
65 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
66 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
67 "MULTIHARDLINK", "CLRTOEOL", NULL
70 static const char *const ls_codes[] =
72 "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
73 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
74 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl", NULL
76 verify (ARRAY_CARDINALITY (slack_codes) == ARRAY_CARDINALITY (ls_codes));
78 static struct option const long_options[] =
80 {"bourne-shell", no_argument, NULL, 'b'},
81 {"sh", no_argument, NULL, 'b'},
82 {"csh", no_argument, NULL, 'c'},
83 {"c-shell", no_argument, NULL, 'c'},
84 {"print-database", no_argument, NULL, 'p'},
85 {GETOPT_HELP_OPTION_DECL},
86 {GETOPT_VERSION_OPTION_DECL},
87 {NULL, 0, NULL, 0}
90 void
91 usage (int status)
93 if (status != EXIT_SUCCESS)
94 emit_try_help ();
95 else
97 printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
98 fputs (_("\
99 Output commands to set the LS_COLORS environment variable.\n\
101 Determine format of output:\n\
102 -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
103 -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
104 -p, --print-database output defaults\n\
105 "), stdout);
106 fputs (HELP_OPTION_DESCRIPTION, stdout);
107 fputs (VERSION_OPTION_DESCRIPTION, stdout);
108 fputs (_("\
110 If FILE is specified, read it to determine which colors to use for which\n\
111 file types and extensions. Otherwise, a precompiled database is used.\n\
112 For details on the format of these files, run 'dircolors --print-database'.\n\
113 "), stdout);
114 emit_ancillary_info ();
117 exit (status);
120 /* If the SHELL environment variable is set to 'csh' or 'tcsh,'
121 assume C shell. Else Bourne shell. */
123 static enum Shell_syntax
124 guess_shell_syntax (void)
126 char *shell;
128 shell = getenv ("SHELL");
129 if (shell == NULL || *shell == '\0')
130 return SHELL_SYNTAX_UNKNOWN;
132 shell = last_component (shell);
134 if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
135 return SHELL_SYNTAX_C;
137 return SHELL_SYNTAX_BOURNE;
140 static void
141 parse_line (char const *line, char **keyword, char **arg)
143 char const *p;
144 char const *keyword_start;
145 char const *arg_start;
147 *keyword = NULL;
148 *arg = NULL;
150 for (p = line; isspace (to_uchar (*p)); ++p)
151 continue;
153 /* Ignore blank lines and shell-style comments. */
154 if (*p == '\0' || *p == '#')
155 return;
157 keyword_start = p;
159 while (!isspace (to_uchar (*p)) && *p != '\0')
161 ++p;
164 *keyword = xstrndup (keyword_start, p - keyword_start);
165 if (*p == '\0')
166 return;
170 ++p;
172 while (isspace (to_uchar (*p)));
174 if (*p == '\0' || *p == '#')
175 return;
177 arg_start = p;
179 while (*p != '\0' && *p != '#')
180 ++p;
182 for (--p; isspace (to_uchar (*p)); --p)
183 continue;
184 ++p;
186 *arg = xstrndup (arg_start, p - arg_start);
189 /* FIXME: Write a string to standard out, while watching for "dangerous"
190 sequences like unescaped : and = characters. */
192 static void
193 append_quoted (const char *str)
195 bool need_backslash = true;
197 while (*str != '\0')
199 switch (*str)
201 case '\'':
202 APPEND_CHAR ('\'');
203 APPEND_CHAR ('\\');
204 APPEND_CHAR ('\'');
205 need_backslash = true;
206 break;
208 case '\\':
209 case '^':
210 need_backslash = !need_backslash;
211 break;
213 case ':':
214 case '=':
215 if (need_backslash)
216 APPEND_CHAR ('\\');
217 /* Fall through */
219 default:
220 need_backslash = true;
221 break;
224 APPEND_CHAR (*str);
225 ++str;
229 /* Read the file open on FP (with name FILENAME). First, look for a
230 'TERM name' directive where name matches the current terminal type.
231 Once found, translate and accumulate the associated directives onto
232 the global obstack LSC_OBSTACK. Give a diagnostic
233 upon failure (unrecognized keyword is the only way to fail here).
234 Return true if successful. */
236 static bool
237 dc_parse_stream (FILE *fp, const char *filename)
239 size_t line_number = 0;
240 char const *next_G_line = G_line;
241 char *input_line = NULL;
242 size_t input_line_size = 0;
243 char const *line;
244 char const *term;
245 bool ok = true;
247 /* State for the parser. */
248 enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
250 /* Get terminal type */
251 term = getenv ("TERM");
252 if (term == NULL || *term == '\0')
253 term = "none";
255 while (1)
257 char *keywd, *arg;
258 bool unrecognized;
260 ++line_number;
262 if (fp)
264 if (getline (&input_line, &input_line_size, fp) <= 0)
266 free (input_line);
267 break;
269 line = input_line;
271 else
273 if (next_G_line == G_line + sizeof G_line)
274 break;
275 line = next_G_line;
276 next_G_line += strlen (next_G_line) + 1;
279 parse_line (line, &keywd, &arg);
281 if (keywd == NULL)
282 continue;
284 if (arg == NULL)
286 error (0, 0, _("%s:%lu: invalid line; missing second token"),
287 filename, (unsigned long int) line_number);
288 ok = false;
289 free (keywd);
290 continue;
293 unrecognized = false;
294 if (c_strcasecmp (keywd, "TERM") == 0)
296 if (STREQ (arg, term))
297 state = ST_TERMSURE;
298 else if (state != ST_TERMSURE)
299 state = ST_TERMNO;
301 else
303 if (state == ST_TERMSURE)
304 state = ST_TERMYES; /* Another TERM can cancel */
306 if (state != ST_TERMNO)
308 if (keywd[0] == '.')
310 APPEND_CHAR ('*');
311 append_quoted (keywd);
312 APPEND_CHAR ('=');
313 append_quoted (arg);
314 APPEND_CHAR (':');
316 else if (keywd[0] == '*')
318 append_quoted (keywd);
319 APPEND_CHAR ('=');
320 append_quoted (arg);
321 APPEND_CHAR (':');
323 else if (c_strcasecmp (keywd, "OPTIONS") == 0
324 || c_strcasecmp (keywd, "COLOR") == 0
325 || c_strcasecmp (keywd, "EIGHTBIT") == 0)
327 /* Ignore. */
329 else
331 int i;
333 for (i = 0; slack_codes[i] != NULL; ++i)
334 if (c_strcasecmp (keywd, slack_codes[i]) == 0)
335 break;
337 if (slack_codes[i] != NULL)
339 APPEND_TWO_CHAR_STRING (ls_codes[i]);
340 APPEND_CHAR ('=');
341 append_quoted (arg);
342 APPEND_CHAR (':');
344 else
346 unrecognized = true;
350 else
352 unrecognized = true;
356 if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
358 error (0, 0, _("%s:%lu: unrecognized keyword %s"),
359 (filename ? quote (filename) : _("<internal>")),
360 (unsigned long int) line_number, keywd);
361 ok = false;
364 free (keywd);
365 free (arg);
368 return ok;
371 static bool
372 dc_parse_file (const char *filename)
374 bool ok;
376 if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL)
378 error (0, errno, "%s", filename);
379 return false;
382 ok = dc_parse_stream (stdin, filename);
384 if (fclose (stdin) != 0)
386 error (0, errno, "%s", quote (filename));
387 return false;
390 return ok;
394 main (int argc, char **argv)
396 bool ok = true;
397 int optc;
398 enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
399 bool print_database = false;
401 initialize_main (&argc, &argv);
402 set_program_name (argv[0]);
403 setlocale (LC_ALL, "");
404 bindtextdomain (PACKAGE, LOCALEDIR);
405 textdomain (PACKAGE);
407 atexit (close_stdout);
409 while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
410 switch (optc)
412 case 'b': /* Bourne shell syntax. */
413 syntax = SHELL_SYNTAX_BOURNE;
414 break;
416 case 'c': /* C shell syntax. */
417 syntax = SHELL_SYNTAX_C;
418 break;
420 case 'p':
421 print_database = true;
422 break;
424 case_GETOPT_HELP_CHAR;
426 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
428 default:
429 usage (EXIT_FAILURE);
432 argc -= optind;
433 argv += optind;
435 /* It doesn't make sense to use --print with either of
436 --bourne or --c-shell. */
437 if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
439 error (0, 0,
440 _("the options to output dircolors' internal database and\n"
441 "to select a shell syntax are mutually exclusive"));
442 usage (EXIT_FAILURE);
445 if (!print_database < argc)
447 error (0, 0, _("extra operand %s"), quote (argv[!print_database]));
448 if (print_database)
449 fprintf (stderr, "%s\n",
450 _("file operands cannot be combined with "
451 "--print-database (-p)"));
452 usage (EXIT_FAILURE);
455 if (print_database)
457 char const *p = G_line;
458 while (p - G_line < sizeof G_line)
460 puts (p);
461 p += strlen (p) + 1;
464 else
466 /* If shell syntax was not explicitly specified, try to guess it. */
467 if (syntax == SHELL_SYNTAX_UNKNOWN)
469 syntax = guess_shell_syntax ();
470 if (syntax == SHELL_SYNTAX_UNKNOWN)
472 error (EXIT_FAILURE, 0,
473 _("no SHELL environment variable, and no shell type option given"));
477 obstack_init (&lsc_obstack);
478 if (argc == 0)
479 ok = dc_parse_stream (NULL, NULL);
480 else
481 ok = dc_parse_file (argv[0]);
483 if (ok)
485 size_t len = obstack_object_size (&lsc_obstack);
486 char *s = obstack_finish (&lsc_obstack);
487 const char *prefix;
488 const char *suffix;
490 if (syntax == SHELL_SYNTAX_BOURNE)
492 prefix = "LS_COLORS='";
493 suffix = "';\nexport LS_COLORS\n";
495 else
497 prefix = "setenv LS_COLORS '";
498 suffix = "'\n";
500 fputs (prefix, stdout);
501 fwrite (s, 1, len, stdout);
502 fputs (suffix, stdout);
506 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);