blob: 9a0c3e2773254eb6650da38ac2534832e6e8ff0f [file] [log] [blame]
/* vi: set sw=4 ts=4:
*
* ls.c - list files
*
* Copyright 2012 Andre Renaud <andre@bluewatersys.com>
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
USE_LS(NEWTOY(ls, "nRlF1a", TOYFLAG_BIN))
config LS
bool "ls"
default n
help
usage: ls [-l] [-F] [-a] [-1] [directory...]
list files
-F append a character as a file type indicator
-a list all files
-1 list one file per line
-l show full details for each file
*/
/* So that we can do 64-bit stat etc... */
#define _FILE_OFFSET_BITS 64
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include "toys.h"
#define FLAG_a 1
#define FLAG_1 2
#define FLAG_F 4
#define FLAG_l 8
#define FLAG_R 16
#define FLAG_n 32
static int dir_filter(const struct dirent *d)
{
/* Skip over the . & .. entries unless -a is given */
if (!(toys.optflags & FLAG_a))
if (d->d_name[0] == '.')
return 0;
return 1;
}
static void do_ls(int fd, char *name)
{
struct dirent **entries;
int nentries;
int i;
int maxwidth = -1;
int ncolumns = 1;
struct dirent file_dirent;
struct dirent *file_direntp;
if (!name || strcmp(name, "-") == 0)
name = ".";
if (toys.optflags & FLAG_R)
xprintf("\n%s:\n", name);
/* Get all the files in this directory */
nentries = scandir(name, &entries, dir_filter, alphasort);
if (nentries < 0) {
/* We've just selected a single file, so create a single-length list */
/* FIXME: This means that ls *.x results in a whole bunch of single
* listings, not one combined listing.
*/
if (errno == ENOTDIR) {
nentries = 1;
strcpy(file_dirent.d_name, name);
file_direntp = &file_dirent;
entries = &file_direntp;
} else
perror_exit("ls: cannot access %s'", name);
}
/* Determine the widest entry so we can flow them properly */
if (!(toys.optflags & FLAG_1)) {
int columns;
char *columns_str;
for (i = 0; i < nentries; i++) {
struct dirent *ent = entries[i];
int width;
width = strlen(ent->d_name);
if (width > maxwidth)
maxwidth = width;
}
/* We always want at least a single space for each entry */
maxwidth++;
if (toys.optflags & FLAG_F)
maxwidth++;
columns_str = getenv("COLUMNS");
columns = columns_str ? atoi(columns_str) : 80;
ncolumns = maxwidth ? columns / maxwidth : 1;
}
for (i = 0; i < nentries; i++) {
struct dirent *ent = entries[i];
int len = strlen(ent->d_name);
struct stat st;
int stat_valid = 0;
sprintf(toybuf, "%s/%s", name, ent->d_name);
/* Provide the ls -l long output */
if (toys.optflags & FLAG_l) {
char type;
char timestamp[64];
struct tm mtime;
if (lstat(toybuf, &st))
perror_exit("Can't stat %s", toybuf);
stat_valid = 1;
if (S_ISDIR(st.st_mode))
type = 'd';
else if (S_ISCHR(st.st_mode))
type = 'c';
else if (S_ISBLK(st.st_mode))
type = 'b';
else if (S_ISLNK(st.st_mode))
type = 'l';
else
type = '-';
xprintf("%c%c%c%c%c%c%c%c%c%c ", type,
(st.st_mode & S_IRUSR) ? 'r' : '-',
(st.st_mode & S_IWUSR) ? 'w' : '-',
(st.st_mode & S_IXUSR) ? 'x' : '-',
(st.st_mode & S_IRGRP) ? 'r' : '-',
(st.st_mode & S_IWGRP) ? 'w' : '-',
(st.st_mode & S_IXGRP) ? 'x' : '-',
(st.st_mode & S_IROTH) ? 'r' : '-',
(st.st_mode & S_IWOTH) ? 'w' : '-',
(st.st_mode & S_IXOTH) ? 'x' : '-');
xprintf("%2d ", st.st_nlink);
if (toys.optflags & FLAG_n) {
xprintf("%4d ", st.st_uid);
xprintf("%4d ", st.st_gid);
} else {
struct passwd *pwd = getpwuid(st.st_uid);
struct group *grp = getgrgid(st.st_gid);
if (!pwd)
xprintf("%4d ", st.st_uid);
else
xprintf("%-10s ", pwd->pw_name);
if (!grp)
xprintf("%4d ", st.st_gid);
else
xprintf("%-10s ", grp->gr_name);
}
if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))
xprintf("%3d, %3d ", major(st.st_rdev), minor(st.st_rdev));
else
xprintf("%12lld ", st.st_size);
localtime_r(&st.st_mtime, &mtime);
strftime(timestamp, sizeof(timestamp), "%b %e %H:%M", &mtime);
xprintf("%s ", timestamp);
}
xprintf("%s", ent->d_name);
/* Append the file-type indicator character */
if (toys.optflags & FLAG_F) {
if (!stat_valid) {
if (lstat(toybuf, &st))
perror_exit("Can't stat %s", toybuf);
stat_valid = 1;
}
if (S_ISDIR(st.st_mode)) {
xprintf("/");
len++;
} else if (S_ISREG(st.st_mode) &&
(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
xprintf("*");
len++;
} else if (S_ISLNK(st.st_mode)) {
xprintf("@");
len++;
}
}
if (toys.optflags & FLAG_1) {
xprintf("\n");
} else {
if (i % ncolumns == ncolumns - 1)
xprintf("\n");
else
xprintf("%*s", maxwidth - len, "");
}
}
/* Make sure we put at a trailing new line in */
if (!(toys.optflags & FLAG_1) && (i % ncolumns))
xprintf("\n");
if (toys.optflags & FLAG_R) {
for (i = 0; i < nentries; i++) {
struct dirent *ent = entries[i];
struct stat st;
char dirname[PATH_MAX];
sprintf(dirname, "%s/%s", name, ent->d_name);
if (lstat(dirname, &st))
perror_exit("Can't stat %s", dirname);
if (S_ISDIR(st.st_mode))
do_ls(0, dirname);
}
}
}
void ls_main(void)
{
/* If the output is not a TTY, then just do one-file per line
* This makes ls easier to use with other command line tools (grep/awk etc...)
*/
if (!isatty(fileno(stdout)))
toys.optflags |= FLAG_1;
/* Long output must be one-file per line */
if (toys.optflags & FLAG_l)
toys.optflags |= FLAG_1;
loopfiles(toys.optargs, do_ls);
}