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