convert single-author programs to use proper_name
[platform/upstream/coreutils.git] / src / dircolors.c
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 proper_name ("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 usage (int status)
94 {
95   if (status != EXIT_SUCCESS)
96     fprintf (stderr, _("Try `%s --help' for more information.\n"),
97              program_name);
98   else
99     {
100       printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
101       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       fputs (HELP_OPTION_DESCRIPTION, stdout);
110       fputs (VERSION_OPTION_DESCRIPTION, stdout);
111       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       emit_bug_reporting_address ();
118     }
119
120   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 guess_shell_syntax (void)
128 {
129   char *shell;
130
131   shell = getenv ("SHELL");
132   if (shell == NULL || *shell == '\0')
133     return SHELL_SYNTAX_UNKNOWN;
134
135   shell = last_component (shell);
136
137   if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
138     return SHELL_SYNTAX_C;
139
140   return SHELL_SYNTAX_BOURNE;
141 }
142
143 static void
144 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   *keyword = NULL;
151   *arg = NULL;
152
153   for (p = line; isspace (to_uchar (*p)); ++p)
154     continue;
155
156   /* Ignore blank lines and shell-style comments.  */
157   if (*p == '\0' || *p == '#')
158     return;
159
160   keyword_start = p;
161
162   while (!isspace (to_uchar (*p)) && *p != '\0')
163     {
164       ++p;
165     }
166
167   *keyword = xstrndup (keyword_start, p - keyword_start);
168   if (*p  == '\0')
169     return;
170
171   do
172     {
173       ++p;
174     }
175   while (isspace (to_uchar (*p)));
176
177   if (*p == '\0' || *p == '#')
178     return;
179
180   arg_start = p;
181
182   while (*p != '\0' && *p != '#')
183     ++p;
184
185   for (--p; isspace (to_uchar (*p)); --p)
186     continue;
187   ++p;
188
189   *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 append_quoted (const char *str)
197 {
198   bool need_backslash = true;
199
200   while (*str != '\0')
201     {
202       switch (*str)
203         {
204         case '\'':
205           APPEND_CHAR ('\'');
206           APPEND_CHAR ('\\');
207           APPEND_CHAR ('\'');
208           need_backslash = true;
209           break;
210
211         case '\\':
212         case '^':
213           need_backslash = !need_backslash;
214           break;
215
216         case ':':
217         case '=':
218           if (need_backslash)
219             APPEND_CHAR ('\\');
220           /* Fall through */
221
222         default:
223           need_backslash = true;
224           break;
225         }
226
227       APPEND_CHAR (*str);
228       ++str;
229     }
230 }
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 dc_parse_stream (FILE *fp, const char *filename)
241 {
242   size_t line_number = 0;
243   char const *next_G_line = G_line;
244   char *input_line = NULL;
245   size_t input_line_size = 0;
246   char const *line;
247   char const *term;
248   bool ok = true;
249
250   /* State for the parser.  */
251   enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
252
253   /* Get terminal type */
254   term = getenv ("TERM");
255   if (term == NULL || *term == '\0')
256     term = "none";
257
258   while (1)
259     {
260       char *keywd, *arg;
261       bool unrecognized;
262
263       ++line_number;
264
265       if (fp)
266         {
267           if (getline (&input_line, &input_line_size, fp) <= 0)
268             {
269               free (input_line);
270               break;
271             }
272           line = input_line;
273         }
274       else
275         {
276           if (next_G_line == G_line + sizeof G_line)
277             break;
278           line = next_G_line;
279           next_G_line += strlen (next_G_line) + 1;
280         }
281
282       parse_line (line, &keywd, &arg);
283
284       if (keywd == NULL)
285         continue;
286
287       if (arg == NULL)
288         {
289           error (0, 0, _("%s:%lu: invalid line;  missing second token"),
290                  filename, (unsigned long int) line_number);
291           ok = false;
292           free (keywd);
293           continue;
294         }
295
296       unrecognized = false;
297       if (c_strcasecmp (keywd, "TERM") == 0)
298         {
299           if (STREQ (arg, term))
300             state = ST_TERMSURE;
301           else if (state != ST_TERMSURE)
302             state = ST_TERMNO;
303         }
304       else
305         {
306           if (state == ST_TERMSURE)
307             state = ST_TERMYES; /* Another TERM can cancel */
308
309           if (state != ST_TERMNO)
310             {
311               if (keywd[0] == '.')
312                 {
313                   APPEND_CHAR ('*');
314                   append_quoted (keywd);
315                   APPEND_CHAR ('=');
316                   append_quoted (arg);
317                   APPEND_CHAR (':');
318                 }
319               else if (keywd[0] == '*')
320                 {
321                   append_quoted (keywd);
322                   APPEND_CHAR ('=');
323                   append_quoted (arg);
324                   APPEND_CHAR (':');
325                 }
326               else if (c_strcasecmp (keywd, "OPTIONS") == 0
327                        || c_strcasecmp (keywd, "COLOR") == 0
328                        || c_strcasecmp (keywd, "EIGHTBIT") == 0)
329                 {
330                   /* Ignore.  */
331                 }
332               else
333                 {
334                   int i;
335
336                   for (i = 0; slack_codes[i] != NULL; ++i)
337                     if (c_strcasecmp (keywd, slack_codes[i]) == 0)
338                       break;
339
340                   if (slack_codes[i] != NULL)
341                     {
342                       APPEND_TWO_CHAR_STRING (ls_codes[i]);
343                       APPEND_CHAR ('=');
344                       append_quoted (arg);
345                       APPEND_CHAR (':');
346                     }
347                   else
348                     {
349                       unrecognized = true;
350                     }
351                 }
352             }
353           else
354             {
355               unrecognized = true;
356             }
357         }
358
359       if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
360         {
361           error (0, 0, _("%s:%lu: unrecognized keyword %s"),
362                  (filename ? quote (filename) : _("<internal>")),
363                  (unsigned long int) line_number, keywd);
364           ok = false;
365         }
366
367       free (keywd);
368       free (arg);
369     }
370
371   return ok;
372 }
373
374 static bool
375 dc_parse_file (const char *filename)
376 {
377   bool ok;
378
379   if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL)
380     {
381       error (0, errno, "%s", filename);
382       return false;
383     }
384
385   ok = dc_parse_stream (stdin, filename);
386
387   if (fclose (stdin) != 0)
388     {
389       error (0, errno, "%s", quote (filename));
390       return false;
391     }
392
393   return ok;
394 }
395
396 int
397 main (int argc, char **argv)
398 {
399   bool ok = true;
400   int optc;
401   enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
402   bool print_database = false;
403
404   initialize_main (&argc, &argv);
405   program_name = argv[0];
406   setlocale (LC_ALL, "");
407   bindtextdomain (PACKAGE, LOCALEDIR);
408   textdomain (PACKAGE);
409
410   atexit (close_stdout);
411
412   while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
413     switch (optc)
414       {
415       case 'b': /* Bourne shell syntax.  */
416         syntax = SHELL_SYNTAX_BOURNE;
417         break;
418
419       case 'c': /* C shell syntax.  */
420         syntax = SHELL_SYNTAX_C;
421         break;
422
423       case 'p':
424         print_database = true;
425         break;
426
427       case_GETOPT_HELP_CHAR;
428
429       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
430
431       default:
432         usage (EXIT_FAILURE);
433       }
434
435   argc -= optind;
436   argv += optind;
437
438   /* It doesn't make sense to use --print with either of
439      --bourne or --c-shell.  */
440   if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
441     {
442       error (0, 0,
443              _("the options to output dircolors' internal database and\n\
444 to select a shell syntax are mutually exclusive"));
445       usage (EXIT_FAILURE);
446     }
447
448   if (!print_database < argc)
449     {
450       error (0, 0, _("extra operand %s"), quote (argv[!print_database]));
451       if (print_database)
452         fprintf (stderr, "%s\n",
453                  _("File operands cannot be combined with "
454                    "--print-database (-p)."));
455       usage (EXIT_FAILURE);
456     }
457
458   if (print_database)
459     {
460       char const *p = G_line;
461       while (p < G_line + sizeof G_line)
462         {
463           puts (p);
464           p += strlen (p) + 1;
465         }
466     }
467   else
468     {
469       /* If shell syntax was not explicitly specified, try to guess it. */
470       if (syntax == SHELL_SYNTAX_UNKNOWN)
471         {
472           syntax = guess_shell_syntax ();
473           if (syntax == SHELL_SYNTAX_UNKNOWN)
474             {
475               error (EXIT_FAILURE, 0,
476          _("no SHELL environment variable, and no shell type option given"));
477             }
478         }
479
480       obstack_init (&lsc_obstack);
481       if (argc == 0)
482         ok = dc_parse_stream (NULL, NULL);
483       else
484         ok = dc_parse_file (argv[0]);
485
486       if (ok)
487         {
488           size_t len = obstack_object_size (&lsc_obstack);
489           char *s = obstack_finish (&lsc_obstack);
490           const char *prefix;
491           const char *suffix;
492
493           if (syntax == SHELL_SYNTAX_BOURNE)
494             {
495               prefix = "LS_COLORS='";
496               suffix = "';\nexport LS_COLORS\n";
497             }
498           else
499             {
500               prefix = "setenv LS_COLORS '";
501               suffix = "'\n";
502             }
503           fputs (prefix, stdout);
504           fwrite (s, 1, len, stdout);
505           fputs (suffix, stdout);
506         }
507     }
508
509   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
510 }