bracket bug address with <> and append a period
[platform/upstream/coreutils.git] / src / ls.c
index dd42e0b..df71ce2 100644 (file)
--- a/src/ls.c
+++ b/src/ls.c
@@ -1,5 +1,5 @@
 /* `dir', `vdir' and `ls' directory listing programs for GNU.
-   Copyright (C) 1985, 1988, 1990, 1991, 1995 Free Software Foundation, Inc.
+   Copyright (C) 85, 88, 90, 91, 95, 1996 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
-\f
-/* If the macro MULTI_COL is defined,
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* If ls_mode is LS_MULTI_COL,
    the multi-column format is the default regardless
    of the type of output device.
    This is for the `dir' program.
 
-   If the macro LONG_FORMAT is defined,
+   If ls_mode is LS_LONG_FORMAT,
    the long format is the default regardless of the
    type of output device.
    This is for the `vdir' program.
 
-   If neither is defined,
+   If ls_mode is LS_LS,
    the output format depends on whether the output
    device is a terminal.
    This is for the `ls' program. */
 
-/* Written by Richard Stallman and David MacKenzie. */
+/* Written by Richard Stallman and David MacKenzie.  */
+
+/* Color support by Peter Anvin <Peter.Anvin@linux.org> and Dennis
+   Flaherty <dennisf@denix.elk.miles.com> based on original patches by
+   Greg Lee <lee@uhunix.uhcc.hawaii.edu>.  */
 
 #ifdef _AIX
  #pragma alloca
@@ -39,7 +43,8 @@
 #include <config.h>
 #include <sys/types.h>
 
-#if HAVE_SYS_IOCTL_H
+#include <termios.h>
+#ifdef GWINSZ_IN_SYS_IOCTL
 # include <sys/ioctl.h>
 #endif
 
@@ -52,7 +57,7 @@
 /* limits.h must come before system.h because limits.h on some systems
    undefs PATH_MAX, whereas system.h includes pathmax.h which sets
    PATH_MAX.  */
-#include <limits.h>
+# include <limits.h>
 #endif
 
 #include "system.h"
 
 #include "obstack.h"
 #include "ls.h"
-#include "version.h"
 #include "error.h"
+#include "argmatch.h"
+#include "xstrtol.h"
 
 #define obstack_chunk_alloc xmalloc
 #define obstack_chunk_free free
 
 #ifndef INT_MAX
-#define INT_MAX 2147483647
+# define INT_MAX 2147483647
 #endif
 
 /* Return an int indicating the result of comparing two longs. */
 #if (INT_MAX <= 65535)
-#define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b) ? 1 : 0)
+# define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b) ? 1 : 0)
 #else
-#define longdiff(a, b) ((a) - (b))
+# define longdiff(a, b) ((a) - (b))
 #endif
 
 /* The maximum number of digits required to print an inode number
    in an unsigned format.  */
 #ifndef INODE_DIGITS
-#define INODE_DIGITS 7
+# define INODE_DIGITS 7
 #endif
 
 enum filetype
@@ -105,11 +111,26 @@ struct fileinfo
        zero. */
     unsigned int linkmode;
 
+    /* For symbolic link and color printing, 1 if linked-to file
+       exists, otherwise 0.  */
+    int linkok;
+
     enum filetype filetype;
   };
 
+#define LEN_STR_PAIR(s) sizeof (s) - 1, s
+
+/* Null is a valid character in a color indicator (think about Epson
+   printers, for example) so we have to use a length/buffer string
+   type.  */
+
+struct bin_str
+  {
+    unsigned int len;          /* Number of bytes */
+    char *string;              /* Pointer to the same */
+  };
+
 #ifndef STDC_HEADERS
-char *ctime ();
 time_t time ();
 void free ();
 #endif
@@ -122,62 +143,64 @@ char *getgroup ();
 char *getuser ();
 char *xmalloc ();
 char *xrealloc ();
-int argmatch ();
 void invalid_arg ();
 
-static char *make_link_path __P ((char *path, char *linkname));
-static int compare_atime __P ((struct fileinfo *file1,
-                              struct fileinfo *file2));
-static int rev_cmp_atime __P ((struct fileinfo *file2,
-                              struct fileinfo *file1));
-static int compare_ctime __P ((struct fileinfo *file1,
-                               struct fileinfo *file2));
-static int rev_cmp_ctime __P ((struct fileinfo *file2,
-                               struct fileinfo *file1));
-static int compare_mtime __P ((struct fileinfo *file1,
-                               struct fileinfo *file2));
-static int rev_cmp_mtime __P ((struct fileinfo *file2,
-                               struct fileinfo *file1));
-static int compare_size __P ((struct fileinfo *file1,
-                               struct fileinfo *file2));
-static int rev_cmp_size __P ((struct fileinfo *file2,
-                               struct fileinfo *file1));
-static int compare_name __P ((struct fileinfo *file1,
-                               struct fileinfo *file2));
-static int rev_cmp_name __P ((struct fileinfo *file2,
-                               struct fileinfo *file1));
-static int compare_extension __P ((struct fileinfo *file1,
-                               struct fileinfo *file2));
-static int rev_cmp_extension __P ((struct fileinfo *file2,
-                               struct fileinfo *file1));
+static char *make_link_path __P ((const char *path, const char *linkname));
+static int compare_atime __P ((const struct fileinfo *file1,
+                              const struct fileinfo *file2));
+static int rev_cmp_atime __P ((const struct fileinfo *file2,
+                              const struct fileinfo *file1));
+static int compare_ctime __P ((const struct fileinfo *file1,
+                              const struct fileinfo *file2));
+static int rev_cmp_ctime __P ((const struct fileinfo *file2,
+                              const struct fileinfo *file1));
+static int compare_mtime __P ((const struct fileinfo *file1,
+                              const struct fileinfo *file2));
+static int rev_cmp_mtime __P ((const struct fileinfo *file2,
+                              const struct fileinfo *file1));
+static int compare_size __P ((const struct fileinfo *file1,
+                             const struct fileinfo *file2));
+static int rev_cmp_size __P ((const struct fileinfo *file2,
+                             const struct fileinfo *file1));
+static int compare_name __P ((const struct fileinfo *file1,
+                             const struct fileinfo *file2));
+static int rev_cmp_name __P ((const struct fileinfo *file2,
+                             const struct fileinfo *file1));
+static int compare_extension __P ((const struct fileinfo *file1,
+                                  const struct fileinfo *file2));
+static int rev_cmp_extension __P ((const struct fileinfo *file2,
+                                  const struct fileinfo *file1));
 static int decode_switches __P ((int argc, char **argv));
-static void parse_ls_color __P ((void));
-static int file_interesting __P ((register struct dirent *next));
+static int file_interesting __P ((const struct dirent *next));
 static int gobble_file __P ((const char *name, int explicit_arg,
                             const char *dirname));
-static int is_not_dot_or_dotdot __P ((char *name));
-static int length_of_file_name_and_frills __P ((struct fileinfo *f));
-static void add_ignore_pattern __P ((char *pattern));
+static int is_not_dot_or_dotdot __P ((const char *name));
+static void print_color_indicator __P ((const char *name, unsigned int mode,
+                                       int linkok));
+static void put_indicator __P ((const struct bin_str *ind));
+static int length_of_file_name_and_frills __P ((const struct fileinfo *f));
+static void add_ignore_pattern __P ((const char *pattern));
 static void attach __P ((char *dest, const char *dirname, const char *name));
 static void clear_files __P ((void));
 static void extract_dirs_from_files __P ((const char *dirname, int recursive));
-static void get_link_name __P ((char *filename, struct fileinfo *f));
+static void get_link_name __P ((const char *filename, struct fileinfo *f));
 static void indent __P ((int from, int to));
 static void print_current_files __P ((void));
 static void print_dir __P ((const char *name, const char *realname));
-static void print_file_name_and_frills __P ((struct fileinfo *f));
+static void print_file_name_and_frills __P ((const struct fileinfo *f));
 static void print_horizontal __P ((void));
-static void print_long_format __P ((struct fileinfo *f));
+static void print_long_format __P ((const struct fileinfo *f));
 static void print_many_per_line __P ((void));
-static void print_name_with_quoting __P ((register char *p));
+static void print_name_with_quoting __P ((const char *p, unsigned int mode,
+                                         int linkok));
+static void prep_non_filename_text __P ((void));
 static void print_type_indicator __P ((unsigned int mode));
-static void print_color_indicator __P ((unsigned int mode));
-static void put_indicator __P ((int n));
 static void print_with_commas __P ((void));
-static void queue_directory __P ((char *name, char *realname));
+static void queue_directory __P ((const char *name, const char *realname));
 static void sort_files __P ((void));
+static void parse_ls_color __P ((void));
 static void usage __P ((int status));
-\f
+
 /* The name the program was run with, stripped of any leading path. */
 char *program_name;
 
@@ -222,7 +245,7 @@ static time_t current_time;
    4, or more if needed for bigger numbers.  */
 
 static int block_size_size;
-\f
+
 /* Option flags */
 
 /* long_format for lots of info, one per line.
@@ -305,81 +328,76 @@ static int kilobyte_blocks;
    strange characters in file names.  */
 static int dired;
 
-/* none means don't mention the type of files.
-   all means mention the types of all files.
-   not_programs means do so except for executables.
+/* `none' means don't mention the type of files.
+   `all' means mention the types of all files.
+   `not_programs' means do so except for executables.
 
    Controlled by -F and -p.  */
 
 enum indicator_style
   {
-    none,                      /* default */
-    all,                       /* -F */
-    not_programs               /* -p */
+    none,                      /* default */
+    all,                       /* -F */
+    not_programs               /* -p */
   };
 
 static enum indicator_style indicator_style;
 
 /* Nonzero means use colors to mark types.  Also define the different
    colors as well as the stuff for the LS_COLORS environment variable.
-   The LS_COLORS variable is now in a termcap-like format.  -o or
-   --color-if-tty. */
+   The LS_COLORS variable is now in a termcap-like format.  */
 
 static int print_with_color;
 
 enum color_type
   {
-    color_no,                  /* 0: default or --color=no */
-    color_yes,                 /* 1: -o or --color=yes */
+    color_never,               /* 0: default or --color=never */
+    color_always,              /* 1: --color=always */
     color_if_tty               /* 2: --color=tty */
   };
 
-/* Note that color_no and color_yes equals boolean values; they will
-   be assigned to print_with_color which is a boolean variable */
-
-#define MAXCOLORLEN  16                /* Max # of characters in a color sequence */
-
 enum indicator_no
   {
-    C_LEFT, C_RIGHT, C_END, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK,
-    C_BLK, C_CHR, C_EXEC
+    C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK,
+    C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC
   };
 
-const char *const indicator_name[] =
-{
-  "lc", "rc", "ec", "fi", "di", "ln", "pi", "so", "bd", "cd", "ex", NULL
-};
-
-struct indicator_type
+static const char *const indicator_name[]=
   {
-    int len;
-    char string[MAXCOLORLEN];
+    "lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so",
+    "bd", "cd", "mi", "or", "ex", NULL
   };
 
-#define LEN_STR_PAIR(s) sizeof (s) - 1, s
+struct col_ext_type
+  {
+    struct bin_str ext;                /* The extension we're looking for */
+    struct bin_str seq;                /* The sequence to output when we do */
+    struct col_ext_type *next; /* Next in list */
+  };
 
-static struct indicator_type color_indicator[] =
-{
-  {LEN_STR_PAIR ("\033[")},    /* lc: Left of color sequence */
-  {LEN_STR_PAIR ("m")},                /* rc: Right of color sequence */
-  {LEN_STR_PAIR ("\033[0m")},  /* ec: End color */
-  {LEN_STR_PAIR ("0")},                /* fi: File: default */
-  {LEN_STR_PAIR ("32")},       /* di: Directory: green */
-  {LEN_STR_PAIR ("36")},       /* ln: Symlink: cyan */
-  {LEN_STR_PAIR ("31")},       /* pi: Pipe: red */
-  {LEN_STR_PAIR ("33")},       /* so: Socket: yellow/brown */
-  {LEN_STR_PAIR ("44;37")},    /* bd: Block device: white on blue */
-  {LEN_STR_PAIR ("44;37")},    /* cd: Char device: white on blue */
-  {LEN_STR_PAIR ("35")},       /* ex: Executable: purple */
-};
+static struct bin_str color_indicator[] =
+  {
+    { LEN_STR_PAIR ("\033[") },                /* lc: Left of color sequence */
+    { LEN_STR_PAIR ("m") },            /* rc: Right of color sequence */
+    { 0, NULL },                       /* ec: End color (replaces lc+no+rc) */
+    { LEN_STR_PAIR ("0") },            /* no: Normal */
+    { LEN_STR_PAIR ("0") },            /* fi: File: default */
+    { LEN_STR_PAIR ("01;34") },                /* di: Directory: bright blue */
+    { LEN_STR_PAIR ("01;36") },                /* ln: Symlink: bright cyan */
+    { LEN_STR_PAIR ("33") },           /* pi: Pipe: yellow/brown */
+    { LEN_STR_PAIR ("01;35") },                /* so: Socket: bright magenta */
+    { LEN_STR_PAIR ("01;33") },                /* bd: Block device: bright yellow */
+    { LEN_STR_PAIR ("01;33") },                /* cd: Char device: bright yellow */
+    { 0, NULL },                       /* mi: Missing file: undefined */
+    { 0, NULL },                       /* or: Orphanned symlink: undefined */
+    { LEN_STR_PAIR ("01;32") }         /* ex: Executable: bright green */
+  };
 
-/* Nonzero means print using ISO 8859 characters.  The default is specified
-   here as well.  -8 enables, -7 disables.  */
+/* FIXME: comment  */
+struct col_ext_type *col_ext_list = NULL;
 
-static int print_iso8859;
-#ifndef DEFAULT_ISO8859
-#define DEFAULT_ISO8859  1
-#endif
+/* Buffer for color sequences */
+static char *color_buf;
 
 /* Nonzero means mention the inode number of each file.  -i  */
 
@@ -416,7 +434,7 @@ static int really_all_files;
 
 struct ignore_pattern
   {
-    char *pattern;
+    const char *pattern;
     struct ignore_pattern *next;
   };
 
@@ -439,7 +457,8 @@ static int qmark_funny_chars;
 
 static int quote_as_string;
 
-/* The number of chars per hardware tab stop.  -T */
+/* The number of chars per hardware tab stop.  Setting this to zero
+   inhibits the use of TAB characters for separating columns.  -T */
 static int tabsize;
 
 /* Nonzero means we are listing the working directory because no
@@ -465,10 +484,10 @@ static int format_needs_stat;
 
 static int exit_status;
 
-/* If non-zero, display usage information and exit.  */
+/* If nonzero, display usage information and exit.  */
 static int show_help;
 
-/* If non-zero, print the version on standard output and exit.  */
+/* If nonzero, print the version on standard output and exit.  */
 static int show_version;
 
 static struct option const long_options[] =
@@ -501,11 +520,8 @@ static struct option const long_options[] =
   {"time", required_argument, 0, 11},
   {"help", no_argument, &show_help, 1},
   {"version", no_argument, &show_version, 1},
-  {"color", optional_argument, 0, 'o'},
-  {"colour", optional_argument, 0, 'o'},
-  {"7bit", no_argument, 0, '7'},
-  {"8bit", no_argument, 0, '8'},
-  {0, 0, 0, 0}
+  {"color", optional_argument, 0, 13},
+  {NULL, 0, NULL, 0}
 };
 
 static char const *const format_args[] =
@@ -587,16 +603,22 @@ static enum time_type const time_types[] =
 };
 
 static char const *const color_args[] =
-{
-  /* Note: "no" is a prefix of "none" so we don't include it */
-  "yes", "force", "none", "tty", "if-tty"
-};
+  {
+    /* Note: "no" is a prefix of "none" so we don't include it.  */
+    /* force and none are for compatibility with another color-ls version */
+    "always", "yes", "force",
+    "never", "none",
+    "auto", "tty", "if-tty", 0
+  };
 
 static enum color_type const color_types[] =
-{
-  color_yes, color_yes, color_no, color_if_tty, color_if_tty
-};
-\f
+  {
+    color_always, color_always, color_always,
+    color_never, color_never,
+    color_if_tty, color_if_tty, color_if_tty
+  };
+
+
 /* Write to standard output the string PREFIX followed by a space-separated
    list of the integers stored in OS all on one line.  */
 
@@ -619,29 +641,42 @@ dired_dump_obstack (const char *prefix, struct obstack *os)
     }
 }
 
-void
+int
 main (int argc, char **argv)
 {
   register int i;
   register struct pending *thispend;
 
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
   exit_status = 0;
   dir_defaulted = 1;
   print_dir_name = 1;
   pending_dirs = 0;
   current_time = time ((time_t *) 0);
 
-  program_name = argv[0];
   i = decode_switches (argc, argv);
 
   if (show_version)
     {
-      printf ("ls - %s\n", version_string);
-      exit (0);
+      printf ("%s (%s) %s\n",
+             (ls_mode == LS_LS ? "ls"
+              : (ls_mode == LS_MULTI_COL ? "dir" : "vdir")),
+             GNU_PACKAGE, VERSION);
+      exit (EXIT_SUCCESS);
     }
 
   if (show_help)
-    usage (0);
+    usage (EXIT_SUCCESS);
+
+  if (print_with_color)
+    {
+      parse_ls_color ();
+      prep_non_filename_text ();
+    }
 
   format_needs_stat = sort_type == sort_time || sort_type == sort_size
     || format == long_format
@@ -708,9 +743,19 @@ main (int argc, char **argv)
       dired_dump_obstack ("//SUBDIRED//", &subdired_obstack);
     }
 
+  if (fclose (stdout) == EOF)
+    error (EXIT_FAILURE, errno, _("write error"));
+
+  /* Restore default color before exiting */
+  if (print_with_color)
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
+
   exit (exit_status);
 }
-\f
+
 /* Set all the option flags according to the switches specified.
    Return the index of the first non-option argument.  */
 
@@ -720,6 +765,7 @@ decode_switches (int argc, char **argv)
   register char *p;
   int c;
   int i;
+  long int tmp_long;
 
   qmark_funny_chars = 0;
   quote_funny_chars = 0;
@@ -774,11 +820,22 @@ decode_switches (int argc, char **argv)
   really_all_files = 0;
   ignore_patterns = 0;
   quote_as_string = 0;
-  print_with_color = 0;
-  print_iso8859 = DEFAULT_ISO8859;
 
-  p = getenv ("COLUMNS");
-  line_length = p ? atoi (p) : 80;
+  line_length = 80;
+  if ((p = getenv ("COLUMNS")))
+    {
+      if (xstrtol (p, NULL, 0, &tmp_long, NULL) == LONGINT_OK
+         && 0 < tmp_long && tmp_long <= INT_MAX)
+       {
+         line_length = (int) tmp_long;
+       }
+      else
+       {
+         error (0, 0,
+              _("ignoring invalid width in environment variable COLUMNS: %s"),
+                p);
+       }
+    }
 
 #ifdef TIOCGWINSZ
   {
@@ -789,21 +846,27 @@ decode_switches (int argc, char **argv)
   }
 #endif
 
-  /* FIXME: reference TABSIZE iff !posix_pedantic.  */
-  p = getenv ("TABSIZE");
-  /* FIXME: use strtol here!  */
-  tabsize = p ? atoi (p) : 8;
-  if (tabsize < 1)
+  /* Using the TABSIZE environment variable is not POSIX-approved.
+     Ignore it when POSIXLY_CORRECT is set.  */
+  tabsize = 8;
+  if (!getenv ("POSIXLY_CORRECT") && (p = getenv ("TABSIZE")))
     {
-      error (0, 0,
-            "ignoring invalid tab size in enironment variable TABSIZE: %s",
-            optarg);
-      tabsize = 8;
+      if (xstrtol (p, NULL, 0, &tmp_long, NULL) == LONGINT_OK
+         && 0 <= tmp_long && tmp_long <= INT_MAX)
+       {
+         tabsize = (int) tmp_long;
+       }
+      else
+       {
+         error (0, 0,
+          _("ignoring invalid tab size in environment variable TABSIZE: %s"),
+                p);
+       }
     }
 
   while ((c = getopt_long (argc, argv,
-                          "abcdfgiklmnopqrstuw:xABCDFGI:LNQRST:UX178",
-                          long_options, (int *) 0)) != EOF)
+                          "abcdfgiklmnopqrstuw:xABCDFGI:LNQRST:UX1",
+                          long_options, NULL)) != -1)
     {
       switch (c)
        {
@@ -838,6 +901,7 @@ decode_switches (int argc, char **argv)
          if (format == long_format)
            format = (isatty (1) ? many_per_line : one_per_line);
          print_block_size = 0; /* disable -s */
+         print_with_color = 0; /* disable --color */
          break;
 
        case 'g':
@@ -864,32 +928,9 @@ decode_switches (int argc, char **argv)
          numeric_users = 1;
          break;
 
-       case 'o':
-         if (optarg)
-           {
-             i = argmatch (optarg, color_args);
-             if (i < 0)
-               {
-                 invalid_arg ("colorization criterion", optarg, i);
-                 usage (1);
-               }
-             i = color_types[i];
-           }
-         else
-           i = color_yes;      /* -o or --color -> do colorize */
-
-         if (i == color_if_tty)
-           print_with_color = isatty (1) ? 1 : 0;
-         else
-           print_with_color = i;
-
-         if (print_with_color)
-           {
-             /* Don't use TAB characters in output.  Some terminal
-                emulators can't handle the combination of tabs and
-                color codes on the same line.  */
-             tabsize = line_length;
-           }
+       case 'o':  /* Just like -l, but don't display group info.  */
+         format = long_format;
+         inhibit_group = 1;
          break;
 
        case 'p':
@@ -918,9 +959,10 @@ decode_switches (int argc, char **argv)
          break;
 
        case 'w':
-         line_length = atoi (optarg);
-         if (line_length < 1)
-           error (1, 0, "invalid line width: %s", optarg);
+         if (xstrtol (optarg, NULL, 0, &tmp_long, NULL) != LONGINT_OK
+             || tmp_long <= 0 || tmp_long > INT_MAX)
+           error (EXIT_FAILURE, 0, _("invalid line width: %s"), optarg);
+         line_length = (int) tmp_long;
          break;
 
        case 'x':
@@ -928,6 +970,7 @@ decode_switches (int argc, char **argv)
          break;
 
        case 'A':
+         really_all_files = 0;
          all_files = 1;
          break;
 
@@ -980,9 +1023,10 @@ decode_switches (int argc, char **argv)
          break;
 
        case 'T':
-         tabsize = atoi (optarg);
-         if (tabsize < 1)
-           error (1, 0, "invalid tab size: %s", optarg);
+         if (xstrtol (optarg, NULL, 0, &tmp_long, NULL) != LONGINT_OK
+             || tmp_long < 0 || tmp_long > INT_MAX)
+           error (EXIT_FAILURE, 0, _("invalid tab size: %s"), optarg);
+         tabsize = (int) tmp_long;
          break;
 
        case 'U':
@@ -997,20 +1041,12 @@ decode_switches (int argc, char **argv)
          format = one_per_line;
          break;
 
-       case '7':
-         print_iso8859 = 0;
-         break;
-
-       case '8':
-         print_iso8859 = 1;
-         break;
-
        case 10:                /* --sort */
          i = argmatch (optarg, sort_args);
          if (i < 0)
            {
-             invalid_arg ("sort type", optarg, i);
-             usage (1);
+             invalid_arg (_("sort type"), optarg, i);
+             usage (EXIT_FAILURE);
            }
          sort_type = sort_types[i];
          break;
@@ -1019,8 +1055,8 @@ decode_switches (int argc, char **argv)
          i = argmatch (optarg, time_args);
          if (i < 0)
            {
-             invalid_arg ("time type", optarg, i);
-             usage (1);
+             invalid_arg (_("time type"), optarg, i);
+             usage (EXIT_FAILURE);
            }
          time_type = time_types[i];
          break;
@@ -1029,270 +1065,379 @@ decode_switches (int argc, char **argv)
          i = argmatch (optarg, format_args);
          if (i < 0)
            {
-             invalid_arg ("format type", optarg, i);
-             usage (1);
+             invalid_arg (_("format type"), optarg, i);
+             usage (EXIT_FAILURE);
            }
          format = formats[i];
          break;
 
+       case 13:                /* --color */
+         if (optarg)
+           {
+             i = argmatch (optarg, color_args);
+             if (i < 0)
+               {
+                 invalid_arg (_("colorization criterion"), optarg, i);
+                 usage (EXIT_FAILURE);
+               }
+             i = color_types[i];
+           }
+         else
+           {
+             /* Using --color with no argument is equivalent to using
+                --color=always.  */
+             i = color_always;
+           }
+
+         print_with_color = (i == color_always
+                             || (i == color_if_tty
+                                 && isatty (STDOUT_FILENO)));
+
+         if (print_with_color)
+           {
+             /* Don't use TAB characters in output.  Some terminal
+                emulators can't handle the combination of tabs and
+                color codes on the same line.  */
+             tabsize = 0;
+           }
+         break;
+
        default:
-         usage (1);
+         usage (EXIT_FAILURE);
        }
     }
 
-  if (print_with_color)
+  return optind;
+}
+
+/* Parse a string as part of the LS_COLORS variable; this may involve
+   decoding all kinds of escape characters.  If equals_end is set an
+   unescaped equal sign ends the string, otherwise only a : or \0
+   does.  Returns the number of characters output, or -1 on failure.
+
+   The resulting string is *not* null-terminated, but may contain
+   embedded nulls.
+
+   Note that both dest and src are char **; on return they point to
+   the first free byte after the array and the character that ended
+   the input string, respectively.  */
+
+static int
+get_funky_string (char **dest, const char **src, int equals_end)
+{
+  int num;                     /* For numerical codes */
+  int count;                   /* Something to count with */
+  enum {
+    ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR
+  } state;
+  const char *p;
+  char *q;
+
+  p = *src;                    /* We don't want to double-indirect */
+  q = *dest;                   /* the whole darn time.  */
+
+  count = 0;                   /* No characters counted in yet.  */
+  num = 0;
+
+  state = ST_GND;              /* Start in ground state.  */
+  while (state < ST_END)
     {
-      parse_ls_color ();
+      switch (state)
+       {
+       case ST_GND:            /* Ground state (no escapes) */
+         switch (*p)
+           {
+           case ':':
+           case '\0':
+             state = ST_END;   /* End of string */
+             break;
+           case '\\':
+             state = ST_BACKSLASH; /* Backslash scape sequence */
+             ++p;
+             break;
+           case '^':
+             state = ST_CARET; /* Caret escape */
+             ++p;
+             break;
+           case '=':
+             if (equals_end)
+               {
+                 state = ST_END; /* End */
+                 break;
+               }
+             /* else fall through */
+           default:
+             *(q++) = *(p++);
+             ++count;
+             break;
+           }
+         break;
+
+       case ST_BACKSLASH:      /* Backslash escaped character */
+         switch (*p)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+             state = ST_OCTAL; /* Octal sequence */
+             num = *p - '0';
+             break;
+           case 'x':
+           case 'X':
+             state = ST_HEX;   /* Hex sequence */
+             num = 0;
+             break;
+           case 'a':           /* Bell */
+             num = 7;          /* Not all C compilers know what \a means */
+             break;
+           case 'b':           /* Backspace */
+             num = '\b';
+             break;
+           case 'e':           /* Escape */
+             num = 27;
+             break;
+           case 'f':           /* Form feed */
+             num = '\f';
+             break;
+           case 'n':           /* Newline */
+             num = '\n';
+             break;
+           case 'r':           /* Carriage return */
+             num = '\r';
+             break;
+           case 't':           /* Tab */
+             num = '\t';
+             break;
+           case 'v':           /* Vtab */
+             num = '\v';
+             break;
+           case '?':           /* Delete */
+              num = 127;
+             break;
+           case '_':           /* Space */
+             num = ' ';
+             break;
+           case '\0':          /* End of string */
+             state = ST_ERROR; /* Error! */
+             break;
+           default:            /* Escaped character like \ ^ : = */
+             num = *p;
+             break;
+           }
+         if (state == ST_BACKSLASH)
+           {
+             *(q++) = num;
+             ++count;
+             state = ST_GND;
+           }
+         ++p;
+         break;
+
+       case ST_OCTAL:          /* Octal sequence */
+         if (*p < '0' || *p > '7')
+           {
+             *(q++) = num;
+             ++count;
+             state = ST_GND;
+           }
+         else
+           num = (num << 3) + (*(p++) - '0');
+         break;
+
+       case ST_HEX:            /* Hex sequence */
+         switch (*p)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+           case '8':
+           case '9':
+             num = (num << 4) + (*(p++) - '0');
+             break;
+           case 'a':
+           case 'b':
+           case 'c':
+           case 'd':
+           case 'e':
+           case 'f':
+             num = (num << 4) + (*(p++) - 'a') + 10;
+             break;
+           case 'A':
+           case 'B':
+           case 'C':
+           case 'D':
+           case 'E':
+           case 'F':
+             num = (num << 4) + (*(p++) - 'A') + 10;
+             break;
+           default:
+             *(q++) = num;
+             ++count;
+             state = ST_GND;
+             break;
+           }
+         break;
+
+       case ST_CARET:          /* Caret escape */
+         state = ST_GND;       /* Should be the next state... */
+         if (*p >= '@' && *p <= '~')
+           {
+             *(q++) = *(p++) & 037;
+             ++count;
+           }
+         else if ( *p == '?')
+           {
+             *(q++) = 127;
+             ++count;
+           }
+         else
+           state = ST_ERROR;
+         break;
+
+       default:
+         abort();
+       }
     }
 
-  return optind;
+  *dest = q;
+  *src = p;
+
+  return state == ST_ERROR ? -1 : count;
 }
-\f
-/* Parse the LS_COLORS/LS_COLOURS variable */
 
 static void
 parse_ls_color (void)
 {
-  register char *p;            /* Pointer to character being parsed */
-  char *whichvar;              /* LS_COLORS or LS_COLOURS? */
+  const char *p;               /* Pointer to character being parsed */
+  char *buf;                   /* color_buf buffer pointer */
   int state;                   /* State of parser */
   int ind_no;                  /* Indicator number */
-  int ccount;                  /* Character count */
-  int num;                     /* Escape char numeral */
-  char label[3] = "??";                /* Indicator label */
+  char label[3];               /* Indicator label */
+  struct col_ext_type *ext;    /* Extension we are working on */
+  struct col_ext_type *ext2;   /* Extra pointer */
+
+  if ((p = getenv ("LS_COLORS")) == NULL || *p == '\0')
+    return;
 
-  if ((p = getenv (whichvar = "LS_COLORS")) ||
-      (p = getenv (whichvar = "LS_COLOURS")))
+  ext = NULL;
+  strcpy (label, "??");
+
+  /* This is an overly conservative estimate, but any possible
+     LS_COLORS string will *not* generate a color_buf longer than
+     itself, so it is a safe way of allocating a buffer in
+     advance.  */
+  buf = color_buf = xstrdup (p);
+
+  state = 1;
+  while (state > 0)
     {
-      state = 1;
-      while (state > 0)
+      switch (state)
        {
-         switch (state)
+       case 1:         /* First label character */
+         switch (*p)
            {
-           case 1:             /* First label character */
-             if (*p)
-               {
-                 label[0] = *(p++);
-                 state = 2;
-               }
-             else
-               state = 0;      /* Done */
+           case ':':
+             ++p;
              break;
 
-           case 2:             /* Second label character */
-             if (*p)
-               {
-                 label[1] = *(p++);
-                 state = 3;
-               }
-             else
-               state = -1;     /* Error */
-             break;
+           case '*':
+             /* Allocate new extension block and add to head of
+                linked list (this way a later definition will
+                override an earlier one, which can be useful for
+                having terminal-specific defs override global).  */
 
-           case 3:             /* Should be equal sign */
-             if (*(p++) != '=')
-               state = -1;     /* Error state */
-             else
-               {
-                 ind_no = 0;
-                 state = -1;   /* In case we fail */
-                 while (indicator_name[ind_no] != NULL)
-                   {
-                     if (strcmp (label, indicator_name[ind_no]) == 0)
-                       {
-                         state = 4;    /* We found it */
-                         ccount = 0;   /* Nothing stored yet */
-                         break;
-                       }
-                     else
-                       ind_no++;
-                   }
-               }
-             break;
+             ext = (struct col_ext_type *)
+               xmalloc (sizeof (struct col_ext_type));
+             ext->next = col_ext_list;
+             col_ext_list = ext;
 
-           case 4:             /* Character to store */
-             switch (*p)
-               {
-               case ':':
-                 color_indicator[ind_no].len = ccount;
-                 state = 1;
-                 break;
-               case '\\':
-                 /* The escape sequence will always generate a character,
-                    so enter error state if the buffer is full */
-                 state = (ccount >= MAXCOLORLEN) ? -1 : 5;
-                 break;
-               case '^':
-                 /* Control character in the ^X notation */
-                 state = (ccount >= MAXCOLORLEN) ? -1 : 8;
-                 break;
-               case '\0':
-                 color_indicator[ind_no].len = ccount;
-                 state = 0;    /* Done */
-                 break;
-               default:
-                 if (ccount >= MAXCOLORLEN)
-                   state = -1; /* Too long */
-                 else
-                   color_indicator[ind_no].string[ccount++] = *p;
-               }
-             p++;
+             ++p;
+             ext->ext.string = buf;
+
+             state = (ext->ext.len =
+                      get_funky_string (&buf, &p, 1)) < 0 ? -1 : 4;
              break;
 
-           case 5:             /* Escape character */
-             num = -1;
-             switch (*p)
-               {
-               case '0':
-               case '1':
-               case '2':
-               case '3':
-               case '4':
-               case '5':
-               case '6':
-               case '7':
-                 state = 6;    /* Octal numeral */
-                 num = *p - '0';
-                 break;
-               case 'x':
-               case 'X':
-                 state = 7;    /* Hex numeral */
-                 num = 0;
-                 break;
-               case 'a':       /* Bell */
-                 num = 7;
-                 break;
-               case 'b':       /* Backspace */
-                 num = '\b';
-                 break;
-               case 'e':       /* Escape */
-                 num = 27;
-                 break;
-               case 'f':       /* Formfeed */
-                 num = '\f';
-                 break;
-               case 'n':       /* Newline */
-                 num = '\n';
-                 break;
-               case 'r':       /* Return */
-                 num = '\r';
-                 break;
-               case 't':       /* Tab */
-                 num = '\t';
-                 break;
-               case 'v':       /* Vtab */
-                 num = '\v';
-                 break;
-               case '?':       /* Delete */
-                 num = 127;
-                 break;
-               case '\0':      /* End of string */
-                 state = -1;   /* Error */
-                 break;
-               default:        /* Escaped character */
-                 num = *p;
-                 break;
-               }
-             if (state == 5)
-               {
-                 color_indicator[ind_no].string[ccount++] = num;
-                 state = 4;
-               }
-             p++;
+           case '\0':
+             state = 0;        /* Done! */
              break;
 
-           case 6:             /* Octal numeral */
-             switch (*p)
-               {
-               case ':':
-               case '\0':
-                 color_indicator[ind_no].string[ccount++] = num;
-                 color_indicator[ind_no].len = ccount;
-                 state = (*(p++) == ':') ? 1 : 0;
-                 p++;
-                 break;
-               case '0':
-               case '1':
-               case '2':
-               case '3':
-               case '4':
-               case '5':
-               case '6':
-               case '7':
-                 num = (num << 3) + (*(p++) - '0');
-                 break;
-               default:
-                 color_indicator[ind_no].string[ccount++] = num;
-                 state = 4;
-                 break;
-               }
+           default:    /* Assume it is file type label */
+             label[0] = *(p++);
+             state = 2;
              break;
+           }
+         break;
+
+       case 2:         /* Second label character */
+         if (*p)
+           {
+             label[1] = *(p++);
+             state = 3;
+           }
+         else
+           state = -1; /* Error */
+         break;
 
-           case 7:             /* Hex numeral */
-             switch (*p)
+       case 3:         /* Equal sign after indicator label */
+         state = -1;   /* Assume failure... */
+         if (*(p++) == '=')/* It *should* be... */
+           {
+             for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no)
                {
-               case ':':
-               case '\0':
-                 color_indicator[ind_no].string[ccount++] = num;
-                 color_indicator[ind_no].len = ccount;
-                 state = (*(p++) == ':') ? 1 : 0;
-                 break;
-               case '0':
-               case '1':
-               case '2':
-               case '3':
-               case '4':
-               case '5':
-               case '6':
-               case '7':
-               case '8':
-               case '9':
-                 num = (num << 4) + (*(p++) - '0');
-                 break;
-               case 'A':
-               case 'B':
-               case 'C':
-               case 'D':
-               case 'E':
-               case 'F':
-                 num = (num << 4) + (*(p++) - 'A' + 10);
-                 break;
-               case 'a':
-               case 'b':
-               case 'c':
-               case 'd':
-               case 'e':
-               case 'f':
-                 num = (num << 4) + (*(p++) - 'a' + 10);
-                 break;
-               default:
-                 color_indicator[ind_no].string[ccount++] = num;
-                 state = 4;
-                 break;
+                 if (STREQ (label, indicator_name[ind_no]))
+                   {
+                     color_indicator[ind_no].string = buf;
+                     state = ((color_indicator[ind_no].len =
+                               get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1);
+                     break;
+                   }
                }
-             break;
+             if (state == -1)
+               error (0, 0, _("unrecognized prefix: %s"), label);
+           }
+        break;
 
-           case 8:             /* ^ notation */
-             state = 4;        /* Usually the next state */
-             if (*p >= '@' && *p <= '~')
-               color_indicator[ind_no].string[ccount++] = *p & 037;
-             else if (*p == '?')
-               color_indicator[ind_no].string[ccount++] = 127;
-             else
-               state = -1;     /* Error */
-             p++;
-             break;
+       case 4:         /* Equal sign after *.ext */
+         if (*(p++) == '=')
+           {
+             ext->seq.string = buf;
+             state = (ext->seq.len =
+                      get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1;
            }
+         else
+           state = -1;
+         break;
        }
+    }
 
-      if (state < 0)
+  if (state < 0)
+    {
+      struct col_ext_type *e;
+
+      error (0, 0,
+            _("unparsable value for LS_COLORS environment variable"));
+      free (color_buf);
+      for (e = col_ext_list; e != NULL ; /* empty */)
        {
-         error (0, 0, "bad %s variable\n", whichvar);
-         print_with_color = 0;
+         ext2 = e;
+         e = e->next;
+         free (ext2);
        }
+      print_with_color = 0;
     }
 }
-\f
+
 /* Request that the directory named `name' have its contents listed later.
    If `realname' is nonzero, it will be used instead of `name' when the
    directory name is printed.  This allows symbolic links to directories
@@ -1300,7 +1445,7 @@ parse_ls_color (void)
    real names. */
 
 static void
-queue_directory (char *name, char *realname)
+queue_directory (const char *name, const char *realname)
 {
   struct pending *new;
 
@@ -1386,12 +1531,12 @@ print_dir (const char *name, const char *realname)
   if (pending_dirs)
     PUTCHAR ('\n');
 }
-\f
+
 /* Add `pattern' to the list of patterns for which files that match are
    not listed.  */
 
 static void
-add_ignore_pattern (char *pattern)
+add_ignore_pattern (const char *pattern)
 {
   register struct ignore_pattern *ignore;
 
@@ -1405,7 +1550,7 @@ add_ignore_pattern (char *pattern)
 /* Return nonzero if the file in `next' should be listed. */
 
 static int
-file_interesting (register struct dirent *next)
+file_interesting (const struct dirent *next)
 {
   register struct ignore_pattern *ignore;
 
@@ -1422,7 +1567,7 @@ file_interesting (register struct dirent *next)
 
   return 0;
 }
-\f
+
 /* Enter and remove entries in the table `files'.  */
 
 /* Empty the table of files. */
@@ -1462,6 +1607,7 @@ gobble_file (const char *name, int explicit_arg, const char *dirname)
 
   files[files_index].linkname = 0;
   files[files_index].linkmode = 0;
+  files[files_index].linkok = 0;
 
   if (explicit_arg || format_needs_stat)
     {
@@ -1494,7 +1640,7 @@ gobble_file (const char *name, int explicit_arg, const char *dirname)
 
 #ifdef S_ISLNK
       if (S_ISLNK (files[files_index].stat.st_mode)
-         && (explicit_arg || format == long_format))
+         && (explicit_arg || format == long_format || print_with_color))
        {
          char *linkpath;
          struct stat linkstats;
@@ -1506,9 +1652,12 @@ gobble_file (const char *name, int explicit_arg, const char *dirname)
             they won't be traced and when no indicator is needed. */
          if (linkpath
              && ((explicit_arg && format != long_format)
-                 || indicator_style != none || print_with_color)
+                 || indicator_style != none
+                 || print_with_color)
              && stat (linkpath, &linkstats) == 0)
            {
+             files[files_index].linkok = 1;
+
              /* Symbolic links to directories that are mentioned on the
                 command line are automatically traced if not being
                 listed as files.  */
@@ -1527,9 +1676,12 @@ gobble_file (const char *name, int explicit_arg, const char *dirname)
                  files[files_index].stat = linkstats;
                }
              else
-               /* Get the linked-to file's mode for the filetype indicator
-                  in long listings.  */
-               files[files_index].linkmode = linkstats.st_mode;
+               {
+                 /* Get the linked-to file's mode for the filetype indicator
+                    in long listings.  */
+                 files[files_index].linkmode = linkstats.st_mode;
+                 files[files_index].linkok = 1;
+               }
            }
          if (linkpath)
            free (linkpath);
@@ -1575,7 +1727,7 @@ gobble_file (const char *name, int explicit_arg, const char *dirname)
    into the `linkname' field of `f'. */
 
 static void
-get_link_name (char *filename, struct fileinfo *f)
+get_link_name (const char *filename, struct fileinfo *f)
 {
   char *linkbuf;
   register int linksize;
@@ -1602,7 +1754,7 @@ get_link_name (char *filename, struct fileinfo *f)
    If `linkname' is zero, return zero. */
 
 static char *
-make_link_path (char *path, char *linkname)
+make_link_path (const char *path, const char *linkname)
 {
   char *linkbuf;
   int bufsiz;
@@ -1671,12 +1823,12 @@ extract_dirs_from_files (const char *dirname, int recursive)
       files[j++] = files[i];
   files_index = j;
 }
-\f
-/* Return non-zero if `name' doesn't end in `.' or `..'
+
+/* Return nonzero if `name' doesn't end in `.' or `..'
    This is so we don't try to recurse on `././././. ...' */
 
 static int
-is_not_dot_or_dotdot (char *name)
+is_not_dot_or_dotdot (const char *name)
 {
   char *t;
 
@@ -1691,7 +1843,7 @@ is_not_dot_or_dotdot (char *name)
 
   return 1;
 }
-\f
+
 /* Sort the files now in the table.  */
 
 static void
@@ -1738,61 +1890,61 @@ sort_files (void)
 /* Comparison routines for sorting the files. */
 
 static int
-compare_ctime (struct fileinfo *file1, struct fileinfo *file2)
+compare_ctime (const struct fileinfo *file1, const struct fileinfo *file2)
 {
   return longdiff (file2->stat.st_ctime, file1->stat.st_ctime);
 }
 
 static int
-rev_cmp_ctime (struct fileinfo *file2, struct fileinfo *file1)
+rev_cmp_ctime (const struct fileinfo *file2, const struct fileinfo *file1)
 {
   return longdiff (file2->stat.st_ctime, file1->stat.st_ctime);
 }
 
 static int
-compare_mtime (struct fileinfo *file1, struct fileinfo *file2)
+compare_mtime (const struct fileinfo *file1, const struct fileinfo *file2)
 {
   return longdiff (file2->stat.st_mtime, file1->stat.st_mtime);
 }
 
 static int
-rev_cmp_mtime (struct fileinfo *file2, struct fileinfo *file1)
+rev_cmp_mtime (const struct fileinfo *file2, const struct fileinfo *file1)
 {
   return longdiff (file2->stat.st_mtime, file1->stat.st_mtime);
 }
 
 static int
-compare_atime (struct fileinfo *file1, struct fileinfo *file2)
+compare_atime (const struct fileinfo *file1, const struct fileinfo *file2)
 {
   return longdiff (file2->stat.st_atime, file1->stat.st_atime);
 }
 
 static int
-rev_cmp_atime (struct fileinfo *file2, struct fileinfo *file1)
+rev_cmp_atime (const struct fileinfo *file2, const struct fileinfo *file1)
 {
   return longdiff (file2->stat.st_atime, file1->stat.st_atime);
 }
 
 static int
-compare_size (struct fileinfo *file1, struct fileinfo *file2)
+compare_size (const struct fileinfo *file1, const struct fileinfo *file2)
 {
   return longdiff (file2->stat.st_size, file1->stat.st_size);
 }
 
 static int
-rev_cmp_size (struct fileinfo *file2, struct fileinfo *file1)
+rev_cmp_size (const struct fileinfo *file2, const struct fileinfo *file1)
 {
   return longdiff (file2->stat.st_size, file1->stat.st_size);
 }
 
 static int
-compare_name (struct fileinfo *file1, struct fileinfo *file2)
+compare_name (const struct fileinfo *file1, const struct fileinfo *file2)
 {
   return strcmp (file1->name, file2->name);
 }
 
 static int
-rev_cmp_name (struct fileinfo *file2, struct fileinfo *file1)
+rev_cmp_name (const struct fileinfo *file2, const struct fileinfo *file1)
 {
   return strcmp (file1->name, file2->name);
 }
@@ -1801,7 +1953,7 @@ rev_cmp_name (struct fileinfo *file2, struct fileinfo *file1)
    If extensions are the same, compare by filenames instead. */
 
 static int
-compare_extension (struct fileinfo *file1, struct fileinfo *file2)
+compare_extension (const struct fileinfo *file1, const struct fileinfo *file2)
 {
   register char *base1, *base2;
   register int cmp;
@@ -1821,7 +1973,7 @@ compare_extension (struct fileinfo *file1, struct fileinfo *file2)
 }
 
 static int
-rev_cmp_extension (struct fileinfo *file2, struct fileinfo *file1)
+rev_cmp_extension (const struct fileinfo *file2, const struct fileinfo *file1)
 {
   register char *base1, *base2;
   register int cmp;
@@ -1839,7 +1991,7 @@ rev_cmp_extension (struct fileinfo *file2, struct fileinfo *file1)
     return strcmp (file1->name, file2->name);
   return cmp;
 }
-\f
+
 /* List all the files now in the table.  */
 
 static void
@@ -1880,21 +2032,29 @@ print_current_files (void)
 }
 
 static void
-print_long_format (struct fileinfo *f)
+print_long_format (const struct fileinfo *f)
 {
-  char modebuf[20];
-  char timebuf[40];
-
-  /* 7 fields that may (worst case be 64-bit integral values) require 20 bytes,
-     10 character mode field,
-     24 characters for the time,
-     9 spaces following each of these fields,
-     and 1 trailing NUL byte.  */
-  char bigbuf[7 * 20 + 10 + 24 + 9 + 1];
+  char modebuf[11];
+
+  /* 7 fields that may (worst case: 64-bit integral values) require 20 bytes,
+     1 10-byte mode string,
+     1 24-byte time string (may be longer in some locales -- see below),
+     9 spaces, one following each of these fields, and
+     1 trailing NUL byte.  */
+  char init_bigbuf[7 * 20 + 10 + 24 + 9 + 1];
+  char *buf = init_bigbuf;
+  size_t bufsize = sizeof (init_bigbuf);
+  size_t s;
   char *p;
   time_t when;
+  const char *fmt;
 
+#ifdef HAVE_ST_DM_MODE
+  mode_string (f->stat.st_dm_mode, modebuf);
+#else
   mode_string (f->stat.st_mode, modebuf);
+#endif
+
   modebuf[10] = '\0';
 
   switch (time_type)
@@ -1910,10 +2070,10 @@ print_long_format (struct fileinfo *f)
       break;
     }
 
-  strcpy (timebuf, ctime (&when));
-
   if (full_time)
-    timebuf[24] = '\0';
+    {
+      fmt = "%a %b %d %H:%M:%S %Y";
+    }
   else
     {
       if (current_time > when + 6L * 30L * 24L * 60L * 60L     /* Old. */
@@ -1925,12 +2085,15 @@ print_long_format (struct fileinfo *f)
             Allow a 1 hour slop factor for what is considered "the future",
             to allow for NFS server/client clock disagreement.
             Show the year instead of the time of day.  */
-         strcpy (timebuf + 11, timebuf + 19);
+         fmt = "%b %e  %Y";
+       }
+      else
+       {
+         fmt = "%b %e %H:%M";
        }
-      timebuf[16] = 0;
     }
 
-  p = bigbuf;
+  p = buf;
 
   if (print_inode)
     {
@@ -1973,17 +2136,27 @@ print_long_format (struct fileinfo *f)
     sprintf (p, "%8lu ", (unsigned long) f->stat.st_size);
   p += strlen (p);
 
-  sprintf (p, "%s ", full_time ? timebuf : timebuf + 4);
-  p += strlen (p);
+  /* Use strftime rather than ctime, because the former can produce
+     locale-dependent names for the weekday (%a) and month (%b).  */
+
+  while (! (s = strftime (p, buf + bufsize - p - 1, fmt, localtime (&when))))
+    {
+      char *newbuf = (char *) alloca (bufsize *= 2);
+      memcpy (newbuf, buf, p - buf);
+      p = newbuf + (p - buf);
+      buf = newbuf;
+    }
+
+  p += s;
+  *p++ = ' ';
+
+  /* NUL-terminate the string -- fputs (via FPUTS) requires it.  */
+  *p = '\0';
 
   DIRED_INDENT ();
-  FPUTS (bigbuf, stdout, p - bigbuf);
+  FPUTS (buf, stdout, p - buf);
   PUSH_CURRENT_DIRED_POS (&dired_obstack);
-  if (print_with_color)
-    print_color_indicator (f->stat.st_mode);
-  print_name_with_quoting (f->name);
-  if (print_with_color)
-    put_indicator (C_END);
+  print_name_with_quoting (f->name, f->stat.st_mode, f->linkok);
   PUSH_CURRENT_DIRED_POS (&dired_obstack);
 
   if (f->filetype == symbolic_link)
@@ -1991,11 +2164,7 @@ print_long_format (struct fileinfo *f)
       if (f->linkname)
        {
          FPUTS_LITERAL (" -> ", stdout);
-         if (print_with_color)
-           print_color_indicator (f->linkmode);
-         print_name_with_quoting (f->linkname);
-         if (print_with_color)
-           put_indicator (C_END);
+         print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1);
          if (indicator_style != none)
            print_type_indicator (f->linkmode);
        }
@@ -2003,7 +2172,7 @@ print_long_format (struct fileinfo *f)
   else if (indicator_style != none)
     print_type_indicator (f->stat.st_mode);
 }
-\f
+
 /* Set QUOTED_LENGTH to strlen(P) and return NULL if P == quoted(P).
    Otherwise, return xmalloc'd storage containing the quoted version
    of P and set QUOTED_LENGTH to the length of the quoted P.  */
@@ -2041,18 +2210,14 @@ quote_filename (register const char *p, size_t *quoted_length)
              break;
 
            default:
-             /* FIXME: why not just use the ISPRINT macro here?  */
-             if (!((c > 040 && c < 0177)
-                   || (print_iso8859 && c >= 0200 && c <= 0377)))
+             if (!ISGRAPH (c))
                found_quotable = 1;
              break;
            }
        }
       else
        {
-         if (!((c >= 040 && c < 0177)
-               || (print_iso8859 && c >= 0xA1 && c <= 0xFF))
-             && qmark_funny_chars)
+         if (!ISPRINT (c) && qmark_funny_chars)
            found_quotable = 1;
        }
       if (found_quotable)
@@ -2116,8 +2281,7 @@ quote_filename (register const char *p, size_t *quoted_length)
              break;
 
            default:
-             if ((c > 040 && c < 0177)
-                 || (print_iso8859 && c >= 0200 && c <= 0377))
+             if (ISGRAPH (c))
                SAVECHAR (c);
              else
                {
@@ -2129,8 +2293,7 @@ quote_filename (register const char *p, size_t *quoted_length)
        }
       else
        {
-         if ((c >= 040 && c < 0177)
-             || (print_iso8859 && c >= 0200 && c <= 0377))
+         if (ISPRINT (c))
            SAVECHAR (c);
          else if (!qmark_funny_chars)
            SAVECHAR (c);
@@ -2150,23 +2313,42 @@ quote_filename (register const char *p, size_t *quoted_length)
 }
 
 static void
-print_name_with_quoting (register char *p)
+print_name_with_quoting (const char *p, unsigned int mode, int linkok)
 {
   char *quoted;
   size_t quoted_length;
 
+  if (print_with_color)
+    print_color_indicator (p, mode, linkok);
+
   quoted = quote_filename (p, &quoted_length);
   FPUTS (quoted != NULL ? quoted : p, stdout, quoted_length);
   if (quoted)
     free (quoted);
+
+  if (print_with_color)
+    prep_non_filename_text ();
 }
-\f
+
+static void
+prep_non_filename_text (void)
+{
+  if (color_indicator[C_END].string != NULL)
+    put_indicator (&color_indicator[C_END]);
+  else
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_NORM]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
+}
+
 /* Print the file name of `f' with appropriate quoting.
    Also print file size, inode number, and filetype indicator character,
    as requested by switches.  */
 
 static void
-print_file_name_and_frills (struct fileinfo *f)
+print_file_name_and_frills (const struct fileinfo *f)
 {
   if (print_inode)
     printf ("%*lu ", INODE_DIGITS, (unsigned long) f->stat.st_ino);
@@ -2176,11 +2358,7 @@ print_file_name_and_frills (struct fileinfo *f)
            (unsigned) convert_blocks (ST_NBLOCKS (f->stat),
                                       kilobyte_blocks));
 
-  if (print_with_color)
-    print_color_indicator (f->stat.st_mode);
-  print_name_with_quoting (f->name);
-  if (print_with_color)
-    put_indicator (C_END);
+  print_name_with_quoting (f->name, f->stat.st_mode, f->linkok);
 
   if (indicator_style != none)
     print_type_indicator (f->stat.st_mode);
@@ -2208,68 +2386,96 @@ print_type_indicator (unsigned int mode)
 #endif
 
   if (S_ISREG (mode) && indicator_style == all
-      && (mode & (S_IEXEC | S_IXGRP | S_IXOTH)))
+      && (mode & S_IXUGO))
     PUTCHAR ('*');
 }
 
 static void
-print_color_indicator (unsigned int mode)
+print_color_indicator (const char *name, unsigned int mode, int linkok)
 {
   int type = C_FILE;
+  struct col_ext_type *ext;    /* Color extension */
+  size_t len;                  /* Length of name */
 
-  if (S_ISDIR (mode))
-    type = C_DIR;
+  /* Is this a nonexistent file?  If so, linkok == -1.  */
+
+  if (linkok == -1 && color_indicator[C_MISSING].string != NULL)
+    {
+      ext = NULL;
+      type = C_MISSING;
+    }
+  else
+    {
+      if (S_ISDIR (mode))
+       type = C_DIR;
 
 #ifdef S_ISLNK
-  else if (S_ISLNK (mode))
-    type = C_LINK;
+      else if (S_ISLNK (mode))
+       type = ((!linkok && color_indicator[C_ORPHAN].string)
+               ? C_ORPHAN : C_LINK);
 #endif
 
 #ifdef S_ISFIFO
-  else if (S_ISFIFO (mode))
-    type = C_FIFO;
+      else if (S_ISFIFO (mode))
+       type = C_FIFO;
 #endif
 
 #ifdef S_ISSOCK
-  else if (S_ISSOCK (mode))
-    type = C_SOCK;
+      else if (S_ISSOCK (mode))
+       type = C_SOCK;
 #endif
 
 #ifdef S_ISBLK
-  else if (S_ISBLK (mode))
-    type = C_BLK;
+      else if (S_ISBLK (mode))
+       type = C_BLK;
 #endif
 
 #ifdef S_ISCHR
-  else if (S_ISCHR (mode))
-    type = C_CHR;
+      else if (S_ISCHR (mode))
+       type = C_CHR;
 #endif
 
-  if (type == C_FILE && (mode & (S_IEXEC | S_IEXEC >> 3 | S_IEXEC >> 6)))
-    type = C_EXEC;
+      if (type == C_FILE && (mode & S_IXUGO) != 0)
+       type = C_EXEC;
 
-  put_indicator (C_LEFT);
-  put_indicator (type);
-  put_indicator (C_RIGHT);
+      /* Check the file's suffix only if still classified as C_FILE.  */
+      ext = NULL;
+      if (type == C_FILE)
+       {
+         /* Test if NAME has a recognized suffix.  */
+
+         len = strlen (name);
+         name += len;          /* Pointer to final \0.  */
+         for (ext = col_ext_list; ext != NULL; ext = ext->next)
+           {
+             if (ext->ext.len <= len
+                 && strncmp (name - ext->ext.len, ext->ext.string,
+                             ext->ext.len) == 0)
+               break;
+           }
+       }
+    }
+
+  put_indicator (&color_indicator[C_LEFT]);
+  put_indicator (ext ? &(ext->seq) : &color_indicator[type]);
+  put_indicator (&color_indicator[C_RIGHT]);
 }
 
-/* Output a color indicator (which may contain nulls) */
+/* Output a color indicator (which may contain nulls) */
 static void
-put_indicator (int n)
+put_indicator (const struct bin_str *ind)
 {
   register int i;
   register char *p;
 
-  p = color_indicator[n].string;
+  p = ind->string;
 
-  for (i = color_indicator[n].len; i; i--)
-    {
-      putchar (*(p++));
-    }
+  for (i = ind->len; i > 0; --i)
+    putchar (*(p++));
 }
 
 static int
-length_of_file_name_and_frills (struct fileinfo *f)
+length_of_file_name_and_frills (const struct fileinfo *f)
 {
   register char *p = f->name;
   register unsigned char c;
@@ -2308,8 +2514,7 @@ length_of_file_name_and_frills (struct fileinfo *f)
              break;
 
            default:
-             if ((c >= 040 && c < 0177)
-                 || (print_iso8859 && c >= 0200 && c <= 0377))
+             if (ISPRINT (c))
                len += 1;
              else
                len += 4;
@@ -2326,7 +2531,7 @@ length_of_file_name_and_frills (struct fileinfo *f)
       if (S_ISREG (filetype))
        {
          if (indicator_style == all
-             && (f->stat.st_mode & (S_IEXEC | S_IEXEC >> 3 | S_IEXEC >> 6)))
+             && (f->stat.st_mode & S_IXUGO))
            len += 1;
        }
       else if (S_ISDIR (filetype)
@@ -2345,7 +2550,7 @@ length_of_file_name_and_frills (struct fileinfo *f)
 
   return len;
 }
-\f
+
 static void
 print_many_per_line (void)
 {
@@ -2399,7 +2604,7 @@ print_many_per_line (void)
       putchar ('\n');
     }
 }
-\f
+
 static void
 print_horizontal (void)
 {
@@ -2450,7 +2655,7 @@ print_horizontal (void)
     }
   putchar ('\n');
 }
-\f
+
 static void
 print_with_commas (void)
 {
@@ -2482,7 +2687,7 @@ print_with_commas (void)
     }
   putchar ('\n');
 }
-\f
+
 /* Assuming cursor is at position FROM, indent up to position TO.
    Use a TAB character instead of two or more spaces whenever possible.  */
 
@@ -2491,7 +2696,7 @@ indent (int from, int to)
 {
   while (from < to)
     {
-      if (to / tabsize > (from + 1) / tabsize)
+      if (tabsize > 0 && to / tabsize > (from + 1) / tabsize)
        {
          putchar ('\t');
          from += tabsize - from % tabsize;
@@ -2529,66 +2734,75 @@ static void
 usage (int status)
 {
   if (status != 0)
-    fprintf (stderr, "Try `%s --help' for more information.\n",
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
             program_name);
   else
     {
-      printf ("Usage: %s [OPTION]... [FILE]...\n", program_name);
-      printf ("\
+      printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
+      printf (_("\
 List information about the FILEs (the current directory by default).\n\
 Sort entries alphabetically if none of -cftuSUX nor --sort.\n\
 \n\
-  -A, --almost-all           do not list implied . and ..\n\
   -a, --all                  do not hide entries starting with .\n\
-  -B, --ignore-backups       do not list implied entries ending with ~\n\
+  -A, --almost-all           do not list implied . and ..\n\
   -b, --escape               print octal escapes for nongraphic characters\n\
-  -C                         list entries by columns\n\
+  -B, --ignore-backups       do not list implied entries ending with ~\n\
   -c                         sort by change time; with -l: show ctime\n\
-  -D, --dired                generate output well suited to Emacs' dired mode\n\
+  -C                         list entries by columns\n\
+      --color[=WHEN]         control whether color is used to distinguish file\n\
+                               types.  WHEN may be `never', `always', or `auto'\n\
   -d, --directory            list directory entries instead of contents\n\
-  -F, --classify             append a character for typing each entry\n\
+  -D, --dired                generate output designed for Emacs' dired mode\n\
   -f                         do not sort, enable -aU, disable -lst\n\
+  -F, --classify             append a character for typing each entry\n\
       --format=WORD          across -x, commas -m, horizontal -x, long -l,\n\
                                single-column -1, verbose -l, vertical -C\n\
-      --full-time            list both full date and full time\n");
+      --full-time            list both full date and full time\n"));
 
-      printf ("\
-  -G, --no-group             inhibit display of group information\n\
+      printf (_("\
   -g                         (ignored)\n\
-  -I, --ignore=PATTERN       do not list implied entries matching shell PATTERN\n\
+  -G, --no-group             inhibit display of group information\n\
   -i, --inode                print index number of each file\n\
+  -I, --ignore=PATTERN       do not list implied entries matching shell PATTERN\n\
   -k, --kilobytes            use 1024 blocks, not 512 despite POSIXLY_CORRECT\n\
-  -L, --dereference          list entries pointed to by symbolic links\n\
   -l                         use a long listing format\n\
+  -L, --dereference          list entries pointed to by symbolic links\n\
   -m                         fill width with a comma separated list of entries\n\
-  -N, --literal              do not quote entry names\n\
   -n, --numeric-uid-gid      list numeric UIDs and GIDs instead of names\n\
-  -o, --color, --colour      colorize entries according to type\n\
-      --colo(u)r=WORD        yes -o, no, tty (if output is a terminal)\n\
+  -N, --literal              print raw entry names (don't treat e.g. control\n\
+                               characters specially)\n\
+  -o                         use long listing format without group info\n\
   -p                         append a character for typing each entry\n\
-  -Q, --quote-name           enclose entry names in double quotes\n\
   -q, --hide-control-chars   print ? instead of non graphic characters\n\
-  -R, --recursive            list subdirectories recursively\n\
+  -Q, --quote-name           enclose entry names in double quotes\n\
   -r, --reverse              reverse order while sorting\n\
-  -S                         sort by file size\n");
+  -R, --recursive            list subdirectories recursively\n\
+  -s, --size                 print size of each file, in blocks\n"));
 
-      printf ("\
-  -s, --size                 print block size of each file\n\
+      printf (_("\
+  -S                         sort by file size\n\
       --sort=WORD            ctime -c, extension -X, none -U, size -S,\n\
-                               status -c, time -t\n\
-      --time=WORD            atime -u, access -u, use -u\n\
-  -T, --tabsize=COLS         assume tab stops at each COLS instead of 8\n\
+                             status -c, time -t, atime -u, access -u, use -u\n\
+      --time=WORD            show time as WORD instead of modification time:\n\
+                             atime, access, use, ctime or status\n\
   -t                         sort by modification time; with -l: show mtime\n\
-  -U                         do not sort; list entries in directory order\n\
+  -T, --tabsize=COLS         assume tab stops at each COLS instead of 8\n\
   -u                         sort by last access time; with -l: show atime\n\
+  -U                         do not sort; list entries in directory order\n\
   -w, --width=COLS           assume screen width instead of current value\n\
   -x                         list entries by lines instead of by columns\n\
   -X                         sort alphabetically by entry extension\n\
   -1                         list one file per line\n\
-  -7, --7bit                 allow only 7-bit ASCII characters to be printed\n\
-  -8, --8bit                 allow 8-bit ISO 8859 characters to be printed\n\
       --help                 display this help and exit\n\
-      --version              output version information and exit");
+      --version              output version information and exit\n\
+\n\
+By default, color is not used to distinguish types of files.  That is\n\
+equivalent to using --color=none.  Using the --color option without the\n\
+optional WHEN argument is equivalent to using --color=always.  With\n\
+--color=auto, color codes are output only if standard output is connected\n\
+to a terminal (tty).\n\
+"));
+      puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
     }
   exit (status);
 }