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