/* `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
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
-\f
-/* If the macro MULTI_COL is defined,
+
+/* 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
#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
/* 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"
#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
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
char *xrealloc ();
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 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_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;
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.
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. */
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. */
+
+static int print_with_color;
+
+enum color_type
+ {
+ color_never, /* 0: default or --color=never */
+ color_always, /* 1: --color=always */
+ color_if_tty /* 2: --color=tty */
+ };
+
+enum indicator_no
+ {
+ 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
+ };
+
+static const char *const indicator_name[]=
+ {
+ "lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so",
+ "bd", "cd", "mi", "or", "ex", NULL
+ };
+
+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 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 */
+ };
+
+/* FIXME: comment */
+struct col_ext_type *col_ext_list = NULL;
+
+/* Buffer for color sequences */
+static char *color_buf;
+
/* Nonzero means mention the inode number of each file. -i */
static int print_inode;
struct ignore_pattern
{
- char *pattern;
+ const char *pattern;
struct ignore_pattern *next;
};
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
{"time", required_argument, 0, 11},
{"help", no_argument, &show_help, 1},
{"version", no_argument, &show_version, 1},
- {0, 0, 0, 0}
+ {"color", optional_argument, 0, 13},
+ {NULL, 0, NULL, 0}
};
static char const *const format_args[] =
time_atime, time_atime, time_atime, time_ctime, time_ctime
};
-\f
+static char const *const color_args[] =
+ {
+ /* 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_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. */
if (show_version)
{
- printf ("ls - %s\n", PACKAGE_VERSION);
- 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
|| trace_links || trace_dirs || indicator_style != none
- || print_block_size || print_inode;
+ || print_block_size || print_inode || print_with_color;
if (dired && format == long_format)
{
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. */
else
{
error (0, 0,
- _("ignoring invalid width in enironment variable COLUMNS: %s"),
+ _("ignoring invalid width in environment variable COLUMNS: %s"),
p);
}
}
}
#endif
- /* TABSIZE is not POSIX-approved.
+ /* Using the TABSIZE environment variable is not POSIX-approved.
Ignore it when POSIXLY_CORRECT is set. */
tabsize = 8;
- if (getenv ("POSIXLY_CORRECT") == 0 && (p = getenv ("TABSIZE")))
+ if (!getenv ("POSIXLY_CORRECT") && (p = getenv ("TABSIZE")))
{
if (xstrtol (p, NULL, 0, &tmp_long, NULL) == LONGINT_OK
- && 0 < tmp_long && tmp_long <= INT_MAX)
+ && 0 <= tmp_long && tmp_long <= INT_MAX)
{
tabsize = (int) tmp_long;
}
else
{
error (0, 0,
- _("ignoring invalid tab size in enironment variable TABSIZE: %s"),
+ _("ignoring invalid tab size in environment variable TABSIZE: %s"),
p);
}
}
while ((c = getopt_long (argc, argv,
"abcdfgiklmnopqrstuw:xABCDFGI:LNQRST:UX1",
- long_options, (int *) 0)) != EOF)
+ long_options, NULL)) != -1)
{
switch (c)
{
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':
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':
break;
case 'A':
+ really_all_files = 0;
all_files = 1;
break;
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':
if (i < 0)
{
invalid_arg (_("sort type"), optarg, i);
- usage (1);
+ usage (EXIT_FAILURE);
}
sort_type = sort_types[i];
break;
if (i < 0)
{
invalid_arg (_("time type"), optarg, i);
- usage (1);
+ usage (EXIT_FAILURE);
}
time_type = time_types[i];
break;
if (i < 0)
{
invalid_arg (_("format type"), optarg, i);
- usage (1);
+ 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);
}
}
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)
+ {
+ 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();
+ }
+ }
+
+ *dest = q;
+ *src = p;
+
+ return state == ST_ERROR ? -1 : count;
+}
+
+static void
+parse_ls_color (void)
+{
+ const char *p; /* Pointer to character being parsed */
+ char *buf; /* color_buf buffer pointer */
+ int state; /* State of parser */
+ int ind_no; /* Indicator number */
+ 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;
+
+ 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)
+ {
+ switch (state)
+ {
+ case 1: /* First label character */
+ switch (*p)
+ {
+ case ':':
+ ++p;
+ 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). */
+
+ ext = (struct col_ext_type *)
+ xmalloc (sizeof (struct col_ext_type));
+ ext->next = col_ext_list;
+ col_ext_list = ext;
+
+ ++p;
+ ext->ext.string = buf;
+
+ state = (ext->ext.len =
+ get_funky_string (&buf, &p, 1)) < 0 ? -1 : 4;
+ break;
+
+ case '\0':
+ state = 0; /* Done! */
+ 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 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)
+ {
+ 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;
+ }
+ }
+ if (state == -1)
+ error (0, 0, _("unrecognized prefix: %s"), label);
+ }
+ 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)
+ {
+ 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 */)
+ {
+ ext2 = e;
+ e = e->next;
+ free (ext2);
+ }
+ print_with_color = 0;
+ }
+}
+
/* 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
real names. */
static void
-queue_directory (char *name, char *realname)
+queue_directory (const char *name, const char *realname)
{
struct pending *new;
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;
/* 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;
return 0;
}
-\f
+
/* Enter and remove entries in the table `files'. */
/* Empty the table of files. */
files[files_index].linkname = 0;
files[files_index].linkmode = 0;
+ files[files_index].linkok = 0;
if (explicit_arg || format_needs_stat)
{
#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;
they won't be traced and when no indicator is needed. */
if (linkpath
&& ((explicit_arg && format != long_format)
- || indicator_style != none)
+ || 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. */
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);
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;
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;
files[j++] = files[i];
files_index = j;
}
-\f
+
/* 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;
return 1;
}
-\f
+
/* Sort the files now in the table. */
static 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);
}
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;
}
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;
return strcmp (file1->name, file2->name);
return cmp;
}
-\f
+
/* List all the files now in the table. */
static void
}
static void
-print_long_format (struct fileinfo *f)
+print_long_format (const struct fileinfo *f)
{
- char modebuf[20];
- char timebuf[40];
+ char modebuf[11];
/* 7 fields that may (worst case: 64-bit integral values) require 20 bytes,
- 1 10-character mode string,
- 1 24-character time string,
- 9 spaces, one following each of these fields,
- and 1 trailing NUL byte. */
- char bigbuf[7 * 20 + 10 + 24 + 9 + 1];
+ 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)
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. */
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)
{
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);
- print_name_with_quoting (f->name);
+ print_name_with_quoting (f->name, f->stat.st_mode, f->linkok);
PUSH_CURRENT_DIRED_POS (&dired_obstack);
if (f->filetype == symbolic_link)
if (f->linkname)
{
FPUTS_LITERAL (" -> ", stdout);
- print_name_with_quoting (f->linkname);
+ print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1);
if (indicator_style != none)
print_type_indicator (f->linkmode);
}
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. */
}
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, "ed_length);
FPUTS (quoted != NULL ? quoted : p, stdout, quoted_length);
if (quoted)
free (quoted);
+
+ if (print_with_color)
+ prep_non_filename_text ();
+}
+
+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]);
+ }
}
-\f
+
/* 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);
(unsigned) convert_blocks (ST_NBLOCKS (f->stat),
kilobyte_blocks));
- print_name_with_quoting (f->name);
+ print_name_with_quoting (f->name, f->stat.st_mode, f->linkok);
if (indicator_style != none)
print_type_indicator (f->stat.st_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 (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 */
+
+ /* 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 = ((!linkok && color_indicator[C_ORPHAN].string)
+ ? C_ORPHAN : C_LINK);
+#endif
+
+#ifdef S_ISFIFO
+ else if (S_ISFIFO (mode))
+ type = C_FIFO;
+#endif
+
+#ifdef S_ISSOCK
+ else if (S_ISSOCK (mode))
+ type = C_SOCK;
+#endif
+
+#ifdef S_ISBLK
+ else if (S_ISBLK (mode))
+ type = C_BLK;
+#endif
+
+#ifdef S_ISCHR
+ else if (S_ISCHR (mode))
+ type = C_CHR;
+#endif
+
+ if (type == C_FILE && (mode & S_IXUGO) != 0)
+ type = C_EXEC;
+
+ /* 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). */
+static void
+put_indicator (const struct bin_str *ind)
+{
+ register int i;
+ register char *p;
+
+ p = ind->string;
+
+ 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;
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)
return len;
}
-\f
+
static void
print_many_per_line (void)
{
putchar ('\n');
}
}
-\f
+
static void
print_horizontal (void)
{
}
putchar ('\n');
}
-\f
+
static void
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. */
{
while (from < to)
{
- if (to / tabsize > (from + 1) / tabsize)
+ if (tabsize > 0 && to / tabsize > (from + 1) / tabsize)
{
putchar ('\t');
from += tabsize - from % tabsize;
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"));
printf (_("\
- -G, --no-group inhibit display of group information\n\
-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\
+ -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\
+ -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\
--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);
}