1N/A/***********************************************************************
1N/A* *
1N/A* This software is part of the ast package *
1N/A* Copyright (c) 1989-2010 AT&T Intellectual Property *
1N/A* and is licensed under the *
1N/A* Common Public License, Version 1.0 *
1N/A* by AT&T Intellectual Property *
1N/A* *
1N/A* A copy of the License is available at *
1N/A* http://www.opensource.org/licenses/cpl1.0.txt *
1N/A* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
1N/A* *
1N/A* Information and Software Systems Research *
1N/A* AT&T Research *
1N/A* Florham Park NJ *
1N/A* *
1N/A* Glenn Fowler <gsf@research.att.com> *
1N/A* *
1N/A***********************************************************************/
1N/A#pragma prototyped
1N/A/*
1N/A * Glenn Fowler
1N/A * AT&T Research
1N/A *
1N/A * ls -- list file status
1N/A */
1N/A
1N/A#define TIME_ISO "%Q/%m-%d+%H:%M/%Y-%m-%d /"
1N/A#define TIME_LONG_ISO "%_K"
1N/A#define TIME_FULL_ISO "%_EK"
1N/A#define TIME_LOCALE "%c"
1N/A
1N/Astatic const char usage[] =
1N/A"[-?\n@(#)$Id: ls (AT&T Research) 2010-05-25 $\n]"
1N/AUSAGE_LICENSE
1N/A"[+NAME?ls - list files and/or directories]"
1N/A"[+DESCRIPTION?For each directory argument \bls\b lists the contents; for each"
1N/A" file argument the name and requested information are listed."
1N/A" The directory \b.\b is assumed if no file arguments appear."
1N/A" The listing is sorted by file name by default, except that file"
1N/A" arguments are listed before directories.]"
1N/A"[+?Multi-column terminal output display width is determined by \bioctl\b(2)"
1N/A" and/or the \bCOLUMNS\b environment variable.]"
1N/A"[+?\bgetconf PATH_RESOLVE\b determines how symbolic links are handled. This"
1N/A" can be explicitly overridden by the \b--logical\b, \b--metaphysical\b,"
1N/A" and \b--physical\b options below. \bPATH_RESOLVE\b can be one of:]{"
1N/A" [+logical?Follow all symbolic links.]"
1N/A" [+metaphysical?Follow command argument symbolic links,"
1N/A" otherwise don't follow.]"
1N/A" [+physical?Don't follow symbolic links.]"
1N/A"}"
1N/A
1N/A"[a:all?List entries starting with \b.\b; turns off \b--almost-all\b.]"
1N/A"[A:almost-all?List all entries but \b.\b and \b..\b; turns off \b--all\b.]"
1N/A"[b:escape?Print escapes for nongraphic characters.]"
1N/A"[B:ignore-backups?Do not list entries ending with ~.]"
1N/A"[c:ctime?Sort by change time; list ctime with \b--long\b.]"
1N/A"[C:multi-column?List entries by columns.]"
1N/A"[d:directory?List directory entries instead of contents.]"
1N/A"[D:define?Define \akey\a with optional \avalue\a. \avalue\a will be expanded"
1N/A" when \b%(\b\akey\a\b)\b is specified in \b--format\b. \akey\a may"
1N/A" override internal \b--format\b identifiers.]:[key[=value]]]"
1N/A"[e:long-iso|long-time?Equivalent to \b--long --time-style=long-iso\b.]"
1N/A"[E:full-iso|full-time?Equivalent to \b--long --time-style=full-iso\b.]"
1N/A"[f:force?Force each argument to be interpreted as a directory and list"
1N/A" the name found in each slot in the physical directory order. Turns"
1N/A" on \b-aU\b and turns off \b-lrst\b. The results are undefined for"
1N/A" non-directory arguments.]"
1N/A"[Z:format?Append to the listing format string. \aformat\a follows"
1N/A" \bprintf\b(3) conventions, except that \bsfio\b(3) inline ids"
1N/A" are used instead of arguments:"
1N/A" %[-+]][\awidth\a[.\aprecis\a[.\abase\a]]]]]](\aid\a[:\asubformat\a]])\achar\a."
1N/A" If \achar\a is \bs\b then the string form of the item is listed,"
1N/A" otherwise the corresponding numeric form is listed. \asubformat\a"
1N/A" overrides the default formatting for \aid\a. Supported \aid\as"
1N/A" and \asubformat\as are:]:[format]{"
1N/A" [+atime?access time]"
1N/A" [+blocks?size in blocks]"
1N/A" [+ctime?change time]"
1N/A" [+device?device number]"
1N/A" [+devmajor?major device number]"
1N/A" [+devminor?minor device number]"
1N/A" [+dir.blocks?directory blocks]"
1N/A" [+dir.bytes?directory size in bytes]"
1N/A" [+dir.count?directory entry count]"
1N/A" [+dir.files?directory file count]"
1N/A" [+flags?command line flags in effect]"
1N/A" [+gid?group id]"
1N/A" [+header?listing header]"
1N/A" [+ino?serial number]"
1N/A" [+linkop?link operation: => for symbolic, == for hard]"
1N/A" [+linkname?symbolic link text]"
1N/A" [+linkpath?symbolic link text]"
1N/A" [+mark?file or directory mark character]"
1N/A" [+markdir?directory mark character]"
1N/A" [+mode?access mode]"
1N/A" [+mtime?modification time]"
1N/A" [+name?entry name]"
1N/A" [+nlink?hard link count]"
1N/A" [+path?file path from original root dir]"
1N/A" [+perm?access permissions]"
1N/A" [+size?file size in bytes]"
1N/A" [+summary?listing summary info]"
1N/A" [+total.blocks?running total block count]"
1N/A" [+total.bytes?running total size in bytes]"
1N/A" [+total.files?running total file count]"
1N/A" [+trailer?listing trailer]"
1N/A" [+uid?owner id]"
1N/A" [+----?subformats ----]"
1N/A" [+case\b::\bp\b\a1\a::\bs\b\a1\a::...::\bp\b\an\a::\bs\b\an\a?Expands"
1N/A" to \bs\b\ai\a if the value of \aid\a matches the shell"
1N/A" pattern \bp\b\ai\a, or the empty string if there is no"
1N/A" match.]"
1N/A" [+mode?The integral value as a \bfmtmode\b(3) string.]"
1N/A" [+perm?The integral value as a \bfmtperm\b(3) string.]"
1N/A" [+time[=\aformat\a]]?The integral value as a \bstrftime\b(3)"
1N/A" string. For example,"
1N/A" \b--format=\"%8(mtime)u %(ctime:time=%H:%M:%S)s\"\b"
1N/A" lists the mtime in seconds since the epoch and the"
1N/A" ctime as hours:minutes:seconds.]"
1N/A" }"
1N/A"[F:classify?Append a character for typing each entry. Turns on \b--physical\b.]"
1N/A"[g:group?\b--long\b with no owner info.]"
1N/A"[G?\b--long\b with no group info.]"
1N/A"[h:scale|binary-scale|human-readable?Scale sizes to powers of 1024 { Ki Mi Gi Ti Pi Xi }.]"
1N/A"[i:inode?List the file serial number.]"
1N/A"[I:ignore?Do not list implied entries matching shell \apattern\a.]:[pattern]"
1N/A"[k:kilobytes?Use 1024 blocks instead of 512.]"
1N/A"[K:shell-quote?Enclose entry names in shell $'...' if necessary.]"
1N/A"[l:long|verbose?Use a long listing format.]"
1N/A"[m:commas|comma-list?List names as comma separated list.]"
1N/A"[n:numeric-uid-gid?List numeric user and group ids instead of names.]"
1N/A"[N:literal|show-controls-chars?Print raw entry names (don't treat e.g. control characters specially).]"
1N/A"[o:owner?\b--long\b with no group info.]"
1N/A"[O?\b--long\b with no owner info.]"
1N/A"[p:markdir?Append / to each directory name.]"
1N/A"[q:hide-control-chars?Print ? instead of non graphic characters.]"
1N/A"[Q:quote-name?Enclose all entry names in \"...\".]"
1N/A"[J:quote-style|quoting-style?Quote entry names according to \astyle\a:]:[style:=question]{"
1N/A" [c:C?C \"...\" quote.]"
1N/A" [e:escape?\b\\\b escape if necessary.]"
1N/A" [l:literal?No quoting.]"
1N/A" [q:question?Replace unprintable characters with \b?\b.]"
1N/A" [s:shell?Shell $'...' quote if necessary.]"
1N/A" [S:shell-always?Shell $'...' every name.]"
1N/A"}"
1N/A"[r:reverse?Reverse order while sorting.]"
1N/A"[R:recursive?List subdirectories recursively.]"
1N/A"[s:size?Print size of each file, in blocks.]"
1N/A"[S:bysize?Sort by file size.]"
1N/A"[t:?Sort by modification time; list mtime with \b--long\b.]"
1N/A"[T:tabsize?Ignored by this implementation.]#[columns]"
1N/A"[u:access?Sort by last access time; list atime with \b--long\b.]"
1N/A"[U?Equivalent to \b--sort=none\b.]"
1N/A"[V:colors|colours?\akey\a determines when color is used to distinguish"
1N/A" types:]:?[key:=never]{"
1N/A" [n:never?Never use color.]"
1N/A" [a:always?Always use color.]"
1N/A" [t:tty|auto?Use color when output is a tty.]"
1N/A"}"
1N/A"[w:width?Set the screen width to \ascreen-width\a and the screen height"
1N/A" to \ascreen-height\a if specified.]:[[screen-heightX]]screen-width]"
1N/A"[W:time?Display \akey\a time instead of the modification time:]:[key]{"
1N/A" [a:atime|access|use?access time]"
1N/A" [c:ctime|status?status change time]"
1N/A" [m:mtime|time?modify time]"
1N/A"}"
1N/A"[x:across?List entries by lines instead of by columns.]"
1N/A"[X:extension?Sort alphabetically by entry extension.]"
1N/A"[y:sort?Sort by \akey\a:]:?[key]{"
1N/A" [a:atime|access|use?Access time.]"
1N/A" [c:ctime|status?Status change time.]"
1N/A" [x:extension?File name extension.]"
1N/A" [m:mtime|time?Modify time.]"
1N/A" [f:name?File name.]"
1N/A" [n:none?Don't sort.]"
1N/A" [s:size|blocks?File size.]"
1N/A" [v:version?File name version.]"
1N/A"}"
1N/A"[Y:layout?Listing layout \akey\a:]:[key]{"
1N/A" [a:across|horizontal?Multi-column across the page.]"
1N/A" [c:comma?Comma separated names across the page.]"
1N/A" [l:long|verbose?Long listing.]"
1N/A" [v:multi-column|vertical?Multi-column by column.]"
1N/A" [1:single-column?One column down the page.]"
1N/A"}"
1N/A"[z:time-style?List the time according to \astyle\a:]:[style]{"
1N/A" [i:iso?Equivalent to \b+" TIME_ISO "\b.]"
1N/A" [10:posix-iso?No change for the C or posix locales, \biso\b otherwise.]"
1N/A" [f:full-iso?Equivalent to \b+" TIME_FULL_ISO "\b.]"
1N/A" [l:long-iso?Equivalent to \b+" TIME_LONG_ISO "\b.]"
1N/A" [11:posix-full-iso?No change for the C or posix locales, \bfull-iso\b"
1N/A" otherwise.]"
1N/A" [L:locale?Equivalent to \b+" TIME_LOCALE "\b.]"
1N/A" [12:+\aformat\a?A \bdate\b(1) +\aformat\a.]"
1N/A"}"
1N/A"[1:one-column?List one file per line.]"
1N/A"[L:logical|follow?Follow symbolic links. The default is determined by"
1N/A" \bgetconf PATH_RESOLVE\b.]"
1N/A"[H:metaphysical?Follow command argument symbolic links, otherwise don't"
1N/A" follow. The default is determined by \bgetconf PATH_RESOLVE\b.]"
1N/A"[P:physical?Don't follow symbolic links. The default is determined by"
1N/A" \bgetconf PATH_RESOLVE\b.]"
1N/A"[101:block-size?Use \ablocksize\a blocks.]#[blocksize]"
1N/A"[102:decimal-scale|thousands?Scale sizes to powers of 1000 { K M G T P X }.]"
1N/A"[103:dump?Print the generated \b--format\b string on the standard output"
1N/A" and exit.]"
1N/A"[104:testdate?\b--format\b time values newer than \adate\a will be printed"
1N/A" as \adate\a. Used for regression testing.]:[date]"
1N/A"[105:testsize?Shift file sizes left \ashift\a bits and set file block counts"
1N/A" to the file size divided by 512. Used for regression testing.]#[shift]"
1N/A
1N/A"\n"
1N/A"\n[ file ... ]\n"
1N/A"\n"
1N/A"[+SEE ALSO?\bchmod\b(1), \bfind\b(1), \bgetconf\b(1), \btw\b(1)]"
1N/A"[+BUGS?Can we add options to something else now?]"
1N/A;
1N/A
1N/A#include <ast.h>
1N/A#include <ls.h>
1N/A#include <ctype.h>
1N/A#include <error.h>
1N/A#include <ftwalk.h>
1N/A#include <sfdisc.h>
1N/A#include <hash.h>
1N/A#include <tmx.h>
1N/A
1N/A#define LS_ACROSS (LS_USER<<0) /* multi-column row order */
1N/A#define LS_ALL (LS_USER<<1) /* list all */
1N/A#define LS_ALWAYS (LS_USER<<2) /* always quote */
1N/A#define LS_COLUMNS (LS_USER<<3) /* multi-column column order */
1N/A#define LS_COMMAS (LS_USER<<4) /* comma separated name list */
1N/A#define LS_DIRECTORY (LS_USER<<5) /* list directories as files */
1N/A#define LS_ESCAPE (LS_USER<<6) /* C escape unprintable chars */
1N/A#define LS_EXTENSION (LS_USER<<7) /* sort by name extension */
1N/A#define LS_LABEL (LS_USER<<8) /* label for all dirs */
1N/A#define LS_MARKDIR (LS_USER<<9) /* marks dirs with / */
1N/A#define LS_MOST (LS_USER<<10) /* list all but . and .. */
1N/A#define LS_NOBACKUP (LS_USER<<11) /* omit *~ names */
1N/A#define LS_NOSTAT (LS_USER<<13) /* leaf FTW_NS ok */
1N/A#define LS_PRINTABLE (LS_USER<<14) /* ? for non-printable chars */
1N/A#define LS_QUOTE (LS_USER<<15) /* "..." file names */
1N/A#define LS_RECURSIVE (LS_USER<<16) /* recursive directory descent */
1N/A#define LS_SEPARATE (LS_USER<<17) /* dir header needs separator */
1N/A#define LS_SHELL (LS_USER<<18) /* $'...' file names */
1N/A#define LS_TIME (LS_USER<<19) /* sort by time */
1N/A
1N/A#define LS_STAT LS_NOSTAT
1N/A
1N/A#define VISIBLE(f) ((f)->level<=0||(!state.ignore||!strmatch((f)->name,state.ignore))&&(!(state.lsflags&LS_NOBACKUP)||(f)->name[(f)->namelen-1]!='~')&&((state.lsflags&LS_ALL)||(f)->name[0]!='.'||(state.lsflags&LS_MOST)&&((f)->name[1]&&(f)->name[1]!='.'||(f)->name[2])))
1N/A
1N/A#define BETWEEN 2 /* space between columns */
1N/A#define AFTER 1 /* space after last column */
1N/A
1N/A#define INVISIBLE (-1)
1N/A#define LISTED (-2)
1N/A
1N/A#define KEY_environ (-1)
1N/A
1N/A#define KEY_atime 1
1N/A#define KEY_blocks 2
1N/A#define KEY_ctime 3
1N/A#define KEY_device 4
1N/A#define KEY_devmajor 5
1N/A#define KEY_devminor 6
1N/A#define KEY_dir_blocks 7
1N/A#define KEY_dir_bytes 8
1N/A#define KEY_dir_count 9
1N/A#define KEY_dir_files 10
1N/A#define KEY_flags 11
1N/A#define KEY_gid 12
1N/A#define KEY_header 13
1N/A#define KEY_ino 14
1N/A#define KEY_linkop 15
1N/A#define KEY_linkpath 16
1N/A#define KEY_mark 17
1N/A#define KEY_markdir 18
1N/A#define KEY_mode 19
1N/A#define KEY_mtime 20
1N/A#define KEY_name 21
1N/A#define KEY_nlink 22
1N/A#define KEY_path 23
1N/A#define KEY_perm 24
1N/A#define KEY_size 25
1N/A#define KEY_summary 26
1N/A#define KEY_total_blocks 27
1N/A#define KEY_total_bytes 28
1N/A#define KEY_total_files 29
1N/A#define KEY_trailer 30
1N/A#define KEY_uid 31
1N/A
1N/A#if 0
1N/A#define BLOCKS(st) ((state.blocksize==LS_BLOCKSIZE)?iblocks(st):(state.blocksize>LS_BLOCKSIZE)?(iblocks(st)+state.blocksize/LS_BLOCKSIZE-1)/(state.blocksize/LS_BLOCKSIZE):iblocks(st)*(LS_BLOCKSIZE/state.blocksize))
1N/A#else
1N/A#define BLOCKS(st) ((state.blocksize==LS_BLOCKSIZE)?iblocks(st):(iblocks(st)*LS_BLOCKSIZE+state.blocksize-1)/state.blocksize)
1N/A#endif
1N/A#define PRINTABLE(s) ((state.lsflags&LS_PRINTABLE)?printable(s):(s))
1N/A
1N/Atypedef int (*Order_f)(Ftw_t*, Ftw_t*);
1N/A
1N/Atypedef struct /* dir/total counts */
1N/A{
1N/A Sfulong_t blocks; /* number of blocks */
1N/A Sfulong_t bytes; /* number of bytes */
1N/A Sfulong_t files; /* number of files */
1N/A} Count_t;
1N/A
1N/Atypedef struct /* sfkeyprintf() keys */
1N/A{
1N/A char* name; /* key name */
1N/A short index; /* index */
1N/A short disable; /* macro being expanded */
1N/A char* macro; /* macro definition */
1N/A} Key_t;
1N/A
1N/Atypedef struct /* list state */
1N/A{
1N/A Count_t count; /* directory counts */
1N/A Ftw_t* ftw; /* ftw info */
1N/A char* dirnam; /* pr() dirnam */
1N/A int dirlen; /* pr() dirlen */
1N/A} List_t;
1N/A
1N/Atypedef struct /* program state */
1N/A{
1N/A char flags[64]; /* command line option flags */
1N/A long ftwflags; /* FTW_* flags */
1N/A long lsflags; /* LS_* flags */
1N/A long timeflags; /* time LS_* flags */
1N/A long blocksize; /* file block size */
1N/A unsigned long directories; /* directory count */
1N/A unsigned long testdate; /* --format test date */
1N/A Count_t total; /* total counts */
1N/A int adjust; /* key() print with adjustment */
1N/A int comma; /* LS_COMMAS ftw.level crossing */
1N/A int height; /* output height in lines */
1N/A int reverse; /* reverse the sort */
1N/A int scale; /* metric scale power */
1N/A int testsize; /* st_size left shift */
1N/A int width; /* output width in chars */
1N/A char* endflags; /* trailing 0 in flags */
1N/A char* format; /* sfkeyprintf() format */
1N/A char* ignore; /* ignore files matching this */
1N/A char* timefmt; /* time list format */
1N/A Hash_table_t* keys; /* sfkeyprintf() keys */
1N/A Sfio_t* tmp; /* tmp string stream */
1N/A Ftw_t* top; /* top directory -- no label */
1N/A Order_f order; /* sort comparison function */
1N/A} State_t;
1N/A
1N/Astatic char DEF_header[] =
1N/A"%(dir.count:case;0;;1;%(path)s:\n;*;\n%(path)s:\n)s"
1N/A"%(flags:case;*d*;;*[ls]*;total %(dir.blocks)u\n)s"
1N/A;
1N/A
1N/Astatic Key_t keys[] =
1N/A{
1N/A { 0 },
1N/A { "atime", KEY_atime },
1N/A { "blocks", KEY_blocks },
1N/A { "ctime", KEY_ctime },
1N/A { "device", KEY_device },
1N/A { "devmajor", KEY_devmajor },
1N/A { "devminor", KEY_devminor },
1N/A { "dir.blocks", KEY_dir_blocks },
1N/A { "dir.bytes", KEY_dir_bytes },
1N/A { "dir.count", KEY_dir_count },
1N/A { "dir.files", KEY_dir_files },
1N/A { "flags", KEY_flags },
1N/A { "gid", KEY_gid },
1N/A { "header", KEY_header, 0, DEF_header },
1N/A { "ino", KEY_ino },
1N/A { "linkop", KEY_linkop },
1N/A { "linkpath", KEY_linkpath },
1N/A { "mark", KEY_mark },
1N/A { "markdir", KEY_markdir },
1N/A { "mode", KEY_mode },
1N/A { "mtime", KEY_mtime },
1N/A { "name", KEY_name },
1N/A { "nlink", KEY_nlink },
1N/A { "path", KEY_path },
1N/A { "perm", KEY_perm },
1N/A { "size", KEY_size },
1N/A { "summary", KEY_summary },
1N/A { "total.blocks", KEY_total_blocks },
1N/A { "total.bytes", KEY_total_bytes },
1N/A { "total.files", KEY_total_files },
1N/A { "trailer", KEY_trailer },
1N/A { "uid", KEY_uid },
1N/A
1N/A /* aliases */
1N/A
1N/A { "linkname", KEY_linkpath },
1N/A};
1N/A
1N/Astatic State_t state;
1N/A
1N/A/*
1N/A * return a copy of s with unprintable chars replaced by ?
1N/A */
1N/A
1N/Astatic char*
1N/Aprintable(register char* s)
1N/A{
1N/A register char* t;
1N/A register char* p;
1N/A register int c;
1N/A
1N/A static char* prdata;
1N/A static int prsize;
1N/A
1N/A if (state.lsflags & LS_ESCAPE)
1N/A {
1N/A if (!(state.lsflags & LS_QUOTE))
1N/A return fmtesc(s);
1N/A if (state.lsflags & LS_SHELL)
1N/A return fmtquote(s, "$'", "'", strlen(s), (state.lsflags & LS_ALWAYS) ? FMT_ALWAYS : 0);
1N/A return fmtquote(s, "\"", "\"", strlen(s), FMT_ALWAYS);
1N/A }
1N/A c = strlen(s) + 4;
1N/A if (c > prsize)
1N/A {
1N/A prsize = roundof(c, 512);
1N/A if (!(prdata = newof(prdata, char, prsize, 0)))
1N/A error(3, "out of space");
1N/A }
1N/A t = prdata;
1N/A if (state.lsflags & LS_QUOTE)
1N/A *t++ = '"';
1N/A if (!mbwide())
1N/A while (c = *s++)
1N/A *t++ = (iscntrl(c) || !isprint(c)) ? '?' : c;
1N/A else
1N/A for (p = s; c = mbchar(s);)
1N/A if (c < 0)
1N/A {
1N/A s++;
1N/A *t++ = '?';
1N/A }
1N/A else if (mbwidth(c) <= 0)
1N/A *t++ = '?';
1N/A else
1N/A while (p < s)
1N/A *t++ = *p++;
1N/A if (state.lsflags & LS_QUOTE)
1N/A *t++ = '"';
1N/A *t = 0;
1N/A return prdata;
1N/A}
1N/A
1N/A/*
1N/A * sfkeyprintf() lookup
1N/A */
1N/A
1N/Astatic int
1N/Akey(void* handle, register Sffmt_t* fp, const char* arg, char** ps, Sflong_t* pn)
1N/A{
1N/A register Ftw_t* ftw;
1N/A register struct stat* st;
1N/A register char* s = 0;
1N/A register Sflong_t n = 0;
1N/A register Key_t* kp;
1N/A List_t* lp;
1N/A Time_t t;
1N/A
1N/A static Sfio_t* mp;
1N/A static const char fmt_mode[] = "mode";
1N/A static const char fmt_perm[] = "perm";
1N/A static const char fmt_time[] = "time";
1N/A
1N/A if (!fp->t_str)
1N/A return 0;
1N/A if (lp = (List_t*)handle)
1N/A {
1N/A ftw = lp->ftw;
1N/A st = &ftw->statb;
1N/A }
1N/A else
1N/A {
1N/A ftw = 0;
1N/A st = 0;
1N/A }
1N/A t = TMX_NOTIME;
1N/A if (!(kp = (Key_t*)hashget(state.keys, fp->t_str)))
1N/A {
1N/A if (*fp->t_str != '$')
1N/A {
1N/A error(3, "%s: unknown format key", fp->t_str);
1N/A return 0;
1N/A }
1N/A if (!(kp = newof(0, Key_t, 1, 0)))
1N/A error(3, "out of space");
1N/A kp->name = hashput(state.keys, 0, kp);
1N/A kp->macro = getenv(fp->t_str + 1);
1N/A kp->index = KEY_environ;
1N/A kp->disable = 1;
1N/A }
1N/A if (kp->macro && !kp->disable)
1N/A {
1N/A kp->disable = 1;
1N/A if (!mp && !(mp = sfstropen()))
1N/A error(3, "out of space");
1N/A sfkeyprintf(mp, handle, kp->macro, key, NiL);
1N/A if (!(s = sfstruse(mp)))
1N/A error(3, "out of space");
1N/A kp->disable = 0;
1N/A }
1N/A else switch (kp->index)
1N/A {
1N/A case KEY_atime:
1N/A if (st)
1N/A {
1N/A n = st->st_atime;
1N/A t = tmxgetatime(st);
1N/A }
1N/A if (!arg)
1N/A arg = state.timefmt;
1N/A break;
1N/A case KEY_blocks:
1N/A if (st)
1N/A n = BLOCKS(st);
1N/A break;
1N/A case KEY_ctime:
1N/A if (st)
1N/A {
1N/A n = st->st_ctime;
1N/A t = tmxgetctime(st);
1N/A }
1N/A if (!arg)
1N/A arg = state.timefmt;
1N/A break;
1N/A case KEY_device:
1N/A if (st && (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)))
1N/A s = fmtdev(st);
1N/A else
1N/A return 0;
1N/A break;
1N/A case KEY_devmajor:
1N/A if (st)
1N/A n = (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) ? major(idevice(st)) : major(st->st_dev);
1N/A break;
1N/A case KEY_devminor:
1N/A if (st)
1N/A n = (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) ? minor(idevice(st)) : minor(st->st_dev);
1N/A break;
1N/A case KEY_dir_blocks:
1N/A if (!state.scale)
1N/A {
1N/A if (lp)
1N/A n = lp->count.blocks;
1N/A break;
1N/A }
1N/A /*FALLTHROUGH*/
1N/A case KEY_dir_bytes:
1N/A if (lp)
1N/A n = lp->count.bytes;
1N/A if (state.scale)
1N/A {
1N/A s = fmtscale(n, state.scale);
1N/A fp->fmt = 's';
1N/A }
1N/A break;
1N/A case KEY_dir_count:
1N/A if (ftw != state.top)
1N/A {
1N/A if (state.lsflags & LS_SEPARATE)
1N/A n = state.directories;
1N/A else if (state.lsflags & LS_LABEL)
1N/A n = 1;
1N/A }
1N/A break;
1N/A case KEY_dir_files:
1N/A if (lp)
1N/A n = lp->count.files;
1N/A break;
1N/A case KEY_environ:
1N/A if (!(s = kp->macro))
1N/A return 0;
1N/A break;
1N/A case KEY_flags:
1N/A s = state.flags;
1N/A break;
1N/A case KEY_gid:
1N/A if (st)
1N/A {
1N/A if (fp->fmt == 's')
1N/A s = fmtgid(st->st_gid);
1N/A else
1N/A n = st->st_gid;
1N/A }
1N/A break;
1N/A case KEY_ino:
1N/A if (st)
1N/A n = st->st_ino;
1N/A break;
1N/A case KEY_linkpath:
1N/A if (ftw && ftw->info == FTW_SL)
1N/A {
1N/A char* dirnam;
1N/A int c;
1N/A
1N/A static char* txtdata;
1N/A static int txtsize;
1N/A
1N/A if ((st->st_size + 1) > txtsize)
1N/A {
1N/A txtsize = roundof(st->st_size + 1, 512);
1N/A if (!(txtdata = newof(txtdata, char, txtsize, 0)))
1N/A error(3, "out of space");
1N/A }
1N/A if (*ftw->name == '/' || !lp->dirnam)
1N/A dirnam = ftw->name;
1N/A else
1N/A {
1N/A sfprintf(state.tmp, "%s/%s", lp->dirnam + streq(lp->dirnam, "/"), ftw->name);
1N/A if (!(dirnam = sfstruse(state.tmp)))
1N/A error(3, "out of space");
1N/A }
1N/A c = pathgetlink(dirnam, txtdata, txtsize);
1N/A if (c > 0)
1N/A s = PRINTABLE(txtdata);
1N/A }
1N/A else
1N/A return 0;
1N/A break;
1N/A case KEY_linkop:
1N/A if (ftw && ftw->info == FTW_SL)
1N/A s = "->";
1N/A else
1N/A return 0;
1N/A break;
1N/A case KEY_mark:
1N/A if (!st)
1N/A return 0;
1N/A else if (S_ISLNK(st->st_mode))
1N/A s = "@";
1N/A else if (S_ISDIR(st->st_mode))
1N/A s = "/";
1N/A#ifdef S_ISDOOR
1N/A else if (S_ISDOOR(st->st_mode))
1N/A s = ">";
1N/A#endif
1N/A else if (S_ISFIFO(st->st_mode))
1N/A s = "|";
1N/A#ifdef S_ISSOCK
1N/A else if (S_ISSOCK(st->st_mode))
1N/A s = "=";
1N/A#endif
1N/A else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode))
1N/A s = "$";
1N/A else if (st->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
1N/A s = "*";
1N/A else
1N/A return 0;
1N/A break;
1N/A case KEY_markdir:
1N/A if (!st || !S_ISDIR(st->st_mode))
1N/A return 0;
1N/A s = "/";
1N/A break;
1N/A case KEY_mode:
1N/A if (st)
1N/A n = st->st_mode;
1N/A if (!arg)
1N/A arg = fmt_mode;
1N/A break;
1N/A case KEY_mtime:
1N/A if (st)
1N/A {
1N/A n = st->st_mtime;
1N/A t = tmxgetmtime(st);
1N/A }
1N/A if (!arg)
1N/A arg = state.timefmt;
1N/A break;
1N/A case KEY_name:
1N/A if (ftw)
1N/A s = PRINTABLE(ftw->name);
1N/A break;
1N/A case KEY_nlink:
1N/A if (st)
1N/A n = st->st_nlink;
1N/A break;
1N/A case KEY_path:
1N/A if (ftw)
1N/A s = ftw->path ? PRINTABLE(ftw->path) : PRINTABLE(ftw->name);
1N/A break;
1N/A case KEY_perm:
1N/A if (st)
1N/A n = st->st_mode & S_IPERM;
1N/A if (!arg)
1N/A arg = fmt_perm;
1N/A break;
1N/A case KEY_size:
1N/A if (st)
1N/A {
1N/A n = st->st_size;
1N/A if (state.scale)
1N/A {
1N/A s = fmtscale(n, state.scale);
1N/A fp->fmt = 's';
1N/A }
1N/A }
1N/A break;
1N/A case KEY_total_blocks:
1N/A if (!state.scale)
1N/A {
1N/A n = state.total.blocks;
1N/A break;
1N/A }
1N/A /*FALLTHROUGH*/
1N/A case KEY_total_bytes:
1N/A n = state.total.bytes;
1N/A if (state.scale)
1N/A {
1N/A s = fmtscale(n, state.scale);
1N/A fp->fmt = 's';
1N/A }
1N/A break;
1N/A case KEY_total_files:
1N/A n = state.total.files;
1N/A break;
1N/A case KEY_uid:
1N/A if (st)
1N/A {
1N/A if (fp->fmt == 's')
1N/A s = fmtuid(st->st_uid);
1N/A else
1N/A n = st->st_uid;
1N/A }
1N/A break;
1N/A default:
1N/A return 0;
1N/A }
1N/A if (s)
1N/A {
1N/A *ps = s;
1N/A if (mbwide())
1N/A {
1N/A register char* p;
1N/A int w;
1N/A int i;
1N/A
1N/A for (p = s; w = mbchar(s); p = s)
1N/A if (w < 0)
1N/A s++;
1N/A else if ((i = mbwidth(w)) >= 0)
1N/A state.adjust -= (s - p) + i - 2;
1N/A }
1N/A }
1N/A else if (fp->fmt == 's' && arg)
1N/A {
1N/A if (strneq(arg, fmt_mode, sizeof(fmt_mode) - 1))
1N/A *ps = fmtmode(n, 0);
1N/A else if (strneq(arg, fmt_perm, sizeof(fmt_perm) - 1))
1N/A *ps = fmtperm(n & S_IPERM);
1N/A else
1N/A {
1N/A if (strneq(arg, fmt_time, sizeof(fmt_time) - 1))
1N/A {
1N/A arg += sizeof(fmt_time) - 1;
1N/A if (*arg == '=')
1N/A arg++;
1N/A }
1N/A if (!*arg)
1N/A arg = state.timefmt;
1N/A if ((unsigned long)n >= state.testdate)
1N/A {
1N/A n = state.testdate;
1N/A t = TMX_NOTIME;
1N/A }
1N/A *ps = t == TMX_NOTIME ? fmttime(arg, (time_t)n) : fmttmx(arg, t);
1N/A }
1N/A }
1N/A else
1N/A *pn = n;
1N/A return 1;
1N/A}
1N/A
1N/A/*
1N/A * print info on a single file
1N/A * parent directory name is dirnam of dirlen chars
1N/A */
1N/A
1N/Astatic void
1N/Apr(register List_t* lp, Ftw_t* ftw, register int fill)
1N/A{
1N/A if (state.testsize)
1N/A {
1N/A ftw->statb.st_size <<= state.testsize;
1N/A ftw->statb.st_blocks = ftw->statb.st_size / LS_BLOCKSIZE;
1N/A }
1N/A#ifdef S_ISLNK
1N/A /*
1N/A * -H == --hairbrained
1N/A * no way around it - this is bud tugley
1N/A * symlinks should be no more visible than mount points
1N/A * but I wear my user hat more than my administrator hat
1N/A */
1N/A
1N/A if (ftw->level == 0 && (state.ftwflags & (FTW_META|FTW_PHYSICAL)) == (FTW_META|FTW_PHYSICAL) && !(ftw->info & FTW_D) && !lstat(ftw->path ? ftw->path : ftw->name, &ftw->statb) && S_ISLNK(ftw->statb.st_mode))
1N/A ftw->info = FTW_SL;
1N/A#endif
1N/A lp->ftw = ftw;
1N/A state.adjust = 0;
1N/A fill -= sfkeyprintf(sfstdout, lp, state.format, key, NiL) + state.adjust;
1N/A if (!(state.lsflags & LS_COMMAS))
1N/A {
1N/A if (fill > 0)
1N/A while (fill-- > 0)
1N/A sfputc(sfstdout, ' ');
1N/A else
1N/A sfputc(sfstdout, '\n');
1N/A }
1N/A}
1N/A
1N/A/*
1N/A * pr() ftw directory child list in column order
1N/A * directory name is dirnam of dirlen chars
1N/A * count is the number of VISIBLE children
1N/A * length is the length of the longest VISIBLE child
1N/A */
1N/A
1N/Astatic void
1N/Acol(register List_t* lp, register Ftw_t* ftw, int length)
1N/A{
1N/A register Ftw_t* p;
1N/A register int i;
1N/A register int n;
1N/A register int files;
1N/A register char* s;
1N/A int w;
1N/A int a;
1N/A
1N/A lp->ftw = ftw;
1N/A if (keys[KEY_header].macro && ftw->level >= 0)
1N/A sfkeyprintf(sfstdout, lp, keys[KEY_header].macro, key, NiL);
1N/A if ((files = lp->count.files) > 0)
1N/A {
1N/A if (!(state.lsflags & LS_COLUMNS) || length <= 0)
1N/A {
1N/A n = w = 1;
1N/A a = 0;
1N/A }
1N/A else
1N/A {
1N/A i = ftw->name[1];
1N/A ftw->name[1] = 0;
1N/A state.adjust = 2;
1N/A a = sfkeyprintf(state.tmp, lp, state.format, key, NiL) - 1;
1N/A w = a + state.adjust + 1;
1N/A length += w;
1N/A sfstrseek(state.tmp, 0, SEEK_SET);
1N/A ftw->name[1] = i;
1N/A n = ((state.width - (length + BETWEEN + 2)) < 0) ? 1 : 2;
1N/A }
1N/A if (state.lsflags & LS_COMMAS)
1N/A {
1N/A length = w - 1;
1N/A i = 0;
1N/A n = state.width;
1N/A for (p = ftw->link; p; p = p->link)
1N/A if (p->local.number != INVISIBLE)
1N/A {
1N/A if (!mbwide())
1N/A w = p->namelen;
1N/A else
1N/A for (s = p->name, w = 0; i = mbchar(s);)
1N/A if (i < 0)
1N/A {
1N/A s++;
1N/A w++;
1N/A }
1N/A else if ((n = mbwidth(i)) > 0)
1N/A w += n;
1N/A w += a;
1N/A if ((n -= length + w) < 0)
1N/A {
1N/A n = state.width - (length + w);
1N/A if (i)
1N/A sfputr(sfstdout, ",\n", -1);
1N/A }
1N/A else if (i)
1N/A sfputr(sfstdout, ", ", -1);
1N/A pr(lp, p, 0);
1N/A i = 1;
1N/A }
1N/A if (i)
1N/A sfputc(sfstdout, '\n');
1N/A }
1N/A else if (n <= 1)
1N/A {
1N/A for (p = ftw->link; p; p = p->link)
1N/A if (p->local.number != INVISIBLE)
1N/A pr(lp, p, 0);
1N/A }
1N/A else
1N/A {
1N/A register Ftw_t** x;
1N/A int c;
1N/A int j;
1N/A int k;
1N/A int l;
1N/A int m;
1N/A int o;
1N/A int q;
1N/A int r;
1N/A int w;
1N/A int z;
1N/A
1N/A static unsigned short* siz;
1N/A static int sizsiz;
1N/A
1N/A static Ftw_t** vec;
1N/A static int vecsiz;
1N/A
1N/A if (files > sizsiz)
1N/A {
1N/A sizsiz = roundof(files, 64);
1N/A if (!(siz = newof(siz, unsigned short, sizsiz, 0)))
1N/A error(3, "out of space");
1N/A }
1N/A if (files > (vecsiz - 1))
1N/A {
1N/A vecsiz = roundof(files + 1, 64);
1N/A if (!(vec = newof(vec, Ftw_t*, vecsiz, 0)))
1N/A error(3, "out of space");
1N/A }
1N/A x = vec;
1N/A i = 0;
1N/A for (p = ftw->link; p; p = p->link)
1N/A if (p->local.number != INVISIBLE)
1N/A x[i++] = p;
1N/A n = i / (state.width / (length + BETWEEN)) + 1;
1N/A o = 0;
1N/A if ((state.lsflags & LS_ACROSS) && n > 1)
1N/A {
1N/A c = (i - 1) / n + 1;
1N/A do
1N/A {
1N/A w = -AFTER;
1N/A for (j = 0; j < c; j++)
1N/A {
1N/A z = 0;
1N/A for (l = 0, r = j; l < n && r < i; r += c, l++)
1N/A if (z < (x[r]->namelen + a))
1N/A z = x[r]->namelen + a;
1N/A w += z + BETWEEN;
1N/A }
1N/A if (w <= state.width)
1N/A o = n;
1N/A } while (c < state.width / 2 && (n = (i + c) / (c + 1)) && ++c);
1N/A n = o ? o : 1;
1N/A c = (i - 1) / n + 1;
1N/A k = 0;
1N/A for (j = 0; j < c; j++)
1N/A {
1N/A siz[k] = 0;
1N/A for (l = 0, r = j; l < n && r < i; r += c, l++)
1N/A if (siz[k] < x[r]->namelen)
1N/A siz[k] = x[r]->namelen;
1N/A siz[k] += a + BETWEEN;
1N/A k++;
1N/A }
1N/A for (j = 0; j <= i; j += c)
1N/A for (l = 0, w = j; l < k && w < i; l++, w++)
1N/A pr(lp, x[w], l < (k - 1) && w < (i - 1) ? siz[l] : 0);
1N/A }
1N/A else
1N/A {
1N/A o = 0;
1N/A if (n > 1)
1N/A {
1N/A if (!(q = i / n))
1N/A q = 1;
1N/A for (c = q; (c - q) < 2 && c <= state.width / (BETWEEN + 1); ++c)
1N/A {
1N/A n = m = (i + c - 1) / c;
1N/A if ((r = i - m * c) > state.height)
1N/A n -= (r + c - 1) / c;
1N/A for (; n <= m; n++)
1N/A {
1N/A w = -AFTER;
1N/A j = 0;
1N/A while (j < i)
1N/A {
1N/A z = 0;
1N/A for (l = 0; l < n && j < i; j++, l++)
1N/A if (z < x[j]->namelen)
1N/A z = x[j]->namelen;
1N/A w += z + a + BETWEEN;
1N/A }
1N/A if (w <= state.width)
1N/A {
1N/A q = c;
1N/A o = n;
1N/A break;
1N/A }
1N/A }
1N/A }
1N/A }
1N/A n = o ? o : 1;
1N/A j = k = 0;
1N/A while (j < i)
1N/A {
1N/A siz[k] = 0;
1N/A for (l = 0; l < n && j < i; j++, l++)
1N/A if (siz[k] < x[j]->namelen)
1N/A siz[k] = x[j]->namelen;
1N/A siz[k] += a + BETWEEN;
1N/A k++;
1N/A }
1N/A for (j = 0; j < n; j++)
1N/A for (l = 0, w = j; l < k && w < i; l++, w += n)
1N/A pr(lp, x[w], l < (k - 1) && w < (i - n) ? siz[l] : 0);
1N/A }
1N/A }
1N/A }
1N/A if (keys[KEY_trailer].macro && ftw->level >= 0)
1N/A sfkeyprintf(sfstdout, lp, keys[KEY_trailer].macro, key, NiL);
1N/A}
1N/A
1N/A/*
1N/A * order() helpers
1N/A */
1N/A
1N/Astatic int
1N/Aorder_none(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A return 0;
1N/A}
1N/A
1N/Astatic int
1N/Aorder_blocks(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A if (f1->statb.st_size < f2->statb.st_size)
1N/A return 1;
1N/A if (f1->statb.st_size > f2->statb.st_size)
1N/A return -1;
1N/A return 0;
1N/A}
1N/A
1N/Astatic int
1N/Aorder_atime(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A Time_t t1;
1N/A Time_t t2;
1N/A
1N/A t1 = tmxgetatime(&f1->statb);
1N/A t2 = tmxgetatime(&f2->statb);
1N/A if (t1 < t2)
1N/A return 1;
1N/A if (t1 > t2)
1N/A return -1;
1N/A return 0;
1N/A}
1N/A
1N/Astatic int
1N/Aorder_ctime(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A Time_t t1;
1N/A Time_t t2;
1N/A
1N/A t1 = tmxgetctime(&f1->statb);
1N/A t2 = tmxgetctime(&f2->statb);
1N/A if (t1 < t2)
1N/A return 1;
1N/A if (t1 > t2)
1N/A return -1;
1N/A return 0;
1N/A}
1N/A
1N/Astatic int
1N/Aorder_mtime(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A Time_t t1;
1N/A Time_t t2;
1N/A
1N/A t1 = tmxgetmtime(&f1->statb);
1N/A t2 = tmxgetmtime(&f2->statb);
1N/A if (t1 < t2)
1N/A return 1;
1N/A if (t1 > t2)
1N/A return -1;
1N/A return 0;
1N/A}
1N/A
1N/Astatic int
1N/Aorder_extension(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A register int n;
1N/A char* x1;
1N/A char* x2;
1N/A
1N/A x1 = strrchr(f1->name, '.');
1N/A x2 = strrchr(f2->name, '.');
1N/A if (x1)
1N/A {
1N/A if (x2)
1N/A n = strcoll(x1, x2);
1N/A else
1N/A n = 1;
1N/A }
1N/A else if (x2)
1N/A n = -1;
1N/A else
1N/A n = 0;
1N/A if (!n)
1N/A n = strcoll(f1->name, f2->name);
1N/A return n;
1N/A}
1N/A
1N/Astatic int
1N/Aorder_version(Ftw_t* f1, Ftw_t* f2)
1N/A{
1N/A return strvcmp(f1->name, f2->name);
1N/A}
1N/A
1N/Astatic int
1N/Aorder_name(Ftw_t* f1, Ftw_t* f2)
1N/A{
1N/A return strcoll(f1->name, f2->name);
1N/A}
1N/A
1N/A/*
1N/A * order child entries
1N/A */
1N/A
1N/Astatic int
1N/Aorder(register Ftw_t* f1, register Ftw_t* f2)
1N/A{
1N/A int n;
1N/A
1N/A if (!(state.lsflags & LS_DIRECTORY) && (state.ftwflags & FTW_MULTIPLE) && f1->level == 0)
1N/A {
1N/A if (f1->info == FTW_D)
1N/A {
1N/A if (f2->info != FTW_D)
1N/A return 1;
1N/A }
1N/A else if (f2->info == FTW_D)
1N/A return -1;
1N/A }
1N/A n = (*state.order)(f1, f2);
1N/A return state.reverse ? -n : n;
1N/A}
1N/A
1N/A/*
1N/A * list a directory and its children
1N/A */
1N/A
1N/Astatic void
1N/Adir(register Ftw_t* ftw)
1N/A{
1N/A register Ftw_t* p;
1N/A register int length;
1N/A int top = 0;
1N/A List_t list;
1N/A
1N/A if (ftw->status == FTW_NAME)
1N/A {
1N/A list.dirlen = ftw->namelen;
1N/A list.dirnam = ftw->path + ftw->pathlen - list.dirlen;
1N/A }
1N/A else
1N/A {
1N/A list.dirlen = ftw->pathlen;
1N/A list.dirnam = ftw->path;
1N/A }
1N/A if (ftw->level >= 0)
1N/A state.directories++;
1N/A else
1N/A state.top = ftw;
1N/A length = 0;
1N/A list.count.blocks = 0;
1N/A list.count.bytes = 0;
1N/A list.count.files = 0;
1N/A for (p = ftw->link; p; p = p->link)
1N/A {
1N/A if (p->level == 0 && p->info == FTW_D && !(state.lsflags & LS_DIRECTORY))
1N/A {
1N/A p->local.number = INVISIBLE;
1N/A top++;
1N/A }
1N/A else if (VISIBLE(p))
1N/A {
1N/A if (p->info == FTW_NS)
1N/A {
1N/A if (ftw->level < 0 || !(state.lsflags & LS_NOSTAT))
1N/A {
1N/A if (ftw->path[0] == '.' && !ftw->path[1])
1N/A error(2, "%s: not found", p->name);
1N/A else
1N/A error(2, "%s/%s: not found", ftw->path, p->name);
1N/A goto invisible;
1N/A }
1N/A }
1N/A else
1N/A {
1N/A list.count.blocks += BLOCKS(&p->statb);
1N/A list.count.bytes += p->statb.st_size;
1N/A }
1N/A list.count.files++;
1N/A if (p->namelen > length)
1N/A length = p->namelen;
1N/A if (!(state.lsflags & LS_RECURSIVE))
1N/A p->status = FTW_SKIP;
1N/A }
1N/A else
1N/A {
1N/A invisible:
1N/A p->local.number = INVISIBLE;
1N/A p->status = FTW_SKIP;
1N/A }
1N/A }
1N/A state.total.blocks += list.count.blocks;
1N/A state.total.bytes += list.count.bytes;
1N/A state.total.files += list.count.files;
1N/A col(&list, ftw, length);
1N/A state.lsflags |= LS_SEPARATE;
1N/A if (top)
1N/A {
1N/A if (list.count.files)
1N/A {
1N/A state.directories++;
1N/A state.top = 0;
1N/A }
1N/A else if (top > 1)
1N/A state.top = 0;
1N/A else
1N/A state.top = ftw->link;
1N/A for (p = ftw->link; p; p = p->link)
1N/A if (p->level == 0 && p->info == FTW_D)
1N/A p->local.number = 0;
1N/A }
1N/A}
1N/A
1N/A/*
1N/A * list info on a single file
1N/A */
1N/A
1N/Astatic int
1N/Als(register Ftw_t* ftw)
1N/A{
1N/A if (!VISIBLE(ftw))
1N/A {
1N/A ftw->status = FTW_SKIP;
1N/A return 0;
1N/A }
1N/A switch (ftw->info)
1N/A {
1N/A case FTW_NS:
1N/A if (ftw->parent->info == FTW_DNX)
1N/A break;
1N/A error(2, "%s: not found", ftw->path);
1N/A return 0;
1N/A case FTW_DC:
1N/A if (state.lsflags & LS_DIRECTORY)
1N/A break;
1N/A error(2, "%s: directory causes cycle", ftw->path);
1N/A return 0;
1N/A case FTW_DNR:
1N/A if (state.lsflags & LS_DIRECTORY)
1N/A break;
1N/A error(2, "%s: cannot read directory", ftw->path);
1N/A return 0;
1N/A case FTW_D:
1N/A case FTW_DNX:
1N/A if ((state.lsflags & LS_DIRECTORY) && ftw->level >= 0)
1N/A break;
1N/A if (!(state.lsflags & LS_RECURSIVE))
1N/A ftw->status = FTW_SKIP;
1N/A else if (ftw->info == FTS_DNX)
1N/A {
1N/A error(2, "%s: cannot search directory", ftw->path, ftw->level);
1N/A ftw->status = FTW_SKIP;
1N/A if (ftw->level > 0 && !(state.lsflags & LS_NOSTAT))
1N/A return 0;
1N/A }
1N/A dir(ftw);
1N/A return 0;
1N/A }
1N/A ftw->status = FTW_SKIP;
1N/A if (!ftw->level)
1N/A {
1N/A static List_t list;
1N/A
1N/A list.ftw = ftw;
1N/A pr(&list, ftw, 0);
1N/A }
1N/A return 0;
1N/A}
1N/A
1N/A#define set(f) (opt_info.num?(state.lsflags|=(f)):((state.lsflags&=~(f)),0))
1N/A#define clr(f) (opt_info.num?(state.lsflags&=~(f)):(state.lsflags|=(f)))
1N/A
1N/Aint
1N/Ab_ls(int argc, register char** argv)
1N/A{
1N/A register int n;
1N/A register char* s;
1N/A char* e;
1N/A Key_t* kp;
1N/A Sfio_t* fmt;
1N/A long lsflags;
1N/A int dump = 0;
1N/A
1N/A static char fmt_color[] = "%(mode:case:d*:\\E[01;34m%(name)s\\E[0m:l*:\\E[01;36m%(name)s\\E[0m:*x*:\\E[01;32m%(name)s\\E[0m:*:%(name)s)s";
1N/A
1N/A NoP(argc);
1N/A setlocale(LC_ALL, "");
1N/A if (s = strrchr(argv[0], '/'))
1N/A s++;
1N/A else
1N/A s = argv[0];
1N/A error_info.id = s;
1N/A state.ftwflags = ftwflags() | FTW_CHILDREN;
1N/A if (!(fmt = sfstropen()) || !(state.tmp = sfstropen()))
1N/A error(3, "out of space");
1N/A if (!(state.keys = hashalloc(NiL, HASH_name, "keys", 0)))
1N/A error(3, "out of space");
1N/A for (n = 1; n < elementsof(keys); n++)
1N/A hashput(state.keys, keys[n].name, &keys[keys[n].index]);
1N/A hashset(state.keys, HASH_ALLOCATE);
1N/A if (streq(s, "lc"))
1N/A state.lsflags |= LS_COLUMNS;
1N/A else if (streq(s, "lf") || streq(s, "lsf"))
1N/A state.lsflags |= LS_MARK;
1N/A else if (streq(s, "ll"))
1N/A state.lsflags |= LS_LONG;
1N/A else if (streq(s, "lsr"))
1N/A state.lsflags |= LS_RECURSIVE;
1N/A else if (streq(s, "lsx"))
1N/A state.lsflags |= LS_ACROSS|LS_COLUMNS;
1N/A else if (isatty(1))
1N/A {
1N/A state.lsflags |= LS_COLUMNS;
1N/A if (!strmatch(setlocale(LC_ALL, NiL), "*[Uu][Tt][Ff]?(-)8"))
1N/A state.lsflags |= LS_PRINTABLE;
1N/A }
1N/A state.endflags = state.flags;
1N/A state.blocksize = 512;
1N/A state.testdate = ~0;
1N/A state.timefmt = "%?%l";
1N/A lsflags = state.lsflags;
1N/A while (n = optget(argv, usage))
1N/A {
1N/A switch (n)
1N/A {
1N/A case 'a':
1N/A set(LS_ALL);
1N/A break;
1N/A case 'b':
1N/A set(LS_PRINTABLE|LS_ESCAPE);
1N/A break;
1N/A case 'c':
1N/A state.lsflags &= ~LS_ATIME;
1N/A state.lsflags |= LS_CTIME;
1N/A if (!state.order)
1N/A state.order = order_ctime;
1N/A break;
1N/A case 'd':
1N/A set(LS_DIRECTORY);
1N/A break;
1N/A case 'e':
1N/A state.lsflags |= LS_LONG;
1N/A state.timefmt = TIME_LONG_ISO;
1N/A break;
1N/A case 'f':
1N/A state.lsflags |= LS_ALL;
1N/A state.lsflags &= ~(LS_BLOCKS|LS_LONG|LS_TIME);
1N/A state.reverse = 0;
1N/A state.order = order_none;
1N/A break;
1N/A case 'g':
1N/A case 'O':
1N/A if (opt_info.num)
1N/A state.lsflags |= LS_LONG|LS_NOUSER;
1N/A else
1N/A state.lsflags |= LS_LONG|LS_NOGROUP;
1N/A break;
1N/A case 'h':
1N/A state.scale = 1024;
1N/A break;
1N/A case 'i':
1N/A set(LS_INUMBER);
1N/A break;
1N/A case 'k':
1N/A state.blocksize = 1024;
1N/A break;
1N/A case 'l':
1N/A set(LS_LONG);
1N/A break;
1N/A case 'm':
1N/A set(LS_COMMAS);
1N/A break;
1N/A case 'n':
1N/A set(LS_NUMBER);
1N/A break;
1N/A case 'o':
1N/A case 'G':
1N/A if (opt_info.num)
1N/A state.lsflags |= LS_LONG|LS_NOGROUP;
1N/A else
1N/A state.lsflags |= LS_LONG|LS_NOUSER;
1N/A break;
1N/A case 'p':
1N/A set(LS_MARKDIR);
1N/A break;
1N/A case 'q':
1N/A set(LS_PRINTABLE);
1N/A break;
1N/A case 'r':
1N/A state.reverse = !!opt_info.num;
1N/A break;
1N/A case 's':
1N/A set(LS_BLOCKS);
1N/A break;
1N/A case 't':
1N/A if (set(LS_TIME) && !state.order)
1N/A state.order = order_mtime;
1N/A break;
1N/A case 'u':
1N/A state.lsflags &= ~LS_CTIME;
1N/A state.lsflags |= LS_ATIME;
1N/A if (!state.order)
1N/A state.order = order_atime;
1N/A break;
1N/A case 'w':
1N/A state.width = strtol(opt_info.arg, &e, 0);
1N/A if (*e == 'x' || *e == 'X' || *e == '.' || *e == '+')
1N/A {
1N/A state.height = state.width;
1N/A state.width = strtol(e + 1, &e, 0);
1N/A }
1N/A if (*e)
1N/A error(2, "%s: invalid screen width specification at `%s'", opt_info.arg, e);
1N/A break;
1N/A case 'x':
1N/A set(LS_ACROSS|LS_COLUMNS);
1N/A break;
1N/A case 'y':
1N/A if (!opt_info.arg)
1N/A state.order = order_none;
1N/A else
1N/A switch (opt_info.num)
1N/A {
1N/A case 'a':
1N/A state.order = order_atime;
1N/A break;
1N/A case 'c':
1N/A state.order = order_ctime;
1N/A break;
1N/A case 'f':
1N/A state.order = 0;
1N/A break;
1N/A case 'm':
1N/A state.order = order_mtime;
1N/A break;
1N/A case 'n':
1N/A state.order = order_none;
1N/A break;
1N/A case 's':
1N/A state.order = order_blocks;
1N/A break;
1N/A case 't':
1N/A state.order = order_mtime;
1N/A break;
1N/A case 'v':
1N/A state.order = order_version;
1N/A break;
1N/A case 'x':
1N/A state.order = order_extension;
1N/A break;
1N/A }
1N/A break;
1N/A case 'z':
1N/A switch (opt_info.num)
1N/A {
1N/A case -10:
1N/A if (!strcmp(setlocale(LC_TIME, NiL), "C"))
1N/A break;
1N/A /*FALLTHROUGH*/
1N/A case 'i':
1N/A state.timefmt = TIME_ISO;
1N/A break;
1N/A case -11:
1N/A if (!strcmp(setlocale(LC_TIME, NiL), "C"))
1N/A break;
1N/A /*FALLTHROUGH*/
1N/A case 'f':
1N/A state.timefmt = TIME_FULL_ISO;
1N/A break;
1N/A case 'l':
1N/A state.timefmt = TIME_LONG_ISO;
1N/A break;
1N/A case 'L':
1N/A state.timefmt = TIME_LOCALE;
1N/A break;
1N/A case -12:
1N/A s = opt_info.arg + 1;
1N/A if (strchr(s, '\n'))
1N/A {
1N/A /*
1N/A * gnu compatibility
1N/A */
1N/A
1N/A s = sfprints("%%Q\n%s\n", s);
1N/A if (!s || !(s = strdup(s)))
1N/A error(ERROR_SYSTEM|3, "out of space");
1N/A }
1N/A state.timefmt = s;
1N/A break;
1N/A }
1N/A break;
1N/A case 'A':
1N/A state.lsflags |= LS_MOST;
1N/A state.lsflags &= ~LS_ALL;
1N/A break;
1N/A case 'B':
1N/A set(LS_NOBACKUP);
1N/A break;
1N/A case 'C':
1N/A set(LS_COLUMNS);
1N/A break;
1N/A case 'D':
1N/A if (s = strchr(opt_info.arg, '='))
1N/A *s++ = 0;
1N/A if (*opt_info.arg == 'n' && *(opt_info.arg + 1) == 'o')
1N/A {
1N/A opt_info.arg += 2;
1N/A s = 0;
1N/A }
1N/A if (!(kp = (Key_t*)hashget(state.keys, opt_info.arg)))
1N/A {
1N/A if (!s)
1N/A break;
1N/A if (!(kp = newof(0, Key_t, 1, 0)))
1N/A error(3, "out of space");
1N/A kp->name = hashput(state.keys, 0, kp);
1N/A }
1N/A if (kp->macro = s)
1N/A {
1N/A stresc(s);
1N/A if (strmatch(s, "*:case:*"))
1N/A state.lsflags |= LS_STAT;
1N/A }
1N/A break;
1N/A case 'E':
1N/A state.lsflags |= LS_LONG;
1N/A state.timefmt = TIME_FULL_ISO;
1N/A break;
1N/A case 'F':
1N/A set(LS_MARK);
1N/A break;
1N/A case 'H':
1N/A state.ftwflags |= FTW_META|FTW_PHYSICAL;
1N/A break;
1N/A case 'I':
1N/A state.ignore = opt_info.arg;
1N/A break;
1N/A case 'J':
1N/A state.lsflags &= ~(LS_ALWAYS|LS_ESCAPE|LS_PRINTABLE|LS_QUOTE|LS_SHELL);
1N/A switch (opt_info.num)
1N/A {
1N/A case 'c':
1N/A state.lsflags |= LS_ESCAPE|LS_PRINTABLE|LS_QUOTE;
1N/A break;
1N/A case 'e':
1N/A state.lsflags |= LS_ESCAPE|LS_PRINTABLE;
1N/A break;
1N/A case 'l':
1N/A break;
1N/A case 'q':
1N/A state.lsflags |= LS_PRINTABLE;
1N/A break;
1N/A case 's':
1N/A state.lsflags |= LS_ESCAPE|LS_PRINTABLE|LS_QUOTE|LS_SHELL;
1N/A break;
1N/A case 'S':
1N/A state.lsflags |= LS_ALWAYS|LS_ESCAPE|LS_PRINTABLE|LS_QUOTE|LS_SHELL;
1N/A break;
1N/A }
1N/A break;
1N/A case 'K':
1N/A set(LS_PRINTABLE|LS_SHELL|LS_QUOTE|LS_ESCAPE);
1N/A break;
1N/A case 'L':
1N/A state.ftwflags &= ~(FTW_META|FTW_PHYSICAL|FTW_SEEDOTDIR);
1N/A break;
1N/A case 'N':
1N/A clr(LS_PRINTABLE);
1N/A break;
1N/A case 'P':
1N/A state.ftwflags &= ~FTW_META;
1N/A state.ftwflags |= FTW_PHYSICAL;
1N/A break;
1N/A case 'Q':
1N/A set(LS_PRINTABLE|LS_QUOTE);
1N/A break;
1N/A case 'R':
1N/A set(LS_RECURSIVE);
1N/A break;
1N/A case 'S':
1N/A state.order = order_blocks;
1N/A break;
1N/A case 'T':
1N/A /* ignored */
1N/A break;
1N/A case 'U':
1N/A state.order = order_none;
1N/A break;
1N/A case 'V':
1N/A switch (opt_info.num)
1N/A {
1N/A case 't':
1N/A if (!isatty(1))
1N/A break;
1N/A /*FALLTHROUGH*/
1N/A case 'a':
1N/A if (kp = (Key_t*)hashget(state.keys, "name"))
1N/A {
1N/A stresc(kp->macro = fmt_color);
1N/A state.lsflags |= LS_STAT;
1N/A }
1N/A break;
1N/A }
1N/A break;
1N/A case 'W':
1N/A state.timeflags = 0;
1N/A switch (opt_info.num)
1N/A {
1N/A case 'a':
1N/A state.timeflags = LS_ATIME;
1N/A break;
1N/A case 'c':
1N/A state.timeflags = LS_CTIME;
1N/A break;
1N/A }
1N/A break;
1N/A case 'X':
1N/A set(LS_EXTENSION);
1N/A break;
1N/A case 'Y':
1N/A switch (opt_info.num)
1N/A {
1N/A case 'a':
1N/A state.lsflags |= LS_ACROSS|LS_COLUMNS;
1N/A break;
1N/A case 'c':
1N/A state.lsflags |= LS_COMMAS;
1N/A break;
1N/A case 'l':
1N/A state.lsflags |= LS_LONG;
1N/A break;
1N/A case 'v':
1N/A state.lsflags &= ~LS_ACROSS;
1N/A state.lsflags |= LS_COLUMNS;
1N/A break;
1N/A case '1':
1N/A state.lsflags &= ~(LS_ACROSS|LS_COLUMNS);
1N/A break;
1N/A }
1N/A break;
1N/A case 'Z':
1N/A if (!sfstrtell(fmt))
1N/A state.lsflags &= ~LS_COLUMNS;
1N/A sfputr(fmt, opt_info.arg, ' ');
1N/A break;
1N/A case '1':
1N/A clr(LS_COLUMNS|LS_PRINTABLE);
1N/A break;
1N/A case -101:
1N/A if (opt_info.num <= 0)
1N/A error(3, "%ld: invalid block size", opt_info.num);
1N/A state.blocksize = opt_info.num;
1N/A break;
1N/A case -102:
1N/A state.scale = 1000;
1N/A break;
1N/A case -103:
1N/A dump = 1;
1N/A break;
1N/A case -104:
1N/A state.testdate = tmdate(opt_info.arg, &e, NiL);
1N/A if (*e)
1N/A error(3, "%s: invalid date string", opt_info.arg, opt_info.option);
1N/A break;
1N/A case -105:
1N/A state.testsize = opt_info.num;
1N/A break;
1N/A case '?':
1N/A error(ERROR_USAGE|4, "%s", opt_info.arg);
1N/A break;
1N/A case ':':
1N/A error(2, "%s", opt_info.arg);
1N/A break;
1N/A default:
1N/A error(1, "%s: option not implemented", opt_info.name);
1N/A continue;
1N/A }
1N/A if (!strchr(state.flags, n))
1N/A *state.endflags++ = n;
1N/A }
1N/A argv += opt_info.index;
1N/A if (error_info.errors)
1N/A error(ERROR_USAGE|4, "%s", optusage(NiL));
1N/A if (state.lsflags == (lsflags|LS_TIME))
1N/A state.ftwflags |= FTW_SEEDOTDIR; /* keep configure happy */
1N/A if (state.lsflags & LS_DIRECTORY)
1N/A state.lsflags &= ~LS_RECURSIVE;
1N/A if (!state.order)
1N/A state.order = order_name;
1N/A if (!state.timeflags)
1N/A state.timeflags = state.lsflags;
1N/A if (state.lsflags & (LS_COLUMNS|LS_COMMAS))
1N/A {
1N/A if (state.lsflags & LS_LONG)
1N/A state.lsflags &= ~(LS_COLUMNS|LS_COMMAS);
1N/A else
1N/A {
1N/A if (!state.width)
1N/A {
1N/A astwinsize(1, &state.height, &state.width);
1N/A if (state.width <= 20)
1N/A state.width = 80;
1N/A }
1N/A if (state.height <= 4)
1N/A state.height = 24;
1N/A }
1N/A }
1N/A if (state.lsflags & LS_STAT)
1N/A state.lsflags &= ~LS_NOSTAT;
1N/A else if (!(state.lsflags & (LS_DIRECTORY|LS_BLOCKS|LS_LONG|LS_MARK|LS_MARKDIR|LS_TIME
1N/A#if !_mem_d_fileno_dirent && !_mem_d_ino_dirent
1N/A |LS_INUMBER
1N/A#endif
1N/A )) && !sfstrtell(fmt))
1N/A {
1N/A state.lsflags |= LS_NOSTAT;
1N/A state.ftwflags |= FTW_DELAY|FTW_DOT;
1N/A }
1N/A if (!sfstrtell(fmt))
1N/A {
1N/A if (state.lsflags & LS_INUMBER)
1N/A sfputr(fmt, "%6(ino)u ", -1);
1N/A if (state.lsflags & LS_BLOCKS)
1N/A sfputr(fmt, "%5(blocks)u ", -1);
1N/A if (state.lsflags & LS_LONG)
1N/A {
1N/A sfputr(fmt, "%(mode)s %3(nlink)u", -1);
1N/A if (!(state.lsflags & LS_NOUSER))
1N/A sfprintf(fmt, " %%-8(uid)%c", (state.lsflags & LS_NUMBER) ? 'd' : 's');
1N/A if (!(state.lsflags & LS_NOGROUP))
1N/A sfprintf(fmt, " %%-8(gid)%c", (state.lsflags & LS_NUMBER) ? 'd' : 's');
1N/A sfputr(fmt, " %8(device:case::%(size)u:*:%(device)s)s", -1);
1N/A sfprintf(fmt, " %%(%s)s ", (state.timeflags & LS_ATIME) ? "atime" : (state.timeflags & LS_CTIME) ? "ctime" : "mtime");
1N/A }
1N/A sfputr(fmt, "%(name)s", -1);
1N/A if (state.lsflags & LS_MARK)
1N/A sfputr(fmt, "%(mark)s", -1);
1N/A else if (state.lsflags & LS_MARKDIR)
1N/A sfputr(fmt, "%(markdir)s", -1);
1N/A if (state.lsflags & LS_LONG)
1N/A sfputr(fmt, "%(linkop:case:?*: %(linkop)s %(linkpath)s)s", -1);
1N/A }
1N/A else
1N/A sfstrseek(fmt, -1, SEEK_CUR);
1N/A if (!(state.format = sfstruse(fmt)))
1N/A error(3, "out of space");
1N/A if (dump)
1N/A {
1N/A sfprintf(sfstdout, "%s\n", state.format);
1N/A return 0;
1N/A }
1N/A stresc(state.format);
1N/A
1N/A /*
1N/A * do it
1N/A */
1N/A
1N/A if (argv[0])
1N/A {
1N/A if (argv[1])
1N/A state.lsflags |= LS_LABEL;
1N/A state.ftwflags |= FTW_MULTIPLE;
1N/A ftwalk((char*)argv, ls, state.ftwflags, order);
1N/A }
1N/A else
1N/A ftwalk(".", ls, state.ftwflags, order);
1N/A if (keys[KEY_summary].macro)
1N/A sfkeyprintf(sfstdout, NiL, keys[KEY_summary].macro, key, NiL);
1N/A return error_info.errors != 0;
1N/A}