/* ls 4.1 - List files. Author: Kees J. Bot
* 25 Apr 1989
*/
/*
* Changes by Gunnar Ritter, Freiburg i. Br., Germany, October 2001. Taken
* from the MINIX sources:
*/
/*
* Copyright (c) 1987,1997, Prentice Hall All rights reserved.
*
* Redistribution and use of the MINIX operating system 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.
*
* Neither the name of Prentice Hall nor the names of the software authors or
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, 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 PRENTICE HALL OR ANY
* AUTHORS 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 __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4
#define USED __attribute__ ((used))
#elif defined __GNUC__
#define USED __attribute__ ((unused))
#else
#define USED
#endif
#if defined (SU3)
static const char sccsid[] USED = "@(#)ls_su3.sl 1.77 (gritter) 5/29/05>";
#elif defined (SUS)
static const char sccsid[] USED = "@(#)ls_sus.sl 1.77 (gritter) 5/29/05>";
#elif defined (UCB)
static const char sccsid[] USED = "@(#)/usr/ucb/ls.sl 1.77 (gritter) 5/29/05>";
#else
static const char sccsid[] USED = "@(#)ls.sl 1.77 (gritter) 5/29/05>";
#endif
/*
* About the amount of bytes for heap + stack under Minix:
* Ls needs a average amount of 42 bytes per unserviced directory entry, so
* scanning 10 directory levels deep in an ls -R with 100 entries per directory
* takes 42000 bytes of heap. So giving ls 10000 bytes is tight, 20000 is
* usually enough, 40000 is pessimistic.
*/
/* The array ifmt_c[] is used in an 'ls -l' to map the type of a file to a
* letter. This is done so that ls can list any future file or device type
* other than symlinks, without recompilation. (Yes it's dirty.)
*/
static char ifmt_c[] = "-pc-d-b--nl-SD--";
/* S_IFIFO
* S_IFCHR
* S_IFDIR
* S_IFBLK
* S_IFREG
* S_IFNWK
* S_IFLNK
* S_IFSOCK
* S_IFDOOR
*/
#define ifmt(mode) ifmt_c[((mode) >> 12) & 0xF]
#ifndef USE_TERMCAP
#ifdef sun
#include <curses.h>
#include <term.h>
#endif
#endif
#define nil 0
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <locale.h>
#include <limits.h>
#include <ctype.h>
#include <libgen.h>
#include <wchar.h>
#include "config.h"
#ifndef USE_TERMCAP
#ifndef sun
#include <curses.h>
#include <term.h>
#endif
#else /* USE_TERMCAP */
#include <termcap.h>
#endif /* USE_TERMCAP */
#ifdef _AIX
#include <sys/sysmacros.h>
#endif
#ifndef S_IFNAM
#define S_IFNAM 0x5000 /* XENIX special named file */
#endif
#ifndef S_INSEM
#define S_INSEM 0x1 /* XENIX semaphore subtype of IFNAM */
#endif
#ifndef S_INSHD
#define S_INSHD 0x2 /* XENIX shared data subtype of IFNAM */
#endif
#ifndef S_IFDOOR
#define S_IFDOOR 0xD000 /* Solaris door */
#endif
#ifndef S_IFNWK
#define S_IFNWK 0x9000 /* HP-UX network special */
#endif
#if !__minix
#define SUPER_ID uid /* Let -A flag be default for SUPER_ID == 0. */
#else
#define SUPER_ID gid
#endif
#if __minix
#define BLOCK 1024
#else
#define BLOCK 512
#endif
/* Assume other systems have st_blocks. */
#if !__minix
#define ST_BLOCKS 1
#endif
/* Some terminals ignore more than 80 characters on a line. Dumb ones wrap
* when the cursor hits the side. Nice terminals don't wrap until they have
* to print the 81st character. Wether we like it or not, no column 80.
*/
static int ncols = 79;
#define NSEP 2 /* # spaces between columns. */
#define MAXCOLS 150 /* Max # of files per line. */
static char *arg0; /* Last component of argv[0]. */
static int uid, gid; /* callers id. */
static int ex = 0; /* Exit status to be. */
static int istty; /* Output is on a terminal. */
static int tinfostat = -1; /* terminfo is initalized */
extern int sysv3; /* emulate SYSV3 behavior */
#ifdef SU3
static struct visit {
ino_t v_ino;
dev_t v_dev;
} *visited;
static int vismax; /* number of members in visited */
#endif /* SU3 */
static enum {
PER_LS = 0,
PER_DIR = 1
} personality = PER_LS;
static struct {
const char *per_opt;
} personalities[] = {
/* orig: acdfgilnqrstu1ACFLMRTX */
#ifdef UCB
{ ":1RaAdClgrtucFqisfLSUXhH" }, /* PER_LS */
#else /* !UCB */
{ "1RadCxmnlogrtucpFbqisfLSUXhH" }, /* PER_LS */
#endif /* !UCB */
{ "1RadCxmnlogrtucpFbqiOfLSXhH" } /* PER_DIR */
};
/* Safer versions of malloc and realloc: */
static void
heaperr(void)
{
write(2, "out of memory\n", 14);
exit(077);
}
/*
* Deliver or die.
*/
static void *
salloc(size_t n)
{
void *a;
if ((a = malloc(n)) == nil)
heaperr();
return a;
}
static void *
srealloc(void *a, size_t n)
{
if ((a = realloc(a, n)) == nil)
heaperr();
return a;
}
static char flags[0477];
static int
present(int f)
{
return f == 0 || flags[f] != 0;
}
/*
* Like perror(3), but in the style: "ls: junk: No such file or directory.
*/
static void
report(const char *f)
{
#ifdef UCB
fprintf(stderr, "%s not found\n", f);
#else /* !UCB */
fprintf(stderr, "%s: %s\n", f, strerror(errno));
#endif /* !UCB */
ex = 1;
}
#ifdef UCB
#define blockcount(n) (((n) & 1 ? (n)+1 : (n)) >> 1)
#else
#define blockcount(n) (n)
#endif
/*
* Two functions, uidname and gidname, translate id's to readable names.
* All names are remembered to avoid searching the password file.
*/
#define NNAMES (1 << (sizeof(int) + sizeof(char *)))
enum whatmap { PASSWD, GROUP };
static struct idname { /* Hash list of names. */
struct idname *next;
char *name;
uid_t id;
} *uids[NNAMES], *gids[NNAMES];
/*
* Return name for a given user/group id.
*/
static char *
idname(unsigned id, enum whatmap map)
{
struct idname *i;
struct idname **ids = &(map == PASSWD ? uids : gids)[id % NNAMES];
while ((i = *ids) != nil && id < i->id)
ids = &i->next;
if (i == nil || id != i->id) {
/* Not found, go look in the password or group map. */
char *name = nil;
char noname[3 * sizeof(uid_t)];
if (!present('n')) {
if (map == PASSWD) {
struct passwd *pw = getpwuid(id);
if (pw != nil) name = pw->pw_name;
} else {
struct group *gr = getgrgid(id);
if (gr != nil) name = gr->gr_name;
}
}
if (name == nil) {
/* Can't find it, weird. Use numerical "name." */
sprintf(noname, "%lu", (long)id);
name = noname;
}
/* Add a new id-to-name cell. */
i = salloc(sizeof(*i));
i->id = id;
i->name = salloc(strlen(name) + 1);
strcpy(i->name, name);
i->next = *ids;
*ids = i;
}
return i->name;
}
#define uidname(uid) idname((uid), PASSWD)
#define gidname(gid) idname((gid), GROUP)
/*
* Path name construction, addpath adds a component, delpath removes it.
* The string path is used throughout the program as the file under examination.
*/
static char *path; /* Path name constructed in path[]. */
static int plen = 0, pidx = 0; /* Lenght/index for path[]. */
/*
* Add a component to path. (name may also be a full path at the first call)
* The index where the current path ends is stored in *pdi.
*/
static void
addpath(int *didx, char *name)
{
if (plen == 0)
path = salloc((plen = 32) * sizeof(path[0]));
if (pidx == 1 && path[0] == '.')
pidx = 0; /* Remove "." */
*didx = pidx; /* Record point to go back to for delpath. */
if (pidx > 0 && path[pidx-1] != '/')
path[pidx++] = '/';
do {
if (*name != '/' || pidx == 0 || path[pidx-1] != '/') {
if (pidx == plen) {
path = srealloc(path,
(plen *= 2) * sizeof(path[0]));
}
path[pidx++] = *name;
}
} while (*name++ != 0);
--pidx; /*
* Put pidx back at the null. The path[pidx++] = '/'
* statement will overwrite it at the next call.
*/
}
#define delpath(didx) (path[pidx = didx] = 0) /* Remove component. */
static int field = 0; /* (used to be) Fields that must be printed. */
/* (now) Effects triggered by certain flags. */
#define FL_INODE 0x001 /* -i */
#define FL_BLOCKS 0x002 /* -s */
#define FL_UNUSED 0x004
#define FL_MODE 0x008 /* -lMX */
#define FL_LONG 0x010 /* -l */
#define FL_GROUP 0x020 /* -g */
#define FL_BYTIME 0x040 /* -tuc */
#define FL_ATIME 0x080 /* -u */
#define FL_CTIME 0x100 /* -c */
#define FL_MARK 0x200 /* -F */
/*#define FL_UNUSED 0x400 */
#define FL_DIR 0x800 /* -d */
#define FL_OWNER 0x1000 /* -o */
#define FL_STATUS 0x2000
#define ENDCOL 0x001 /* last printable column in -x mode */
struct file { /* A file plus stat(2) information. */
off_t size;
#if ST_BLOCKS
blkcnt_t blocks;
#endif
ino_t ino;
struct file *next; /* Lists are made of them. */
struct file *sord; /* Saved order of list. */
char *name; /* Null terminated name. */
time_t mtime;
time_t atime;
time_t ctime;
mode_t mode;
int flag;
uid_t uid;
gid_t gid;
dev_t rdev;
nlink_t nlink;
unsigned short namlen;
};
static void
setstat(struct file *f, struct stat *stp)
{
f->ino = stp->st_ino;
f->mode = stp->st_mode;
f->nlink = stp->st_nlink;
f->uid = stp->st_uid;
f->gid = stp->st_gid;
f->rdev = stp->st_rdev;
f->size = stp->st_size;
f->mtime = stp->st_mtime;
f->atime = stp->st_atime;
f->ctime = stp->st_ctime;
#if ST_BLOCKS
f->blocks = stp->st_blocks;
#ifdef __hpux
f->blocks <<= 1;
#endif
#endif
}
#define PAST (26*7*24*3600L) /* Half a year ago. */
/* Between PAST and FUTURE from now a time is printed, otherwise a year. */
#define FUTURE (15*60L) /* Fifteen minutes. */
/*
* Transform the right time field into something readable.
*/
static char *
timestamp(struct file *f)
{
struct tm *tm;
time_t t;
static time_t now;
static int drift = 0;
static char date[32];
t = f->mtime;
if (field & FL_ATIME)
t = f->atime;
if (field & FL_CTIME)
t = f->ctime;
tm = localtime(&t);
if (--drift < 0) {
time(&now);
drift = 50;
} /* limit time() calls */
if (t < now - PAST || t > now + FUTURE) {
strftime(date, sizeof date - 1, "%b %e %Y", tm);
} else {
strftime(date, sizeof date - 1, "%b %e %H:%M", tm);
}
return date;
}
/*
* Compute long or short rwx bits.
*/
static char *
permissions(struct file *f)
{
/*
* Note that rwx[0] is a guess for the more alien file types. It is
* correct for BSD4.3 and derived systems. I just don't know how
* "standardized" these numbers are.
*/
static char rwx[] = "drwxr-x--x ";
char *p = rwx+1;
int mode = f->mode;
rwx[0] = ifmt(f->mode);
if ((f->mode & S_IFMT) == S_IFNAM) {
if (f->rdev == S_INSEM)
rwx[0] = 's';
else if (f->rdev == S_INSHD)
rwx[0] = 'm';
else
rwx[0] = '?';
}
do {
p[0] = (mode & S_IRUSR) ? 'r' : '-';
p[1] = (mode & S_IWUSR) ? 'w' : '-';
p[2] = (mode & S_IXUSR) ? 'x' : '-';
mode <<= 3;
} while ((p += 3) <= rwx + 7);
if (f->mode&S_ISUID)
rwx[3] = f->mode&(S_IXUSR>>0) ? 's' : 'S';
if (f->mode&S_ISGID)
rwx[6] = f->mode&(S_IXGRP>>0) ? 's' :
#ifndef UCB
(f->mode & S_IFMT) != S_IFDIR ?
#if defined (SUS) || defined (SU3)
'L'
#else /* !SUS, !SU3 */
'l'
#endif /* !SUS, !SU3 */
:
#endif /* !UCB */
'S'
;
if (f->mode&S_ISVTX)
rwx[9] = f->mode&(S_IXUSR>>6) ? 't' : 'T';
/*
* rwx[10] would be the "optional alternate access method flag",
* leave as a space for now.
*/
return rwx;
}
#ifdef notdef
void
numeral(int i, char **pp)
{
char itoa[3*sizeof(int)], *a = itoa;
do
*a++ = i % 10 + '0';
while ((i /= 10) > 0);
do
*(*pp)++ = *--a;
while (a > itoa);
}
#endif
static char *
extension(const char *fn)
{
const char *ep = "";
while (*fn++)
if (*fn == '.')
ep = &fn[1];
return (char *)ep;
}
static int (*CMP)(struct file *f1, struct file *f2);
static int (*rCMP)(struct file *f1, struct file *f2);
/*
* This is either a stable mergesort, or thermal noise, I'm no longer sure.
* It must be called like this: if (L != nil && L->next != nil) mergesort(&L);
*/
static void
_mergesort(struct file **al)
{
/* static */ struct file *l1, **mid; /* Need not be local */
struct file *l2;
l1 = *(mid = &(*al)->next);
do {
if ((l1 = l1->next) == nil)
break;
mid = &(*mid)->next;
} while ((l1 = l1->next) != nil);
l2 = *mid;
*mid = nil;
if ((*al)->next != nil)
_mergesort(al);
if (l2->next != nil)
_mergesort(&l2);
l1 = *al;
for (;;) {
if ((*CMP)(l1, l2) <= 0) {
if ((l1 = *(al = &l1->next)) == nil) {
*al = l2;
break;
}
} else {
*al = l2;
l2 = *(al = &l2->next);
*al = l1;
if (l2 == nil)
break;
}
}
}
static int
namecmp(struct file *f1, struct file *f2)
{
return strcoll(f1->name, f2->name);
}
static int
extcmp(struct file *f1, struct file *f2)
{
return strcoll(extension(f1->name), extension(f2->name));
}
static int
mtimecmp(struct file *f1, struct file *f2)
{
return f1->mtime == f2->mtime ? 0 : f1->mtime > f2->mtime ? -1 : 1;
}
static int
atimecmp(struct file *f1, struct file *f2)
{
return f1->atime == f2->atime ? 0 : f1->atime > f2->atime ? -1 : 1;
}
static int
ctimecmp(struct file *f1, struct file *f2)
{
return f1->ctime == f2->ctime ? 0 : f1->ctime > f2->ctime ? -1 : 1;
}
static int
sizecmp(struct file *f1, struct file *f2)
{
return f1->size == f2->size ? 0 : f1->size > f2->size ? -1 : 1;
}
static int
revcmp(struct file *f1, struct file *f2) {
return (*rCMP)(f2, f1);
}
/*
* Sort the files according to the flags.
*/
static void
sort(struct file **al)
{
if (present('U'))
return;
if (!present('f') && *al != nil && (*al)->next != nil) {
CMP = namecmp;
if (!(field & FL_BYTIME)) {
/* Sort on name */
if (present('r')) {
rCMP = CMP;
CMP = revcmp;
}
_mergesort(al);
} else {
/* Sort on name first, then sort on time. */
_mergesort(al);
if (field & FL_CTIME) {
CMP = ctimecmp;
} else if (field & FL_ATIME) {
CMP = atimecmp;
} else {
CMP = mtimecmp;
}
if (present('r')) {
rCMP = CMP;
CMP = revcmp;
}
_mergesort(al);
}
if (present('X')) {
CMP = extcmp;
if (present('r')) {
rCMP = CMP;
CMP = revcmp;
}
_mergesort(al);
}
if (present('S')) {
CMP = sizecmp;
if (present('r')) {
rCMP = CMP;
CMP = revcmp;
}
_mergesort(al);
}
}
}
/*
* Create file structure for given name.
*/
static struct file *
newfile(char *name)
{
struct file *new;
new = salloc(sizeof(*new));
new->name = strcpy(salloc(strlen(name)+1), name);
new->namlen = 0;
new->flag = 0;
return new;
}
/*
* Add file to the head of a list.
*/
static void
pushfile(struct file **flist, struct file *new)
{
new->next = *flist;
*flist = new;
}
/*
* Release old file structure.
*/
static void
delfile(struct file *old)
{
free(old->name);
free(old);
}
/*
* Pop file off top of file list.
*/
static struct file *
popfile(struct file **flist)
{
struct file *f;
f = *flist;
*flist = f->next;
return f;
}
/*
* Save the current file order.
*/
static void
savord(struct file *f)
{
while (f) {
f->sord = f->next;
f = f->next;
}
}
/*
* Restore the saved file order.
*/
static void
resord(struct file *f)
{
while (f) {
f->next = f->sord;
f = f->sord;
}
}
/*
* Return flag that would make ls list this name: -a or -A.
*/
static int
dotflag(char *name)
{
if (*name++ != '.')
return 0;
switch (*name++) {
case 0:
return 'a'; /* "." */
case '.':
if (*name == 0)
return 'a'; /* ".." */
default:
return 'A'; /* ".*" */
}
}
/*
* Add directory entries of directory name to a file list.
*/
static int
adddir(struct file **aflist, char *name)
{
DIR *d;
struct dirent *e;
if (access(name, 0) < 0) {
report(name);
return 0;
}
if ((d = opendir(name)) == nil) {
#ifdef UCB
fprintf(stderr, "%s unreadable\n", name);
#else
fprintf(stderr, "can not access directory %s\n", name);
#endif
ex = 1;
return 0;
}
while ((e = readdir(d)) != nil) {
if (e->d_ino != 0 && present(dotflag(e->d_name))) {
pushfile(aflist, newfile(e->d_name));
aflist = &(*aflist)->next;
}
}
closedir(d);
return 1;
}
/*
* Compute total block count for a list of files.
*/
static off_t
countblocks(struct file *flist)
{
off_t cb = 0;
while (flist != nil) {
switch (flist->mode & S_IFMT) {
case S_IFDIR:
case S_IFREG:
#ifdef S_IFLNK
case S_IFLNK:
#endif
cb += flist->blocks;
}
flist = flist->next;
}
return cb;
}
static char *
#ifdef LONGLONG
hfmt(long long n, int fill)
#else
hfmt(long n, int fill)
#endif
{
static char b[10];
const char units[] = " KMGTPE", *up = units;
int rest = 0;
while (n > 1023) {
rest = (n % 1024) / 128;
n /= 1024;
up++;
}
#ifdef LONGLONG
if (up == units)
snprintf(b, sizeof b, "%*llu", fill ? 5 : 1, n);
else if (n < 10 && rest)
snprintf(b, sizeof b, "%*llu.%u%c", fill ? 2 : 1, n, rest, *up);
else
snprintf(b, sizeof b, "%*llu%c", fill ? 4 : 1, n, *up);
#else /* !LONGLONG */
if (up == units)
snprintf(b, sizeof b, "%*lu", fill ? 5 : 1, n);
else if (n < 10 && rest)
snprintf(b, sizeof b, "%*lu.%u%c", fill ? 2 : 1, n, rest, *up);
else
snprintf(b, sizeof b, "%*lu%c", fill ? 4 : 1, n, *up);
#endif /* !LONGLONG */
return b;
}
#define FC_NORMAL 0
#define FC_BLACK 30
#define FC_RED 31
#define FC_GREEN 32
#define FC_YELLOW 33
#define FC_BLUE 34
#define FC_MAGENTA 35
#define FC_CYAN 36
#define FC_WHITE 37
/*
* The curses color interface is nearly unusable since 1) it interferes
* with bold attributes and 2) there is no clean possibility to reset
* all colors to the previous (NOT black/white) values. This uses curses
* to check whether running on an ANSI-style color terminal, and if yes,
* returns appropriate attributes after.
*/
static char *
fc_get(int color)
{
static int status;
static char sequence[8];
if (status == 0) {
#if defined (COLOR_BLACK) && !defined (USE_TERMCAP)
char *cp = tparm(set_a_foreground, COLOR_BLACK);
if (cp && strcmp(cp, "\33[30m") == 0)
status = 1;
else
status = -1;
#else /* !COLOR_BLACK */
char *cp = getenv("TERM");
status = strncmp(cp, "rxvt", 4) == 0 ||
strncmp(cp, "dtterm", 6) == 0 ||
strncmp(cp, "xterm", 5) == 0;
#endif /* !COLOR_BLACK */
}
if (status == -1)
return "";
snprintf(sequence, sizeof sequence, "\33[%um", color);
return sequence;
}
/*
* Display a nonprintable character.
*/
static unsigned
nonprint(register int c, int doit)
{
register int d;
unsigned n;
if (present('b')) {
n = 4;
if (doit) {
char *nums = "01234567";
putchar('\\');
putchar(nums[(c & ~077) >> 6]);
c &= 077;
d = c & 07;
if (c > d)
putchar(nums[(c - d) >> 3]);
else
putchar(nums[0]);
putchar(nums[d]);
}
} else {
n = 1;
if (doit)
putchar('?');
}
return n;
}
/*
* Print a name with control characters as '?' (unless -q). The terminal is
* assumed to be eight bit clean.
*/
static unsigned
printname(const char *name, struct file *f, int doit)
{
int c, q = present('q'), bold = 0;
unsigned n = 0;
char *color = NULL;
#if defined (USE_TERMCAP)
static char tspace[1000];
char *tptr = tspace;
static char *Bold, *Normal;
static int washere;
if (washere == 0 && tinfostat == 1) {
washere = 1;
Bold = tgetstr("md", &tptr);
Normal = tgetstr("me", &tptr);
}
#endif /* USE_TERMCAP */
if (f != NULL && doit == 0 && f->namlen != 0)
return f->namlen;
if (doit && f != NULL && tinfostat == 1) {
if ((f->mode & S_IFMT) == S_IFDIR) {
color = fc_get(FC_BLUE);
bold++;
} else if ((f->mode & S_IFMT) == S_IFCHR ||
(f->mode & S_IFMT) == S_IFBLK) {
color = fc_get(FC_YELLOW);
bold++;
#ifdef S_IFLNK
} else if ((f->mode & S_IFMT) == S_IFLNK) {
color = fc_get(FC_CYAN);
bold++;
#endif
#ifdef S_IFSOCK
} else if ((f->mode & S_IFMT) == S_IFSOCK) {
color = fc_get(FC_MAGENTA);
bold++;
#endif
#ifdef S_IFDOOR
} else if ((f->mode & S_IFMT) == S_IFDOOR) {
color = fc_get(FC_MAGENTA);
bold++;
#endif
#ifdef S_IFNAM
} else if ((f->mode & S_IFMT) == S_IFNAM) {
color = fc_get(FC_MAGENTA);
#endif
#ifdef S_IFNWK
} else if ((f->mode & S_IFMT) == S_IFNWK) {
color = fc_get(FC_MAGENTA);
#endif
#ifdef S_IFIFO
} else if ((f->mode & S_IFMT) == S_IFIFO) {
color = fc_get(FC_YELLOW);
#endif
} else if (f->mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
color = fc_get(FC_GREEN);
bold++;
} else if ((f->mode & S_IFMT) == 0) {
color = fc_get(FC_RED);
bold++;
}
if (color) {
#ifndef USE_TERMCAP
if (bold)
vidattr(A_BOLD);
#else /* USE_TERMCAP */
if (Bold)
tputs(Bold, 1, putchar);
#endif /* USE_TERMCAP */
printf(color);
}
}
if (q || istty) {
#ifdef MB_CUR_MAX
wchar_t wc;
while (
#define ASCII
#ifdef ASCII
(*name & 0200) == 0 ?
c = 1, (wc = *name) != '\0' :
#endif
(c = mbtowc(&wc, name, MB_CUR_MAX)) != 0) {
if (c != -1) {
if (iswprint(wc)
#ifdef UCB
|| wc == '\t' || wc == '\n'
#else /* !UCB */
&& wc != '\t'
#endif /* !UCB */
) {
if (doit) {
while (c-- > 0)
putchar(*name++ & 0377);
} else
name += c;
n += wcwidth(wc);
} else {
while (c-- > 0)
n += nonprint(*name++ & 0377,
doit);
}
} else
n += nonprint(*name++ & 0377, doit);
}
#else /* !MB_CUR_MAX */
while ((c = (*name++ & 0377)) != '\0') {
if (isprint(c)
#ifdef UCB
|| c == '\t' || c == '\n'
#else /* !UCB */
&& c != '\t'
#endif /* !UCB */
) {
if (doit)
putchar(c);
n++;
} else
n += nonprint(c, doit);
}
#endif /* !MB_CUR_MAX */
} else {
while ((c = (*name++ & 0377)) != '\0') {
if (doit)
putchar(c);
n++;
}
}
if (doit && color) {
#if !defined (USE_TERMCAP)
if (bold)
vidattr(A_NORMAL);
#else /* USE_TERMCAP */
if (Normal)
tputs(Normal, 1, putchar);
#endif /* USE_TERMCAP */
printf(fc_get(FC_NORMAL));
}
if (f)
f->namlen = n;
return n;
}
static int
mark(struct file *f, int doit)
{
int c;
if (!(field & FL_MARK))
return 0;
switch (f->mode & S_IFMT) {
case S_IFDIR: c = '/'; break;
#ifdef S_IFIFO
case S_IFIFO: c = '|'; break;
#endif
#ifdef S_IFLNK
case S_IFLNK: c = '@'; break;
#endif
#ifdef S_IFSOCK
case S_IFSOCK: c = '='; break;
#endif
#ifdef S_IFDOOR
case S_IFDOOR: c = '>'; break;
#endif
case S_IFREG:
if (f->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
c = '*';
break;
}
default:
c = 0;
}
if (doit && c != 0)
putchar(c);
return c;
}
static int colwidth[MAXCOLS]; /* Need colwidth[i] spaces to print column i. */
static int sizwidth[MAXCOLS]; /* Spaces for the size field in a -X print. */
static int namwidth[MAXCOLS]; /* Name field. */
/*
* Set *aw to the larger of it and w. Then return it.
*/
static int
maxise(int *aw, int w)
{
if (w > *aw) *aw = w;
return *aw;
}
static int nsp = 0; /* This many spaces have not been printed yet. */
#define spaces(n) (nsp = (n))
#define terpri() (nsp = 0, putchar('\n')) /* No trailing spaces */
/*
* Either compute the number of spaces needed to print file f (doit == 0) or
* really print it (doit == 1).
*/
static int
print1(struct file *f, int col, int doit)
{
int width = 0, n;
n = printname(f->name, f, 0);
if (present('m')) {
if (present('p') && (f->mode & S_IFMT) == S_IFDIR)
width++;
if (present('F'))
width++;
}
while (nsp > 0) {
putchar(' ');
nsp--;
}/* Fill gap between two columns */
if (field & FL_INODE) {
char dummy[2];
if (doit)
#ifdef LONGLONG
printf("%*llu ", present('m') ? 1 : 5,
(long long)f->ino);
#else
printf("%*lu ", present('m') ? 1 : 5,
(long)f->ino);
#endif
else
#ifdef LONGLONG
width += snprintf(dummy, sizeof dummy,
"%*llu ", present('m') ? 1 : 5,
(long long)f->ino);
#else
width += snprintf(dummy, sizeof dummy,
"%*lu ", present('m') ? 1 : 5,
(long)f->ino);
#endif
}
if (field & FL_BLOCKS) {
char dummy[2];
if (doit) {
if (present('h'))
printf("%s ", hfmt(f->blocks * 512,
!present('m')));
else
#ifdef LONGLONG
printf("%*llu ", present('m') ? 1 : 4,
(long long)blockcount(f->blocks));
#else
printf("%*lu ", present('m') ? 1 : 4,
(long)blockcount(f->blocks));
#endif
} else {
if (present('h'))
width += 6;
else
#ifdef LONGLONG
width += snprintf(dummy, sizeof dummy, "%*llu ",
present('m') ? 1 : 4,
(long long)f->blocks);
#else
width += snprintf(dummy, sizeof dummy, "%*lu ",
present('m') ? 1 : 4,
(long)f->blocks);
#endif
}
}
if (field & FL_MODE) {
if (doit) {
printf("%s ", permissions(f));
} else {
width += 11;
}
}
if (field & FL_LONG) {
if (doit) {
printf("%2u ", (unsigned) f->nlink);
if (!(field & FL_GROUP)) {
printf("%-8s ", uidname(f->uid));
}
if (!(field & FL_OWNER)) {
printf("%-8s ", gidname(f->gid));
}
switch (f->mode & S_IFMT) {
case S_IFBLK:
case S_IFCHR:
#ifdef S_IFMPB
case S_IFMPB:
#endif
#ifdef S_IFMPC
case S_IFMPC:
#endif
printf("%3lu,%3lu ",
(long)major(f->rdev),
(long)minor(f->rdev));
break;
default:
if (present('h'))
printf("%5s ", hfmt(f->size, 1));
else
#ifdef LONGLONG
printf("%7llu ", (long long)f->size);
#else
printf("%7lu ", (long)f->size);
#endif
}
printf("%s ", timestamp(f));
} else {
width += (field & FL_GROUP) ? 34 : 43;
}
}
if (doit) {
printname(f->name, f, 1);
if (mark(f, 1) != 0)
n++;
if (present('p') && (f->mode & S_IFMT) == S_IFDIR) {
n++;
if (doit)
putchar('/');
}
#ifdef S_IFLNK
if ((field & FL_LONG) && (f->mode & S_IFMT) == S_IFLNK) {
char *buf;
int sz, r, didx;
sz = f->size ? f->size : PATH_MAX;
buf = salloc(sz + 1);
addpath(&didx, f->name);
if ((r = readlink(path, buf, sz)) < 0)
r = 0;
delpath(didx);
buf[r] = 0;
printf(" -> ");
printname(buf, NULL, 1);
free(buf);
n += 4 + r;
}
#endif
if (!present('m'))
spaces(namwidth[col] - n);
} else {
if (mark(f, 0) != 0)
n++;
#ifdef S_IFLNK
if ((field & FL_LONG) && (f->mode & S_IFMT) == S_IFLNK) {
n += 4 + (int) f->size;
}
#endif
if (!present('m')) {
width += maxise(&namwidth[col], n + NSEP);
maxise(&colwidth[col], width);
}
}
return n + width;
}
/*
* Return number of files in the list.
*/
static int
countfiles(struct file *flist)
{
int n = 0;
while (flist != nil) {
n++;
flist = flist->next;
}
return n;
}
static struct file *filecol[MAXCOLS]; /*
* filecol[i] is list of files
* for column i.
*/
static int nfiles, nlines; /* # files to print, # of lines needed. */
/*
* Chop list of files up in columns. Note that 3 columns are used for 5 files
* even though nplin may be 4, filecol[3] will simply be nil.
*/
static void
columnise(struct file **flist, struct file *fsav, int nplin)
{
struct file *f = *flist;
int i, j;
nlines = (nfiles + nplin - 1) / nplin; /* nlines needed for nfiles */
filecol[0] = f;
if (!present('x')) {
for (i = 1; i < nplin; i++) {
/* Give nlines files to each column. */
for (j = 0; j < nlines && f != nil; j++)
f = f->next;
filecol[i] = f;
}
} else {
/*
* Ok, this is an ugly hack. We use the mechanisms for '-C'
* and thus have to change the file list order.
*/
struct file *curcol[MAXCOLS];
resord(fsav);
*flist = fsav;
f = *flist;
for (i = 0; i < nplin && f; i++) {
filecol[i] = curcol[i] = f;
f->flag &= ~ENDCOL;
f = f->next;
}
while (f != NULL) {
for (i = 0; i < nplin && f; i++) {
curcol[i]->next = f;
curcol[i] = f;
f->flag &= ~ENDCOL;
f = f->next;
}
}
for (i = 1; i < nplin; i++) {
curcol[i - 1]->next = filecol[i];
curcol[i - 1]->flag |= ENDCOL;
}
curcol[nplin - 1]->next = NULL;
}
}
/*
* Try (doit == 0), or really print the list of files over nplin columns.
* Return true if it can be done in nplin columns or if nplin == 1.
*/
static int
print(struct file **flist, struct file *fsav, int nplin, int doit)
{
register struct file *f;
register int i, totlen;
if (present('m')) {
if (!doit)
return 1;
totlen = 0;
for (f = *flist; f; f = f->next) {
i = print1(f, 0, 0) + 2;
totlen += i;
if (totlen > ncols+1) {
putchar('\n');
totlen = i;
} else if (f != *flist)
putchar(' ');
print1(f, 0, 1);
if (f->next)
putchar(',');
}
putchar('\n');
return 1;
}
columnise(flist, fsav, nplin);
if (!doit) {
if (nplin == 1)
return 1; /* No need to try 1 column. */
for (i = 0; i < nplin; i++) {
colwidth[i] = sizwidth[i] = namwidth[i] = 0;
}
}
while (--nlines >= 0) {
totlen = 0;
for (i = 0; i < nplin; i++) {
if ((f = filecol[i]) != nil) {
if (f->flag & ENDCOL)
filecol[i] = NULL;
else
filecol[i] = f->next;
print1(f, i, doit);
}
if (!doit && nplin>1) {
/* See if this line is not too long. */
totlen += colwidth[i];
if (totlen > ncols+NSEP)
return 0;
}
}
if (doit && !present('m'))
terpri();
}
return 1;
}
enum depth { SURFACE, SURFACE1, SUBMERGED };
enum state { BOTTOM, SINKING, FLOATING };
/*
* Main workhorse of ls, it sorts and prints the list of files. Flags:
* depth: working with the command line / just one file / listing dir.
* state: How "recursive" do we have to be.
*/
static void
listfiles(struct file *flist, enum depth depth, enum state state, int level)
{
struct file *dlist = nil, **afl = &flist, **adl = &dlist, *fsav;
int nplin, t;
static int white = 1; /* Nothing printed yet. */
/*
* Flush everything previously printed, so new error output will
* not intermix with files listed earlier.
*/
fflush(stdout);
if (field != 0 || state != BOTTOM) { /* Need stat(2) info. */
while (*afl != nil) {
static struct stat st;
int didx;
#ifdef S_IFLNK
int (*status)(const char *file, struct stat *stp) =
depth == SURFACE1 && (field & FL_LONG) == 0
#ifdef SU3
&& !present('F')
#endif /* SU3 */
|| present('L')
|| present('H') && depth != SUBMERGED ?
stat : lstat;
#else
#define status stat
#endif
/* Basic disk block size is 512 except for one niche O.S. */
addpath(&didx, (*afl)->name);
if ((t = status(path, &st)) < 0
#ifdef S_IFLNK
&& (status == lstat || lstat(path, &st) < 0)
#endif
) {
if (depth != SUBMERGED || errno != ENOENT)
report((*afl)->name);
#ifdef SU3
fail:
#endif /* SU3 */
delfile(popfile(afl));
} else {
#ifdef SU3
if (t < 0 && errno == ELOOP && (present('H') ||
present('L'))) {
report((*afl)->name);
goto fail;
}
if (present('L')) {
int i;
for (i = 0; i < level; i++) {
if (st.st_dev==visited[i].v_dev
&& st.st_ino==
visited[i].v_ino) {
fprintf(stderr, "link "
"loop at %s\n",
path);
ex = 1;
goto fail;
}
}
if (level >= vismax) {
vismax += 20;
visited = srealloc(visited,
sizeof *visited
* vismax);
}
visited[level].v_dev = st.st_dev;
visited[level].v_ino = st.st_ino;
}
#endif /* SU3 */
if (((field & FL_MARK) || tinfostat == 1 ||
present('H')) &&
!present('L') &&
status != lstat &&
(st.st_mode & S_IFMT)
!= S_IFDIR) {
struct stat lst;
if (lstat(path, &lst) == 0)
st = lst;
}
setstat(*afl, &st);
afl = &(*afl)->next;
}
delpath(didx);
}
}
sort(&flist);
if (depth == SUBMERGED && (field & (FL_BLOCKS | FL_LONG))) {
printf("total ");
if (present('h'))
printf("%s\n", hfmt(countblocks(flist) * 512, 0));
else
#ifdef LONGLONG
printf("%lld\n", (long long)blockcount(countblocks(flist)));
#else
printf("%ld\n", (long)blockcount(countblocks(flist)));
#endif
}
if (state == SINKING || depth == SURFACE1) {
/* Don't list directories themselves, list their contents later. */
afl = &flist;
while (*afl != nil) {
if (((*afl)->mode & S_IFMT) == S_IFDIR) {
pushfile(adl, popfile(afl));
adl = &(*adl)->next;
} else {
afl = &(*afl)->next;
}
}
}
if ((nfiles = countfiles(flist)) > 0) {
/* Print files in how many columns? */
nplin = !present('C') && !present('x') ?
1 : nfiles < MAXCOLS ? nfiles : MAXCOLS;
fsav = flist;
if (present('x'))
savord(flist);
while (!print(&flist, fsav, nplin, 0))
nplin--; /* Try first */
print(&flist, fsav, nplin, 1); /* Then do it! */
white = 0;
}
while (flist != nil) { /* Destroy file list */
if (state == FLOATING && (flist->mode & S_IFMT) == S_IFDIR) {
/* But keep these directories for ls -R. */
pushfile(adl, popfile(&flist));
adl = &(*adl)->next;
} else {
delfile(popfile(&flist));
}
}
while (dlist != nil) { /* List directories */
if (dotflag(dlist->name) != 'a' || depth != SUBMERGED) {
int didx;
addpath(&didx, dlist->name);
flist = nil;
if (adddir(&flist, path)) {
if (depth != SURFACE1) {
if (!white)
putchar('\n');
white = 0;
}
if (depth != SURFACE1 || present('R'))
printf("%s:\n", path);
listfiles(flist, SUBMERGED,
state == FLOATING ? FLOATING : BOTTOM,
level + 1);
}
delpath(didx);
}
delfile(popfile(&dlist));
}
}
#ifndef UCB
static void
usage(void)
{
if (personality == PER_LS)
fprintf(stderr, "usage: %s -1RadCxmnlogrtucpFbqisfL [files]\n",
arg0);
exit(2);
}
#else /* UCB */
#define usage()
#endif /* UCB */
int
main(int argc, char **argv)
{
struct file *flist = nil, **aflist = &flist;
enum depth depth;
struct winsize ws;
int i;
char *cp;
#ifdef __GLIBC__
putenv("POSIXLY_CORRECT=1");
#endif
setlocale(LC_COLLATE, "");
setlocale(LC_CTYPE, "");
setlocale(LC_TIME, "");
#ifndef UCB
if (getenv("SYSV3") != NULL)
sysv3 = 1;
#endif /* !UCB */
uid = geteuid();
gid = getegid();
arg0 = basename(argv[0]);
if (strcmp(arg0, "dir") == 0) {
flags['s'] = flags['U'] = 1;
personality = PER_DIR;
} else if (strcmp(arg0, "lc") == 0) {
flags['C'] = 1;
istty = 1;
}
if (istty || isatty(1)) {
istty = 1;
#if !defined (USE_TERMCAP)
setupterm(NULL, 1, &tinfostat);
#else /* USE_TERMCAP */
{
char buf[2048];
if ((cp = getenv("TERM")) != NULL)
if (tgetent(buf, cp) > 0)
tinfostat = 1;
}
#endif /* USE_TERMCAP */
field |= FL_STATUS;
}
while ((i = getopt(argc, argv, personalities[personality].per_opt))
!= EOF) {
switch (i) {
case '?':
usage();
break;
case 'O':
flags['U'] = 0;
break;
case '1':
flags[i] = 1;
flags['C'] = 0;
break;
case 'L':
flags[i] = 1;
flags['H'] = 0;
break;
case 'H':
flags[i] = 1;
flags['L'] = 0;
break;
#if defined (SUS) || defined (SU3)
case 'C':
flags[i] = 1;
flags['l'] = flags['m'] = flags['x'] = flags ['o'] =
flags['g'] = flags['n'] = flags['1'] = 0;
break;
case 'm':
flags[i] = 1;
flags['l'] = flags['C'] = flags['x'] = flags ['o'] =
flags['g'] = flags['n'] = flags['1'] = 0;
break;
case 'l':
flags[i] = 1;
flags['m'] = flags['C'] = flags['x'] = flags ['o'] =
flags['g'] = 0;
break;
case 'x':
flags[i] = 1;
flags['m'] = flags['C'] = flags['l'] = flags ['o'] =
flags['g'] = flags['n'] = flags['1'] = 0;
break;
case 'g':
flags[i] = 1;
flags['m'] = flags['C'] = flags['l'] = flags ['x'] =
flags['o'] = 0;
break;
case 'o':
flags[i] = 1;
flags['m'] = flags['C'] = flags['l'] = flags ['x'] =
flags['g'] = 0;
break;
case 'n':
flags[i] = 1;
flags['m'] = flags['C'] = flags ['x'] = 0;
break;
#endif /* !SUS, !SU3 */
default:
flags[i] = 1;
}
}
#ifdef UCB
if (present('l')) {
if (present('g'))
flags['g'] = 0;
else
flags['o'] = 1;
} else if (present('g'))
flags['g'] = 0;
#endif
#ifdef UCB
if (SUPER_ID == 0 || present('a'))
flags['A'] = 1;
#else /* !UCB */
if (present('a'))
flags['A'] = 1;
#endif /* !UCB */
if (present('i'))
field |= FL_INODE;
if (present('s'))
field |= FL_BLOCKS;
if (present('t'))
field |= FL_BYTIME;
if (present('u'))
field |= FL_ATIME;
if (present('c'))
field |= FL_CTIME;
if (present('l') || present('n') || present('g') || present('o')) {
field = field | FL_MODE | FL_LONG;
flags['m'] = flags['C'] = flags['x'] = 0;
}
if (present('g'))
field = field | FL_MODE | FL_LONG | FL_GROUP;
if (present('o'))
field = field | FL_MODE | FL_LONG | FL_OWNER;
if (present('F'))
field |= FL_MARK;
if (present('d'))
field |= FL_DIR;
if (present('f')) {
field &= ~(FL_LONG|FL_BYTIME|FL_BLOCKS|FL_MODE|FL_MARK|FL_DIR);
flags['o'] = flags['g'] = flags['l'] = flags['t'] = flags['s'] =
flags['r'] = flags['d'] = flags['F'] = flags['R'] =
flags['p'] = 0;
flags['a'] = flags['A'] = 1;
field |= FL_STATUS;
}
if (!present('1') && !present('C') && !present('l') &&
!present('o') && !present('g') && !present('m') &&
(istty && !sysv3 || present('x')))
flags['C'] = 1;
if (istty)
flags['q'] = 1;
if ((cp = getenv("COLUMNS")) != NULL) {
ncols = atoi(cp);
} else if ((present('C') || present('x') || present('m')) && istty) {
if (ioctl(1, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
ncols = ws.ws_col - 1;
#if !defined (USE_TERMCAP)
else if (tinfostat == 1 && columns > 0)
ncols = columns;
#endif /* !USE_TERMCAP */
}
depth = SURFACE;
if (optind == argc) {
if (!(field & FL_DIR))
depth = SURFACE1;
pushfile(aflist, newfile("."));
} else {
if (optind+1 == argc && !(field & FL_DIR))
depth = SURFACE1;
while (optind < argc) {
if (present('f')) {
struct stat st;
if (stat(argv[optind], &st) == 0 &&
(st.st_mode & S_IFMT)
!= S_IFDIR) {
#ifdef UCB
fprintf(stderr, "%s unreadable\n",
argv[optind]);
#else
fprintf(stderr, "%s: Not a directory\n",
argv[optind]);
#endif
ex = 1;
optind++;
continue;
}
}
pushfile(aflist, newfile(argv[optind++]));
aflist = &(*aflist)->next;
}
}
listfiles(flist, depth,
(field & FL_DIR) ? BOTTOM : present('R') ? FLOATING : SINKING,
0);
return ex;
}
syntax highlighted by Code2HTML, v. 0.9.1