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