mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-04-16 00:36:08 +02:00
2604 lines
61 KiB
C
2604 lines
61 KiB
C
/* `dir', `vdir' and `ls' directory listing programs for GNU.
|
||
Copyright (C) 1985, 1988, 1990, 1991, 1995 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
|
||
the Free Software Foundation; either version 2, or (at your option)
|
||
any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
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. */
|
||
|
||
/* If the macro MULTI_COL is defined,
|
||
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,
|
||
the long format is the default regardless of the
|
||
type of output device.
|
||
This is for the `vdir' program.
|
||
|
||
If neither is defined,
|
||
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. */
|
||
|
||
#ifdef _AIX
|
||
#pragma alloca
|
||
#endif
|
||
|
||
#include <config.h>
|
||
#include <sys/types.h>
|
||
|
||
#if HAVE_SYS_IOCTL_H
|
||
# include <sys/ioctl.h>
|
||
#endif
|
||
|
||
#include <stdio.h>
|
||
#include <grp.h>
|
||
#include <pwd.h>
|
||
#include <getopt.h>
|
||
|
||
#if HAVE_LIMITS_H
|
||
/* 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>
|
||
#endif
|
||
|
||
#include "system.h"
|
||
#include <fnmatch.h>
|
||
|
||
#include "obstack.h"
|
||
#include "ls.h"
|
||
#include "error.h"
|
||
#include "argmatch.h"
|
||
|
||
#define obstack_chunk_alloc xmalloc
|
||
#define obstack_chunk_free free
|
||
|
||
#ifndef INT_MAX
|
||
#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)
|
||
#else
|
||
#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
|
||
#endif
|
||
|
||
enum filetype
|
||
{
|
||
symbolic_link,
|
||
directory,
|
||
arg_directory, /* Directory given as command line arg. */
|
||
normal /* All others. */
|
||
};
|
||
|
||
struct fileinfo
|
||
{
|
||
/* The file name. */
|
||
char *name;
|
||
|
||
struct stat stat;
|
||
|
||
/* For symbolic link, name of the file linked to, otherwise zero. */
|
||
char *linkname;
|
||
|
||
/* For symbolic link and long listing, st_mode of file linked to, otherwise
|
||
zero. */
|
||
unsigned int linkmode;
|
||
|
||
enum filetype filetype;
|
||
};
|
||
|
||
#ifndef STDC_HEADERS
|
||
char *ctime ();
|
||
time_t time ();
|
||
void free ();
|
||
#endif
|
||
|
||
void mode_string ();
|
||
|
||
char *stpcpy ();
|
||
char *xstrdup ();
|
||
char *getgroup ();
|
||
char *getuser ();
|
||
char *xmalloc ();
|
||
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 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 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 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 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_horizontal __P ((void));
|
||
static void print_long_format __P ((struct fileinfo *f));
|
||
static void print_many_per_line __P ((void));
|
||
static void print_name_with_quoting __P ((register char *p));
|
||
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 sort_files __P ((void));
|
||
static void usage __P ((int status));
|
||
|
||
/* The name the program was run with, stripped of any leading path. */
|
||
char *program_name;
|
||
|
||
/* The table of files in the current directory:
|
||
|
||
`files' points to a vector of `struct fileinfo', one per file.
|
||
`nfiles' is the number of elements space has been allocated for.
|
||
`files_index' is the number actually in use. */
|
||
|
||
/* Address of block containing the files that are described. */
|
||
|
||
static struct fileinfo *files;
|
||
|
||
/* Length of block that `files' points to, measured in files. */
|
||
|
||
static int nfiles;
|
||
|
||
/* Index of first unused in `files'. */
|
||
|
||
static int files_index;
|
||
|
||
/* Record of one pending directory waiting to be listed. */
|
||
|
||
struct pending
|
||
{
|
||
char *name;
|
||
/* If the directory is actually the file pointed to by a symbolic link we
|
||
were told to list, `realname' will contain the name of the symbolic
|
||
link, otherwise zero. */
|
||
char *realname;
|
||
struct pending *next;
|
||
};
|
||
|
||
static struct pending *pending_dirs;
|
||
|
||
/* Current time (seconds since 1970). When we are printing a file's time,
|
||
include the year if it is more than 6 months before this time. */
|
||
|
||
static time_t current_time;
|
||
|
||
/* The number of digits to use for block sizes.
|
||
4, or more if needed for bigger numbers. */
|
||
|
||
static int block_size_size;
|
||
|
||
/* Option flags */
|
||
|
||
/* long_format for lots of info, one per line.
|
||
one_per_line for just names, one per line.
|
||
many_per_line for just names, many per line, sorted vertically.
|
||
horizontal for just names, many per line, sorted horizontally.
|
||
with_commas for just names, many per line, separated by commas.
|
||
|
||
-l, -1, -C, -x and -m control this parameter. */
|
||
|
||
enum format
|
||
{
|
||
long_format, /* -l */
|
||
one_per_line, /* -1 */
|
||
many_per_line, /* -C */
|
||
horizontal, /* -x */
|
||
with_commas /* -m */
|
||
};
|
||
|
||
static enum format format;
|
||
|
||
/* Type of time to print or sort by. Controlled by -c and -u. */
|
||
|
||
enum time_type
|
||
{
|
||
time_mtime, /* default */
|
||
time_ctime, /* -c */
|
||
time_atime /* -u */
|
||
};
|
||
|
||
static enum time_type time_type;
|
||
|
||
/* print the full time, otherwise the standard unix heuristics. */
|
||
|
||
int full_time;
|
||
|
||
/* The file characteristic to sort by. Controlled by -t, -S, -U, -X. */
|
||
|
||
enum sort_type
|
||
{
|
||
sort_none, /* -U */
|
||
sort_name, /* default */
|
||
sort_extension, /* -X */
|
||
sort_time, /* -t */
|
||
sort_size /* -S */
|
||
};
|
||
|
||
static enum sort_type sort_type;
|
||
|
||
/* Direction of sort.
|
||
0 means highest first if numeric,
|
||
lowest first if alphabetic;
|
||
these are the defaults.
|
||
1 means the opposite order in each case. -r */
|
||
|
||
static int sort_reverse;
|
||
|
||
/* Nonzero means to NOT display group information. -G */
|
||
|
||
int inhibit_group;
|
||
|
||
/* Nonzero means print the user and group id's as numbers rather
|
||
than as names. -n */
|
||
|
||
static int numeric_users;
|
||
|
||
/* Nonzero means mention the size in 512 byte blocks of each file. -s */
|
||
|
||
static int print_block_size;
|
||
|
||
/* Nonzero means show file sizes in kilobytes instead of blocks
|
||
(the size of which is system-dependent). -k */
|
||
|
||
static int kilobyte_blocks;
|
||
|
||
/* Precede each line of long output (per file) with a string like `m,n:'
|
||
where M is the number of characters after the `:' and before the
|
||
filename and N is the length of the filename. Using this format,
|
||
Emacs' dired mode starts up twice as fast, and can handle all
|
||
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.
|
||
|
||
Controlled by -F and -p. */
|
||
|
||
enum indicator_style
|
||
{
|
||
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. */
|
||
|
||
static int print_with_color;
|
||
|
||
enum color_type
|
||
{
|
||
color_no, /* 0: default or --color=no */
|
||
color_yes, /* 1: -o or --color=yes */
|
||
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
|
||
};
|
||
|
||
const char *const indicator_name[] =
|
||
{
|
||
"lc", "rc", "ec", "fi", "di", "ln", "pi", "so", "bd", "cd", "ex", NULL
|
||
};
|
||
|
||
struct indicator_type
|
||
{
|
||
int len;
|
||
char string[MAXCOLORLEN];
|
||
};
|
||
|
||
#define LEN_STR_PAIR(s) sizeof (s) - 1, s
|
||
|
||
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 */
|
||
};
|
||
|
||
/* Nonzero means print using ISO 8859 characters. The default is specified
|
||
here as well. -8 enables, -7 disables. */
|
||
|
||
static int print_iso8859;
|
||
#ifndef DEFAULT_ISO8859
|
||
#define DEFAULT_ISO8859 1
|
||
#endif
|
||
|
||
/* Nonzero means mention the inode number of each file. -i */
|
||
|
||
static int print_inode;
|
||
|
||
/* Nonzero means when a symbolic link is found, display info on
|
||
the file linked to. -L */
|
||
|
||
static int trace_links;
|
||
|
||
/* Nonzero means when a directory is found, display info on its
|
||
contents. -R */
|
||
|
||
static int trace_dirs;
|
||
|
||
/* Nonzero means when an argument is a directory name, display info
|
||
on it itself. -d */
|
||
|
||
static int immediate_dirs;
|
||
|
||
/* Nonzero means don't omit files whose names start with `.'. -A */
|
||
|
||
static int all_files;
|
||
|
||
/* Nonzero means don't omit files `.' and `..'
|
||
This flag implies `all_files'. -a */
|
||
|
||
static int really_all_files;
|
||
|
||
/* A linked list of shell-style globbing patterns. If a non-argument
|
||
file name matches any of these patterns, it is omitted.
|
||
Controlled by -I. Multiple -I options accumulate.
|
||
The -B option adds `*~' and `.*~' to this list. */
|
||
|
||
struct ignore_pattern
|
||
{
|
||
char *pattern;
|
||
struct ignore_pattern *next;
|
||
};
|
||
|
||
static struct ignore_pattern *ignore_patterns;
|
||
|
||
/* Nonzero means quote nongraphic chars in file names. -b */
|
||
|
||
static int quote_funny_chars;
|
||
|
||
/* Nonzero means output nongraphic chars in file names as `?'. -q */
|
||
|
||
static int qmark_funny_chars;
|
||
|
||
/* Nonzero means output each file name using C syntax for a string.
|
||
Always accompanied by `quote_funny_chars'.
|
||
This mode, together with -x or -C or -m,
|
||
and without such frills as -F or -s,
|
||
is guaranteed to make it possible for a program receiving
|
||
the output to tell exactly what file names are present. -Q */
|
||
|
||
static int quote_as_string;
|
||
|
||
/* The number of chars per hardware tab stop. -T */
|
||
static int tabsize;
|
||
|
||
/* Nonzero means we are listing the working directory because no
|
||
non-option arguments were given. */
|
||
|
||
static int dir_defaulted;
|
||
|
||
/* Nonzero means print each directory name before listing it. */
|
||
|
||
static int print_dir_name;
|
||
|
||
/* The line length to use for breaking lines in many-per-line format.
|
||
Can be set with -w. */
|
||
|
||
static int line_length;
|
||
|
||
/* If nonzero, the file listing format requires that stat be called on
|
||
each file. */
|
||
|
||
static int format_needs_stat;
|
||
|
||
/* The exit status to use if we don't get any fatal errors. */
|
||
|
||
static int exit_status;
|
||
|
||
/* If nonzero, display usage information and exit. */
|
||
static int show_help;
|
||
|
||
/* If nonzero, print the version on standard output and exit. */
|
||
static int show_version;
|
||
|
||
static struct option const long_options[] =
|
||
{
|
||
{"all", no_argument, 0, 'a'},
|
||
{"escape", no_argument, 0, 'b'},
|
||
{"directory", no_argument, 0, 'd'},
|
||
{"dired", no_argument, 0, 'D'},
|
||
{"full-time", no_argument, &full_time, 1},
|
||
{"inode", no_argument, 0, 'i'},
|
||
{"kilobytes", no_argument, 0, 'k'},
|
||
{"numeric-uid-gid", no_argument, 0, 'n'},
|
||
{"no-group", no_argument, 0, 'G'},
|
||
{"hide-control-chars", no_argument, 0, 'q'},
|
||
{"reverse", no_argument, 0, 'r'},
|
||
{"size", no_argument, 0, 's'},
|
||
{"width", required_argument, 0, 'w'},
|
||
{"almost-all", no_argument, 0, 'A'},
|
||
{"ignore-backups", no_argument, 0, 'B'},
|
||
{"classify", no_argument, 0, 'F'},
|
||
{"file-type", no_argument, 0, 'F'},
|
||
{"ignore", required_argument, 0, 'I'},
|
||
{"dereference", no_argument, 0, 'L'},
|
||
{"literal", no_argument, 0, 'N'},
|
||
{"quote-name", no_argument, 0, 'Q'},
|
||
{"recursive", no_argument, 0, 'R'},
|
||
{"format", required_argument, 0, 12},
|
||
{"sort", required_argument, 0, 10},
|
||
{"tabsize", required_argument, 0, 'T'},
|
||
{"time", required_argument, 0, 11},
|
||
{"help", no_argument, &show_help, 1},
|
||
{"version", no_argument, &show_version, 1},
|
||
{"color", optional_argument, 0, 13},
|
||
{"7bit", no_argument, 0, '7'},
|
||
{"8bit", no_argument, 0, '8'},
|
||
{0, 0, 0, 0}
|
||
};
|
||
|
||
static char const *const format_args[] =
|
||
{
|
||
"verbose", "long", "commas", "horizontal", "across",
|
||
"vertical", "single-column", 0
|
||
};
|
||
|
||
static enum format const formats[] =
|
||
{
|
||
long_format, long_format, with_commas, horizontal, horizontal,
|
||
many_per_line, one_per_line
|
||
};
|
||
|
||
static char const *const sort_args[] =
|
||
{
|
||
"none", "time", "size", "extension", 0
|
||
};
|
||
|
||
static enum sort_type const sort_types[] =
|
||
{
|
||
sort_none, sort_time, sort_size, sort_extension
|
||
};
|
||
|
||
static char const *const time_args[] =
|
||
{
|
||
"atime", "access", "use", "ctime", "status", 0
|
||
};
|
||
|
||
/* This zero-based index is used solely with the --dired option.
|
||
When that option is in effect, this counter is incremented for each
|
||
character of output generated by this program so that the beginning
|
||
and ending indices (in that output) of every file name can be recorded
|
||
and later output themselves. */
|
||
static size_t dired_pos;
|
||
|
||
#define PUTCHAR(c) do {putchar ((c)); ++dired_pos;} while (0)
|
||
|
||
/* Write S to STREAM and increment DIRED_POS by S_LEN. */
|
||
#define FPUTS(s, stream, s_len) \
|
||
do {fputs ((s), (stream)); dired_pos += s_len;} while (0)
|
||
|
||
/* Like FPUTS, but for use when S is a literal string. */
|
||
#define FPUTS_LITERAL(s, stream) \
|
||
do {fputs ((s), (stream)); dired_pos += sizeof((s)) - 1;} while (0)
|
||
|
||
#define DIRED_INDENT() \
|
||
do \
|
||
{ \
|
||
/* FIXME: remove the `&& format == long_format' clause. */ \
|
||
if (dired && format == long_format) \
|
||
FPUTS_LITERAL (" ", stdout); \
|
||
} \
|
||
while (0)
|
||
|
||
/* With --dired, store pairs of beginning and ending indices of filenames. */
|
||
static struct obstack dired_obstack;
|
||
|
||
/* With --dired, store pairs of beginning and ending indices of any
|
||
directory names that appear as headers (just before `total' line)
|
||
for lists of directory entries. Such directory names are seen when
|
||
listing hierarchies using -R and when a directory is listed with at
|
||
least one other command line argument. */
|
||
static struct obstack subdired_obstack;
|
||
|
||
/* Save the current index on the specified obstack, OBS. */
|
||
#define PUSH_CURRENT_DIRED_POS(obs) \
|
||
do \
|
||
{ \
|
||
/* FIXME: remove the `&& format == long_format' clause. */ \
|
||
if (dired && format == long_format) \
|
||
obstack_grow ((obs), &dired_pos, sizeof (dired_pos)); \
|
||
} \
|
||
while (0)
|
||
|
||
static enum time_type const time_types[] =
|
||
{
|
||
time_atime, time_atime, time_atime, time_ctime, time_ctime
|
||
};
|
||
|
||
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"
|
||
};
|
||
|
||
static enum color_type const color_types[] =
|
||
{
|
||
color_yes, color_yes, color_no, 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. */
|
||
|
||
static void
|
||
dired_dump_obstack (const char *prefix, struct obstack *os)
|
||
{
|
||
int n_pos;
|
||
|
||
n_pos = obstack_object_size (os) / sizeof (size_t);
|
||
if (n_pos > 0)
|
||
{
|
||
int i;
|
||
size_t *pos;
|
||
|
||
pos = (size_t *) obstack_finish (os);
|
||
fputs (prefix, stdout);
|
||
for (i = 0; i < n_pos; i++)
|
||
printf (" %d", (int) pos[i]);
|
||
fputs ("\n", stdout);
|
||
}
|
||
}
|
||
|
||
void
|
||
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);
|
||
|
||
i = decode_switches (argc, argv);
|
||
|
||
if (show_version)
|
||
{
|
||
printf ("ls - %s\n", PACKAGE_VERSION);
|
||
exit (0);
|
||
}
|
||
|
||
if (show_help)
|
||
usage (0);
|
||
|
||
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_with_color;
|
||
|
||
if (dired && format == long_format)
|
||
{
|
||
obstack_init (&dired_obstack);
|
||
obstack_init (&subdired_obstack);
|
||
}
|
||
|
||
nfiles = 100;
|
||
files = (struct fileinfo *) xmalloc (sizeof (struct fileinfo) * nfiles);
|
||
files_index = 0;
|
||
|
||
clear_files ();
|
||
|
||
if (i < argc)
|
||
dir_defaulted = 0;
|
||
for (; i < argc; i++)
|
||
gobble_file (argv[i], 1, "");
|
||
|
||
if (dir_defaulted)
|
||
{
|
||
if (immediate_dirs)
|
||
gobble_file (".", 1, "");
|
||
else
|
||
queue_directory (".", 0);
|
||
}
|
||
|
||
if (files_index)
|
||
{
|
||
sort_files ();
|
||
if (!immediate_dirs)
|
||
extract_dirs_from_files ("", 0);
|
||
/* `files_index' might be zero now. */
|
||
}
|
||
if (files_index)
|
||
{
|
||
print_current_files ();
|
||
if (pending_dirs)
|
||
PUTCHAR ('\n');
|
||
}
|
||
else if (pending_dirs && pending_dirs->next == 0)
|
||
print_dir_name = 0;
|
||
|
||
while (pending_dirs)
|
||
{
|
||
thispend = pending_dirs;
|
||
pending_dirs = pending_dirs->next;
|
||
print_dir (thispend->name, thispend->realname);
|
||
free (thispend->name);
|
||
if (thispend->realname)
|
||
free (thispend->realname);
|
||
free (thispend);
|
||
print_dir_name = 1;
|
||
}
|
||
|
||
if (dired && format == long_format)
|
||
{
|
||
/* No need to free these since we're about to exit. */
|
||
dired_dump_obstack ("//DIRED//", &dired_obstack);
|
||
dired_dump_obstack ("//SUBDIRED//", &subdired_obstack);
|
||
}
|
||
|
||
exit (exit_status);
|
||
}
|
||
|
||
/* Set all the option flags according to the switches specified.
|
||
Return the index of the first non-option argument. */
|
||
|
||
static int
|
||
decode_switches (int argc, char **argv)
|
||
{
|
||
register char *p;
|
||
int c;
|
||
int i;
|
||
|
||
qmark_funny_chars = 0;
|
||
quote_funny_chars = 0;
|
||
|
||
/* initialize all switches to default settings */
|
||
|
||
switch (ls_mode)
|
||
{
|
||
case LS_MULTI_COL:
|
||
/* This is for the `dir' program. */
|
||
format = many_per_line;
|
||
quote_funny_chars = 1;
|
||
break;
|
||
|
||
case LS_LONG_FORMAT:
|
||
/* This is for the `vdir' program. */
|
||
format = long_format;
|
||
quote_funny_chars = 1;
|
||
break;
|
||
|
||
case LS_LS:
|
||
/* This is for the `ls' program. */
|
||
if (isatty (1))
|
||
{
|
||
format = many_per_line;
|
||
qmark_funny_chars = 1;
|
||
}
|
||
else
|
||
{
|
||
format = one_per_line;
|
||
qmark_funny_chars = 0;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
time_type = time_mtime;
|
||
full_time = 0;
|
||
sort_type = sort_name;
|
||
sort_reverse = 0;
|
||
numeric_users = 0;
|
||
print_block_size = 0;
|
||
kilobyte_blocks = getenv ("POSIXLY_CORRECT") == 0;
|
||
indicator_style = none;
|
||
print_inode = 0;
|
||
trace_links = 0;
|
||
trace_dirs = 0;
|
||
immediate_dirs = 0;
|
||
all_files = 0;
|
||
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;
|
||
|
||
#ifdef TIOCGWINSZ
|
||
{
|
||
struct winsize ws;
|
||
|
||
if (ioctl (1, TIOCGWINSZ, &ws) != -1 && ws.ws_col != 0)
|
||
line_length = ws.ws_col;
|
||
}
|
||
#endif
|
||
|
||
/* FIXME: reference TABSIZE iff !posix_pedantic. */
|
||
p = getenv ("TABSIZE");
|
||
/* FIXME: use strtol here! */
|
||
tabsize = p ? atoi (p) : 8;
|
||
if (tabsize < 1)
|
||
{
|
||
error (0, 0,
|
||
_("ignoring invalid tab size in enironment variable TABSIZE: %s"),
|
||
optarg);
|
||
tabsize = 8;
|
||
}
|
||
|
||
while ((c = getopt_long (argc, argv,
|
||
"abcdfgiklmnopqrstuw:xABCDFGI:LNQRST:UX178",
|
||
long_options, (int *) 0)) != EOF)
|
||
{
|
||
switch (c)
|
||
{
|
||
case 0:
|
||
break;
|
||
|
||
case 'a':
|
||
all_files = 1;
|
||
really_all_files = 1;
|
||
break;
|
||
|
||
case 'b':
|
||
quote_funny_chars = 1;
|
||
qmark_funny_chars = 0;
|
||
break;
|
||
|
||
case 'c':
|
||
time_type = time_ctime;
|
||
sort_type = sort_time;
|
||
break;
|
||
|
||
case 'd':
|
||
immediate_dirs = 1;
|
||
break;
|
||
|
||
case 'f':
|
||
/* Same as enabling -a -U and disabling -l -s. */
|
||
all_files = 1;
|
||
really_all_files = 1;
|
||
sort_type = sort_none;
|
||
/* disable -l */
|
||
if (format == long_format)
|
||
format = (isatty (1) ? many_per_line : one_per_line);
|
||
print_block_size = 0; /* disable -s */
|
||
break;
|
||
|
||
case 'g':
|
||
/* No effect. For BSD compatibility. */
|
||
break;
|
||
|
||
case 'i':
|
||
print_inode = 1;
|
||
break;
|
||
|
||
case 'k':
|
||
kilobyte_blocks = 1;
|
||
break;
|
||
|
||
case 'l':
|
||
format = long_format;
|
||
break;
|
||
|
||
case 'm':
|
||
format = with_commas;
|
||
break;
|
||
|
||
case 'n':
|
||
numeric_users = 1;
|
||
break;
|
||
|
||
case 'o': /* Just like -l, but don't display group info. */
|
||
format = long_format;
|
||
inhibit_group = 1;
|
||
break;
|
||
|
||
case 13: /* --color */
|
||
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; /* --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;
|
||
}
|
||
break;
|
||
|
||
case 'p':
|
||
indicator_style = not_programs;
|
||
break;
|
||
|
||
case 'q':
|
||
qmark_funny_chars = 1;
|
||
quote_funny_chars = 0;
|
||
break;
|
||
|
||
case 'r':
|
||
sort_reverse = 1;
|
||
break;
|
||
|
||
case 's':
|
||
print_block_size = 1;
|
||
break;
|
||
|
||
case 't':
|
||
sort_type = sort_time;
|
||
break;
|
||
|
||
case 'u':
|
||
time_type = time_atime;
|
||
break;
|
||
|
||
case 'w':
|
||
line_length = atoi (optarg);
|
||
if (line_length < 1)
|
||
error (1, 0, _("invalid line width: %s"), optarg);
|
||
break;
|
||
|
||
case 'x':
|
||
format = horizontal;
|
||
break;
|
||
|
||
case 'A':
|
||
all_files = 1;
|
||
break;
|
||
|
||
case 'B':
|
||
add_ignore_pattern ("*~");
|
||
add_ignore_pattern (".*~");
|
||
break;
|
||
|
||
case 'C':
|
||
format = many_per_line;
|
||
break;
|
||
|
||
case 'D':
|
||
dired = 1;
|
||
break;
|
||
|
||
case 'F':
|
||
indicator_style = all;
|
||
break;
|
||
|
||
case 'G': /* inhibit display of group info */
|
||
inhibit_group = 1;
|
||
break;
|
||
|
||
case 'I':
|
||
add_ignore_pattern (optarg);
|
||
break;
|
||
|
||
case 'L':
|
||
trace_links = 1;
|
||
break;
|
||
|
||
case 'N':
|
||
quote_funny_chars = 0;
|
||
qmark_funny_chars = 0;
|
||
break;
|
||
|
||
case 'Q':
|
||
quote_as_string = 1;
|
||
quote_funny_chars = 1;
|
||
qmark_funny_chars = 0;
|
||
break;
|
||
|
||
case 'R':
|
||
trace_dirs = 1;
|
||
break;
|
||
|
||
case 'S':
|
||
sort_type = sort_size;
|
||
break;
|
||
|
||
case 'T':
|
||
tabsize = atoi (optarg);
|
||
if (tabsize < 1)
|
||
error (1, 0, _("invalid tab size: %s"), optarg);
|
||
break;
|
||
|
||
case 'U':
|
||
sort_type = sort_none;
|
||
break;
|
||
|
||
case 'X':
|
||
sort_type = sort_extension;
|
||
break;
|
||
|
||
case '1':
|
||
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);
|
||
}
|
||
sort_type = sort_types[i];
|
||
break;
|
||
|
||
case 11: /* --time */
|
||
i = argmatch (optarg, time_args);
|
||
if (i < 0)
|
||
{
|
||
invalid_arg (_("time type"), optarg, i);
|
||
usage (1);
|
||
}
|
||
time_type = time_types[i];
|
||
break;
|
||
|
||
case 12: /* --format */
|
||
i = argmatch (optarg, format_args);
|
||
if (i < 0)
|
||
{
|
||
invalid_arg (_("format type"), optarg, i);
|
||
usage (1);
|
||
}
|
||
format = formats[i];
|
||
break;
|
||
|
||
default:
|
||
usage (1);
|
||
}
|
||
}
|
||
|
||
if (print_with_color)
|
||
{
|
||
parse_ls_color ();
|
||
}
|
||
|
||
return optind;
|
||
}
|
||
|
||
/* 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? */
|
||
int state; /* State of parser */
|
||
int ind_no; /* Indicator number */
|
||
int ccount; /* Character count */
|
||
int num; /* Escape char numeral */
|
||
char label[3]; /* Indicator label */
|
||
|
||
strcpy (label, "??");
|
||
if ((p = getenv (whichvar = "LS_COLORS")) ||
|
||
(p = getenv (whichvar = "LS_COLOURS")))
|
||
{
|
||
state = 1;
|
||
while (state > 0)
|
||
{
|
||
switch (state)
|
||
{
|
||
case 1: /* First label character */
|
||
if (*p)
|
||
{
|
||
label[0] = *(p++);
|
||
state = 2;
|
||
}
|
||
else
|
||
state = 0; /* Done */
|
||
break;
|
||
|
||
case 2: /* Second label character */
|
||
if (*p)
|
||
{
|
||
label[1] = *(p++);
|
||
state = 3;
|
||
}
|
||
else
|
||
state = -1; /* Error */
|
||
break;
|
||
|
||
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;
|
||
|
||
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++;
|
||
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++;
|
||
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;
|
||
}
|
||
break;
|
||
|
||
case 7: /* Hex numeral */
|
||
switch (*p)
|
||
{
|
||
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;
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
if (state < 0)
|
||
{
|
||
error (0, 0, _("bad %s variable\n"), whichvar);
|
||
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
|
||
to be treated as regular directories but still be listed under their
|
||
real names. */
|
||
|
||
static void
|
||
queue_directory (char *name, char *realname)
|
||
{
|
||
struct pending *new;
|
||
|
||
new = (struct pending *) xmalloc (sizeof (struct pending));
|
||
new->next = pending_dirs;
|
||
pending_dirs = new;
|
||
new->name = xstrdup (name);
|
||
if (realname)
|
||
new->realname = xstrdup (realname);
|
||
else
|
||
new->realname = 0;
|
||
}
|
||
|
||
/* Read directory `name', and list the files in it.
|
||
If `realname' is nonzero, print its name instead of `name';
|
||
this is used for symbolic links to directories. */
|
||
|
||
static void
|
||
print_dir (const char *name, const char *realname)
|
||
{
|
||
register DIR *reading;
|
||
register struct dirent *next;
|
||
register int total_blocks = 0;
|
||
|
||
errno = 0;
|
||
reading = opendir (name);
|
||
if (!reading)
|
||
{
|
||
error (0, errno, "%s", name);
|
||
exit_status = 1;
|
||
return;
|
||
}
|
||
|
||
/* Read the directory entries, and insert the subfiles into the `files'
|
||
table. */
|
||
|
||
clear_files ();
|
||
|
||
while ((next = readdir (reading)) != NULL)
|
||
if (file_interesting (next))
|
||
total_blocks += gobble_file (next->d_name, 0, name);
|
||
|
||
if (CLOSEDIR (reading))
|
||
{
|
||
error (0, errno, "%s", name);
|
||
exit_status = 1;
|
||
/* Don't return; print whatever we got. */
|
||
}
|
||
|
||
/* Sort the directory contents. */
|
||
sort_files ();
|
||
|
||
/* If any member files are subdirectories, perhaps they should have their
|
||
contents listed rather than being mentioned here as files. */
|
||
|
||
if (trace_dirs)
|
||
extract_dirs_from_files (name, 1);
|
||
|
||
if (print_dir_name)
|
||
{
|
||
const char *dir;
|
||
|
||
DIRED_INDENT ();
|
||
dir = (realname ? realname : name);
|
||
PUSH_CURRENT_DIRED_POS (&subdired_obstack);
|
||
FPUTS (dir, stdout, strlen (dir));
|
||
PUSH_CURRENT_DIRED_POS (&subdired_obstack);
|
||
FPUTS_LITERAL (":\n", stdout);
|
||
}
|
||
|
||
if (format == long_format || print_block_size)
|
||
{
|
||
char buf[6 + 20 + 1 + 1];
|
||
|
||
DIRED_INDENT ();
|
||
sprintf (buf, "total %u\n", total_blocks);
|
||
FPUTS (buf, stdout, strlen (buf));
|
||
}
|
||
|
||
if (files_index)
|
||
print_current_files ();
|
||
|
||
if (pending_dirs)
|
||
PUTCHAR ('\n');
|
||
}
|
||
|
||
/* Add `pattern' to the list of patterns for which files that match are
|
||
not listed. */
|
||
|
||
static void
|
||
add_ignore_pattern (char *pattern)
|
||
{
|
||
register struct ignore_pattern *ignore;
|
||
|
||
ignore = (struct ignore_pattern *) xmalloc (sizeof (struct ignore_pattern));
|
||
ignore->pattern = pattern;
|
||
/* Add it to the head of the linked list. */
|
||
ignore->next = ignore_patterns;
|
||
ignore_patterns = ignore;
|
||
}
|
||
|
||
/* Return nonzero if the file in `next' should be listed. */
|
||
|
||
static int
|
||
file_interesting (register struct dirent *next)
|
||
{
|
||
register struct ignore_pattern *ignore;
|
||
|
||
for (ignore = ignore_patterns; ignore; ignore = ignore->next)
|
||
if (fnmatch (ignore->pattern, next->d_name, FNM_PERIOD) == 0)
|
||
return 0;
|
||
|
||
if (really_all_files
|
||
|| next->d_name[0] != '.'
|
||
|| (all_files
|
||
&& next->d_name[1] != '\0'
|
||
&& (next->d_name[1] != '.' || next->d_name[2] != '\0')))
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Enter and remove entries in the table `files'. */
|
||
|
||
/* Empty the table of files. */
|
||
|
||
static void
|
||
clear_files (void)
|
||
{
|
||
register int i;
|
||
|
||
for (i = 0; i < files_index; i++)
|
||
{
|
||
free (files[i].name);
|
||
if (files[i].linkname)
|
||
free (files[i].linkname);
|
||
}
|
||
|
||
files_index = 0;
|
||
block_size_size = 4;
|
||
}
|
||
|
||
/* Add a file to the current table of files.
|
||
Verify that the file exists, and print an error message if it does not.
|
||
Return the number of blocks that the file occupies. */
|
||
|
||
static int
|
||
gobble_file (const char *name, int explicit_arg, const char *dirname)
|
||
{
|
||
register int blocks;
|
||
register int val;
|
||
register char *path;
|
||
|
||
if (files_index == nfiles)
|
||
{
|
||
nfiles *= 2;
|
||
files = (struct fileinfo *) xrealloc (files, sizeof (*files) * nfiles);
|
||
}
|
||
|
||
files[files_index].linkname = 0;
|
||
files[files_index].linkmode = 0;
|
||
|
||
if (explicit_arg || format_needs_stat)
|
||
{
|
||
/* `path' is the absolute pathname of this file. */
|
||
|
||
if (name[0] == '/' || dirname[0] == 0)
|
||
path = (char *) name;
|
||
else
|
||
{
|
||
path = (char *) alloca (strlen (name) + strlen (dirname) + 2);
|
||
attach (path, dirname, name);
|
||
}
|
||
|
||
if (trace_links)
|
||
{
|
||
val = stat (path, &files[files_index].stat);
|
||
if (val < 0)
|
||
/* Perhaps a symbolically-linked to file doesn't exist; stat
|
||
the link instead. */
|
||
val = lstat (path, &files[files_index].stat);
|
||
}
|
||
else
|
||
val = lstat (path, &files[files_index].stat);
|
||
if (val < 0)
|
||
{
|
||
error (0, errno, "%s", path);
|
||
exit_status = 1;
|
||
return 0;
|
||
}
|
||
|
||
#ifdef S_ISLNK
|
||
if (S_ISLNK (files[files_index].stat.st_mode)
|
||
&& (explicit_arg || format == long_format))
|
||
{
|
||
char *linkpath;
|
||
struct stat linkstats;
|
||
|
||
get_link_name (path, &files[files_index]);
|
||
linkpath = make_link_path (path, files[files_index].linkname);
|
||
|
||
/* Avoid following symbolic links when possible, ie, when
|
||
they won't be traced and when no indicator is needed. */
|
||
if (linkpath
|
||
&& ((explicit_arg && format != long_format)
|
||
|| indicator_style != none || print_with_color)
|
||
&& stat (linkpath, &linkstats) == 0)
|
||
{
|
||
/* Symbolic links to directories that are mentioned on the
|
||
command line are automatically traced if not being
|
||
listed as files. */
|
||
if (explicit_arg && format != long_format
|
||
&& S_ISDIR (linkstats.st_mode))
|
||
{
|
||
/* Substitute the linked-to directory's name, but
|
||
save the real name in `linkname' for printing. */
|
||
if (!immediate_dirs)
|
||
{
|
||
const char *tempname = name;
|
||
name = linkpath;
|
||
linkpath = files[files_index].linkname;
|
||
files[files_index].linkname = (char *) tempname;
|
||
}
|
||
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;
|
||
}
|
||
if (linkpath)
|
||
free (linkpath);
|
||
}
|
||
#endif
|
||
|
||
#ifdef S_ISLNK
|
||
if (S_ISLNK (files[files_index].stat.st_mode))
|
||
files[files_index].filetype = symbolic_link;
|
||
else
|
||
#endif
|
||
if (S_ISDIR (files[files_index].stat.st_mode))
|
||
{
|
||
if (explicit_arg && !immediate_dirs)
|
||
files[files_index].filetype = arg_directory;
|
||
else
|
||
files[files_index].filetype = directory;
|
||
}
|
||
else
|
||
files[files_index].filetype = normal;
|
||
|
||
blocks = convert_blocks (ST_NBLOCKS (files[files_index].stat),
|
||
kilobyte_blocks);
|
||
if (blocks >= 10000 && block_size_size < 5)
|
||
block_size_size = 5;
|
||
if (blocks >= 100000 && block_size_size < 6)
|
||
block_size_size = 6;
|
||
if (blocks >= 1000000 && block_size_size < 7)
|
||
block_size_size = 7;
|
||
}
|
||
else
|
||
blocks = 0;
|
||
|
||
files[files_index].name = xstrdup (name);
|
||
files_index++;
|
||
|
||
return blocks;
|
||
}
|
||
|
||
#ifdef S_ISLNK
|
||
|
||
/* Put the name of the file that `filename' is a symbolic link to
|
||
into the `linkname' field of `f'. */
|
||
|
||
static void
|
||
get_link_name (char *filename, struct fileinfo *f)
|
||
{
|
||
char *linkbuf;
|
||
register int linksize;
|
||
|
||
linkbuf = (char *) alloca (PATH_MAX + 2);
|
||
/* Some automounters give incorrect st_size for mount points.
|
||
I can't think of a good workaround for it, though. */
|
||
linksize = readlink (filename, linkbuf, PATH_MAX + 1);
|
||
if (linksize < 0)
|
||
{
|
||
error (0, errno, "%s", filename);
|
||
exit_status = 1;
|
||
}
|
||
else
|
||
{
|
||
linkbuf[linksize] = '\0';
|
||
f->linkname = xstrdup (linkbuf);
|
||
}
|
||
}
|
||
|
||
/* If `linkname' is a relative path and `path' contains one or more
|
||
leading directories, return `linkname' with those directories
|
||
prepended; otherwise, return a copy of `linkname'.
|
||
If `linkname' is zero, return zero. */
|
||
|
||
static char *
|
||
make_link_path (char *path, char *linkname)
|
||
{
|
||
char *linkbuf;
|
||
int bufsiz;
|
||
|
||
if (linkname == 0)
|
||
return 0;
|
||
|
||
if (*linkname == '/')
|
||
return xstrdup (linkname);
|
||
|
||
/* The link is to a relative path. Prepend any leading path
|
||
in `path' to the link name. */
|
||
linkbuf = strrchr (path, '/');
|
||
if (linkbuf == 0)
|
||
return xstrdup (linkname);
|
||
|
||
bufsiz = linkbuf - path + 1;
|
||
linkbuf = xmalloc (bufsiz + strlen (linkname) + 1);
|
||
strncpy (linkbuf, path, bufsiz);
|
||
strcpy (linkbuf + bufsiz, linkname);
|
||
return linkbuf;
|
||
}
|
||
#endif
|
||
|
||
/* Remove any entries from `files' that are for directories,
|
||
and queue them to be listed as directories instead.
|
||
`dirname' is the prefix to prepend to each dirname
|
||
to make it correct relative to ls's working dir.
|
||
`recursive' is nonzero if we should not treat `.' and `..' as dirs.
|
||
This is desirable when processing directories recursively. */
|
||
|
||
static void
|
||
extract_dirs_from_files (const char *dirname, int recursive)
|
||
{
|
||
register int i, j;
|
||
register char *path;
|
||
int dirlen;
|
||
|
||
dirlen = strlen (dirname) + 2;
|
||
/* Queue the directories last one first, because queueing reverses the
|
||
order. */
|
||
for (i = files_index - 1; i >= 0; i--)
|
||
if ((files[i].filetype == directory || files[i].filetype == arg_directory)
|
||
&& (!recursive || is_not_dot_or_dotdot (files[i].name)))
|
||
{
|
||
if (files[i].name[0] == '/' || dirname[0] == 0)
|
||
{
|
||
queue_directory (files[i].name, files[i].linkname);
|
||
}
|
||
else
|
||
{
|
||
path = (char *) xmalloc (strlen (files[i].name) + dirlen);
|
||
attach (path, dirname, files[i].name);
|
||
queue_directory (path, files[i].linkname);
|
||
free (path);
|
||
}
|
||
if (files[i].filetype == arg_directory)
|
||
free (files[i].name);
|
||
}
|
||
|
||
/* Now delete the directories from the table, compacting all the remaining
|
||
entries. */
|
||
|
||
for (i = 0, j = 0; i < files_index; i++)
|
||
if (files[i].filetype != arg_directory)
|
||
files[j++] = files[i];
|
||
files_index = j;
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
char *t;
|
||
|
||
t = strrchr (name, '/');
|
||
if (t)
|
||
name = t + 1;
|
||
|
||
if (name[0] == '.'
|
||
&& (name[1] == '\0'
|
||
|| (name[1] == '.' && name[2] == '\0')))
|
||
return 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Sort the files now in the table. */
|
||
|
||
static void
|
||
sort_files (void)
|
||
{
|
||
int (*func) ();
|
||
|
||
switch (sort_type)
|
||
{
|
||
case sort_none:
|
||
return;
|
||
case sort_time:
|
||
switch (time_type)
|
||
{
|
||
case time_ctime:
|
||
func = sort_reverse ? rev_cmp_ctime : compare_ctime;
|
||
break;
|
||
case time_mtime:
|
||
func = sort_reverse ? rev_cmp_mtime : compare_mtime;
|
||
break;
|
||
case time_atime:
|
||
func = sort_reverse ? rev_cmp_atime : compare_atime;
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
case sort_name:
|
||
func = sort_reverse ? rev_cmp_name : compare_name;
|
||
break;
|
||
case sort_extension:
|
||
func = sort_reverse ? rev_cmp_extension : compare_extension;
|
||
break;
|
||
case sort_size:
|
||
func = sort_reverse ? rev_cmp_size : compare_size;
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
qsort (files, files_index, sizeof (struct fileinfo), func);
|
||
}
|
||
|
||
/* Comparison routines for sorting the files. */
|
||
|
||
static int
|
||
compare_ctime (struct fileinfo *file1, struct fileinfo *file2)
|
||
{
|
||
return longdiff (file2->stat.st_ctime, file1->stat.st_ctime);
|
||
}
|
||
|
||
static int
|
||
rev_cmp_ctime (struct fileinfo *file2, struct fileinfo *file1)
|
||
{
|
||
return longdiff (file2->stat.st_ctime, file1->stat.st_ctime);
|
||
}
|
||
|
||
static int
|
||
compare_mtime (struct fileinfo *file1, struct fileinfo *file2)
|
||
{
|
||
return longdiff (file2->stat.st_mtime, file1->stat.st_mtime);
|
||
}
|
||
|
||
static int
|
||
rev_cmp_mtime (struct fileinfo *file2, struct fileinfo *file1)
|
||
{
|
||
return longdiff (file2->stat.st_mtime, file1->stat.st_mtime);
|
||
}
|
||
|
||
static int
|
||
compare_atime (struct fileinfo *file1, struct fileinfo *file2)
|
||
{
|
||
return longdiff (file2->stat.st_atime, file1->stat.st_atime);
|
||
}
|
||
|
||
static int
|
||
rev_cmp_atime (struct fileinfo *file2, struct fileinfo *file1)
|
||
{
|
||
return longdiff (file2->stat.st_atime, file1->stat.st_atime);
|
||
}
|
||
|
||
static int
|
||
compare_size (struct fileinfo *file1, struct fileinfo *file2)
|
||
{
|
||
return longdiff (file2->stat.st_size, file1->stat.st_size);
|
||
}
|
||
|
||
static int
|
||
rev_cmp_size (struct fileinfo *file2, struct fileinfo *file1)
|
||
{
|
||
return longdiff (file2->stat.st_size, file1->stat.st_size);
|
||
}
|
||
|
||
static int
|
||
compare_name (struct fileinfo *file1, struct fileinfo *file2)
|
||
{
|
||
return strcmp (file1->name, file2->name);
|
||
}
|
||
|
||
static int
|
||
rev_cmp_name (struct fileinfo *file2, struct fileinfo *file1)
|
||
{
|
||
return strcmp (file1->name, file2->name);
|
||
}
|
||
|
||
/* Compare file extensions. Files with no extension are `smallest'.
|
||
If extensions are the same, compare by filenames instead. */
|
||
|
||
static int
|
||
compare_extension (struct fileinfo *file1, struct fileinfo *file2)
|
||
{
|
||
register char *base1, *base2;
|
||
register int cmp;
|
||
|
||
base1 = strrchr (file1->name, '.');
|
||
base2 = strrchr (file2->name, '.');
|
||
if (base1 == 0 && base2 == 0)
|
||
return strcmp (file1->name, file2->name);
|
||
if (base1 == 0)
|
||
return -1;
|
||
if (base2 == 0)
|
||
return 1;
|
||
cmp = strcmp (base1, base2);
|
||
if (cmp == 0)
|
||
return strcmp (file1->name, file2->name);
|
||
return cmp;
|
||
}
|
||
|
||
static int
|
||
rev_cmp_extension (struct fileinfo *file2, struct fileinfo *file1)
|
||
{
|
||
register char *base1, *base2;
|
||
register int cmp;
|
||
|
||
base1 = strrchr (file1->name, '.');
|
||
base2 = strrchr (file2->name, '.');
|
||
if (base1 == 0 && base2 == 0)
|
||
return strcmp (file1->name, file2->name);
|
||
if (base1 == 0)
|
||
return -1;
|
||
if (base2 == 0)
|
||
return 1;
|
||
cmp = strcmp (base1, base2);
|
||
if (cmp == 0)
|
||
return strcmp (file1->name, file2->name);
|
||
return cmp;
|
||
}
|
||
|
||
/* List all the files now in the table. */
|
||
|
||
static void
|
||
print_current_files (void)
|
||
{
|
||
register int i;
|
||
|
||
switch (format)
|
||
{
|
||
case one_per_line:
|
||
for (i = 0; i < files_index; i++)
|
||
{
|
||
print_file_name_and_frills (files + i);
|
||
putchar ('\n');
|
||
}
|
||
break;
|
||
|
||
case many_per_line:
|
||
print_many_per_line ();
|
||
break;
|
||
|
||
case horizontal:
|
||
print_horizontal ();
|
||
break;
|
||
|
||
case with_commas:
|
||
print_with_commas ();
|
||
break;
|
||
|
||
case long_format:
|
||
for (i = 0; i < files_index; i++)
|
||
{
|
||
print_long_format (files + i);
|
||
PUTCHAR ('\n');
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
print_long_format (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 *p;
|
||
time_t when;
|
||
|
||
mode_string (f->stat.st_mode, modebuf);
|
||
modebuf[10] = '\0';
|
||
|
||
switch (time_type)
|
||
{
|
||
case time_ctime:
|
||
when = f->stat.st_ctime;
|
||
break;
|
||
case time_mtime:
|
||
when = f->stat.st_mtime;
|
||
break;
|
||
case time_atime:
|
||
when = f->stat.st_atime;
|
||
break;
|
||
}
|
||
|
||
strcpy (timebuf, ctime (&when));
|
||
|
||
if (full_time)
|
||
timebuf[24] = '\0';
|
||
else
|
||
{
|
||
if (current_time > when + 6L * 30L * 24L * 60L * 60L /* Old. */
|
||
|| current_time < when - 60L * 60L) /* In the future. */
|
||
{
|
||
/* The file is fairly old or in the future.
|
||
POSIX says the cutoff is 6 months old;
|
||
approximate this by 6*30 days.
|
||
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);
|
||
}
|
||
timebuf[16] = 0;
|
||
}
|
||
|
||
p = bigbuf;
|
||
|
||
if (print_inode)
|
||
{
|
||
sprintf (p, "%*lu ", INODE_DIGITS, (unsigned long) f->stat.st_ino);
|
||
p += strlen (p);
|
||
}
|
||
|
||
if (print_block_size)
|
||
{
|
||
sprintf (p, "%*u ", block_size_size,
|
||
(unsigned) convert_blocks (ST_NBLOCKS (f->stat),
|
||
kilobyte_blocks));
|
||
p += strlen (p);
|
||
}
|
||
|
||
/* The space between the mode and the number of links is the POSIX
|
||
"optional alternate access method flag". */
|
||
sprintf (p, "%s %3u ", modebuf, (unsigned int) f->stat.st_nlink);
|
||
p += strlen (p);
|
||
|
||
if (numeric_users)
|
||
sprintf (p, "%-8u ", (unsigned int) f->stat.st_uid);
|
||
else
|
||
sprintf (p, "%-8.8s ", getuser (f->stat.st_uid));
|
||
p += strlen (p);
|
||
|
||
if (!inhibit_group)
|
||
{
|
||
if (numeric_users)
|
||
sprintf (p, "%-8u ", (unsigned int) f->stat.st_gid);
|
||
else
|
||
sprintf (p, "%-8.8s ", getgroup (f->stat.st_gid));
|
||
p += strlen (p);
|
||
}
|
||
|
||
if (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode))
|
||
sprintf (p, "%3u, %3u ", (unsigned) major (f->stat.st_rdev),
|
||
(unsigned) minor (f->stat.st_rdev));
|
||
else
|
||
sprintf (p, "%8lu ", (unsigned long) f->stat.st_size);
|
||
p += strlen (p);
|
||
|
||
sprintf (p, "%s ", full_time ? timebuf : timebuf + 4);
|
||
p += strlen (p);
|
||
|
||
DIRED_INDENT ();
|
||
FPUTS (bigbuf, stdout, p - bigbuf);
|
||
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);
|
||
PUSH_CURRENT_DIRED_POS (&dired_obstack);
|
||
|
||
if (f->filetype == symbolic_link)
|
||
{
|
||
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);
|
||
if (indicator_style != none)
|
||
print_type_indicator (f->linkmode);
|
||
}
|
||
}
|
||
else if (indicator_style != none)
|
||
print_type_indicator (f->stat.st_mode);
|
||
}
|
||
|
||
/* 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 char *
|
||
quote_filename (register const char *p, size_t *quoted_length)
|
||
{
|
||
register unsigned char c;
|
||
const char *p0 = p;
|
||
char *quoted, *q;
|
||
int found_quotable;
|
||
|
||
if (!quote_as_string && !quote_funny_chars && !qmark_funny_chars)
|
||
{
|
||
*quoted_length = strlen (p);
|
||
return NULL;
|
||
}
|
||
|
||
found_quotable = 0;
|
||
for (c = *p; c; c = *++p)
|
||
{
|
||
if (quote_funny_chars)
|
||
{
|
||
switch (c)
|
||
{
|
||
case '\\':
|
||
case '\n':
|
||
case '\b':
|
||
case '\r':
|
||
case '\t':
|
||
case '\f':
|
||
case ' ':
|
||
case '"':
|
||
found_quotable = 1;
|
||
break;
|
||
|
||
default:
|
||
/* FIXME: why not just use the ISPRINT macro here? */
|
||
if (!((c > 040 && c < 0177)
|
||
|| (print_iso8859 && c >= 0200 && c <= 0377)))
|
||
found_quotable = 1;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!((c >= 040 && c < 0177)
|
||
|| (print_iso8859 && c >= 0xA1 && c <= 0xFF))
|
||
&& qmark_funny_chars)
|
||
found_quotable = 1;
|
||
}
|
||
if (found_quotable)
|
||
break;
|
||
}
|
||
|
||
if (!found_quotable && !quote_as_string)
|
||
{
|
||
*quoted_length = p - p0;
|
||
return NULL;
|
||
}
|
||
|
||
p = p0;
|
||
quoted = xmalloc (4 * strlen (p) + 1);
|
||
q = quoted;
|
||
|
||
#define SAVECHAR(c) *q++ = (c)
|
||
#define SAVE_2_CHARS(c12) \
|
||
do { *q++ = ((c12)[0]); \
|
||
*q++ = ((c12)[1]); } while (0)
|
||
|
||
if (quote_as_string)
|
||
SAVECHAR ('"');
|
||
|
||
while ((c = *p++))
|
||
{
|
||
if (quote_funny_chars)
|
||
{
|
||
switch (c)
|
||
{
|
||
case '\\':
|
||
SAVE_2_CHARS ("\\\\");
|
||
break;
|
||
|
||
case '\n':
|
||
SAVE_2_CHARS ("\\n");
|
||
break;
|
||
|
||
case '\b':
|
||
SAVE_2_CHARS ("\\b");
|
||
break;
|
||
|
||
case '\r':
|
||
SAVE_2_CHARS ("\\r");
|
||
break;
|
||
|
||
case '\t':
|
||
SAVE_2_CHARS ("\\t");
|
||
break;
|
||
|
||
case '\f':
|
||
SAVE_2_CHARS ("\\f");
|
||
break;
|
||
|
||
case ' ':
|
||
SAVE_2_CHARS ("\\ ");
|
||
break;
|
||
|
||
case '"':
|
||
SAVE_2_CHARS ("\\\"");
|
||
break;
|
||
|
||
default:
|
||
if ((c > 040 && c < 0177)
|
||
|| (print_iso8859 && c >= 0200 && c <= 0377))
|
||
SAVECHAR (c);
|
||
else
|
||
{
|
||
char buf[5];
|
||
sprintf (buf, "\\%03o", (unsigned int) c);
|
||
q = stpcpy (q, buf);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if ((c >= 040 && c < 0177)
|
||
|| (print_iso8859 && c >= 0200 && c <= 0377))
|
||
SAVECHAR (c);
|
||
else if (!qmark_funny_chars)
|
||
SAVECHAR (c);
|
||
else
|
||
SAVECHAR ('?');
|
||
}
|
||
}
|
||
|
||
if (quote_as_string)
|
||
SAVECHAR ('"');
|
||
|
||
*quoted_length = q - quoted;
|
||
|
||
SAVECHAR ('\0');
|
||
|
||
return quoted;
|
||
}
|
||
|
||
static void
|
||
print_name_with_quoting (register char *p)
|
||
{
|
||
char *quoted;
|
||
size_t quoted_length;
|
||
|
||
quoted = quote_filename (p, "ed_length);
|
||
FPUTS (quoted != NULL ? quoted : p, stdout, quoted_length);
|
||
if (quoted)
|
||
free (quoted);
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
if (print_inode)
|
||
printf ("%*lu ", INODE_DIGITS, (unsigned long) f->stat.st_ino);
|
||
|
||
if (print_block_size)
|
||
printf ("%*u ", block_size_size,
|
||
(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);
|
||
|
||
if (indicator_style != none)
|
||
print_type_indicator (f->stat.st_mode);
|
||
}
|
||
|
||
static void
|
||
print_type_indicator (unsigned int mode)
|
||
{
|
||
if (S_ISDIR (mode))
|
||
PUTCHAR ('/');
|
||
|
||
#ifdef S_ISLNK
|
||
if (S_ISLNK (mode))
|
||
PUTCHAR ('@');
|
||
#endif
|
||
|
||
#ifdef S_ISFIFO
|
||
if (S_ISFIFO (mode))
|
||
PUTCHAR ('|');
|
||
#endif
|
||
|
||
#ifdef S_ISSOCK
|
||
if (S_ISSOCK (mode))
|
||
PUTCHAR ('=');
|
||
#endif
|
||
|
||
if (S_ISREG (mode) && indicator_style == all
|
||
&& (mode & (S_IEXEC | S_IXGRP | S_IXOTH)))
|
||
PUTCHAR ('*');
|
||
}
|
||
|
||
static void
|
||
print_color_indicator (unsigned int mode)
|
||
{
|
||
int type = C_FILE;
|
||
|
||
if (S_ISDIR (mode))
|
||
type = C_DIR;
|
||
|
||
#ifdef S_ISLNK
|
||
else if (S_ISLNK (mode))
|
||
type = 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_IEXEC | S_IEXEC >> 3 | S_IEXEC >> 6)))
|
||
type = C_EXEC;
|
||
|
||
put_indicator (C_LEFT);
|
||
put_indicator (type);
|
||
put_indicator (C_RIGHT);
|
||
}
|
||
|
||
/* Output a color indicator (which may contain nulls) */
|
||
static void
|
||
put_indicator (int n)
|
||
{
|
||
register int i;
|
||
register char *p;
|
||
|
||
p = color_indicator[n].string;
|
||
|
||
for (i = color_indicator[n].len; i; i--)
|
||
{
|
||
putchar (*(p++));
|
||
}
|
||
}
|
||
|
||
static int
|
||
length_of_file_name_and_frills (struct fileinfo *f)
|
||
{
|
||
register char *p = f->name;
|
||
register unsigned char c;
|
||
register int len = 0;
|
||
|
||
if (print_inode)
|
||
len += INODE_DIGITS + 1;
|
||
|
||
if (print_block_size)
|
||
len += 1 + block_size_size;
|
||
|
||
if (quote_as_string)
|
||
len += 2;
|
||
|
||
while ((c = *p++))
|
||
{
|
||
if (quote_funny_chars)
|
||
{
|
||
switch (c)
|
||
{
|
||
case '\\':
|
||
case '\n':
|
||
case '\b':
|
||
case '\r':
|
||
case '\t':
|
||
case '\f':
|
||
case ' ':
|
||
len += 2;
|
||
break;
|
||
|
||
case '"':
|
||
if (quote_as_string)
|
||
len += 2;
|
||
else
|
||
len += 1;
|
||
break;
|
||
|
||
default:
|
||
if ((c >= 040 && c < 0177)
|
||
|| (print_iso8859 && c >= 0200 && c <= 0377))
|
||
len += 1;
|
||
else
|
||
len += 4;
|
||
}
|
||
}
|
||
else
|
||
len += 1;
|
||
}
|
||
|
||
if (indicator_style != none)
|
||
{
|
||
unsigned filetype = f->stat.st_mode;
|
||
|
||
if (S_ISREG (filetype))
|
||
{
|
||
if (indicator_style == all
|
||
&& (f->stat.st_mode & (S_IEXEC | S_IEXEC >> 3 | S_IEXEC >> 6)))
|
||
len += 1;
|
||
}
|
||
else if (S_ISDIR (filetype)
|
||
#ifdef S_ISLNK
|
||
|| S_ISLNK (filetype)
|
||
#endif
|
||
#ifdef S_ISFIFO
|
||
|| S_ISFIFO (filetype)
|
||
#endif
|
||
#ifdef S_ISSOCK
|
||
|| S_ISSOCK (filetype)
|
||
#endif
|
||
)
|
||
len += 1;
|
||
}
|
||
|
||
return len;
|
||
}
|
||
|
||
static void
|
||
print_many_per_line (void)
|
||
{
|
||
int filesno; /* Index into files. */
|
||
int row; /* Current row. */
|
||
int max_name_length; /* Length of longest file name + frills. */
|
||
int name_length; /* Length of each file name + frills. */
|
||
int pos; /* Current character column. */
|
||
int cols; /* Number of files across. */
|
||
int rows; /* Maximum number of files down. */
|
||
|
||
/* Compute the maximum file name length. */
|
||
max_name_length = 0;
|
||
for (filesno = 0; filesno < files_index; filesno++)
|
||
{
|
||
name_length = length_of_file_name_and_frills (files + filesno);
|
||
if (name_length > max_name_length)
|
||
max_name_length = name_length;
|
||
}
|
||
|
||
/* Allow at least two spaces between names. */
|
||
max_name_length += 2;
|
||
|
||
/* Calculate the maximum number of columns that will fit. */
|
||
cols = line_length / max_name_length;
|
||
if (cols == 0)
|
||
cols = 1;
|
||
/* Calculate the number of rows that will be in each column except possibly
|
||
for a short column on the right. */
|
||
rows = files_index / cols + (files_index % cols != 0);
|
||
/* Recalculate columns based on rows. */
|
||
cols = files_index / rows + (files_index % rows != 0);
|
||
|
||
for (row = 0; row < rows; row++)
|
||
{
|
||
filesno = row;
|
||
pos = 0;
|
||
/* Print the next row. */
|
||
while (1)
|
||
{
|
||
print_file_name_and_frills (files + filesno);
|
||
name_length = length_of_file_name_and_frills (files + filesno);
|
||
|
||
filesno += rows;
|
||
if (filesno >= files_index)
|
||
break;
|
||
|
||
indent (pos + name_length, pos + max_name_length);
|
||
pos += max_name_length;
|
||
}
|
||
putchar ('\n');
|
||
}
|
||
}
|
||
|
||
static void
|
||
print_horizontal (void)
|
||
{
|
||
int filesno;
|
||
int max_name_length;
|
||
int name_length;
|
||
int cols;
|
||
int pos;
|
||
|
||
/* Compute the maximum file name length. */
|
||
max_name_length = 0;
|
||
for (filesno = 0; filesno < files_index; filesno++)
|
||
{
|
||
name_length = length_of_file_name_and_frills (files + filesno);
|
||
if (name_length > max_name_length)
|
||
max_name_length = name_length;
|
||
}
|
||
|
||
/* Allow two spaces between names. */
|
||
max_name_length += 2;
|
||
|
||
cols = line_length / max_name_length;
|
||
if (cols == 0)
|
||
cols = 1;
|
||
|
||
pos = 0;
|
||
name_length = 0;
|
||
|
||
for (filesno = 0; filesno < files_index; filesno++)
|
||
{
|
||
if (filesno != 0)
|
||
{
|
||
if (filesno % cols == 0)
|
||
{
|
||
putchar ('\n');
|
||
pos = 0;
|
||
}
|
||
else
|
||
{
|
||
indent (pos + name_length, pos + max_name_length);
|
||
pos += max_name_length;
|
||
}
|
||
}
|
||
|
||
print_file_name_and_frills (files + filesno);
|
||
|
||
name_length = length_of_file_name_and_frills (files + filesno);
|
||
}
|
||
putchar ('\n');
|
||
}
|
||
|
||
static void
|
||
print_with_commas (void)
|
||
{
|
||
int filesno;
|
||
int pos, old_pos;
|
||
|
||
pos = 0;
|
||
|
||
for (filesno = 0; filesno < files_index; filesno++)
|
||
{
|
||
old_pos = pos;
|
||
|
||
pos += length_of_file_name_and_frills (files + filesno);
|
||
if (filesno + 1 < files_index)
|
||
pos += 2; /* For the comma and space */
|
||
|
||
if (old_pos != 0 && pos >= line_length)
|
||
{
|
||
putchar ('\n');
|
||
pos -= old_pos;
|
||
}
|
||
|
||
print_file_name_and_frills (files + filesno);
|
||
if (filesno + 1 < files_index)
|
||
{
|
||
putchar (',');
|
||
putchar (' ');
|
||
}
|
||
}
|
||
putchar ('\n');
|
||
}
|
||
|
||
/* Assuming cursor is at position FROM, indent up to position TO.
|
||
Use a TAB character instead of two or more spaces whenever possible. */
|
||
|
||
static void
|
||
indent (int from, int to)
|
||
{
|
||
while (from < to)
|
||
{
|
||
if (to / tabsize > (from + 1) / tabsize)
|
||
{
|
||
putchar ('\t');
|
||
from += tabsize - from % tabsize;
|
||
}
|
||
else
|
||
{
|
||
putchar (' ');
|
||
from++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Put DIRNAME/NAME into DEST, handling `.' and `/' properly. */
|
||
|
||
static void
|
||
attach (char *dest, const char *dirname, const char *name)
|
||
{
|
||
const char *dirnamep = dirname;
|
||
|
||
/* Copy dirname if it is not ".". */
|
||
if (dirname[0] != '.' || dirname[1] != 0)
|
||
{
|
||
while (*dirnamep)
|
||
*dest++ = *dirnamep++;
|
||
/* Add '/' if `dirname' doesn't already end with it. */
|
||
if (dirnamep > dirname && dirnamep[-1] != '/')
|
||
*dest++ = '/';
|
||
}
|
||
while (*name)
|
||
*dest++ = *name++;
|
||
*dest = 0;
|
||
}
|
||
|
||
static void
|
||
usage (int status)
|
||
{
|
||
if (status != 0)
|
||
fprintf (stderr, _("Try `%s --help' for more information.\n"),
|
||
program_name);
|
||
else
|
||
{
|
||
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\
|
||
-b, --escape print octal escapes for nongraphic characters\n\
|
||
-C list entries by columns\n\
|
||
-c sort by change time; with -l: show ctime\n\
|
||
--color colorize entries according to type\n\
|
||
--color=WORD yes, no, tty (if output is a terminal)\n\
|
||
-D, --dired generate output well suited to Emacs' dired mode\n\
|
||
-d, --directory list directory entries instead of contents\n\
|
||
-F, --classify append a character for typing each entry\n\
|
||
-f do not sort, enable -aU, disable -lst\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\
|
||
-i, --inode print index number of each file\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\
|
||
-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 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\
|
||
-r, --reverse reverse order while sorting\n\
|
||
-S sort by file size\n"));
|
||
|
||
printf (_("\
|
||
-s, --size print block size of each file\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\
|
||
-t sort by modification time; with -l: show mtime\n\
|
||
-U do not sort; list entries in directory order\n\
|
||
-u sort by last access time; with -l: show atime\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"));
|
||
}
|
||
exit (status);
|
||
}
|