aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/ls.c
diff options
context:
space:
mode:
Diffstat (limited to 'toys/posix/ls.c')
-rw-r--r--toys/posix/ls.c155
1 files changed, 111 insertions, 44 deletions
diff --git a/toys/posix/ls.c b/toys/posix/ls.c
index 15511059..56d5c638 100644
--- a/toys/posix/ls.c
+++ b/toys/posix/ls.c
@@ -11,40 +11,45 @@
* add -Z -ll --color
* Posix says the -l date format should vary based on how recent it is
* and we do --time-style=long-iso instead
+ * ignore -k because we default to 1024 byte blocks
-USE_LS(NEWTOY(ls, "(color):;(full-time)(show-control-chars)ZgoACFHLRSabcdfhikl@mnpqrstuw#=80<0x1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL][!qb]", TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_LS(NEWTOY(ls, "(sort):(color):;(full-time)(show-control-chars)\241(group-directories-first)\376ZgoACFHLNRSUXabcdfhikl@mnpqrstuw#=80<0x1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL][-Nqb]", TOYFLAG_BIN|TOYFLAG_LOCALE))
config LS
bool "ls"
default y
help
- usage: ls [-ACFHLRSZacdfhiklmnpqrstuwx1] [--color[=auto]] [FILE...]
+ usage: ls [-1ACFHLNRSUXZabcdfghilmnopqrstuwx] [--color[=auto]] [FILE...]
- List files.
+ List files
what to show:
- -a all files including .hidden -b escape nongraphic chars
- -c use ctime for timestamps -d directory, not contents
- -i inode number -p put a '/' after dir names
- -q unprintable chars as '?' -s storage used (1024 byte units)
- -u use access time for timestamps -A list all files but . and ..
- -H follow command line symlinks -L follow symlinks
- -R recursively list in subdirs -F append /dir *exe @sym |FIFO
+ -A all files except . and .. -a all files including .hidden
+ -b escape nongraphic chars -d directory, not contents
+ -F append /dir *exe @sym |FIFO -f files (no sort/filter/format)
+ -H follow command line symlinks -i inode number
+ -L follow symlinks -N no escaping, even on tty
+ -p put '/' after dir names -q unprintable chars as '?'
+ -R recursively list in subdirs -s storage used (1024 byte units)
-Z security context
output formats:
-1 list one file per line -C columns (sorted vertically)
-g like -l but no owner -h human readable sizes
- -l long (show full details) -m comma separated
- -n like -l but numeric uid/gid -o like -l but no group
+ -l long (show full details) -ll long with nanoseconds (--full-time)
+ -m comma separated -n long with numeric uid/gid
+ -o long without group column -r reverse order
-w set column width -x columns (horizontal sort)
- -ll long with nanoseconds (--full-time)
- --color device=yellow symlink=turquoise/red dir=blue socket=purple
- files: exe=green suid=red suidfile=redback stickydir=greenback
- =auto means detect if output is a tty.
- sorting (default is alphabetical):
- -f unsorted -r reverse -t timestamp -S size
+ sort by: (also --sort=longname,longname... ends with alphabetical)
+ -c ctime -r reverse -S size -t time -u atime -U none
+ -X extension -! dirfirst -~ nocase
+
+ --color =always (default) =auto (when stdout is tty) =never
+ exe=green suid=red suidfile=redback stickydir=greenback
+ device=yellow symlink=turquoise/red dir=blue socket=purple
+
+ Long output uses -cu for display, use -ltc/-ltu to also sort by ctime/atime.
*/
#define FOR_ls
@@ -55,9 +60,8 @@ config LS
// ls -lR starts .: then ./subdir:
GLOBALS(
- long w;
- long l;
- char *color;
+ long w, l;
+ char *color, *sort;
struct dirtree *files, *singledir;
unsigned screen_width;
@@ -155,24 +159,86 @@ static void entrylen(struct dirtree *dt, unsigned *len)
len[7] = FLAG(Z) ? strwidth((char *)dt->extra) : 0;
}
+// Perform one or more comparisons on a pair of files.
+// Reused FLAG_a to mean "alphabetical"
+static int do_compare(struct dirtree *a, struct dirtree *b, long flags)
+{
+ struct timespec *ts1 = 0, *ts2;
+ char *s1, *s2;
+ int ret;
+
+// TODO -? nocase -! dirfirst
+
+ if (flags&FLAG_S) {
+ if (a->st.st_size > b->st.st_size) return -1;
+ else if (a->st.st_size < b->st.st_size) return 1;
+ }
+
+ if (flags&FLAG_t) ts1 = &a->st.st_mtim, ts2 = &b->st.st_mtim;
+ if (flags&FLAG_u) ts1 = &a->st.st_atim, ts2 = &b->st.st_atim;
+ if (flags&FLAG_c) ts1 = &a->st.st_ctim, ts2 = &b->st.st_ctim;
+ if (ts1) {
+ if (ts1->tv_sec > ts2->tv_sec) return -1;
+ else if (ts1->tv_sec < ts2->tv_sec) return 1;
+ else if (ts1->tv_nsec > ts2->tv_nsec) return -1;
+ else if (ts1->tv_nsec < ts2->tv_nsec) return 1;
+ }
+ if (flags&FLAG_X21) // dirfirst
+ if (S_ISDIR(a->st.st_mode)!=S_ISDIR(b->st.st_mode))
+ return S_ISDIR(a->st.st_mode) ? -1 : 1;
+
+ // -X is a form of alphabetical sort, without -~ do case sensitive comparison
+ if ((flags&FLAG_X) && (s1 = strrchr(a->name, '.')) && (s2 = strrchr(b->name, '.'))) {
+ if (!(flags&FLAG_X7E)) flags |= FLAG_a;
+ } else {
+ s1 = a->name;
+ s2 = b->name;
+ }
+
+ // case insensitive sort falls back to case sensitive sort when equal
+ ret = (flags&FLAG_X7E) ? strcasecmp(s1, s2) : 0;
+ if (!ret && (flags&FLAG_a)) ret = strcmp(s1, s2);
+
+ return ret;
+}
+
+int comma_start(char **aa, char *b)
+{
+ return strstart(aa, b) && (!**aa || **aa==',');
+}
+
+// callback for qsort
static int compare(void *a, void *b)
{
struct dirtree *dta = *(struct dirtree **)a;
struct dirtree *dtb = *(struct dirtree **)b;
- int ret = 0, reverse = FLAG(r) ? -1 : 1;
-
- if (FLAG(S)) {
- if (dta->st.st_size > dtb->st.st_size) ret = -1;
- else if (dta->st.st_size < dtb->st.st_size) ret = 1;
- }
- if (FLAG(t)) {
- if (dta->st.st_mtime > dtb->st.st_mtime) ret = -1;
- else if (dta->st.st_mtime < dtb->st.st_mtime) ret = 1;
- else if (dta->st.st_mtim.tv_nsec > dtb->st.st_mtim.tv_nsec) ret = -1;
- else if (dta->st.st_mtim.tv_nsec < dtb->st.st_mtim.tv_nsec) ret = 1;
+ char *ss = TT.sort;
+ long long ll = 0;
+ int ret = 0;
+
+// TODO: test --sort=reverse with fallback alphabetical
+
+ if (ss) while (*ss) {
+ if (comma_start(&ss, "reverse")) toys.optflags |= FLAG_r;
+ else if (comma_start(&ss, "none")) goto skip;
+ else if (ret) continue;
+ else if (comma_start(&ss, "ctime")) ll = FLAG_c;
+ else if (comma_start(&ss, "size")) ll = FLAG_S;
+ else if (comma_start(&ss, "time")) ll = FLAG_t;
+ else if (comma_start(&ss, "atime")) ll = FLAG_u;
+ else if (comma_start(&ss, "nocase")) ll = FLAG_X7E;
+ else if (comma_start(&ss, "extension")) ll = FLAG_X;
+ else if (comma_start(&ss, "dirfirst")) ll = FLAG_X21;
+ else error_exit("bad --sort %s", ss);
+
+ ret = do_compare(dta, dtb, ll);
}
- if (!ret) ret = strcmp(dta->name, dtb->name);
- return ret * reverse;
+
+ if (!ret) ret = do_compare(dta, dtb, toys.optflags|FLAG_a);
+skip:
+ if (FLAG(r)) ret *= -1;
+
+ return ret;
}
// callback from dirtree_recurse() determining how to handle this entry.
@@ -203,7 +269,7 @@ static int filter(struct dirtree *new)
new->st.st_blocks >>= 1; // Use 1KiB blocks rather than 512B blocks.
if (FLAG(a)||FLAG(f)) return DIRTREE_SAVE;
- if (!FLAG(A) && new->name[0]=='.') return 0;
+ if (!FLAG(A) && *new->name=='.') return 0;
return dirtree_notdotdot(new) & DIRTREE_SAVE;
}
@@ -290,8 +356,8 @@ static void listfiles(int dirfd, struct dirtree *indir)
if (toys.optflags == (FLAG_1|FLAG_f)) return;
// Read directory contents. We dup() the fd because this will close it.
// This reads/saves contents to display later, except for in "ls -1f" mode.
- } else dirtree_recurse(indir, filter, dup(dirfd),
- DIRTREE_STATLESS|DIRTREE_SYMFOLLOW*!!FLAG(L));
+ } else dirtree_recurse(indir, filter, dirfd,
+ DIRTREE_STATLESS|DIRTREE_SYMFOLLOW*FLAG(L));
// Copy linked list to array and sort it. Directories go in array because
// we visit them in sorted order too. (The nested loops let us measure and
@@ -315,7 +381,7 @@ static void listfiles(int dirfd, struct dirtree *indir)
if (!FLAG(f)) {
unsigned long long blocks = 0;
- qsort(sort, dtlen, sizeof(void *), (void *)compare);
+ if (!FLAG(U)) qsort(sort, dtlen, sizeof(void *), (void *)compare);
for (ul = 0; ul<dtlen; ul++) {
entrylen(sort[ul], len);
for (width = 0; width<8; width++)
@@ -379,9 +445,7 @@ static void listfiles(int dirfd, struct dirtree *indir)
// Handle padding and wrapping for display purposes
entrylen(dt, len);
if (ul) {
- int mm = !!FLAG(m);
-
- if (mm) xputc(',');
+ if (FLAG(m)) xputc(',');
if (FLAG(C)||FLAG(x)) {
if (!curcol) xputc('\n');
else {
@@ -392,8 +456,8 @@ static void listfiles(int dirfd, struct dirtree *indir)
xputc('\n');
width = 0;
} else {
- printf(" "+mm, 0); // shut up the stupid compiler
- width += 2-mm;
+ xputsn(" "+FLAG(m));
+ width += 2-FLAG(m);
}
}
width += *len;
@@ -518,6 +582,9 @@ void ls_main(void)
if (TT.color) toys.optflags ^= FLAG_color;
}
+ // -N *doesn't* disable -q; you need --show-control-chars for that.
+ if (FLAG(N)) toys.optflags &= ~FLAG_b;
+
TT.screen_width = 80;
if (FLAG(w)) TT.screen_width = TT.w+2;
else terminal_size(&TT.screen_width, NULL);
@@ -541,7 +608,7 @@ void ls_main(void)
// note: double_list->prev temporarily goes in dirtree->parent
if (dt) {
- if (dt->again&2) {
+ if (dt->again&DIRTREE_STATLESS) {
perror_msg_raw(*s);
free(dt);
} else dlist_add_nomalloc((void *)&TT.files->child, (void *)dt);