aboutsummaryrefslogtreecommitdiff
path: root/bin/ls
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ls')
-rw-r--r--bin/ls/Makefile16
-rw-r--r--bin/ls/Makefile.depend17
-rw-r--r--bin/ls/Makefile.depend.options7
-rw-r--r--bin/ls/cmp.c204
-rw-r--r--bin/ls/extern.h77
-rw-r--r--bin/ls/ls.1969
-rw-r--r--bin/ls/ls.c1054
-rw-r--r--bin/ls/ls.h87
-rw-r--r--bin/ls/print.c838
-rw-r--r--bin/ls/tests/Makefile7
-rw-r--r--bin/ls/tests/Makefile.depend10
-rwxr-xr-xbin/ls/tests/ls_tests.sh997
-rw-r--r--bin/ls/util.c228
13 files changed, 4511 insertions, 0 deletions
diff --git a/bin/ls/Makefile b/bin/ls/Makefile
new file mode 100644
index 000000000000..574ab0d71a45
--- /dev/null
+++ b/bin/ls/Makefile
@@ -0,0 +1,16 @@
+.include <src.opts.mk>
+
+PACKAGE=runtime
+PROG= ls
+SRCS= cmp.c ls.c print.c util.c
+LIBADD= util
+
+.if ${MK_LS_COLORS} != no
+CFLAGS+= -DCOLORLS
+LIBADD+= termcapw
+.endif
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/bin/ls/Makefile.depend b/bin/ls/Makefile.depend
new file mode 100644
index 000000000000..307d2b3370ba
--- /dev/null
+++ b/bin/ls/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/ncurses/tinfo \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/ls/Makefile.depend.options b/bin/ls/Makefile.depend.options
new file mode 100644
index 000000000000..b8115550385a
--- /dev/null
+++ b/bin/ls/Makefile.depend.options
@@ -0,0 +1,7 @@
+# This file is not autogenerated - take care!
+
+DIRDEPS_OPTIONS= LS_COLORS
+
+DIRDEPS.LS_COLORS.yes= lib/ncurses/ncursesw
+
+.include <dirdeps-options.mk>
diff --git a/bin/ls/cmp.c b/bin/ls/cmp.c
new file mode 100644
index 000000000000..4c60506f3456
--- /dev/null
+++ b/bin/ls/cmp.c
@@ -0,0 +1,204 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fts.h>
+#include <string.h>
+
+#include "ls.h"
+#include "extern.h"
+
+int
+namecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (strcoll(a->fts_name, b->fts_name));
+}
+
+int
+revnamecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (strcoll(b->fts_name, a->fts_name));
+}
+
+int
+verscmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (strverscmp(a->fts_name, b->fts_name));
+}
+
+int
+revverscmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (strverscmp(b->fts_name, a->fts_name));
+}
+
+int
+modcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_mtim.tv_sec >
+ a->fts_statp->st_mtim.tv_sec)
+ return (1);
+ if (b->fts_statp->st_mtim.tv_sec <
+ a->fts_statp->st_mtim.tv_sec)
+ return (-1);
+ if (b->fts_statp->st_mtim.tv_nsec >
+ a->fts_statp->st_mtim.tv_nsec)
+ return (1);
+ if (b->fts_statp->st_mtim.tv_nsec <
+ a->fts_statp->st_mtim.tv_nsec)
+ return (-1);
+ if (f_samesort)
+ return (strcoll(b->fts_name, a->fts_name));
+ else
+ return (strcoll(a->fts_name, b->fts_name));
+}
+
+int
+revmodcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (modcmp(b, a));
+}
+
+int
+acccmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_atim.tv_sec >
+ a->fts_statp->st_atim.tv_sec)
+ return (1);
+ if (b->fts_statp->st_atim.tv_sec <
+ a->fts_statp->st_atim.tv_sec)
+ return (-1);
+ if (b->fts_statp->st_atim.tv_nsec >
+ a->fts_statp->st_atim.tv_nsec)
+ return (1);
+ if (b->fts_statp->st_atim.tv_nsec <
+ a->fts_statp->st_atim.tv_nsec)
+ return (-1);
+ if (f_samesort)
+ return (strcoll(b->fts_name, a->fts_name));
+ else
+ return (strcoll(a->fts_name, b->fts_name));
+}
+
+int
+revacccmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (acccmp(b, a));
+}
+
+int
+birthcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_birthtim.tv_sec >
+ a->fts_statp->st_birthtim.tv_sec)
+ return (1);
+ if (b->fts_statp->st_birthtim.tv_sec <
+ a->fts_statp->st_birthtim.tv_sec)
+ return (-1);
+ if (b->fts_statp->st_birthtim.tv_nsec >
+ a->fts_statp->st_birthtim.tv_nsec)
+ return (1);
+ if (b->fts_statp->st_birthtim.tv_nsec <
+ a->fts_statp->st_birthtim.tv_nsec)
+ return (-1);
+ if (f_samesort)
+ return (strcoll(b->fts_name, a->fts_name));
+ else
+ return (strcoll(a->fts_name, b->fts_name));
+}
+
+int
+revbirthcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (birthcmp(b, a));
+}
+
+int
+statcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_ctim.tv_sec >
+ a->fts_statp->st_ctim.tv_sec)
+ return (1);
+ if (b->fts_statp->st_ctim.tv_sec <
+ a->fts_statp->st_ctim.tv_sec)
+ return (-1);
+ if (b->fts_statp->st_ctim.tv_nsec >
+ a->fts_statp->st_ctim.tv_nsec)
+ return (1);
+ if (b->fts_statp->st_ctim.tv_nsec <
+ a->fts_statp->st_ctim.tv_nsec)
+ return (-1);
+ if (f_samesort)
+ return (strcoll(b->fts_name, a->fts_name));
+ else
+ return (strcoll(a->fts_name, b->fts_name));
+}
+
+int
+revstatcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (statcmp(b, a));
+}
+
+int
+sizecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_size > a->fts_statp->st_size)
+ return (1);
+ if (b->fts_statp->st_size < a->fts_statp->st_size)
+ return (-1);
+ return (strcoll(a->fts_name, b->fts_name));
+}
+
+int
+revsizecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (sizecmp(b, a));
+}
diff --git a/bin/ls/extern.h b/bin/ls/extern.h
new file mode 100644
index 000000000000..fd9a3f111a47
--- /dev/null
+++ b/bin/ls/extern.h
@@ -0,0 +1,77 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+
+int acccmp(const FTSENT *, const FTSENT *);
+int revacccmp(const FTSENT *, const FTSENT *);
+int birthcmp(const FTSENT *, const FTSENT *);
+int revbirthcmp(const FTSENT *, const FTSENT *);
+int modcmp(const FTSENT *, const FTSENT *);
+int revmodcmp(const FTSENT *, const FTSENT *);
+int namecmp(const FTSENT *, const FTSENT *);
+int revnamecmp(const FTSENT *, const FTSENT *);
+int verscmp(const FTSENT *, const FTSENT *);
+int revverscmp(const FTSENT *, const FTSENT *);
+int statcmp(const FTSENT *, const FTSENT *);
+int revstatcmp(const FTSENT *, const FTSENT *);
+int sizecmp(const FTSENT *, const FTSENT *);
+int revsizecmp(const FTSENT *, const FTSENT *);
+
+void printcol(const DISPLAY *);
+void printlong(const DISPLAY *);
+int printname(const char *);
+void printscol(const DISPLAY *);
+void printstream(const DISPLAY *);
+void usage(void);
+int prn_normal(const char *);
+size_t len_octal(const char *, int);
+int prn_octal(const char *);
+int prn_printable(const char *);
+#ifdef COLORLS
+void parsecolors(const char *cs);
+void colorquit(int);
+
+extern char *ansi_fgcol;
+extern char *ansi_bgcol;
+extern char *ansi_coloff;
+extern char *attrs_off;
+extern char *enter_bold;
+extern char *enter_underline;
+
+extern int colorflag;
+extern bool explicitansi;
+
+#define COLORFLAG_NEVER 0
+#define COLORFLAG_AUTO 1
+#define COLORFLAG_ALWAYS 2
+#endif
+extern int termwidth;
diff --git a/bin/ls/ls.1 b/bin/ls/ls.1
new file mode 100644
index 000000000000..9621959821be
--- /dev/null
+++ b/bin/ls/ls.1
@@ -0,0 +1,969 @@
+.\"-
+.\" Copyright (c) 1980, 1990, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd January 16, 2025
+.Dt LS 1
+.Os
+.Sh NAME
+.Nm ls
+.Nd list directory contents
+.Sh SYNOPSIS
+.Nm
+.Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1\&,
+.Op Fl -color Ns = Ns Ar when
+.Op Fl -group-directories Ns = Ns Ar order
+.Op Fl -group-directories-first
+.Op Fl D Ar format
+.Op Ar
+.Sh DESCRIPTION
+For each operand that names a
+.Ar file
+of a type other than
+directory,
+.Nm
+displays its name as well as any requested,
+associated information.
+For each operand that names a
+.Ar file
+of type directory,
+.Nm
+displays the names of files contained
+within that directory, as well as any requested, associated
+information.
+.Pp
+If no operands are given, the contents of the current
+directory are displayed.
+If more than one operand is given,
+non-directory operands are displayed first; directory
+and non-directory operands are sorted separately and in
+lexicographical order.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl A
+Include directory entries whose names begin with a
+dot
+.Pq Sq Pa \&.
+except for
+.Pa \&.
+and
+.Pa .. .
+Automatically set for the super-user unless
+.Fl I
+is specified.
+.It Fl B
+Force printing of non-printable characters (as defined by
+.Xr ctype 3
+and current locale settings) in file names as
+.Li \e Ns Va xxx ,
+where
+.Va xxx
+is the numeric value of the character in octal.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl C
+Force multi-column output; this is the default when output is to a terminal.
+.It Fl D Ar format
+When printing in the long
+.Pq Fl l
+format, use
+.Ar format
+to format the date and time output.
+The argument
+.Ar format
+is a string used by
+.Xr strftime 3 .
+Depending on the choice of format string, this may result in a
+different number of columns in the output.
+This option overrides the
+.Fl T
+option.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl F
+Display a slash
+.Pq Ql /
+immediately after each pathname that is a directory,
+an asterisk
+.Pq Ql *
+after each that is executable,
+an at sign
+.Pq Ql @
+after each symbolic link,
+an equals sign
+.Pq Ql =
+after each socket,
+a percent sign
+.Pq Ql %
+after each whiteout,
+and a vertical bar
+.Pq Ql \&|
+after each that is a
+.Tn FIFO .
+.It Fl G
+Enable colorized output.
+This option is equivalent to defining
+.Ev CLICOLOR
+or
+.Ev COLORTERM
+in the environment and setting
+.Fl -color Ns = Ns Ar auto .
+(See below.)
+This functionality can be compiled out by removing the definition of
+.Ev COLORLS .
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl H
+Symbolic links on the command line are followed.
+This option is assumed if
+none of the
+.Fl F , d ,
+or
+.Fl l
+options are specified.
+.It Fl I
+Prevent
+.Fl A
+from being automatically set for the super-user.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl L
+If argument is a symbolic link, list the file or directory the link references
+rather than the link itself.
+This option cancels the
+.Fl P
+option.
+.It Fl P
+If argument is a symbolic link, list the link itself rather than the
+object the link references.
+This option cancels the
+.Fl H
+and
+.Fl L
+options.
+.It Fl R
+Recursively list subdirectories encountered.
+.It Fl S
+Sort by size (largest file first) before sorting the operands in
+lexicographical order.
+.It Fl T
+When printing in the long
+.Pq Fl l
+format, display complete time information for the file, including
+month, day, hour, minute, second, and year.
+The
+.Fl D
+option gives even more control over the output format.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl U
+Use time when file was created for sorting or printing.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl W
+Display whiteouts when scanning directories.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl Z
+Display each file's MAC label; see
+.Xr maclabel 7 .
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl a
+Include directory entries whose names begin with a
+dot
+.Pq Sq Pa \&. .
+.It Fl b
+As
+.Fl B ,
+but use
+.Tn C
+escape codes whenever possible.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl c
+Use time when file status was last changed for sorting or printing.
+.It Fl -color Ns = Ns Ar when
+Output colored escape sequences based on
+.Ar when ,
+which may be set to either
+.Cm always ,
+.Cm auto ,
+or
+.Cm never .
+.Pp
+.Cm always
+will make
+.Nm
+always output color.
+If
+.Ev TERM
+is unset or set to an invalid terminal, then
+.Nm
+will fall back to explicit
+.Tn ANSI
+escape sequences without the help of
+.Xr termcap 5 .
+.Cm always
+is the default if
+.Fl -color
+is specified without an argument.
+.Pp
+.Cm auto
+will make
+.Nm
+output escape sequences based on
+.Xr termcap 5 ,
+but only if
+.Dv stdout
+is a tty and either the
+.Fl G
+flag is specified or one of the environment variables
+.Ev COLORTERM
+or
+.Ev CLICOLOR
+is set and not empty.
+.Pp
+.Cm never
+will disable color regardless of environment variables.
+.Cm never
+is the default when neither
+.Fl -color
+nor
+.Fl G
+is specified.
+.Pp
+For compatibility with GNU coreutils,
+.Nm
+supports
+.Cm yes
+or
+.Cm force
+as equivalent to
+.Cm always ,
+.Cm no
+or
+.Cm none
+as equivalent to
+.Cm never ,
+and
+.Cm tty
+or
+.Cm if-tty
+as equivalent to
+.Cm auto .
+.It Fl d
+Directories are listed as plain files (not searched recursively).
+.It Fl f
+Output is not sorted.
+This option turns on
+.Fl a .
+It also negates the effect of the
+.Fl r ,
+.Fl S
+and
+.Fl t
+options.
+As allowed by
+.St -p1003.1-2008 ,
+this option has no effect on the
+.Fl d ,
+.Fl l ,
+.Fl R
+and
+.Fl s
+options.
+.It Fl g
+Display the long
+.Pq Fl l
+format output without the file owner's name or number.
+.It Fl -group-directories Ns = Ns Ar order
+Within results for each operand,
+group directories together and print them either
+.Cm first
+or
+.Cm last.
+.It Fl -group-directories-first
+Equivalent to
+.Fl -group-directories Ns = Ns Ar first .
+Implemented for compatibility with GNU coreutils.
+.It Fl h
+When used with the
+.Fl l
+option, use unit suffixes: Byte, Kilobyte, Megabyte, Gigabyte, Terabyte
+and Petabyte in order to reduce the number of digits to four or fewer
+using base 2 for sizes.
+This option is not defined in
+.St -p1003.1-2008 .
+.It Fl i
+For each file, print the file's file serial number (inode number).
+.It Fl k
+This has the same effect as setting environment variable
+.Ev BLOCKSIZE
+to 1024, except that it also nullifies any
+.Fl h
+options to its left.
+.It Fl l
+(The lowercase letter
+.Dq ell . )
+List files in the long format, as described in the
+.Sx The Long Format
+subsection below.
+.It Fl m
+Stream output format; list files across the page, separated by commas.
+.It Fl n
+Display user and group IDs numerically rather than converting to a user
+or group name in a long
+.Pq Fl l
+output.
+.It Fl o
+Include the file flags in a long
+.Pq Fl l
+output.
+This option is incompatible with
+.St -p1003.1-2008 .
+See
+.Xr chflags 1
+for a list of file flags and their meanings.
+.It Fl p
+Write a slash
+.Pq Ql /
+after each filename if that file is a directory.
+.It Fl q
+Force printing of non-graphic characters in file names as
+the character
+.Ql \&? ;
+this is the default when output is to a terminal.
+.It Fl r
+Reverse the order of the sort.
+.It Fl s
+Display the number of blocks used in the file system by each file.
+Block sizes and directory totals are handled as described in
+.Sx The Long Format
+subsection below, except (if the long format is not also requested)
+the directory totals are not output when the output is in a
+single column, even if multi-column output is requested.
+.It Fl t
+Sort by descending time modified (most recently modified first).
+If two files have the same modification timestamp, sort their names
+in ascending lexicographical order.
+The
+.Fl r
+option reverses both of these sort orders.
+.Pp
+Note that these sort orders are contradictory: the time sequence is in
+descending order, the lexicographical sort is in ascending order.
+This behavior is mandated by
+.St -p1003.2 .
+This feature can cause problems listing files stored with sequential names on
+FAT file systems, such as from digital cameras, where it is possible to have
+more than one image with the same timestamp.
+In such a case, the photos cannot be listed in the sequence in which
+they were taken.
+To ensure the same sort order for time and for lexicographical sorting, set the
+environment variable
+.Ev LS_SAMESORT
+or use the
+.Fl y
+option.
+This causes
+.Nm
+to reverse the lexicographical sort order when sorting files with the
+same modification timestamp.
+.It Fl u
+Use time of last access,
+instead of time of last modification
+of the file for sorting
+.Pq Fl t
+or printing
+.Pq Fl l .
+.It Fl v
+Sort following a natural ordering, using
+.Xr strverscmp 3
+instead of
+.Xr strcoll 3
+as the comparison function.
+E.g., files lexicographically ordered
+"bloem1", "bloem10", and "bloem9" would instead be ordered
+"bloem1", "bloem9", and "bloem10", as one would perhaps expect.
+.It Fl w
+Force raw printing of non-printable characters.
+This is the default
+when output is not to a terminal.
+This option is not defined in
+.St -p1003.1-2001 .
+.It Fl x
+The same as
+.Fl C ,
+except that the multi-column output is produced with entries sorted
+across, rather than down, the columns.
+.It Fl y
+When the
+.Fl t
+option is set, sort the alphabetical output in the same order as the time output.
+This has the same effect as setting
+.Ev LS_SAMESORT .
+See the description of the
+.Fl t
+option for more details.
+This option is not defined in
+.St -p1003.1-2001 .
+.It Fl 1
+(The numeric digit
+.Dq one . )
+Force output to be
+one entry per line.
+This is the default when
+output is not to a terminal.
+.It Fl ,
+(Comma) When the
+.Fl l
+or
+.Fl s
+option is set, print file sizes grouped and separated by thousands using the
+non-monetary separator returned by
+.Xr localeconv 3 ,
+typically a comma or period.
+If no locale is set, or the locale does not have a non-monetary separator, this
+option has no effect.
+This option is not defined in
+.St -p1003.1-2001 .
+.El
+.Pp
+The
+.Fl 1 , C , x ,
+and
+.Fl l
+options all override each other; the last one specified determines
+the format used.
+.Pp
+The
+.Fl c , u ,
+and
+.Fl U
+options all override each other; the last one specified determines
+the file time used.
+.Pp
+The
+.Fl S , t
+and
+.Fl v
+options override each other; the last one specified determines
+the sort order used.
+.Pp
+The
+.Fl B , b , w ,
+and
+.Fl q
+options all override each other; the last one specified determines
+the format used for non-printable characters.
+.Pp
+The
+.Fl H , L
+and
+.Fl P
+options all override each other (either partially or fully); they
+are applied in the order specified.
+.Pp
+By default,
+.Nm
+lists one entry per line to standard
+output; the exceptions are to terminals or when the
+.Fl C
+or
+.Fl x
+options are specified.
+.Pp
+File information is displayed with one or more
+.Ao blank Ac Ns s
+separating the information associated with the
+.Fl i , s ,
+and
+.Fl l
+options.
+.Ss The Long Format
+If the
+.Fl l
+option is given, the following information
+is displayed for each file:
+file mode,
+number of links, owner name, group name,
+MAC label,
+number of bytes in the file, abbreviated
+month, day-of-month file was last modified,
+hour file last modified, minute file last
+modified, and the pathname.
+.Pp
+If the modification time of the file is more than 6 months
+in the past or future, and the
+.Fl D
+or
+.Fl T
+are not specified,
+then the year of the last modification
+is displayed in place of the hour and minute fields.
+.Pp
+If the owner or group names are not a known user or group name,
+or the
+.Fl n
+option is given,
+the numeric ID's are displayed.
+.Pp
+If the file is a character special or block special file,
+the device number for the file is displayed in the size field.
+If the file is a symbolic link the pathname of the
+linked-to file is preceded by
+.Dq Li -> .
+.Pp
+The listing of a directory's contents is preceded
+by a labeled total number of blocks used in the file system by the files
+which are listed as the directory's contents
+(which may or may not include
+.Pa \&.
+and
+.Pa ..
+and other files which start with a dot, depending on other options).
+If the
+.Fl h
+option is given,
+the total size is displayed as the number of bytes.
+.Pp
+The default block size is 512 bytes.
+The block size may be set with option
+.Fl k
+or environment variable
+.Ev BLOCKSIZE .
+Numbers of blocks in the output will have been rounded up so the
+numbers of bytes is at least as many as used by the corresponding
+file system blocks (which might have a different size).
+.Pp
+The file mode printed under the
+.Fl l
+option consists of the
+entry type and the permissions.
+The entry type character describes the type of file, as
+follows:
+.Pp
+.Bl -tag -width 4n -offset indent -compact
+.It Sy \-
+Regular file.
+.It Sy b
+Block special file.
+.It Sy c
+Character special file.
+.It Sy d
+Directory.
+.It Sy l
+Symbolic link.
+.It Sy p
+.Tn FIFO .
+.It Sy s
+Socket.
+.It Sy w
+Whiteout.
+.El
+.Pp
+The next three fields
+are three characters each:
+owner permissions,
+group permissions, and
+other permissions.
+Each field has three character positions:
+.Bl -enum -offset indent
+.It
+If
+.Sy r ,
+the file is readable; if
+.Sy \- ,
+it is not readable.
+.It
+If
+.Sy w ,
+the file is writable; if
+.Sy \- ,
+it is not writable.
+.It
+The first of the following that applies:
+.Bl -tag -width 4n -offset indent
+.It Sy S
+If in the owner permissions, the file is not executable and
+set-user-ID mode is set.
+If in the group permissions, the file is not executable
+and set-group-ID mode is set.
+.It Sy s
+If in the owner permissions, the file is executable
+and set-user-ID mode is set.
+If in the group permissions, the file is executable
+and setgroup-ID mode is set.
+.It Sy x
+The file is executable or the directory is
+searchable.
+.It Sy \-
+The file is neither readable, writable, executable,
+nor set-user-ID nor set-group-ID mode, nor sticky.
+(See below.)
+.El
+.Pp
+These next two apply only to the third character in the last group
+(other permissions).
+.Bl -tag -width 4n -offset indent
+.It Sy T
+The sticky bit is set
+(mode
+.Li 1000 ) ,
+but not execute or search permission.
+(See
+.Xr chmod 1
+or
+.Xr sticky 7 . )
+.It Sy t
+The sticky bit is set (mode
+.Li 1000 ) ,
+and is searchable or executable.
+(See
+.Xr chmod 1
+or
+.Xr sticky 7 . )
+.El
+.El
+.Pp
+The next field contains a
+plus
+.Pq Ql +
+character if the file has an ACL, or a
+space
+.Pq Ql " "
+if it does not.
+The
+.Nm
+utility does not show the actual ACL;
+use
+.Xr getfacl 1
+to do this.
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev CLICOLOR_FORCE"
+.It Ev BLOCKSIZE
+If this is set, its value, rounded up to 512 or down to a
+multiple of 512, will be used as the block size in bytes by the
+.Fl l
+and
+.Fl s
+options.
+See
+.Sx The Long Format
+subsection for more information.
+.It Ev CLICOLOR
+Use
+.Tn ANSI
+color sequences to distinguish file types.
+See
+.Ev LSCOLORS
+below.
+In addition to the file types mentioned in the
+.Fl F
+option some extra attributes (setuid bit set, etc.) are also displayed.
+The colorization is dependent on a terminal type with the proper
+.Xr termcap 5
+capabilities.
+The default
+.Dq Li cons25
+console has the proper capabilities,
+but to display the colors in an
+.Xr xterm 1 Pq Pa ports/x11/xterm ,
+for example,
+the
+.Ev TERM
+variable must be set to
+.Dq Li xterm-color .
+Other terminal types may require similar adjustments.
+Colorization
+is silently disabled if the output is not directed to a terminal
+unless the
+.Ev CLICOLOR_FORCE
+variable is defined or
+.Fl -color
+is set to
+.Dq always .
+.It Ev CLICOLOR_FORCE
+Color sequences are normally disabled if the output is not directed to
+a terminal.
+This can be overridden by setting this variable.
+The
+.Ev TERM
+variable still needs to reference a color capable terminal however
+otherwise it is not possible to determine which color sequences to
+use.
+.It Ev COLORTERM
+See description for
+.Ev CLICOLOR
+above.
+.It Ev COLUMNS
+If this variable contains a string representing a
+decimal integer, it is used as the
+column position width for displaying
+multiple-text-column output.
+The
+.Nm
+utility calculates how
+many pathname text columns to display
+based on the width provided.
+(See
+.Fl C
+and
+.Fl x . )
+.It Ev LANG
+The locale to use when determining the order of day and month in the long
+.Fl l
+format output.
+See
+.Xr environ 7
+for more information.
+.It Ev LSCOLORS
+The value of this variable describes what color to use for which
+attribute when colors are enabled with
+.Ev CLICOLOR
+or
+.Ev COLORTERM .
+This string is a concatenation of pairs of the format
+.Ar f Ns Ar b ,
+where
+.Ar f
+is the foreground color and
+.Ar b
+is the background color.
+When the background color is capitalized, the text is underlined.
+.Pp
+The color designators are as follows:
+.Pp
+.Bl -tag -width 4n -offset indent -compact
+.It Sy a
+black
+.It Sy b
+red
+.It Sy c
+green
+.It Sy d
+brown
+.It Sy e
+blue
+.It Sy f
+magenta
+.It Sy g
+cyan
+.It Sy h
+light grey
+.It Sy A
+bold or underlined black, usually shows up as dark grey
+.It Sy B
+bold or underlined red
+.It Sy C
+bold or underlined green
+.It Sy D
+bold or underlined brown, usually shows up as yellow
+.It Sy E
+bold or underlined blue
+.It Sy F
+bold or underlined magenta
+.It Sy G
+bold or underlined cyan
+.It Sy H
+bold or underlined light grey; looks like bright white
+.It Sy x
+default foreground or background
+.It Sy X
+default foreground or background, with an underline or bold
+.El
+.Pp
+Note that the above are standard
+.Tn ANSI
+colors.
+The actual display may differ
+depending on the color capabilities of the terminal in use.
+.Pp
+The order of the attributes are as follows:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+directory
+.It
+symbolic link
+.It
+socket
+.It
+pipe
+.It
+executable
+.It
+block special
+.It
+character special
+.It
+executable with setuid bit set
+.It
+executable with setgid bit set
+.It
+directory writable to others, with sticky bit
+.It
+directory writable to others, without sticky bit
+.El
+.Pp
+The default is
+.Qq "exfxcxdxbxegedabagacad" ,
+i.e., blue foreground and
+default background for regular directories, black foreground and red
+background for setuid executables, etc.
+.It Ev LS_COLWIDTHS
+If this variable is set, it is considered to be a
+colon-delimited list of minimum column widths.
+Unreasonable
+and insufficient widths are ignored (thus zero signifies
+a dynamically sized column).
+Not all columns have changeable widths.
+The fields are,
+in order: inode, block count, number of links, user name,
+group name, flags, file size, file name.
+.It Ev LS_SAMESORT
+If this variable is set, the
+.Fl t
+option sorts the names of files with the same modification timestamp in the same
+sense as the time sort.
+See the description of the
+.Fl t
+option for more details.
+.It Ev TERM
+The
+.Ev CLICOLOR
+and
+.Ev COLORTERM
+functionality depends on a terminal type with color capabilities.
+.It Ev TZ
+The timezone to use when displaying dates.
+See
+.Xr environ 7
+for more information.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+List the contents of the current working directory in long format:
+.Pp
+.Dl $ ls -l
+.Pp
+In addition to listing the contents of the current working directory in
+long format, show inode numbers, file flags (see
+.Xr chflags 1 ) ,
+and suffix each filename with a symbol representing its file type:
+.Pp
+.Dl $ ls -lioF
+.Pp
+List the files in
+.Pa /var/log ,
+sorting the output such that the mostly recently modified entries are
+printed first:
+.Pp
+.Dl $ ls -lt /var/log
+.Sh COMPATIBILITY
+The group field is now automatically included in the long listing for
+files in order to be compatible with the
+.St -p1003.2
+specification.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chmod 1 ,
+.Xr getfacl 1 ,
+.Xr sort 1 ,
+.Xr xterm 1 Pq Pa ports/x11/xterm ,
+.Xr localeconv 3 ,
+.Xr strcoll 3 ,
+.Xr strftime 3 ,
+.Xr strmode 3 ,
+.Xr strverscmp 3 ,
+.Xr termcap 5 ,
+.Xr maclabel 7 ,
+.Xr sticky 7 ,
+.Xr symlink 7 ,
+.Xr getfmac 8
+.Sh STANDARDS
+With the exception of options
+.Fl g , n
+and
+.Fl o ,
+the
+.Nm
+utility conforms to
+.St -p1003.1-2001
+and
+.St -p1003.1-2008 .
+The options
+.Fl B , D , G , I , T , U , W , Z , b , h , v , w , y
+,
+.Fl ,
+.Fl -color
+and
+.Fl -group-directories Ns =
+(including
+.Fl -group-directories-first )
+are non-standard extensions.
+.Pp
+The ACL support is compatible with
+.Tn IEEE
+Std\~1003.2c
+.Pq Dq Tn POSIX Ns .2c
+Draft\~17
+(withdrawn).
+.Sh HISTORY
+An
+.Nm
+command appeared in
+.At v1 .
+.Pp
+The
+.Fl v
+option was added in
+.Fx 13.2 .
+.Sh BUGS
+To maintain backward compatibility, the relationships between the many
+options are quite complex.
+.Pp
+The exception mentioned in the
+.Fl s
+option description might be a feature that was
+based on the fact that single-column output
+usually goes to something other than a terminal.
+It is debatable whether this is a design bug.
+.Pp
+.St -p1003.2
+mandates opposite sort orders for files with the same timestamp when
+sorting with the
+.Fl t
+option.
diff --git a/bin/ls/ls.c b/bin/ls/ls.c
new file mode 100644
index 000000000000..b3d0a508d714
--- /dev/null
+++ b/bin/ls/ls.c
@@ -0,0 +1,1054 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mac.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <getopt.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef COLORLS
+#include <termcap.h>
+#include <signal.h>
+#endif
+
+#include "ls.h"
+#include "extern.h"
+
+/*
+ * Upward approximation of the maximum number of characters needed to
+ * represent a value of integral type t as a string, excluding the
+ * NUL terminator, with provision for a sign.
+ */
+#define STRBUF_SIZEOF(t) (1 + CHAR_BIT * sizeof(t) / 3 + 1)
+
+/*
+ * MAKENINES(n) turns n into (10**n)-1. This is useful for converting a width
+ * into a number that wide in decimal.
+ * XXX: Overflows are not considered.
+ */
+#define MAKENINES(n) \
+ do { \
+ intmax_t __i; \
+ \
+ /* Use a loop as all values of n are small. */ \
+ for (__i = 1; n > 0; __i *= 10) \
+ n--; \
+ n = __i - 1; \
+ } while(0)
+
+static void display(const FTSENT *, FTSENT *, int);
+static int mastercmp(const FTSENT * const *, const FTSENT * const *);
+static void traverse(int, char **, int);
+
+enum {
+ GRP_NONE = 0,
+ GRP_DIR_FIRST = -1,
+ GRP_DIR_LAST = 1
+};
+
+enum {
+ BIN_OPT = CHAR_MAX,
+ COLOR_OPT,
+ GROUP_OPT
+};
+
+static const struct option long_opts[] =
+{
+ {"color", optional_argument, NULL, COLOR_OPT},
+ {"group-directories", optional_argument, NULL, GROUP_OPT},
+ {"group-directories-first", no_argument, NULL, GROUP_OPT},
+ {NULL, no_argument, NULL, 0}
+};
+
+static void (*printfcn)(const DISPLAY *);
+static int (*sortfcn)(const FTSENT *, const FTSENT *);
+
+long blocksize; /* block size units */
+int termwidth = 80; /* default terminal width */
+
+/* flags */
+ int f_accesstime; /* use time of last access */
+ int f_birthtime; /* use time of birth */
+ int f_flags; /* show flags associated with a file */
+static int f_groupdir = GRP_NONE;/* group directories first/last */
+ int f_humanval; /* show human-readable file sizes */
+ int f_inode; /* print inode */
+static int f_kblocks; /* print size in kilobytes */
+ int f_label; /* show MAC label */
+static int f_listdir; /* list actual directory, not contents */
+static int f_listdot; /* list files beginning with . */
+ int f_longform; /* long listing format */
+static int f_noautodot; /* do not automatically enable -A for root */
+static int f_nofollow; /* don't follow symbolic link arguments */
+ int f_nonprint; /* show unprintables as ? */
+static int f_nosort; /* don't sort output */
+ int f_notabs; /* don't use tab-separated multi-col output */
+static int f_numericonly; /* don't convert uid/gid to name */
+ int f_octal; /* show unprintables as \xxx */
+ int f_octal_escape; /* like f_octal but use C escapes if possible */
+static int f_recursive; /* ls subdirectories also */
+static int f_reversesort; /* reverse whatever sort is used */
+static int f_verssort; /* sort names using strverscmp(3) rather than strcoll(3) */
+ int f_samesort; /* sort time and name in same direction */
+ int f_sectime; /* print full time information */
+static int f_singlecol; /* use single column output */
+ int f_size; /* list size in short listing */
+static int f_sizesort;
+ int f_slash; /* similar to f_type, but only for dirs */
+ int f_sortacross; /* sort across rows, not down columns */
+ int f_sowner; /* disable showing owner's name */
+ int f_statustime; /* use time of last mode change */
+static int f_stream; /* stream the output, separate with commas */
+ int f_thousands; /* show file sizes with thousands separators */
+ char *f_timeformat; /* user-specified time format */
+static int f_timesort; /* sort by time vice name */
+ int f_type; /* add type character for non-regular files */
+static int f_whiteout; /* show whiteout entries */
+#ifdef COLORLS
+ int colorflag = COLORFLAG_NEVER; /* passed in colorflag */
+ int f_color; /* add type in color for non-regular files */
+ bool explicitansi; /* Explicit ANSI sequences, no termcap(5) */
+char *ansi_bgcol; /* ANSI sequence to set background colour */
+char *ansi_fgcol; /* ANSI sequence to set foreground colour */
+char *ansi_coloff; /* ANSI sequence to reset colours */
+char *attrs_off; /* ANSI sequence to turn off attributes */
+char *enter_bold; /* ANSI sequence to set color to bold mode */
+char *enter_underline; /* ANSI sequence to enter underline mode */
+#endif
+
+static int rval;
+
+static bool
+do_color_from_env(void)
+{
+ const char *p;
+ bool doit;
+
+ doit = false;
+ p = getenv("CLICOLOR");
+ if (p == NULL) {
+ /*
+ * COLORTERM is the more standard name for this variable. We'll
+ * honor it as long as it's both set and not empty.
+ */
+ p = getenv("COLORTERM");
+ if (p != NULL && *p != '\0')
+ doit = true;
+ } else
+ doit = true;
+
+ return (doit &&
+ (isatty(STDOUT_FILENO) || getenv("CLICOLOR_FORCE")));
+}
+
+static bool
+do_color(void)
+{
+
+#ifdef COLORLS
+ if (colorflag == COLORFLAG_NEVER)
+ return (false);
+ else if (colorflag == COLORFLAG_ALWAYS)
+ return (true);
+#endif
+ return (do_color_from_env());
+}
+
+#ifdef COLORLS
+static bool
+do_color_always(const char *term)
+{
+
+ return (strcmp(term, "always") == 0 || strcmp(term, "yes") == 0 ||
+ strcmp(term, "force") == 0);
+}
+
+static bool
+do_color_never(const char *term)
+{
+
+ return (strcmp(term, "never") == 0 || strcmp(term, "no") == 0 ||
+ strcmp(term, "none") == 0);
+}
+
+static bool
+do_color_auto(const char *term)
+{
+
+ return (strcmp(term, "auto") == 0 || strcmp(term, "tty") == 0 ||
+ strcmp(term, "if-tty") == 0);
+}
+#endif /* COLORLS */
+
+int
+main(int argc, char *argv[])
+{
+ static char dot[] = ".", *dotav[] = {dot, NULL};
+ struct winsize win;
+ int ch, fts_options, notused;
+ char *p;
+ const char *errstr = NULL;
+#ifdef COLORLS
+ char termcapbuf[1024]; /* termcap definition buffer */
+ char tcapbuf[512]; /* capability buffer */
+ char *bp = tcapbuf, *term;
+#endif
+
+ (void)setlocale(LC_ALL, "");
+
+ /* Terminal defaults to -Cq, non-terminal defaults to -1. */
+ if (isatty(STDOUT_FILENO)) {
+ termwidth = 80;
+ if ((p = getenv("COLUMNS")) != NULL && *p != '\0')
+ termwidth = strtonum(p, 0, INT_MAX, &errstr);
+ else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 &&
+ win.ws_col > 0)
+ termwidth = win.ws_col;
+ f_nonprint = 1;
+ } else {
+ f_singlecol = 1;
+ /* retrieve environment variable, in case of explicit -C */
+ p = getenv("COLUMNS");
+ if (p)
+ termwidth = strtonum(p, 0, INT_MAX, &errstr);
+ }
+
+ if (errstr)
+ termwidth = 80;
+
+ fts_options = FTS_PHYSICAL;
+ if (getenv("LS_SAMESORT"))
+ f_samesort = 1;
+
+ /*
+ * For historical compatibility, we'll use our autodetection if CLICOLOR
+ * is set.
+ */
+#ifdef COLORLS
+ if (getenv("CLICOLOR"))
+ colorflag = COLORFLAG_AUTO;
+#endif
+ while ((ch = getopt_long(argc, argv,
+ "+1ABCD:FGHILPRSTUWXZabcdfghiklmnopqrstuvwxy,", long_opts,
+ NULL)) != -1) {
+ switch (ch) {
+ /*
+ * The -1, -C, -x and -l options all override each other so
+ * shell aliasing works right.
+ */
+ case '1':
+ f_singlecol = 1;
+ f_longform = 0;
+ f_stream = 0;
+ break;
+ case 'C':
+ f_sortacross = f_longform = f_singlecol = 0;
+ break;
+ case 'l':
+ f_longform = 1;
+ f_singlecol = 0;
+ f_stream = 0;
+ break;
+ case 'x':
+ f_sortacross = 1;
+ f_longform = 0;
+ f_singlecol = 0;
+ break;
+ /* The -c, -u, and -U options override each other. */
+ case 'c':
+ f_statustime = 1;
+ f_accesstime = 0;
+ f_birthtime = 0;
+ break;
+ case 'u':
+ f_accesstime = 1;
+ f_statustime = 0;
+ f_birthtime = 0;
+ break;
+ case 'U':
+ f_birthtime = 1;
+ f_accesstime = 0;
+ f_statustime = 0;
+ break;
+ case 'f':
+ f_nosort = 1;
+ /* FALLTHROUGH */
+ case 'a':
+ fts_options |= FTS_SEEDOT;
+ /* FALLTHROUGH */
+ case 'A':
+ f_listdot = 1;
+ break;
+ /* The -S, -t and -v options override each other. */
+ case 'S':
+ f_sizesort = 1;
+ f_timesort = 0;
+ f_verssort = 0;
+ break;
+ case 't':
+ f_timesort = 1;
+ f_sizesort = 0;
+ f_verssort = 0;
+ break;
+ case 'v':
+ f_verssort = 1;
+ f_sizesort = 0;
+ f_timesort = 0;
+ break;
+ /* Other flags. Please keep alphabetic. */
+ case ',':
+ f_thousands = 1;
+ break;
+ case 'B':
+ f_nonprint = 0;
+ f_octal = 1;
+ f_octal_escape = 0;
+ break;
+ case 'D':
+ f_timeformat = optarg;
+ break;
+ case 'F':
+ f_type = 1;
+ f_slash = 0;
+ break;
+ case 'G':
+ /*
+ * We both set CLICOLOR here and set colorflag to
+ * COLORFLAG_AUTO, because -G should not force color if
+ * stdout isn't a tty.
+ */
+ setenv("CLICOLOR", "", 1);
+#ifdef COLORLS
+ colorflag = COLORFLAG_AUTO;
+#endif
+ break;
+ case 'H':
+ fts_options |= FTS_COMFOLLOW;
+ f_nofollow = 0;
+ break;
+ case 'I':
+ f_noautodot = 1;
+ break;
+ case 'L':
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ f_nofollow = 0;
+ break;
+ case 'P':
+ fts_options &= ~FTS_COMFOLLOW;
+ fts_options &= ~FTS_LOGICAL;
+ fts_options |= FTS_PHYSICAL;
+ f_nofollow = 1;
+ break;
+ case 'R':
+ f_recursive = 1;
+ break;
+ case 'T':
+ f_sectime = 1;
+ break;
+ case 'W':
+ f_whiteout = 1;
+ break;
+ case 'Z':
+ f_label = 1;
+ break;
+ case 'b':
+ f_nonprint = 0;
+ f_octal = 0;
+ f_octal_escape = 1;
+ break;
+ /* The -d option turns off the -R option. */
+ case 'd':
+ f_listdir = 1;
+ f_recursive = 0;
+ break;
+ case 'g':
+ f_longform = 1;
+ f_singlecol = 0;
+ f_stream = 0;
+ f_sowner = 1;
+ break;
+ case 'h':
+ f_humanval = 1;
+ break;
+ case 'i':
+ f_inode = 1;
+ break;
+ case 'k':
+ f_humanval = 0;
+ f_kblocks = 1;
+ break;
+ case 'm':
+ f_stream = 1;
+ f_singlecol = 0;
+ f_longform = 0;
+ break;
+ case 'n':
+ f_numericonly = 1;
+ f_longform = 1;
+ f_singlecol = 0;
+ f_stream = 0;
+ break;
+ case 'o':
+ f_flags = 1;
+ break;
+ case 'p':
+ f_slash = 1;
+ f_type = 1;
+ break;
+ case 'q':
+ f_nonprint = 1;
+ f_octal = 0;
+ f_octal_escape = 0;
+ break;
+ case 'r':
+ f_reversesort = 1;
+ break;
+ case 's':
+ f_size = 1;
+ break;
+ case 'w':
+ f_nonprint = 0;
+ f_octal = 0;
+ f_octal_escape = 0;
+ break;
+ case 'y':
+ f_samesort = 1;
+ break;
+ case GROUP_OPT:
+ if (optarg == NULL || strcmp(optarg, "first") == 0)
+ f_groupdir = GRP_DIR_FIRST;
+ else if (strcmp(optarg, "last") == 0)
+ f_groupdir = GRP_DIR_LAST;
+ else
+ errx(2, "unsupported --group-directories value '%s' (must be first or last)",
+ optarg);
+ break;
+ case COLOR_OPT:
+#ifdef COLORLS
+ if (optarg == NULL || do_color_always(optarg))
+ colorflag = COLORFLAG_ALWAYS;
+ else if (do_color_auto(optarg))
+ colorflag = COLORFLAG_AUTO;
+ else if (do_color_never(optarg))
+ colorflag = COLORFLAG_NEVER;
+ else
+ errx(2, "unsupported --color value '%s' (must be always, auto, or never)",
+ optarg);
+ break;
+#else
+ warnx("color support not compiled in");
+#endif
+ default:
+ case '?':
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Root is -A automatically unless -I. */
+ if (!f_listdot && getuid() == (uid_t)0 && !f_noautodot)
+ f_listdot = 1;
+
+ /*
+ * Enabling of colours is conditional on the environment in conjunction
+ * with the --color and -G arguments, if supplied.
+ */
+ if (do_color()) {
+#ifdef COLORLS
+ if ((term = getenv("TERM")) != NULL &&
+ tgetent(termcapbuf, term) == 1) {
+ ansi_fgcol = tgetstr("AF", &bp);
+ ansi_bgcol = tgetstr("AB", &bp);
+ attrs_off = tgetstr("me", &bp);
+ enter_bold = tgetstr("md", &bp);
+ enter_underline = tgetstr("us", &bp);
+
+ /* To switch colours off use 'op' if
+ * available, otherwise use 'oc', or
+ * don't do colours at all. */
+ ansi_coloff = tgetstr("op", &bp);
+ if (!ansi_coloff)
+ ansi_coloff = tgetstr("oc", &bp);
+ if (ansi_fgcol && ansi_bgcol && ansi_coloff)
+ f_color = 1;
+ } else if (colorflag == COLORFLAG_ALWAYS) {
+ /*
+ * If we're *always* doing color but we don't have
+ * a functional TERM supplied, we'll fallback to
+ * outputting raw ANSI sequences.
+ */
+ f_color = 1;
+ explicitansi = true;
+ }
+#endif /*COLORLS*/
+ }
+
+#ifdef COLORLS
+ if (f_color) {
+ /*
+ * We can't put tabs and color sequences together:
+ * column number will be incremented incorrectly
+ * for "stty oxtabs" mode.
+ */
+ f_notabs = 1;
+ (void)signal(SIGINT, colorquit);
+ (void)signal(SIGQUIT, colorquit);
+ parsecolors(getenv("LSCOLORS"));
+ }
+#endif
+
+ /*
+ * If not -F, -i, -l, -s, -S, -t or --group-directories options,
+ * don't require stat information, unless in color mode in which case
+ * we do need this to determine which colors to display.
+ */
+ if (!f_inode && !f_longform && !f_size && !f_timesort &&
+ !f_sizesort && !f_type && f_groupdir == GRP_NONE
+#ifdef COLORLS
+ && !f_color
+#endif
+ )
+ fts_options |= FTS_NOSTAT;
+
+ /*
+ * If not -F, -P, -d or -l options, follow any symbolic links listed on
+ * the command line, unless in color mode in which case we need to
+ * distinguish file type for a symbolic link itself and its target.
+ */
+ if (!f_nofollow && !f_longform && !f_listdir && (!f_type || f_slash)
+#ifdef COLORLS
+ && !f_color
+#endif
+ )
+ fts_options |= FTS_COMFOLLOW;
+
+ /*
+ * If -W, show whiteout entries
+ */
+#ifdef FTS_WHITEOUT
+ if (f_whiteout)
+ fts_options |= FTS_WHITEOUT;
+#endif
+
+ /* If -i, -l or -s, figure out block size. */
+ if (f_inode || f_longform || f_size) {
+ if (f_kblocks)
+ blocksize = 2;
+ else {
+ (void)getbsize(&notused, &blocksize);
+ blocksize /= 512;
+ }
+ }
+
+ /* Select a sort function. */
+ if (f_reversesort) {
+ if (f_sizesort)
+ sortfcn = revsizecmp;
+ else if (f_verssort)
+ sortfcn = revverscmp;
+ else if (!f_timesort)
+ sortfcn = revnamecmp;
+ else if (f_accesstime)
+ sortfcn = revacccmp;
+ else if (f_birthtime)
+ sortfcn = revbirthcmp;
+ else if (f_statustime)
+ sortfcn = revstatcmp;
+ else /* Use modification time. */
+ sortfcn = revmodcmp;
+ } else {
+ if (f_sizesort)
+ sortfcn = sizecmp;
+ else if (f_verssort)
+ sortfcn = verscmp;
+ else if (!f_timesort)
+ sortfcn = namecmp;
+ else if (f_accesstime)
+ sortfcn = acccmp;
+ else if (f_birthtime)
+ sortfcn = birthcmp;
+ else if (f_statustime)
+ sortfcn = statcmp;
+ else /* Use modification time. */
+ sortfcn = modcmp;
+ }
+
+ /* Select a print function. */
+ if (f_singlecol)
+ printfcn = printscol;
+ else if (f_longform)
+ printfcn = printlong;
+ else if (f_stream)
+ printfcn = printstream;
+ else
+ printfcn = printcol;
+
+ if (argc)
+ traverse(argc, argv, fts_options);
+ else
+ traverse(1, dotav, fts_options);
+ exit(rval);
+}
+
+static int output; /* If anything output. */
+
+/*
+ * Traverse() walks the logical directory structure specified by the argv list
+ * in the order specified by the mastercmp() comparison function. During the
+ * traversal it passes linked lists of structures to display() which represent
+ * a superset (may be exact set) of the files to be displayed.
+ */
+static void
+traverse(int argc, char *argv[], int options)
+{
+ FTS *ftsp;
+ FTSENT *p, *chp;
+ int ch_options;
+
+ if ((ftsp =
+ fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
+ err(1, "fts_open");
+
+ /*
+ * We ignore errors from fts_children here since they will be
+ * replicated and signalled on the next call to fts_read() below.
+ */
+ chp = fts_children(ftsp, 0);
+ if (chp != NULL)
+ display(NULL, chp, options);
+ if (f_listdir) {
+ fts_close(ftsp);
+ return;
+ }
+
+ /*
+ * If not recursing down this tree and don't need stat info, just get
+ * the names.
+ */
+ ch_options = !f_recursive && !f_label &&
+ options & FTS_NOSTAT ? FTS_NAMEONLY : 0;
+
+ while (errno = 0, (p = fts_read(ftsp)) != NULL)
+ switch (p->fts_info) {
+ case FTS_DC:
+ warnx("%s: directory causes a cycle", p->fts_name);
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ case FTS_D:
+ if (p->fts_level != FTS_ROOTLEVEL &&
+ p->fts_name[0] == '.' && !f_listdot)
+ break;
+
+ /*
+ * If already output something, put out a newline as
+ * a separator. If multiple arguments, precede each
+ * directory with its name.
+ */
+ if (output) {
+ putchar('\n');
+ (void)printname(p->fts_path);
+ puts(":");
+ } else if (argc > 1) {
+ (void)printname(p->fts_path);
+ puts(":");
+ output = 1;
+ }
+ chp = fts_children(ftsp, ch_options);
+ display(p, chp, options);
+
+ if (!f_recursive && chp != NULL)
+ (void)fts_set(ftsp, p, FTS_SKIP);
+ break;
+ default:
+ break;
+ }
+ if (errno)
+ err(1, "fts_read");
+ fts_close(ftsp);
+}
+
+/*
+ * Display() takes a linked list of FTSENT structures and passes the list
+ * along with any other necessary information to the print function. P
+ * points to the parent directory of the display list.
+ */
+static void
+display(const FTSENT *p, FTSENT *list, int options)
+{
+ struct stat *sp;
+ DISPLAY d;
+ FTSENT *cur;
+ NAMES *np;
+ off_t maxsize;
+ long maxblock;
+ uintmax_t maxinode;
+ u_long btotal, labelstrlen, maxlen, maxnlink;
+ u_long maxlabelstr;
+ u_int sizelen;
+ int maxflags;
+ gid_t maxgroup;
+ uid_t maxuser;
+ size_t flen, ulen, glen;
+ char *initmax;
+ int entries, needstats;
+ const char *user, *group;
+ char *flags, *labelstr = NULL;
+ char ngroup[STRBUF_SIZEOF(uid_t) + 1];
+ char nuser[STRBUF_SIZEOF(gid_t) + 1];
+ u_long width[9];
+ int i;
+
+ needstats = f_inode || f_longform || f_size;
+ flen = 0;
+ btotal = 0;
+
+#define LS_COLWIDTHS_FIELDS 9
+ initmax = getenv("LS_COLWIDTHS");
+
+ for (i = 0 ; i < LS_COLWIDTHS_FIELDS; i++)
+ width[i] = 0;
+
+ if (initmax != NULL) {
+ char *endp;
+
+ for (i = 0; i < LS_COLWIDTHS_FIELDS && *initmax != '\0'; i++) {
+ if (*initmax == ':') {
+ width[i] = 0;
+ } else {
+ width[i] = strtoul(initmax, &endp, 10);
+ initmax = endp;
+ while (isspace(*initmax))
+ initmax++;
+ if (*initmax != ':')
+ break;
+ initmax++;
+ }
+ }
+ if (i < LS_COLWIDTHS_FIELDS)
+#ifdef COLORLS
+ if (!f_color)
+#endif
+ f_notabs = 0;
+ }
+
+ /* Fields match -lios order. New ones should be added at the end. */
+ maxinode = width[0];
+ maxblock = width[1];
+ maxnlink = width[2];
+ maxuser = width[3];
+ maxgroup = width[4];
+ maxflags = width[5];
+ maxsize = width[6];
+ maxlen = width[7];
+ maxlabelstr = width[8];
+
+ MAKENINES(maxinode);
+ MAKENINES(maxblock);
+ MAKENINES(maxnlink);
+ MAKENINES(maxsize);
+
+ d.s_size = 0;
+ sizelen = 0;
+ flags = NULL;
+ for (cur = list, entries = 0; cur; cur = cur->fts_link) {
+ if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) {
+ warnx("%s: %s",
+ cur->fts_name, strerror(cur->fts_errno));
+ cur->fts_number = NO_PRINT;
+ rval = 1;
+ continue;
+ }
+ /*
+ * P is NULL if list is the argv list, to which different rules
+ * apply.
+ */
+ if (p == NULL) {
+ /* Directories will be displayed later. */
+ if (cur->fts_info == FTS_D && !f_listdir) {
+ cur->fts_number = NO_PRINT;
+ continue;
+ }
+ } else {
+ /* Only display dot file if -a/-A set. */
+ if (cur->fts_name[0] == '.' && !f_listdot) {
+ cur->fts_number = NO_PRINT;
+ continue;
+ }
+ }
+ if (cur->fts_namelen > maxlen)
+ maxlen = cur->fts_namelen;
+ if (f_octal || f_octal_escape) {
+ u_long t = len_octal(cur->fts_name, cur->fts_namelen);
+
+ if (t > maxlen)
+ maxlen = t;
+ }
+ if (needstats) {
+ sp = cur->fts_statp;
+ if (sp->st_blocks > maxblock)
+ maxblock = sp->st_blocks;
+ if (sp->st_ino > maxinode)
+ maxinode = sp->st_ino;
+ if (sp->st_nlink > maxnlink)
+ maxnlink = sp->st_nlink;
+ if (sp->st_size > maxsize)
+ maxsize = sp->st_size;
+
+ btotal += sp->st_blocks;
+ if (f_longform) {
+ if (f_numericonly) {
+ (void)snprintf(nuser, sizeof(nuser),
+ "%u", sp->st_uid);
+ (void)snprintf(ngroup, sizeof(ngroup),
+ "%u", sp->st_gid);
+ user = nuser;
+ group = ngroup;
+ } else {
+ user = user_from_uid(sp->st_uid, 0);
+ /*
+ * user_from_uid(..., 0) only returns
+ * NULL in OOM conditions. We could
+ * format the uid here, but (1) in
+ * general ls(1) exits on OOM, and (2)
+ * there is another allocation/exit
+ * path directly below, which will
+ * likely exit anyway.
+ */
+ if (user == NULL)
+ err(1, "user_from_uid");
+ group = group_from_gid(sp->st_gid, 0);
+ /* Ditto. */
+ if (group == NULL)
+ err(1, "group_from_gid");
+ }
+ if ((ulen = strlen(user)) > maxuser)
+ maxuser = ulen;
+ if ((glen = strlen(group)) > maxgroup)
+ maxgroup = glen;
+ if (f_flags) {
+ flags = fflagstostr(sp->st_flags);
+ if (flags != NULL && *flags == '\0') {
+ free(flags);
+ flags = strdup("-");
+ }
+ if (flags == NULL)
+ err(1, "fflagstostr");
+ flen = strlen(flags);
+ if (flen > (size_t)maxflags)
+ maxflags = flen;
+ } else
+ flen = 0;
+ labelstr = NULL;
+ if (f_label) {
+ char name[PATH_MAX + 1];
+ mac_t label;
+ int error;
+
+ error = mac_prepare_file_label(&label);
+ if (error == -1) {
+ warn("MAC label for %s/%s",
+ cur->fts_parent->fts_path,
+ cur->fts_name);
+ goto label_out;
+ }
+
+ if (cur->fts_level == FTS_ROOTLEVEL)
+ snprintf(name, sizeof(name),
+ "%s", cur->fts_name);
+ else
+ snprintf(name, sizeof(name),
+ "%s/%s", cur->fts_parent->
+ fts_accpath, cur->fts_name);
+
+ if (options & FTS_LOGICAL)
+ error = mac_get_file(name,
+ label);
+ else
+ error = mac_get_link(name,
+ label);
+ if (error == -1) {
+ warn("MAC label for %s/%s",
+ cur->fts_parent->fts_path,
+ cur->fts_name);
+ mac_free(label);
+ goto label_out;
+ }
+
+ error = mac_to_text(label,
+ &labelstr);
+ if (error == -1) {
+ warn("MAC label for %s/%s",
+ cur->fts_parent->fts_path,
+ cur->fts_name);
+ mac_free(label);
+ goto label_out;
+ }
+ mac_free(label);
+label_out:
+ if (labelstr == NULL)
+ labelstr = strdup("-");
+ labelstrlen = strlen(labelstr);
+ if (labelstrlen > maxlabelstr)
+ maxlabelstr = labelstrlen;
+ } else
+ labelstrlen = 0;
+
+ if ((np = malloc(sizeof(NAMES) + labelstrlen +
+ ulen + glen + flen + 4)) == NULL)
+ err(1, "malloc");
+
+ np->user = &np->data[0];
+ (void)strcpy(np->user, user);
+ np->group = &np->data[ulen + 1];
+ (void)strcpy(np->group, group);
+
+ if (S_ISCHR(sp->st_mode) ||
+ S_ISBLK(sp->st_mode)) {
+ sizelen = snprintf(NULL, 0,
+ "%#jx", (uintmax_t)sp->st_rdev);
+ if (d.s_size < sizelen)
+ d.s_size = sizelen;
+ }
+
+ if (f_flags) {
+ np->flags = &np->data[ulen + glen + 2];
+ (void)strcpy(np->flags, flags);
+ free(flags);
+ }
+ if (f_label) {
+ np->label = &np->data[ulen + glen + 2
+ + (f_flags ? flen + 1 : 0)];
+ (void)strcpy(np->label, labelstr);
+ free(labelstr);
+ }
+ cur->fts_pointer = np;
+ }
+ }
+ ++entries;
+ }
+
+ /*
+ * If there are no entries to display, we normally stop right
+ * here. However, we must continue if we have to display the
+ * total block count. In this case, we display the total only
+ * on the second (p != NULL) pass.
+ */
+ if (!entries && (!(f_longform || f_size) || p == NULL))
+ return;
+
+ d.list = list;
+ d.entries = entries;
+ d.maxlen = maxlen;
+ if (needstats) {
+ d.btotal = btotal;
+ d.s_block = snprintf(NULL, 0, f_thousands ? "%'ld" : "%ld",
+ howmany(maxblock, blocksize));
+ d.s_flags = maxflags;
+ d.s_label = maxlabelstr;
+ d.s_group = maxgroup;
+ d.s_inode = snprintf(NULL, 0, "%ju", maxinode);
+ d.s_nlink = snprintf(NULL, 0, "%lu", maxnlink);
+ sizelen = f_humanval ? HUMANVALSTR_LEN :
+ snprintf(NULL, 0, "%ju", maxsize);
+ if (d.s_size < sizelen)
+ d.s_size = sizelen;
+ d.s_user = maxuser;
+ }
+ if (f_thousands) /* make space for commas */
+ d.s_size += (d.s_size - 1) / 3;
+ printfcn(&d);
+ output = 1;
+
+ if (f_longform)
+ for (cur = list; cur; cur = cur->fts_link)
+ free(cur->fts_pointer);
+}
+
+/*
+ * Ordering for mastercmp:
+ * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories
+ * as larger than directories. Within either group, use the sort function.
+ * All other levels use the sort function. Error entries remain unsorted.
+ */
+static int
+mastercmp(const FTSENT * const *a, const FTSENT * const *b)
+{
+ int a_info, b_info, dir;
+
+ a_info = (*a)->fts_info;
+ if (a_info == FTS_ERR)
+ return (0);
+ b_info = (*b)->fts_info;
+ if (b_info == FTS_ERR)
+ return (0);
+
+ if (a_info == FTS_NS || b_info == FTS_NS)
+ return (namecmp(*a, *b));
+
+ if (a_info != b_info &&
+ (*a)->fts_level == FTS_ROOTLEVEL && !f_listdir) {
+ if (a_info == FTS_D)
+ return (1);
+ if (b_info == FTS_D)
+ return (-1);
+ }
+
+ if (f_groupdir != GRP_NONE)
+ if ((dir = (a_info == FTS_D) - (b_info == FTS_D)) != 0)
+ return (f_groupdir * dir);
+
+ return (sortfcn(*a, *b));
+}
diff --git a/bin/ls/ls.h b/bin/ls/ls.h
new file mode 100644
index 000000000000..1c3c1cb786f6
--- /dev/null
+++ b/bin/ls/ls.h
@@ -0,0 +1,87 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define NO_PRINT 1
+
+#define HUMANVALSTR_LEN 5
+
+extern long blocksize; /* block size units */
+
+extern int f_accesstime; /* use time of last access */
+extern int f_birthtime; /* use time of file creation */
+extern int f_flags; /* show flags associated with a file */
+extern int f_humanval; /* show human-readable file sizes */
+extern int f_label; /* show MAC label */
+extern int f_inode; /* print inode */
+extern int f_longform; /* long listing format */
+extern int f_octal; /* print unprintables in octal */
+extern int f_octal_escape; /* like f_octal but use C escapes if possible */
+extern int f_nonprint; /* show unprintables as ? */
+extern int f_samesort; /* sort time and name in same direction */
+extern int f_sectime; /* print the real time for all files */
+extern int f_size; /* list size in short listing */
+extern int f_slash; /* append a '/' if the file is a directory */
+extern int f_sortacross; /* sort across rows, not down columns */
+extern int f_sowner; /* disable showing the owner's name */
+extern int f_statustime; /* use time of last mode change */
+extern int f_thousands; /* show file sizes with thousands separators */
+extern char *f_timeformat; /* user-specified time format */
+extern int f_notabs; /* don't use tab-separated multi-col output */
+extern int f_type; /* add type character for non-regular files */
+#ifdef COLORLS
+extern int f_color; /* add type in color for non-regular files */
+#endif
+
+typedef struct {
+ FTSENT *list;
+ u_long btotal;
+ int entries;
+ int maxlen;
+ u_int s_block;
+ u_int s_flags;
+ u_int s_label;
+ u_int s_group;
+ u_int s_inode;
+ u_int s_nlink;
+ u_int s_size;
+ u_int s_user;
+} DISPLAY;
+
+typedef struct {
+ char *user;
+ char *group;
+ char *flags;
+ char *label;
+ char data[1];
+} NAMES;
diff --git a/bin/ls/print.c b/bin/ls/print.c
new file mode 100644
index 000000000000..a504ec63dc26
--- /dev/null
+++ b/bin/ls/print.c
@@ -0,0 +1,838 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/acl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <langinfo.h>
+#include <libutil.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wchar.h>
+#ifdef COLORLS
+#include <ctype.h>
+#include <termcap.h>
+#include <signal.h>
+#endif
+
+#include "ls.h"
+#include "extern.h"
+
+static int printaname(const FTSENT *, u_long, u_long);
+static void printdev(size_t, dev_t);
+static void printlink(const FTSENT *);
+static void printtime(time_t);
+static int printtype(u_int);
+static void printsize(size_t, off_t);
+#ifdef COLORLS
+static void endcolor_termcap(int);
+static void endcolor_ansi(void);
+static void endcolor(int);
+static int colortype(mode_t);
+#endif
+static void aclmode(char *, const FTSENT *);
+
+#define IS_NOPRINT(p) ((p)->fts_number == NO_PRINT)
+
+#ifdef COLORLS
+/* Most of these are taken from <sys/stat.h> */
+typedef enum Colors {
+ C_DIR, /* directory */
+ C_LNK, /* symbolic link */
+ C_SOCK, /* socket */
+ C_FIFO, /* pipe */
+ C_EXEC, /* executable */
+ C_BLK, /* block special */
+ C_CHR, /* character special */
+ C_SUID, /* setuid executable */
+ C_SGID, /* setgid executable */
+ C_WSDIR, /* directory writeble to others, with sticky
+ * bit */
+ C_WDIR, /* directory writeble to others, without
+ * sticky bit */
+ C_NUMCOLORS /* just a place-holder */
+} Colors;
+
+static const char *defcolors = "exfxcxdxbxegedabagacad";
+
+/* colors for file types */
+static struct {
+ int num[2];
+ bool bold;
+ bool underline;
+} colors[C_NUMCOLORS];
+#endif
+
+static size_t padding_for_month[12];
+static size_t month_max_size = 0;
+
+void
+printscol(const DISPLAY *dp)
+{
+ FTSENT *p;
+
+ for (p = dp->list; p; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ (void)printaname(p, dp->s_inode, dp->s_block);
+ (void)putchar('\n');
+ }
+}
+
+/*
+ * print name in current style
+ */
+int
+printname(const char *name)
+{
+ if (f_octal || f_octal_escape)
+ return prn_octal(name);
+ else if (f_nonprint)
+ return prn_printable(name);
+ else
+ return prn_normal(name);
+}
+
+static const char *
+get_abmon(int mon)
+{
+
+ switch (mon) {
+ case 0: return (nl_langinfo(ABMON_1));
+ case 1: return (nl_langinfo(ABMON_2));
+ case 2: return (nl_langinfo(ABMON_3));
+ case 3: return (nl_langinfo(ABMON_4));
+ case 4: return (nl_langinfo(ABMON_5));
+ case 5: return (nl_langinfo(ABMON_6));
+ case 6: return (nl_langinfo(ABMON_7));
+ case 7: return (nl_langinfo(ABMON_8));
+ case 8: return (nl_langinfo(ABMON_9));
+ case 9: return (nl_langinfo(ABMON_10));
+ case 10: return (nl_langinfo(ABMON_11));
+ case 11: return (nl_langinfo(ABMON_12));
+ }
+
+ /* should never happen */
+ abort();
+}
+
+static size_t
+mbswidth(const char *month)
+{
+ wchar_t wc;
+ size_t width, donelen, clen, w;
+
+ width = donelen = 0;
+ while ((clen = mbrtowc(&wc, month + donelen, MB_LEN_MAX, NULL)) != 0) {
+ if (clen == (size_t)-1 || clen == (size_t)-2)
+ return (-1);
+ donelen += clen;
+ if ((w = wcwidth(wc)) == (size_t)-1)
+ return (-1);
+ width += w;
+ }
+
+ return (width);
+}
+
+static void
+compute_abbreviated_month_size(void)
+{
+ int i;
+ size_t width;
+ size_t months_width[12];
+
+ for (i = 0; i < 12; i++) {
+ width = mbswidth(get_abmon(i));
+ if (width == (size_t)-1) {
+ month_max_size = -1;
+ return;
+ }
+ months_width[i] = width;
+ if (width > month_max_size)
+ month_max_size = width;
+ }
+
+ for (i = 0; i < 12; i++)
+ padding_for_month[i] = month_max_size - months_width[i];
+}
+
+void
+printlong(const DISPLAY *dp)
+{
+ struct stat *sp;
+ FTSENT *p;
+ NAMES *np;
+ char buf[20];
+#ifdef COLORLS
+ int color_printed = 0;
+#endif
+
+ if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) &&
+ (f_longform || f_size)) {
+ if (!f_humanval)
+ (void)printf("total %lu\n", howmany(dp->btotal, blocksize));
+ else {
+ (void)humanize_number(buf, 7 /* "1024 KB" */,
+ dp->btotal * 512, "B", HN_AUTOSCALE, HN_DECIMAL);
+
+ (void)printf("total %s\n", buf);
+ }
+ }
+
+ for (p = dp->list; p; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ sp = p->fts_statp;
+ if (f_inode)
+ (void)printf("%*ju ",
+ dp->s_inode, (uintmax_t)sp->st_ino);
+ if (f_size)
+ (void)printf(f_thousands ? "%'*jd " : "%*jd ",
+ dp->s_block, howmany(sp->st_blocks, blocksize));
+ strmode(sp->st_mode, buf);
+ aclmode(buf, p);
+ np = p->fts_pointer;
+ (void)printf("%s %*ju ", buf, dp->s_nlink,
+ (uintmax_t)sp->st_nlink);
+ if (!f_sowner)
+ (void)printf("%-*s ", dp->s_user, np->user);
+ (void)printf("%-*s ", dp->s_group, np->group);
+ if (f_flags)
+ (void)printf("%-*s ", dp->s_flags, np->flags);
+ if (f_label)
+ (void)printf("%-*s ", dp->s_label, np->label);
+ if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode))
+ printdev(dp->s_size, sp->st_rdev);
+ else
+ printsize(dp->s_size, sp->st_size);
+ if (f_accesstime)
+ printtime(sp->st_atime);
+ else if (f_birthtime)
+ printtime(sp->st_birthtime);
+ else if (f_statustime)
+ printtime(sp->st_ctime);
+ else
+ printtime(sp->st_mtime);
+#ifdef COLORLS
+ if (f_color)
+ color_printed = colortype(sp->st_mode);
+#endif
+ (void)printname(p->fts_name);
+#ifdef COLORLS
+ if (f_color && color_printed)
+ endcolor(0);
+#endif
+ if (f_type)
+ (void)printtype(sp->st_mode);
+ if (S_ISLNK(sp->st_mode))
+ printlink(p);
+ (void)putchar('\n');
+ }
+}
+
+void
+printstream(const DISPLAY *dp)
+{
+ FTSENT *p;
+ int chcnt;
+
+ for (p = dp->list, chcnt = 0; p; p = p->fts_link) {
+ if (p->fts_number == NO_PRINT)
+ continue;
+ /* XXX strlen does not take octal escapes into account. */
+ if (strlen(p->fts_name) + chcnt +
+ (p->fts_link ? 2 : 0) >= (unsigned)termwidth) {
+ putchar('\n');
+ chcnt = 0;
+ }
+ chcnt += printaname(p, dp->s_inode, dp->s_block);
+ if (p->fts_link) {
+ printf(", ");
+ chcnt += 2;
+ }
+ }
+ if (chcnt)
+ putchar('\n');
+}
+
+void
+printcol(const DISPLAY *dp)
+{
+ static FTSENT **array;
+ static int lastentries = -1;
+ FTSENT *p;
+ FTSENT **narray;
+ int base;
+ int chcnt;
+ int cnt;
+ int col;
+ int colwidth;
+ int endcol;
+ int num;
+ int numcols;
+ int numrows;
+ int row;
+ int tabwidth;
+
+ if (f_notabs)
+ tabwidth = 1;
+ else
+ tabwidth = 8;
+
+ /*
+ * Have to do random access in the linked list -- build a table
+ * of pointers.
+ */
+ if (dp->entries > lastentries) {
+ if ((narray =
+ realloc(array, dp->entries * sizeof(FTSENT *))) == NULL) {
+ warn(NULL);
+ printscol(dp);
+ return;
+ }
+ lastentries = dp->entries;
+ array = narray;
+ }
+ for (p = dp->list, num = 0; p; p = p->fts_link)
+ if (p->fts_number != NO_PRINT)
+ array[num++] = p;
+
+ colwidth = dp->maxlen;
+ if (f_inode)
+ colwidth += dp->s_inode + 1;
+ if (f_size)
+ colwidth += dp->s_block + 1;
+ if (f_type)
+ colwidth += 1;
+
+ colwidth = (colwidth + tabwidth) & ~(tabwidth - 1);
+ if (termwidth < 2 * colwidth) {
+ printscol(dp);
+ return;
+ }
+ numcols = termwidth / colwidth;
+ numrows = num / numcols;
+ if (num % numcols)
+ ++numrows;
+
+ if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) &&
+ (f_longform || f_size)) {
+ (void)printf("total %lu\n", howmany(dp->btotal, blocksize));
+ }
+
+ base = 0;
+ for (row = 0; row < numrows; ++row) {
+ endcol = colwidth;
+ if (!f_sortacross)
+ base = row;
+ for (col = 0, chcnt = 0; col < numcols; ++col) {
+ chcnt += printaname(array[base], dp->s_inode,
+ dp->s_block);
+ if (f_sortacross)
+ base++;
+ else
+ base += numrows;
+ if (base >= num)
+ break;
+ while ((cnt = ((chcnt + tabwidth) & ~(tabwidth - 1)))
+ <= endcol) {
+ if (f_sortacross && col + 1 >= numcols)
+ break;
+ (void)putchar(f_notabs ? ' ' : '\t');
+ chcnt = cnt;
+ }
+ endcol += colwidth;
+ }
+ (void)putchar('\n');
+ }
+}
+
+/*
+ * print [inode] [size] name
+ * return # of characters printed, no trailing characters.
+ */
+static int
+printaname(const FTSENT *p, u_long inodefield, u_long sizefield)
+{
+ struct stat *sp;
+ int chcnt;
+#ifdef COLORLS
+ int color_printed = 0;
+#endif
+
+ sp = p->fts_statp;
+ chcnt = 0;
+ if (f_inode)
+ chcnt += printf("%*ju ",
+ (int)inodefield, (uintmax_t)sp->st_ino);
+ if (f_size)
+ chcnt += printf(f_thousands ? "%'*jd " : "%*jd ",
+ (int)sizefield, howmany(sp->st_blocks, blocksize));
+#ifdef COLORLS
+ if (f_color)
+ color_printed = colortype(sp->st_mode);
+#endif
+ chcnt += printname(p->fts_name);
+#ifdef COLORLS
+ if (f_color && color_printed)
+ endcolor(0);
+#endif
+ if (f_type)
+ chcnt += printtype(sp->st_mode);
+ return (chcnt);
+}
+
+/*
+ * Print device special file major and minor numbers.
+ */
+static void
+printdev(size_t width, dev_t dev)
+{
+
+ (void)printf("%#*jx ", (u_int)width, (uintmax_t)dev);
+}
+
+static void
+ls_strftime(char *str, size_t len, const char *fmt, const struct tm *tm)
+{
+ char *posb, nfmt[BUFSIZ];
+ const char *format = fmt;
+
+ if ((posb = strstr(fmt, "%b")) != NULL) {
+ if (month_max_size == 0) {
+ compute_abbreviated_month_size();
+ }
+ if (month_max_size > 0 && tm != NULL) {
+ snprintf(nfmt, sizeof(nfmt), "%.*s%s%*s%s",
+ (int)(posb - fmt), fmt,
+ get_abmon(tm->tm_mon),
+ (int)padding_for_month[tm->tm_mon],
+ "",
+ posb + 2);
+ format = nfmt;
+ }
+ }
+ if (tm != NULL)
+ strftime(str, len, format, tm);
+ else
+ strlcpy(str, "bad date val", len);
+}
+
+static void
+printtime(time_t ftime)
+{
+ char longstring[80];
+ static time_t now = 0;
+ const char *format;
+ static int d_first = -1;
+
+ if (d_first < 0)
+ d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
+ if (now == 0)
+ now = time(NULL);
+
+#define SIXMONTHS ((365 / 2) * 86400)
+ if (f_timeformat) /* user specified format */
+ format = f_timeformat;
+ else if (f_sectime)
+ /* mmm dd hh:mm:ss yyyy || dd mmm hh:mm:ss yyyy */
+ format = d_first ? "%e %b %T %Y" : "%b %e %T %Y";
+ else if (ftime + SIXMONTHS > now && ftime < now + SIXMONTHS)
+ /* mmm dd hh:mm || dd mmm hh:mm */
+ format = d_first ? "%e %b %R" : "%b %e %R";
+ else
+ /* mmm dd yyyy || dd mmm yyyy */
+ format = d_first ? "%e %b %Y" : "%b %e %Y";
+ ls_strftime(longstring, sizeof(longstring), format, localtime(&ftime));
+ fputs(longstring, stdout);
+ fputc(' ', stdout);
+}
+
+static int
+printtype(u_int mode)
+{
+
+ if (f_slash) {
+ if ((mode & S_IFMT) == S_IFDIR) {
+ (void)putchar('/');
+ return (1);
+ }
+ return (0);
+ }
+
+ switch (mode & S_IFMT) {
+ case S_IFDIR:
+ (void)putchar('/');
+ return (1);
+ case S_IFIFO:
+ (void)putchar('|');
+ return (1);
+ case S_IFLNK:
+ (void)putchar('@');
+ return (1);
+ case S_IFSOCK:
+ (void)putchar('=');
+ return (1);
+ case S_IFWHT:
+ (void)putchar('%');
+ return (1);
+ default:
+ break;
+ }
+ if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+ (void)putchar('*');
+ return (1);
+ }
+ return (0);
+}
+
+#ifdef COLORLS
+static int
+putch(int c)
+{
+ (void)putchar(c);
+ return 0;
+}
+
+static int
+writech(int c)
+{
+ char tmp = (char)c;
+
+ (void)write(STDOUT_FILENO, &tmp, 1);
+ return 0;
+}
+
+static void
+printcolor_termcap(Colors c)
+{
+ char *ansiseq;
+
+ if (colors[c].bold)
+ tputs(enter_bold, 1, putch);
+ if (colors[c].underline)
+ tputs(enter_underline, 1, putch);
+
+ if (colors[c].num[0] != -1) {
+ ansiseq = tgoto(ansi_fgcol, 0, colors[c].num[0]);
+ if (ansiseq)
+ tputs(ansiseq, 1, putch);
+ }
+ if (colors[c].num[1] != -1) {
+ ansiseq = tgoto(ansi_bgcol, 0, colors[c].num[1]);
+ if (ansiseq)
+ tputs(ansiseq, 1, putch);
+ }
+}
+
+static void
+printcolor_ansi(Colors c)
+{
+
+ printf("\033[");
+
+ if (colors[c].bold)
+ printf("1");
+ if (colors[c].underline)
+ printf(";4");
+ if (colors[c].num[0] != -1)
+ printf(";3%d", colors[c].num[0]);
+ if (colors[c].num[1] != -1)
+ printf(";4%d", colors[c].num[1]);
+ printf("m");
+}
+
+static void
+printcolor(Colors c)
+{
+
+ if (explicitansi)
+ printcolor_ansi(c);
+ else
+ printcolor_termcap(c);
+}
+
+static void
+endcolor_termcap(int sig)
+{
+
+ tputs(ansi_coloff, 1, sig ? writech : putch);
+ tputs(attrs_off, 1, sig ? writech : putch);
+}
+
+static void
+endcolor_ansi(void)
+{
+
+ printf("\33[m");
+}
+
+static void
+endcolor(int sig)
+{
+
+ if (explicitansi)
+ endcolor_ansi();
+ else
+ endcolor_termcap(sig);
+}
+
+static int
+colortype(mode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFDIR:
+ if (mode & S_IWOTH)
+ if (mode & S_ISTXT)
+ printcolor(C_WSDIR);
+ else
+ printcolor(C_WDIR);
+ else
+ printcolor(C_DIR);
+ return (1);
+ case S_IFLNK:
+ printcolor(C_LNK);
+ return (1);
+ case S_IFSOCK:
+ printcolor(C_SOCK);
+ return (1);
+ case S_IFIFO:
+ printcolor(C_FIFO);
+ return (1);
+ case S_IFBLK:
+ printcolor(C_BLK);
+ return (1);
+ case S_IFCHR:
+ printcolor(C_CHR);
+ return (1);
+ default:;
+ }
+ if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+ if (mode & S_ISUID)
+ printcolor(C_SUID);
+ else if (mode & S_ISGID)
+ printcolor(C_SGID);
+ else
+ printcolor(C_EXEC);
+ return (1);
+ }
+ return (0);
+}
+
+void
+parsecolors(const char *cs)
+{
+ int i;
+ int j;
+ size_t len;
+ char c[2];
+ short legacy_warn = 0;
+
+ if (cs == NULL)
+ cs = ""; /* LSCOLORS not set */
+ len = strlen(cs);
+ for (i = 0; i < (int)C_NUMCOLORS; i++) {
+ colors[i].bold = false;
+ colors[i].underline = false;
+
+ if (len <= 2 * (size_t)i) {
+ c[0] = defcolors[2 * i];
+ c[1] = defcolors[2 * i + 1];
+ } else {
+ c[0] = cs[2 * i];
+ c[1] = cs[2 * i + 1];
+ }
+ for (j = 0; j < 2; j++) {
+ /* Legacy colours used 0-7 */
+ if (c[j] >= '0' && c[j] <= '7') {
+ colors[i].num[j] = c[j] - '0';
+ if (!legacy_warn) {
+ warnx("LSCOLORS should use "
+ "characters a-h instead of 0-9 ("
+ "see the manual page)");
+ }
+ legacy_warn = 1;
+ } else if (c[j] >= 'a' && c[j] <= 'h')
+ colors[i].num[j] = c[j] - 'a';
+ else if (c[j] >= 'A' && c[j] <= 'H') {
+ colors[i].num[j] = c[j] - 'A';
+ if (j == 1)
+ colors[i].underline = true;
+ else
+ colors[i].bold = true;
+ } else if (tolower((unsigned char)c[j]) == 'x') {
+ if (j == 1 && c[j] == 'X')
+ colors[i].underline = true;
+ colors[i].num[j] = -1;
+ } else {
+ warnx("invalid character '%c' in LSCOLORS"
+ " env var", c[j]);
+ colors[i].num[j] = -1;
+ }
+ }
+ }
+}
+
+void
+colorquit(int sig)
+{
+ endcolor(sig);
+
+ (void)signal(sig, SIG_DFL);
+ (void)kill(getpid(), sig);
+}
+
+#endif /* COLORLS */
+
+static void
+printlink(const FTSENT *p)
+{
+ int lnklen;
+ char name[MAXPATHLEN + 1];
+ char path[MAXPATHLEN + 1];
+
+ if (p->fts_level == FTS_ROOTLEVEL)
+ (void)snprintf(name, sizeof(name), "%s", p->fts_name);
+ else
+ (void)snprintf(name, sizeof(name),
+ "%s/%s", p->fts_parent->fts_accpath, p->fts_name);
+ if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
+ (void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno));
+ return;
+ }
+ path[lnklen] = '\0';
+ (void)printf(" -> ");
+ (void)printname(path);
+}
+
+static void
+printsize(size_t width, off_t bytes)
+{
+
+ if (f_humanval) {
+ /*
+ * Reserve one space before the size and allocate room for
+ * the trailing '\0'.
+ */
+ char buf[HUMANVALSTR_LEN - 1 + 1];
+
+ humanize_number(buf, sizeof(buf), (int64_t)bytes, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ (void)printf("%*s ", (u_int)width, buf);
+ } else {
+ (void)printf(f_thousands ? "%'*jd " : "%*jd ",
+ (u_int)width, bytes);
+ }
+}
+
+/*
+ * Add a + after the standard rwxrwxrwx mode if the file has an
+ * ACL. strmode() reserves space at the end of the string.
+ */
+static void
+aclmode(char *buf, const FTSENT *p)
+{
+ char name[MAXPATHLEN + 1];
+ int ret, trivial;
+ static dev_t previous_dev = NODEV;
+ static int supports_acls = -1;
+ static int type = ACL_TYPE_ACCESS;
+ acl_t facl;
+
+ /*
+ * XXX: ACLs are not supported on whiteouts and device files
+ * residing on UFS.
+ */
+ if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) ||
+ S_ISWHT(p->fts_statp->st_mode))
+ return;
+
+ if (previous_dev == p->fts_statp->st_dev && supports_acls == 0)
+ return;
+
+ if (p->fts_level == FTS_ROOTLEVEL)
+ snprintf(name, sizeof(name), "%s", p->fts_name);
+ else
+ snprintf(name, sizeof(name), "%s/%s",
+ p->fts_parent->fts_accpath, p->fts_name);
+
+ if (previous_dev != p->fts_statp->st_dev) {
+ previous_dev = p->fts_statp->st_dev;
+ supports_acls = 0;
+
+ ret = lpathconf(name, _PC_ACL_NFS4);
+ if (ret > 0) {
+ type = ACL_TYPE_NFS4;
+ supports_acls = 1;
+ } else if (ret < 0 && errno != EINVAL) {
+ warn("%s", name);
+ return;
+ }
+ if (supports_acls == 0) {
+ ret = lpathconf(name, _PC_ACL_EXTENDED);
+ if (ret > 0) {
+ type = ACL_TYPE_ACCESS;
+ supports_acls = 1;
+ } else if (ret < 0 && errno != EINVAL) {
+ warn("%s", name);
+ return;
+ }
+ }
+ }
+ if (supports_acls == 0)
+ return;
+ facl = acl_get_link_np(name, type);
+ if (facl == NULL) {
+ warn("%s", name);
+ return;
+ }
+ if (acl_is_trivial_np(facl, &trivial)) {
+ acl_free(facl);
+ warn("%s", name);
+ return;
+ }
+ if (!trivial)
+ buf[10] = '+';
+ acl_free(facl);
+}
diff --git a/bin/ls/tests/Makefile b/bin/ls/tests/Makefile
new file mode 100644
index 000000000000..e750910cc91d
--- /dev/null
+++ b/bin/ls/tests/Makefile
@@ -0,0 +1,7 @@
+ATF_TESTS_SH+= ls_tests
+# This seems like overkill, but the idea in mind is that all of the testcases
+# should be runnable as !root
+TEST_METADATA.ls_tests+= required_user="unprivileged"
+TEST_METADATA.ls_tests+= required_files="/usr/bin/awk /usr/bin/nc /usr/bin/sort"
+
+.include <bsd.test.mk>
diff --git a/bin/ls/tests/Makefile.depend b/bin/ls/tests/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/bin/ls/tests/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/ls/tests/ls_tests.sh b/bin/ls/tests/ls_tests.sh
new file mode 100755
index 000000000000..c732b60b21a4
--- /dev/null
+++ b/bin/ls/tests/ls_tests.sh
@@ -0,0 +1,997 @@
+#
+# Copyright 2015 EMC Corp.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+#
+
+create_test_dir()
+{
+ [ -z "$ATF_TMPDIR" ] || return 0
+
+ export ATF_TMPDIR=$(pwd)
+
+ # XXX: need to nest this because of how kyua creates $TMPDIR; otherwise
+ # it will run into EPERM issues later
+ TEST_INPUTS_DIR="${ATF_TMPDIR}/test/inputs"
+
+ atf_check -e empty -s exit:0 mkdir -m 0777 -p $TEST_INPUTS_DIR
+ cd $TEST_INPUTS_DIR
+}
+
+create_test_inputs()
+{
+ create_test_dir
+
+ atf_check -e empty -s exit:0 mkdir -m 0755 -p a/b/1
+ atf_check -e empty -s exit:0 ln -s a/b c
+ atf_check -e empty -s exit:0 touch d
+ atf_check -e empty -s exit:0 ln d e
+ atf_check -e empty -s exit:0 touch .f
+ atf_check -e empty -s exit:0 mkdir .g
+ atf_check -e empty -s exit:0 mkfifo h
+ atf_check -e ignore -s exit:0 dd if=/dev/zero of=i count=1000 bs=1
+ atf_check -e empty -s exit:0 touch klmn
+ atf_check -e empty -s exit:0 touch opqr
+ atf_check -e empty -s exit:0 touch stuv
+ atf_check -e empty -s exit:0 install -m 0755 /dev/null wxyz
+ atf_check -e empty -s exit:0 touch 0b00000001
+ atf_check -e empty -s exit:0 touch 0b00000010
+ atf_check -e empty -s exit:0 touch 0b00000011
+ atf_check -e empty -s exit:0 touch 0b00000100
+ atf_check -e empty -s exit:0 touch 0b00000101
+ atf_check -e empty -s exit:0 touch 0b00000110
+ atf_check -e empty -s exit:0 touch 0b00000111
+ atf_check -e empty -s exit:0 touch 0b00001000
+ atf_check -e empty -s exit:0 touch 0b00001001
+ atf_check -e empty -s exit:0 touch 0b00001010
+ atf_check -e empty -s exit:0 touch 0b00001011
+ atf_check -e empty -s exit:0 touch 0b00001100
+ atf_check -e empty -s exit:0 touch 0b00001101
+ atf_check -e empty -s exit:0 touch 0b00001110
+ atf_check -e empty -s exit:0 touch 0b00001111
+}
+
+KB=1024
+MB=$(( 1024 * $KB ))
+GB=$(( 1024 * $MB ))
+TB=$(( 1024 * $GB ))
+PB=$(( 1024 * $TB ))
+
+create_test_inputs2()
+{
+ create_test_dir
+
+ if ! getconf MIN_HOLE_SIZE "$(pwd)"; then
+ echo "getconf MIN_HOLE_SIZE $(pwd) failed; sparse files probably" \
+ "not supported by file system"
+ mount
+ atf_skip "Test's work directory does not support sparse files;" \
+ "try with a different TMPDIR?"
+ fi
+
+ for filesize in 1 512 $(( 2 * $KB )) $(( 10 * $KB )) $(( 512 * $KB )); \
+ do
+ atf_check -e ignore -o empty -s exit:0 \
+ dd if=/dev/zero of=${filesize}.file bs=1 \
+ count=1 oseek=${filesize} conv=sparse
+ files="${files} ${filesize}.file"
+ done
+
+ for filesize in $MB $GB $TB; do
+ atf_check -e ignore -o empty -s exit:0 \
+ dd if=/dev/zero of=${filesize}.file bs=$MB \
+ count=1 oseek=$(( $filesize / $MB )) conv=sparse
+ files="${files} ${filesize}.file"
+ done
+}
+
+atf_test_case A_flag
+A_flag_head()
+{
+ atf_set "descr" "Verify -A support with unprivileged users"
+}
+
+A_flag_body()
+{
+ create_test_dir
+
+ atf_check -e empty -o empty -s exit:0 ls -A
+
+ create_test_inputs
+
+ WITH_A=$PWD/../with_A.out
+ WITHOUT_A=$PWD/../without_A.out
+
+ atf_check -e empty -o save:$WITH_A -s exit:0 ls -A
+ atf_check -e empty -o save:$WITHOUT_A -s exit:0 ls
+
+ echo "-A usage"
+ cat $WITH_A
+ echo "No -A usage"
+ cat $WITHOUT_A
+
+ for dot_path in '\.f' '\.g'; do
+ atf_check -e empty -o not-empty -s exit:0 grep "${dot_path}" \
+ $WITH_A
+ atf_check -e empty -o empty -s not-exit:0 grep "${dot_path}" \
+ $WITHOUT_A
+ done
+}
+
+atf_test_case A_flag_implied_when_root
+A_flag_implied_when_root_head()
+{
+ atf_set "descr" "Verify that -A is implied for root"
+ atf_set "require.user" "root"
+}
+
+A_flag_implied_when_root_body()
+{
+ create_test_dir
+
+ atf_check -e empty -o empty -s exit:0 ls -A
+
+ create_test_inputs
+
+ WITH_EXPLICIT=$PWD/../with_explicit_A.out
+ WITH_IMPLIED=$PWD/../with_implied_A.out
+
+ atf_check -e empty -o save:$WITH_EXPLICIT -s exit:0 ls -A
+ atf_check -e empty -o save:$WITH_IMPLIED -s exit:0 ls
+
+ echo "Explicit -A usage"
+ cat $WITH_EXPLICIT
+ echo "Implicit -A usage"
+ cat $WITH_IMPLIED
+
+ atf_check_equal "$(cat $WITH_EXPLICIT)" "$(cat $WITH_IMPLIED)"
+}
+
+atf_test_case B_flag
+B_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -B prints out non-printable characters"
+}
+
+B_flag_body()
+{
+ atf_check -e empty -o empty -s exit:0 touch "$(printf "y\013z")"
+ atf_check -e empty -o match:'y\\013z' -s exit:0 ls -B
+}
+
+atf_test_case C_flag
+C_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -C is multi-column, sorted down"
+}
+
+print_index()
+{
+ local i=1
+ local wanted_index=$1; shift
+
+ while [ $i -le $wanted_index ]; do
+ if [ $i -eq $wanted_index ]; then
+ echo $1
+ return
+ fi
+ shift
+ : $(( i += 1 ))
+ done
+}
+
+C_flag_body()
+{
+ create_test_inputs
+
+ WITH_C=$PWD/../with_C.out
+
+ export COLUMNS=40
+ atf_check -e empty -o save:$WITH_C -s exit:0 ls -C
+
+ echo "With -C usage"
+ cat $WITH_C
+
+ paths=$(find -s . -mindepth 1 -maxdepth 1 \! -name '.*' -exec basename {} \; )
+ set -- $paths
+ num_paths=$#
+ num_columns=2
+
+ max_num_paths_per_column=$(( $(( $num_paths + 1 )) / $num_columns ))
+
+ local i=1
+ while [ $i -le $max_num_paths_per_column ]; do
+ column_1=$(print_index $i $paths)
+ column_2=$(print_index $(( $i + $max_num_paths_per_column )) $paths)
+ #echo "paths[$(( $i + $max_num_paths_per_column ))] = $column_2"
+ expected_expr="$column_1"
+ if [ -n "$column_2" ]; then
+ expected_expr="$expected_expr[[:space:]]+$column_2"
+ fi
+ atf_check -e ignore -o not-empty -s exit:0 \
+ egrep "$expected_expr" $WITH_C
+ : $(( i += 1 ))
+ done
+}
+
+atf_test_case D_flag
+D_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -D modifies the time format used with ls -l"
+}
+
+D_flag_body()
+{
+ atf_check -e empty -o empty -s exit:0 touch a.file
+ atf_check -e empty -o match:"$(stat -f '%c[[:space:]]+%N' a.file)" \
+ -s exit:0 ls -lD '%s'
+}
+
+atf_test_case F_flag
+F_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -F prints out appropriate symbols after files"
+}
+
+F_flag_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -s exit:0 \
+ sh -c "pid=${ATF_TMPDIR}/nc.pid; daemon -p \$pid nc -lU j; sleep 2; pkill -F \$pid"
+
+ atf_check -e empty -o match:'a/' -s exit:0 ls -F
+ atf_check -e empty -o match:'c@' -s exit:0 ls -F
+ atf_check -e empty -o match:'h\|' -s exit:0 ls -F
+ atf_check -e empty -o match:'j=' -s exit:0 ls -F
+ #atf_check -e empty -o match:'<whiteout-file>%' -s exit:0 ls -F
+ atf_check -e empty -o match:'stuv' -s exit:0 ls -F
+ atf_check -e empty -o match:'wxyz\*' -s exit:0 ls -F
+}
+
+atf_test_case H_flag
+H_flag_head()
+{
+ atf_set "descr" "Verify that ls -H follows symlinks"
+}
+
+H_flag_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o match:'1' -s exit:0 ls -H c
+}
+
+atf_test_case I_flag
+I_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -I is the same as ls for an unprivileged user"
+}
+
+I_flag_body()
+{
+ create_test_inputs
+
+ WITH_I=$PWD/../with_I.out
+ WITHOUT_I=$PWD/../without_I.out
+
+ atf_check -e empty -o save:$WITH_I -s exit:0 ls -I
+ atf_check -e empty -o save:$WITHOUT_I -s exit:0 ls
+
+ echo "Explicit -I usage"
+ cat $WITH_I
+ echo "No -I usage"
+ cat $WITHOUT_I
+
+ atf_check_equal "$(cat $WITH_I)" "$(cat $WITHOUT_I)"
+}
+
+atf_test_case I_flag_voids_implied_A_flag_when_root
+I_flag_voids_implied_A_flag_when_root_head()
+{
+ atf_set "descr" "Verify that -I voids out implied -A for root"
+ atf_set "require.user" "root"
+}
+
+I_flag_voids_implied_A_flag_when_root_body()
+{
+ create_test_inputs
+
+ atf_check -o not-match:'\.f' -s exit:0 ls -I
+ atf_check -o not-match:'\.g' -s exit:0 ls -I
+
+ atf_check -o match:'\.f' -s exit:0 ls -A -I
+ atf_check -o match:'\.g' -s exit:0 ls -A -I
+}
+
+atf_test_case L_flag
+L_flag_head()
+{
+ atf_set "descr" "Verify that -L prints out the symbolic link and conversely -P prints out the target for the symbolic link"
+}
+
+L_flag_body()
+{
+ atf_check -e empty -o empty -s exit:0 ln -s target1/target2 link1
+ atf_check -e empty -o match:link1 -s exit:0 ls -L
+ atf_check -e empty -o not-match:target1/target2 -s exit:0 ls -L
+}
+
+atf_test_case R_flag
+R_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -R prints out the directory contents recursively"
+}
+
+R_flag_body()
+{
+ create_test_inputs
+
+ WITH_R=$PWD/../with_R.out
+ WITH_R_expected_output=$PWD/../with_R_expected.out
+
+ atf_check -e empty -o save:$WITH_R -s exit:0 ls -R
+
+ set -- . $(find -s . \! -name '.*' -type d)
+ while [ $# -gt 0 ]; do
+ dir=$1; shift
+ [ "$dir" != "." ] && echo "$dir:"
+ (cd $dir && ls -1A | sed -e '/^\./d')
+ [ $# -ne 0 ] && echo
+ done > $WITH_R_expected_output
+
+ echo "-R usage"
+ cat $WITH_R
+ echo "-R expected output"
+ cat $WITH_R_expected_output
+
+ atf_check_equal "$(cat $WITH_R)" "$(cat $WITH_R_expected_output)"
+}
+
+atf_test_case S_flag
+S_flag_head()
+{
+ atf_set "descr" "Verify that -S sorts by file size, then by filename lexicographically"
+}
+
+S_flag_body()
+{
+ create_test_dir
+
+ file_list_dir=$PWD/../files
+
+ atf_check -e empty -o empty -s exit:0 mkdir -p $file_list_dir
+
+ create_test_inputs
+ create_test_inputs2
+
+ WITH_S=$PWD/../with_S.out
+ WITHOUT_S=$PWD/../without_S.out
+
+ atf_check -e empty -o save:$WITH_S ls -D '%s' -lS
+ atf_check -e empty -o save:$WITHOUT_S ls -D '%s' -l
+
+ WITH_S_parsed=$(awk '! /^total/ { print $7 }' $WITH_S)
+ set -- $(awk '! /^total/ { print $5, $7 }' $WITHOUT_S)
+ while [ $# -gt 0 ]; do
+ size=$1; shift
+ filename=$1; shift
+ echo $filename >> $file_list_dir/${size}
+ done
+ file_lists=$(find $file_list_dir -type f -exec basename {} \; | sort -nr)
+ WITHOUT_S_parsed=$(for file_list in $file_lists; do sort < $file_list_dir/$file_list; done)
+
+ echo "-lS usage (parsed)"
+ echo "$WITH_S_parsed"
+ echo "-l usage (parsed)"
+ echo "$WITHOUT_S_parsed"
+
+ atf_check_equal "$WITHOUT_S_parsed" "$WITH_S_parsed"
+}
+
+atf_test_case T_flag
+T_flag_head()
+{
+ atf_set "descr" "Verify -T support"
+}
+
+T_flag_body()
+{
+ create_test_dir
+
+ atf_check -e empty -o empty -s exit:0 touch a.file
+
+ mtime_in_secs=$(stat -f %m -t %s a.file)
+ mtime=$(date -j -f %s $mtime_in_secs +"[[:space:]]+%b[[:space:]]+%e[[:space:]]+%H:%M:%S[[:space:]]+%Y")
+
+ atf_check -e empty -o match:"$mtime"'[[:space:]]+a\.file' \
+ -s exit:0 ls -lT a.file
+}
+
+atf_test_case a_flag
+a_flag_head()
+{
+ atf_set "descr" "Verify -a support"
+}
+
+a_flag_body()
+{
+ create_test_dir
+
+ # Make sure "." and ".." show up with -a
+ atf_check -e empty -o match:'\.[[:space:]]+\.\.' -s exit:0 ls -ax
+
+ create_test_inputs
+
+ WITH_a=$PWD/../with_a.out
+ WITHOUT_a=$PWD/../without_a.out
+
+ atf_check -e empty -o save:$WITH_a -s exit:0 ls -a
+ atf_check -e empty -o save:$WITHOUT_a -s exit:0 ls
+
+ echo "-a usage"
+ cat $WITH_a
+ echo "No -a usage"
+ cat $WITHOUT_a
+
+ for dot_path in '\.f' '\.g'; do
+ atf_check -e empty -o not-empty -s exit:0 grep "${dot_path}" \
+ $WITH_a
+ atf_check -e empty -o empty -s not-exit:0 grep "${dot_path}" \
+ $WITHOUT_a
+ done
+}
+
+atf_test_case b_flag
+b_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -b prints out non-printable characters"
+}
+
+b_flag_body()
+{
+ atf_check -e empty -o empty -s exit:0 touch "$(printf "y\013z")"
+ atf_check -e empty -o match:'y\\vz' -s exit:0 ls -b
+}
+
+atf_test_case d_flag
+d_flag_head()
+{
+ atf_set "descr" "Verify that -d doesn't descend down directories"
+}
+
+d_flag_body()
+{
+ create_test_dir
+
+ output=$PWD/../output
+
+ atf_check -e empty -o empty -s exit:0 mkdir -p a/b
+
+ for path in . $PWD a; do
+ atf_check -e empty -o save:$output -s exit:0 ls -d $path
+ atf_check_equal "$(cat $output)" "$path"
+ done
+}
+
+atf_test_case f_flag
+f_flag_head()
+{
+ atf_set "descr" "Verify that -f prints out the contents of a directory unsorted"
+}
+
+f_flag_body()
+{
+ create_test_inputs
+
+ output=$PWD/../output
+
+ # XXX: I don't have enough understanding of how the algorithm works yet
+ # to determine more than the fact that all the entries printed out
+ # exist
+ paths=$(find -s . -mindepth 1 -maxdepth 1 \! -name '.*' -exec basename {} \; )
+
+ atf_check -e empty -o save:$output -s exit:0 ls -f
+
+ for path in $paths; do
+ atf_check -e ignore -o not-empty -s exit:0 \
+ egrep "^$path$" $output
+ done
+}
+
+atf_test_case g_flag
+g_flag_head()
+{
+ atf_set "descr" "Verify that -g implies -l but omits the owner name field"
+}
+
+g_flag_body()
+{
+ atf_check -e empty -o empty -s exit:0 touch a.file
+
+ mtime_in_secs=$(stat -f "%m" -t "%s" a.file)
+ mtime=$(date -j -f "%s" $mtime_in_secs +"%b[[:space:]]+%e[[:space:]]+%H:%M")
+
+ expected_output=$(stat -f "%Sp[[:space:]]+%l[[:space:]]+%Sg[[:space:]]+%z[[:space:]]+$mtime[[:space:]]+a\\.file" a.file)
+
+ atf_check -e empty -o match:"$expected_output" -s exit:0 ls -g a.file
+}
+
+atf_test_case h_flag
+h_flag_head()
+{
+ atf_set "descr" "Verify that -h prints out the humanized units for file sizes with ls -l"
+ atf_set "require.progs" "bc"
+}
+
+h_flag_body()
+{
+ # XXX: this test doesn't currently show how 999 bytes will be 999B,
+ # but 1000 bytes will be 1.0K, due to how humanize_number(3) works.
+ create_test_inputs2
+ for file in $files; do
+ file_size=$(stat -f '%z' "$file") || \
+ atf_fail "stat'ing $file failed"
+ scale=2
+ if [ $file_size -lt $KB ]; then
+ divisor=1
+ scale=0
+ suffix=B
+ elif [ $file_size -lt $MB ]; then
+ divisor=$KB
+ suffix=K
+ elif [ $file_size -lt $GB ]; then
+ divisor=$MB
+ suffix=M
+ elif [ $file_size -lt $TB ]; then
+ divisor=$GB
+ suffix=G
+ elif [ $file_size -lt $PB ]; then
+ divisor=$TB
+ suffix=T
+ else
+ divisor=$PB
+ suffix=P
+ fi
+
+ bc_expr="$(printf "scale=%s\n%s/%s\nquit" $scale $file_size $divisor)"
+ size_humanized=$(bc -e "$bc_expr" | tr '.' '\.' | sed -e 's,\.00,,')
+
+ atf_check -e empty -o match:"$size_humanized.+$file" \
+ -s exit:0 ls -hl $file
+ done
+}
+
+atf_test_case i_flag
+i_flag_head()
+{
+ atf_set "descr" "Verify that -i prints out the inode for files"
+}
+
+i_flag_body()
+{
+ create_test_inputs
+
+ paths=$(find -L . -mindepth 1)
+ [ -n "$paths" ] || atf_skip 'Could not find any paths to iterate over (!)'
+
+ for path in $paths; do
+ atf_check -e empty \
+ -o match:"$(stat -f '[[:space:]]*%i[[:space:]]+%N' $path)" \
+ -s exit:0 ls -d1i $path
+ done
+}
+
+atf_test_case k_flag
+k_flag_head()
+{
+ atf_set "descr" "Verify that -k prints out the size with a block size of 1kB"
+}
+
+k_flag_body()
+{
+ create_test_inputs2
+ for file in $files; do
+ atf_check -e empty \
+ -o match:"[[:space:]]+$(stat -f "%z" $file)[[:space:]]+.+[[:space:]]+$file" ls -lk $file
+ done
+}
+
+atf_test_case l_flag
+l_flag_head()
+{
+ atf_set "descr" "Verify that -l prints out the output in long format"
+}
+
+l_flag_body()
+{
+
+ atf_check -e empty -o empty -s exit:0 touch a.file
+
+ mtime_in_secs=$(stat -f "%m" -t "%s" a.file)
+ mtime=$(date -j -f "%s" $mtime_in_secs +"%b[[:space:]]+%e[[:space:]]+%H:%M")
+
+ expected_output=$(stat -f "%Sp[[:space:]]+%l[[:space:]]+%Su[[:space:]]+%Sg[[:space:]]+%z[[:space:]]+$mtime[[:space:]]+a\\.file" a.file)
+
+ atf_check -e empty -o match:"$expected_output" -s exit:0 ls -l a.file
+}
+
+atf_test_case lcomma_flag
+lcomma_flag_head()
+{
+ atf_set "descr" "Verify that -l, prints out the size with ',' delimiters"
+}
+
+lcomma_flag_body()
+{
+ create_test_inputs
+
+ atf_check \
+ -o match:'\-rw\-r\-\-r\-\-[[:space:]]+.+[[:space:]]+1,000[[:space:]]+.+i' \
+ env LC_ALL=en_US.ISO8859-1 ls -l, i
+}
+
+atf_test_case m_flag
+m_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -m is comma-separated"
+}
+
+m_flag_body()
+{
+ create_test_dir
+
+ output=$PWD/../output
+
+ atf_check -e empty -o empty -s exit:0 touch ,, "a,b " c d e
+
+ atf_check -e empty -o save:$output -s exit:0 ls -m
+
+ atf_check_equal "$(cat $output)" ",,, a,b , c, d, e"
+}
+
+atf_test_case n_flag
+n_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -n prints out numeric GIDs/UIDs instead of symbolic GIDs/UIDs"
+ atf_set "require.user" "root"
+}
+
+n_flag_body()
+{
+ daemon_gid=$(id -g daemon) || atf_skip "could not resolve gid for daemon (!)"
+ nobody_uid=$(id -u nobody) || atf_skip "could not resolve uid for nobody (!)"
+
+ atf_check -e empty -o empty -s exit:0 touch a.file
+ atf_check -e empty -o empty -s exit:0 chown $nobody_uid:$daemon_gid a.file
+
+ atf_check -e empty \
+ -o match:'\-rw\-r\-\-r\-\-[[:space:]]+1[[:space:]]+'"$nobody_uid[[:space:]]+$daemon_gid"'[[:space:]]+.+a\.file' \
+ ls -n a.file
+
+}
+
+atf_test_case o_flag
+o_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -o prints out the chflag values or '-' if none are set"
+}
+
+o_flag_body()
+{
+ local size=12345
+
+ create_test_dir
+
+ atf_check -e ignore -o empty -s exit:0 dd if=/dev/zero of=a.file \
+ bs=$size count=1
+ atf_check -e ignore -o empty -s exit:0 dd if=/dev/zero of=b.file \
+ bs=$size count=1
+ atf_check -e empty -o empty -s exit:0 chflags uarch a.file
+ atf_check -e empty -o empty -s exit:0 chflags 0 b.file
+
+ atf_check -e empty -o match:"[[:space:]]+uarch[[:space:]]$size+.+a\\.file" \
+ -s exit:0 ls -lo a.file
+ atf_check -e empty -o match:"[[:space:]]+\\-[[:space:]]$size+.+b\\.file" \
+ -s exit:0 ls -lo b.file
+}
+
+atf_test_case p_flag
+p_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -p prints out '/' after directories"
+}
+
+p_flag_body()
+{
+ create_test_inputs
+
+ paths=$(find -L .)
+ [ -n "$paths" ] || atf_skip 'Could not find any paths to iterate over (!)'
+
+ for path in $paths; do
+ suffix=
+ # If path is not a symlink and is a directory, then the suffix
+ # must be "/".
+ if [ ! -L "${path}" -a -d "$path" ]; then
+ suffix=/
+ fi
+ atf_check -e empty -o match:"$path${suffix}" -s exit:0 \
+ ls -dp $path
+ done
+}
+
+atf_test_case q_flag_and_w_flag
+q_flag_and_w_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -q prints out '?' for ESC and ls -w prints out the escape character"
+}
+
+q_flag_and_w_flag_body()
+{
+ create_test_dir
+
+ test_file="$(printf "y\01z")"
+
+ atf_check -e empty -o empty -s exit:0 touch "$test_file"
+
+ atf_check -e empty -o match:'y\?z' -s exit:0 ls -q "$test_file"
+ atf_check -e empty -o match:"$test_file" -s exit:0 ls -w "$test_file"
+}
+
+atf_test_case r_flag
+r_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -r sorts the same way as reverse sorting with sort(1)"
+}
+
+r_flag_body()
+{
+ create_test_inputs
+
+ WITH_r=$PWD/../with_r.out
+ WITH_sort=$PWD/../with_sort.out
+
+ atf_check -e empty -o save:$WITH_r -s exit:0 ls -1r
+ atf_check -e empty -o save:$WITH_sort -s exit:0 sh -c 'ls -1 | sort -r'
+
+ echo "Sorted with -r"
+ cat $WITH_r
+ echo "Reverse sorted with sort(1)"
+ cat $WITH_sort
+
+ atf_check_equal "$(cat $WITH_r)" "$(cat $WITH_sort)"
+}
+
+atf_test_case s_flag
+s_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -s matches the output from stat(1)"
+}
+
+s_flag_body()
+{
+ create_test_inputs2
+ for file in $files; do
+ atf_check -e empty \
+ -o match:"$(stat -f "%b" $file)[[:space:]]+$file" ls -s $file
+ done
+}
+
+atf_test_case scomma_flag
+scomma_flag_head()
+{
+ atf_set "descr" "Verify that -s, prints out the size with ',' delimiters"
+}
+
+scomma_flag_body()
+{
+ export LC_ALL=en_US.UTF-8
+ atf_check -e ignore dd if=/dev/urandom of=file bs=65536 count=64
+ blocks=$(stat -f "%b" file)
+ cblocks=$(printf "%'d" $blocks)
+ atf_check -e empty -o match:"$cblocks[[:space:]]+file" ls -s, file
+}
+
+atf_test_case t_flag
+t_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -t sorts by modification time"
+}
+
+t_flag_body()
+{
+ create_test_dir
+
+ atf_check -e empty -o empty -s exit:0 touch a.file
+ atf_check -e empty -o empty -s exit:0 touch b.file
+
+ atf_check -e empty -o match:'a\.file' -s exit:0 sh -c 'ls -lt | tail -n 1'
+ atf_check -e empty -o match:'b\.file.*a\.file' -s exit:0 ls -Ct
+
+ atf_check -e empty -o empty -s exit:0 rm a.file
+ atf_check -e empty -o empty -s exit:0 sh -c 'echo "i am a" > a.file'
+
+ atf_check -e empty -o match:'b\.file' -s exit:0 sh -c 'ls -lt | tail -n 1'
+ atf_check -e empty -o match:'a\.file.*b\.file' -s exit:0 ls -Ct
+}
+
+atf_test_case u_flag
+u_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -u sorts by last access"
+}
+
+u_flag_body()
+{
+ create_test_dir
+
+ atf_check -e empty -o empty -s exit:0 touch a.file
+ atf_check -e empty -o empty -s exit:0 touch b.file
+
+ atf_check -e empty -o match:'b\.file' -s exit:0 sh -c 'ls -lu | tail -n 1'
+ atf_check -e empty -o match:'a\.file.*b\.file' -s exit:0 ls -Cu
+
+ atf_check -e empty -o empty -s exit:0 sh -c 'echo "i am a" > a.file'
+ atf_check -e empty -o match:'i am a' -s exit:0 cat a.file
+
+ atf_check -e empty -o match:'b\.file' -s exit:0 sh -c 'ls -lu | tail -n 1'
+ atf_check -e empty -o match:'a\.file.*b\.file' -s exit:0 ls -Cu
+}
+
+atf_test_case v_flag
+v_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -v sorts based on strverscmp(3)"
+}
+
+v_flag_body()
+{
+ create_test_dir
+
+ atf_check -e empty -o empty -s exit:0 touch 000 00 01 010 09 0 1 9 10
+ atf_check -e empty -o match:"000.00.01.010.09.0.1.9.10" -s exit:0 sh -c 'ls -Cv'
+}
+
+atf_test_case x_flag
+x_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -x is multi-column, sorted across"
+}
+
+x_flag_body()
+{
+ create_test_inputs
+
+ WITH_x=$PWD/../with_x.out
+
+ atf_check -e empty -o save:$WITH_x -s exit:0 ls -x
+
+ echo "With -x usage"
+ cat $WITH_x
+
+ atf_check -e ignore -o not-empty -s exit:0 \
+ egrep "a[[:space:]]+c[[:space:]]+d[[:space:]]+e[[:space:]]+h" $WITH_x
+ atf_check -e ignore -o not-empty -s exit:0 \
+ egrep "i[[:space:]]+klmn[[:space:]]+opqr[[:space:]]+stuv[[:space:]]+wxyz" $WITH_x
+}
+
+atf_test_case y_flag
+y_flag_head()
+{
+ atf_set "descr" "Verify that the output from ls -y sorts the same way as sort(1)"
+}
+
+y_flag_body()
+{
+ create_test_inputs
+
+ WITH_sort=$PWD/../with_sort.out
+ WITH_y=$PWD/../with_y.out
+
+ atf_check -e empty -o save:$WITH_sort -s exit:0 sh -c 'ls -1 | sort'
+ atf_check -e empty -o save:$WITH_y -s exit:0 ls -1y
+
+ echo "Sorted with sort(1)"
+ cat $WITH_sort
+ echo "Sorted with -y"
+ cat $WITH_y
+
+ atf_check_equal "$(cat $WITH_sort)" "$(cat $WITH_y)"
+}
+
+atf_test_case 1_flag
+1_flag_head()
+{
+ atf_set "descr" "Verify that -1 prints out one item per line"
+}
+
+1_flag_body()
+{
+ create_test_inputs
+
+ WITH_1=$PWD/../with_1.out
+ WITHOUT_1=$PWD/../without_1.out
+
+ atf_check -e empty -o save:$WITH_1 -s exit:0 ls -1
+ atf_check -e empty -o save:$WITHOUT_1 -s exit:0 \
+ sh -c 'for i in $(ls); do echo $i; done'
+
+ echo "Explicit -1 usage"
+ cat $WITH_1
+ echo "No -1 usage"
+ cat $WITHOUT_1
+
+ atf_check_equal "$(cat $WITH_1)" "$(cat $WITHOUT_1)"
+}
+
+atf_init_test_cases()
+{
+ export BLOCKSIZE=512
+
+ atf_add_test_case A_flag
+ atf_add_test_case A_flag_implied_when_root
+ atf_add_test_case B_flag
+ atf_add_test_case C_flag
+ atf_add_test_case D_flag
+ atf_add_test_case F_flag
+ #atf_add_test_case G_flag
+ atf_add_test_case H_flag
+ atf_add_test_case I_flag
+ atf_add_test_case I_flag_voids_implied_A_flag_when_root
+ atf_add_test_case L_flag
+ #atf_add_test_case P_flag
+ atf_add_test_case R_flag
+ atf_add_test_case S_flag
+ atf_add_test_case T_flag
+ #atf_add_test_case U_flag
+ #atf_add_test_case W_flag
+ #atf_add_test_case Z_flag
+ atf_add_test_case a_flag
+ atf_add_test_case b_flag
+ #atf_add_test_case c_flag
+ atf_add_test_case d_flag
+ atf_add_test_case f_flag
+ atf_add_test_case g_flag
+ atf_add_test_case h_flag
+ atf_add_test_case i_flag
+ atf_add_test_case k_flag
+ atf_add_test_case l_flag
+ atf_add_test_case lcomma_flag
+ atf_add_test_case m_flag
+ atf_add_test_case n_flag
+ atf_add_test_case o_flag
+ atf_add_test_case p_flag
+ atf_add_test_case q_flag_and_w_flag
+ atf_add_test_case r_flag
+ atf_add_test_case s_flag
+ atf_add_test_case scomma_flag
+ atf_add_test_case t_flag
+ atf_add_test_case u_flag
+ atf_add_test_case v_flag
+ atf_add_test_case x_flag
+ atf_add_test_case y_flag
+ atf_add_test_case 1_flag
+}
diff --git a/bin/ls/util.c b/bin/ls/util.c
new file mode 100644
index 000000000000..d83cae0e98d1
--- /dev/null
+++ b/bin/ls/util.c
@@ -0,0 +1,228 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fts.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "ls.h"
+#include "extern.h"
+
+int
+prn_normal(const char *s)
+{
+ mbstate_t mbs;
+ wchar_t wc;
+ int i, n;
+ size_t clen;
+
+ memset(&mbs, 0, sizeof(mbs));
+ n = 0;
+ while ((clen = mbrtowc(&wc, s, MB_LEN_MAX, &mbs)) != 0) {
+ if (clen == (size_t)-2) {
+ n += printf("%s", s);
+ break;
+ }
+ if (clen == (size_t)-1) {
+ memset(&mbs, 0, sizeof(mbs));
+ putchar((unsigned char)*s);
+ s++;
+ n++;
+ continue;
+ }
+ for (i = 0; i < (int)clen; i++)
+ putchar((unsigned char)s[i]);
+ s += clen;
+ if (iswprint(wc))
+ n += wcwidth(wc);
+ }
+ return (n);
+}
+
+int
+prn_printable(const char *s)
+{
+ mbstate_t mbs;
+ wchar_t wc;
+ int i, n;
+ size_t clen;
+
+ memset(&mbs, 0, sizeof(mbs));
+ n = 0;
+ while ((clen = mbrtowc(&wc, s, MB_LEN_MAX, &mbs)) != 0) {
+ if (clen == (size_t)-1) {
+ putchar('?');
+ s++;
+ n++;
+ memset(&mbs, 0, sizeof(mbs));
+ continue;
+ }
+ if (clen == (size_t)-2) {
+ putchar('?');
+ n++;
+ break;
+ }
+ if (!iswprint(wc)) {
+ putchar('?');
+ s += clen;
+ n++;
+ continue;
+ }
+ for (i = 0; i < (int)clen; i++)
+ putchar((unsigned char)s[i]);
+ s += clen;
+ n += wcwidth(wc);
+ }
+ return (n);
+}
+
+/*
+ * The fts system makes it difficult to replace fts_name with a different-
+ * sized string, so we just calculate the real length here and do the
+ * conversion in prn_octal()
+ *
+ * XXX when using f_octal_escape (-b) rather than f_octal (-B), the
+ * length computed by len_octal may be too big. I just can't be buggered
+ * to fix this as an efficient fix would involve a lookup table. Same goes
+ * for the rather inelegant code in prn_octal.
+ *
+ * DES 1998/04/23
+ */
+
+size_t
+len_octal(const char *s, int len)
+{
+ mbstate_t mbs;
+ wchar_t wc;
+ size_t clen, r;
+
+ memset(&mbs, 0, sizeof(mbs));
+ r = 0;
+ while (len != 0 && (clen = mbrtowc(&wc, s, len, &mbs)) != 0) {
+ if (clen == (size_t)-1) {
+ r += 4;
+ s++;
+ len--;
+ memset(&mbs, 0, sizeof(mbs));
+ continue;
+ }
+ if (clen == (size_t)-2) {
+ r += 4 * len;
+ break;
+ }
+ if (iswprint(wc))
+ r++;
+ else
+ r += 4 * clen;
+ s += clen;
+ }
+ return (r);
+}
+
+int
+prn_octal(const char *s)
+{
+ static const char esc[] = "\\\\\"\"\aa\bb\ff\nn\rr\tt\vv";
+ const char *p;
+ mbstate_t mbs;
+ wchar_t wc;
+ size_t clen;
+ unsigned char ch;
+ int goodchar, i, len, prtlen;
+
+ memset(&mbs, 0, sizeof(mbs));
+ len = 0;
+ while ((clen = mbrtowc(&wc, s, MB_LEN_MAX, &mbs)) != 0) {
+ goodchar = clen != (size_t)-1 && clen != (size_t)-2;
+ if (goodchar && iswprint(wc) && wc != L'\"' && wc != L'\\') {
+ for (i = 0; i < (int)clen; i++)
+ putchar((unsigned char)s[i]);
+ len += wcwidth(wc);
+ } else if (goodchar && f_octal_escape &&
+#if WCHAR_MIN < 0
+ wc >= 0 &&
+#endif
+ wc <= (wchar_t)UCHAR_MAX &&
+ (p = strchr(esc, (char)wc)) != NULL) {
+ putchar('\\');
+ putchar(p[1]);
+ len += 2;
+ } else {
+ if (goodchar)
+ prtlen = clen;
+ else if (clen == (size_t)-1)
+ prtlen = 1;
+ else
+ prtlen = strlen(s);
+ for (i = 0; i < prtlen; i++) {
+ ch = (unsigned char)s[i];
+ putchar('\\');
+ putchar('0' + (ch >> 6));
+ putchar('0' + ((ch >> 3) & 7));
+ putchar('0' + (ch & 7));
+ len += 4;
+ }
+ }
+ if (clen == (size_t)-2)
+ break;
+ if (clen == (size_t)-1) {
+ memset(&mbs, 0, sizeof(mbs));
+ s++;
+ } else
+ s += clen;
+ }
+ return (len);
+}
+
+void
+usage(void)
+{
+ (void)fprintf(stderr,
+#ifdef COLORLS
+ "usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [--color=when] [-D format] [--group-directories=]"
+#else
+ "usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [-D format] [--group-directories=]"
+#endif
+ " [file ...]\n");
+ exit(1);
+}