sbase/ls.c
Michael Forney b91b9e79ec Revert "Fix showing directories when -R flag is set in ls(1)"
This reverts commit bb83eade39.

This commit causes the loop through dents at the end of main to
continue past the end of the dents array, causing a crash when
called with multiple directory arguments.
2016-07-09 10:09:50 +01:00

488 lines
9.4 KiB
C

/* See LICENSE file for copyright and license details. */
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "utf.h"
#include "util.h"
struct entry {
char *name;
mode_t mode, tmode;
nlink_t nlink;
uid_t uid;
gid_t gid;
off_t size;
struct timespec t;
dev_t dev;
dev_t rdev;
ino_t ino, tino;
};
static struct {
dev_t dev;
ino_t ino;
} tree[PATH_MAX];
static int ret = 0;
static int Aflag = 0;
static int aflag = 0;
static int cflag = 0;
static int dflag = 0;
static int Fflag = 0;
static int fflag = 0;
static int Hflag = 0;
static int hflag = 0;
static int iflag = 0;
static int Lflag = 0;
static int lflag = 0;
static int nflag = 0;
static int pflag = 0;
static int qflag = 0;
static int Rflag = 0;
static int rflag = 0;
static int Uflag = 0;
static int uflag = 0;
static int first = 1;
static char sort = 0;
static size_t ds = 0;
static void ls(const char *, const struct entry *, int);
static void
mkent(struct entry *ent, char *path, int dostat, int follow)
{
struct stat st;
ent->name = path;
if (!dostat)
return;
if ((follow ? stat : lstat)(path, &st) < 0)
eprintf("%s %s:", follow ? "stat" : "lstat", path);
ent->mode = st.st_mode;
ent->nlink = st.st_nlink;
ent->uid = st.st_uid;
ent->gid = st.st_gid;
ent->size = st.st_size;
if (cflag)
ent->t = st.st_ctim;
else if (uflag)
ent->t = st.st_atim;
else
ent->t = st.st_mtim;
ent->dev = st.st_dev;
ent->rdev = st.st_rdev;
ent->ino = st.st_ino;
if (S_ISLNK(ent->mode)) {
if (stat(path, &st) == 0) {
ent->tmode = st.st_mode;
ent->dev = st.st_dev;
ent->tino = st.st_ino;
} else {
ent->tmode = ent->tino = 0;
}
}
}
static char *
indicator(mode_t mode)
{
if (pflag || Fflag)
if (S_ISDIR(mode))
return "/";
if (Fflag) {
if (S_ISLNK(mode))
return "@";
else if (S_ISFIFO(mode))
return "|";
else if (S_ISSOCK(mode))
return "=";
else if (mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH)
return "*";
}
return "";
}
static void
output(const struct entry *ent)
{
struct group *gr;
struct passwd *pw;
struct tm *tm;
ssize_t len;
size_t l;
char *name, *c, *u, *fmt, buf[BUFSIZ],
pwname[_SC_LOGIN_NAME_MAX], grname[_SC_LOGIN_NAME_MAX],
mode[] = "----------";
Rune r;
if (qflag) {
name = emalloc(strlen(ent->name) + 1);
for (c = name, u = ent->name; *u; u += l) {
l = chartorune(&r, u);
if (isprintrune(r)) {
memcpy(c, u, l);
c += l;
} else {
*c++ = '?';
}
}
*c = '\0';
} else {
name = ent->name;
}
if (iflag)
printf("%lu ", (unsigned long)ent->ino);
if (!lflag) {
printf("%s%s\n", name, indicator(ent->mode));
goto cleanup;
}
if (S_ISREG(ent->mode))
mode[0] = '-';
else if (S_ISBLK(ent->mode))
mode[0] = 'b';
else if (S_ISCHR(ent->mode))
mode[0] = 'c';
else if (S_ISDIR(ent->mode))
mode[0] = 'd';
else if (S_ISFIFO(ent->mode))
mode[0] = 'p';
else if (S_ISLNK(ent->mode))
mode[0] = 'l';
else if (S_ISSOCK(ent->mode))
mode[0] = 's';
else
mode[0] = '?';
if (ent->mode & S_IRUSR) mode[1] = 'r';
if (ent->mode & S_IWUSR) mode[2] = 'w';
if (ent->mode & S_IXUSR) mode[3] = 'x';
if (ent->mode & S_IRGRP) mode[4] = 'r';
if (ent->mode & S_IWGRP) mode[5] = 'w';
if (ent->mode & S_IXGRP) mode[6] = 'x';
if (ent->mode & S_IROTH) mode[7] = 'r';
if (ent->mode & S_IWOTH) mode[8] = 'w';
if (ent->mode & S_IXOTH) mode[9] = 'x';
if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
if (!nflag && (pw = getpwuid(ent->uid)))
snprintf(pwname, sizeof(pwname), "%s", pw->pw_name);
else
snprintf(pwname, sizeof(pwname), "%d", ent->uid);
if (!nflag && (gr = getgrgid(ent->gid)))
snprintf(grname, sizeof(grname), "%s", gr->gr_name);
else
snprintf(grname, sizeof(grname), "%d", ent->gid);
if (time(NULL) > ent->t.tv_sec + (180 * 24 * 60 * 60)) /* 6 months ago? */
fmt = "%b %d %Y";
else
fmt = "%b %d %H:%M";
if ((tm = localtime(&ent->t.tv_sec)))
strftime(buf, sizeof(buf), fmt, tm);
else
snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname);
if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
else if (hflag)
printf("%10s ", humansize(ent->size));
else
printf("%10lu ", (unsigned long)ent->size);
printf("%s %s%s", buf, ent->name, indicator(ent->mode));
if (S_ISLNK(ent->mode)) {
if ((len = readlink(ent->name, buf, sizeof(buf) - 1)) < 0)
eprintf("readlink %s:", ent->name);
buf[len] = '\0';
printf(" -> %s%s", buf, indicator(ent->tmode));
}
putchar('\n');
cleanup:
if (qflag)
free(name);
}
static int
entcmp(const void *va, const void *vb)
{
int cmp = 0;
const struct entry *a = va, *b = vb;
switch (sort) {
case 'S':
cmp = b->size - a->size;
break;
case 't':
if (!(cmp = b->t.tv_sec - a->t.tv_sec))
cmp = b->t.tv_nsec - a->t.tv_nsec;
break;
}
if (!cmp)
cmp = strcmp(a->name, b->name);
return rflag ? 0 - cmp : cmp;
}
static void
lsdir(const char *path, const struct entry *dir)
{
DIR *dp;
struct entry *ent, *ents = NULL;
struct dirent *d;
size_t i, n = 0;
char prefix[PATH_MAX];
if (!(dp = opendir(dir->name))) {
ret = 1;
weprintf("opendir %s%s:", path, dir->name);
return;
}
if (chdir(dir->name) < 0)
eprintf("chdir %s:", dir->name);
while ((d = readdir(dp))) {
if (d->d_name[0] == '.' && !aflag && !Aflag)
continue;
else if (Aflag)
if (strcmp(d->d_name, ".") == 0 ||
strcmp(d->d_name, "..") == 0)
continue;
ents = ereallocarray(ents, ++n, sizeof(*ents));
mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
lflag || pflag || Rflag || sort, Lflag);
}
closedir(dp);
if (!Uflag)
qsort(ents, n, sizeof(*ents), entcmp);
if (ds > 1 && (path[0] || dir->name[0] != '.'))
printf("%s:\n", dir->name);
for (i = 0; i < n; i++)
output(&ents[i]);
if (Rflag) {
if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >=
PATH_MAX)
eprintf("path too long: %s%s\n", path, dir->name);
for (i = 0; i < n; i++) {
ent = &ents[i];
if (strcmp(ent->name, ".") == 0 ||
strcmp(ent->name, "..") == 0)
continue;
if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag)
continue;
ls(prefix, ent, Rflag);
}
}
for (i = 0; i < n; ++i)
free(ents[i].name);
free(ents);
}
static int
visit(const struct entry *ent)
{
dev_t dev;
ino_t ino;
int i;
dev = ent->dev;
ino = S_ISLNK(ent->mode) ? ent->tino : ent->ino;
for (i = 0; i < PATH_MAX && tree[i].ino; ++i) {
if (ino == tree[i].ino && dev == tree[i].dev)
return -1;
}
tree[i].ino = ino;
tree[i].dev = dev;
return i;
}
static void
ls(const char *path, const struct entry *ent, int listdir)
{
int treeind;
char cwd[PATH_MAX];
if (!listdir) {
output(ent);
} else if (S_ISDIR(ent->mode) ||
(S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
if ((treeind = visit(ent)) < 0) {
ret = 1;
weprintf("%s%s: Already visited\n", path, ent->name);
return;
}
if (!getcwd(cwd, PATH_MAX))
eprintf("getcwd:");
if (first)
first = !first;
else
putchar('\n');
fputs(path, stdout);
lsdir(path, ent);
tree[treeind].ino = 0;
if (chdir(cwd) < 0)
eprintf("chdir %s:", cwd);
}
}
static void
usage(void)
{
eprintf("usage: %s [-1AacdFfHhiLlnpqRrtUu] [file ...]\n", argv0);
}
int
main(int argc, char *argv[])
{
struct entry ent, *dents, *fents;
size_t i, fs;
ARGBEGIN {
case '1':
/* force output to 1 entry per line */
qflag = 1;
break;
case 'A':
Aflag = 1;
break;
case 'a':
aflag = 1;
break;
case 'c':
cflag = 1;
uflag = 0;
break;
case 'd':
dflag = 1;
break;
case 'f':
aflag = 1;
fflag = 1;
Uflag = 1;
break;
case 'F':
Fflag = 1;
break;
case 'H':
Hflag = 1;
break;
case 'h':
hflag = 1;
break;
case 'i':
iflag = 1;
break;
case 'L':
Lflag = 1;
break;
case 'l':
lflag = 1;
break;
case 'n':
lflag = 1;
nflag = 1;
break;
case 'p':
pflag = 1;
break;
case 'q':
qflag = 1;
break;
case 'R':
Rflag = 1;
break;
case 'r':
rflag = 1;
break;
case 'S':
sort = 'S';
break;
case 't':
sort = 't';
break;
case 'U':
Uflag = 1;
break;
case 'u':
uflag = 1;
cflag = 0;
break;
default:
usage();
} ARGEND
switch (argc) {
case 0: /* fallthrough */
*--argv = ".", ++argc;
case 1:
mkent(&ent, argv[0], 1, Hflag || Lflag);
ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
((S_ISLNK(ent.mode) && S_ISDIR(ent.tmode)) &&
((Hflag || Lflag) || !(dflag || Fflag || lflag))));
break;
default:
for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) {
mkent(&ent, argv[i], 1, Hflag || Lflag);
if ((!dflag && S_ISDIR(ent.mode)) ||
((S_ISLNK(ent.mode) && S_ISDIR(ent.tmode)) &&
((Hflag || Lflag) || !(dflag || Fflag || lflag)))) {
dents = ereallocarray(dents, ++ds, sizeof(*dents));
memcpy(&dents[ds - 1], &ent, sizeof(ent));
} else {
fents = ereallocarray(fents, ++fs, sizeof(*fents));
memcpy(&fents[fs - 1], &ent, sizeof(ent));
}
}
qsort(fents, fs, sizeof(ent), entcmp);
qsort(dents, ds, sizeof(ent), entcmp);
for (i = 0; i < fs; ++i)
ls("", &fents[i], 0);
free(fents);
if (fs && ds)
putchar('\n');
for (i = 0; i < ds; ++i)
ls("", &dents[i], 1);
free(dents);
}
return (fshut(stdout, "<stdout>") | ret);
}