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