Radix cross Linux

The main Radix cross Linux repository contains the build scripts of packages, which have the most complete and common functionality for desktop machines

452 Commits   2 Branches   1 Tag
Index: coreutils-8.32-new/src/ls.c
===================================================================
--- coreutils-8.32-new/src/ls.c	(nonexistent)
+++ coreutils-8.32-new/src/ls.c	(revision 5)
@@ -0,0 +1,5529 @@
+/* 'dir', 'vdir' and 'ls' directory listing programs for GNU.
+   Copyright (C) 1985-2020 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 3 of the License, 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, see <https://www.gnu.org/licenses/>.  */
+
+/* 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 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 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.  */
+
+/* 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>.  */
+
+#include <config.h>
+#include <sys/types.h>
+
+#include <termios.h>
+#if HAVE_STROPTS_H
+# include <stropts.h>
+#endif
+#include <sys/ioctl.h>
+
+#ifdef WINSIZE_IN_PTEM
+# include <sys/stream.h>
+# include <sys/ptem.h>
+#endif
+
+#ifdef __linux__
+# include <sys/syscall.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <pwd.h>
+#include <getopt.h>
+#include <signal.h>
+#include <selinux/selinux.h>
+#include <wchar.h>
+
+#if HAVE_LANGINFO_CODESET
+# include <langinfo.h>
+#endif
+
+/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
+   present.  */
+#ifndef SA_NOCLDSTOP
+# define SA_NOCLDSTOP 0
+# define sigprocmask(How, Set, Oset) /* empty */
+# define sigset_t int
+# if ! HAVE_SIGINTERRUPT
+#  define siginterrupt(sig, flag) /* empty */
+# endif
+#endif
+
+/* NonStop circa 2011 lacks both SA_RESTART and siginterrupt, so don't
+   restart syscalls after a signal handler fires.  This may cause
+   colors to get messed up on the screen if 'ls' is interrupted, but
+   that's the best we can do on such a platform.  */
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
+
+#include "system.h"
+#include <fnmatch.h>
+
+#include "acl.h"
+#include "argmatch.h"
+#include "c-strcase.h"
+#include "dev-ino.h"
+#include "die.h"
+#include "error.h"
+#include "filenamecat.h"
+#include "hard-locale.h"
+#include "hash.h"
+#include "human.h"
+#include "filemode.h"
+#include "filevercmp.h"
+#include "idcache.h"
+#include "ls.h"
+#include "mbswidth.h"
+#include "mpsort.h"
+#include "obstack.h"
+#include "quote.h"
+#include "smack.h"
+#include "stat-size.h"
+#include "stat-time.h"
+#include "strftime.h"
+#include "xdectoint.h"
+#include "xstrtol.h"
+#include "xstrtol-error.h"
+#include "areadlink.h"
+#include "mbsalign.h"
+#include "dircolors.h"
+#include "xgethostname.h"
+#include "c-ctype.h"
+#include "canonicalize.h"
+#include "statx.h"
+
+/* Include <sys/capability.h> last to avoid a clash of <sys/types.h>
+   include guards with some premature versions of libcap.
+   For more details, see <https://bugzilla.redhat.com/483548>.  */
+#ifdef HAVE_CAP
+# include <sys/capability.h>
+#endif
+
+#define PROGRAM_NAME (ls_mode == LS_LS ? "ls" \
+                      : (ls_mode == LS_MULTI_COL \
+                         ? "dir" : "vdir"))
+
+#define AUTHORS \
+  proper_name ("Richard M. Stallman"), \
+  proper_name ("David MacKenzie")
+
+#define obstack_chunk_alloc malloc
+#define obstack_chunk_free free
+
+/* Return an int indicating the result of comparing two integers.
+   Subtracting doesn't always work, due to overflow.  */
+#define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b))
+
+/* Unix-based readdir implementations have historically returned a dirent.d_ino
+   value that is sometimes not equal to the stat-obtained st_ino value for
+   that same entry.  This error occurs for a readdir entry that refers
+   to a mount point.  readdir's error is to return the inode number of
+   the underlying directory -- one that typically cannot be stat'ed, as
+   long as a file system is mounted on that directory.  RELIABLE_D_INO
+   encapsulates whether we can use the more efficient approach of relying
+   on readdir-supplied d_ino values, or whether we must incur the cost of
+   calling stat or lstat to obtain each guaranteed-valid inode number.  */
+
+#ifndef READDIR_LIES_ABOUT_MOUNTPOINT_D_INO
+# define READDIR_LIES_ABOUT_MOUNTPOINT_D_INO 1
+#endif
+
+#if READDIR_LIES_ABOUT_MOUNTPOINT_D_INO
+# define RELIABLE_D_INO(dp) NOT_AN_INODE_NUMBER
+#else
+# define RELIABLE_D_INO(dp) D_INO (dp)
+#endif
+
+#if ! HAVE_STRUCT_STAT_ST_AUTHOR
+# define st_author st_uid
+#endif
+
+enum filetype
+  {
+    unknown,
+    fifo,
+    chardev,
+    directory,
+    blockdev,
+    normal,
+    symbolic_link,
+    sock,
+    whiteout,
+    arg_directory
+  };
+
+/* Display letters and indicators for each filetype.
+   Keep these in sync with enum filetype.  */
+static char const filetype_letter[] = "?pcdb-lswd";
+
+/* Ensure that filetype and filetype_letter have the same
+   number of elements.  */
+verify (sizeof filetype_letter - 1 == arg_directory + 1);
+
+#define FILETYPE_INDICATORS				\
+  {							\
+    C_ORPHAN, C_FIFO, C_CHR, C_DIR, C_BLK, C_FILE,	\
+    C_LINK, C_SOCK, C_FILE, C_DIR			\
+  }
+
+enum acl_type
+  {
+    ACL_T_NONE,
+    ACL_T_LSM_CONTEXT_ONLY,
+    ACL_T_YES
+  };
+
+struct fileinfo
+  {
+    /* The file name.  */
+    char *name;
+
+    /* For symbolic link, name of the file linked to, otherwise zero.  */
+    char *linkname;
+
+    /* For terminal hyperlinks. */
+    char *absolute_name;
+
+    struct stat stat;
+
+    enum filetype filetype;
+
+    /* For symbolic link and long listing, st_mode of file linked to, otherwise
+       zero.  */
+    mode_t linkmode;
+
+    /* security context.  */
+    char *scontext;
+
+    bool stat_ok;
+
+    /* For symbolic link and color printing, true if linked-to file
+       exists, otherwise false.  */
+    bool linkok;
+
+    /* For long listings, true if the file has an access control list,
+       or a security context.  */
+    enum acl_type acl_type;
+
+    /* For color listings, true if a regular file has capability info.  */
+    bool has_capability;
+
+    /* Whether file name needs quoting. tri-state with -1 == unknown.  */
+    int quoted;
+  };
+
+#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
+  {
+    size_t len;			/* Number of bytes */
+    const char *string;		/* Pointer to the same */
+  };
+
+#if ! HAVE_TCGETPGRP
+# define tcgetpgrp(Fd) 0
+#endif
+
+static size_t quote_name (char const *name,
+                          struct quoting_options const *options,
+                          int needs_general_quoting,
+                          const struct bin_str *color,
+                          bool allow_pad, struct obstack *stack,
+                          char const *absolute_name);
+static size_t quote_name_buf (char **inbuf, size_t bufsize, char *name,
+                              struct quoting_options const *options,
+                              int needs_general_quoting, size_t *width,
+                              bool *pad);
+static char *make_link_name (char const *name, char const *linkname);
+static int decode_switches (int argc, char **argv);
+static bool file_ignored (char const *name);
+static uintmax_t gobble_file (char const *name, enum filetype type,
+                              ino_t inode, bool command_line_arg,
+                              char const *dirname);
+static const struct bin_str * get_color_indicator (const struct fileinfo *f,
+                                                   bool symlink_target);
+static bool print_color_indicator (const struct bin_str *ind);
+static void put_indicator (const struct bin_str *ind);
+static void add_ignore_pattern (const char *pattern);
+static void attach (char *dest, const char *dirname, const char *name);
+static void clear_files (void);
+static void extract_dirs_from_files (char const *dirname,
+                                     bool command_line_arg);
+static void get_link_name (char const *filename, struct fileinfo *f,
+                           bool command_line_arg);
+static void indent (size_t from, size_t to);
+static size_t calculate_columns (bool by_columns);
+static void print_current_files (void);
+static void print_dir (char const *name, char const *realname,
+                       bool command_line_arg);
+static size_t print_file_name_and_frills (const struct fileinfo *f,
+                                          size_t start_col);
+static void print_horizontal (void);
+static int format_user_width (uid_t u);
+static int format_group_width (gid_t g);
+static void print_long_format (const struct fileinfo *f);
+static void print_many_per_line (void);
+static size_t print_name_with_quoting (const struct fileinfo *f,
+                                       bool symlink_target,
+                                       struct obstack *stack,
+                                       size_t start_col);
+static void prep_non_filename_text (void);
+static bool print_type_indicator (bool stat_ok, mode_t mode,
+                                  enum filetype type);
+static void print_with_separator (char sep);
+static void queue_directory (char const *name, char const *realname,
+                             bool command_line_arg);
+static void sort_files (void);
+static void parse_ls_color (void);
+
+static void getenv_quoting_style (void);
+
+/* Initial size of hash table.
+   Most hierarchies are likely to be shallower than this.  */
+#define INITIAL_TABLE_SIZE 30
+
+/* The set of 'active' directories, from the current command-line argument
+   to the level in the hierarchy at which files are being listed.
+   A directory is represented by its device and inode numbers (struct dev_ino).
+   A directory is added to this set when ls begins listing it or its
+   entries, and it is removed from the set just after ls has finished
+   processing it.  This set is used solely to detect loops, e.g., with
+   mkdir loop; cd loop; ln -s ../loop sub; ls -RL  */
+static Hash_table *active_dir_set;
+
+#define LOOP_DETECT (!!active_dir_set)
+
+/* The table of files in the current directory:
+
+   'cwd_file' points to a vector of 'struct fileinfo', one per file.
+   'cwd_n_alloc' is the number of elements space has been allocated for.
+   'cwd_n_used' is the number actually in use.  */
+
+/* Address of block containing the files that are described.  */
+static struct fileinfo *cwd_file;
+
+/* Length of block that 'cwd_file' points to, measured in files.  */
+static size_t cwd_n_alloc;
+
+/* Index of first unused slot in 'cwd_file'.  */
+static size_t cwd_n_used;
+
+/* Whether files needs may need padding due to quoting.  */
+static bool cwd_some_quoted;
+
+/* Whether quoting style _may_ add outer quotes,
+   and whether aligning those is useful.  */
+static bool align_variable_outer_quotes;
+
+/* Vector of pointers to files, in proper sorted order, and the number
+   of entries allocated for it.  */
+static void **sorted_file;
+static size_t sorted_file_alloc;
+
+/* When true, in a color listing, color each symlink name according to the
+   type of file it points to.  Otherwise, color them according to the 'ln'
+   directive in LS_COLORS.  Dangling (orphan) symlinks are treated specially,
+   regardless.  This is set when 'ln=target' appears in LS_COLORS.  */
+
+static bool color_symlink_as_referent;
+
+static char const *hostname;
+
+/* mode of appropriate file for colorization */
+#define FILE_OR_LINK_MODE(File) \
+    ((color_symlink_as_referent && (File)->linkok) \
+     ? (File)->linkmode : (File)->stat.st_mode)
+
+
+/* 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;
+    bool command_line_arg;
+    struct pending *next;
+  };
+
+static struct pending *pending_dirs;
+
+/* Current time in seconds and nanoseconds since 1970, updated as
+   needed when deciding whether a file is recent.  */
+
+static struct timespec current_time;
+
+static bool print_scontext;
+static char UNKNOWN_SECURITY_CONTEXT[] = "?";
+
+/* Whether any of the files has an ACL.  This affects the width of the
+   mode column.  */
+
+static bool any_has_acl;
+
+/* The number of columns to use for columns containing inode numbers,
+   block sizes, link counts, owners, groups, authors, major device
+   numbers, minor device numbers, and file sizes, respectively.  */
+
+static int inode_number_width;
+static int block_size_width;
+static int nlink_width;
+static int scontext_width;
+static int owner_width;
+static int group_width;
+static int author_width;
+static int major_device_number_width;
+static int minor_device_number_width;
+static int file_size_width;
+
+/* 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 (and other options that imply -l), -1, -C, -x and -m control
+   this parameter.  */
+
+enum format
+  {
+    long_format,		/* -l and other options that imply -l */
+    one_per_line,		/* -1 */
+    many_per_line,		/* -C */
+    horizontal,			/* -x */
+    with_commas			/* -m */
+  };
+
+static enum format format;
+
+/* 'full-iso' uses full ISO-style dates and times.  'long-iso' uses longer
+   ISO-style timestamps, though shorter than 'full-iso'.  'iso' uses shorter
+   ISO-style timestamps.  'locale' uses locale-dependent timestamps.  */
+enum time_style
+  {
+    full_iso_time_style,	/* --time-style=full-iso */
+    long_iso_time_style,	/* --time-style=long-iso */
+    iso_time_style,		/* --time-style=iso */
+    locale_time_style		/* --time-style=locale */
+  };
+
+static char const *const time_style_args[] =
+{
+  "full-iso", "long-iso", "iso", "locale", NULL
+};
+static enum time_style const time_style_types[] =
+{
+  full_iso_time_style, long_iso_time_style, iso_time_style,
+  locale_time_style
+};
+ARGMATCH_VERIFY (time_style_args, time_style_types);
+
+/* Type of time to print or sort by.  Controlled by -c and -u.
+   The values of each item of this enum are important since they are
+   used as indices in the sort functions array (see sort_files()).  */
+
+enum time_type
+  {
+    time_mtime,			/* default */
+    time_ctime,			/* -c */
+    time_atime,			/* -u */
+    time_btime,                 /* birth time */
+    time_numtypes		/* the number of elements of this enum */
+  };
+
+static enum time_type time_type;
+
+/* The file characteristic to sort by.  Controlled by -t, -S, -U, -X, -v.
+   The values of each item of this enum are important since they are
+   used as indices in the sort functions array (see sort_files()).  */
+
+enum sort_type
+  {
+    sort_none = -1,		/* -U */
+    sort_name,			/* default */
+    sort_extension,		/* -X */
+    sort_size,			/* -S */
+    sort_version,		/* -v */
+    sort_time,			/* -t */
+    sort_numtypes		/* the number of elements of this enum */
+  };
+
+static enum sort_type sort_type;
+
+/* Direction of sort.
+   false means highest first if numeric,
+   lowest first if alphabetic;
+   these are the defaults.
+   true means the opposite order in each case.  -r  */
+
+static bool sort_reverse;
+
+/* True means to display owner information.  -g turns this off.  */
+
+static bool print_owner = true;
+
+/* True means to display author information.  */
+
+static bool print_author;
+
+/* True means to display group information.  -G and -o turn this off.  */
+
+static bool print_group = true;
+
+/* True means print the user and group id's as numbers rather
+   than as names.  -n  */
+
+static bool numeric_ids;
+
+/* True means mention the size in blocks of each file.  -s  */
+
+static bool print_block_size;
+
+/* Human-readable options for output, when printing block counts.  */
+static int human_output_opts;
+
+/* The units to use when printing block counts.  */
+static uintmax_t output_block_size;
+
+/* Likewise, but for file sizes.  */
+static int file_human_output_opts;
+static uintmax_t file_output_block_size = 1;
+
+/* Follow the output with a special string.  Using this format,
+   Emacs' dired mode starts up twice as fast, and can handle all
+   strange characters in file names.  */
+static bool dired;
+
+/* 'none' means don't mention the type of files.
+   'slash' means mention directories only, with a '/'.
+   'file_type' means mention file types.
+   'classify' means mention file types and mark executables.
+
+   Controlled by -F, -p, and --indicator-style.  */
+
+enum indicator_style
+  {
+    none,	/*     --indicator-style=none */
+    slash,	/* -p, --indicator-style=slash */
+    file_type,	/*     --indicator-style=file-type */
+    classify	/* -F, --indicator-style=classify */
+  };
+
+static enum indicator_style indicator_style;
+
+/* Names of indicator styles.  */
+static char const *const indicator_style_args[] =
+{
+  "none", "slash", "file-type", "classify", NULL
+};
+static enum indicator_style const indicator_style_types[] =
+{
+  none, slash, file_type, classify
+};
+ARGMATCH_VERIFY (indicator_style_args, indicator_style_types);
+
+/* True 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 bool print_with_color;
+
+static bool print_hyperlink;
+
+/* Whether we used any colors in the output so far.  If so, we will
+   need to restore the default color later.  If not, we will need to
+   call prep_non_filename_text before using color for the first time. */
+
+static bool used_color = false;
+
+enum when_type
+  {
+    when_never,		/* 0: default or --color=never */
+    when_always,	/* 1: --color=always */
+    when_if_tty		/* 2: --color=tty */
+  };
+
+enum Dereference_symlink
+  {
+    DEREF_UNDEFINED = 1,
+    DEREF_NEVER,
+    DEREF_COMMAND_LINE_ARGUMENTS,	/* -H */
+    DEREF_COMMAND_LINE_SYMLINK_TO_DIR,	/* the default, in certain cases */
+    DEREF_ALWAYS			/* -L */
+  };
+
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_RESET, C_NORM, C_FILE, C_DIR, C_LINK,
+    C_FIFO, C_SOCK,
+    C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR, C_SETUID, C_SETGID,
+    C_STICKY, C_OTHER_WRITABLE, C_STICKY_OTHER_WRITABLE, C_CAP, C_MULTIHARDLINK,
+    C_CLR_TO_EOL
+  };
+
+static const char *const indicator_name[]=
+  {
+    "lc", "rc", "ec", "rs", "no", "fi", "di", "ln", "pi", "so",
+    "bd", "cd", "mi", "or", "ex", "do", "su", "sg", "st",
+    "ow", "tw", "ca", "mh", "cl", NULL
+  };
+
+struct color_ext_type
+  {
+    struct bin_str ext;		/* The extension we're looking for */
+    struct bin_str seq;		/* The sequence to output when we do */
+    struct color_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+rs+rc) */
+    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
+    { 0, NULL },			/* no: Normal */
+    { 0, NULL },			/* 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: Orphaned symlink: undefined */
+    { LEN_STR_PAIR ("01;32") },		/* ex: Executable: bright green */
+    { LEN_STR_PAIR ("01;35") },		/* do: Door: bright magenta */
+    { LEN_STR_PAIR ("37;41") },		/* su: setuid: white on red */
+    { LEN_STR_PAIR ("30;43") },		/* sg: setgid: black on yellow */
+    { LEN_STR_PAIR ("37;44") },		/* st: sticky: black on blue */
+    { LEN_STR_PAIR ("34;42") },		/* ow: other-writable: blue on green */
+    { LEN_STR_PAIR ("30;42") },		/* tw: ow w/ sticky: black on green */
+    { LEN_STR_PAIR ("30;41") },		/* ca: black on red */
+    { 0, NULL },			/* mh: disabled by default */
+    { LEN_STR_PAIR ("\033[K") },	/* cl: clear to end of line */
+  };
+
+/* FIXME: comment  */
+static struct color_ext_type *color_ext_list = NULL;
+
+/* Buffer for color sequences */
+static char *color_buf;
+
+/* True means to check for orphaned symbolic link, for displaying
+   colors, or to group symlink to directories with other dirs.  */
+
+static bool check_symlink_mode;
+
+/* True means mention the inode number of each file.  -i  */
+
+static bool print_inode;
+
+/* What to do with symbolic links.  Affected by -d, -F, -H, -l (and
+   other options that imply -l), and -L.  */
+
+static enum Dereference_symlink dereference;
+
+/* True means when a directory is found, display info on its
+   contents.  -R  */
+
+static bool recursive;
+
+/* True means when an argument is a directory name, display info
+   on it itself.  -d  */
+
+static bool immediate_dirs;
+
+/* True means that directories are grouped before files. */
+
+static bool directories_first;
+
+/* Which files to ignore.  */
+
+static enum
+{
+  /* Ignore files whose names start with '.', and files specified by
+     --hide and --ignore.  */
+  IGNORE_DEFAULT,
+
+  /* Ignore '.', '..', and files specified by --ignore.  */
+  IGNORE_DOT_AND_DOTDOT,
+
+  /* Ignore only files specified by --ignore.  */
+  IGNORE_MINIMAL
+} ignore_mode;
+
+/* A linked list of shell-style globbing patterns.  If a non-argument
+   file name matches any of these patterns, it is ignored.
+   Controlled by -I.  Multiple -I options accumulate.
+   The -B option adds '*~' and '.*~' to this list.  */
+
+struct ignore_pattern
+  {
+    const char *pattern;
+    struct ignore_pattern *next;
+  };
+
+static struct ignore_pattern *ignore_patterns;
+
+/* Similar to IGNORE_PATTERNS, except that -a or -A causes this
+   variable itself to be ignored.  */
+static struct ignore_pattern *hide_patterns;
+
+/* True means output nongraphic chars in file names as '?'.
+   (-q, --hide-control-chars)
+   qmark_funny_chars and the quoting style (-Q, --quoting-style=WORD) are
+   independent.  The algorithm is: first, obey the quoting style to get a
+   string representing the file name;  then, if qmark_funny_chars is set,
+   replace all nonprintable chars in that string with '?'.  It's necessary
+   to replace nonprintable chars even in quoted strings, because we don't
+   want to mess up the terminal if control chars get sent to it, and some
+   quoting methods pass through control chars as-is.  */
+static bool qmark_funny_chars;
+
+/* Quoting options for file and dir name output.  */
+
+static struct quoting_options *filename_quoting_options;
+static struct quoting_options *dirname_quoting_options;
+
+/* The number of chars per hardware tab stop.  Setting this to zero
+   inhibits the use of TAB characters for separating columns.  -T */
+static size_t tabsize;
+
+/* True means print each directory name before listing it.  */
+
+static bool print_dir_name;
+
+/* The line length to use for breaking lines in many-per-line format.
+   Can be set with -w.  */
+
+static size_t line_length;
+
+/* The local time zone rules, as per the TZ environment variable.  */
+
+static timezone_t localtz;
+
+/* If true, the file listing format requires that stat be called on
+   each file.  */
+
+static bool format_needs_stat;
+
+/* Similar to 'format_needs_stat', but set if only the file type is
+   needed.  */
+
+static bool format_needs_type;
+
+/* An arbitrary limit on the number of bytes in a printed timestamp.
+   This is set to a relatively small value to avoid the need to worry
+   about denial-of-service attacks on servers that run "ls" on behalf
+   of remote clients.  1000 bytes should be enough for any practical
+   timestamp format.  */
+
+enum { TIME_STAMP_LEN_MAXIMUM = MAX (1000, INT_STRLEN_BOUND (time_t)) };
+
+/* strftime formats for non-recent and recent files, respectively, in
+   -l output.  */
+
+static char const *long_time_format[2] =
+  {
+    /* strftime format for non-recent files (older than 6 months), in
+       -l output.  This should contain the year, month and day (at
+       least), in an order that is understood by people in your
+       locale's territory.  Please try to keep the number of used
+       screen columns small, because many people work in windows with
+       only 80 columns.  But make this as wide as the other string
+       below, for recent files.  */
+    /* TRANSLATORS: ls output needs to be aligned for ease of reading,
+       so be wary of using variable width fields from the locale.
+       Note %b is handled specially by ls and aligned correctly.
+       Note also that specifying a width as in %5b is erroneous as strftime
+       will count bytes rather than characters in multibyte locales.  */
+    N_("%b %e  %Y"),
+    /* strftime format for recent files (younger than 6 months), in -l
+       output.  This should contain the month, day and time (at
+       least), in an order that is understood by people in your
+       locale's territory.  Please try to keep the number of used
+       screen columns small, because many people work in windows with
+       only 80 columns.  But make this as wide as the other string
+       above, for non-recent files.  */
+    /* TRANSLATORS: ls output needs to be aligned for ease of reading,
+       so be wary of using variable width fields from the locale.
+       Note %b is handled specially by ls and aligned correctly.
+       Note also that specifying a width as in %5b is erroneous as strftime
+       will count bytes rather than characters in multibyte locales.  */
+    N_("%b %e %H:%M")
+  };
+
+/* The set of signals that are caught.  */
+
+static sigset_t caught_signals;
+
+/* If nonzero, the value of the pending fatal signal.  */
+
+static sig_atomic_t volatile interrupt_signal;
+
+/* A count of the number of pending stop signals that have been received.  */
+
+static sig_atomic_t volatile stop_signal_count;
+
+/* Desired exit status.  */
+
+static int exit_status;
+
+/* Exit statuses.  */
+enum
+  {
+    /* "ls" had a minor problem.  E.g., while processing a directory,
+       ls obtained the name of an entry via readdir, yet was later
+       unable to stat that name.  This happens when listing a directory
+       in which entries are actively being removed or renamed.  */
+    LS_MINOR_PROBLEM = 1,
+
+    /* "ls" had more serious trouble (e.g., memory exhausted, invalid
+       option or failure to stat a command line argument.  */
+    LS_FAILURE = 2
+  };
+
+/* For long options that have no equivalent short option, use a
+   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
+enum
+{
+  AUTHOR_OPTION = CHAR_MAX + 1,
+  BLOCK_SIZE_OPTION,
+  COLOR_OPTION,
+  DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION,
+  FILE_TYPE_INDICATOR_OPTION,
+  FORMAT_OPTION,
+  FULL_TIME_OPTION,
+  GROUP_DIRECTORIES_FIRST_OPTION,
+  HIDE_OPTION,
+  HYPERLINK_OPTION,
+  INDICATOR_STYLE_OPTION,
+  QUOTING_STYLE_OPTION,
+  SHOW_CONTROL_CHARS_OPTION,
+  SI_OPTION,
+  SORT_OPTION,
+  TIME_OPTION,
+  TIME_STYLE_OPTION
+};
+
+static struct option const long_options[] =
+{
+  {"all", no_argument, NULL, 'a'},
+  {"escape", no_argument, NULL, 'b'},
+  {"directory", no_argument, NULL, 'd'},
+  {"dired", no_argument, NULL, 'D'},
+  {"full-time", no_argument, NULL, FULL_TIME_OPTION},
+  {"group-directories-first", no_argument, NULL,
+   GROUP_DIRECTORIES_FIRST_OPTION},
+  {"human-readable", no_argument, NULL, 'h'},
+  {"inode", no_argument, NULL, 'i'},
+  {"kibibytes", no_argument, NULL, 'k'},
+  {"numeric-uid-gid", no_argument, NULL, 'n'},
+  {"no-group", no_argument, NULL, 'G'},
+  {"hide-control-chars", no_argument, NULL, 'q'},
+  {"reverse", no_argument, NULL, 'r'},
+  {"size", no_argument, NULL, 's'},
+  {"width", required_argument, NULL, 'w'},
+  {"almost-all", no_argument, NULL, 'A'},
+  {"ignore-backups", no_argument, NULL, 'B'},
+  {"classify", no_argument, NULL, 'F'},
+  {"file-type", no_argument, NULL, FILE_TYPE_INDICATOR_OPTION},
+  {"si", no_argument, NULL, SI_OPTION},
+  {"dereference-command-line", no_argument, NULL, 'H'},
+  {"dereference-command-line-symlink-to-dir", no_argument, NULL,
+   DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION},
+  {"hide", required_argument, NULL, HIDE_OPTION},
+  {"ignore", required_argument, NULL, 'I'},
+  {"indicator-style", required_argument, NULL, INDICATOR_STYLE_OPTION},
+  {"dereference", no_argument, NULL, 'L'},
+  {"literal", no_argument, NULL, 'N'},
+  {"quote-name", no_argument, NULL, 'Q'},
+  {"quoting-style", required_argument, NULL, QUOTING_STYLE_OPTION},
+  {"recursive", no_argument, NULL, 'R'},
+  {"format", required_argument, NULL, FORMAT_OPTION},
+  {"show-control-chars", no_argument, NULL, SHOW_CONTROL_CHARS_OPTION},
+  {"sort", required_argument, NULL, SORT_OPTION},
+  {"tabsize", required_argument, NULL, 'T'},
+  {"time", required_argument, NULL, TIME_OPTION},
+  {"time-style", required_argument, NULL, TIME_STYLE_OPTION},
+  {"color", optional_argument, NULL, COLOR_OPTION},
+  {"hyperlink", optional_argument, NULL, HYPERLINK_OPTION},
+  {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION},
+  {"context", no_argument, 0, 'Z'},
+  {"author", no_argument, NULL, AUTHOR_OPTION},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
+static char const *const format_args[] =
+{
+  "verbose", "long", "commas", "horizontal", "across",
+  "vertical", "single-column", NULL
+};
+static enum format const format_types[] =
+{
+  long_format, long_format, with_commas, horizontal, horizontal,
+  many_per_line, one_per_line
+};
+ARGMATCH_VERIFY (format_args, format_types);
+
+static char const *const sort_args[] =
+{
+  "none", "time", "size", "extension", "version", NULL
+};
+static enum sort_type const sort_types[] =
+{
+  sort_none, sort_time, sort_size, sort_extension, sort_version
+};
+ARGMATCH_VERIFY (sort_args, sort_types);
+
+static char const *const time_args[] =
+{
+  "atime", "access", "use",
+  "ctime", "status",
+  "birth", "creation",
+  NULL
+};
+static enum time_type const time_types[] =
+{
+  time_atime, time_atime, time_atime,
+  time_ctime, time_ctime,
+  time_btime, time_btime,
+};
+ARGMATCH_VERIFY (time_args, time_types);
+
+static char const *const when_args[] =
+{
+  /* force and none are for compatibility with another color-ls version */
+  "always", "yes", "force",
+  "never", "no", "none",
+  "auto", "tty", "if-tty", NULL
+};
+static enum when_type const when_types[] =
+{
+  when_always, when_always, when_always,
+  when_never, when_never, when_never,
+  when_if_tty, when_if_tty, when_if_tty
+};
+ARGMATCH_VERIFY (when_args, when_types);
+
+/* Information about filling a column.  */
+struct column_info
+{
+  bool valid_len;
+  size_t line_len;
+  size_t *col_arr;
+};
+
+/* Array with information about column filledness.  */
+static struct column_info *column_info;
+
+/* Maximum number of columns ever possible for this display.  */
+static size_t max_idx;
+
+/* The minimum width of a column is 3: 1 character for the name and 2
+   for the separating white space.  */
+#define MIN_COLUMN_WIDTH	3
+
+
+/* This zero-based index is used solely with the --dired option.
+   When that option is in effect, this counter is incremented for each
+   byte 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 DIRED_PUTCHAR(c) do {putchar ((c)); ++dired_pos;} while (0)
+
+/* Write S to STREAM and increment DIRED_POS by S_LEN.  */
+#define DIRED_FPUTS(s, stream, s_len) \
+    do {fputs (s, stream); dired_pos += s_len;} while (0)
+
+/* Like DIRED_FPUTS, but for use when S is a literal string.  */
+#define DIRED_FPUTS_LITERAL(s, stream) \
+    do {fputs (s, stream); dired_pos += sizeof (s) - 1;} while (0)
+
+#define DIRED_INDENT()							\
+    do									\
+      {									\
+        if (dired)							\
+          DIRED_FPUTS_LITERAL ("  ", stdout);				\
+      }									\
+    while (0)
+
+/* With --dired, store pairs of beginning and ending indices of file names.  */
+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									\
+    {									\
+      if (dired)							\
+        obstack_grow (obs, &dired_pos, sizeof (dired_pos));		\
+    }									\
+  while (0)
+
+/* With -R, this stack is used to help detect directory cycles.
+   The device/inode pairs on this stack mirror the pairs in the
+   active_dir_set hash table.  */
+static struct obstack dev_ino_obstack;
+
+/* Push a pair onto the device/inode stack.  */
+static void
+dev_ino_push (dev_t dev, ino_t ino)
+{
+  void *vdi;
+  struct dev_ino *di;
+  int dev_ino_size = sizeof *di;
+  obstack_blank (&dev_ino_obstack, dev_ino_size);
+  vdi = obstack_next_free (&dev_ino_obstack);
+  di = vdi;
+  di--;
+  di->st_dev = dev;
+  di->st_ino = ino;
+}
+
+/* Pop a dev/ino struct off the global dev_ino_obstack
+   and return that struct.  */
+static struct dev_ino
+dev_ino_pop (void)
+{
+  void *vdi;
+  struct dev_ino *di;
+  int dev_ino_size = sizeof *di;
+  assert (dev_ino_size <= obstack_object_size (&dev_ino_obstack));
+  obstack_blank_fast (&dev_ino_obstack, -dev_ino_size);
+  vdi = obstack_next_free (&dev_ino_obstack);
+  di = vdi;
+  return *di;
+}
+
+/* Note the use commented out below:
+#define ASSERT_MATCHING_DEV_INO(Name, Di)	\
+  do						\
+    {						\
+      struct stat sb;				\
+      assert (Name);				\
+      assert (0 <= stat (Name, &sb));		\
+      assert (sb.st_dev == Di.st_dev);		\
+      assert (sb.st_ino == Di.st_ino);		\
+    }						\
+  while (0)
+*/
+
+/* Write to standard output PREFIX, followed by the quoting style and
+   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)
+{
+  size_t n_pos;
+
+  n_pos = obstack_object_size (os) / sizeof (dired_pos);
+  if (n_pos > 0)
+    {
+      size_t *pos = (size_t *) obstack_finish (os);
+      fputs (prefix, stdout);
+      for (size_t i = 0; i < n_pos; i++)
+        printf (" %lu", (unsigned long int) pos[i]);
+      putchar ('\n');
+    }
+}
+
+/* Return the platform birthtime member of the stat structure,
+   or fallback to the mtime member, which we have populated
+   from the statx structure or reset to an invalid timestamp
+   where birth time is not supported.  */
+static struct timespec
+get_stat_btime (struct stat const *st)
+{
+  struct timespec btimespec;
+
+#if HAVE_STATX && defined STATX_INO
+  btimespec = get_stat_mtime (st);
+#else
+  btimespec = get_stat_birthtime (st);
+#endif
+
+  return btimespec;
+}
+
+#if HAVE_STATX && defined STATX_INO
+static unsigned int _GL_ATTRIBUTE_PURE
+time_type_to_statx (void)
+{
+  switch (time_type)
+    {
+    case time_ctime:
+      return STATX_CTIME;
+    case time_mtime:
+      return STATX_MTIME;
+    case time_atime:
+      return STATX_ATIME;
+    case time_btime:
+      return STATX_BTIME;
+    default:
+      abort ();
+    }
+    return 0;
+}
+
+static unsigned int _GL_ATTRIBUTE_PURE
+calc_req_mask (void)
+{
+  unsigned int mask = STATX_MODE;
+
+  if (print_inode)
+    mask |= STATX_INO;
+
+  if (print_block_size)
+    mask |= STATX_BLOCKS;
+
+  if (format == long_format) {
+    mask |= STATX_NLINK | STATX_SIZE | time_type_to_statx ();
+    if (print_owner || print_author)
+      mask |= STATX_UID;
+    if (print_group)
+      mask |= STATX_GID;
+  }
+
+  switch (sort_type)
+    {
+    case sort_none:
+    case sort_name:
+    case sort_version:
+    case sort_extension:
+      break;
+    case sort_time:
+      mask |= time_type_to_statx ();
+      break;
+    case sort_size:
+      mask |= STATX_SIZE;
+      break;
+    default:
+      abort ();
+    }
+
+  return mask;
+}
+
+static int
+do_statx (int fd, const char *name, struct stat *st, int flags,
+          unsigned int mask)
+{
+  struct statx stx;
+  bool want_btime = mask & STATX_BTIME;
+  int ret = statx (fd, name, flags, mask, &stx);
+  if (ret >= 0)
+    {
+      statx_to_stat (&stx, st);
+      /* Since we only need one timestamp type,
+         store birth time in st_mtim.  */
+      if (want_btime)
+        {
+          if (stx.stx_mask & STATX_BTIME)
+            st->st_mtim = statx_timestamp_to_timespec (stx.stx_btime);
+          else
+            st->st_mtim.tv_sec = st->st_mtim.tv_nsec = -1;
+        }
+    }
+
+  return ret;
+}
+
+static inline int
+do_stat (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, 0, calc_req_mask ());
+}
+
+static inline int
+do_lstat (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, AT_SYMLINK_NOFOLLOW, calc_req_mask ());
+}
+
+static inline int
+stat_for_mode (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, 0, STATX_MODE);
+}
+
+/* dev+ino should be static, so no need to sync with backing store */
+static inline int
+stat_for_ino (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, 0, STATX_INO);
+}
+
+static inline int
+fstat_for_ino (int fd, struct stat *st)
+{
+  return do_statx (fd, "", st, AT_EMPTY_PATH, STATX_INO);
+}
+#else
+static inline int
+do_stat (const char *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+static inline int
+do_lstat (const char *name, struct stat *st)
+{
+  return lstat (name, st);
+}
+
+static inline int
+stat_for_mode (const char *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+static inline int
+stat_for_ino (const char *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+static inline int
+fstat_for_ino (int fd, struct stat *st)
+{
+  return fstat (fd, st);
+}
+#endif
+
+/* Return the address of the first plain %b spec in FMT, or NULL if
+   there is no such spec.  %5b etc. do not match, so that user
+   widths/flags are honored.  */
+
+static char const * _GL_ATTRIBUTE_PURE
+first_percent_b (char const *fmt)
+{
+  for (; *fmt; fmt++)
+    if (fmt[0] == '%')
+      switch (fmt[1])
+        {
+        case 'b': return fmt;
+        case '%': fmt++; break;
+        }
+  return NULL;
+}
+
+static char RFC3986[256];
+static void
+file_escape_init (void)
+{
+  for (int i = 0; i < 256; i++)
+    RFC3986[i] |= c_isalnum (i) || i == '~' || i == '-' || i == '.' || i == '_';
+}
+
+/* Read the abbreviated month names from the locale, to align them
+   and to determine the max width of the field and to truncate names
+   greater than our max allowed.
+   Note even though this handles multibyte locales correctly
+   it's not restricted to them as single byte locales can have
+   variable width abbreviated months and also precomputing/caching
+   the names was seen to increase the performance of ls significantly.  */
+
+/* max number of display cells to use.
+   As of 2018 the abmon for Arabic has entries with width 12.
+   It doesn't make much sense to support wider than this
+   and locales should aim for abmon entries of width <= 5.  */
+enum { MAX_MON_WIDTH = 12 };
+/* abformat[RECENT][MON] is the format to use for timestamps with
+   recentness RECENT and month MON.  */
+enum { ABFORMAT_SIZE = 128 };
+static char abformat[2][12][ABFORMAT_SIZE];
+/* True if precomputed formats should be used.  This can be false if
+   nl_langinfo fails, if a format or month abbreviation is unusually
+   long, or if a month abbreviation contains '%'.  */
+static bool use_abformat;
+
+/* Store into ABMON the abbreviated month names, suitably aligned.
+   Return true if successful.  */
+
+static bool
+abmon_init (char abmon[12][ABFORMAT_SIZE])
+{
+#ifndef HAVE_NL_LANGINFO
+  return false;
+#else
+  size_t required_mon_width = MAX_MON_WIDTH;
+  size_t curr_max_width;
+  do
+    {
+      curr_max_width = required_mon_width;
+      required_mon_width = 0;
+      for (int i = 0; i < 12; i++)
+        {
+          size_t width = curr_max_width;
+          char const *abbr = nl_langinfo (ABMON_1 + i);
+          if (strchr (abbr, '%'))
+            return false;
+          mbs_align_t alignment = isdigit (to_uchar (*abbr))
+                                  ? MBS_ALIGN_RIGHT : MBS_ALIGN_LEFT;
+          size_t req = mbsalign (abbr, abmon[i], ABFORMAT_SIZE,
+                                 &width, alignment, 0);
+          if (! (req < ABFORMAT_SIZE))
+            return false;
+          required_mon_width = MAX (required_mon_width, width);
+        }
+    }
+  while (curr_max_width > required_mon_width);
+
+  return true;
+#endif
+}
+
+/* Initialize ABFORMAT and USE_ABFORMAT.  */
+
+static void
+abformat_init (void)
+{
+  char const *pb[2];
+  for (int recent = 0; recent < 2; recent++)
+    pb[recent] = first_percent_b (long_time_format[recent]);
+  if (! (pb[0] || pb[1]))
+    return;
+
+  char abmon[12][ABFORMAT_SIZE];
+  if (! abmon_init (abmon))
+    return;
+
+  for (int recent = 0; recent < 2; recent++)
+    {
+      char const *fmt = long_time_format[recent];
+      for (int i = 0; i < 12; i++)
+        {
+          char *nfmt = abformat[recent][i];
+          int nbytes;
+
+          if (! pb[recent])
+            nbytes = snprintf (nfmt, ABFORMAT_SIZE, "%s", fmt);
+          else
+            {
+              if (! (pb[recent] - fmt <= MIN (ABFORMAT_SIZE, INT_MAX)))
+                return;
+              int prefix_len = pb[recent] - fmt;
+              nbytes = snprintf (nfmt, ABFORMAT_SIZE, "%.*s%s%s",
+                                 prefix_len, fmt, abmon[i], pb[recent] + 2);
+            }
+
+          if (! (0 <= nbytes && nbytes < ABFORMAT_SIZE))
+            return;
+        }
+    }
+
+  use_abformat = true;
+}
+
+static size_t
+dev_ino_hash (void const *x, size_t table_size)
+{
+  struct dev_ino const *p = x;
+  return (uintmax_t) p->st_ino % table_size;
+}
+
+static bool
+dev_ino_compare (void const *x, void const *y)
+{
+  struct dev_ino const *a = x;
+  struct dev_ino const *b = y;
+  return SAME_INODE (*a, *b) ? true : false;
+}
+
+static void
+dev_ino_free (void *x)
+{
+  free (x);
+}
+
+/* Add the device/inode pair (P->st_dev/P->st_ino) to the set of
+   active directories.  Return true if there is already a matching
+   entry in the table.  */
+
+static bool
+visit_dir (dev_t dev, ino_t ino)
+{
+  struct dev_ino *ent;
+  struct dev_ino *ent_from_table;
+  bool found_match;
+
+  ent = xmalloc (sizeof *ent);
+  ent->st_ino = ino;
+  ent->st_dev = dev;
+
+  /* Attempt to insert this entry into the table.  */
+  ent_from_table = hash_insert (active_dir_set, ent);
+
+  if (ent_from_table == NULL)
+    {
+      /* Insertion failed due to lack of memory.  */
+      xalloc_die ();
+    }
+
+  found_match = (ent_from_table != ent);
+
+  if (found_match)
+    {
+      /* ent was not inserted, so free it.  */
+      free (ent);
+    }
+
+  return found_match;
+}
+
+static void
+free_pending_ent (struct pending *p)
+{
+  free (p->name);
+  free (p->realname);
+  free (p);
+}
+
+static bool
+is_colored (enum indicator_no type)
+{
+  size_t len = color_indicator[type].len;
+  char const *s = color_indicator[type].string;
+  return ! (len == 0
+            || (len == 1 && STRNCMP_LIT (s, "0") == 0)
+            || (len == 2 && STRNCMP_LIT (s, "00") == 0));
+}
+
+static void
+restore_default_color (void)
+{
+  put_indicator (&color_indicator[C_LEFT]);
+  put_indicator (&color_indicator[C_RIGHT]);
+}
+
+static void
+set_normal_color (void)
+{
+  if (print_with_color && is_colored (C_NORM))
+    {
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (&color_indicator[C_NORM]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
+}
+
+/* An ordinary signal was received; arrange for the program to exit.  */
+
+static void
+sighandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, SIG_IGN);
+  if (! interrupt_signal)
+    interrupt_signal = sig;
+}
+
+/* A SIGTSTP was received; arrange for the program to suspend itself.  */
+
+static void
+stophandler (int sig)
+{
+  if (! SA_NOCLDSTOP)
+    signal (sig, stophandler);
+  if (! interrupt_signal)
+    stop_signal_count++;
+}
+
+/* Process any pending signals.  If signals are caught, this function
+   should be called periodically.  Ideally there should never be an
+   unbounded amount of time when signals are not being processed.
+   Signal handling can restore the default colors, so callers must
+   immediately change colors after invoking this function.  */
+
+static void
+process_signals (void)
+{
+  while (interrupt_signal || stop_signal_count)
+    {
+      int sig;
+      int stops;
+      sigset_t oldset;
+
+      if (used_color)
+        restore_default_color ();
+      fflush (stdout);
+
+      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+
+      /* Reload interrupt_signal and stop_signal_count, in case a new
+         signal was handled before sigprocmask took effect.  */
+      sig = interrupt_signal;
+      stops = stop_signal_count;
+
+      /* SIGTSTP is special, since the application can receive that signal
+         more than once.  In this case, don't set the signal handler to the
+         default.  Instead, just raise the uncatchable SIGSTOP.  */
+      if (stops)
+        {
+          stop_signal_count = stops - 1;
+          sig = SIGSTOP;
+        }
+      else
+        signal (sig, SIG_DFL);
+
+      /* Exit or suspend the program.  */
+      raise (sig);
+      sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+      /* If execution reaches here, then the program has been
+         continued (after being suspended).  */
+    }
+}
+
+/* Setup signal handlers if INIT is true,
+   otherwise restore to the default.  */
+
+static void
+signal_setup (bool init)
+{
+  /* The signals that are trapped, and the number of such signals.  */
+  static int const sig[] =
+    {
+      /* This one is handled specially.  */
+      SIGTSTP,
+
+      /* The usual suspects.  */
+      SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+#ifdef SIGPOLL
+      SIGPOLL,
+#endif
+#ifdef SIGPROF
+      SIGPROF,
+#endif
+#ifdef SIGVTALRM
+      SIGVTALRM,
+#endif
+#ifdef SIGXCPU
+      SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+      SIGXFSZ,
+#endif
+    };
+  enum { nsigs = ARRAY_CARDINALITY (sig) };
+
+#if ! SA_NOCLDSTOP
+  static bool caught_sig[nsigs];
+#endif
+
+  int j;
+
+  if (init)
+    {
+#if SA_NOCLDSTOP
+      struct sigaction act;
+
+      sigemptyset (&caught_signals);
+      for (j = 0; j < nsigs; j++)
+        {
+          sigaction (sig[j], NULL, &act);
+          if (act.sa_handler != SIG_IGN)
+            sigaddset (&caught_signals, sig[j]);
+        }
+
+      act.sa_mask = caught_signals;
+      act.sa_flags = SA_RESTART;
+
+      for (j = 0; j < nsigs; j++)
+        if (sigismember (&caught_signals, sig[j]))
+          {
+            act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler;
+            sigaction (sig[j], &act, NULL);
+          }
+#else
+      for (j = 0; j < nsigs; j++)
+        {
+          caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN);
+          if (caught_sig[j])
+            {
+              signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler);
+              siginterrupt (sig[j], 0);
+            }
+        }
+#endif
+    }
+  else /* restore.  */
+    {
+#if SA_NOCLDSTOP
+      for (j = 0; j < nsigs; j++)
+        if (sigismember (&caught_signals, sig[j]))
+          signal (sig[j], SIG_DFL);
+#else
+      for (j = 0; j < nsigs; j++)
+        if (caught_sig[j])
+          signal (sig[j], SIG_DFL);
+#endif
+    }
+}
+
+static inline void
+signal_init (void)
+{
+  signal_setup (true);
+}
+
+static inline void
+signal_restore (void)
+{
+  signal_setup (false);
+}
+
+int
+main (int argc, char **argv)
+{
+  int i;
+  struct pending *thispend;
+  int n_files;
+
+  initialize_main (&argc, &argv);
+  set_program_name (argv[0]);
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  initialize_exit_failure (LS_FAILURE);
+  atexit (close_stdout);
+
+  assert (ARRAY_CARDINALITY (color_indicator) + 1
+          == ARRAY_CARDINALITY (indicator_name));
+
+  exit_status = EXIT_SUCCESS;
+  print_dir_name = true;
+  pending_dirs = NULL;
+
+  current_time.tv_sec = TYPE_MINIMUM (time_t);
+  current_time.tv_nsec = -1;
+
+  i = decode_switches (argc, argv);
+
+  if (print_with_color)
+    parse_ls_color ();
+
+  /* Test print_with_color again, because the call to parse_ls_color
+     may have just reset it -- e.g., if LS_COLORS is invalid.  */
+  if (directories_first)
+    check_symlink_mode = true;
+  else if (print_with_color)
+    {
+      /* Avoid following symbolic links when possible.  */
+      if (is_colored (C_ORPHAN)
+          || (is_colored (C_EXEC) && color_symlink_as_referent)
+          || (is_colored (C_MISSING) && format == long_format))
+        check_symlink_mode = true;
+    }
+
+  if (dereference == DEREF_UNDEFINED)
+    dereference = ((immediate_dirs
+                    || indicator_style == classify
+                    || format == long_format)
+                   ? DEREF_NEVER
+                   : DEREF_COMMAND_LINE_SYMLINK_TO_DIR);
+
+  /* When using -R, initialize a data structure we'll use to
+     detect any directory cycles.  */
+  if (recursive)
+    {
+      active_dir_set = hash_initialize (INITIAL_TABLE_SIZE, NULL,
+                                        dev_ino_hash,
+                                        dev_ino_compare,
+                                        dev_ino_free);
+      if (active_dir_set == NULL)
+        xalloc_die ();
+
+      obstack_init (&dev_ino_obstack);
+    }
+
+  localtz = tzalloc (getenv ("TZ"));
+
+  format_needs_stat = sort_type == sort_time || sort_type == sort_size
+    || format == long_format
+    || print_scontext
+    || print_block_size;
+  format_needs_type = (! format_needs_stat
+                       && (recursive
+                           || print_with_color
+                           || indicator_style != none
+                           || directories_first));
+
+  if (dired)
+    {
+      obstack_init (&dired_obstack);
+      obstack_init (&subdired_obstack);
+    }
+
+  if (print_hyperlink)
+    {
+      file_escape_init ();
+
+      hostname = xgethostname ();
+      /* The hostname is generally ignored,
+         so ignore failures obtaining it.  */
+      if (! hostname)
+        hostname = "";
+    }
+
+  cwd_n_alloc = 100;
+  cwd_file = xnmalloc (cwd_n_alloc, sizeof *cwd_file);
+  cwd_n_used = 0;
+
+  clear_files ();
+
+  n_files = argc - i;
+
+  if (n_files <= 0)
+    {
+      if (immediate_dirs)
+        gobble_file (".", directory, NOT_AN_INODE_NUMBER, true, "");
+      else
+        queue_directory (".", NULL, true);
+    }
+  else
+    do
+      gobble_file (argv[i++], unknown, NOT_AN_INODE_NUMBER, true, "");
+    while (i < argc);
+
+  if (cwd_n_used)
+    {
+      sort_files ();
+      if (!immediate_dirs)
+        extract_dirs_from_files (NULL, true);
+      /* 'cwd_n_used' might be zero now.  */
+    }
+
+  /* In the following if/else blocks, it is sufficient to test 'pending_dirs'
+     (and not pending_dirs->name) because there may be no markers in the queue
+     at this point.  A marker may be enqueued when extract_dirs_from_files is
+     called with a non-empty string or via print_dir.  */
+  if (cwd_n_used)
+    {
+      print_current_files ();
+      if (pending_dirs)
+        DIRED_PUTCHAR ('\n');
+    }
+  else if (n_files <= 1 && pending_dirs && pending_dirs->next == 0)
+    print_dir_name = false;
+
+  while (pending_dirs)
+    {
+      thispend = pending_dirs;
+      pending_dirs = pending_dirs->next;
+
+      if (LOOP_DETECT)
+        {
+          if (thispend->name == NULL)
+            {
+              /* thispend->name == NULL means this is a marker entry
+                 indicating we've finished processing the directory.
+                 Use its dev/ino numbers to remove the corresponding
+                 entry from the active_dir_set hash table.  */
+              struct dev_ino di = dev_ino_pop ();
+              struct dev_ino *found = hash_delete (active_dir_set, &di);
+              /* ASSERT_MATCHING_DEV_INO (thispend->realname, di); */
+              assert (found);
+              dev_ino_free (found);
+              free_pending_ent (thispend);
+              continue;
+            }
+        }
+
+      print_dir (thispend->name, thispend->realname,
+                 thispend->command_line_arg);
+
+      free_pending_ent (thispend);
+      print_dir_name = true;
+    }
+
+  if (print_with_color && used_color)
+    {
+      int j;
+
+      /* Skip the restore when it would be a no-op, i.e.,
+         when left is "\033[" and right is "m".  */
+      if (!(color_indicator[C_LEFT].len == 2
+            && memcmp (color_indicator[C_LEFT].string, "\033[", 2) == 0
+            && color_indicator[C_RIGHT].len == 1
+            && color_indicator[C_RIGHT].string[0] == 'm'))
+        restore_default_color ();
+
+      fflush (stdout);
+
+      signal_restore ();
+
+      /* Act on any signals that arrived before the default was restored.
+         This can process signals out of order, but there doesn't seem to
+         be an easy way to do them in order, and the order isn't that
+         important anyway.  */
+      for (j = stop_signal_count; j; j--)
+        raise (SIGSTOP);
+      j = interrupt_signal;
+      if (j)
+        raise (j);
+    }
+
+  if (dired)
+    {
+      /* No need to free these since we're about to exit.  */
+      dired_dump_obstack ("//DIRED//", &dired_obstack);
+      dired_dump_obstack ("//SUBDIRED//", &subdired_obstack);
+      printf ("//DIRED-OPTIONS// --quoting-style=%s\n",
+              quoting_style_args[get_quoting_style (filename_quoting_options)]);
+    }
+
+  if (LOOP_DETECT)
+    {
+      assert (hash_get_n_entries (active_dir_set) == 0);
+      hash_free (active_dir_set);
+    }
+
+  return exit_status;
+}
+
+/* Set the line length to the value given by SPEC.  Return true if
+   successful.  0 means no limit on line length.  */
+
+static bool
+set_line_length (char const *spec)
+{
+  uintmax_t val;
+
+  /* Treat too-large values as if they were SIZE_MAX, which is
+     effectively infinity.  */
+  switch (xstrtoumax (spec, NULL, 0, &val, ""))
+    {
+    case LONGINT_OK:
+      line_length = MIN (val, SIZE_MAX);
+      return true;
+
+    case LONGINT_OVERFLOW:
+      line_length = SIZE_MAX;
+      return true;
+
+    default:
+      return false;
+    }
+}
+
+/* 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)
+{
+  char *time_style_option = NULL;
+
+  bool sort_type_specified = false;
+  bool kibibytes_specified = false;
+
+  qmark_funny_chars = false;
+
+  /* initialize all switches to default settings */
+
+  switch (ls_mode)
+    {
+    case LS_MULTI_COL:
+      /* This is for the 'dir' program.  */
+      format = many_per_line;
+      set_quoting_style (NULL, escape_quoting_style);
+      break;
+
+    case LS_LONG_FORMAT:
+      /* This is for the 'vdir' program.  */
+      format = long_format;
+      set_quoting_style (NULL, escape_quoting_style);
+      break;
+
+    case LS_LS:
+      /* This is for the 'ls' program.  */
+      if (isatty (STDOUT_FILENO))
+        {
+          format = many_per_line;
+          set_quoting_style (NULL, shell_escape_quoting_style);
+          /* See description of qmark_funny_chars, above.  */
+          qmark_funny_chars = true;
+        }
+      else
+        {
+          format = one_per_line;
+          qmark_funny_chars = false;
+        }
+      break;
+
+    default:
+      abort ();
+    }
+
+  time_type = time_mtime;
+  sort_type = sort_name;
+  sort_reverse = false;
+  numeric_ids = false;
+  print_block_size = false;
+  indicator_style = none;
+  print_inode = false;
+  dereference = DEREF_UNDEFINED;
+  recursive = false;
+  immediate_dirs = false;
+  ignore_mode = IGNORE_DEFAULT;
+  ignore_patterns = NULL;
+  hide_patterns = NULL;
+  print_scontext = false;
+
+  getenv_quoting_style ();
+
+  line_length = 80;
+  {
+    char const *p = getenv ("COLUMNS");
+    if (p && *p && ! set_line_length (p))
+      error (0, 0,
+             _("ignoring invalid width in environment variable COLUMNS: %s"),
+             quote (p));
+  }
+
+#ifdef TIOCGWINSZ
+  {
+    struct winsize ws;
+
+    if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws) != -1
+        && 0 < ws.ws_col && ws.ws_col == (size_t) ws.ws_col)
+      line_length = ws.ws_col;
+  }
+#endif
+
+  {
+    char const *p = getenv ("TABSIZE");
+    tabsize = 8;
+    if (p)
+      {
+        uintmax_t tmp;
+        if (xstrtoumax (p, NULL, 0, &tmp, "") == LONGINT_OK
+            && tmp <= SIZE_MAX)
+          tabsize = tmp;
+        else
+          error (0, 0,
+                 _("ignoring invalid tab size in environment variable TABSIZE:"
+                   " %s"),
+                 quote (p));
+      }
+  }
+
+  while (true)
+    {
+      int oi = -1;
+      int c = getopt_long (argc, argv,
+                           "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UXZ1",
+                           long_options, &oi);
+      if (c == -1)
+        break;
+
+      switch (c)
+        {
+        case 'a':
+          ignore_mode = IGNORE_MINIMAL;
+          break;
+
+        case 'b':
+          set_quoting_style (NULL, escape_quoting_style);
+          break;
+
+        case 'c':
+          time_type = time_ctime;
+          break;
+
+        case 'd':
+          immediate_dirs = true;
+          break;
+
+        case 'f':
+          /* Same as enabling -a -U and disabling -l -s.  */
+          ignore_mode = IGNORE_MINIMAL;
+          sort_type = sort_none;
+          sort_type_specified = true;
+          /* disable -l */
+          if (format == long_format)
+            format = (isatty (STDOUT_FILENO) ? many_per_line : one_per_line);
+          print_block_size = false;	/* disable -s */
+          print_with_color = false;	/* disable --color */
+          print_hyperlink = false;	/* disable --hyperlink */
+          break;
+
+        case FILE_TYPE_INDICATOR_OPTION: /* --file-type */
+          indicator_style = file_type;
+          break;
+
+        case 'g':
+          format = long_format;
+          print_owner = false;
+          break;
+
+        case 'h':
+          file_human_output_opts = human_output_opts =
+            human_autoscale | human_SI | human_base_1024;
+          file_output_block_size = output_block_size = 1;
+          break;
+
+        case 'i':
+          print_inode = true;
+          break;
+
+        case 'k':
+          kibibytes_specified = true;
+          break;
+
+        case 'l':
+          format = long_format;
+          break;
+
+        case 'm':
+          format = with_commas;
+          break;
+
+        case 'n':
+          numeric_ids = true;
+          format = long_format;
+          break;
+
+        case 'o':  /* Just like -l, but don't display group info.  */
+          format = long_format;
+          print_group = false;
+          break;
+
+        case 'p':
+          indicator_style = slash;
+          break;
+
+        case 'q':
+          qmark_funny_chars = true;
+          break;
+
+        case 'r':
+          sort_reverse = true;
+          break;
+
+        case 's':
+          print_block_size = true;
+          break;
+
+        case 't':
+          sort_type = sort_time;
+          sort_type_specified = true;
+          break;
+
+        case 'u':
+          time_type = time_atime;
+          break;
+
+        case 'v':
+          sort_type = sort_version;
+          sort_type_specified = true;
+          break;
+
+        case 'w':
+          if (! set_line_length (optarg))
+            die (LS_FAILURE, 0, "%s: %s", _("invalid line width"),
+                 quote (optarg));
+          break;
+
+        case 'x':
+          format = horizontal;
+          break;
+
+        case 'A':
+          ignore_mode = IGNORE_DOT_AND_DOTDOT;
+          break;
+
+        case 'B':
+          add_ignore_pattern ("*~");
+          add_ignore_pattern (".*~");
+          break;
+
+        case 'C':
+          format = many_per_line;
+          break;
+
+        case 'D':
+          dired = true;
+          break;
+
+        case 'F':
+          indicator_style = classify;
+          break;
+
+        case 'G':		/* inhibit display of group info */
+          print_group = false;
+          break;
+
+        case 'H':
+          dereference = DEREF_COMMAND_LINE_ARGUMENTS;
+          break;
+
+        case DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION:
+          dereference = DEREF_COMMAND_LINE_SYMLINK_TO_DIR;
+          break;
+
+        case 'I':
+          add_ignore_pattern (optarg);
+          break;
+
+        case 'L':
+          dereference = DEREF_ALWAYS;
+          break;
+
+        case 'N':
+          set_quoting_style (NULL, literal_quoting_style);
+          break;
+
+        case 'Q':
+          set_quoting_style (NULL, c_quoting_style);
+          break;
+
+        case 'R':
+          recursive = true;
+          break;
+
+        case 'S':
+          sort_type = sort_size;
+          sort_type_specified = true;
+          break;
+
+        case 'T':
+          tabsize = xnumtoumax (optarg, 0, 0, SIZE_MAX, "",
+                                _("invalid tab size"), LS_FAILURE);
+          break;
+
+        case 'U':
+          sort_type = sort_none;
+          sort_type_specified = true;
+          break;
+
+        case 'X':
+          sort_type = sort_extension;
+          sort_type_specified = true;
+          break;
+
+        case '1':
+          /* -1 has no effect after -l.  */
+          if (format != long_format)
+            format = one_per_line;
+          break;
+
+        case AUTHOR_OPTION:
+          print_author = true;
+          break;
+
+        case HIDE_OPTION:
+          {
+            struct ignore_pattern *hide = xmalloc (sizeof *hide);
+            hide->pattern = optarg;
+            hide->next = hide_patterns;
+            hide_patterns = hide;
+          }
+          break;
+
+        case SORT_OPTION:
+          sort_type = XARGMATCH ("--sort", optarg, sort_args, sort_types);
+          sort_type_specified = true;
+          break;
+
+        case GROUP_DIRECTORIES_FIRST_OPTION:
+          directories_first = true;
+          break;
+
+        case TIME_OPTION:
+          time_type = XARGMATCH ("--time", optarg, time_args, time_types);
+          break;
+
+        case FORMAT_OPTION:
+          format = XARGMATCH ("--format", optarg, format_args, format_types);
+          break;
+
+        case FULL_TIME_OPTION:
+          format = long_format;
+          time_style_option = bad_cast ("full-iso");
+          break;
+
+        case COLOR_OPTION:
+          {
+            int i;
+            if (optarg)
+              i = XARGMATCH ("--color", optarg, when_args, when_types);
+            else
+              /* Using --color with no argument is equivalent to using
+                 --color=always.  */
+              i = when_always;
+
+            print_with_color = (i == when_always
+                                || (i == when_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;
+          }
+
+        case HYPERLINK_OPTION:
+          {
+            int i;
+            if (optarg)
+              i = XARGMATCH ("--hyperlink", optarg, when_args, when_types);
+            else
+              /* Using --hyperlink with no argument is equivalent to using
+                 --hyperlink=always.  */
+              i = when_always;
+
+            print_hyperlink = (i == when_always
+                               || (i == when_if_tty
+                                   && isatty (STDOUT_FILENO)));
+            break;
+          }
+
+        case INDICATOR_STYLE_OPTION:
+          indicator_style = XARGMATCH ("--indicator-style", optarg,
+                                       indicator_style_args,
+                                       indicator_style_types);
+          break;
+
+        case QUOTING_STYLE_OPTION:
+          set_quoting_style (NULL,
+                             XARGMATCH ("--quoting-style", optarg,
+                                        quoting_style_args,
+                                        quoting_style_vals));
+          break;
+
+        case TIME_STYLE_OPTION:
+          time_style_option = optarg;
+          break;
+
+        case SHOW_CONTROL_CHARS_OPTION:
+          qmark_funny_chars = false;
+          break;
+
+        case BLOCK_SIZE_OPTION:
+          {
+            enum strtol_error e = human_options (optarg, &human_output_opts,
+                                                 &output_block_size);
+            if (e != LONGINT_OK)
+              xstrtol_fatal (e, oi, 0, long_options, optarg);
+            file_human_output_opts = human_output_opts;
+            file_output_block_size = output_block_size;
+          }
+          break;
+
+        case SI_OPTION:
+          file_human_output_opts = human_output_opts =
+            human_autoscale | human_SI;
+          file_output_block_size = output_block_size = 1;
+          break;
+
+        case 'Z':
+          print_scontext = true;
+          break;
+
+        case_GETOPT_HELP_CHAR;
+
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+        default:
+          usage (LS_FAILURE);
+        }
+    }
+
+  if (! output_block_size)
+    {
+      char const *ls_block_size = getenv ("LS_BLOCK_SIZE");
+      human_options (ls_block_size,
+                     &human_output_opts, &output_block_size);
+      if (ls_block_size || getenv ("BLOCK_SIZE"))
+        {
+          file_human_output_opts = human_output_opts;
+          file_output_block_size = output_block_size;
+        }
+      if (kibibytes_specified)
+        {
+          human_output_opts = 0;
+          output_block_size = 1024;
+        }
+    }
+
+  /* Determine the max possible number of display columns.  */
+  max_idx = line_length / MIN_COLUMN_WIDTH;
+  /* Account for first display column not having a separator,
+     or line_lengths shorter than MIN_COLUMN_WIDTH.  */
+  max_idx += line_length % MIN_COLUMN_WIDTH != 0;
+
+  enum quoting_style qs = get_quoting_style (NULL);
+  align_variable_outer_quotes = format != with_commas
+                                && format != one_per_line
+                                && (line_length || format == long_format)
+                                && (qs == shell_quoting_style
+                                    || qs == shell_escape_quoting_style
+                                    || qs == c_maybe_quoting_style);
+  filename_quoting_options = clone_quoting_options (NULL);
+  if (qs == escape_quoting_style)
+    set_char_quoting (filename_quoting_options, ' ', 1);
+  if (file_type <= indicator_style)
+    {
+      char const *p;
+      for (p = &"*=>@|"[indicator_style - file_type]; *p; p++)
+        set_char_quoting (filename_quoting_options, *p, 1);
+    }
+
+  dirname_quoting_options = clone_quoting_options (NULL);
+  set_char_quoting (dirname_quoting_options, ':', 1);
+
+  /* --dired is meaningful only with --format=long (-l).
+     Otherwise, ignore it.  FIXME: warn about this?
+     Alternatively, make --dired imply --format=long?  */
+  if (dired && (format != long_format || print_hyperlink))
+    dired = false;
+
+  /* If -c or -u is specified and not -l (or any other option that implies -l),
+     and no sort-type was specified, then sort by the ctime (-c) or atime (-u).
+     The behavior of ls when using either -c or -u but with neither -l nor -t
+     appears to be unspecified by POSIX.  So, with GNU ls, '-u' alone means
+     sort by atime (this is the one that's not specified by the POSIX spec),
+     -lu means show atime and sort by name, -lut means show atime and sort
+     by atime.  */
+
+  if ((time_type == time_ctime || time_type == time_atime
+       || time_type == time_btime)
+      && !sort_type_specified && format != long_format)
+    {
+      sort_type = sort_time;
+    }
+
+  if (format == long_format)
+    {
+      char *style = time_style_option;
+      static char const posix_prefix[] = "posix-";
+
+      if (! style)
+        if (! (style = getenv ("TIME_STYLE")))
+          style = bad_cast ("locale");
+
+      while (STREQ_LEN (style, posix_prefix, sizeof posix_prefix - 1))
+        {
+          if (! hard_locale (LC_TIME))
+            return optind;
+          style += sizeof posix_prefix - 1;
+        }
+
+      if (*style == '+')
+        {
+          char *p0 = style + 1;
+          char *p1 = strchr (p0, '\n');
+          if (! p1)
+            p1 = p0;
+          else
+            {
+              if (strchr (p1 + 1, '\n'))
+                die (LS_FAILURE, 0, _("invalid time style format %s"),
+                     quote (p0));
+              *p1++ = '\0';
+            }
+          long_time_format[0] = p0;
+          long_time_format[1] = p1;
+        }
+      else
+        {
+          ptrdiff_t res = argmatch (style, time_style_args,
+                                    (char const *) time_style_types,
+                                    sizeof (*time_style_types));
+          if (res < 0)
+            {
+              /* This whole block used to be a simple use of XARGMATCH.
+                 but that didn't print the "posix-"-prefixed variants or
+                 the "+"-prefixed format string option upon failure.  */
+              argmatch_invalid ("time style", style, res);
+
+              /* The following is a manual expansion of argmatch_valid,
+                 but with the added "+ ..." description and the [posix-]
+                 prefixes prepended.  Note that this simplification works
+                 only because all four existing time_style_types values
+                 are distinct.  */
+              fputs (_("Valid arguments are:\n"), stderr);
+              char const *const *p = time_style_args;
+              while (*p)
+                fprintf (stderr, "  - [posix-]%s\n", *p++);
+              fputs (_("  - +FORMAT (e.g., +%H:%M) for a 'date'-style"
+                       " format\n"), stderr);
+              usage (LS_FAILURE);
+            }
+          switch (res)
+            {
+            case full_iso_time_style:
+              long_time_format[0] = long_time_format[1] =
+                "%Y-%m-%d %H:%M:%S.%N %z";
+              break;
+
+            case long_iso_time_style:
+              long_time_format[0] = long_time_format[1] = "%Y-%m-%d %H:%M";
+              break;
+
+            case iso_time_style:
+              long_time_format[0] = "%Y-%m-%d ";
+              long_time_format[1] = "%m-%d %H:%M";
+              break;
+
+            case locale_time_style:
+              if (hard_locale (LC_TIME))
+                {
+                  for (int i = 0; i < 2; i++)
+                    long_time_format[i] =
+                      dcgettext (NULL, long_time_format[i], LC_TIME);
+                }
+            }
+        }
+
+      abformat_init ();
+    }
+
+  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.  Set *OUTPUT_COUNT to the number of bytes output.  Return
+   true if successful.
+
+   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 bool
+get_funky_string (char **dest, const char **src, bool equals_end,
+                  size_t *output_count)
+{
+  char num;			/* For numerical codes */
+  size_t 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 escape sequence */
+              ++p;
+              break;
+            case '^':
+              state = ST_CARET; /* Caret escape */
+              ++p;
+              break;
+            case '=':
+              if (equals_end)
+                {
+                  state = ST_END; /* End */
+                  break;
+                }
+              FALLTHROUGH;
+            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 = '\a';
+              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;
+  *output_count = count;
+
+  return state != ST_ERROR;
+}
+
+enum parse_state
+  {
+    PS_START = 1,
+    PS_2,
+    PS_3,
+    PS_4,
+    PS_DONE,
+    PS_FAIL
+  };
+
+
+/* Check if the content of TERM is a valid name in dircolors.  */
+
+static bool
+known_term_type (void)
+{
+  char const *term = getenv ("TERM");
+  if (! term || ! *term)
+    return false;
+
+  char const *line = G_line;
+  while (line - G_line < sizeof (G_line))
+    {
+      if (STRNCMP_LIT (line, "TERM ") == 0)
+        {
+          if (fnmatch (line + 5, term, 0) == 0)
+            return true;
+        }
+      line += strlen (line) + 1;
+    }
+
+  return false;
+}
+
+static void
+parse_ls_color (void)
+{
+  const char *p;		/* Pointer to character being parsed */
+  char *buf;			/* color_buf buffer pointer */
+  int ind_no;			/* Indicator number */
+  char label[3];		/* Indicator label */
+  struct color_ext_type *ext;	/* Extension we are working on */
+
+  if ((p = getenv ("LS_COLORS")) == NULL || *p == '\0')
+    {
+      /* LS_COLORS takes precedence, but if that's not set then
+         honor the COLORTERM and TERM env variables so that
+         we only go with the internal ANSI color codes if the
+         former is non empty or the latter is set to a known value.  */
+      char const *colorterm = getenv ("COLORTERM");
+      if (! (colorterm && *colorterm) && ! known_term_type ())
+        print_with_color = false;
+      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);
+
+  enum parse_state state = PS_START;
+  while (true)
+    {
+      switch (state)
+        {
+        case PS_START:		/* 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 = xmalloc (sizeof *ext);
+              ext->next = color_ext_list;
+              color_ext_list = ext;
+
+              ++p;
+              ext->ext.string = buf;
+
+              state = (get_funky_string (&buf, &p, true, &ext->ext.len)
+                       ? PS_4 : PS_FAIL);
+              break;
+
+            case '\0':
+              state = PS_DONE;	/* Done! */
+              goto done;
+
+            default:	/* Assume it is file type label */
+              label[0] = *(p++);
+              state = PS_2;
+              break;
+            }
+          break;
+
+        case PS_2:		/* Second label character */
+          if (*p)
+            {
+              label[1] = *(p++);
+              state = PS_3;
+            }
+          else
+            state = PS_FAIL;	/* Error */
+          break;
+
+        case PS_3:		/* Equal sign after indicator label */
+          state = PS_FAIL;	/* 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 = (get_funky_string (&buf, &p, false,
+                                                 &color_indicator[ind_no].len)
+                               ? PS_START : PS_FAIL);
+                      break;
+                    }
+                }
+              if (state == PS_FAIL)
+                error (0, 0, _("unrecognized prefix: %s"), quote (label));
+            }
+          break;
+
+        case PS_4:		/* Equal sign after *.ext */
+          if (*(p++) == '=')
+            {
+              ext->seq.string = buf;
+              state = (get_funky_string (&buf, &p, false, &ext->seq.len)
+                       ? PS_START : PS_FAIL);
+            }
+          else
+            state = PS_FAIL;
+          break;
+
+        case PS_FAIL:
+          goto done;
+
+        default:
+          abort ();
+        }
+    }
+ done:
+
+  if (state == PS_FAIL)
+    {
+      struct color_ext_type *e;
+      struct color_ext_type *e2;
+
+      error (0, 0,
+             _("unparsable value for LS_COLORS environment variable"));
+      free (color_buf);
+      for (e = color_ext_list; e != NULL; /* empty */)
+        {
+          e2 = e;
+          e = e->next;
+          free (e2);
+        }
+      print_with_color = false;
+    }
+
+  if (color_indicator[C_LINK].len == 6
+      && !STRNCMP_LIT (color_indicator[C_LINK].string, "target"))
+    color_symlink_as_referent = true;
+}
+
+/* Set the quoting style default if the environment variable
+   QUOTING_STYLE is set.  */
+
+static void
+getenv_quoting_style (void)
+{
+  char const *q_style = getenv ("QUOTING_STYLE");
+  if (q_style)
+    {
+      int i = ARGMATCH (q_style, quoting_style_args, quoting_style_vals);
+      if (0 <= i)
+        set_quoting_style (NULL, quoting_style_vals[i]);
+      else
+        error (0, 0,
+       _("ignoring invalid value of environment variable QUOTING_STYLE: %s"),
+               quote (q_style));
+    }
+}
+
+/* Set the exit status to report a failure.  If SERIOUS, it is a
+   serious failure; otherwise, it is merely a minor problem.  */
+
+static void
+set_exit_status (bool serious)
+{
+  if (serious)
+    exit_status = LS_FAILURE;
+  else if (exit_status == EXIT_SUCCESS)
+    exit_status = LS_MINOR_PROBLEM;
+}
+
+/* Assuming a failure is serious if SERIOUS, use the printf-style
+   MESSAGE to report the failure to access a file named FILE.  Assume
+   errno is set appropriately for the failure.  */
+
+static void
+file_failure (bool serious, char const *message, char const *file)
+{
+  error (0, errno, message, quoteaf (file));
+  set_exit_status (serious);
+}
+
+/* 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.  NAME == NULL is used to insert a marker entry for the
+   directory named in REALNAME.
+   If NAME is non-NULL, we use its dev/ino information to save
+   a call to stat -- when doing a recursive (-R) traversal.
+   COMMAND_LINE_ARG means this directory was mentioned on the command line.  */
+
+static void
+queue_directory (char const *name, char const *realname, bool command_line_arg)
+{
+  struct pending *new = xmalloc (sizeof *new);
+  new->realname = realname ? xstrdup (realname) : NULL;
+  new->name = name ? xstrdup (name) : NULL;
+  new->command_line_arg = command_line_arg;
+  new->next = pending_dirs;
+  pending_dirs = new;
+}
+
+/* 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.
+   COMMAND_LINE_ARG means this directory was mentioned on the command line.  */
+
+static void
+print_dir (char const *name, char const *realname, bool command_line_arg)
+{
+  DIR *dirp;
+  struct dirent *next;
+  uintmax_t total_blocks = 0;
+  static bool first = true;
+  bool found_any_entries = false;
+
+  errno = 0;
+  dirp = opendir (name);
+  if (!dirp)
+    {
+      file_failure (command_line_arg, _("cannot open directory %s"), name);
+      return;
+    }
+
+  if (LOOP_DETECT)
+    {
+      struct stat dir_stat;
+      int fd = dirfd (dirp);
+
+      /* If dirfd failed, endure the overhead of stat'ing by path  */
+      if ((0 <= fd
+           ? fstat_for_ino (fd, &dir_stat)
+           : stat_for_ino (name, &dir_stat)) < 0)
+        {
+          file_failure (command_line_arg,
+                        _("cannot determine device and inode of %s"), name);
+          closedir (dirp);
+          return;
+        }
+
+      /* If we've already visited this dev/inode pair, warn that
+         we've found a loop, and do not process this directory.  */
+      if (visit_dir (dir_stat.st_dev, dir_stat.st_ino))
+        {
+          error (0, 0, _("%s: not listing already-listed directory"),
+                 quotef (name));
+          closedir (dirp);
+          set_exit_status (true);
+          return;
+        }
+
+      dev_ino_push (dir_stat.st_dev, dir_stat.st_ino);
+    }
+
+  clear_files ();
+
+  if (recursive || print_dir_name)
+    {
+      if (!first)
+        DIRED_PUTCHAR ('\n');
+      first = false;
+      DIRED_INDENT ();
+
+      char *absolute_name = NULL;
+      if (print_hyperlink)
+        {
+          absolute_name = canonicalize_filename_mode (name, CAN_MISSING);
+          if (! absolute_name)
+            file_failure (command_line_arg,
+                          _("error canonicalizing %s"), name);
+        }
+      quote_name (realname ? realname : name, dirname_quoting_options, -1,
+                  NULL, true, &subdired_obstack, absolute_name);
+
+      free (absolute_name);
+
+      DIRED_FPUTS_LITERAL (":\n", stdout);
+    }
+
+  /* Read the directory entries, and insert the subfiles into the 'cwd_file'
+     table.  */
+
+  while (1)
+    {
+      /* Set errno to zero so we can distinguish between a readdir failure
+         and when readdir simply finds that there are no more entries.  */
+      errno = 0;
+      next = readdir (dirp);
+      if (next)
+        {
+          found_any_entries = true;
+          if (! file_ignored (next->d_name))
+            {
+              enum filetype type = unknown;
+
+#if HAVE_STRUCT_DIRENT_D_TYPE
+              switch (next->d_type)
+                {
+                case DT_BLK:  type = blockdev;		break;
+                case DT_CHR:  type = chardev;		break;
+                case DT_DIR:  type = directory;		break;
+                case DT_FIFO: type = fifo;		break;
+                case DT_LNK:  type = symbolic_link;	break;
+                case DT_REG:  type = normal;		break;
+                case DT_SOCK: type = sock;		break;
+# ifdef DT_WHT
+                case DT_WHT:  type = whiteout;		break;
+# endif
+                }
+#endif
+              total_blocks += gobble_file (next->d_name, type,
+                                           RELIABLE_D_INO (next),
+                                           false, name);
+
+              /* In this narrow case, print out each name right away, so
+                 ls uses constant memory while processing the entries of
+                 this directory.  Useful when there are many (millions)
+                 of entries in a directory.  */
+              if (format == one_per_line && sort_type == sort_none
+                      && !print_block_size && !recursive)
+                {
+                  /* We must call sort_files in spite of
+                     "sort_type == sort_none" for its initialization
+                     of the sorted_file vector.  */
+                  sort_files ();
+                  print_current_files ();
+                  clear_files ();
+                }
+            }
+        }
+      else if (errno != 0)
+        {
+          file_failure (command_line_arg, _("reading directory %s"), name);
+          if (errno != EOVERFLOW)
+            break;
+        }
+#ifdef __linux__
+      else if (! found_any_entries)
+        {
+          /* If readdir finds no directory entries at all, not even "." or
+             "..", then double check that the directory exists.  */
+#if defined(__x86_64__)    || \
+    defined(__aarch64__)   || \
+    defined(__powerpc64__) || \
+    defined(__sparc__) && defined(__arch64__) || \
+    defined(__riscv_xlen) && __riscv_xlen == 64
+          if (syscall (SYS_getdents64, dirfd (dirp), NULL, 0) == -1
+              && errno != EINVAL)
+#else
+          if (syscall (SYS_getdents, dirfd (dirp), NULL, 0) == -1
+              && errno != EINVAL)
+#endif
+            {
+              /* We exclude EINVAL as that pertains to buffer handling,
+                 and we've passed NULL as the buffer for simplicity.
+                 ENOENT is returned if appropriate before buffer handling.  */
+              file_failure (command_line_arg, _("reading directory %s"), name);
+            }
+          break;
+        }
+#endif
+      else
+        break;
+
+      /* When processing a very large directory, and since we've inhibited
+         interrupts, this loop would take so long that ls would be annoyingly
+         uninterruptible.  This ensures that it handles signals promptly.  */
+      process_signals ();
+    }
+
+  if (closedir (dirp) != 0)
+    {
+      file_failure (command_line_arg, _("closing directory %s"), name);
+      /* 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 (recursive)
+    extract_dirs_from_files (name, false);
+
+  if (format == long_format || print_block_size)
+    {
+      const char *p;
+      char buf[LONGEST_HUMAN_READABLE + 1];
+
+      DIRED_INDENT ();
+      p = _("total");
+      DIRED_FPUTS (p, stdout, strlen (p));
+      DIRED_PUTCHAR (' ');
+      p = human_readable (total_blocks, buf, human_output_opts,
+                          ST_NBLOCKSIZE, output_block_size);
+      DIRED_FPUTS (p, stdout, strlen (p));
+      DIRED_PUTCHAR ('\n');
+    }
+
+  if (cwd_n_used)
+    print_current_files ();
+}
+
+/* Add 'pattern' to the list of patterns for which files that match are
+   not listed.  */
+
+static void
+add_ignore_pattern (const char *pattern)
+{
+  struct ignore_pattern *ignore;
+
+  ignore = xmalloc (sizeof *ignore);
+  ignore->pattern = pattern;
+  /* Add it to the head of the linked list.  */
+  ignore->next = ignore_patterns;
+  ignore_patterns = ignore;
+}
+
+/* Return true if one of the PATTERNS matches FILE.  */
+
+static bool
+patterns_match (struct ignore_pattern const *patterns, char const *file)
+{
+  struct ignore_pattern const *p;
+  for (p = patterns; p; p = p->next)
+    if (fnmatch (p->pattern, file, FNM_PERIOD) == 0)
+      return true;
+  return false;
+}
+
+/* Return true if FILE should be ignored.  */
+
+static bool
+file_ignored (char const *name)
+{
+  return ((ignore_mode != IGNORE_MINIMAL
+           && name[0] == '.'
+           && (ignore_mode == IGNORE_DEFAULT || ! name[1 + (name[1] == '.')]))
+          || (ignore_mode == IGNORE_DEFAULT
+              && patterns_match (hide_patterns, name))
+          || patterns_match (ignore_patterns, name));
+}
+
+/* POSIX requires that a file size be printed without a sign, even
+   when negative.  Assume the typical case where negative sizes are
+   actually positive values that have wrapped around.  */
+
+static uintmax_t
+unsigned_file_size (off_t size)
+{
+  return size + (size < 0) * ((uintmax_t) OFF_T_MAX - OFF_T_MIN + 1);
+}
+
+#ifdef HAVE_CAP
+/* Return true if NAME has a capability (see linux/capability.h) */
+static bool
+has_capability (char const *name)
+{
+  char *result;
+  bool has_cap;
+
+  cap_t cap_d = cap_get_file (name);
+  if (cap_d == NULL)
+    return false;
+
+  result = cap_to_text (cap_d, NULL);
+  cap_free (cap_d);
+  if (!result)
+    return false;
+
+  /* check if human-readable capability string is empty */
+  has_cap = !!*result;
+
+  cap_free (result);
+  return has_cap;
+}
+#else
+static bool
+has_capability (char const *name _GL_UNUSED)
+{
+  errno = ENOTSUP;
+  return false;
+}
+#endif
+
+/* Enter and remove entries in the table 'cwd_file'.  */
+
+static void
+free_ent (struct fileinfo *f)
+{
+  free (f->name);
+  free (f->linkname);
+  free (f->absolute_name);
+  if (f->scontext != UNKNOWN_SECURITY_CONTEXT)
+    {
+      if (is_smack_enabled ())
+        free (f->scontext);
+      else
+        freecon (f->scontext);
+    }
+}
+
+/* Empty the table of files.  */
+static void
+clear_files (void)
+{
+  for (size_t i = 0; i < cwd_n_used; i++)
+    {
+      struct fileinfo *f = sorted_file[i];
+      free_ent (f);
+    }
+
+  cwd_n_used = 0;
+  cwd_some_quoted = false;
+  any_has_acl = false;
+  inode_number_width = 0;
+  block_size_width = 0;
+  nlink_width = 0;
+  owner_width = 0;
+  group_width = 0;
+  author_width = 0;
+  scontext_width = 0;
+  major_device_number_width = 0;
+  minor_device_number_width = 0;
+  file_size_width = 0;
+}
+
+/* Return true if ERR implies lack-of-support failure by a
+   getxattr-calling function like getfilecon or file_has_acl.  */
+static bool
+errno_unsupported (int err)
+{
+  return (err == EINVAL || err == ENOSYS || is_ENOTSUP (err));
+}
+
+/* Cache *getfilecon failure, when it's trivial to do so.
+   Like getfilecon/lgetfilecon, but when F's st_dev says it's doesn't
+   support getting the security context, fail with ENOTSUP immediately.  */
+static int
+getfilecon_cache (char const *file, struct fileinfo *f, bool deref)
+{
+  /* st_dev of the most recently processed device for which we've
+     found that [l]getfilecon fails indicating lack of support.  */
+  static dev_t unsupported_device;
+
+  if (f->stat.st_dev == unsupported_device)
+    {
+      errno = ENOTSUP;
+      return -1;
+    }
+  int r = 0;
+#ifdef HAVE_SMACK
+  if (is_smack_enabled ())
+    r = smack_new_label_from_path (file, "security.SMACK64", deref,
+                                   &f->scontext);
+  else
+#endif
+    r = (deref
+         ? getfilecon (file, &f->scontext)
+         : lgetfilecon (file, &f->scontext));
+  if (r < 0 && errno_unsupported (errno))
+    unsupported_device = f->stat.st_dev;
+  return r;
+}
+
+/* Cache file_has_acl failure, when it's trivial to do.
+   Like file_has_acl, but when F's st_dev says it's on a file
+   system lacking ACL support, return 0 with ENOTSUP immediately.  */
+static int
+file_has_acl_cache (char const *file, struct fileinfo *f)
+{
+  /* st_dev of the most recently processed device for which we've
+     found that file_has_acl fails indicating lack of support.  */
+  static dev_t unsupported_device;
+
+  if (f->stat.st_dev == unsupported_device)
+    {
+      errno = ENOTSUP;
+      return 0;
+    }
+
+  /* Zero errno so that we can distinguish between two 0-returning cases:
+     "has-ACL-support, but only a default ACL" and "no ACL support". */
+  errno = 0;
+  int n = file_has_acl (file, &f->stat);
+  if (n <= 0 && errno_unsupported (errno))
+    unsupported_device = f->stat.st_dev;
+  return n;
+}
+
+/* Cache has_capability failure, when it's trivial to do.
+   Like has_capability, but when F's st_dev says it's on a file
+   system lacking capability support, return 0 with ENOTSUP immediately.  */
+static bool
+has_capability_cache (char const *file, struct fileinfo *f)
+{
+  /* st_dev of the most recently processed device for which we've
+     found that has_capability fails indicating lack of support.  */
+  static dev_t unsupported_device;
+
+  if (f->stat.st_dev == unsupported_device)
+    {
+      errno = ENOTSUP;
+      return 0;
+    }
+
+  bool b = has_capability (file);
+  if ( !b && errno_unsupported (errno))
+    unsupported_device = f->stat.st_dev;
+  return b;
+}
+
+static bool
+needs_quoting (char const* name)
+{
+  char test[2];
+  size_t len = quotearg_buffer (test, sizeof test , name, -1,
+                                filename_quoting_options);
+  return *name != *test || strlen (name) != len;
+}
+
+/* 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 uintmax_t
+gobble_file (char const *name, enum filetype type, ino_t inode,
+             bool command_line_arg, char const *dirname)
+{
+  uintmax_t blocks = 0;
+  struct fileinfo *f;
+
+  /* An inode value prior to gobble_file necessarily came from readdir,
+     which is not used for command line arguments.  */
+  assert (! command_line_arg || inode == NOT_AN_INODE_NUMBER);
+
+  if (cwd_n_used == cwd_n_alloc)
+    {
+      cwd_file = xnrealloc (cwd_file, cwd_n_alloc, 2 * sizeof *cwd_file);
+      cwd_n_alloc *= 2;
+    }
+
+  f = &cwd_file[cwd_n_used];
+  memset (f, '\0', sizeof *f);
+  f->stat.st_ino = inode;
+  f->filetype = type;
+
+  f->quoted = -1;
+  if ((! cwd_some_quoted) && align_variable_outer_quotes)
+    {
+      /* Determine if any quoted for padding purposes.  */
+      f->quoted = needs_quoting (name);
+      if (f->quoted)
+        cwd_some_quoted = 1;
+    }
+
+  if (command_line_arg
+      || print_hyperlink
+      || format_needs_stat
+      /* When coloring a directory (we may know the type from
+         direct.d_type), we have to stat it in order to indicate
+         sticky and/or other-writable attributes.  */
+      || (type == directory && print_with_color
+          && (is_colored (C_OTHER_WRITABLE)
+              || is_colored (C_STICKY)
+              || is_colored (C_STICKY_OTHER_WRITABLE)))
+      /* When dereferencing symlinks, the inode and type must come from
+         stat, but readdir provides the inode and type of lstat.  */
+      || ((print_inode || format_needs_type)
+          && (type == symbolic_link || type == unknown)
+          && (dereference == DEREF_ALWAYS
+              || color_symlink_as_referent || check_symlink_mode))
+      /* Command line dereferences are already taken care of by the above
+         assertion that the inode number is not yet known.  */
+      || (print_inode && inode == NOT_AN_INODE_NUMBER)
+      || (format_needs_type
+          && (type == unknown || command_line_arg
+              /* --indicator-style=classify (aka -F)
+                 requires that we stat each regular file
+                 to see if it's executable.  */
+              || (type == normal && (indicator_style == classify
+                                     /* This is so that --color ends up
+                                        highlighting files with these mode
+                                        bits set even when options like -F are
+                                        not specified.  Note we do a redundant
+                                        stat in the very unlikely case where
+                                        C_CAP is set but not the others. */
+                                     || (print_with_color
+                                         && (is_colored (C_EXEC)
+                                             || is_colored (C_SETUID)
+                                             || is_colored (C_SETGID)
+                                             || is_colored (C_CAP)))
+                                     )))))
+
+    {
+      /* Absolute name of this file.  */
+      char *full_name;
+      bool do_deref;
+      int err;
+
+      if (name[0] == '/' || dirname[0] == 0)
+        full_name = (char *) name;
+      else
+        {
+          full_name = alloca (strlen (name) + strlen (dirname) + 2);
+          attach (full_name, dirname, name);
+        }
+
+      if (print_hyperlink)
+        {
+          f->absolute_name = canonicalize_filename_mode (full_name,
+                                                         CAN_MISSING);
+          if (! f->absolute_name)
+            file_failure (command_line_arg,
+                          _("error canonicalizing %s"), full_name);
+        }
+
+      switch (dereference)
+        {
+        case DEREF_ALWAYS:
+          err = do_stat (full_name, &f->stat);
+          do_deref = true;
+          break;
+
+        case DEREF_COMMAND_LINE_ARGUMENTS:
+        case DEREF_COMMAND_LINE_SYMLINK_TO_DIR:
+          if (command_line_arg)
+            {
+              bool need_lstat;
+              err = do_stat (full_name, &f->stat);
+              do_deref = true;
+
+              if (dereference == DEREF_COMMAND_LINE_ARGUMENTS)
+                break;
+
+              need_lstat = (err < 0
+                            ? errno == ENOENT
+                            : ! S_ISDIR (f->stat.st_mode));
+              if (!need_lstat)
+                break;
+
+              /* stat failed because of ENOENT, maybe indicating a dangling
+                 symlink.  Or stat succeeded, FULL_NAME does not refer to a
+                 directory, and --dereference-command-line-symlink-to-dir is
+                 in effect.  Fall through so that we call lstat instead.  */
+            }
+          FALLTHROUGH;
+
+        default: /* DEREF_NEVER */
+          err = do_lstat (full_name, &f->stat);
+          do_deref = false;
+          break;
+        }
+
+      if (err != 0)
+        {
+          /* Failure to stat a command line argument leads to
+             an exit status of 2.  For other files, stat failure
+             provokes an exit status of 1.  */
+          file_failure (command_line_arg,
+                        _("cannot access %s"), full_name);
+          if (command_line_arg)
+            return 0;
+
+          f->name = xstrdup (name);
+          cwd_n_used++;
+
+          return 0;
+        }
+
+      f->stat_ok = true;
+
+      /* Note has_capability() adds around 30% runtime to 'ls --color'  */
+      if ((type == normal || S_ISREG (f->stat.st_mode))
+          && print_with_color && is_colored (C_CAP))
+        f->has_capability = has_capability_cache (full_name, f);
+
+      if (format == long_format || print_scontext)
+        {
+          bool have_scontext = false;
+          bool have_acl = false;
+          int attr_len = getfilecon_cache (full_name, f, do_deref);
+          err = (attr_len < 0);
+
+          if (err == 0)
+            {
+              if (is_smack_enabled ())
+                have_scontext = ! STREQ ("_", f->scontext);
+              else
+                have_scontext = ! STREQ ("unlabeled", f->scontext);
+            }
+          else
+            {
+              f->scontext = UNKNOWN_SECURITY_CONTEXT;
+
+              /* When requesting security context information, don't make
+                 ls fail just because the file (even a command line argument)
+                 isn't on the right type of file system.  I.e., a getfilecon
+                 failure isn't in the same class as a stat failure.  */
+              if (is_ENOTSUP (errno) || errno == ENODATA)
+                err = 0;
+            }
+
+          if (err == 0 && format == long_format)
+            {
+              int n = file_has_acl_cache (full_name, f);
+              err = (n < 0);
+              have_acl = (0 < n);
+            }
+
+          f->acl_type = (!have_scontext && !have_acl
+                         ? ACL_T_NONE
+                         : (have_scontext && !have_acl
+                            ? ACL_T_LSM_CONTEXT_ONLY
+                            : ACL_T_YES));
+          any_has_acl |= f->acl_type != ACL_T_NONE;
+
+          if (err)
+            error (0, errno, "%s", quotef (full_name));
+        }
+
+      if (S_ISLNK (f->stat.st_mode)
+          && (format == long_format || check_symlink_mode))
+        {
+          struct stat linkstats;
+
+          get_link_name (full_name, f, command_line_arg);
+          char *linkname = make_link_name (full_name, f->linkname);
+
+          /* Use the slower quoting path for this entry, though
+             don't update CWD_SOME_QUOTED since alignment not affected.  */
+          if (linkname && f->quoted == 0 && needs_quoting (f->linkname))
+            f->quoted = -1;
+
+          /* Avoid following symbolic links when possible, ie, when
+             they won't be traced and when no indicator is needed.  */
+          if (linkname
+              && (file_type <= indicator_style || check_symlink_mode)
+              && stat_for_mode (linkname, &linkstats) == 0)
+            {
+              f->linkok = true;
+              f->linkmode = linkstats.st_mode;
+            }
+          free (linkname);
+        }
+
+      if (S_ISLNK (f->stat.st_mode))
+        f->filetype = symbolic_link;
+      else if (S_ISDIR (f->stat.st_mode))
+        {
+          if (command_line_arg && !immediate_dirs)
+            f->filetype = arg_directory;
+          else
+            f->filetype = directory;
+        }
+      else
+        f->filetype = normal;
+
+      blocks = ST_NBLOCKS (f->stat);
+      if (format == long_format || print_block_size)
+        {
+          char buf[LONGEST_HUMAN_READABLE + 1];
+          int len = mbswidth (human_readable (blocks, buf, human_output_opts,
+                                              ST_NBLOCKSIZE, output_block_size),
+                              0);
+          if (block_size_width < len)
+            block_size_width = len;
+        }
+
+      if (format == long_format)
+        {
+          if (print_owner)
+            {
+              int len = format_user_width (f->stat.st_uid);
+              if (owner_width < len)
+                owner_width = len;
+            }
+
+          if (print_group)
+            {
+              int len = format_group_width (f->stat.st_gid);
+              if (group_width < len)
+                group_width = len;
+            }
+
+          if (print_author)
+            {
+              int len = format_user_width (f->stat.st_author);
+              if (author_width < len)
+                author_width = len;
+            }
+        }
+
+      if (print_scontext)
+        {
+          int len = strlen (f->scontext);
+          if (scontext_width < len)
+            scontext_width = len;
+        }
+
+      if (format == long_format)
+        {
+          char b[INT_BUFSIZE_BOUND (uintmax_t)];
+          int b_len = strlen (umaxtostr (f->stat.st_nlink, b));
+          if (nlink_width < b_len)
+            nlink_width = b_len;
+
+          if (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode))
+            {
+              char buf[INT_BUFSIZE_BOUND (uintmax_t)];
+              int len = strlen (umaxtostr (major (f->stat.st_rdev), buf));
+              if (major_device_number_width < len)
+                major_device_number_width = len;
+              len = strlen (umaxtostr (minor (f->stat.st_rdev), buf));
+              if (minor_device_number_width < len)
+                minor_device_number_width = len;
+              len = major_device_number_width + 2 + minor_device_number_width;
+              if (file_size_width < len)
+                file_size_width = len;
+            }
+          else
+            {
+              char buf[LONGEST_HUMAN_READABLE + 1];
+              uintmax_t size = unsigned_file_size (f->stat.st_size);
+              int len = mbswidth (human_readable (size, buf,
+                                                  file_human_output_opts,
+                                                  1, file_output_block_size),
+                                  0);
+              if (file_size_width < len)
+                file_size_width = len;
+            }
+        }
+    }
+
+  if (print_inode)
+    {
+      char buf[INT_BUFSIZE_BOUND (uintmax_t)];
+      int len = strlen (umaxtostr (f->stat.st_ino, buf));
+      if (inode_number_width < len)
+        inode_number_width = len;
+    }
+
+  f->name = xstrdup (name);
+  cwd_n_used++;
+
+  return blocks;
+}
+
+/* Return true if F refers to a directory.  */
+static bool
+is_directory (const struct fileinfo *f)
+{
+  return f->filetype == directory || f->filetype == arg_directory;
+}
+
+/* Return true if F refers to a (symlinked) directory.  */
+static bool
+is_linked_directory (const struct fileinfo *f)
+{
+  return f->filetype == directory || f->filetype == arg_directory
+         || S_ISDIR (f->linkmode);
+}
+
+/* Put the name of the file that FILENAME is a symbolic link to
+   into the LINKNAME field of 'f'.  COMMAND_LINE_ARG indicates whether
+   FILENAME is a command-line argument.  */
+
+static void
+get_link_name (char const *filename, struct fileinfo *f, bool command_line_arg)
+{
+  f->linkname = areadlink_with_size (filename, f->stat.st_size);
+  if (f->linkname == NULL)
+    file_failure (command_line_arg, _("cannot read symbolic link %s"),
+                  filename);
+}
+
+/* If LINKNAME is a relative name and NAME contains one or more
+   leading directories, return LINKNAME with those directories
+   prepended; otherwise, return a copy of LINKNAME.
+   If LINKNAME is NULL, return NULL.  */
+
+static char *
+make_link_name (char const *name, char const *linkname)
+{
+  if (!linkname)
+    return NULL;
+
+  if (IS_ABSOLUTE_FILE_NAME (linkname))
+    return xstrdup (linkname);
+
+  /* The link is to a relative name.  Prepend any leading directory
+     in 'name' to the link name.  */
+  size_t prefix_len = dir_len (name);
+  if (prefix_len == 0)
+    return xstrdup (linkname);
+
+  char *p = xmalloc (prefix_len + 1 + strlen (linkname) + 1);
+
+  /* PREFIX_LEN usually specifies a string not ending in slash.
+     In that case, extend it by one, since the next byte *is* a slash.
+     Otherwise, the prefix is "/", so leave the length unchanged.  */
+  if ( ! ISSLASH (name[prefix_len - 1]))
+    ++prefix_len;
+
+  stpcpy (stpncpy (p, name, prefix_len), linkname);
+  return p;
+}
+
+/* Return true if the last component of NAME is '.' or '..'
+   This is so we don't try to recurse on '././././. ...' */
+
+static bool
+basename_is_dot_or_dotdot (const char *name)
+{
+  char const *base = last_component (name);
+  return dot_or_dotdot (base);
+}
+
+/* Remove any entries from CWD_FILE 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;
+   if it is null, no prefix is needed and "." and ".." should not be ignored.
+   If COMMAND_LINE_ARG is true, this directory was mentioned at the top level,
+   This is desirable when processing directories recursively.  */
+
+static void
+extract_dirs_from_files (char const *dirname, bool command_line_arg)
+{
+  size_t i;
+  size_t j;
+  bool ignore_dot_and_dot_dot = (dirname != NULL);
+
+  if (dirname && LOOP_DETECT)
+    {
+      /* Insert a marker entry first.  When we dequeue this marker entry,
+         we'll know that DIRNAME has been processed and may be removed
+         from the set of active directories.  */
+      queue_directory (NULL, dirname, false);
+    }
+
+  /* Queue the directories last one first, because queueing reverses the
+     order.  */
+  for (i = cwd_n_used; i-- != 0; )
+    {
+      struct fileinfo *f = sorted_file[i];
+
+      if (is_directory (f)
+          && (! ignore_dot_and_dot_dot
+              || ! basename_is_dot_or_dotdot (f->name)))
+        {
+          if (!dirname || f->name[0] == '/')
+            queue_directory (f->name, f->linkname, command_line_arg);
+          else
+            {
+              char *name = file_name_concat (dirname, f->name, NULL);
+              queue_directory (name, f->linkname, command_line_arg);
+              free (name);
+            }
+          if (f->filetype == arg_directory)
+            free_ent (f);
+        }
+    }
+
+  /* Now delete the directories from the table, compacting all the remaining
+     entries.  */
+
+  for (i = 0, j = 0; i < cwd_n_used; i++)
+    {
+      struct fileinfo *f = sorted_file[i];
+      sorted_file[j] = f;
+      j += (f->filetype != arg_directory);
+    }
+  cwd_n_used = j;
+}
+
+/* Use strcoll to compare strings in this locale.  If an error occurs,
+   report an error and longjmp to failed_strcoll.  */
+
+static jmp_buf failed_strcoll;
+
+static int
+xstrcoll (char const *a, char const *b)
+{
+  int diff;
+  errno = 0;
+  diff = strcoll (a, b);
+  if (errno)
+    {
+      error (0, errno, _("cannot compare file names %s and %s"),
+             quote_n (0, a), quote_n (1, b));
+      set_exit_status (false);
+      longjmp (failed_strcoll, 1);
+    }
+  return diff;
+}
+
+/* Comparison routines for sorting the files.  */
+
+typedef void const *V;
+typedef int (*qsortFunc)(V a, V b);
+
+/* Used below in DEFINE_SORT_FUNCTIONS for _df_ sort function variants.
+   The do { ... } while(0) makes it possible to use the macro more like
+   a statement, without violating C89 rules: */
+#define DIRFIRST_CHECK(a, b)						\
+  do									\
+    {									\
+      bool a_is_dir = is_linked_directory ((struct fileinfo const *) a);\
+      bool b_is_dir = is_linked_directory ((struct fileinfo const *) b);\
+      if (a_is_dir && !b_is_dir)					\
+        return -1;         /* a goes before b */			\
+      if (!a_is_dir && b_is_dir)					\
+        return 1;          /* b goes before a */			\
+    }									\
+  while (0)
+
+/* Define the 8 different sort function variants required for each sortkey.
+   KEY_NAME is a token describing the sort key, e.g., ctime, atime, size.
+   KEY_CMP_FUNC is a function to compare records based on that key, e.g.,
+   ctime_cmp, atime_cmp, size_cmp.  Append KEY_NAME to the string,
+   '[rev_][x]str{cmp|coll}[_df]_', to create each function name.  */
+#define DEFINE_SORT_FUNCTIONS(key_name, key_cmp_func)			\
+  /* direct, non-dirfirst versions */					\
+  static int xstrcoll_##key_name (V a, V b)				\
+  { return key_cmp_func (a, b, xstrcoll); }				\
+  static int _GL_ATTRIBUTE_PURE strcmp_##key_name (V a, V b)		\
+  { return key_cmp_func (a, b, strcmp); }				\
+                                                                        \
+  /* reverse, non-dirfirst versions */					\
+  static int rev_xstrcoll_##key_name (V a, V b)				\
+  { return key_cmp_func (b, a, xstrcoll); }				\
+  static int _GL_ATTRIBUTE_PURE rev_strcmp_##key_name (V a, V b)	\
+  { return key_cmp_func (b, a, strcmp); }				\
+                                                                        \
+  /* direct, dirfirst versions */					\
+  static int xstrcoll_df_##key_name (V a, V b)				\
+  { DIRFIRST_CHECK (a, b); return key_cmp_func (a, b, xstrcoll); }	\
+  static int _GL_ATTRIBUTE_PURE strcmp_df_##key_name (V a, V b)		\
+  { DIRFIRST_CHECK (a, b); return key_cmp_func (a, b, strcmp); }	\
+                                                                        \
+  /* reverse, dirfirst versions */					\
+  static int rev_xstrcoll_df_##key_name (V a, V b)			\
+  { DIRFIRST_CHECK (a, b); return key_cmp_func (b, a, xstrcoll); }	\
+  static int _GL_ATTRIBUTE_PURE rev_strcmp_df_##key_name (V a, V b)	\
+  { DIRFIRST_CHECK (a, b); return key_cmp_func (b, a, strcmp); }
+
+static inline int
+cmp_ctime (struct fileinfo const *a, struct fileinfo const *b,
+           int (*cmp) (char const *, char const *))
+{
+  int diff = timespec_cmp (get_stat_ctime (&b->stat),
+                           get_stat_ctime (&a->stat));
+  return diff ? diff : cmp (a->name, b->name);
+}
+
+static inline int
+cmp_mtime (struct fileinfo const *a, struct fileinfo const *b,
+           int (*cmp) (char const *, char const *))
+{
+  int diff = timespec_cmp (get_stat_mtime (&b->stat),
+                           get_stat_mtime (&a->stat));
+  return diff ? diff : cmp (a->name, b->name);
+}
+
+static inline int
+cmp_atime (struct fileinfo const *a, struct fileinfo const *b,
+           int (*cmp) (char const *, char const *))
+{
+  int diff = timespec_cmp (get_stat_atime (&b->stat),
+                           get_stat_atime (&a->stat));
+  return diff ? diff : cmp (a->name, b->name);
+}
+
+static inline int
+cmp_btime (struct fileinfo const *a, struct fileinfo const *b,
+           int (*cmp) (char const *, char const *))
+{
+  int diff = timespec_cmp (get_stat_btime (&b->stat),
+                           get_stat_btime (&a->stat));
+  return diff ? diff : cmp (a->name, b->name);
+}
+
+static inline int
+cmp_size (struct fileinfo const *a, struct fileinfo const *b,
+          int (*cmp) (char const *, char const *))
+{
+  int diff = longdiff (b->stat.st_size, a->stat.st_size);
+  return diff ? diff : cmp (a->name, b->name);
+}
+
+static inline int
+cmp_name (struct fileinfo const *a, struct fileinfo const *b,
+          int (*cmp) (char const *, char const *))
+{
+  return cmp (a->name, b->name);
+}
+
+/* Compare file extensions.  Files with no extension are 'smallest'.
+   If extensions are the same, compare by file names instead.  */
+
+static inline int
+cmp_extension (struct fileinfo const *a, struct fileinfo const *b,
+               int (*cmp) (char const *, char const *))
+{
+  char const *base1 = strrchr (a->name, '.');
+  char const *base2 = strrchr (b->name, '.');
+  int diff = cmp (base1 ? base1 : "", base2 ? base2 : "");
+  return diff ? diff : cmp (a->name, b->name);
+}
+
+DEFINE_SORT_FUNCTIONS (ctime, cmp_ctime)
+DEFINE_SORT_FUNCTIONS (mtime, cmp_mtime)
+DEFINE_SORT_FUNCTIONS (atime, cmp_atime)
+DEFINE_SORT_FUNCTIONS (btime, cmp_btime)
+DEFINE_SORT_FUNCTIONS (size, cmp_size)
+DEFINE_SORT_FUNCTIONS (name, cmp_name)
+DEFINE_SORT_FUNCTIONS (extension, cmp_extension)
+
+/* Compare file versions.
+   Unlike all other compare functions above, cmp_version depends only
+   on filevercmp, which does not fail (even for locale reasons), and does not
+   need a secondary sort key. See lib/filevercmp.h for function description.
+
+   All the other sort options, in fact, need xstrcoll and strcmp variants,
+   because they all use a string comparison (either as the primary or secondary
+   sort key), and xstrcoll has the ability to do a longjmp if strcoll fails for
+   locale reasons.  Lastly, filevercmp is ALWAYS available with gnulib.  */
+static inline int
+cmp_version (struct fileinfo const *a, struct fileinfo const *b)
+{
+  return filevercmp (a->name, b->name);
+}
+
+static int xstrcoll_version (V a, V b)
+{ return cmp_version (a, b); }
+static int rev_xstrcoll_version (V a, V b)
+{ return cmp_version (b, a); }
+static int xstrcoll_df_version (V a, V b)
+{ DIRFIRST_CHECK (a, b); return cmp_version (a, b); }
+static int rev_xstrcoll_df_version (V a, V b)
+{ DIRFIRST_CHECK (a, b); return cmp_version (b, a); }
+
+
+/* We have 2^3 different variants for each sort-key function
+   (for 3 independent sort modes).
+   The function pointers stored in this array must be dereferenced as:
+
+    sort_variants[sort_key][use_strcmp][reverse][dirs_first]
+
+   Note that the order in which sort keys are listed in the function pointer
+   array below is defined by the order of the elements in the time_type and
+   sort_type enums!  */
+
+#define LIST_SORTFUNCTION_VARIANTS(key_name)                        \
+  {                                                                 \
+    {                                                               \
+      { xstrcoll_##key_name, xstrcoll_df_##key_name },              \
+      { rev_xstrcoll_##key_name, rev_xstrcoll_df_##key_name },      \
+    },                                                              \
+    {                                                               \
+      { strcmp_##key_name, strcmp_df_##key_name },                  \
+      { rev_strcmp_##key_name, rev_strcmp_df_##key_name },          \
+    }                                                               \
+  }
+
+static qsortFunc const sort_functions[][2][2][2] =
+  {
+    LIST_SORTFUNCTION_VARIANTS (name),
+    LIST_SORTFUNCTION_VARIANTS (extension),
+    LIST_SORTFUNCTION_VARIANTS (size),
+
+    {
+      {
+        { xstrcoll_version, xstrcoll_df_version },
+        { rev_xstrcoll_version, rev_xstrcoll_df_version },
+      },
+
+      /* We use NULL for the strcmp variants of version comparison
+         since as explained in cmp_version definition, version comparison
+         does not rely on xstrcoll, so it will never longjmp, and never
+         need to try the strcmp fallback. */
+      {
+        { NULL, NULL },
+        { NULL, NULL },
+      }
+    },
+
+    /* last are time sort functions */
+    LIST_SORTFUNCTION_VARIANTS (mtime),
+    LIST_SORTFUNCTION_VARIANTS (ctime),
+    LIST_SORTFUNCTION_VARIANTS (atime),
+    LIST_SORTFUNCTION_VARIANTS (btime)
+  };
+
+/* The number of sort keys is calculated as the sum of
+     the number of elements in the sort_type enum (i.e., sort_numtypes)
+     the number of elements in the time_type enum (i.e., time_numtypes) - 1
+   This is because when sort_type==sort_time, we have up to
+   time_numtypes possible sort keys.
+
+   This line verifies at compile-time that the array of sort functions has been
+   initialized for all possible sort keys. */
+verify (ARRAY_CARDINALITY (sort_functions)
+        == sort_numtypes + time_numtypes - 1 );
+
+/* Set up SORTED_FILE to point to the in-use entries in CWD_FILE, in order.  */
+
+static void
+initialize_ordering_vector (void)
+{
+  for (size_t i = 0; i < cwd_n_used; i++)
+    sorted_file[i] = &cwd_file[i];
+}
+
+/* Sort the files now in the table.  */
+
+static void
+sort_files (void)
+{
+  bool use_strcmp;
+
+  if (sorted_file_alloc < cwd_n_used + cwd_n_used / 2)
+    {
+      free (sorted_file);
+      sorted_file = xnmalloc (cwd_n_used, 3 * sizeof *sorted_file);
+      sorted_file_alloc = 3 * cwd_n_used;
+    }
+
+  initialize_ordering_vector ();
+
+  if (sort_type == sort_none)
+    return;
+
+  /* Try strcoll.  If it fails, fall back on strcmp.  We can't safely
+     ignore strcoll failures, as a failing strcoll might be a
+     comparison function that is not a total order, and if we ignored
+     the failure this might cause qsort to dump core.  */
+
+  if (! setjmp (failed_strcoll))
+    use_strcmp = false;      /* strcoll() succeeded */
+  else
+    {
+      use_strcmp = true;
+      assert (sort_type != sort_version);
+      initialize_ordering_vector ();
+    }
+
+  /* When sort_type == sort_time, use time_type as subindex.  */
+  mpsort ((void const **) sorted_file, cwd_n_used,
+          sort_functions[sort_type + (sort_type == sort_time ? time_type : 0)]
+                        [use_strcmp][sort_reverse]
+                        [directories_first]);
+}
+
+/* List all the files now in the table.  */
+
+static void
+print_current_files (void)
+{
+  size_t i;
+
+  switch (format)
+    {
+    case one_per_line:
+      for (i = 0; i < cwd_n_used; i++)
+        {
+          print_file_name_and_frills (sorted_file[i], 0);
+          putchar ('\n');
+        }
+      break;
+
+    case many_per_line:
+      if (! line_length)
+        print_with_separator (' ');
+      else
+        print_many_per_line ();
+      break;
+
+    case horizontal:
+      if (! line_length)
+        print_with_separator (' ');
+      else
+        print_horizontal ();
+      break;
+
+    case with_commas:
+      print_with_separator (',');
+      break;
+
+    case long_format:
+      for (i = 0; i < cwd_n_used; i++)
+        {
+          set_normal_color ();
+          print_long_format (sorted_file[i]);
+          DIRED_PUTCHAR ('\n');
+        }
+      break;
+    }
+}
+
+/* Replace the first %b with precomputed aligned month names.
+   Note on glibc-2.7 at least, this speeds up the whole 'ls -lU'
+   process by around 17%, compared to letting strftime() handle the %b.  */
+
+static size_t
+align_nstrftime (char *buf, size_t size, bool recent, struct tm const *tm,
+                 timezone_t tz, int ns)
+{
+  char const *nfmt = (use_abformat
+                      ? abformat[recent][tm->tm_mon]
+                      : long_time_format[recent]);
+  return nstrftime (buf, size, nfmt, tm, tz, ns);
+}
+
+/* Return the expected number of columns in a long-format timestamp,
+   or zero if it cannot be calculated.  */
+
+static int
+long_time_expected_width (void)
+{
+  static int width = -1;
+
+  if (width < 0)
+    {
+      time_t epoch = 0;
+      struct tm tm;
+      char buf[TIME_STAMP_LEN_MAXIMUM + 1];
+
+      /* In case you're wondering if localtime_rz can fail with an input time_t
+         value of 0, let's just say it's very unlikely, but not inconceivable.
+         The TZ environment variable would have to specify a time zone that
+         is 2**31-1900 years or more ahead of UTC.  This could happen only on
+         a 64-bit system that blindly accepts e.g., TZ=UTC+20000000000000.
+         However, this is not possible with Solaris 10 or glibc-2.3.5, since
+         their implementations limit the offset to 167:59 and 24:00, resp.  */
+      if (localtime_rz (localtz, &epoch, &tm))
+        {
+          size_t len = align_nstrftime (buf, sizeof buf, false,
+                                        &tm, localtz, 0);
+          if (len != 0)
+            width = mbsnwidth (buf, len, 0);
+        }
+
+      if (width < 0)
+        width = 0;
+    }
+
+  return width;
+}
+
+/* Print the user or group name NAME, with numeric id ID, using a
+   print width of WIDTH columns.  */
+
+static void
+format_user_or_group (char const *name, unsigned long int id, int width)
+{
+  size_t len;
+
+  if (name)
+    {
+      int width_gap = width - mbswidth (name, 0);
+      int pad = MAX (0, width_gap);
+      fputs (name, stdout);
+      len = strlen (name) + pad;
+
+      do
+        putchar (' ');
+      while (pad--);
+    }
+  else
+    {
+      printf ("%*lu ", width, id);
+      len = width;
+    }
+
+  dired_pos += len + 1;
+}
+
+/* Print the name or id of the user with id U, using a print width of
+   WIDTH.  */
+
+static void
+format_user (uid_t u, int width, bool stat_ok)
+{
+  format_user_or_group (! stat_ok ? "?" :
+                        (numeric_ids ? NULL : getuser (u)), u, width);
+}
+
+/* Likewise, for groups.  */
+
+static void
+format_group (gid_t g, int width, bool stat_ok)
+{
+  format_user_or_group (! stat_ok ? "?" :
+                        (numeric_ids ? NULL : getgroup (g)), g, width);
+}
+
+/* Return the number of columns that format_user_or_group will print.  */
+
+static int
+format_user_or_group_width (char const *name, unsigned long int id)
+{
+  if (name)
+    {
+      int len = mbswidth (name, 0);
+      return MAX (0, len);
+    }
+  else
+    {
+      char buf[INT_BUFSIZE_BOUND (id)];
+      sprintf (buf, "%lu", id);
+      return strlen (buf);
+    }
+}
+
+/* Return the number of columns that format_user will print.  */
+
+static int
+format_user_width (uid_t u)
+{
+  return format_user_or_group_width (numeric_ids ? NULL : getuser (u), u);
+}
+
+/* Likewise, for groups.  */
+
+static int
+format_group_width (gid_t g)
+{
+  return format_user_or_group_width (numeric_ids ? NULL : getgroup (g), g);
+}
+
+/* Return a pointer to a formatted version of F->stat.st_ino,
+   possibly using buffer, BUF, of length BUFLEN, which must be at least
+   INT_BUFSIZE_BOUND (uintmax_t) bytes.  */
+static char *
+format_inode (char *buf, size_t buflen, const struct fileinfo *f)
+{
+  assert (INT_BUFSIZE_BOUND (uintmax_t) <= buflen);
+  return (f->stat_ok && f->stat.st_ino != NOT_AN_INODE_NUMBER
+          ? umaxtostr (f->stat.st_ino, buf)
+          : (char *) "?");
+}
+
+/* Print information about F in long format.  */
+static void
+print_long_format (const struct fileinfo *f)
+{
+  char modebuf[12];
+  char buf
+    [LONGEST_HUMAN_READABLE + 1		/* inode */
+     + LONGEST_HUMAN_READABLE + 1	/* size in blocks */
+     + sizeof (modebuf) - 1 + 1		/* mode string */
+     + INT_BUFSIZE_BOUND (uintmax_t)	/* st_nlink */
+     + LONGEST_HUMAN_READABLE + 2	/* major device number */
+     + LONGEST_HUMAN_READABLE + 1	/* minor device number */
+     + TIME_STAMP_LEN_MAXIMUM + 1	/* max length of time/date */
+     ];
+  size_t s;
+  char *p;
+  struct timespec when_timespec;
+  struct tm when_local;
+  bool btime_ok = true;
+
+  /* Compute the mode string, except remove the trailing space if no
+     file in this directory has an ACL or security context.  */
+  if (f->stat_ok)
+    filemodestring (&f->stat, modebuf);
+  else
+    {
+      modebuf[0] = filetype_letter[f->filetype];
+      memset (modebuf + 1, '?', 10);
+      modebuf[11] = '\0';
+    }
+  if (! any_has_acl)
+    modebuf[10] = '\0';
+  else if (f->acl_type == ACL_T_LSM_CONTEXT_ONLY)
+    modebuf[10] = '.';
+  else if (f->acl_type == ACL_T_YES)
+    modebuf[10] = '+';
+
+  switch (time_type)
+    {
+    case time_ctime:
+      when_timespec = get_stat_ctime (&f->stat);
+      break;
+    case time_mtime:
+      when_timespec = get_stat_mtime (&f->stat);
+      break;
+    case time_atime:
+      when_timespec = get_stat_atime (&f->stat);
+      break;
+    case time_btime:
+      when_timespec = get_stat_btime (&f->stat);
+      if (when_timespec.tv_sec == -1 && when_timespec.tv_nsec == -1)
+        btime_ok = false;
+      break;
+    default:
+      abort ();
+    }
+
+  p = buf;
+
+  if (print_inode)
+    {
+      char hbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+      sprintf (p, "%*s ", inode_number_width,
+               format_inode (hbuf, sizeof hbuf, f));
+      /* Increment by strlen (p) here, rather than by inode_number_width + 1.
+         The latter is wrong when inode_number_width is zero.  */
+      p += strlen (p);
+    }
+
+  if (print_block_size)
+    {
+      char hbuf[LONGEST_HUMAN_READABLE + 1];
+      char const *blocks =
+        (! f->stat_ok
+         ? "?"
+         : human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts,
+                           ST_NBLOCKSIZE, output_block_size));
+      int pad;
+      for (pad = block_size_width - mbswidth (blocks, 0); 0 < pad; pad--)
+        *p++ = ' ';
+      while ((*p++ = *blocks++))
+        continue;
+      p[-1] = ' ';
+    }
+
+  /* The last byte of the mode string is the POSIX
+     "optional alternate access method flag".  */
+  {
+    char hbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+    sprintf (p, "%s %*s ", modebuf, nlink_width,
+             ! f->stat_ok ? "?" : umaxtostr (f->stat.st_nlink, hbuf));
+  }
+  /* Increment by strlen (p) here, rather than by, e.g.,
+     sizeof modebuf - 2 + any_has_acl + 1 + nlink_width + 1.
+     The latter is wrong when nlink_width is zero.  */
+  p += strlen (p);
+
+  DIRED_INDENT ();
+
+  if (print_owner || print_group || print_author || print_scontext)
+    {
+      DIRED_FPUTS (buf, stdout, p - buf);
+
+      if (print_owner)
+        format_user (f->stat.st_uid, owner_width, f->stat_ok);
+
+      if (print_group)
+        format_group (f->stat.st_gid, group_width, f->stat_ok);
+
+      if (print_author)
+        format_user (f->stat.st_author, author_width, f->stat_ok);
+
+      if (print_scontext)
+        format_user_or_group (f->scontext, 0, scontext_width);
+
+      p = buf;
+    }
+
+  if (f->stat_ok
+      && (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode)))
+    {
+      char majorbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+      char minorbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+      int blanks_width = (file_size_width
+                          - (major_device_number_width + 2
+                             + minor_device_number_width));
+      sprintf (p, "%*s, %*s ",
+               major_device_number_width + MAX (0, blanks_width),
+               umaxtostr (major (f->stat.st_rdev), majorbuf),
+               minor_device_number_width,
+               umaxtostr (minor (f->stat.st_rdev), minorbuf));
+      p += file_size_width + 1;
+    }
+  else
+    {
+      char hbuf[LONGEST_HUMAN_READABLE + 1];
+      char const *size =
+        (! f->stat_ok
+         ? "?"
+         : human_readable (unsigned_file_size (f->stat.st_size),
+                           hbuf, file_human_output_opts, 1,
+                           file_output_block_size));
+      int pad;
+      for (pad = file_size_width - mbswidth (size, 0); 0 < pad; pad--)
+        *p++ = ' ';
+      while ((*p++ = *size++))
+        continue;
+      p[-1] = ' ';
+    }
+
+  s = 0;
+  *p = '\1';
+
+  if (f->stat_ok && btime_ok
+      && localtime_rz (localtz, &when_timespec.tv_sec, &when_local))
+    {
+      struct timespec six_months_ago;
+      bool recent;
+
+      /* If the file appears to be in the future, update the current
+         time, in case the file happens to have been modified since
+         the last time we checked the clock.  */
+      if (timespec_cmp (current_time, when_timespec) < 0)
+        gettime (&current_time);
+
+      /* Consider a time to be recent if it is within the past six months.
+         A Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds
+         on the average.  Write this value as an integer constant to
+         avoid floating point hassles.  */
+      six_months_ago.tv_sec = current_time.tv_sec - 31556952 / 2;
+      six_months_ago.tv_nsec = current_time.tv_nsec;
+
+      recent = (timespec_cmp (six_months_ago, when_timespec) < 0
+                && (timespec_cmp (when_timespec, current_time) < 0));
+
+      /* We assume here that all time zones are offset from UTC by a
+         whole number of seconds.  */
+      s = align_nstrftime (p, TIME_STAMP_LEN_MAXIMUM + 1, recent,
+                           &when_local, localtz, when_timespec.tv_nsec);
+    }
+
+  if (s || !*p)
+    {
+      p += s;
+      *p++ = ' ';
+
+      /* NUL-terminate the string -- fputs (via DIRED_FPUTS) requires it.  */
+      *p = '\0';
+    }
+  else
+    {
+      /* The time cannot be converted using the desired format, so
+         print it as a huge integer number of seconds.  */
+      char hbuf[INT_BUFSIZE_BOUND (intmax_t)];
+      sprintf (p, "%*s ", long_time_expected_width (),
+               (! f->stat_ok || ! btime_ok
+                ? "?"
+                : timetostr (when_timespec.tv_sec, hbuf)));
+      /* FIXME: (maybe) We discarded when_timespec.tv_nsec. */
+      p += strlen (p);
+    }
+
+  DIRED_FPUTS (buf, stdout, p - buf);
+  size_t w = print_name_with_quoting (f, false, &dired_obstack, p - buf);
+
+  if (f->filetype == symbolic_link)
+    {
+      if (f->linkname)
+        {
+          DIRED_FPUTS_LITERAL (" -> ", stdout);
+          print_name_with_quoting (f, true, NULL, (p - buf) + w + 4);
+          if (indicator_style != none)
+            print_type_indicator (true, f->linkmode, unknown);
+        }
+    }
+  else if (indicator_style != none)
+    print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype);
+}
+
+/* Write to *BUF a quoted representation of the file name NAME, if non-NULL,
+   using OPTIONS to control quoting.  *BUF is set to NAME if no quoting
+   is required.  *BUF is allocated if more space required (and the original
+   *BUF is not deallocated).
+   Store the number of screen columns occupied by NAME's quoted
+   representation into WIDTH, if non-NULL.
+   Store into PAD whether an initial space is needed for padding.
+   Return the number of bytes in *BUF.  */
+
+static size_t
+quote_name_buf (char **inbuf, size_t bufsize, char *name,
+                struct quoting_options const *options,
+                int needs_general_quoting, size_t *width, bool *pad)
+{
+  char *buf = *inbuf;
+  size_t displayed_width IF_LINT ( = 0);
+  size_t len = 0;
+  bool quoted;
+
+  enum quoting_style qs = get_quoting_style (options);
+  bool needs_further_quoting = qmark_funny_chars
+                               && (qs == shell_quoting_style
+                                   || qs == shell_always_quoting_style
+                                   || qs == literal_quoting_style);
+
+  if (needs_general_quoting != 0)
+    {
+      len = quotearg_buffer (buf, bufsize, name, -1, options);
+      if (bufsize <= len)
+        {
+          buf = xmalloc (len + 1);
+          quotearg_buffer (buf, len + 1, name, -1, options);
+        }
+
+      quoted = (*name != *buf) || strlen (name) != len;
+    }
+  else if (needs_further_quoting)
+    {
+      len = strlen (name);
+      if (bufsize <= len)
+        buf = xmalloc (len + 1);
+      memcpy (buf, name, len + 1);
+
+      quoted = false;
+    }
+  else
+    {
+      len = strlen (name);
+      buf = name;
+      quoted = false;
+    }
+
+  if (needs_further_quoting)
+    {
+      if (MB_CUR_MAX > 1)
+        {
+          char const *p = buf;
+          char const *plimit = buf + len;
+          char *q = buf;
+          displayed_width = 0;
+
+          while (p < plimit)
+            switch (*p)
+              {
+                case ' ': case '!': case '"': case '#': case '%':
+                case '&': case '\'': case '(': case ')': case '*':
+                case '+': case ',': case '-': case '.': case '/':
+                case '0': case '1': case '2': case '3': case '4':
+                case '5': case '6': case '7': case '8': case '9':
+                case ':': case ';': case '<': case '=': case '>':
+                case '?':
+                case 'A': case 'B': case 'C': case 'D': case 'E':
+                case 'F': case 'G': case 'H': case 'I': case 'J':
+                case 'K': case 'L': case 'M': case 'N': case 'O':
+                case 'P': case 'Q': case 'R': case 'S': case 'T':
+                case 'U': case 'V': case 'W': case 'X': case 'Y':
+                case 'Z':
+                case '[': case '\\': case ']': case '^': case '_':
+                case 'a': case 'b': case 'c': case 'd': case 'e':
+                case 'f': case 'g': case 'h': case 'i': case 'j':
+                case 'k': case 'l': case 'm': case 'n': case 'o':
+                case 'p': case 'q': case 'r': case 's': case 't':
+                case 'u': case 'v': case 'w': case 'x': case 'y':
+                case 'z': case '{': case '|': case '}': case '~':
+                  /* These characters are printable ASCII characters.  */
+                  *q++ = *p++;
+                  displayed_width += 1;
+                  break;
+                default:
+                  /* If we have a multibyte sequence, copy it until we
+                     reach its end, replacing each non-printable multibyte
+                     character with a single question mark.  */
+                  {
+                    mbstate_t mbstate = { 0, };
+                    do
+                      {
+                        wchar_t wc;
+                        size_t bytes;
+                        int w;
+
+                        bytes = mbrtowc (&wc, p, plimit - p, &mbstate);
+
+                        if (bytes == (size_t) -1)
+                          {
+                            /* An invalid multibyte sequence was
+                               encountered.  Skip one input byte, and
+                               put a question mark.  */
+                            p++;
+                            *q++ = '?';
+                            displayed_width += 1;
+                            break;
+                          }
+
+                        if (bytes == (size_t) -2)
+                          {
+                            /* An incomplete multibyte character
+                               at the end.  Replace it entirely with
+                               a question mark.  */
+                            p = plimit;
+                            *q++ = '?';
+                            displayed_width += 1;
+                            break;
+                          }
+
+                        if (bytes == 0)
+                          /* A null wide character was encountered.  */
+                          bytes = 1;
+
+                        w = wcwidth (wc);
+                        if (w >= 0)
+                          {
+                            /* A printable multibyte character.
+                               Keep it.  */
+                            for (; bytes > 0; --bytes)
+                              *q++ = *p++;
+                            displayed_width += w;
+                          }
+                        else
+                          {
+                            /* An unprintable multibyte character.
+                               Replace it entirely with a question
+                               mark.  */
+                            p += bytes;
+                            *q++ = '?';
+                            displayed_width += 1;
+                          }
+                      }
+                    while (! mbsinit (&mbstate));
+                  }
+                  break;
+              }
+
+          /* The buffer may have shrunk.  */
+          len = q - buf;
+        }
+      else
+        {
+          char *p = buf;
+          char const *plimit = buf + len;
+
+          while (p < plimit)
+            {
+              if (! isprint (to_uchar (*p)))
+                *p = '?';
+              p++;
+            }
+          displayed_width = len;
+        }
+    }
+  else if (width != NULL)
+    {
+      if (MB_CUR_MAX > 1)
+        displayed_width = mbsnwidth (buf, len, 0);
+      else
+        {
+          char const *p = buf;
+          char const *plimit = buf + len;
+
+          displayed_width = 0;
+          while (p < plimit)
+            {
+              if (isprint (to_uchar (*p)))
+                displayed_width++;
+              p++;
+            }
+        }
+    }
+
+  /* Set padding to better align quoted items,
+     and also give a visual indication that quotes are
+     not actually part of the name.  */
+  *pad = (align_variable_outer_quotes && cwd_some_quoted && ! quoted);
+
+  if (width != NULL)
+    *width = displayed_width;
+
+  *inbuf = buf;
+
+  return len;
+}
+
+static size_t
+quote_name_width (const char *name, struct quoting_options const *options,
+                  int needs_general_quoting)
+{
+  char smallbuf[BUFSIZ];
+  char *buf = smallbuf;
+  size_t width;
+  bool pad;
+
+  quote_name_buf (&buf, sizeof smallbuf, (char *) name, options,
+                  needs_general_quoting, &width, &pad);
+
+  if (buf != smallbuf && buf != name)
+    free (buf);
+
+  width += pad;
+
+  return width;
+}
+
+/* %XX escape any input out of range as defined in RFC3986,
+   and also if PATH, convert all path separators to '/'.  */
+static char *
+file_escape (const char *str, bool path)
+{
+  char *esc = xnmalloc (3, strlen (str) + 1);
+  char *p = esc;
+  while (*str)
+    {
+      if (path && ISSLASH (*str))
+        {
+          *p++ = '/';
+          str++;
+        }
+      else if (RFC3986[to_uchar (*str)])
+        *p++ = *str++;
+      else
+        p += sprintf (p, "%%%02x", to_uchar (*str++));
+    }
+  *p = '\0';
+  return esc;
+}
+
+static size_t
+quote_name (char const *name, struct quoting_options const *options,
+            int needs_general_quoting, const struct bin_str *color,
+            bool allow_pad, struct obstack *stack, char const *absolute_name)
+{
+  char smallbuf[BUFSIZ];
+  char *buf = smallbuf;
+  size_t len;
+  bool pad;
+
+  len = quote_name_buf (&buf, sizeof smallbuf, (char *) name, options,
+                        needs_general_quoting, NULL, &pad);
+
+  if (pad && allow_pad)
+      DIRED_PUTCHAR (' ');
+
+  if (color)
+    print_color_indicator (color);
+
+  /* If we're padding, then don't include the outer quotes in
+     the --hyperlink, to improve the alignment of those links.  */
+  bool skip_quotes = false;
+
+  if (absolute_name)
+    {
+      if (align_variable_outer_quotes && cwd_some_quoted && ! pad)
+        {
+          skip_quotes = true;
+          putchar (*buf);
+        }
+      char *h = file_escape (hostname, /* path= */ false);
+      char *n = file_escape (absolute_name, /* path= */ true);
+      /* TODO: It would be good to be able to define parameters
+         to give hints to the terminal as how best to render the URI.
+         For example since ls is outputting a dense block of URIs
+         it would be best to not underline by default, and only
+         do so upon hover etc.  */
+      printf ("\033]8;;file://%s%s%s\a", h, *n == '/' ? "" : "/", n);
+      free (h);
+      free (n);
+    }
+
+  if (stack)
+    PUSH_CURRENT_DIRED_POS (stack);
+
+  fwrite (buf + skip_quotes, 1, len - (skip_quotes * 2), stdout);
+
+  dired_pos += len;
+
+  if (stack)
+    PUSH_CURRENT_DIRED_POS (stack);
+
+  if (absolute_name)
+    {
+      fputs ("\033]8;;\a", stdout);
+      if (skip_quotes)
+        putchar (*(buf + len - 1));
+    }
+
+  if (buf != smallbuf && buf != name)
+    free (buf);
+
+  return len + pad;
+}
+
+static size_t
+print_name_with_quoting (const struct fileinfo *f,
+                         bool symlink_target,
+                         struct obstack *stack,
+                         size_t start_col)
+{
+  const char* name = symlink_target ? f->linkname : f->name;
+
+  const struct bin_str *color = print_with_color ?
+                                get_color_indicator (f, symlink_target) : NULL;
+
+  bool used_color_this_time = (print_with_color
+                               && (color || is_colored (C_NORM)));
+
+  size_t len = quote_name (name, filename_quoting_options, f->quoted,
+                           color, !symlink_target, stack, f->absolute_name);
+
+  process_signals ();
+  if (used_color_this_time)
+    {
+      prep_non_filename_text ();
+
+      /* We use the byte length rather than display width here as
+         an optimization to avoid accurately calculating the width,
+         because we only output the clear to EOL sequence if the name
+         _might_ wrap to the next line.  This may output a sequence
+         unnecessarily in multi-byte locales for example,
+         but in that case it's inconsequential to the output.  */
+      if (line_length
+          && (start_col / line_length != (start_col + len - 1) / line_length))
+        put_indicator (&color_indicator[C_CLR_TO_EOL]);
+    }
+
+  return len;
+}
+
+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_RESET]);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
+}
+
+/* Print the file name of 'f' with appropriate quoting.
+   Also print file size, inode number, and filetype indicator character,
+   as requested by switches.  */
+
+static size_t
+print_file_name_and_frills (const struct fileinfo *f, size_t start_col)
+{
+  char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))];
+
+  set_normal_color ();
+
+  if (print_inode)
+    printf ("%*s ", format == with_commas ? 0 : inode_number_width,
+            format_inode (buf, sizeof buf, f));
+
+  if (print_block_size)
+    printf ("%*s ", format == with_commas ? 0 : block_size_width,
+            ! f->stat_ok ? "?"
+            : human_readable (ST_NBLOCKS (f->stat), buf, human_output_opts,
+                              ST_NBLOCKSIZE, output_block_size));
+
+  if (print_scontext)
+    printf ("%*s ", format == with_commas ? 0 : scontext_width, f->scontext);
+
+  size_t width = print_name_with_quoting (f, false, NULL, start_col);
+
+  if (indicator_style != none)
+    width += print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype);
+
+  return width;
+}
+
+/* Given these arguments describing a file, return the single-byte
+   type indicator, or 0.  */
+static char
+get_type_indicator (bool stat_ok, mode_t mode, enum filetype type)
+{
+  char c;
+
+  if (stat_ok ? S_ISREG (mode) : type == normal)
+    {
+      if (stat_ok && indicator_style == classify && (mode & S_IXUGO))
+        c = '*';
+      else
+        c = 0;
+    }
+  else
+    {
+      if (stat_ok ? S_ISDIR (mode) : type == directory || type == arg_directory)
+        c = '/';
+      else if (indicator_style == slash)
+        c = 0;
+      else if (stat_ok ? S_ISLNK (mode) : type == symbolic_link)
+        c = '@';
+      else if (stat_ok ? S_ISFIFO (mode) : type == fifo)
+        c = '|';
+      else if (stat_ok ? S_ISSOCK (mode) : type == sock)
+        c = '=';
+      else if (stat_ok && S_ISDOOR (mode))
+        c = '>';
+      else
+        c = 0;
+    }
+  return c;
+}
+
+static bool
+print_type_indicator (bool stat_ok, mode_t mode, enum filetype type)
+{
+  char c = get_type_indicator (stat_ok, mode, type);
+  if (c)
+    DIRED_PUTCHAR (c);
+  return !!c;
+}
+
+/* Returns if color sequence was printed.  */
+static bool
+print_color_indicator (const struct bin_str *ind)
+{
+  if (ind)
+    {
+      /* Need to reset so not dealing with attribute combinations */
+      if (is_colored (C_NORM))
+        restore_default_color ();
+      put_indicator (&color_indicator[C_LEFT]);
+      put_indicator (ind);
+      put_indicator (&color_indicator[C_RIGHT]);
+    }
+
+  return ind != NULL;
+}
+
+/* Returns color indicator or NULL if none.  */
+static const struct bin_str* _GL_ATTRIBUTE_PURE
+get_color_indicator (const struct fileinfo *f, bool symlink_target)
+{
+  enum indicator_no type;
+  struct color_ext_type *ext;	/* Color extension */
+  size_t len;			/* Length of name */
+
+  const char* name;
+  mode_t mode;
+  int linkok;
+  if (symlink_target)
+    {
+      name = f->linkname;
+      mode = f->linkmode;
+      linkok = f->linkok ? 0 : -1;
+    }
+  else
+    {
+      name = f->name;
+      mode = FILE_OR_LINK_MODE (f);
+      linkok = f->linkok;
+    }
+
+  /* Is this a nonexistent file?  If so, linkok == -1.  */
+
+  if (linkok == -1 && is_colored (C_MISSING))
+    type = C_MISSING;
+  else if (!f->stat_ok)
+    {
+      static enum indicator_no filetype_indicator[] = FILETYPE_INDICATORS;
+      type = filetype_indicator[f->filetype];
+    }
+  else
+    {
+      if (S_ISREG (mode))
+        {
+          type = C_FILE;
+
+          if ((mode & S_ISUID) != 0 && is_colored (C_SETUID))
+            type = C_SETUID;
+          else if ((mode & S_ISGID) != 0 && is_colored (C_SETGID))
+            type = C_SETGID;
+          else if (is_colored (C_CAP) && f->has_capability)
+            type = C_CAP;
+          else if ((mode & S_IXUGO) != 0 && is_colored (C_EXEC))
+            type = C_EXEC;
+          else if ((1 < f->stat.st_nlink) && is_colored (C_MULTIHARDLINK))
+            type = C_MULTIHARDLINK;
+        }
+      else if (S_ISDIR (mode))
+        {
+          type = C_DIR;
+
+          if ((mode & S_ISVTX) && (mode & S_IWOTH)
+              && is_colored (C_STICKY_OTHER_WRITABLE))
+            type = C_STICKY_OTHER_WRITABLE;
+          else if ((mode & S_IWOTH) != 0 && is_colored (C_OTHER_WRITABLE))
+            type = C_OTHER_WRITABLE;
+          else if ((mode & S_ISVTX) != 0 && is_colored (C_STICKY))
+            type = C_STICKY;
+        }
+      else if (S_ISLNK (mode))
+        type = C_LINK;
+      else if (S_ISFIFO (mode))
+        type = C_FIFO;
+      else if (S_ISSOCK (mode))
+        type = C_SOCK;
+      else if (S_ISBLK (mode))
+        type = C_BLK;
+      else if (S_ISCHR (mode))
+        type = C_CHR;
+      else if (S_ISDOOR (mode))
+        type = C_DOOR;
+      else
+        {
+          /* Classify a file of some other type as C_ORPHAN.  */
+          type = C_ORPHAN;
+        }
+    }
+
+  /* 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 = color_ext_list; ext != NULL; ext = ext->next)
+        {
+          if (ext->ext.len <= len
+              && c_strncasecmp (name - ext->ext.len, ext->ext.string,
+                                ext->ext.len) == 0)
+            break;
+        }
+    }
+
+  /* Adjust the color for orphaned symlinks.  */
+  if (type == C_LINK && !linkok)
+    {
+      if (color_symlink_as_referent || is_colored (C_ORPHAN))
+        type = C_ORPHAN;
+    }
+
+  const struct bin_str *const s
+    = ext ? &(ext->seq) : &color_indicator[type];
+
+  return s->string ? s : NULL;
+}
+
+/* Output a color indicator (which may contain nulls).  */
+static void
+put_indicator (const struct bin_str *ind)
+{
+  if (! used_color)
+    {
+      used_color = true;
+
+      /* If the standard output is a controlling terminal, watch out
+         for signals, so that the colors can be restored to the
+         default state if "ls" is suspended or interrupted.  */
+
+      if (0 <= tcgetpgrp (STDOUT_FILENO))
+        signal_init ();
+
+      prep_non_filename_text ();
+    }
+
+  fwrite (ind->string, ind->len, 1, stdout);
+}
+
+static size_t
+length_of_file_name_and_frills (const struct fileinfo *f)
+{
+  size_t len = 0;
+  char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))];
+
+  if (print_inode)
+    len += 1 + (format == with_commas
+                ? strlen (umaxtostr (f->stat.st_ino, buf))
+                : inode_number_width);
+
+  if (print_block_size)
+    len += 1 + (format == with_commas
+                ? strlen (! f->stat_ok ? "?"
+                          : human_readable (ST_NBLOCKS (f->stat), buf,
+                                            human_output_opts, ST_NBLOCKSIZE,
+                                            output_block_size))
+                : block_size_width);
+
+  if (print_scontext)
+    len += 1 + (format == with_commas ? strlen (f->scontext) : scontext_width);
+
+  len += quote_name_width (f->name, filename_quoting_options, f->quoted);
+
+  if (indicator_style != none)
+    {
+      char c = get_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype);
+      len += (c != 0);
+    }
+
+  return len;
+}
+
+static void
+print_many_per_line (void)
+{
+  size_t row;			/* Current row.  */
+  size_t cols = calculate_columns (true);
+  struct column_info const *line_fmt = &column_info[cols - 1];
+
+  /* Calculate the number of rows that will be in each column except possibly
+     for a short column on the right.  */
+  size_t rows = cwd_n_used / cols + (cwd_n_used % cols != 0);
+
+  for (row = 0; row < rows; row++)
+    {
+      size_t col = 0;
+      size_t filesno = row;
+      size_t pos = 0;
+
+      /* Print the next row.  */
+      while (1)
+        {
+          struct fileinfo const *f = sorted_file[filesno];
+          size_t name_length = length_of_file_name_and_frills (f);
+          size_t max_name_length = line_fmt->col_arr[col++];
+          print_file_name_and_frills (f, pos);
+
+          filesno += rows;
+          if (filesno >= cwd_n_used)
+            break;
+
+          indent (pos + name_length, pos + max_name_length);
+          pos += max_name_length;
+        }
+      putchar ('\n');
+    }
+}
+
+static void
+print_horizontal (void)
+{
+  size_t filesno;
+  size_t pos = 0;
+  size_t cols = calculate_columns (false);
+  struct column_info const *line_fmt = &column_info[cols - 1];
+  struct fileinfo const *f = sorted_file[0];
+  size_t name_length = length_of_file_name_and_frills (f);
+  size_t max_name_length = line_fmt->col_arr[0];
+
+  /* Print first entry.  */
+  print_file_name_and_frills (f, 0);
+
+  /* Now the rest.  */
+  for (filesno = 1; filesno < cwd_n_used; ++filesno)
+    {
+      size_t col = filesno % cols;
+
+      if (col == 0)
+        {
+          putchar ('\n');
+          pos = 0;
+        }
+      else
+        {
+          indent (pos + name_length, pos + max_name_length);
+          pos += max_name_length;
+        }
+
+      f = sorted_file[filesno];
+      print_file_name_and_frills (f, pos);
+
+      name_length = length_of_file_name_and_frills (f);
+      max_name_length = line_fmt->col_arr[col];
+    }
+  putchar ('\n');
+}
+
+/* Output name + SEP + ' '.  */
+
+static void
+print_with_separator (char sep)
+{
+  size_t filesno;
+  size_t pos = 0;
+
+  for (filesno = 0; filesno < cwd_n_used; filesno++)
+    {
+      struct fileinfo const *f = sorted_file[filesno];
+      size_t len = line_length ? length_of_file_name_and_frills (f) : 0;
+
+      if (filesno != 0)
+        {
+          char separator;
+
+          if (! line_length
+              || ((pos + len + 2 < line_length)
+                  && (pos <= SIZE_MAX - len - 2)))
+            {
+              pos += 2;
+              separator = ' ';
+            }
+          else
+            {
+              pos = 0;
+              separator = '\n';
+            }
+
+          putchar (sep);
+          putchar (separator);
+        }
+
+      print_file_name_and_frills (f, pos);
+      pos += len;
+    }
+  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 (size_t from, size_t to)
+{
+  while (from < to)
+    {
+      if (tabsize != 0 && to / tabsize > (from + 1) / tabsize)
+        {
+          putchar ('\t');
+          from += tabsize - from % tabsize;
+        }
+      else
+        {
+          putchar (' ');
+          from++;
+        }
+    }
+}
+
+/* Put DIRNAME/NAME into DEST, handling '.' and '/' properly.  */
+/* FIXME: maybe remove this function someday.  See about using a
+   non-malloc'ing version of file_name_concat.  */
+
+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;
+}
+
+/* Allocate enough column info suitable for the current number of
+   files and display columns, and initialize the info to represent the
+   narrowest possible columns.  */
+
+static void
+init_column_info (void)
+{
+  size_t i;
+  size_t max_cols = MIN (max_idx, cwd_n_used);
+
+  /* Currently allocated columns in column_info.  */
+  static size_t column_info_alloc;
+
+  if (column_info_alloc < max_cols)
+    {
+      size_t new_column_info_alloc;
+      size_t *p;
+
+      if (max_cols < max_idx / 2)
+        {
+          /* The number of columns is far less than the display width
+             allows.  Grow the allocation, but only so that it's
+             double the current requirements.  If the display is
+             extremely wide, this avoids allocating a lot of memory
+             that is never needed.  */
+          column_info = xnrealloc (column_info, max_cols,
+                                   2 * sizeof *column_info);
+          new_column_info_alloc = 2 * max_cols;
+        }
+      else
+        {
+          column_info = xnrealloc (column_info, max_idx, sizeof *column_info);
+          new_column_info_alloc = max_idx;
+        }
+
+      /* Allocate the new size_t objects by computing the triangle
+         formula n * (n + 1) / 2, except that we don't need to
+         allocate the part of the triangle that we've already
+         allocated.  Check for address arithmetic overflow.  */
+      {
+        size_t column_info_growth = new_column_info_alloc - column_info_alloc;
+        size_t s = column_info_alloc + 1 + new_column_info_alloc;
+        size_t t = s * column_info_growth;
+        if (s < new_column_info_alloc || t / column_info_growth != s)
+          xalloc_die ();
+        p = xnmalloc (t / 2, sizeof *p);
+      }
+
+      /* Grow the triangle by parceling out the cells just allocated.  */
+      for (i = column_info_alloc; i < new_column_info_alloc; i++)
+        {
+          column_info[i].col_arr = p;
+          p += i + 1;
+        }
+
+      column_info_alloc = new_column_info_alloc;
+    }
+
+  for (i = 0; i < max_cols; ++i)
+    {
+      size_t j;
+
+      column_info[i].valid_len = true;
+      column_info[i].line_len = (i + 1) * MIN_COLUMN_WIDTH;
+      for (j = 0; j <= i; ++j)
+        column_info[i].col_arr[j] = MIN_COLUMN_WIDTH;
+    }
+}
+
+/* Calculate the number of columns needed to represent the current set
+   of files in the current display width.  */
+
+static size_t
+calculate_columns (bool by_columns)
+{
+  size_t filesno;		/* Index into cwd_file.  */
+  size_t cols;			/* Number of files across.  */
+
+  /* Normally the maximum number of columns is determined by the
+     screen width.  But if few files are available this might limit it
+     as well.  */
+  size_t max_cols = MIN (max_idx, cwd_n_used);
+
+  init_column_info ();
+
+  /* Compute the maximum number of possible columns.  */
+  for (filesno = 0; filesno < cwd_n_used; ++filesno)
+    {
+      struct fileinfo const *f = sorted_file[filesno];
+      size_t name_length = length_of_file_name_and_frills (f);
+
+      for (size_t i = 0; i < max_cols; ++i)
+        {
+          if (column_info[i].valid_len)
+            {
+              size_t idx = (by_columns
+                            ? filesno / ((cwd_n_used + i) / (i + 1))
+                            : filesno % (i + 1));
+              size_t real_length = name_length + (idx == i ? 0 : 2);
+
+              if (column_info[i].col_arr[idx] < real_length)
+                {
+                  column_info[i].line_len += (real_length
+                                              - column_info[i].col_arr[idx]);
+                  column_info[i].col_arr[idx] = real_length;
+                  column_info[i].valid_len = (column_info[i].line_len
+                                              < line_length);
+                }
+            }
+        }
+    }
+
+  /* Find maximum allowed columns.  */
+  for (cols = max_cols; 1 < cols; --cols)
+    {
+      if (column_info[cols - 1].valid_len)
+        break;
+    }
+
+  return cols;
+}
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    emit_try_help ();
+  else
+    {
+      printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
+      fputs (_("\
+List information about the FILEs (the current directory by default).\n\
+Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.\n\
+"), stdout);
+
+      emit_mandatory_arg_note ();
+
+      fputs (_("\
+  -a, --all                  do not ignore entries starting with .\n\
+  -A, --almost-all           do not list implied . and ..\n\
+      --author               with -l, print the author of each file\n\
+  -b, --escape               print C-style escapes for nongraphic characters\n\
+"), stdout);
+      fputs (_("\
+      --block-size=SIZE      with -l, scale sizes by SIZE when printing them;\n\
+                               e.g., '--block-size=M'; see SIZE format below\n\
+"), stdout);
+      fputs (_("\
+  -B, --ignore-backups       do not list implied entries ending with ~\n\
+  -c                         with -lt: sort by, and show, ctime (time of last\n\
+                               modification of file status information);\n\
+                               with -l: show ctime and sort by name;\n\
+                               otherwise: sort by ctime, newest first\n\
+"), stdout);
+      fputs (_("\
+  -C                         list entries by columns\n\
+      --color[=WHEN]         colorize the output; WHEN can be 'always' (default\
+\n\
+                               if omitted), 'auto', or 'never'; more info below\
+\n\
+  -d, --directory            list directories themselves, not their contents\n\
+  -D, --dired                generate output designed for Emacs' dired mode\n\
+"), stdout);
+      fputs (_("\
+  -f                         do not sort, enable -aU, disable -ls --color\n\
+  -F, --classify             append indicator (one of */=>@|) to entries\n\
+      --file-type            likewise, except do not append '*'\n\
+      --format=WORD          across -x, commas -m, horizontal -x, long -l,\n\
+                               single-column -1, verbose -l, vertical -C\n\
+      --full-time            like -l --time-style=full-iso\n\
+"), stdout);
+      fputs (_("\
+  -g                         like -l, but do not list owner\n\
+"), stdout);
+      fputs (_("\
+      --group-directories-first\n\
+                             group directories before files;\n\
+                               can be augmented with a --sort option, but any\n\
+                               use of --sort=none (-U) disables grouping\n\
+"), stdout);
+      fputs (_("\
+  -G, --no-group             in a long listing, don't print group names\n\
+"), stdout);
+      fputs (_("\
+  -h, --human-readable       with -l and -s, print sizes like 1K 234M 2G etc.\n\
+      --si                   likewise, but use powers of 1000 not 1024\n\
+"), stdout);
+      fputs (_("\
+  -H, --dereference-command-line\n\
+                             follow symbolic links listed on the command line\n\
+      --dereference-command-line-symlink-to-dir\n\
+                             follow each command line symbolic link\n\
+                               that points to a directory\n\
+      --hide=PATTERN         do not list implied entries matching shell PATTERN\
+\n\
+                               (overridden by -a or -A)\n\
+"), stdout);
+      fputs (_("\
+      --hyperlink[=WHEN]     hyperlink file names; WHEN can be 'always'\n\
+                               (default if omitted), 'auto', or 'never'\n\
+"), stdout);
+      fputs (_("\
+      --indicator-style=WORD  append indicator with style WORD to entry names:\
+\n\
+                               none (default), slash (-p),\n\
+                               file-type (--file-type), classify (-F)\n\
+  -i, --inode                print the index number of each file\n\
+  -I, --ignore=PATTERN       do not list implied entries matching shell PATTERN\
+\n\
+"), stdout);
+      fputs (_("\
+  -k, --kibibytes            default to 1024-byte blocks for disk usage;\n\
+                               used only with -s and per directory totals\n\
+"), stdout);
+      fputs (_("\
+  -l                         use a long listing format\n\
+  -L, --dereference          when showing file information for a symbolic\n\
+                               link, show information for the file the link\n\
+                               references rather than for the link itself\n\
+  -m                         fill width with a comma separated list of entries\
+\n\
+"), stdout);
+      fputs (_("\
+  -n, --numeric-uid-gid      like -l, but list numeric user and group IDs\n\
+  -N, --literal              print entry names without quoting\n\
+  -o                         like -l, but do not list group information\n\
+  -p, --indicator-style=slash\n\
+                             append / indicator to directories\n\
+"), stdout);
+      fputs (_("\
+  -q, --hide-control-chars   print ? instead of nongraphic characters\n\
+      --show-control-chars   show nongraphic characters as-is (the default,\n\
+                               unless program is 'ls' and output is a terminal)\
+\n\
+  -Q, --quote-name           enclose entry names in double quotes\n\
+      --quoting-style=WORD   use quoting style WORD for entry names:\n\
+                               literal, locale, shell, shell-always,\n\
+                               shell-escape, shell-escape-always, c, escape\n\
+                               (overrides QUOTING_STYLE environment variable)\n\
+"), stdout);
+      fputs (_("\
+  -r, --reverse              reverse order while sorting\n\
+  -R, --recursive            list subdirectories recursively\n\
+  -s, --size                 print the allocated size of each file, in blocks\n\
+"), stdout);
+      fputs (_("\
+  -S                         sort by file size, largest first\n\
+      --sort=WORD            sort by WORD instead of name: none (-U), size (-S)\
+,\n\
+                               time (-t), version (-v), extension (-X)\n\
+      --time=WORD            change the default of using modification times;\n\
+                               access time (-u): atime, access, use;\n\
+                               change time (-c): ctime, status;\n\
+                               birth time: birth, creation;\n\
+                             with -l, WORD determines which time to show;\n\
+                             with --sort=time, sort by WORD (newest first)\n\
+"), stdout);
+      fputs (_("\
+      --time-style=TIME_STYLE  time/date format with -l; see TIME_STYLE below\n\
+"), stdout);
+      fputs (_("\
+  -t                         sort by time, newest first; see --time\n\
+  -T, --tabsize=COLS         assume tab stops at each COLS instead of 8\n\
+"), stdout);
+      fputs (_("\
+  -u                         with -lt: sort by, and show, access time;\n\
+                               with -l: show access time and sort by name;\n\
+                               otherwise: sort by access time, newest first\n\
+  -U                         do not sort; list entries in directory order\n\
+  -v                         natural sort of (version) numbers within text\n\
+"), stdout);
+      fputs (_("\
+  -w, --width=COLS           set output width to COLS.  0 means no limit\n\
+  -x                         list entries by lines instead of by columns\n\
+  -X                         sort alphabetically by entry extension\n\
+  -Z, --context              print any security context of each file\n\
+  -1                         list one file per line.  Avoid '\\n' with -q or -b\
+\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      emit_size_note ();
+      fputs (_("\
+\n\
+The TIME_STYLE argument can be full-iso, long-iso, iso, locale, or +FORMAT.\n\
+FORMAT is interpreted like in date(1).  If FORMAT is FORMAT1<newline>FORMAT2,\n\
+then FORMAT1 applies to non-recent files and FORMAT2 to recent files.\n\
+TIME_STYLE prefixed with 'posix-' takes effect only outside the POSIX locale.\n\
+Also the TIME_STYLE environment variable sets the default style to use.\n\
+"), stdout);
+      fputs (_("\
+\n\
+Using color to distinguish file types is disabled both by default and\n\
+with --color=never.  With --color=auto, ls emits color codes only when\n\
+standard output is connected to a terminal.  The LS_COLORS environment\n\
+variable can change the settings.  Use the dircolors command to set it.\n\
+"), stdout);
+      fputs (_("\
+\n\
+Exit status:\n\
+ 0  if OK,\n\
+ 1  if minor problems (e.g., cannot access subdirectory),\n\
+ 2  if serious trouble (e.g., cannot access command-line argument).\n\
+"), stdout);
+      emit_ancillary_info (PROGRAM_NAME);
+    }
+  exit (status);
+}
Index: coreutils-8.32-new/src
===================================================================
--- coreutils-8.32-new/src	(nonexistent)
+++ coreutils-8.32-new/src	(revision 5)

Property changes on: coreutils-8.32-new/src
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: coreutils-8.32-new
===================================================================
--- coreutils-8.32-new	(nonexistent)
+++ coreutils-8.32-new	(revision 5)

Property changes on: coreutils-8.32-new
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: create.patch.sh
===================================================================
--- create.patch.sh	(nonexistent)
+++ create.patch.sh	(revision 5)
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+VERSION=8.32
+
+tar --files-from=file.list -xJvf ../coreutils-$VERSION.tar.xz
+mv coreutils-$VERSION coreutils-$VERSION-orig
+
+cp -rf ./coreutils-$VERSION-new ./coreutils-$VERSION
+
+diff --unified -Nr  coreutils-$VERSION-orig  coreutils-$VERSION > coreutils-$VERSION-syscall64-ls.patch
+
+mv coreutils-$VERSION-syscall64-ls.patch ../patches
+
+rm -rf ./coreutils-$VERSION
+rm -rf ./coreutils-$VERSION-orig

Property changes on: create.patch.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: file.list
===================================================================
--- file.list	(nonexistent)
+++ file.list	(revision 5)
@@ -0,0 +1 @@
+coreutils-8.32/src/ls.c
Index: .
===================================================================
--- .	(nonexistent)
+++ .	(revision 5)

Property changes on: .
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~