bracket bug address with <> and append a period
[platform/upstream/coreutils.git] / src / dircolors.c
index 9391b5c..405406f 100644 (file)
@@ -1,10 +1,4 @@
-/* FIXME: accept, but ignore EIGHTBIT option
-   FIXME: embed contents of default mapping file
-   FIXME: add option to print that default mapping?
-
-   Only distinction necessary: Bourne vs. C-shell
- */
-/* dircolors - parse a Slackware-style DIR_COLORS file.
+/* dircolors - output commands to set the LS_COLOR environment variable
    Copyright (C) 1994, 1995 H. Peter Anvin
    Copyright (C) 1996 Free Software Foundation, Inc.
 
@@ -26,32 +20,51 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 # include <config.h>
 #endif
 
+#include <sys/types.h>
 #include <ctype.h>
 #include <getopt.h>
 #include <stdio.h>
 
 #include "system.h"
+#include "getline.h"
+#include "long-options.h"
 #include "error.h"
+#include "obstack.h"
+#include "dircolors.h"
 
-char *xmalloc ();
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
 
-#define USER_FILE ".dir_colors"        /* Versus user's home directory */
-#define SYSTEM_FILE "DIR_COLORS" /* System-wide file in directory SYSTEM_DIR
-                                    (defined on the cc command line).  */
+#ifndef STDC_HEADERS
+void free ();
+#endif
 
-#define STRINGLEN 2048         /* Max length of a string */
+char *xmalloc ();
+char *basename ();
+char *strndup();
 
-enum modes { MO_SH, MO_CSH, MO_KSH, MO_ZSH, MO_UNKNOWN, MO_ERR };
+enum Shell_syntax
+{
+  SHELL_SYNTAX_BOURNE,
+  SHELL_SYNTAX_C,
+  SHELL_SYNTAX_UNKNOWN
+};
 
-/* FIXME: associate these arrays? */
-static const char *const shells[] =
-{ "sh", "ash", "csh", "tcsh", "bash", "ksh", "zsh", NULL };
+#define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
+#define APPEND_TWO_CHAR_STRING(S)                                      \
+  do                                                                   \
+    {                                                                  \
+      APPEND_CHAR (S[0]);                                              \
+      APPEND_CHAR (S[1]);                                              \
+    }                                                                  \
+  while (0)
 
-static const int shell_mode[] =
-{ MO_SH, MO_SH, MO_CSH, MO_CSH, MO_KSH, MO_KSH, MO_ZSH };
+/* Accumulate in this obstack the value for the LS_COLORS environment
+   variable.  */
+static struct obstack lsc_obstack;
 
-/* Parser needs these state variables.  */
-enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
+/* Nonzero if the input file was the standard input. */
+static int have_read_stdin;
 
 /* FIXME: associate with ls_codes? */
 static const char *const slack_codes[] =
@@ -70,14 +83,14 @@ static const char *const ls_codes[] =
 
 static struct option const long_options[] =
   {
-    {"ash", no_argument, NULL, 'a'},
-    {"bash", no_argument, NULL, 'b'},
+    {"bourne-shell", no_argument, NULL, 'b'},
+    {"sh", no_argument, NULL, 'b'},
     {"csh", no_argument, NULL, 'c'},
+    {"c-shell", no_argument, NULL, 'c'},
     {"help", no_argument, NULL, 'h'},
-    {"sh", no_argument, NULL, 's'},
-    {"tcsh", no_argument, NULL, 't'},
+    {"print-database", no_argument, NULL, 'p'},
     {"version", no_argument, NULL, 'v'},
-    {"zsh", no_argument, NULL, 'z'},
+    {NULL, 0, NULL, 0}
   };
 
 char *program_name;
@@ -92,93 +105,112 @@ usage (int status)
     {
       printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
       printf (_("\
-  -h, --help        display this help and exit\n\
-      --version     output version information and exit\n\
+Output commands to set the LS_COLORS environment variable.\n\
+\n\
 Determine format of output:\n\
-  -a, --ash         assume ash shell\n\
-  -b, --bash        assume bash shell\n\
-  -c, --csh         assume csh shell\n\
-  -s, --sh          assume Bourne shell\n\
-  -t, --tcsh        assume tcsh shell\n\
-  -z, --zsh         assume zsh shell\n"));
+  -b, --sh, --bourne-shell    output Bourne shell code to set LS_COLORS\n\
+  -c, --csh, --c-shell        output C shell code to set LS_COLORS\n\
+  -p, --print-database        output defaults\n\
+      --help                  display this help and exit\n\
+      --version               output version information and exit\n\
+\n\
+If FILE is specified, read it to determine which colors to use for which\n\
+file types and extensions.  Otherwise, a precompiled database is used.\n\
+For details on the format of these files, run `dircolors --print-database'.\n\
+"));
+      puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
     }
 
-  exit (status);
+  exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
-/* If SHELL is csh or tcsh, assume C-shell.  Else Bourne shell.  */
-/* FIXME: rename functon.  */
+static void *
+xstrndup (const char *s, size_t n)
+{
+  char *new = strndup (s, n);
+  if (new == NULL)
+    error (EXIT_FAILURE, 0, _("virtual memory exhausted"));
+  return new;
+}
 
-static int
-figure_mode (void)
+/* If the SHELL environment variable is set to `csh' or `tcsh,'
+   assume C shell.  Else Bourne shell.  */
+
+static enum Shell_syntax
+guess_shell_syntax (void)
 {
-  char *shell, *shellv;
-  int i;
+  char *shell;
 
-  shellv = getenv ("SHELL");
-  if (shellv == NULL || *shellv == '\0')
-    error (1, 0, _("\
-No SHELL variable, and no mode option specified"));
+  shell = getenv ("SHELL");
+  if (shell == NULL || *shell == '\0')
+    return SHELL_SYNTAX_UNKNOWN;
 
-  shell = strrchr (shellv, '/');
-  if (shell != NULL)
-    ++shell;
-  else
-    shell = shellv;
+  shell = basename (shell);
 
-  for (i = 0; shells[i]; ++i)
-    if (strcmp (shell, shells[i]) == 0)
-      return shell_mode[i];
+  if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
+    return SHELL_SYNTAX_C;
 
-  error (1, 0, _("Unknown shell `%s'\n"), shell);
-  /* NOTREACHED */
+  return SHELL_SYNTAX_BOURNE;
 }
 
 static void
-parse_line (char **keyword, char **arg, char *line)
+parse_line (const char *line, char **keyword, char **arg)
 {
-  char *p;
+  const char *p;
+  const char *keyword_start;
+  const char *arg_start;
 
-  *keyword = *arg = "";
+  *keyword = NULL;
+  *arg = NULL;
 
   for (p = line; isspace (*p); ++p)
     ;
 
+  /* Ignore blank lines and shell-style comments.  */
   if (*p == '\0' || *p == '#')
     return;
 
-  *keyword = p;
+  keyword_start = p;
 
-  while (!isspace (*p))
-    if (*p++ == '\0')
-      return;
+  while (!isspace (*p) && *p != '\0')
+    {
+      ++p;
+    }
 
-  *p++ = '\0';
+  *keyword = xstrndup (keyword_start, p - keyword_start);
+  if (*p  == '\0')
+    return;
 
-  while (isspace (*p))
-    ++p;
+  do
+    {
+      ++p;
+    }
+  while (isspace (*p));
 
   if (*p == '\0' || *p == '#')
     return;
 
-  *arg = p;
+  arg_start = p;
 
   while (*p != '\0' && *p != '#')
     ++p;
+
   for (--p; isspace (*p); --p)
-    ;
+    {
+      /* empty */
+    }
   ++p;
 
-  *p = '\0';
+  *arg = xstrndup (arg_start, p - arg_start);
 }
 
-/* Write a string to standard out, while watching for "dangerous"
+/* FIXME: Write a string to standard out, while watching for "dangerous"
    sequences like unescaped : and = characters.  */
 
 static void
-put_seq (const char *str, char follow)
+append_quoted (const char *str)
 {
-  int danger = 1;
+  int need_backslash = 1;
 
   while (*str != '\0')
     {
@@ -186,232 +218,314 @@ put_seq (const char *str, char follow)
        {
        case '\\':
        case '^':
-         danger = !danger;
+         need_backslash = !need_backslash;
          break;
 
        case ':':
        case '=':
-         if (danger)
-           putchar ('\\');
+         if (need_backslash)
+           APPEND_CHAR ('\\');
          /* Fall through */
 
        default:
-         danger = 1;
+         need_backslash = 1;
          break;
        }
 
-      putchar (*str++);
+      APPEND_CHAR (*str);
+      ++str;
     }
-
-  putchar (follow);            /* The character that ends the sequence.  */
 }
 
-int
-main (int argc, char *argv[])
+/* Read the file open on FP (with name FILENAME).  First, look for a
+   `TERM name' directive where name matches the current terminal type.
+   Once found, translate and accumulate the associated directives onto
+   the global obstack LSC_OBSTACK.  Give a diagnostic and return nonzero
+   upon failure (unrecognized keyword is the only way to fail here).
+   Return zero otherwise.  */
+
+static int
+dc_parse_stream (FILE *fp, const char *filename)
 {
-  char *p;
-  int optc;
-  int mode = MO_UNKNOWN;
-  FILE *fp = NULL;
-  char *term;
+  size_t line_number = 0;
+  char *line = NULL;
+  size_t line_chars_allocated = 0;
   int state;
+  char *term;
+  int err = 0;
 
-  char line[STRINGLEN];
-  char useropts[2048] = "";
-  char *keywd, *arg;
+  /* State for the parser.  */
+  enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
 
-  int do_help = 0;
-  int do_version = 0;
+  state = ST_GLOBAL;
 
-  char *input_file;
+  /* Get terminal type */
+  term = getenv ("TERM");
+  if (term == NULL || *term == '\0')
+    term = "none";
+
+  while (1)
+    {
+      int line_length;
+      char *keywd, *arg;
+      int unrecognized;
+
+      ++line_number;
+
+      if (fp)
+       {
+         line_length = getline (&line, &line_chars_allocated, fp);
+         if (line_length <= 0)
+           {
+             if (line)
+               free (line);
+             break;
+           }
+       }
+      else
+       {
+         line = (char *) (G_line[line_number - 1]);
+         line_length = G_line_length[line_number - 1];
+         if (line_number > G_N_LINES)
+           break;
+       }
+
+      parse_line (line, &keywd, &arg);
+
+      if (keywd == NULL)
+       continue;
+
+      unrecognized = 0;
+      if (strcasecmp (keywd, "TERM") == 0)
+       {
+         if (strcmp (arg, term) == 0)
+           state = ST_TERMSURE;
+         else if (state != ST_TERMSURE)
+           state = ST_TERMNO;
+       }
+      else
+       {
+         if (state == ST_TERMSURE)
+           state = ST_TERMYES; /* Another TERM can cancel */
+
+         if (state != ST_TERMNO)
+           {
+             if (keywd[0] == '.')
+               {
+                 APPEND_CHAR ('*');
+                 append_quoted (keywd);
+                 APPEND_CHAR ('=');
+                 append_quoted (arg);
+                 APPEND_CHAR (':');
+               }
+             else if (keywd[0] == '*')
+               {
+                 append_quoted (keywd);
+                 APPEND_CHAR ('=');
+                 append_quoted (arg);
+                 APPEND_CHAR (':');
+               }
+             else if (strcasecmp (keywd, "OPTIONS") == 0
+                      || strcasecmp (keywd, "COLOR") == 0
+                      || strcasecmp (keywd, "EIGHTBIT") == 0)
+               {
+                 /* Ignore.  */
+               }
+             else
+               {
+                 int i;
+
+                 for (i = 0; slack_codes[i] != NULL; ++i)
+                   if (strcasecmp (keywd, slack_codes[i]) == 0)
+                     break;
+
+                 if (slack_codes[i] != NULL)
+                   {
+                     APPEND_TWO_CHAR_STRING (ls_codes[i]);
+                     APPEND_CHAR ('=');
+                     append_quoted (arg);
+                     APPEND_CHAR (':');
+                   }
+                 else
+                   {
+                     unrecognized = 1;
+                   }
+               }
+           }
+         else
+           {
+             unrecognized = 1;
+           }
+       }
+
+      if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
+       {
+         error (0, 0, _("%s:%lu: unrecognized keyword `%s'"),
+                filename, (long unsigned) line_number, keywd);
+         err = 1;
+       }
+
+      free (keywd);
+      if (arg)
+       free (arg);
+    }
+
+  return err;
+}
+
+static int
+dc_parse_file (const char *filename)
+{
+  FILE *fp;
+  int err;
+
+  if (strcmp (filename, "-") == 0)
+    {
+      have_read_stdin = 1;
+      fp = stdin;
+    }
+  else
+    {
+      /* OPENOPTS is a macro.  It varies with the system.
+        Some systems distinguish between internal and
+        external text representations.  */
+
+      fp = fopen (filename, "r");
+      if (fp == NULL)
+       {
+         error (0, errno, "%s", filename);
+         return 1;
+       }
+    }
+
+  err = dc_parse_stream (fp, filename);
+
+  if (fp != stdin && fclose (fp) == EOF)
+    {
+      error (0, errno, "%s", filename);
+      return 1;
+    }
+
+  return err;
+}
+
+int
+main (int argc, char **argv)
+{
+  int err = 0;
+  int optc;
+  enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
+  int print_database = 0;
 
   program_name = argv[0];
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
 
-  /* Parse command line.  */
+  parse_long_options (argc, argv, "dircolors", GNU_PACKAGE, VERSION, usage);
 
-  while ((optc = getopt_long (argc, argv, "abhckPstz", long_options, NULL))
-        != EOF)
+  while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
     switch (optc)
       {
-      case 'a':
-      case 's':        /* Plain sh mode */
-       mode = MO_SH;
-       break;
-
-      case 'c':
-      case 't':
-       mode = MO_CSH;
+      case 'b':        /* Bourne shell syntax.  */
+       syntax = SHELL_SYNTAX_BOURNE;
        break;
 
-      case 'b':
-      case 'k':
-       mode = MO_KSH;
+      case 'c':        /* C shell syntax.  */
+       syntax = SHELL_SYNTAX_C;
        break;
 
-      case 'h':
-       do_help = 1;
-       break;
-
-      case 'z':
-       mode = MO_ZSH;
-       break;
-
-      case 'v':
-       do_version = 1;
+      case 'p':
+       print_database = 1;
        break;
 
       default:
        usage (1);
       }
 
-  if (do_version)
+  argc -= optind;
+  argv += optind;
+
+  /* It doesn't make sense to use --print with either of
+     --bourne or --c-shell.  */
+  if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
     {
-      printf ("%s - %s\n", program_name, PACKAGE_VERSION);
-      exit (0);
+      error (0, 0,
+            _("the options to output dircolors' internal database and\n\
+to select a shell syntax are mutually exclusive"));
+      usage (1);
     }
 
-  if (do_help)
-    usage (0);
-
-  /* Use shell to determine mode, if not already done. */
-  if (mode == MO_UNKNOWN)
-    mode = figure_mode ();
+  if (print_database && argc > 0)
+    {
+      error (0, 0,
+            _("no FILE arguments may be used with the option to output\n\
+dircolors' internal database"));
+      usage (1);
+    }
 
-  /* Open dir_colors file */
-  if (optind == argc)
+  if (!print_database && argc > 1)
     {
-      p = getenv ("HOME");
-      if (p != NULL && *p != '\0')
-       {
-         /* Note: deliberate leak.  It's not worth freeing this.  */
-         input_file = xmalloc (strlen (p) + 1
-                               + strlen (USER_FILE) + 1);
-         stpcpy (stpcpy (stpcpy (input_file, p), "/"), USER_FILE);
-         fp = fopen (input_file, "r");
-       }
+      error (0, 0, _("too many arguments"));
+      usage (1);
+    }
 
-      if (fp == NULL)
+  if (print_database)
+    {
+      int i;
+      for (i = 0; i < G_N_LINES; i++)
        {
-         /* Note: deliberate leak.  It's not worth freeing this.  */
-         input_file = xmalloc (strlen (SHAREDIR) + 1
-                               + strlen (USER_FILE) + 1);
-         stpcpy (stpcpy (stpcpy (input_file, SHAREDIR), "/"),
-                 SYSTEM_FILE);
-         fp = fopen (input_file, "r");
+         fwrite (G_line[i], 1, G_line_length[i], stdout);
+         fputc ('\n', stdout);
        }
     }
   else
     {
-      input_file = argv[optind];
-      fp = fopen (input_file, "r");
-    }
-
-  if (fp == NULL)
-    error (1, errno, _("while opening input file `%s'"), input_file);
-
-  /* Get terminal type */
-  term = getenv ("TERM");
-  if (term == NULL || *term == '\0')
-    term = "none";
-
-  /* Write out common start */
-  switch (mode)
-    {
-    case MO_CSH:
-      puts ("set noglob;\n\
-setenv LS_COLORS \':");
-      break;
-    case MO_SH:
-    case MO_KSH:
-    case MO_ZSH:
-      fputs ("LS_COLORS=\'", stdout);
-      break;
-    }
+      /* If shell syntax was not explicitly specified, try to guess it. */
+      if (syntax == SHELL_SYNTAX_UNKNOWN)
+       {
+         syntax = guess_shell_syntax ();
+         if (syntax == SHELL_SYNTAX_UNKNOWN)
+           {
+             error (EXIT_FAILURE, 0,
+        _("no SHELL environment variable, and no shell type option given"));
+           }
+       }
 
-  state = ST_GLOBAL;
+      obstack_init (&lsc_obstack);
+      if (argc == 0)
+       err = dc_parse_stream (NULL, NULL);
+      else
+       err = dc_parse_file (argv[0]);
 
-  /* FIXME: use getline */
-  while (fgets (line, STRINGLEN, fp) != NULL)
-    {
-      parse_line (&keywd, &arg, line);
-      if (*keywd != '\0')
+      if (!err)
        {
-         if (strcasecmp (keywd, "TERM") == 0)
+         size_t len = obstack_object_size (&lsc_obstack);
+         char *s = obstack_finish (&lsc_obstack);
+         const char *prefix;
+         const char *suffix;
+
+         if (syntax == SHELL_SYNTAX_BOURNE)
            {
-             if (strcmp (arg, term) == 0)
-               state = ST_TERMSURE;
-             else if (state != ST_TERMSURE)
-               state = ST_TERMNO;
+             prefix = "LS_COLORS='";
+             suffix = "';\nexport LS_COLORS\n";
            }
          else
            {
-             if (state == ST_TERMSURE)
-               state = ST_TERMYES; /* Another TERM can cancel */
-
-             if (state != ST_TERMNO)
-               {
-                 if (keywd[0] == '.')
-                   {
-                     putchar ('*');
-                     put_seq (keywd, '=');
-                     put_seq (arg, ':');
-                   }
-                 else if (keywd[0] == '*')
-                   {
-                     put_seq (keywd, '=');
-                     put_seq (arg, ':');
-                   }
-                 else if (strcasecmp (keywd, "OPTIONS") == 0)
-                   {
-                     strcat (useropts, " ");
-                     strcat (useropts, arg);
-                   }
-                 else if (strcasecmp (keywd, "COLOR") == 0)
-                   {
-                      /* FIXME: ignored now.  */
-                   }
-                 else
-                   {
-                     int i;
-
-                     for (i = 0; slack_codes[i] != NULL; ++i)
-                       if (strcasecmp (keywd, slack_codes[i]) == 0)
-                         break;
-
-                     if (slack_codes[i] != NULL)
-                       {
-                         printf ("%s=", ls_codes[i]);
-                         put_seq (arg, ':');
-                       }
-                     else
-                       error (0, 0, _("Unknown keyword %s\n"), keywd);
-                   }
-               }
+             prefix = "setenv LS_COLORS '";
+             suffix = "'\n";
            }
+         fputs (prefix, stdout);
+         fwrite (s, 1, len, stdout);
+         fputs (suffix, stdout);
        }
     }
 
-  fclose (fp);
+  if (fclose (stdout) == EOF)
+    error (EXIT_FAILURE, errno, _("write error"));
 
-  /* Write it out.  */
-  switch (mode)
-    {
-    case MO_SH:
-    case MO_KSH:
-    case MO_ZSH:
-      printf ("\';\nexport LS_COLORS;\n");
-      break;
-
-    case MO_CSH:
-      printf ("\';\nunset noglob;\n");
-      break;
-
-    default:
-      abort ();
-    }
+  if (have_read_stdin && fclose (stdin) == EOF)
+    error (EXIT_FAILURE, errno, _("standard input"));
 
-  exit (0);
+  exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }