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