summaryrefslogtreecommitdiffstats
path: root/bin/ls
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ls')
-rw-r--r--bin/ls/Makefile20
-rw-r--r--bin/ls/Makefile.depend21
-rw-r--r--bin/ls/cmp.c197
-rw-r--r--bin/ls/extern.h69
-rw-r--r--bin/ls/ls.1860
-rw-r--r--bin/ls/ls.c933
-rw-r--r--bin/ls/ls.h90
-rw-r--r--bin/ls/print.c855
-rw-r--r--bin/ls/tests/Makefile9
-rwxr-xr-xbin/ls/tests/ls_tests.sh958
-rw-r--r--bin/ls/util.c248
11 files changed, 4260 insertions, 0 deletions
diff --git a/bin/ls/Makefile b/bin/ls/Makefile
new file mode 100644
index 0000000..e57bf35
--- /dev/null
+++ b/bin/ls/Makefile
@@ -0,0 +1,20 @@
+# @(#)Makefile 8.1 (Berkeley) 6/2/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= ls
+SRCS= cmp.c ls.c print.c util.c
+LIBADD= xo util
+
+.if !defined(RELEASE_CRUNCH) && \
+ ${MK_LS_COLORS} != no
+CFLAGS+= -DCOLORLS
+LIBADD+= termcapw
+.endif
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/bin/ls/Makefile.depend b/bin/ls/Makefile.depend
new file mode 100644
index 0000000..6151c71
--- /dev/null
+++ b/bin/ls/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/libxo \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/ls/cmp.c b/bin/ls/cmp.c
new file mode 100644
index 0000000..a2e46ff
--- /dev/null
+++ b/bin/ls/cmp.c
@@ -0,0 +1,197 @@
+/*-
+ * 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.
+ * 4. 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)cmp.c 8.1 (Berkeley) 5/31/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+
+#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
+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 0000000..e4b5059
--- /dev/null
+++ b/bin/ls/extern.h
@@ -0,0 +1,69 @@
+/*-
+ * 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.
+ * 4. 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.
+ *
+ * from: @(#)extern.h 8.1 (Berkeley) 5/31/93
+ * $FreeBSD$
+ */
+
+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 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 *, const char *);
+void printscol(const DISPLAY *);
+void printstream(const DISPLAY *);
+void usage(void);
+int prn_normal(const char *, const char *);
+char * getname(const char *);
+size_t len_octal(const char *, int);
+int prn_octal(const char *, const char *);
+char * get_octal(const char *);
+int prn_printable(const char *, const char *);
+char * get_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;
+#endif
+extern int termwidth;
diff --git a/bin/ls/ls.1 b/bin/ls/ls.1
new file mode 100644
index 0000000..16ad49b
--- /dev/null
+++ b/bin/ls/ls.1
@@ -0,0 +1,860 @@
+.\"-
+.\" 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.
+.\" 4. 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.
+.\"
+.\" @(#)ls.1 8.7 (Berkeley) 7/29/94
+.\" $FreeBSD$
+.\"
+.Dd December 1, 2015
+.Dt LS 1
+.Os
+.Sh NAME
+.Nm ls
+.Nd list directory contents
+.Sh SYNOPSIS
+.Nm
+.Op Fl -libxo
+.Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1,
+.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 -libxo
+Generate output via
+.Xr libxo 3
+in a selection of different human and machine readable formats.
+See
+.Xr xo_parse_args 3
+for details on command line arguments.
+.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-2001 .
+.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-2001 .
+.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
+in the environment.
+(See below.)
+This functionality can be compiled out by removing the definition of
+.Ev COLORLS .
+This option is not defined in
+.St -p1003.1-2001 .
+.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-2001 .
+.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-2001 .
+.It Fl U
+Use time when file was created for sorting or printing.
+This option is not defined in
+.St -p1003.1-2001 .
+.It Fl W
+Display whiteouts when scanning directories.
+This option is not defined in
+.St -p1003.1-2001 .
+.It Fl Z
+Display each file's MAC label; see
+.Xr maclabel 7 .
+This option is not defined in
+.St -p1003.1-2001 .
+.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-2001 .
+.It Fl c
+Use time when file status was last changed for sorting or printing.
+.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-2001 ,
+this option has no effect on the
+.Fl d ,
+.Fl l ,
+.Fl R
+and
+.Fl s
+options.
+.It Fl g
+This option has no effect.
+It is only available for compatibility with
+.Bx 4.3 ,
+where it was used to display the group name in the long
+.Pq Fl l
+format output.
+This option is incompatible with
+.St -p1003.1-2001 .
+.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-2001 .
+.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-2001 .
+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 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
+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
+and
+.Fl t
+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).
+.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 ,
+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.
+.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 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 .
+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.
+.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 black, usually shows up as dark grey
+.It Sy B
+bold red
+.It Sy C
+bold green
+.It Sy D
+bold brown, usually shows up as yellow
+.It Sy E
+bold blue
+.It Sy F
+bold magenta
+.It Sy G
+bold cyan
+.It Sy H
+bold light grey; looks like bright white
+.It Sy x
+default foreground or background
+.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
+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 ,
+.Xr libxo 3 ,
+.Xr localeconv 3 ,
+.Xr strftime 3 ,
+.Xr strmode 3 ,
+.Xr xo_parse_args 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 .
+The options
+.Fl B , D , G , I , T , U , W , Z , b , h , w , y
+and
+.Fl ,
+are compatible extensions not defined in
+.St -p1003.1-2001 .
+.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 .
+.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 0000000..91ef9ea
--- /dev/null
+++ b/bin/ls/ls.c
@@ -0,0 +1,933 @@
+/*-
+ * 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.
+ * 4. 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.
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)ls.c 8.5 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mac.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef COLORLS
+#include <termcap.h>
+#include <signal.h>
+#endif
+#include <libxo/xo.h>
+
+#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);
+
+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 */
+ 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 */
+ 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 */
+ 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_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 f_color; /* add type in color for non-regular files */
+
+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 */
+#endif
+
+static int rval;
+
+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;
+#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;
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ return (1);
+ xo_set_flags(NULL, XOF_COLUMNS);
+ xo_set_version(LS_XO_VERSION);
+
+ while ((ch = getopt(argc, argv,
+ "1ABCD:FGHILPRSTUWXZabcdfghiklmnopqrstuwxy,")) != -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 -t and -S options override each other. */
+ case 'S':
+ f_sizesort = 1;
+ f_timesort = 0;
+ break;
+ case 't':
+ f_timesort = 1;
+ f_sizesort = 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':
+ setenv("CLICOLOR", "", 1);
+ 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': /* Compatibility with 4.3BSD. */
+ 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;
+ 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;
+ 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. */
+ if (getenv("CLICOLOR") &&
+ (isatty(STDOUT_FILENO) || getenv("CLICOLOR_FORCE")))
+#ifdef COLORLS
+ if (tgetent(termcapbuf, getenv("TERM")) == 1) {
+ ansi_fgcol = tgetstr("AF", &bp);
+ ansi_bgcol = tgetstr("AB", &bp);
+ attrs_off = tgetstr("me", &bp);
+ enter_bold = tgetstr("md", &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
+ xo_warnx("color support not compiled in");
+#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 or -t 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
+#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_timesort && !f_sizesort)
+ sortfcn = revnamecmp;
+ else if (f_sizesort)
+ sortfcn = revsizecmp;
+ 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_timesort && !f_sizesort)
+ sortfcn = namecmp;
+ else if (f_sizesort)
+ sortfcn = sizecmp;
+ 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;
+
+ xo_open_container("file-information");
+ if (argc)
+ traverse(argc, argv, fts_options);
+ else
+ traverse(1, dotav, fts_options);
+ xo_close_container("file-information");
+ xo_finish();
+ 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;
+ int first = 1;
+
+ if ((ftsp =
+ fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
+ xo_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)
+ 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 ((p = fts_read(ftsp)) != NULL)
+ switch (p->fts_info) {
+ case FTS_DC:
+ xo_warnx("%s: directory causes a cycle", p->fts_name);
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ xo_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 (first) {
+ first = 0;
+ xo_open_list("directory");
+ }
+ xo_open_instance("directory");
+
+ /*
+ * If already output something, put out a newline as
+ * a separator. If multiple arguments, precede each
+ * directory with its name.
+ */
+ if (output) {
+ xo_emit("\n");
+ (void)printname("path", p->fts_path);
+ xo_emit(":\n");
+ } else if (argc > 1) {
+ (void)printname("path", p->fts_path);
+ xo_emit(":\n");
+ output = 1;
+ }
+ chp = fts_children(ftsp, ch_options);
+ display(p, chp, options);
+
+ xo_close_instance("directory");
+ if (!f_recursive && chp != NULL)
+ (void)fts_set(ftsp, p, FTS_SKIP);
+ break;
+ default:
+ break;
+ }
+ if (!first)
+ xo_close_list("directory");
+ if (errno)
+ xo_err(1, "fts_read");
+}
+
+/*
+ * 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];
+
+ needstats = f_inode || f_longform || f_size;
+ flen = 0;
+ btotal = 0;
+ initmax = getenv("LS_COLWIDTHS");
+ /* Fields match -lios order. New ones should be added at the end. */
+ maxlabelstr = maxblock = maxlen = maxnlink = 0;
+ maxuser = maxgroup = maxflags = maxsize = 0;
+ maxinode = 0;
+ if (initmax != NULL && *initmax != '\0') {
+ char *initmax2, *jinitmax;
+ int ninitmax;
+
+ /* Fill-in "::" as "0:0:0" for the sake of scanf. */
+ jinitmax = malloc(strlen(initmax) * 2 + 2);
+ if (jinitmax == NULL)
+ xo_err(1, "malloc");
+ initmax2 = jinitmax;
+ if (*initmax == ':')
+ strcpy(initmax2, "0:"), initmax2 += 2;
+ else
+ *initmax2++ = *initmax, *initmax2 = '\0';
+ for (initmax++; *initmax != '\0'; initmax++) {
+ if (initmax[-1] == ':' && initmax[0] == ':') {
+ *initmax2++ = '0';
+ *initmax2++ = initmax[0];
+ initmax2[1] = '\0';
+ } else {
+ *initmax2++ = initmax[0];
+ initmax2[1] = '\0';
+ }
+ }
+ if (initmax2[-1] == ':')
+ strcpy(initmax2, "0");
+
+ ninitmax = sscanf(jinitmax,
+ " %ju : %ld : %lu : %u : %u : %i : %jd : %lu : %lu ",
+ &maxinode, &maxblock, &maxnlink, &maxuser,
+ &maxgroup, &maxflags, &maxsize, &maxlen, &maxlabelstr);
+ f_notabs = 1;
+ switch (ninitmax) {
+ case 0:
+ maxinode = 0;
+ /* FALLTHROUGH */
+ case 1:
+ maxblock = 0;
+ /* FALLTHROUGH */
+ case 2:
+ maxnlink = 0;
+ /* FALLTHROUGH */
+ case 3:
+ maxuser = 0;
+ /* FALLTHROUGH */
+ case 4:
+ maxgroup = 0;
+ /* FALLTHROUGH */
+ case 5:
+ maxflags = 0;
+ /* FALLTHROUGH */
+ case 6:
+ maxsize = 0;
+ /* FALLTHROUGH */
+ case 7:
+ maxlen = 0;
+ /* FALLTHROUGH */
+ case 8:
+ maxlabelstr = 0;
+ /* FALLTHROUGH */
+#ifdef COLORLS
+ if (!f_color)
+#endif
+ f_notabs = 0;
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ MAKENINES(maxinode);
+ MAKENINES(maxblock);
+ MAKENINES(maxnlink);
+ MAKENINES(maxsize);
+ free(jinitmax);
+ }
+ 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) {
+ xo_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);
+ group = group_from_gid(sp->st_gid, 0);
+ }
+ 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)
+ xo_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) {
+ xo_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) {
+ xo_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) {
+ xo_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)
+ xo_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, "%lu", 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;
+
+ 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);
+ }
+ return (sortfcn(*a, *b));
+}
diff --git a/bin/ls/ls.h b/bin/ls/ls.h
new file mode 100644
index 0000000..e486777
--- /dev/null
+++ b/bin/ls/ls.h
@@ -0,0 +1,90 @@
+/*-
+ * 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.
+ * 4. 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.
+ *
+ * from: @(#)ls.h 8.1 (Berkeley) 5/31/93
+ * $FreeBSD$
+ */
+
+#define NO_PRINT 1
+
+#define HUMANVALSTR_LEN 5
+
+#define LS_XO_VERSION "1"
+
+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_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_numericonly; /* don't convert uid/gid to name */
+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 0000000..7fe73a6
--- /dev/null
+++ b/bin/ls/print.c
@@ -0,0 +1,855 @@
+/*-
+ * 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.
+ * 4. 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)print.c 8.4 (Berkeley) 4/17/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#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 <libxo/xo.h>
+
+#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(const char *, time_t);
+static int printtype(u_int);
+static void printsize(const char *, size_t, off_t);
+#ifdef COLORLS
+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];
+ int bold;
+} 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;
+
+ xo_open_list("entry");
+ for (p = dp->list; p; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ xo_open_instance("entry");
+ (void)printaname(p, dp->s_inode, dp->s_block);
+ xo_close_instance("entry");
+ xo_emit("\n");
+ }
+ xo_close_list("entry");
+}
+
+/*
+ * print name in current style
+ */
+int
+printname(const char *field, const char *name)
+{
+ char fmt[BUFSIZ];
+ char *s = getname(name);
+ int rc;
+
+ snprintf(fmt, sizeof(fmt), "{:%s/%%hs}", field);
+ rc = xo_emit(fmt, s);
+ free(s);
+ return rc;
+}
+
+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];
+}
+
+/*
+ * print name in current style
+ */
+char *
+getname(const char *name)
+{
+ if (f_octal || f_octal_escape)
+ return get_octal(name);
+ else if (f_nonprint)
+ return get_printable(name);
+ else
+ return strdup(name);
+}
+
+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)) {
+ xo_emit("{L:total} {:total-blocks/%lu}\n",
+ howmany(dp->btotal, blocksize));
+ }
+
+ xo_open_list("entry");
+ for (p = dp->list; p; p = p->fts_link) {
+ char *name, *type;
+ if (IS_NOPRINT(p))
+ continue;
+ xo_open_instance("entry");
+ sp = p->fts_statp;
+ name = getname(p->fts_name);
+ if (name)
+ xo_emit("{ke:name/%hs}", name);
+ if (f_inode)
+ xo_emit("{t:inode/%*ju} ",
+ dp->s_inode, (uintmax_t)sp->st_ino);
+ if (f_size)
+ xo_emit("{t:blocks/%*jd} ",
+ dp->s_block, howmany(sp->st_blocks, blocksize));
+ strmode(sp->st_mode, buf);
+ aclmode(buf, p);
+ np = p->fts_pointer;
+ xo_attr("value", "%03o", (int) sp->st_mode & ALLPERMS);
+ if (f_numericonly) {
+ xo_emit("{t:mode/%s}{e:mode_octal/%03o} {t:links/%*u} {td:user/%-*s}{e:user/%ju} {td:group/%-*s}{e:group/%ju} ",
+ buf, (int) sp->st_mode & ALLPERMS, dp->s_nlink, sp->st_nlink,
+ dp->s_user, np->user, (uintmax_t)sp->st_uid, dp->s_group, np->group, (uintmax_t)sp->st_gid);
+ } else {
+ xo_emit("{t:mode/%s}{e:mode_octal/%03o} {t:links/%*u} {t:user/%-*s} {t:group/%-*s} ",
+ buf, (int) sp->st_mode & ALLPERMS, dp->s_nlink, sp->st_nlink,
+ dp->s_user, np->user, dp->s_group, np->group);
+ }
+ if (S_ISBLK(sp->st_mode))
+ asprintf(&type, "block");
+ if (S_ISCHR(sp->st_mode))
+ asprintf(&type, "character");
+ if (S_ISDIR(sp->st_mode))
+ asprintf(&type, "directory");
+ if (S_ISFIFO(sp->st_mode))
+ asprintf(&type, "fifo");
+ if (S_ISLNK(sp->st_mode))
+ asprintf(&type, "symlink");
+ if (S_ISREG(sp->st_mode))
+ asprintf(&type, "regular");
+ if (S_ISSOCK(sp->st_mode))
+ asprintf(&type, "socket");
+ if (S_ISWHT(sp->st_mode))
+ asprintf(&type, "whiteout");
+ xo_emit("{e:type/%s}", type);
+ free(type);
+ if (f_flags)
+ xo_emit("{:flags/%-*s} ", dp->s_flags, np->flags);
+ if (f_label)
+ xo_emit("{t:label/%-*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("size", dp->s_size, sp->st_size);
+ if (f_accesstime)
+ printtime("access-time", sp->st_atime);
+ else if (f_birthtime)
+ printtime("birth-time", sp->st_birthtime);
+ else if (f_statustime)
+ printtime("change-time", sp->st_ctime);
+ else
+ printtime("modify-time", sp->st_mtime);
+#ifdef COLORLS
+ if (f_color)
+ color_printed = colortype(sp->st_mode);
+#endif
+
+ if (name) {
+ xo_emit("{dk:name/%hs}", name);
+ free(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);
+ xo_close_instance("entry");
+ xo_emit("\n");
+ }
+ xo_close_list("entry");
+}
+
+void
+printstream(const DISPLAY *dp)
+{
+ FTSENT *p;
+ int chcnt;
+
+ xo_open_list("entry");
+ 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) {
+ xo_emit("\n");
+ chcnt = 0;
+ }
+ xo_open_instance("file");
+ chcnt += printaname(p, dp->s_inode, dp->s_block);
+ xo_close_instance("file");
+ if (p->fts_link) {
+ xo_emit(", ");
+ chcnt += 2;
+ }
+ }
+ xo_close_list("entry");
+ if (chcnt)
+ xo_emit("\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) {
+ 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)) {
+ xo_emit("{L:total} {:total-blocks/%lu}\n",
+ howmany(dp->btotal, blocksize));
+ }
+
+ xo_open_list("entry");
+ base = 0;
+ for (row = 0; row < numrows; ++row) {
+ endcol = colwidth;
+ if (!f_sortacross)
+ base = row;
+ for (col = 0, chcnt = 0; col < numcols; ++col) {
+ xo_open_instance("entry");
+ chcnt += printaname(array[base], dp->s_inode,
+ dp->s_block);
+ xo_close_instance("entry");
+ 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;
+ xo_emit(f_notabs ? " " : "\t");
+ chcnt = cnt;
+ }
+ endcol += colwidth;
+ }
+ xo_emit("\n");
+ }
+ xo_close_list("entry");
+}
+
+/*
+ * 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 += xo_emit("{t:inode/%*ju} ",
+ (int)inodefield, (uintmax_t)sp->st_ino);
+ if (f_size)
+ chcnt += xo_emit("{t:size/%*jd} ",
+ (int)sizefield, howmany(sp->st_blocks, blocksize));
+#ifdef COLORLS
+ if (f_color)
+ color_printed = colortype(sp->st_mode);
+#endif
+ chcnt += printname("name", 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)
+{
+ xo_emit("{:device/%#*jx} ", (u_int)width, (uintmax_t)dev);
+}
+
+static size_t
+ls_strftime(char *str, size_t len, const char *fmt, const struct tm *tm)
+{
+ char *posb, nfmt[BUFSIZ];
+ const char *format = fmt;
+ size_t ret;
+
+ if ((posb = strstr(fmt, "%b")) != NULL) {
+ if (month_max_size == 0) {
+ compute_abbreviated_month_size();
+ }
+ if (month_max_size > 0) {
+ 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;
+ }
+ }
+ ret = strftime(str, len, format, tm);
+ return (ret);
+}
+
+static void
+printtime(const char *field, time_t ftime)
+{
+ char longstring[80];
+ char fmt[BUFSIZ];
+ 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));
+
+ snprintf(fmt, sizeof(fmt), "{d:%s/%%hs} ", field);
+ xo_attr("value", "%ld", (long) ftime);
+ xo_emit(fmt, longstring);
+ snprintf(fmt, sizeof(fmt), "{en:%s/%%ld}", field);
+ xo_emit(fmt, (long) ftime);
+}
+
+static int
+printtype(u_int mode)
+{
+
+ if (f_slash) {
+ if ((mode & S_IFMT) == S_IFDIR) {
+ xo_emit("{D:\\/}{e:type/directory}");
+ return (1);
+ }
+ return (0);
+ }
+
+ switch (mode & S_IFMT) {
+ case S_IFDIR:
+ xo_emit("{D:/\\/}{e:type/directory}");
+ return (1);
+ case S_IFIFO:
+ xo_emit("{D:|}{e:type/fifo}");
+ return (1);
+ case S_IFLNK:
+ xo_emit("{D:@}{e:type/link}");
+ return (1);
+ case S_IFSOCK:
+ xo_emit("{D:=}{e:type/socket}");
+ return (1);
+ case S_IFWHT:
+ xo_emit("{D:%%}{e:type/whiteout}");
+ return (1);
+ default:
+ break;
+ }
+ if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+ xo_emit("{D:*}{e:executable/}");
+ return (1);
+ }
+ return (0);
+}
+
+#ifdef COLORLS
+static int
+putch(int c)
+{
+ xo_emit("{D:/%c}", c);
+ return 0;
+}
+
+static int
+writech(int c)
+{
+ char tmp = (char)c;
+
+ (void)write(STDOUT_FILENO, &tmp, 1);
+ return 0;
+}
+
+static void
+printcolor(Colors c)
+{
+ char *ansiseq;
+
+ if (colors[c].bold)
+ tputs(enter_bold, 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
+endcolor(int sig)
+{
+ tputs(ansi_coloff, 1, sig ? writech : putch);
+ tputs(attrs_off, 1, sig ? writech : putch);
+}
+
+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 = 0;
+
+ 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) {
+ xo_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';
+ colors[i].bold = 1;
+ } else if (tolower((unsigned char)c[j]) == 'x')
+ colors[i].num[j] = -1;
+ else {
+ xo_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) {
+ xo_error("\nls: %s: %s\n", name, strerror(errno));
+ return;
+ }
+ path[lnklen] = '\0';
+ xo_emit(" -> ");
+ (void)printname("target", path);
+}
+
+static void
+printsize(const char *field, size_t width, off_t bytes)
+{
+ char fmt[BUFSIZ];
+
+ 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);
+ snprintf(fmt, sizeof(fmt), "{:%s/%%%ds} ", field, (int) width);
+ xo_attr("value", "%jd", (intmax_t) bytes);
+ xo_emit(fmt, buf);
+ } else { /* with commas */
+ /* This format assignment needed to work round gcc bug. */
+ snprintf(fmt, sizeof(fmt), "{:%s/%%%dj%sd} ",
+ field, (int) width, f_thousands ? "'" : "");
+ xo_emit(fmt, (intmax_t) 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) {
+ xo_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) {
+ xo_warn("%s", name);
+ return;
+ }
+ }
+ }
+ if (supports_acls == 0)
+ return;
+ facl = acl_get_link_np(name, type);
+ if (facl == NULL) {
+ xo_warn("%s", name);
+ return;
+ }
+ if (acl_is_trivial_np(facl, &trivial)) {
+ acl_free(facl);
+ xo_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 0000000..89a2e8c
--- /dev/null
+++ b/bin/ls/tests/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+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/ls_tests.sh b/bin/ls/tests/ls_tests.sh
new file mode 100755
index 0000000..3317876
--- /dev/null
+++ b/bin/ls/tests/ls_tests.sh
@@ -0,0 +1,958 @@
+#
+# 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.
+#
+# $FreeBSD$
+#
+
+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
+
+ 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 does nothing (compatibility flag)"
+}
+
+g_flag_body()
+{
+ create_test_inputs2
+ for file in $files; do
+ atf_check -e empty -o match:"$(ls -a $file)" -s exit:0 \
+ ls -ag $file
+ atf_check -e empty -o match:"$(ls -la $file)" -s exit:0 \
+ ls -alg $file
+ done
+}
+
+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.files" "/usr/bin/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 -ln 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"
+ atf_set "require.user" "root"
+}
+
+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 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 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 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 t_flag
+ atf_add_test_case u_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 0000000..b75cbec
--- /dev/null
+++ b/bin/ls/util.c
@@ -0,0 +1,248 @@
+/*-
+ * 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.
+ * 4. 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)util.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#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 <libxo/xo.h>
+
+#include "ls.h"
+#include "extern.h"
+
+int
+prn_normal(const char *field, const char *s)
+{
+ char fmt[_POSIX2_LINE_MAX];
+
+ snprintf(fmt, sizeof(fmt), "{:%s/%%hs}", field);
+ return xo_emit(fmt, s);
+#if 0
+ 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);
+#endif
+}
+
+char *
+get_printable(const char *s)
+{
+ mbstate_t mbs;
+ wchar_t wc;
+ int i, n;
+ size_t clen;
+ int slen = strlen(s);
+ char *buf = alloca(slen + 1), *bp = buf;
+
+ memset(&mbs, 0, sizeof(mbs));
+ n = 0;
+ while ((clen = mbrtowc(&wc, s, MB_LEN_MAX, &mbs)) != 0) {
+ if (clen == (size_t)-1) {
+ *bp++ = '?';
+ s++;
+ n++;
+ memset(&mbs, 0, sizeof(mbs));
+ continue;
+ }
+ if (clen == (size_t)-2) {
+ *bp++ = '?';
+ n++;
+ break;
+ }
+ if (!iswprint(wc)) {
+ *bp++ = '?';
+ s += clen;
+ n++;
+ continue;
+ }
+ for (i = 0; i < (int)clen; i++)
+ *bp++ = (unsigned char)s[i];
+ s += clen;
+ n += wcwidth(wc);
+ }
+ *bp = '\0';
+ return strdup(buf);
+}
+
+/*
+ * 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);
+}
+
+char *
+get_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;
+ int slen = strlen(s);
+ char *buf = alloca(slen * 4 + 1), *bp = buf;
+
+ 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++)
+ *bp++ = (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) {
+ *bp ++ = '\\';
+ *bp++ = 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];
+ *bp++ = '\\';
+ *bp++ = '0' + (ch >> 6);
+ *bp++ = '0' + ((ch >> 3) & 7);
+ *bp++ = '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;
+ }
+
+ *bp = '\0';
+ return strdup(buf);
+}
+
+void
+usage(void)
+{
+ xo_error(
+#ifdef COLORLS
+ "usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [-D format]"
+#else
+ "usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [-D format]"
+#endif
+ " [file ...]\n");
+ exit(1);
+}
OpenPOWER on IntegriCloud