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