Line data Source code
1 : /* dircolors - output commands to set the LS_COLOR environment variable
2 : Copyright (C) 1996-2008 Free Software Foundation, Inc.
3 : Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000 H. Peter Anvin
4 :
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.
9 :
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.
14 :
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/>. */
17 :
18 : #include <config.h>
19 :
20 : #include <sys/types.h>
21 : #include <getopt.h>
22 : #include <stdio.h>
23 :
24 : #include "system.h"
25 : #include "dircolors.h"
26 : #include "c-strcase.h"
27 : #include "error.h"
28 : #include "obstack.h"
29 : #include "quote.h"
30 : #include "xstrndup.h"
31 :
32 : /* The official name of this program (e.g., no `g' prefix). */
33 : #define PROGRAM_NAME "dircolors"
34 :
35 : #define AUTHORS "H. Peter Anvin"
36 :
37 : #define obstack_chunk_alloc malloc
38 : #define obstack_chunk_free free
39 :
40 : enum Shell_syntax
41 : {
42 : SHELL_SYNTAX_BOURNE,
43 : SHELL_SYNTAX_C,
44 : SHELL_SYNTAX_UNKNOWN
45 : };
46 :
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)
55 :
56 : /* Accumulate in this obstack the value for the LS_COLORS environment
57 : variable. */
58 : static struct obstack lsc_obstack;
59 :
60 : static const char *const slack_codes[] =
61 : {
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", NULL
67 : };
68 :
69 : static const char *const ls_codes[] =
70 : {
71 : "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
72 : "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
73 : "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", NULL
74 : };
75 : #define array_len(Array) (sizeof (Array) / sizeof *(Array))
76 : verify (array_len (slack_codes) == array_len (ls_codes));
77 :
78 : static struct option const long_options[] =
79 : {
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}
88 : };
89 :
90 : char *program_name;
91 :
92 : void
93 33 : usage (int status)
94 : {
95 33 : if (status != EXIT_SUCCESS)
96 32 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
97 : program_name);
98 : else
99 : {
100 1 : printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
101 1 : fputs (_("\
102 : Output commands to set the LS_COLORS environment variable.\n\
103 : \n\
104 : Determine format of output:\n\
105 : -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
106 : -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
107 : -p, --print-database output defaults\n\
108 : "), stdout);
109 1 : fputs (HELP_OPTION_DESCRIPTION, stdout);
110 1 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
111 1 : fputs (_("\
112 : \n\
113 : If FILE is specified, read it to determine which colors to use for which\n\
114 : file types and extensions. Otherwise, a precompiled database is used.\n\
115 : For details on the format of these files, run `dircolors --print-database'.\n\
116 : "), stdout);
117 1 : emit_bug_reporting_address ();
118 : }
119 :
120 33 : exit (status);
121 : }
122 :
123 : /* If the SHELL environment variable is set to `csh' or `tcsh,'
124 : assume C shell. Else Bourne shell. */
125 :
126 : static enum Shell_syntax
127 24 : guess_shell_syntax (void)
128 : {
129 : char *shell;
130 :
131 24 : shell = getenv ("SHELL");
132 24 : if (shell == NULL || *shell == '\0')
133 24 : return SHELL_SYNTAX_UNKNOWN;
134 :
135 0 : shell = last_component (shell);
136 :
137 0 : if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
138 0 : return SHELL_SYNTAX_C;
139 :
140 0 : return SHELL_SYNTAX_BOURNE;
141 : }
142 :
143 : static void
144 370 : parse_line (char const *line, char **keyword, char **arg)
145 : {
146 : char const *p;
147 : char const *keyword_start;
148 : char const *arg_start;
149 :
150 370 : *keyword = NULL;
151 370 : *arg = NULL;
152 :
153 398 : for (p = line; isspace (to_uchar (*p)); ++p)
154 28 : continue;
155 :
156 : /* Ignore blank lines and shell-style comments. */
157 370 : if (*p == '\0' || *p == '#')
158 98 : return;
159 :
160 272 : keyword_start = p;
161 :
162 1688 : while (!isspace (to_uchar (*p)) && *p != '\0')
163 : {
164 1144 : ++p;
165 : }
166 :
167 272 : *keyword = xstrndup (keyword_start, p - keyword_start);
168 272 : if (*p == '\0')
169 0 : return;
170 :
171 : do
172 : {
173 272 : ++p;
174 : }
175 272 : while (isspace (to_uchar (*p)));
176 :
177 272 : if (*p == '\0' || *p == '#')
178 0 : return;
179 :
180 272 : arg_start = p;
181 :
182 2262 : while (*p != '\0' && *p != '#')
183 1718 : ++p;
184 :
185 300 : for (--p; isspace (to_uchar (*p)); --p)
186 28 : continue;
187 272 : ++p;
188 :
189 272 : *arg = xstrndup (arg_start, p - arg_start);
190 : }
191 :
192 : /* FIXME: Write a string to standard out, while watching for "dangerous"
193 : sequences like unescaped : and = characters. */
194 :
195 : static void
196 338 : append_quoted (const char *str)
197 : {
198 338 : bool need_backslash = true;
199 :
200 2212 : while (*str != '\0')
201 : {
202 1536 : switch (*str)
203 : {
204 0 : case '\'':
205 0 : APPEND_CHAR ('\'');
206 0 : APPEND_CHAR ('\\');
207 0 : APPEND_CHAR ('\'');
208 0 : need_backslash = true;
209 0 : break;
210 :
211 0 : case '\\':
212 : case '^':
213 0 : need_backslash = !need_backslash;
214 0 : break;
215 :
216 0 : case ':':
217 : case '=':
218 0 : if (need_backslash)
219 0 : APPEND_CHAR ('\\');
220 : /* Fall through */
221 :
222 : default:
223 1536 : need_backslash = true;
224 1536 : break;
225 : }
226 :
227 1536 : APPEND_CHAR (*str);
228 1536 : ++str;
229 : }
230 338 : }
231 :
232 : /* Read the file open on FP (with name FILENAME). First, look for a
233 : `TERM name' directive where name matches the current terminal type.
234 : Once found, translate and accumulate the associated directives onto
235 : the global obstack LSC_OBSTACK. Give a diagnostic
236 : upon failure (unrecognized keyword is the only way to fail here).
237 : Return true if successful. */
238 :
239 : static bool
240 5 : dc_parse_stream (FILE *fp, const char *filename)
241 : {
242 5 : size_t line_number = 0;
243 5 : char const *next_G_line = G_line;
244 5 : char *input_line = NULL;
245 5 : size_t input_line_size = 0;
246 : char const *line;
247 : char const *term;
248 5 : bool ok = true;
249 :
250 : /* State for the parser. */
251 5 : enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
252 :
253 : /* Get terminal type */
254 5 : term = getenv ("TERM");
255 5 : if (term == NULL || *term == '\0')
256 0 : term = "none";
257 :
258 : while (1)
259 370 : {
260 : char *keywd, *arg;
261 : bool unrecognized;
262 :
263 375 : ++line_number;
264 :
265 375 : if (fp)
266 : {
267 27 : if (getline (&input_line, &input_line_size, fp) <= 0)
268 : {
269 3 : free (input_line);
270 8 : break;
271 : }
272 24 : line = input_line;
273 : }
274 : else
275 : {
276 348 : if (next_G_line == G_line + sizeof G_line)
277 2 : break;
278 346 : line = next_G_line;
279 346 : next_G_line += strlen (next_G_line) + 1;
280 : }
281 :
282 370 : parse_line (line, &keywd, &arg);
283 :
284 370 : if (keywd == NULL)
285 196 : continue;
286 :
287 272 : if (arg == NULL)
288 : {
289 0 : error (0, 0, _("%s:%lu: invalid line; missing second token"),
290 : filename, (unsigned long int) line_number);
291 0 : ok = false;
292 0 : free (keywd);
293 0 : continue;
294 : }
295 :
296 272 : unrecognized = false;
297 272 : if (c_strcasecmp (keywd, "TERM") == 0)
298 : {
299 88 : if (STREQ (arg, term))
300 2 : state = ST_TERMSURE;
301 86 : else if (state != ST_TERMSURE)
302 76 : state = ST_TERMNO;
303 : }
304 : else
305 : {
306 184 : if (state == ST_TERMSURE)
307 2 : state = ST_TERMYES; /* Another TERM can cancel */
308 :
309 184 : if (state != ST_TERMNO)
310 : {
311 184 : if (keywd[0] == '.')
312 : {
313 154 : APPEND_CHAR ('*');
314 154 : append_quoted (keywd);
315 154 : APPEND_CHAR ('=');
316 154 : append_quoted (arg);
317 154 : APPEND_CHAR (':');
318 : }
319 30 : else if (keywd[0] == '*')
320 : {
321 0 : append_quoted (keywd);
322 0 : APPEND_CHAR ('=');
323 0 : append_quoted (arg);
324 0 : APPEND_CHAR (':');
325 : }
326 30 : else if (c_strcasecmp (keywd, "OPTIONS") == 0
327 30 : || c_strcasecmp (keywd, "COLOR") == 0
328 30 : || c_strcasecmp (keywd, "EIGHTBIT") == 0)
329 : {
330 : /* Ignore. */
331 : }
332 : else
333 : {
334 : int i;
335 :
336 532 : for (i = 0; slack_codes[i] != NULL; ++i)
337 532 : if (c_strcasecmp (keywd, slack_codes[i]) == 0)
338 30 : break;
339 :
340 30 : if (slack_codes[i] != NULL)
341 : {
342 30 : APPEND_TWO_CHAR_STRING (ls_codes[i]);
343 30 : APPEND_CHAR ('=');
344 30 : append_quoted (arg);
345 30 : APPEND_CHAR (':');
346 : }
347 : else
348 : {
349 0 : unrecognized = true;
350 : }
351 : }
352 : }
353 : else
354 : {
355 0 : unrecognized = true;
356 : }
357 : }
358 :
359 272 : if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
360 : {
361 0 : error (0, 0, _("%s:%lu: unrecognized keyword %s"),
362 : (filename ? quote (filename) : _("<internal>")),
363 : (unsigned long int) line_number, keywd);
364 0 : ok = false;
365 : }
366 :
367 272 : free (keywd);
368 272 : free (arg);
369 : }
370 :
371 10 : return ok;
372 : }
373 :
374 : static bool
375 5 : dc_parse_file (const char *filename)
376 : {
377 : bool ok;
378 :
379 5 : if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL)
380 : {
381 2 : error (0, errno, "%s", filename);
382 2 : return false;
383 : }
384 :
385 3 : ok = dc_parse_stream (stdin, filename);
386 :
387 3 : if (fclose (stdin) != 0)
388 : {
389 0 : error (0, errno, "%s", quote (filename));
390 0 : return false;
391 : }
392 :
393 3 : return ok;
394 : }
395 :
396 : int
397 67 : main (int argc, char **argv)
398 : {
399 67 : bool ok = true;
400 : int optc;
401 67 : enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
402 67 : bool print_database = false;
403 :
404 : initialize_main (&argc, &argv);
405 67 : program_name = argv[0];
406 67 : setlocale (LC_ALL, "");
407 : bindtextdomain (PACKAGE, LOCALEDIR);
408 : textdomain (PACKAGE);
409 :
410 67 : atexit (close_stdout);
411 :
412 150 : while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
413 23 : switch (optc)
414 : {
415 10 : case 'b': /* Bourne shell syntax. */
416 10 : syntax = SHELL_SYNTAX_BOURNE;
417 10 : break;
418 :
419 2 : case 'c': /* C shell syntax. */
420 2 : syntax = SHELL_SYNTAX_C;
421 2 : break;
422 :
423 4 : case 'p':
424 4 : print_database = true;
425 4 : break;
426 :
427 1 : case_GETOPT_HELP_CHAR;
428 :
429 1 : case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
430 :
431 5 : default:
432 5 : usage (EXIT_FAILURE);
433 : }
434 :
435 60 : argc -= optind;
436 60 : argv += optind;
437 :
438 : /* It doesn't make sense to use --print with either of
439 : --bourne or --c-shell. */
440 60 : if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
441 : {
442 1 : error (0, 0,
443 : _("the options to output dircolors' internal database and\n\
444 : to select a shell syntax are mutually exclusive"));
445 1 : usage (EXIT_FAILURE);
446 : }
447 :
448 59 : if (!print_database < argc)
449 : {
450 26 : error (0, 0, _("extra operand %s"), quote (argv[!print_database]));
451 26 : if (print_database)
452 1 : fprintf (stderr, "%s\n",
453 : _("File operands cannot be combined with "
454 : "--print-database (-p)."));
455 26 : usage (EXIT_FAILURE);
456 : }
457 :
458 33 : if (print_database)
459 : {
460 2 : char const *p = G_line;
461 350 : while (p < G_line + sizeof G_line)
462 : {
463 346 : puts (p);
464 346 : p += strlen (p) + 1;
465 : }
466 : }
467 : else
468 : {
469 : /* If shell syntax was not explicitly specified, try to guess it. */
470 31 : if (syntax == SHELL_SYNTAX_UNKNOWN)
471 : {
472 24 : syntax = guess_shell_syntax ();
473 24 : if (syntax == SHELL_SYNTAX_UNKNOWN)
474 : {
475 24 : error (EXIT_FAILURE, 0,
476 : _("no SHELL environment variable, and no shell type option given"));
477 : }
478 : }
479 :
480 7 : obstack_init (&lsc_obstack);
481 7 : if (argc == 0)
482 2 : ok = dc_parse_stream (NULL, NULL);
483 : else
484 5 : ok = dc_parse_file (argv[0]);
485 :
486 7 : if (ok)
487 : {
488 5 : size_t len = obstack_object_size (&lsc_obstack);
489 5 : char *s = obstack_finish (&lsc_obstack);
490 : const char *prefix;
491 : const char *suffix;
492 :
493 5 : if (syntax == SHELL_SYNTAX_BOURNE)
494 : {
495 4 : prefix = "LS_COLORS='";
496 4 : suffix = "';\nexport LS_COLORS\n";
497 : }
498 : else
499 : {
500 1 : prefix = "setenv LS_COLORS '";
501 1 : suffix = "'\n";
502 : }
503 5 : fputs (prefix, stdout);
504 5 : fwrite (s, 1, len, stdout);
505 5 : fputs (suffix, stdout);
506 : }
507 : }
508 :
509 9 : exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
510 : }
|