aboutsummaryrefslogtreecommitdiff
path: root/toys/posix
diff options
context:
space:
mode:
Diffstat (limited to 'toys/posix')
-rw-r--r--toys/posix/cat.c60
-rw-r--r--toys/posix/chgrp.c17
-rw-r--r--toys/posix/cksum.c30
-rw-r--r--toys/posix/cmp.c22
-rw-r--r--toys/posix/comm.c16
-rw-r--r--toys/posix/cp.c34
-rw-r--r--toys/posix/cpio.c131
-rw-r--r--toys/posix/cut.c6
-rw-r--r--toys/posix/date.c4
-rw-r--r--toys/posix/df.c4
-rw-r--r--toys/posix/du.c19
-rw-r--r--toys/posix/file.c84
-rw-r--r--toys/posix/find.c44
-rw-r--r--toys/posix/grep.c229
-rw-r--r--toys/posix/head.c7
-rw-r--r--toys/posix/iconv.c1
-rw-r--r--toys/posix/kill.c5
-rw-r--r--toys/posix/ln.c6
-rw-r--r--toys/posix/logger.c22
-rw-r--r--toys/posix/ls.c155
-rw-r--r--toys/posix/nohup.c11
-rw-r--r--toys/posix/od.c24
-rw-r--r--toys/posix/patch.c35
-rw-r--r--toys/posix/printf.c19
-rw-r--r--toys/posix/ps.c33
-rw-r--r--toys/posix/pwd.c3
-rw-r--r--toys/posix/rm.c2
-rw-r--r--toys/posix/sed.c236
-rw-r--r--toys/posix/sleep.c9
-rw-r--r--toys/posix/sort.c23
-rw-r--r--toys/posix/tail.c19
-rw-r--r--toys/posix/tar.c322
-rw-r--r--toys/posix/test.c50
-rw-r--r--toys/posix/time.c2
-rw-r--r--toys/posix/touch.c1
-rw-r--r--toys/posix/ulimit.c1
-rw-r--r--toys/posix/uname.c3
-rw-r--r--toys/posix/uuencode.c13
-rw-r--r--toys/posix/who.c1
-rw-r--r--toys/posix/xargs.c4
40 files changed, 1036 insertions, 671 deletions
diff --git a/toys/posix/cat.c b/toys/posix/cat.c
index 6daba7a2..1eb15d49 100644
--- a/toys/posix/cat.c
+++ b/toys/posix/cat.c
@@ -3,48 +3,23 @@
* Copyright 2006 Rob Landley <rob@landley.net>
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/cat.html
- *
- * And "Cat -v considered harmful" at
- * http://cm.bell-labs.com/cm/cs/doc/84/kp.ps.gz
-USE_CAT(NEWTOY(cat, "u"USE_CAT_V("vte"), TOYFLAG_BIN))
-USE_CATV(NEWTOY(catv, USE_CATV("vte"), TOYFLAG_USR|TOYFLAG_BIN))
+USE_CAT(NEWTOY(cat, "uvte", TOYFLAG_BIN))
config CAT
bool "cat"
default y
help
- usage: cat [-u] [FILE...]
+ usage: cat [-etuv] [FILE...]
Copy (concatenate) files to stdout. If no files listed, copy from stdin.
Filename "-" is a synonym for stdin.
- -u Copy one byte at a time (slow)
-
-config CAT_V
- bool "cat -etv"
- default n
- depends on CAT
- help
- usage: cat [-evt]
-
-e Mark each newline with $
-t Show tabs as ^I
+ -u Copy one byte at a time (slow)
-v Display nonprinting characters as escape sequences with M-x for
high ascii characters (>127), and ^x for other nonprinting chars
-
-config CATV
- bool "catv"
- default y
- help
- usage: catv [-evt] [FILE...]
-
- Display nonprinting characters as escape sequences. Use M-x for
- high ascii characters (>127), and ^x for other nonprinting chars.
-
- -e Mark each newline with $
- -t Show tabs as ^I
- -v Don't use ^x or M-x escapes
*/
#define FOR_cat
@@ -53,21 +28,18 @@ config CATV
static void do_cat(int fd, char *name)
{
- int i, len, size=(toys.optflags & FLAG_u) ? 1 : sizeof(toybuf);
+ int i, len, size = FLAG(u) ? 1 : sizeof(toybuf);
for(;;) {
len = read(fd, toybuf, size);
- if (len < 0) {
- toys.exitval = EXIT_FAILURE;
- perror_msg_raw(name);
- }
- if (len < 1) break;
- if ((CFG_CAT_V || CFG_CATV) && (toys.optflags&~FLAG_u)) {
- for (i=0; i<len; i++) {
- char c=toybuf[i];
+ if (len<0) perror_msg_raw(name);
+ if (len<1) break;
+ if (toys.optflags&~FLAG_u) {
+ for (i = 0; i<len; i++) {
+ char c = toybuf[i];
- if (c > 126 && (toys.optflags & FLAG_v)) {
- if (c > 127) {
+ if (c>126 && FLAG(v)) {
+ if (c>127) {
printf("M-");
c -= 128;
}
@@ -76,9 +48,9 @@ static void do_cat(int fd, char *name)
continue;
}
}
- if (c < 32) {
+ if (c<32) {
if (c == 10) {
- if (toys.optflags & FLAG_e) xputc('$');
+ if (FLAG(e)) xputc('$');
} else if (toys.optflags & (c==9 ? FLAG_t : FLAG_v)) {
printf("^%c", c+'@');
continue;
@@ -94,9 +66,3 @@ void cat_main(void)
{
loopfiles(toys.optargs, do_cat);
}
-
-void catv_main(void)
-{
- toys.optflags ^= FLAG_v;
- loopfiles(toys.optargs, do_cat);
-}
diff --git a/toys/posix/chgrp.c b/toys/posix/chgrp.c
index a63272a5..73c4b223 100644
--- a/toys/posix/chgrp.c
+++ b/toys/posix/chgrp.c
@@ -44,24 +44,24 @@ GLOBALS(
static int do_chgrp(struct dirtree *node)
{
- int fd, ret, flags = toys.optflags;
+ int fd, ret;
// Depth first search
if (!dirtree_notdotdot(node)) return 0;
- if ((flags & FLAG_R) && !node->again && S_ISDIR(node->st.st_mode))
- return DIRTREE_COMEAGAIN|(DIRTREE_SYMFOLLOW*!!(flags&FLAG_L));
+ if (FLAG(R) && !node->again && S_ISDIR(node->st.st_mode))
+ return DIRTREE_COMEAGAIN|DIRTREE_SYMFOLLOW*FLAG(L);
fd = dirtree_parentfd(node);
ret = fchownat(fd, node->name, TT.owner, TT.group,
- AT_SYMLINK_NOFOLLOW*(!(flags&(FLAG_L|FLAG_H)) && (flags&(FLAG_h|FLAG_R))));
+ AT_SYMLINK_NOFOLLOW*(!(FLAG(L)|FLAG(H)) && (FLAG(h)|FLAG(R))));
- if (ret || (flags & FLAG_v)) {
+ if (ret || FLAG(v)) {
char *path = dirtree_path(node, 0);
- if (flags & FLAG_v)
+ if (FLAG(v))
xprintf("%s %s%s%s %s\n", toys.which->name, TT.owner_name,
(toys.which->name[2]=='o' && *TT.group_name) ? ":" : "",
TT.group_name, path);
- if (ret == -1 && !(toys.optflags & FLAG_f))
+ if (ret == -1 && !FLAG(f))
perror_msg("'%s' to '%s:%s'", path, TT.owner_name, TT.group_name);
free(path);
}
@@ -94,8 +94,7 @@ void chgrp_main(void)
TT.group = xgetgid(TT.group_name);
for (s=toys.optargs+1; *s; s++)
- dirtree_flagread(*s, DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)),
- do_chgrp);
+ dirtree_flagread(*s, DIRTREE_SYMFOLLOW*(FLAG(H)|FLAG(L)), do_chgrp);
if (CFG_TOYBOX_FREE && ischown) free(own);
}
diff --git a/toys/posix/cksum.c b/toys/posix/cksum.c
index 6e5b915c..8bb6f815 100644
--- a/toys/posix/cksum.c
+++ b/toys/posix/cksum.c
@@ -39,24 +39,23 @@ GLOBALS(
unsigned crc_table[256];
)
-static unsigned cksum_be(unsigned crc, unsigned char c)
+static unsigned cksum_be(unsigned crc, char c)
{
- return (crc<<8)^TT.crc_table[(crc>>24)^c];
+ return (crc<<8) ^ TT.crc_table[(crc>>24)^c];
}
-static unsigned cksum_le(unsigned crc, unsigned char c)
+static unsigned cksum_le(unsigned crc, char c)
{
return TT.crc_table[(crc^c)&0xff] ^ (crc>>8);
}
static void do_cksum(int fd, char *name)
{
- unsigned crc = (toys.optflags & FLAG_P) ? 0xffffffff : 0;
- uint64_t llen = 0, llen2;
- unsigned (*cksum)(unsigned crc, unsigned char c);
+ unsigned (*cksum)(unsigned crc, char c), crc = FLAG(P) ? ~0 : 0;
+ unsigned long long llen = 0, llen2;
int len, i;
- cksum = (toys.optflags & FLAG_L) ? cksum_le : cksum_be;
+ cksum = FLAG(L) ? cksum_le : cksum_be;
// CRC the data
for (;;) {
@@ -65,29 +64,22 @@ static void do_cksum(int fd, char *name)
if (len<1) break;
llen += len;
- for (i=0; i<len; i++) crc=cksum(crc, toybuf[i]);
+ for (i = 0; i<len; i++) crc = cksum(crc, toybuf[i]);
}
// CRC the length
- llen2 = llen;
- if (!(toys.optflags & FLAG_N)) {
- while (llen) {
- crc = cksum(crc, llen);
- llen >>= 8;
- }
- }
+ if (!FLAG(N)) for (llen2 = llen; llen2; llen2 >>= 8) crc = cksum(crc, llen2);
- printf((toys.optflags & FLAG_H) ? "%08x" : "%u",
- (toys.optflags & FLAG_I) ? crc : ~crc);
- if (!(toys.optflags&FLAG_N)) printf(" %"PRIu64, llen2);
+ printf(FLAG(H) ? "%08x" : "%u", FLAG(I) ? crc : ~crc);
+ if (!FLAG(N)) printf(" %llu", llen);
if (toys.optc) printf(" %s", name);
xputc('\n');
}
void cksum_main(void)
{
- crc_init(TT.crc_table, toys.optflags & FLAG_L);
+ crc_init(TT.crc_table, FLAG(L));
loopfiles(toys.optargs, do_cksum);
}
diff --git a/toys/posix/cmp.c b/toys/posix/cmp.c
index 8e33c92d..2621e25b 100644
--- a/toys/posix/cmp.c
+++ b/toys/posix/cmp.c
@@ -34,11 +34,10 @@ GLOBALS(
static void do_cmp(int fd, char *name)
{
int i, len1, len2, min_len, size = sizeof(toybuf)/2;
- long byte_no = 1, line_no = 1;
+ long long byte_no = 1, line_no = 1;
char *buf2 = toybuf+size;
- if (toys.optc>(i = 2+!!TT.fd) && lskip(fd, atolx(toys.optargs[i])))
- error_exit("EOF on %s", name);
+ if (toys.optc>(i = 2+!!TT.fd)) lskip(fd, atolx(toys.optargs[i]));
// First time through, cache the data and return.
if (!TT.fd) {
@@ -51,17 +50,17 @@ static void do_cmp(int fd, char *name)
toys.exitval = 0;
- for (;!FLAG(n) || TT.n;) {
+ while (!FLAG(n) || TT.n) {
if (FLAG(n)) TT.n -= size = minof(size, TT.n);
len1 = readall(TT.fd, toybuf, size);
len2 = readall(fd, buf2, size);
min_len = minof(len1, len2);
- for (i=0; i<min_len; i++) {
+ for (i = 0; i<min_len; i++) {
if (toybuf[i] != buf2[i]) {
toys.exitval = 1;
- if (FLAG(l)) printf("%ld %o %o\n", byte_no, toybuf[i], buf2[i]);
+ if (FLAG(l)) printf("%lld %o %o\n", byte_no, toybuf[i], buf2[i]);
else {
- if (!FLAG(s)) printf("%s %s differ: char %ld, line %ld\n",
+ if (!FLAG(s)) printf("%s %s differ: char %lld, line %lld\n",
TT.name, name, byte_no, line_no);
goto out;
}
@@ -70,8 +69,11 @@ static void do_cmp(int fd, char *name)
if (toybuf[i] == '\n') line_no++;
}
if (len1 != len2) {
- if (!FLAG(s)) error_msg("EOF on %s", len1 < len2 ? TT.name : name);
- else toys.exitval = 1;
+ if (!FLAG(s)) {
+ strcpy(toybuf, "EOF on %s after byte %lld, line %lld");
+ if (FLAG(l)) *strchr(toybuf, ',') = 0;
+ error_msg(toybuf, len1 < len2 ? TT.name : name, byte_no-1, line_no-1);
+ } else toys.exitval = 1;
break;
}
if (len1 < 1) break;
@@ -84,6 +86,6 @@ out:
void cmp_main(void)
{
toys.exitval = 2;
- loopfiles_rw(toys.optargs, O_CLOEXEC|(WARN_ONLY*!FLAG(s)), 0, do_cmp);
+ loopfiles_rw(toys.optargs, O_CLOEXEC|WARN_ONLY*!FLAG(s), 0, do_cmp);
if (toys.optc == 1) do_cmp(0, "-");
}
diff --git a/toys/posix/comm.c b/toys/posix/comm.c
index 2d118b7b..91359246 100644
--- a/toys/posix/comm.c
+++ b/toys/posix/comm.c
@@ -43,26 +43,26 @@ void comm_main(void)
{
FILE *file[2];
char *line[2];
- int i;
+ int i = 0;
- if (toys.optflags == 7) return;
-
- for (i = 0; i < 2; i++) {
- file[i] = xfopen(toys.optargs[i], "r");
+ for (i = 0; i<2; i++) {
+ file[i] = strcmp(toys.optargs[i], "-")?xfopen(toys.optargs[i], "r"):stdin;
line[i] = xgetline(file[i]);
}
+ if (toys.optflags == 7) return;
+
while (line[0] && line[1]) {
int order = strcmp(line[0], line[1]);
- if (order == 0) {
+ if (!order) {
writeline(line[0], 2);
for (i = 0; i < 2; i++) {
free(line[i]);
line[i] = xgetline(file[i]);
}
} else {
- i = order < 0 ? 0 : 1;
+ i = order>0;
writeline(line[i], i);
free(line[i]);
line[i] = xgetline(file[i]);
@@ -76,5 +76,5 @@ void comm_main(void)
line[i] = xgetline(file[i]);
}
- if (CFG_TOYBOX_FREE) for (i = 0; i < 2; i++) fclose(file[i]);
+ if (CFG_TOYBOX_FREE) fclose(file[0]), fclose(file[1]);
}
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index 7b9889a2..c11da163 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -16,8 +16,8 @@
// for FLAG macros to work out right in shared infrastructure.
USE_CP(NEWTOY(cp, "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu][+Rr]", TOYFLAG_BIN))
-USE_MV(NEWTOY(mv, "<1vnF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
-USE_INSTALL(NEWTOY(install, "<1cdDpsvt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MV(NEWTOY(mv, "<1v(verbose)nF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
+USE_INSTALL(NEWTOY(install, "<1cdDp(preserve-timestamps)svt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
config CP
bool "cp"
@@ -148,10 +148,8 @@ static int cp_node(struct dirtree *try)
// Detect recursive copies via repeated top node (cp -R .. .) or
// identical source/target (fun with hardlinks).
- if ((TT.top.st_dev == try->st.st_dev && TT.top.st_ino == try->st.st_ino
- && (catch = TT.destname))
- || (!fstatat(cfd, catch, &cst, 0) && cst.st_dev == try->st.st_dev
- && cst.st_ino == try->st.st_ino))
+ if ((same_file(&TT.top, &try->st) && (catch = TT.destname))
+ || (!fstatat(cfd, catch, &cst, 0) && same_file(&cst, &try->st)))
{
error_msg("'%s' is '%s'", catch, err = dirtree_path(try, 0));
free(err);
@@ -206,7 +204,7 @@ static int cp_node(struct dirtree *try)
if (!mkdirat(cfd, catch, try->st.st_mode | 0200) || errno == EEXIST)
if (-1 != (try->extra = openat(cfd, catch, O_NOFOLLOW)))
if (!fstat(try->extra, &st2) && S_ISDIR(st2.st_mode))
- return DIRTREE_COMEAGAIN | (DIRTREE_SYMFOLLOW*!!FLAG(L));
+ return DIRTREE_COMEAGAIN | DIRTREE_SYMFOLLOW*FLAG(L);
// Hardlink
@@ -432,9 +430,7 @@ void cp_main(void)
// "mv across devices" triggers cp fallback path, so set that as default
errno = EXDEV;
if (CFG_MV && toys.which->name[0] == 'm') {
- int force = FLAG(f), no_clobber = FLAG(n);
-
- if (!force || no_clobber) {
+ if (!FLAG(f) || FLAG(n)) {
struct stat st;
int exists = !stat(TT.destname, &st);
@@ -447,7 +443,7 @@ void cp_main(void)
else unlink(TT.destname);
}
// if -n and dest exists, don't try to rename() or copy
- if (exists && no_clobber) send = 0;
+ if (exists && FLAG(n)) send = 0;
}
if (send) send = rename(src, TT.destname);
if (trail) trail[1] = '/';
@@ -456,7 +452,7 @@ void cp_main(void)
// Copy if we didn't mv or hit an error, skipping nonexistent sources
if (send) {
if (errno!=EXDEV || dirtree_flagread(src, DIRTREE_SHUTUP+
- DIRTREE_SYMFOLLOW*!!(FLAG(H)||FLAG(L)), TT.callback))
+ DIRTREE_SYMFOLLOW*(FLAG(H)|FLAG(L)), TT.callback))
perror_msg("bad '%s'", src);
}
if (destdir) free(TT.destname);
@@ -472,9 +468,9 @@ void mv_main(void)
// Export cp flags into install's flag context.
-static inline int cp_flag_F(void) { return FLAG_F; };
-static inline int cp_flag_p(void) { return FLAG_p; };
-static inline int cp_flag_v(void) { return FLAG_v; };
+static inline int cp_flag_F(void) { return FLAG_F; }
+static inline int cp_flag_p(void) { return FLAG_p; }
+static inline int cp_flag_v(void) { return FLAG_v; }
// Switch to install's flag context
#define FOR_install
@@ -516,15 +512,15 @@ void install_main(void)
}
if (FLAG(D)) {
- char *destname = FLAG(t) ? TT.i.t : (TT.destname = toys.optargs[toys.optc-1]);
- if (mkpathat(AT_FDCWD, destname, 0777, MKPATHAT_MAKE | (FLAG(t) ? MKPATHAT_MKLAST : 0)))
+ char *destname = TT.i.t ? : (TT.destname = toys.optargs[toys.optc-1]);
+ if (mkpathat(AT_FDCWD, destname, 0777, MKPATHAT_MAKE|MKPATHAT_MKLAST*FLAG(t)))
perror_exit("-D '%s'", destname);
if (toys.optc == !FLAG(t)) return;
}
// Translate flags from install to cp
- toys.optflags = cp_flag_F() + cp_flag_v()*!!FLAG(v)
- + cp_flag_p()*!!(FLAG(p)|FLAG(o)|FLAG(g));
+ toys.optflags = cp_flag_F() + cp_flag_v()*FLAG(v)
+ + cp_flag_p()*(FLAG(p)|FLAG(o)|FLAG(g));
TT.callback = install_node;
cp_main();
diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c
index 5ecd56df..bbcde4b5 100644
--- a/toys/posix/cpio.c
+++ b/toys/posix/cpio.c
@@ -13,36 +13,39 @@
* In order: magic ino mode uid gid nlink mtime filesize devmajor devminor
* rdevmajor rdevminor namesize check
* This is the equivalent of mode -H newc in other implementations.
+ * We always do --quiet, but accept it as a compatibility NOP.
*
- * todo: export/import linux file list text format ala gen_initramfs_list.sh
+ * TODO: export/import linux file list text format ala gen_initramfs_list.sh
+ * TODO: hardlink support, -A, -0, -a, -L, --sparse
+ * TODO: --renumber-archives (probably always?) --ignore-devno --reproducible
-USE_CPIO(NEWTOY(cpio, "(ignore-devno)(renumber-inodes)(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
+USE_CPIO(NEWTOY(cpio, "(ignore-devno)(renumber-inodes)(quiet)(no-preserve-owner)R(owner):md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
config CPIO
bool "cpio"
default y
help
- usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]
- [ignored: -m -H newc]
+ usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [-R [USER][:GROUP] [--no-preserve-owner]
Copy files into and out of a "newc" format cpio archive.
+ -d Create directories if needed
-F FILE Use archive FILE instead of stdin/stdout
- -p DEST Copy-pass mode, copy stdin file list to directory DEST
-i Extract from archive into file system (stdin=archive)
-o Create archive (stdin=list of files, stdout=archive)
+ -p DEST Copy-pass mode, copy stdin file list to directory DEST
+ -R USER Replace owner with USER[:GROUP]
-t Test files (list only, stdin=archive, stdout=list of files)
- -d Create directories if needed
- -u unlink existing files when extracting
+ -u Unlink existing files when extracting
-v Verbose
- --no-preserve-owner (don't set ownership during extract)
+ --no-preserve-owner Don't set ownership during extract
*/
#define FOR_cpio
#include "toys.h"
GLOBALS(
- char *F, *H;
+ char *F, *H, *R;
)
// Read strings, tail padded to 4 byte alignment. Argument "align" is amount
@@ -80,9 +83,21 @@ static unsigned x8u(char *hex)
void cpio_main(void)
{
- // Subtle bit: FLAG_o is 1 so we can just use it to select stdin/stdout.
- int pipe, afd = FLAG(o), empty = 1;
+ int pipe, afd = FLAG(o), reown = !geteuid() && !FLAG(no_preserve_owner),
+ empty = 1;
pid_t pid = 0;
+ long Ruid = -1, Rgid = -1;
+ char *tofree = 0;
+
+ if (TT.R) {
+ char *group = TT.R+strcspn(TT.R, ":.");
+
+ if (*group) {
+ Rgid = xgetgid(group+1);
+ *group = 0;
+ }
+ if (group != TT.R) Ruid = xgetuid(TT.R);
+ }
// In passthrough mode, parent stays in original dir and generates archive
// to pipe, child does chdir to new dir and reads archive from stdin (pipe).
@@ -90,7 +105,7 @@ void cpio_main(void)
if (FLAG(d)) {
if (!*toys.optargs) error_exit("need directory for -p");
if (mkdir(*toys.optargs, 0700) == -1 && errno != EEXIST)
- perror_exit("mkdir %s", *toys.optargs);
+ perror_msg("mkdir %s", *toys.optargs);
}
if (toys.stacktop) {
// xpopen() doesn't return from child due to vfork(), instead restarts
@@ -113,10 +128,12 @@ void cpio_main(void)
// read cpio archive
if (FLAG(i) || FLAG(t)) for (;; empty = 0) {
- char *name, *tofree, *data;
+ char *name, *data;
unsigned mode, uid, gid, timestamp;
int test = FLAG(t), err = 0, size = 0, len;
+ free(tofree);
+ tofree = 0;
// read header, skipping arbitrary leading NUL bytes (concatenated archives)
for (;;) {
if (1>(len = readall(afd, toybuf+size, 110-size))) break;
@@ -132,12 +149,10 @@ void cpio_main(void)
if (empty) error_exit("empty archive");
else break;
}
- if (size != 110 || memcmp(toybuf, "070701", 6)) error_exit("bad header");
+ if (size != 110 || smemcmp(toybuf, "070701", 6)) error_exit("bad header");
tofree = name = strpad(afd, x8u(toybuf+94), 110);
- if (!strcmp("TRAILER!!!", name)) {
- free(tofree);
- continue;
- }
+ // TODO: this flushes hardlink detection via major/minor/ino match
+ if (!strcmp("TRAILER!!!", name)) continue;
// If you want to extract absolute paths, "cd /" and run cpio.
while (*name == '/') name++;
@@ -145,8 +160,8 @@ void cpio_main(void)
size = x8u(toybuf+54);
mode = x8u(toybuf+14);
- uid = x8u(toybuf+22);
- gid = x8u(toybuf+30);
+ uid = (Ruid>=0) ? Ruid : x8u(toybuf+22);
+ gid = (Rgid>=0) ? Rgid : x8u(toybuf+30);
timestamp = x8u(toybuf+46); // unsigned 32 bit, so year 2100 problem
// (This output is unaffected by --quiet.)
@@ -163,16 +178,23 @@ void cpio_main(void)
// properly aligned with next file.
if (S_ISDIR(mode)) {
- if (!test) err = mkdir(name, mode) && !FLAG(u);
- } else if (S_ISLNK(mode)) {
- data = strpad(afd, size, 0);
- if (!test) {
- err = symlink(data, name);
- // Can't get a filehandle to a symlink, so do special chown
- if (!err && !geteuid() && !FLAG(no_preserve_owner))
- err = lchown(name, uid, gid);
+ if (test) continue;
+ err = mkdir(name, mode) && (errno != EEXIST && !FLAG(u));
+
+ // Creading dir/dev doesn't give us a filehandle, we have to refer to it
+ // by name to chown/utime, but how do we know it's the same item?
+ // Check that we at least have the right type of entity open, and do
+ // NOT restore dropped suid bit in this case.
+ if (S_ISDIR(mode) && reown) {
+ int fd = open(name, O_RDONLY|O_NOFOLLOW);
+ struct stat st;
+
+ if (fd != -1 && !fstat(fd, &st) && (st.st_mode&S_IFMT) == (mode&S_IFMT))
+ err = fchown(fd, uid, gid);
+ else err = 1;
+
+ close(fd);
}
- free(data);
} else if (S_ISREG(mode)) {
int fd = test ? 0 : open(name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, mode);
@@ -197,46 +219,33 @@ void cpio_main(void)
if (!test) {
// set owner, restore dropped suid bit
- if (!geteuid() && !FLAG(no_preserve_owner)) {
- err = fchown(fd, uid, gid);
- if (!err) err = fchmod(fd, mode);
- }
+ if (reown) err = fchown(fd, uid, gid) && fchmod(fd, mode);
close(fd);
}
- } else if (!test)
- err = mknod(name, mode, dev_makedev(x8u(toybuf+78), x8u(toybuf+86)));
-
- // Set ownership and timestamp.
- if (!test && !err) {
- // Creading dir/dev doesn't give us a filehandle, we have to refer to it
- // by name to chown/utime, but how do we know it's the same item?
- // Check that we at least have the right type of entity open, and do
- // NOT restore dropped suid bit in this case.
- if (!S_ISREG(mode) && !S_ISLNK(mode) && !geteuid()
- && !FLAG(no_preserve_owner))
- {
- int fd = open(name, O_RDONLY|O_NOFOLLOW);
- struct stat st;
-
- if (fd != -1 && !fstat(fd, &st) && (st.st_mode&S_IFMT) == (mode&S_IFMT))
- err = fchown(fd, uid, gid);
- else err = 1;
+ } else {
+ data = S_ISLNK(mode) ? strpad(afd, size, 0) : 0;
+ if (!test) {
+ err = data ? symlink(data, name)
+ : mknod(name, mode, dev_makedev(x8u(toybuf+78), x8u(toybuf+86)));
- close(fd);
+ // Can't get a filehandle to a symlink or a node on nodev mount,
+ // so do special chown that at least doesn't follow symlinks.
+ // We also don't chmod after, so dropped suid bit isn't restored
+ if (!err && reown) err = lchown(name, uid, gid);
}
+ free(data);
+ }
- // set timestamp
- if (!err) {
- struct timespec times[2];
+ // Set timestamp.
+ if (!test && !err) {
+ struct timespec times[2];
- memset(times, 0, sizeof(struct timespec)*2);
- times[0].tv_sec = times[1].tv_sec = timestamp;
- err = utimensat(AT_FDCWD, name, times, AT_SYMLINK_NOFOLLOW);
- }
+ memset(times, 0, sizeof(struct timespec)*2);
+ times[0].tv_sec = times[1].tv_sec = timestamp;
+ err = utimensat(AT_FDCWD, name, times, AT_SYMLINK_NOFOLLOW);
}
if (err) perror_msg_raw(name);
- free(tofree);
// Output cpio archive
@@ -266,6 +275,8 @@ void cpio_main(void)
// encrypted filesystems can stat the wrong link size
if (link) st.st_size = strlen(link);
+ if (Ruid>=0) st.st_uid = Ruid;
+ if (Rgid>=0) st.st_gid = Rgid;
if (FLAG(no_preserve_owner)) st.st_uid = st.st_gid = 0;
if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) st.st_size = 0;
if (st.st_size >> 32) perror_msg("skipping >2G file '%s'", name);
diff --git a/toys/posix/cut.c b/toys/posix/cut.c
index c4f34f95..b9710ea5 100644
--- a/toys/posix/cut.c
+++ b/toys/posix/cut.c
@@ -8,9 +8,9 @@
* "-" counts as start to end. Using spaces to separate a comma-separated list
* is silly and inconsistent with dd, ps, cp, and mount.
*
- * todo: -n, -s with -c
+ * TODO: -s with -c
-USE_CUT(NEWTOY(cut, "b*|c*|f*|F*|C*|O(output-delimiter):d:sDn[!cbfF]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CUT(NEWTOY(cut, "b*|c*|f*|F(regex-fields)*|C*|O(output-delimiter):d:sD(allow-duplicates)n[!cbfF]", TOYFLAG_USR|TOYFLAG_BIN))
config CUT
bool "cut"
@@ -25,7 +25,7 @@ config CUT
from start). By default selection ranges are sorted and collated, use -D
to prevent that.
- -b Select bytes
+ -b Select bytes (with -n round start/end down to start of utf8 char)
-c Select UTF-8 characters
-C Select unicode columns
-d Use DELIM (default is TAB for -f, run of whitespace for -F)
diff --git a/toys/posix/date.c b/toys/posix/date.c
index 99a41695..5a0f27e1 100644
--- a/toys/posix/date.c
+++ b/toys/posix/date.c
@@ -7,7 +7,7 @@
* Note: setting a 2 year date is 50 years back/forward from today,
* not posix's hardwired magic dates.
-USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:s:u(utc)[!dr]", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:D:I(iso-8601):;r:s:u(utc)[!dr]", TOYFLAG_BIN))
config DATE
bool "date"
@@ -156,7 +156,7 @@ void date_main(void)
struct tm tm = {};
char *s = strptime(TT.d, TT.D+(*TT.D=='+'), &tm);
- t = (s && *s) ? xvali_date(&tm, s) : xvali_date(0, TT.d);
+ t = (s && !*s) ? xvali_date(&tm, s) : xvali_date(0, TT.d);
} else parse_date(TT.d, &t);
} else {
struct timespec ts;
diff --git a/toys/posix/df.c b/toys/posix/df.c
index caf8cf89..e07b33bf 100644
--- a/toys/posix/df.c
+++ b/toys/posix/df.c
@@ -4,7 +4,7 @@
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/df.html
-USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN))
+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_BIN))
config DF
bool "df"
@@ -115,7 +115,7 @@ static void show_mt(struct mtab_list *mt, int measuring)
suap[i] = (block*suap[i])/TT.units;
if (FLAG(H)||FLAG(h))
- human_readable(dsuapm[i+1], suap[i], FLAG(H) ? HR_1000 : 0);
+ human_readable(dsuapm[i+1], suap[i], HR_1000*FLAG(H));
else sprintf(dsuapm[i+1], "%llu", suap[i]);
dsuapm[i+2] = strchr(dsuapm[i+1], 0)+1;
}
diff --git a/toys/posix/du.c b/toys/posix/du.c
index 86118676..8bf9575d 100644
--- a/toys/posix/du.c
+++ b/toys/posix/du.c
@@ -89,17 +89,15 @@ static int seen_inode(void **list, struct stat *st)
else if (!S_ISDIR(st->st_mode) && st->st_nlink > 1) {
struct inode_list {
struct inode_list *next;
- ino_t ino;
- dev_t dev;
+ struct dev_ino di;
} *new;
for (new = *list; new; new = new->next)
- if(new->ino == st->st_ino && new->dev == st->st_dev)
- return 1;
+ if(same_dev_ino(st, &new->di)) return 1;
new = xzalloc(sizeof(*new));
- new->ino = st->st_ino;
- new->dev = st->st_dev;
+ new->di.ino = st->st_ino;
+ new->di.dev = st->st_dev;
new->next = *list;
*list = new;
}
@@ -116,16 +114,13 @@ static int do_du(struct dirtree *node)
else if (!dirtree_notdotdot(node)) return 0;
// detect swiching filesystems
- if (FLAG(x) && (TT.st_dev != node->st.st_dev))
- return 0;
+ if (FLAG(x) && TT.st_dev != node->st.st_dev) return 0;
// Don't loop endlessly on recursive directory symlink
if (FLAG(L)) {
struct dirtree *try = node;
- while ((try = try->parent))
- if (node->st.st_dev==try->st.st_dev && node->st.st_ino==try->st.st_ino)
- return 0;
+ while ((try = try->parent)) if (same_file(&node->st, &try->st)) return 0;
}
// Don't count hard links twice
@@ -136,7 +131,7 @@ static int do_du(struct dirtree *node)
if (S_ISDIR(node->st.st_mode)) {
if (!node->again) {
TT.depth++;
- return DIRTREE_COMEAGAIN|(DIRTREE_SYMFOLLOW*!!FLAG(L));
+ return DIRTREE_COMEAGAIN|DIRTREE_SYMFOLLOW*FLAG(L);
} else TT.depth--;
}
diff --git a/toys/posix/file.c b/toys/posix/file.c
index 2e65bcfa..e48a5472 100644
--- a/toys/posix/file.c
+++ b/toys/posix/file.c
@@ -4,7 +4,7 @@
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/file.html
-USE_FILE(NEWTOY(file, "<1bhLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_FILE(NEWTOY(file, "<1b(brief)hLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
config FILE
bool "file"
@@ -177,10 +177,10 @@ static void do_elf_file(int fd)
goto bad;
}
- if (n_namesz==4 && !memcmp(note+12, "GNU", 4) && n_type==3) {
+ if (n_namesz==4 && !smemcmp(note+12, "GNU", 4) && n_type==3) {
printf(", BuildID=");
for (j = 0; j<n_descsz; j++) printf("%02x", note[16+j]);
- } else if (n_namesz==8 && !memcmp(note+12, "Android", 8)) {
+ } else if (n_namesz==8 && !smemcmp(note+12, "Android", 8)) {
if (n_type==1 /*.android.note.ident*/ && n_descsz >= 4) {
printf(", for Android %d", (int)elf_int(note+20, 4));
// NDK r14 and later also include NDK version info. OS binaries
@@ -206,6 +206,7 @@ static void do_regular_file(int fd, char *name)
{
char *s = toybuf;
unsigned len, magic;
+ int ii;
// zero through elf shnum, just in case
memset(s, 0, 80);
@@ -215,6 +216,8 @@ static void do_regular_file(int fd, char *name)
// 45 bytes: https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
else if (len>=45 && strstart(&s, "\177ELF")) do_elf_file(fd);
else if (strstart(&s, "!<arch>\n")) xputs("ar archive");
+ else if (*s=='%' && 2==sscanf(s, "%%PDF%d.%u", &ii, &magic))
+ xprintf("PDF document, version %d.%u\n", -ii, magic);
else if (len>28 && strstart(&s, "\x89PNG\x0d\x0a\x1a\x0a")) {
// PNG is big-endian: https://www.w3.org/TR/PNG/#7Integers-and-byte-order
int chunk_length = peek_be(s, 4);
@@ -241,7 +244,7 @@ static void do_regular_file(int fd, char *name)
s-3, (int)peek_le(s, 2), (int)peek_le(s+2, 2));
// TODO: parsing JPEG for width/height is harder than GIF or PNG.
- else if (len>32 && !memcmp(s, "\xff\xd8", 2)) xputs("JPEG image data");
+ else if (len>32 && !smemcmp(s, "\xff\xd8", 2)) xputs("JPEG image data");
else if (len>8 && strstart(&s, "\xca\xfe\xba\xbe")) {
unsigned count = peek_be(s, 4), i, arch;
@@ -255,9 +258,9 @@ static void do_regular_file(int fd, char *name)
count, count == 1 ? "" : "s");
for (i = 0, s += 4; i < count; i++, s += 20) {
arch = peek_be(s, 4);
- if (arch == 0x00000007) name = "i386";
+ if (arch == 0x00000007) name = "i386";
else if (arch == 0x01000007) name = "x86_64";
- else if (arch == 0x0000000c) name = "arm";
+ else if (arch == 0x0000000c) name = "arm";
else if (arch == 0x0100000c) name = "arm64";
else name = "unknown";
xprintf(" [%s]", name);
@@ -304,7 +307,7 @@ static void do_regular_file(int fd, char *name)
else if (len>31 && peek_be(s, 7) == 0xfd377a585a0000UL)
xputs("xz compressed data");
else if (len>10 && strstart(&s, "\x1f\x8b")) xputs("gzip compressed data");
- else if (len>32 && !memcmp(s+1, "\xfa\xed\xfe", 3)) {
+ else if (len>32 && !smemcmp(s+1, "\xfa\xed\xfe", 3)) {
int bit = (*s==0xce) ? 32 : 64;
char *what = 0;
@@ -322,26 +325,26 @@ static void do_regular_file(int fd, char *name)
else what = NULL;
if (what) xprintf("%s\n", what);
else xprintf("(bad type %d)\n", s[9]);
- } else if (len>36 && !memcmp(s, "OggS\x00\x02", 6)) {
+ } else if (len>36 && !smemcmp(s, "OggS\x00\x02", 6)) {
xprintf("Ogg data");
// https://wiki.xiph.org/MIMETypesCodecs
- if (!memcmp(s+28, "CELT ", 8)) xprintf(", celt audio");
- else if (!memcmp(s+28, "CMML ", 8)) xprintf(", cmml text");
- else if (!memcmp(s+28, "BBCD\0", 5)) xprintf(", dirac video");
- else if (!memcmp(s+28, "\177FLAC", 5)) xprintf(", flac audio");
- else if (!memcmp(s+28, "\x8bJNG\r\n\x1a\n", 8)) xprintf(", jng video");
- else if (!memcmp(s+28, "\x80kate\0\0\0", 8)) xprintf(", kate text");
- else if (!memcmp(s+28, "OggMIDI\0", 8)) xprintf(", midi text");
- else if (!memcmp(s+28, "\x8aMNG\r\n\x1a\n", 8)) xprintf(", mng video");
- else if (!memcmp(s+28, "OpusHead", 8)) xprintf(", opus audio");
- else if (!memcmp(s+28, "PCM ", 8)) xprintf(", pcm audio");
- else if (!memcmp(s+28, "\x89PNG\r\n\x1a\n", 8)) xprintf(", png video");
- else if (!memcmp(s+28, "Speex ", 8)) xprintf(", speex audio");
- else if (!memcmp(s+28, "\x80theora", 7)) xprintf(", theora video");
- else if (!memcmp(s+28, "\x01vorbis", 7)) xprintf(", vorbis audio");
- else if (!memcmp(s+28, "YUV4MPEG", 8)) xprintf(", yuv4mpeg video");
+ if (!smemcmp(s+28, "CELT ", 8)) xprintf(", celt audio");
+ else if (!smemcmp(s+28, "CMML ", 8)) xprintf(", cmml text");
+ else if (!smemcmp(s+28, "BBCD", 5)) xprintf(", dirac video");
+ else if (!smemcmp(s+28, "\177FLAC", 5)) xprintf(", flac audio");
+ else if (!smemcmp(s+28, "\x8bJNG\r\n\x1a\n", 8)) xprintf(", jng video");
+ else if (!smemcmp(s+28, "\x80kate\0\0", 8)) xprintf(", kate text");
+ else if (!smemcmp(s+28, "OggMIDI", 8)) xprintf(", midi text");
+ else if (!smemcmp(s+28, "\x8aMNG\r\n\x1a\n", 8)) xprintf(", mng video");
+ else if (!smemcmp(s+28, "OpusHead", 8)) xprintf(", opus audio");
+ else if (!smemcmp(s+28, "PCM ", 8)) xprintf(", pcm audio");
+ else if (!smemcmp(s+28, "\x89PNG\r\n\x1a\n", 8)) xprintf(", png video");
+ else if (!smemcmp(s+28, "Speex ", 8)) xprintf(", speex audio");
+ else if (!smemcmp(s+28, "\x80theora", 7)) xprintf(", theora video");
+ else if (!smemcmp(s+28, "\x01vorbis", 7)) xprintf(", vorbis audio");
+ else if (!smemcmp(s+28, "YUV4MPEG", 8)) xprintf(", yuv4mpeg video");
xputc('\n');
- } else if (len>32 && !memcmp(s, "RIF", 3) && !memcmp(s+8, "WAVEfmt ", 8)) {
+ } else if (len>32 && !smemcmp(s, "RIF", 3) && !smemcmp(s+8, "WAVEfmt ", 8)) {
// https://en.wikipedia.org/wiki/WAV
int le = (s[3] == 'F');
int format = le ? peek_le(s+20, 2) : peek_be(s+20, 2);
@@ -369,7 +372,7 @@ static void do_regular_file(int fd, char *name)
else xprintf("unknown format %d", format);
xputc('\n');
} else if (len>12 && peek_be(s, 4)==0x10000) xputs("TrueType font");
- else if (len>12 && !memcmp(s, "ttcf\x00", 5)) {
+ else if (len>12 && !smemcmp(s, "ttcf", 5)) {
xprintf("TrueType font collection, version %d, %d fonts\n",
(int)peek_be(s+4, 2), (int)peek_be(s+8, 4));
@@ -379,8 +382,29 @@ static void do_regular_file(int fd, char *name)
else if (strstart(&s,"-----BEGIN CERTIFICATE-----")) xputs("PEM certificate");
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx
- else if (len>0x70 && !memcmp(s, "MZ", 2) &&
- (magic=peek_le(s+0x3c,4))<len-4 && !memcmp(s+magic, "\x50\x45\0", 4)) {
+ else if (len>0x70 && !smemcmp(s, "MZ", 2) &&
+ (magic=peek_le(s+0x3c,4))<len-4 && !smemcmp(s+magic, "\x50\x45\0", 4)) {
+
+ // Linux kernel images look like PE files.
+ // https://www.kernel.org/doc/Documentation/arm64/booting.txt
+ // I've only ever seen LE, 4KiB pages, so ignore flags for now.
+ if (!smemcmp(s+0x38, "ARMd", 4)) return xputs("Linux arm64 kernel image");
+ else if (!smemcmp(s+0x202, "HdrS", 4)) {
+ // https://www.kernel.org/doc/Documentation/x86/boot.txt
+ unsigned ver_off = peek_le(s+0x20e, 2);
+
+ xprintf("Linux x86-64 kernel image");
+ if ((0x200 + ver_off) < len) {
+ s += 0x200 + ver_off;
+ } else {
+ if (lseek(fd, ver_off - len + 0x200, SEEK_CUR)<0 ||
+ (len = readall(fd, s, sizeof(toybuf)))<0)
+ return perror_msg("%s", name);
+ }
+ xprintf(", version %s\n", s);
+ return;
+ }
+
xprintf("MS PE32%s executable %s", (peek_le(s+magic+24, 2)==0x20b)?"+":"",
(peek_le(s+magic+22, 2)&0x2000)?"(DLL) ":"");
if (peek_le(s+magic+20, 2)>70) {
@@ -394,7 +418,7 @@ static void do_regular_file(int fd, char *name)
xprintf("x86%s\n", (peek_le(s+magic+4, 2)==0x14c) ? "" : "-64");
// https://en.wikipedia.org/wiki/BMP_file_format
- } else if (len>0x32 && !memcmp(s, "BM", 2) && !peek_be(s+6, 4)) {
+ } else if (len>0x32 && !smemcmp(s, "BM", 2) && !peek_be(s+6, 4)) {
xprintf("BMP image, %d x %d, %d bpp\n", (int)peek_le(s+18, 4),
(int)peek_le(s+22,4), (int)peek_le(s+28, 2));
@@ -408,7 +432,7 @@ static void do_regular_file(int fd, char *name)
(int)peek_le(s+12, 4), (int)peek_le(s+20, 4));
// https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h
- } else if (len>1632 && !memcmp(s, "ANDROID!", 8)) {
+ } else if (len>1632 && !smemcmp(s, "ANDROID!", 8)) {
xprintf("Android boot image v%d\n", (int)peek_le(s+40, 4));
// https://source.android.com/devices/architecture/dto/partitions
@@ -417,7 +441,7 @@ static void do_regular_file(int fd, char *name)
(int)peek_be(s+16, 4));
// frameworks/base/core/java/com/android/internal/util/BinaryXmlSerializer.java
- } else if (len>4 && !memcmp(s, "ABX", 3)) {
+ } else if (len>4 && !smemcmp(s, "ABX", 3)) {
xprintf("Android Binary XML v%d\n", s[3]);
// Text files, including shell scripts.
diff --git a/toys/posix/find.c b/toys/posix/find.c
index bca7c67b..f82817ca 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -29,19 +29,19 @@ config FIND
-group GROUP belongs to group GROUP -nogroup group ID not known
-perm [-/]MODE permissions (-=min /=any) -prune ignore dir contents
-size N[c] 512 byte blocks (c=bytes) -xdev only this filesystem
- -links N hardlink count -atime N[u] accessed N units ago
- -ctime N[u] created N units ago -mtime N[u] modified N units ago
- -inum N inode number N -empty empty files and dirs
- -true always true -false always false
- -context PATTERN security context -executable access(X_OK) perm+ACL
- -samefile FILE hardlink to FILE -quit exit immediately
- -depth ignore contents of dir -maxdepth N at most N dirs down
- -newer FILE newer mtime than FILE -mindepth N at least N dirs down
- -newerXY FILE X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)
+ -links N hardlink count -empty empty files and dirs
+ -atime N[u] accessed N units ago -true always true
+ -ctime N[u] created N units ago -false always false
+ -mtime N[u] modified N units ago -executable access(X_OK) perm+ACL
+ -inum N inode number N -readable access(R_OK) perm+ACL
+ -context PATTERN security context -depth contents before dir
+ -samefile FILE hardlink to FILE -maxdepth N at most N dirs down
+ -newer FILE newer mtime than FILE -mindepth N at least N dirs down
+ -newerXY FILE X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)
-type [bcdflps] type is (block, char, dir, file, symlink, pipe, socket)
- Numbers N may be prefixed by a - (less than) or + (greater than). Units for
- -Xtime are d (days, default), h (hours), m (minutes), or s (seconds).
+ Numbers N may be prefixed by - (less than) or + (greater than). Units for
+ -[acm]time are d (days, default), h (hours), m (minutes), or s (seconds).
Combine matches with:
!, -a, -o, ( ) not, and, or, group expressions
@@ -51,6 +51,7 @@ config FIND
-exec Run command with path -execdir Run command in file's dir
-ok Ask before exec -okdir Ask before execdir
-delete Remove matching file/dir -printf FORMAT Print using format string
+ -quit Exit immediately
Commands substitute "{}" with matched file. End with ";" to run each file,
or "+" (next argument after "{}") to collect and run with multiple files.
@@ -220,7 +221,7 @@ static int do_find(struct dirtree *new)
// skip . and .. below topdir, handle -xdev and -depth
if (new) {
// Handle stat failures first.
- if (new->again&2) {
+ if (new->again&DIRTREE_STATLESS) {
if (!new->parent || errno != ENOENT) {
perror_msg("'%s'", s = dirtree_path(new, 0));
free(s);
@@ -238,7 +239,7 @@ static int do_find(struct dirtree *new)
struct dirtree *n;
for (n = new->parent; n; n = n->parent) {
- if (n->st.st_ino==new->st.st_ino && n->st.st_dev==new->st.st_dev) {
+ if (same_file(&n->st, &new->st)) {
error_msg("'%s': loop detected", s = dirtree_path(new, 0));
free(s);
@@ -352,8 +353,9 @@ static int do_find(struct dirtree *new)
if (check && bufgetgrgid(new->st.st_gid)) test = 0;
} else if (!strcmp(s, "prune")) {
if (check && S_ISDIR(new->st.st_mode) && !TT.depth) recurse = 0;
- } else if (!strcmp(s, "executable")) {
- if (check && faccessat(dirtree_parentfd(new), new->name,X_OK,0)) test = 0;
+ } else if (!strcmp(s, "executable") || !strcmp(s, "readable")) {
+ if (check && faccessat(dirtree_parentfd(new), new->name,
+ *s=='r' ? R_OK : X_OK, 0)) test = 0;
} else if (!strcmp(s, "quit")) {
if (check) {
execdir(0, 1);
@@ -467,10 +469,7 @@ static int do_find(struct dirtree *new)
uid_t uid;
gid_t gid;
struct timespec tm;
- struct {
- dev_t d;
- ino_t i;
- };
+ struct dev_ino di;
};
} *udl;
struct stat st;
@@ -485,7 +484,7 @@ static int do_find(struct dirtree *new)
goto error;
if (*s=='s' || !s[5] || s[6]!='t') {
xstat(arg, &st);
- if (*s=='s') udl->d = st.st_dev, udl->i = st.st_ino;
+ if (*s=='s') udl->di.dev = st.st_dev, udl->di.ino = st.st_ino;
else udl->tm = *(struct timespec *)(((char *)&st)
+ macoff[!s[5] ? 0 : stridx("ac", s[6])+1]);
} else if (s[6] == 't') {
@@ -502,8 +501,7 @@ static int do_find(struct dirtree *new)
if (check) {
if (*s == 'u') test = new->st.st_uid == udl->uid;
else if (*s == 'g') test = new->st.st_gid == udl->gid;
- else if (*s == 's')
- test = new->st.st_dev == udl->d && new->st.st_ino == udl->i;
+ else if (*s == 's') test = same_dev_ino(&new->st, &udl->di);
else {
struct timespec *tm = (void *)(((char *)&new->st)
+ macoff[!s[5] ? 0 : stridx("ac", s[5])+1]);
@@ -621,7 +619,7 @@ static int do_find(struct dirtree *new)
ff = 0;
ch = *fmt;
- // long long is its own stack size on LP64, so handle seperately
+ // long long is its own stack size on LP64, so handle separately
if (ch == 'i' || ch == 's') {
strcpy(next+len, "lld");
printf(next, (ch == 'i') ? (long long)new->st.st_ino
diff --git a/toys/posix/grep.c b/toys/posix/grep.c
index 0474f7db..137c9c3c 100644
--- a/toys/posix/grep.c
+++ b/toys/posix/grep.c
@@ -4,13 +4,10 @@
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
*
- * Posix doesn't even specify -r, documenting deviations from it is silly.
-* echo hello | grep -w ''
-* echo '' | grep -w ''
-* echo hello | grep -f </dev/null
-*
+ * Posix doesn't even specify -r: too many deviations to document.
+ * TODO: -i is only ascii case insensitive, not unicode.
-USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcL(files-without-match)l(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EF]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
+USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwc(count)L(files-without-match)l(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EF]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
@@ -74,8 +71,8 @@ GLOBALS(
char *purple, *cyan, *red, *green, *grey;
struct double_list *reg;
- char indelim, outdelim;
- int found, tried;
+ int found, tried, delim;
+ struct arg_list *fixed[256];
)
struct reg {
@@ -96,16 +93,14 @@ static void outline(char *line, char dash, char *name, long lcount, long bcount,
{
if (!trim && FLAG(o)) return;
if (name && FLAG(H)) printf("%s%s%s%c", TT.purple, name, TT.cyan, dash);
- if (FLAG(c)) {
- printf("%s%ld", TT.grey, lcount);
- xputc(TT.outdelim);
- } else if (lcount && FLAG(n)) numdash(lcount, dash);
+ if (FLAG(c)) xprintf("%s%ld%c", TT.grey, lcount, TT.delim);
+ else if (lcount && FLAG(n)) numdash(lcount, dash);
if (bcount && FLAG(b)) numdash(bcount-1, dash);
if (line) {
if (FLAG(color)) xputsn(FLAG(o) ? TT.red : TT.grey);
// support embedded NUL bytes in output
xputsl(line, trim);
- xputc(TT.outdelim);
+ xputc(TT.delim);
}
}
@@ -145,82 +140,84 @@ static void do_grep(int fd, char *name)
// Loop through lines of input
for (;;) {
- char *line = 0, *start;
+ char *line = 0, *start, *ss, *pp;
struct reg *shoe;
size_t ulen;
long len;
- int matched = 0, rc = 1;
+ int matched = 0, rc = 1, move = 0, ii;
// get next line, check and trim delimiter
lcount++;
errno = 0;
- ulen = len = getdelim(&line, &ulen, TT.indelim, file);
+ ulen = len = getdelim(&line, &ulen, TT.delim, file);
if (len == -1 && errno) perror_msg("%s", name);
if (len<1) break;
- if (line[ulen-1] == TT.indelim) line[--ulen] = 0;
+ if (line[ulen-1] == TT.delim) line[--ulen] = 0;
// Prepare for next line
start = line;
- if (TT.reg) for (shoe = (void *)TT.reg; shoe; shoe = shoe->next)
- shoe->rc = 0;
+ for (shoe = (void *)TT.reg; shoe; shoe = shoe->next) shoe->rc = 0;
// Loop to handle multiple matches in same line
do {
regmatch_t *mm = (void *)toybuf;
+ struct arg_list *seek;
+
+ mm->rm_so = mm->rm_eo = 0;
+ rc = 1;
+
+ // Handle "fixed" (literal) matches (if any)
+ if (TT.e && *start) for (ss = start; ss-line<ulen; ss++) {
+ ii = FLAG(i) ? toupper(*ss) : *ss;
+ for (seek = TT.fixed[ii]; seek; seek = seek->next) {
+ if (*(pp = seek->arg)=='^' && !FLAG(F)) {
+ if (ss!=start) continue;
+ pp++;
+ }
+ for (ii = 0; pp[ii] && ss[ii]; ii++) {
+ if (!FLAG(F)) {
+ if (pp[ii]=='.') continue;
+ if (pp[ii]=='\\' && pp[ii+1]) pp++;
+ else if (pp[ii]=='$' && !pp[ii+1]) break;
+ }
+ if (FLAG(i)) {
+ if (toupper(pp[ii])!=toupper(ss[ii])) break;
+ } else if (pp[ii]!=ss[ii]) break;
+ }
+ if (pp[ii] && (pp[ii]!='$' || pp[ii+1] || ss[ii])) continue;
+ mm->rm_eo = (mm->rm_so = ss-start)+ii;
+ rc = 0;
- // Handle "fixed" (literal) matches
- if (FLAG(F)) {
- struct arg_list *seek, fseek;
- char *s = 0;
-
- for (seek = TT.e; seek; seek = seek->next) {
- if (FLAG(x)) {
- if (!(FLAG(i) ? strcasecmp : strcmp)(seek->arg, line)) s = line;
- } else if (!*seek->arg) {
- // No need to set fseek.next because this will match every line.
- seek = &fseek;
- fseek.arg = s = line;
- } else if (FLAG(i)) s = strcasestr(start, seek->arg);
- else s = strstr(start, seek->arg);
-
- if (s) break;
+ goto got;
}
+ if (FLAG(x)) break;
+ }
- if (s) {
- rc = 0;
- mm->rm_so = (s-start);
- mm->rm_eo = (s-start)+strlen(seek->arg);
- } else rc = 1;
-
- // Handle regex matches
- } else {
- int baseline = mm->rm_eo;
-
- mm->rm_so = mm->rm_eo = INT_MAX;
- rc = 1;
- for (shoe = (void *)TT.reg; shoe; shoe = shoe->next) {
-
- // Do we need to re-check this regex?
- if (!shoe->rc) {
- shoe->m.rm_so -= baseline;
- shoe->m.rm_eo -= baseline;
- if (!matched || shoe->m.rm_so<0)
- shoe->rc = regexec0(&shoe->r, start, ulen-(start-line), 1,
- &shoe->m, start==line ? 0 : REG_NOTBOL);
- }
+ // Empty pattern always matches
+ if (rc && *TT.fixed && !FLAG(o)) rc = 0;
+got:
+ // Handle regex matches (if any)
+ for (shoe = (void *)TT.reg; shoe; shoe = shoe->next) {
+ // Do we need to re-check this regex?
+ if (!shoe->rc) {
+ shoe->m.rm_so -= move;
+ shoe->m.rm_eo -= move;
+ if (!matched || shoe->m.rm_so<0)
+ shoe->rc = regexec0(&shoe->r, start, ulen-(start-line), 1,
+ &shoe->m, start==line ? 0 : REG_NOTBOL);
+ }
- // If we got a match, is it a _better_ match?
- if (!shoe->rc && (shoe->m.rm_so < mm->rm_so ||
- (shoe->m.rm_so == mm->rm_so && shoe->m.rm_eo >= mm->rm_eo)))
- {
- mm = &shoe->m;
- rc = 0;
- }
+ // If we got a match, is it a _better_ match?
+ if (!shoe->rc && (rc || shoe->m.rm_so < mm->rm_so ||
+ (shoe->m.rm_so == mm->rm_so && shoe->m.rm_eo >= mm->rm_eo)))
+ {
+ mm = &shoe->m;
+ rc = 0;
}
}
if (!rc && FLAG(o) && !mm->rm_eo && ulen>start-line) {
- start++;
+ move = 1;
continue;
}
@@ -238,7 +235,7 @@ static void do_grep(int fd, char *name)
if (!isalnum(c) && c != '_') c = 0;
}
if (c) {
- start += mm->rm_so+1;
+ move = mm->rm_so+1;
continue;
}
}
@@ -247,7 +244,7 @@ static void do_grep(int fd, char *name)
if (FLAG(o)) {
if (rc) mm->rm_eo = ulen-(start-line);
else if (!mm->rm_so) {
- start += mm->rm_eo;
+ move = mm->rm_eo;
continue;
} else mm->rm_eo = mm->rm_so;
} else {
@@ -271,7 +268,7 @@ static void do_grep(int fd, char *name)
xexit();
}
if (FLAG(L) || FLAG(l)) {
- if (FLAG(l)) xprintf("%s%c", name, TT.outdelim);
+ if (FLAG(l)) xprintf("%s%c", name, '\n'*!FLAG(Z));
free(line);
fclose(file);
return;
@@ -308,9 +305,8 @@ static void do_grep(int fd, char *name)
}
}
- start += mm->rm_eo;
- if (mm->rm_so == mm->rm_eo) break;
- } while (*start);
+ if (mm->rm_so == (move = mm->rm_eo)) break;
+ } while (*(start += move));
offset += len;
if (matched) {
@@ -318,7 +314,7 @@ static void do_grep(int fd, char *name)
if (FLAG(color) && !FLAG(o)) {
xputsn(TT.grey);
if (ulen > start-line) xputsl(start, ulen-(start-line));
- xputc(TT.outdelim);
+ xputc(TT.delim);
}
mcount++;
} else {
@@ -355,7 +351,7 @@ static void do_grep(int fd, char *name)
if (FLAG(m) && mcount >= TT.m) break;
}
- if (FLAG(L)) xprintf("%s%c", name, TT.outdelim);
+ if (FLAG(L)) xprintf("%s%c", name, TT.delim);
else if (FLAG(c)) outline(0, ':', name, mcount, 0, 1);
// loopfiles will also close the fd, but this frees an (opaque) struct.
@@ -368,21 +364,32 @@ static void do_grep(int fd, char *name)
}
}
+static int lensort(struct arg_list **a, struct arg_list **b)
+{
+ long la = strlen((*a)->arg), lb = strlen((*b)->arg);
+
+ if (la<lb) return -1;
+ if (la>lb) return 1;
+
+ return 0;
+}
+
static void parse_regex(void)
{
- struct arg_list *al, *new, *list = NULL;
- char *s, *ss;
+ struct arg_list *al, *new, *list = NULL, **last;
+ char *s, *ss, *special = "\\.^$[()|*+?{";
+ int len, ii, key;
// Add all -f lines to -e list. (Yes, this is leaking allocation context for
// exit to free. Not supporting nofork for this command any time soon.)
al = TT.f ? TT.f : TT.e;
while (al) {
if (TT.f) {
- if (!*(s = ss = xreadfile(al->arg, 0, 0))) {
- free(ss);
+ if (!*(s = xreadfile(al->arg, 0, 0))) {
+ free(s);
s = 0;
- }
- } else s = ss = al->arg;
+ } else if (*(ss = s+strlen(s)-1)=='\n') *ss = 0;
+ } else s = al->arg;
// Advance, when we run out of -f switch to -e.
al = al->next;
@@ -392,30 +399,69 @@ static void parse_regex(void)
}
if (!s) continue;
+ // NOTE: even with -z, -f is still \n delimited. Blank line = match all
// Split lines at \n, add individual lines to new list.
do {
- ss = FLAG(z) ? 0 : strchr(s, '\n');
- if (ss) *(ss++) = 0;
+ if ((ss = strchr(s, '\n'))) *(ss++) = 0;
new = xmalloc(sizeof(struct arg_list));
new->next = list;
new->arg = s;
list = new;
s = ss;
- } while (ss && *s);
+ } while (s);
}
TT.e = list;
- if (!FLAG(F)) {
- // Convert regex list
- for (al = TT.e; al; al = al->next) {
+ // Convert to regex where appropriate
+ for (last = &TT.e; *last;) {
+ // Can we use the fast path?
+ s = (*last)->arg;
+ if ('.'!=*s && !FLAG(F) && strcmp(s, "^$")) for (; *s; s++) {
+ if (*s=='\\') {
+ if (!s[1] || !strchr(special, *++s)) break;
+ if (!FLAG(E) && *s=='(') break;
+ } else if (*s>127 || strchr(special+4, *s)) break;
+ }
+
+ // Add entry to fast path (literal-ish match) or slow path (regexec)
+ if (!*s || FLAG(F)) last = &((*last)->next);
+ else {
struct reg *shoe;
- if (FLAG(o) && !*al->arg) continue;
dlist_add_nomalloc(&TT.reg, (void *)(shoe = xmalloc(sizeof(struct reg))));
- xregcomp(&shoe->r, al->arg,
- (REG_EXTENDED*!!FLAG(E))|(REG_ICASE*!!FLAG(i)));
+ xregcomp(&shoe->r, (*last)->arg, REG_EXTENDED*FLAG(E)|REG_ICASE*FLAG(i));
+ al = *last;
+ *last = (*last)->next;
+ free(al);
+ }
+ }
+ dlist_terminate(TT.reg);
+
+ // Sort fast path patterns into buckets by first character
+ for (al = TT.e; al; al = new) {
+ new = al->next;
+ if (FLAG(F)) key = 0;
+ else {
+ key = '^'==*al->arg;
+ if ('\\'==al->arg[key]) key++;
+ else if ('$'==al->arg[key] && !al->arg[key+1]) key++;
}
- dlist_terminate(TT.reg);
+ key = al->arg[key];
+ if (FLAG(i)) key = toupper(key);
+ al->next = TT.fixed[key];
+ TT.fixed[key] = al;
+ }
+
+ // Sort each fast path pattern set by length so first hit is longest match
+ if (TT.e) for (key = 0; key<256; key++) {
+ if (!TT.fixed[key]) continue;
+ for (len = 0, al = TT.fixed[key]; al; al = al->next) len++;
+ last = xmalloc(len*sizeof(void *));
+ for (len = 0, al = TT.fixed[key]; al; al = al->next) last[len++] = al;
+ qsort(last, len, sizeof(void *), (void *)lensort);
+ for (ii = 0; ii<len; ii++) last[ii]->next = ii ? last[ii-1] : 0;
+ TT.fixed[key] = last[len-1];
+ free(last);
}
}
@@ -429,7 +475,7 @@ static int do_grep_r(struct dirtree *new)
if (S_ISDIR(new->st.st_mode)) {
for (al = TT.exclude_dir; al; al = al->next)
if (!fnmatch(al->arg, new->name, 0)) return 0;
- return DIRTREE_RECURSE|(FLAG(R)?DIRTREE_SYMFOLLOW:0);
+ return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW*FLAG(R);
}
if (TT.S || TT.M) {
for (al = TT.S; al; al = al->next)
@@ -476,8 +522,7 @@ void grep_main(void)
if (!TT.A) TT.A = TT.C;
if (!TT.B) TT.B = TT.C;
- TT.indelim = '\n' * !FLAG(z);
- TT.outdelim = '\n' * !FLAG(Z);
+ TT.delim = '\n' * !FLAG(z);
// Handle egrep and fgrep
if (*toys.which->name == 'e') toys.optflags |= FLAG_E;
diff --git a/toys/posix/head.c b/toys/posix/head.c
index d5c0700c..04e1658c 100644
--- a/toys/posix/head.c
+++ b/toys/posix/head.c
@@ -34,7 +34,7 @@ GLOBALS(
static void do_head(int fd, char *name)
{
- long i, len, lines=TT.n, bytes=TT.c;
+ long i = 0, len = 0, lines = TT.n, bytes = TT.c;
if ((toys.optc > 1 && !FLAG(q)) || FLAG(v)) {
// Print an extra newline for all but the first file
@@ -50,11 +50,14 @@ static void do_head(int fd, char *name)
if (bytes) {
i = bytes >= len ? len : bytes;
bytes -= i;
- } else for(i=0; i<len;) if (toybuf[i++] == '\n' && !--lines) break;
+ } else for(i = 0; i<len;) if (toybuf[i++] == '\n' && !--lines) break;
xwrite(1, toybuf, i);
}
+ // attempt to unget extra data
+ if (len>i) lseek(fd, i-len, SEEK_CUR);
+
TT.file_no++;
}
diff --git a/toys/posix/iconv.c b/toys/posix/iconv.c
index d2721672..0674f3db 100644
--- a/toys/posix/iconv.c
+++ b/toys/posix/iconv.c
@@ -11,7 +11,6 @@ USE_ICONV(NEWTOY(iconv, "cst:f:", TOYFLAG_USR|TOYFLAG_BIN))
config ICONV
bool "iconv"
default y
- depends on TOYBOX_ICONV
help
usage: iconv [-f FROM] [-t TO] [FILE...]
diff --git a/toys/posix/kill.c b/toys/posix/kill.c
index 2eaba031..2f47f60f 100644
--- a/toys/posix/kill.c
+++ b/toys/posix/kill.c
@@ -144,8 +144,9 @@ void kill_main(void)
while (*args) {
char *arg = *(args++);
- pid = strtol(arg, &tmp, 10);
- if (*tmp || kill(pid, signum) < 0) error_msg("unknown pid '%s'", arg);
+ pid = estrtol(arg, &tmp, 10);
+ if (!errno && *tmp) errno = ESRCH;
+ if (errno || kill(pid, signum)<0) perror_msg("bad pid '%s'", arg);
}
}
}
diff --git a/toys/posix/ln.c b/toys/posix/ln.c
index 3cd5c7b8..65e1d46a 100644
--- a/toys/posix/ln.c
+++ b/toys/posix/ln.c
@@ -52,13 +52,15 @@ void ln_main(void)
} else buf.st_mode = 0;
for (i=0; i<toys.optc; i++) {
- char *oldnew = 0, *try = toys.optargs[i];
+ char *oldnew = 0, *try = toys.optargs[i], *ss;
if (S_ISDIR(buf.st_mode)) new = xmprintf("%s/%s", dest, basename(try));
else new = dest;
if (FLAG(r)) {
- try = relative_path(new, try);
+ ss = xstrdup(new);
+ try = relative_path(dirname(ss), try, 1);
+ free(ss);
if (!try) {
if (new != dest) free(new);
continue;
diff --git a/toys/posix/logger.c b/toys/posix/logger.c
index 906d64f4..427a5f5c 100644
--- a/toys/posix/logger.c
+++ b/toys/posix/logger.c
@@ -26,6 +26,8 @@ config LOGGER
GLOBALS(
char *p, *t;
+
+ int priority;
)
// find str in names[], accepting unambiguous short matches
@@ -48,9 +50,15 @@ static int arrayfind(char *str, char *names[], int len)
return maybe;
}
+static void syslog_line(char **pline, long len)
+{
+ if (!pline) return;
+ syslog(TT.priority, "%s", *pline);
+}
+
void logger_main(void)
{
- int facility = LOG_USER, priority = LOG_NOTICE, len = 0;
+ int facility = LOG_USER, len = 0;
char *s1, *s2, **arg,
*priorities[] = {"emerg", "alert", "crit", "error", "warning", "notice",
"info", "debug"},
@@ -58,6 +66,7 @@ void logger_main(void)
"lpr", "news", "uucp", "cron", "authpriv", "ftp"};
if (!TT.t) TT.t = xgetpwuid(geteuid())->pw_name;
+ TT.priority = LOG_NOTICE;
if (TT.p) {
if (!(s1 = strchr(TT.p, '.'))) s1 = TT.p;
else {
@@ -71,10 +80,11 @@ void logger_main(void)
facility *= 8;
}
- priority = arrayfind(s1, priorities, ARRAY_LEN(priorities));
- if (priority<0) error_exit("bad priority: %s", s1);
+ TT.priority = arrayfind(s1, priorities, ARRAY_LEN(priorities));
+ if (TT.priority<0) error_exit("bad priority: %s", s1);
}
+ openlog(TT.t, LOG_PERROR*FLAG(s), facility);
if (toys.optc) {
for (arg = toys.optargs; *arg; arg++) len += strlen(*arg)+1;
s1 = s2 = xmalloc(len);
@@ -82,9 +92,7 @@ void logger_main(void)
if (arg != toys.optargs) *s2++ = ' ';
s2 = stpcpy(s2, *arg);
}
- } else toybuf[readall(0, s1 = toybuf, sizeof(toybuf)-1)] = 0;
-
- openlog(TT.t, LOG_PERROR*FLAG(s), facility);
- syslog(priority, "%s", s1);
+ syslog(TT.priority, "%s", s1);
+ } else do_lines(0, '\n', syslog_line);
closelog();
}
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);
diff --git a/toys/posix/nohup.c b/toys/posix/nohup.c
index 90d374ef..48eb38c6 100644
--- a/toys/posix/nohup.c
+++ b/toys/posix/nohup.c
@@ -21,17 +21,14 @@ config NOHUP
void nohup_main(void)
{
- toys.exitval = 125;
xsignal(SIGHUP, SIG_IGN);
if (isatty(1)) {
close(1);
- if (-1 == open("nohup.out", O_CREAT|O_APPEND|O_WRONLY,
- S_IRUSR|S_IWUSR ))
- {
+ if (open("nohup.out", O_CREAT|O_APPEND|O_WRONLY, 0600) == -1) {
char *temp = getenv("HOME");
- temp = xmprintf("%s/%s", temp ? temp : "", "nohup.out");
- xcreate(temp, O_CREAT|O_APPEND|O_WRONLY, 0600);
+ xcreate(temp ? temp = xmprintf("%s/nohup.out", temp) : "nohup.out",
+ O_CREAT|O_APPEND|O_WRONLY, 0600);
free(temp);
}
}
@@ -39,6 +36,6 @@ void nohup_main(void)
close(0);
xopen_stdio("/dev/null", O_RDONLY);
}
- toys.exitval = 0;
+
xexec(toys.optargs);
}
diff --git a/toys/posix/od.c b/toys/posix/od.c
index f0edcd76..c12055e6 100644
--- a/toys/posix/od.c
+++ b/toys/posix/od.c
@@ -56,8 +56,8 @@ static int od_out_t(struct odtype *t, char *buf, int *offset)
// Handle ascii
if (t->type < 2) {
char c = TT.buf[(*offset)++];
- pad += 4;
+ pad += 4;
if (!t->type) {
c &= 127;
if (c<=32) sprintf(buf, "%.3s", ascii+(3*c));
@@ -93,8 +93,7 @@ static int od_out_t(struct odtype *t, char *buf, int *offset)
// Integer types
} else {
unsigned long long ll = 0, or;
- char *c[] = {"%*lld", "%*llu", "%0*llo", "%0*llx"},
- *class = c[t->type-2];
+ char *c[] = {"%*lld", "%*llu", "%0*llo", "%0*llx"}, *class = c[t->type-2];
// Work out width of field
if (t->size == 8) {
@@ -126,7 +125,6 @@ static int od_out_t(struct odtype *t, char *buf, int *offset)
static void od_outline(void)
{
- unsigned flags = toys.optflags;
char buf[128], *abases[] = {"", "%07lld", "%07llo", "%06llx"};
struct odtype *types = (struct odtype *)toybuf;
int i, j, len, pad;
@@ -134,8 +132,8 @@ static void od_outline(void)
if (TT.leftover<TT.w) memset(TT.buf+TT.leftover, 0, TT.w-TT.leftover);
// Handle duplciate lines as *
- if (!(flags&FLAG_v) && TT.j != TT.pos && TT.leftover
- && !memcmp(TT.bufs[0], TT.bufs[1], TT.w))
+ if (!FLAG(v) && TT.j != TT.pos && TT.leftover
+ && !smemcmp(TT.bufs[0], TT.bufs[1], TT.w))
{
if (!TT.star) {
xputs("*");
@@ -201,7 +199,7 @@ static void do_od(int fd, char *name)
char *buf = TT.buf + TT.leftover;
int len = TT.w - TT.leftover;
- if (toys.optflags & FLAG_N) {
+ if (FLAG(N)) {
if (!TT.N) break;
if (TT.N < len) len = TT.N;
}
@@ -276,12 +274,12 @@ void od_main(void)
// Collect -t entries
for (arg = TT.t; arg; arg = arg->next) append_base(arg->arg);
- if (toys.optflags & FLAG_b) append_base("o1");
- if (toys.optflags & FLAG_c) append_base("c");
- if (toys.optflags & FLAG_d) append_base("u2");
- if (toys.optflags & FLAG_o) append_base("o2");
- if (toys.optflags & FLAG_s) append_base("d2");
- if (toys.optflags & FLAG_x) append_base("x2");
+ if (FLAG(b)) append_base("o1");
+ if (FLAG(c)) append_base("c");
+ if (FLAG(d)) append_base("u2");
+ if (FLAG(o)) append_base("o2");
+ if (FLAG(s)) append_base("d2");
+ if (FLAG(x)) append_base("x2");
if (!TT.types) append_base("o2");
loopfiles(toys.optargs, do_od);
diff --git a/toys/posix/patch.c b/toys/posix/patch.c
index 4b8c61c3..6bdd4615 100644
--- a/toys/posix/patch.c
+++ b/toys/posix/patch.c
@@ -143,7 +143,7 @@ static int loosecmp(char *aa, char *bb)
static int apply_one_hunk(void)
{
struct double_list *plist, *buf = 0, *check;
- int matcheof, trail = 0, reverse = FLAG(R), backwarn = 0, allfuzz, fuzz, i;
+ int matcheof, trail = 0, backwarn = 0, allfuzz, fuzz, i;
int (*lcmp)(char *aa, char *bb) = FLAG(l) ? (void *)loosecmp : (void *)strcmp;
// Match EOF if there aren't as many ending context lines as beginning
@@ -157,7 +157,7 @@ static int apply_one_hunk(void)
// Only allow fuzz if 2 context lines have multiple nonwhitespace chars.
// avoids the "all context was blank or } lines" issue. Removed lines
// count as context since they're matched.
- if (c==' ' || c=="-+"[reverse]) {
+ if (c==' ' || c=="-+"[FLAG(R)]) {
s = plist->data+1;
while (isspace(*s)) s++;
if (*s && s[1] && !isspace(s[1])) fuzz++;
@@ -167,7 +167,7 @@ static int apply_one_hunk(void)
}
matcheof = !trail || trail < TT.context;
if (fuzz<2) allfuzz = 0;
- else allfuzz = FLAG(F) ? TT.F : (TT.context ? TT.context-1 : 0);
+ else allfuzz = TT.F ? : TT.context ? TT.context-1 : 0;
if (FLAG(x)) fprintf(stderr,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
@@ -180,7 +180,7 @@ static int apply_one_hunk(void)
// Figure out which line of hunk to compare with next. (Skip lines
// of the hunk we'd be adding.)
- while (plist && *plist->data == "+-"[reverse]) {
+ while (plist && *plist->data == "+-"[FLAG(R)]) {
if (data && !lcmp(data, plist->data+1))
if (!backwarn) backwarn = TT.linenum;
plist = plist->next;
@@ -260,7 +260,7 @@ fuzzed:
}
out:
// We have a match. Emit changed data.
- TT.state = "-+"[reverse];
+ TT.state = "-+"[FLAG(R)];
while ((plist = dlist_pop(&TT.current_hunk))) {
if (TT.state == *plist->data || *plist->data == ' ') {
if (*plist->data == ' ') dprintf(TT.fileout, "%s\n", buf->data);
@@ -279,13 +279,13 @@ done:
// read a filename that has been quoted or escaped
static char *unquote_file(char *filename)
{
- char *s = filename, *t;
+ char *s = filename, *t, *newfile;
// Return copy of file that wasn't quoted
if (*s++ != '"' || !*s) return xstrdup(filename);
// quoted and escaped filenames are larger than the original
- for (t = filename = xmalloc(strlen(s) + 1); *s != '"'; s++) {
+ for (t = newfile = xmalloc(strlen(s) + 1); *s != '"'; s++) {
if (!s[1]) error_exit("bad %s", filename);
// don't accept escape sequences unless the filename is quoted
@@ -300,7 +300,7 @@ static char *unquote_file(char *filename)
}
*t = 0;
- return filename;
+ return newfile;
}
// Read a patch file and find hunks, opening/creating/deleting files.
@@ -313,7 +313,7 @@ static char *unquote_file(char *filename)
void patch_main(void)
{
- int reverse = FLAG(R), state = 0, patchlinenum = 0, strip = 0;
+ int state = 0, patchlinenum = 0, strip = 0;
char *oldname = NULL, *newname = NULL;
if (toys.optc == 2) TT.i = toys.optargs[1];
@@ -420,7 +420,7 @@ void patch_main(void)
// If an original file was provided on the command line, it overrides
// *all* files mentioned in the patch, not just the first.
if (toys.optc) {
- char **which = reverse ? &oldname : &newname;
+ char **which = FLAG(R) ? &oldname : &newname;
free(*which);
*which = strdup(toys.optargs[0]);
@@ -429,12 +429,12 @@ void patch_main(void)
TT.p = 0;
}
- name = reverse ? oldname : newname;
+ name = FLAG(R) ? oldname : newname;
// We're deleting oldname if new file is /dev/null (before -p)
// or if new hunk is empty (zero context) after patching
- if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) {
- name = reverse ? newname : oldname;
+ if (!strcmp(name, "/dev/null") || !(FLAG(R) ? oldsum : newsum)) {
+ name = FLAG(R) ? newname : oldname;
del++;
}
@@ -449,7 +449,7 @@ void patch_main(void)
if (del) {
if (!FLAG(s)) printf("removing %s\n", name);
- xunlink(name);
+ if (!FLAG(dry_run)) xunlink(name);
state = 0;
// If we've got a file to open, do so.
} else if (!FLAG(p) || i <= TT.p) {
@@ -457,8 +457,11 @@ void patch_main(void)
if ((!strcmp(oldname, "/dev/null") || !oldsum) && access(name, F_OK))
{
if (!FLAG(s)) printf("creating %s\n", name);
- if (mkpath(name)) perror_exit("mkpath %s", name);
- TT.filein = xcreate(name, O_CREAT|O_EXCL|O_RDWR, 0666);
+ if (FLAG(dry_run)) TT.filein = xopen("/dev/null", O_RDWR);
+ else {
+ if (mkpath(name)) perror_exit("mkpath %s", name);
+ TT.filein = xcreate(name, O_CREAT|O_EXCL|O_RDWR, 0666);
+ }
} else {
if (!FLAG(s)) printf("patching %s\n", name);
TT.filein = xopenro(name);
diff --git a/toys/posix/printf.c b/toys/posix/printf.c
index 2cbf3035..93374731 100644
--- a/toys/posix/printf.c
+++ b/toys/posix/printf.c
@@ -5,7 +5,7 @@
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
*
- * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
+ * TODO: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MAYFORK))
@@ -23,7 +23,7 @@ config PRINTF
#include "toys.h"
// Detect matching character (return true/false) and advance pointer if match.
-static int eat(char **s, char c)
+static int chrstart(char **s, char c)
{
int x = (**s == c);
@@ -42,7 +42,7 @@ static int handle_slash(char **esc_val, int posix)
if (*ptr == 'c') xexit();
// 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
- if (eat(&ptr, 'x')) base = 16;
+ if (chrstart(&ptr, 'x')) base = 16;
else {
if (posix && *ptr=='0') ptr++;
if (*ptr >= '0' && *ptr <= '7') base = 8;
@@ -85,8 +85,8 @@ void printf_main(void)
// Loop through characters in format
while (*f) {
- if (eat(&f, '\\')) putchar(handle_slash(&f, 0));
- else if (!eat(&f, '%') || *f == '%') putchar(*f++);
+ if (chrstart(&f, '\\')) putchar(handle_slash(&f, 0));
+ else if (!chrstart(&f, '%') || *f == '%') putchar(*f++);
// Handle %escape
else {
@@ -97,10 +97,10 @@ void printf_main(void)
*to++ = '%';
while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
for (;;) {
- if (eat(&f, '*')) {
+ if (chrstart(&f, '*')) {
if (*arg) wp[i] = atolx(*arg++);
} else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0';
- if (i++ || !eat(&f, '.')) break;
+ if (i++ || !chrstart(&f, '.')) break;
wp[1] = 0;
}
c = *f++;
@@ -110,7 +110,8 @@ void printf_main(void)
// Output %esc using parsed format string
if (c == 'b') {
- while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
+ while (*aa)
+ putchar(chrstart(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
continue;
} else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
@@ -130,7 +131,7 @@ void printf_main(void)
printf(toybuf, wp[0], wp[1], ld);
} else error_exit("bad %%%c@%ld", c, (long)(f-*toys.optargs));
- if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
+ if (end && (*end || errno==ERANGE)) perror_msg("bad %%%c %s", c, aa);
}
}
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index 3884097b..2ff3ba73 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -209,7 +209,10 @@ GLOBALS(
} pgrep;
};
- struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
+ struct ps_ptr_len {
+ void *ptr;
+ long len;
+ } gg, GG, pp, PP, ss, tt, uu, UU;
struct dirtree *threadparent;
unsigned width, height, scroll;
dev_t tty;
@@ -461,7 +464,7 @@ static void help_help(void)
// process match filter for top/ps/pgrep: Return 0 to discard, nonzero to keep
static int shared_match_process(long long *slot)
{
- struct ptr_len match[] = {
+ struct ps_ptr_len match[] = {
{&TT.gg, SLOT_gid}, {&TT.GG, SLOT_rgid}, {&TT.pp, SLOT_pid},
{&TT.PP, SLOT_ppid}, {&TT.ss, SLOT_sid}, {&TT.tt, SLOT_ttynr},
{&TT.uu, SLOT_uid}, {&TT.UU, SLOT_ruid}
@@ -471,7 +474,7 @@ static int shared_match_process(long long *slot)
// Do we have -g -G -p -P -s -t -u -U options selecting processes?
for (i = 0; i < ARRAY_LEN(match); i++) {
- struct ptr_len *mm = match[i].ptr;
+ struct ps_ptr_len *mm = match[i].ptr;
if (mm->len) {
ll = mm->ptr;
@@ -842,7 +845,7 @@ static int get_ps(struct dirtree *new)
off_t temp = 6;
sprintf(buf, "%lld/exe", slot[SLOT_tid]);
- if (readfileat(fd, buf, buf, &temp) && !memcmp(buf, "\177ELF", 4)) {
+ if (readfileat(fd, buf, buf, &temp) && !smemcmp(buf, "\177ELF", 4)) {
if (buf[4] == 1) slot[SLOT_bits] = 32;
else if (buf[4] == 2) slot[SLOT_bits] = 64;
}
@@ -1146,7 +1149,7 @@ static long long get_headers(struct ofields *field, char *buf, int blen)
// Parse command line options -p -s -t -u -U -g -G
static char *parse_rest(void *data, char *str, int len)
{
- struct ptr_len *pl = (struct ptr_len *)data;
+ struct ps_ptr_len *pl = (struct ps_ptr_len *)data;
long *ll = pl->ptr;
char *end;
int num = 0;
@@ -1345,7 +1348,7 @@ void ps_main(void)
not_o = "F,S,UID,%sPPID,C,PRI,NI,BIT,SZ,WCHAN,TTY,TIME,CMD";
else if (CFG_TOYBOX_ON_ANDROID)
sprintf(not_o = toybuf+128,
- "USER,%%sPPID,VSIZE,RSS,WCHAN:10,ADDR:10,S,%s",
+ "USER,%%sPPID,VSIZE:10,RSS,WCHAN:10,ADDR:10,S,%s",
FLAG(T) ? "CMD" : "NAME");
sprintf(toybuf, not_o, FLAG(T) ? "PID,TID," : "PID,");
@@ -1518,13 +1521,13 @@ static void top_common(
"iow", "irq", "sirq", "host"};
unsigned tock = 0;
int i, lines, topoff = 0, done = 0;
- char stdout_buf[BUFSIZ];
+ char stdout_buf[8192];
if (!TT.fields) perror_exit("no -o");
// Avoid flicker and hide the cursor in interactive mode.
if (!FLAG(b)) {
- setbuf(stdout, stdout_buf);
+ setbuffer(stdout, stdout_buf, sizeof(stdout_buf));
sigatexit(top_cursor_cleanup);
xputsn("\e[?25l");
}
@@ -1631,8 +1634,8 @@ static void top_common(
run[1+stridx("RTtZ", *string_field(mix.tb[i], &field))]++;
sprintf(toybuf,
"%ss: %d total, %3ld running, %3ld sleeping, %3ld stopped, "
- "%3ld zombie", FLAG(H)?"Thread":"Task", mix.count, run[1], run[0],
- run[2]+run[3], run[4]);
+ "%3ld zombie", FLAG(H) ? "Thread" : "Task", mix.count, run[1],
+ run[0], run[2]+run[3], run[4]);
lines = header_line(lines, 0);
if (readfile("/proc/meminfo", toybuf+256, sizeof(toybuf)-256)) {
@@ -1840,12 +1843,12 @@ static int iotop_filter(long long *oslot, long long *nslot, int milis)
if (!FLAG(a)) merge_deltas(oslot, nslot, milis);
else oslot[SLOT_upticks] = ((millitime()-TT.time)*TT.ticks)/1000;
- return !FLAG(O)||oslot[SLOT_iobytes+!FLAG(A)];
+ return !FLAG(O) || oslot[SLOT_iobytes+!FLAG(A)];
}
void iotop_main(void)
{
- char *s1 = 0, *s2 = 0, *d = "D"+!!FLAG(A);
+ char *s1 = 0, *s2 = 0, *d = "D"+FLAG(A);
if (FLAG(K)) TT.forcek++;
@@ -1880,9 +1883,7 @@ static void do_pgk(struct procpid *tb)
}
if (!FLAG(c) && (!TT.pgrep.signal || TT.tty)) {
printf("%lld", *tb->slot);
- if (FLAG(l))
- printf(" %s", tb->str+tb->offset[4]*!!FLAG(f));
-
+ if (FLAG(l)) printf(" %s", tb->str+tb->offset[4]*FLAG(f));
printf("%s", TT.pgrep.d ? TT.pgrep.d : "\n");
}
}
@@ -1892,7 +1893,7 @@ static void match_pgrep(void *p)
struct procpid *tb = p;
regmatch_t match;
struct regex_list *reg;
- char *name = tb->str+tb->offset[4]*!!FLAG(f);
+ char *name = tb->str+tb->offset[4]*FLAG(f);
// Never match ourselves.
if (TT.pgrep.self == *tb->slot) return;
diff --git a/toys/posix/pwd.c b/toys/posix/pwd.c
index c7dc78f7..c6f45ad0 100644
--- a/toys/posix/pwd.c
+++ b/toys/posix/pwd.c
@@ -41,8 +41,7 @@ void pwd_main(void)
// If current directory exists, make sure it matches.
if (s && pwd)
- if (stat(pwd, &st1) || stat(PWD, &st2) || st1.st_ino != st2.st_ino ||
- st1.st_dev != st2.st_dev) s = 0;
+ if (stat(pwd, &st1) || stat(PWD, &st2) || !same_file(&st1, &st2)) s = 0;
} else s = 0;
// If -L didn't give us a valid path, use cwd.
diff --git a/toys/posix/rm.c b/toys/posix/rm.c
index be611e67..cfe282f7 100644
--- a/toys/posix/rm.c
+++ b/toys/posix/rm.c
@@ -4,7 +4,7 @@
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html
-USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
+USE_RM(NEWTOY(rm, "f(force)iRrv[-fi]", TOYFLAG_BIN))
config RM
bool "rm"
diff --git a/toys/posix/sed.c b/toys/posix/sed.c
index cea12ea5..0de90633 100644
--- a/toys/posix/sed.c
+++ b/toys/posix/sed.c
@@ -4,6 +4,8 @@
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
*
+ * xform See https://www.gnu.org/software/tar/manual/html_section/transform.html
+ *
* TODO: lines > 2G could wrap signed int length counters. Not just getline()
* but N and s///
* TODO: make y// handle unicode, unicode delimiters
@@ -12,12 +14,15 @@
* test '//q' with no previous regex, also repeat previous regex?
*
* Deviations from POSIX: allow extended regular expressions with -r,
- * editing in place with -i, separate with -s, NUL-separated input with -z,
+ * editing in place with -i, separate with -s, NUL-delimited strings with -z,
* printf escapes in text, line continuations, semicolons after all commands,
* 2-address anywhere an address is allowed, "T" command, multiline
* continuations for [abc], \; to end [abc] argument before end of line.
+ * Explicit violations of stuff posix says NOT to do: N at EOF does default
+ * print, l escapes \n
+ * Added --tarxform mode to support tar --xform
-USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
+USE_SED(NEWTOY(sed, "(help)(version)(tarxform)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
config SED
bool "sed"
@@ -74,8 +79,8 @@ config SED
G Get remembered line (appending to current line)
h Remember this line (overwriting remembered line)
H Remember this line (appending to remembered line, if any)
- l Print line escaping \abfrtv (but not \n), octal escape other nonprintng
- chars, wrap lines to terminal width with \, append $ to end of line.
+ l Print line escaping \abfrtvn, octal escape other nonprintng chars,
+ wrap lines to terminal width with \, append $ to end of line.
n Print default output and read next line over current line (quit at EOF)
N Append \n and next line of input to this line. Quit at EOF without
default output. Advances line counter for ADDRESS and "=".
@@ -126,12 +131,12 @@ GLOBALS(
// processed pattern list
struct double_list *pattern;
- char *nextline, *remember;
+ char *nextline, *remember, *tarxform;
void *restart, *lastregex;
long nextlen, rememberlen, count;
int fdout, noeol;
- unsigned xx;
- char delim;
+ unsigned xx, tarxlen, xflags;
+ char delim, xftype;
)
// Linked list of parsed sed commands. Offset fields indicate location where
@@ -147,21 +152,38 @@ struct sedcmd {
int rmatch[2]; // offset of regex struct for prefix matches (/abc/,/def/p)
int arg1, arg2, w; // offset of two arguments per command, plus s//w filename
unsigned not, hit;
- unsigned sflags; // s///flag bits: i=1, g=2, p=4, x=8
+ unsigned sflags; // s///flag bits, see SFLAG macros below
char c; // action
};
+#define SFLAG_i 1
+#define SFLAG_g 2
+#define SFLAG_p 4
+#define SFLAG_x 8
+#define SFLAG_slash 16
+#define SFLAG_R 32
+#define SFLAG_S 64
+#define SFLAG_H 128
+
// Write out line with potential embedded NUL, handling eol/noeol
static int emit(char *line, long len, int eol)
{
- int l, old = line[len];
-
- if (TT.noeol && !writeall(TT.fdout, "\n", 1)) return 1;
+ int l = len, old = line[len];
+
+ if (FLAG(tarxform)) {
+ TT.tarxform = xrealloc(TT.tarxform, TT.tarxlen+len+TT.noeol+eol);
+ if (TT.noeol) TT.tarxform[TT.tarxlen++] = TT.delim;
+ memcpy(TT.tarxform+TT.tarxlen, line, len);
+ TT.tarxlen += len;
+ if (eol) TT.tarxform[TT.tarxlen++] = TT.delim;
+ } else {
+ if (TT.noeol && !writeall(TT.fdout, &TT.delim, 1)) return 1;
+ if (eol) line[len++] = TT.delim;
+ if (!len) return 0;
+ l = writeall(TT.fdout, line, len);
+ if (eol) line[len-1] = old;
+ }
TT.noeol = !eol;
- if (eol) line[len++] = '\n';
- if (!len) return 0;
- l = writeall(TT.fdout, line, len);
- if (eol) line[len-1] = old;
if (l != len) {
if (TT.fdout != 1) perror_msg("short write");
@@ -180,7 +202,7 @@ static char *extend_string(char **old, char *new, int oldlen, int newlen)
if (newline) newlen = -newlen;
s = *old = xrealloc(*old, oldlen+newlen+newline+1);
- if (newline) s[oldlen++] = '\n';
+ if (newline) s[oldlen++] = TT.delim;
memcpy(s+oldlen, new, newlen);
s[oldlen+newlen] = 0;
@@ -206,32 +228,52 @@ static void sed_line(char **pline, long plen)
int file;
char *str;
} *append = 0;
- char *line = TT.nextline;
- long len = TT.nextlen;
+ char *line;
+ long len;
struct sedcmd *command;
int eol = 0, tea = 0;
- // Ignore EOF for all files before last unless -i
- if (!pline && !FLAG(i) && !FLAG(s)) return;
-
- // Grab next line for deferred processing (EOF detection: we get a NULL
- // pline at EOF to flush last line). Note that only end of _last_ input
- // file matches $ (unless we're doing -i).
- TT.nextline = 0;
- TT.nextlen = 0;
- if (pline) {
- TT.nextline = *pline;
- TT.nextlen = plen;
+ if (FLAG(tarxform)) {
+ if (!pline) return;
+
+ line = *pline;
+ len = plen;
*pline = 0;
+ pline = 0;
+ } else {
+ line = TT.nextline;
+ len = TT.nextlen;
+
+ // Ignore EOF for all files before last unless -i or -s
+ if (!pline && !FLAG(i) && !FLAG(s)) return;
+
+ // Grab next line for deferred processing (EOF detection: we get a NULL
+ // pline at EOF to flush last line). Note that only end of _last_ input
+ // file matches $ (unless we're doing -i).
+ TT.nextline = 0;
+ TT.nextlen = 0;
+ if (pline) {
+ TT.nextline = *pline;
+ TT.nextlen = plen;
+ *pline = 0;
+ }
}
if (!line || !len) return;
- if (line[len-1] == '\n') line[--len] = eol++;
+ if (line[len-1] == TT.delim) line[--len] = eol++;
+ if (FLAG(tarxform) && len) {
+ TT.xftype = line[--len];
+ line[len] = 0;
+ }
TT.count++;
- // The restart-1 is because we added one to make sure it wasn't NULL,
- // otherwise N as last command would restart script
- command = TT.restart ? ((struct sedcmd *)TT.restart)-1 : (void *)TT.pattern;
+ // To prevent N as last command from restarting script, we added 1 to restart
+ // so we'd use it here even when NULL. Alas, compilers that think C has
+ // references instead of pointers assume ptr-1 can never be NULL (demonstrably
+ // untrue) and inappropriately dead code eliminate, so use LP64 math until
+ // we get a -fpointers-are-not-references compiler option.
+ command = (void *)(TT.restart ? ((unsigned long)TT.restart)-1
+ : (unsigned long)TT.pattern);
TT.restart = 0;
while (command) {
@@ -328,7 +370,7 @@ static void sed_line(char **pline, long plen)
} else if (c=='D') {
// Delete up to \n or end of buffer
str = line;
- while ((str-line)<len) if (*(str++) == '\n') break;
+ while ((str-line)<len) if (*(str++) == TT.delim) break;
len -= str - line;
memmove(line, str, len);
@@ -344,11 +386,11 @@ static void sed_line(char **pline, long plen)
continue;
} else if (c=='g') {
free(line);
- line = xstrdup(TT.remember);
+ line = xmemdup(TT.remember, TT.rememberlen+1);
len = TT.rememberlen;
} else if (c=='G') {
line = xrealloc(line, len+TT.rememberlen+2);
- line[len++] = '\n';
+ line[len++] = TT.delim;
memcpy(line+len, TT.remember, TT.rememberlen);
line[len += TT.rememberlen] = 0;
} else if (c=='h') {
@@ -357,7 +399,7 @@ static void sed_line(char **pline, long plen)
TT.rememberlen = len;
} else if (c=='H') {
TT.remember = xrealloc(TT.remember, TT.rememberlen+len+2);
- TT.remember[TT.rememberlen++] = '\n';
+ TT.remember[TT.rememberlen++] = TT.delim;
memcpy(TT.remember+TT.rememberlen, line, len);
TT.remember[TT.rememberlen += len] = 0;
} else if (c=='i') {
@@ -379,24 +421,26 @@ static void sed_line(char **pline, long plen)
emit(toybuf, off, 1);
off = 0;
}
- x = stridx("\\\a\b\f\r\t\v", line[i]);
+ x = stridx("\\\a\b\f\r\t\v\n", line[i]);
if (x != -1) {
toybuf[off++] = '\\';
- toybuf[off++] = "\\abfrtv"[x];
+ toybuf[off++] = "\\abfrtvn"[x];
} else if (line[i] >= ' ') toybuf[off++] = line[i];
else off += sprintf(toybuf+off, "\\%03o", line[i]);
}
toybuf[off++] = '$';
emit(toybuf, off, 1);
} else if (c=='n') {
- TT.restart = command->next+1;
+ // The +1 forces restart processing even when next is null
+ TT.restart = (void *)(((unsigned long)command->next)+1);
break;
} else if (c=='N') {
// Can't just grab next line because we could have multiple N and
// we need to actually read ahead to get N;$p EOF detection right.
if (pline) {
- TT.restart = command->next+1;
+ // The +1 forces restart processing even when next is null
+ TT.restart = (void *)(((unsigned long)command->next)+1);
extend_string(&line, TT.nextline, len, -TT.nextlen);
free(TT.nextline);
TT.nextline = line;
@@ -407,7 +451,7 @@ static void sed_line(char **pline, long plen)
// Pending append goes out right after N
goto done;
} else if (c=='p' || c=='P') {
- char *l = (c=='P') ? strchr(line, '\n') : 0;
+ char *l = (c=='P') ? strchr(line, TT.delim) : 0;
if (emit(line, l ? l-line : len, eol)) break;
} else if (c=='q' || c=='Q') {
@@ -425,23 +469,33 @@ static void sed_line(char **pline, long plen)
regmatch_t *match = (void *)toybuf;
regex_t *reg = get_regex(command, command->arg1);
int mflags = 0, count = 0, l2used = 0, zmatch = 1, l2l = len, l2old = 0,
- mlen, off, newlen;
+ bonk = 0, mlen, off, newlen;
+
+ // Skip suppressed --tarxform types
+ if (TT.xftype && (command->sflags & (SFLAG_R<<stridx("rsh", TT.xftype))));
// Loop finding match in remaining line (up to remaining len)
- while (!regexec0(reg, rline, len-(rline-line), 10, match, mflags)) {
+ else while (!regexec0(reg, rline, len-(rline-line), 10, match, mflags)) {
+ mlen = match[0].rm_eo-match[0].rm_so;
+
+ // xform matches ending in / aren't allowed to match entire line
+ if ((command->sflags & SFLAG_slash) && mlen==len) {
+ while (len && ++bonk && line[--len]=='/');
+ continue;
+ }
+
mflags = REG_NOTBOL;
// Zero length matches don't count immediately after a previous match
- mlen = match[0].rm_eo-match[0].rm_so;
if (!mlen && !zmatch) {
if (rline-line == len) break;
- l2[l2used++] = *rline++;
+ if (l2) l2[l2used++] = *rline++;
zmatch++;
continue;
} else zmatch = 0;
// If we're replacing only a specific match, skip if this isn't it
- off = command->sflags>>4;
+ off = command->sflags>>8;
if (off && off != ++count) {
if (l2) memcpy(l2+l2used, rline, match[0].rm_eo);
l2used += match[0].rm_eo;
@@ -470,7 +524,11 @@ static void sed_line(char **pline, long plen)
// Adjust allocation size of new string, copy data we know we'll keep
l2l += newlen-mlen;
- if ((l2l|0xfff) > l2old) l2 = xrealloc(l2, l2old = (l2l|0xfff)+1);
+ if ((mlen = l2l|0xfff) > l2old) {
+ l2 = xrealloc(l2, ++mlen);
+ if (l2used && !l2old) memcpy(l2, rline-l2used, l2used);
+ l2old = mlen;
+ }
if (match[0].rm_so) {
memcpy(l2+l2used, rline, match[0].rm_so);
l2used += match[0].rm_so;
@@ -503,9 +561,9 @@ static void sed_line(char **pline, long plen)
l2used += newlen;
rline += match[0].rm_eo;
- // Stop after first substitution unless we have flag g
- if (!(command->sflags & 2)) break;
+ if (!(command->sflags & SFLAG_g)) break;
}
+ len += bonk;
// If we made any changes, finish off l2 and swap it for line
if (l2) {
@@ -518,8 +576,7 @@ static void sed_line(char **pline, long plen)
}
if (mflags) {
- // flag p
- if (command->sflags & 4) emit(line, len, eol);
+ if (command->sflags & SFLAG_p) emit(line, len, eol);
tea = 1;
if (command->w) goto writenow;
@@ -529,6 +586,8 @@ static void sed_line(char **pline, long plen)
char *name;
writenow:
+ if (FLAG(tarxform)) error_exit("tilt");
+
// Swap out emit() context
fd = TT.fdout;
noeol = TT.noeol;
@@ -572,9 +631,10 @@ writenow:
command = command->next;
}
+done:
if (line && !FLAG(n)) emit(line, len, eol);
-done:
+ // TODO: should "sed -z ax" use \n instead of NUL?
if (dlist_terminate(append)) while (append) {
struct append *a = append->next;
@@ -583,7 +643,7 @@ done:
// Force newline if noeol pending
if (fd != -1) {
- if (TT.noeol) xwrite(TT.fdout, "\n", 1);
+ if (TT.noeol) xwrite(TT.fdout, &TT.delim, 1);
TT.noeol = 0;
xsendfile(fd, TT.fdout);
close(fd);
@@ -594,6 +654,12 @@ done:
append = a;
}
free(line);
+
+ if (TT.tarxlen) {
+ dprintf(TT.fdout, "%08x", --TT.tarxlen);
+ writeall(TT.fdout, TT.tarxform, TT.tarxlen);
+ TT.tarxlen = 0;
+ }
}
// Callback called on each input file
@@ -736,6 +802,16 @@ static void parse_pattern(char **pline, long len)
}
if (!*line) return;
+ if (FLAG(tarxform) && strstart(&line, "flags=")) {
+ TT.xflags = 7;
+ while (0<=(i = stridx("rRsShH", *line))) {
+ if (i&1) TT.xflags |= 1<<(i>>1);
+ else TT.xflags &= ~(1<<(i>>1));
+ line++;
+ }
+ continue;
+ }
+
// Start by writing data into toybuf.
errstart = line;
@@ -761,7 +837,7 @@ static void parse_pattern(char **pline, long len)
if (!(s = unescape_delimited_string(&line, 0))) goto error;
if (!*s) command->rmatch[i] = 0;
else {
- xregcomp((void *)reg, s, REG_EXTENDED*!!FLAG(r));
+ xregcomp((void *)reg, s, REG_EXTENDED*FLAG(r));
command->rmatch[i] = reg-toybuf;
reg += sizeof(regex_t);
}
@@ -839,27 +915,36 @@ resume_s:
i = command->arg1;
command->arg1 = command->arg2;
command->arg2 = i;
+ command->sflags = TT.xflags*SFLAG_R;
// get flags
for (line++; *line; line++) {
long l;
if (isspace(*line) && *line != '\n') continue;
-
if (0 <= (l = stridx("igpx", *line))) command->sflags |= 1<<l;
else if (*line == 'I') command->sflags |= 1<<0;
- else if (!(command->sflags>>4) && 0<(l = strtol(line, &line, 10))) {
- command->sflags |= l << 4;
+ else if (FLAG(tarxform) && 0 <= (l = stridx("RSH", *line)))
+ command->sflags |= SFLAG_R<<l;
+ // Given that the default is rsh all enabled... why do these exist?
+ else if (FLAG(tarxform) && 0 <= (l = stridx("rsh", *line)))
+ command->sflags &= ~(SFLAG_R<<l);
+ else if (!(command->sflags>>8) && 0<(l = strtol(line, &line, 10))) {
+ command->sflags |= l << 8;
line--;
} else break;
}
- flags = (FLAG(r) || (command->sflags&8)) ? REG_EXTENDED : 0;
- if (command->sflags&1) flags |= REG_ICASE;
+ flags = (FLAG(r) || (command->sflags & SFLAG_x)) ? REG_EXTENDED : 0;
+ if (command->sflags & SFLAG_i) flags |= REG_ICASE;
// We deferred actually parsing the regex until we had the s///i flag
// allocating the space was done by extend_string() above
if (!*TT.remember) command->arg1 = 0;
- else xregcomp((void *)(command->arg1+(char *)command),TT.remember,flags);
+ else {
+ xregcomp((void *)(command->arg1+(char *)command), TT.remember, flags);
+ if (FLAG(tarxform) && TT.remember[strlen(TT.remember)-1]=='/')
+ command->sflags |= SFLAG_slash;
+ }
free(TT.remember);
TT.remember = 0;
if (*line == 'w') {
@@ -980,11 +1065,17 @@ error:
error_exit("bad pattern '%s'@%ld (%c)", errstart, line-errstart+1L, *line);
}
+// Is the pointer "find" within the string "range".
+static int instr(char *find, char *range)
+{
+ return find>=range && range+strlen(range)>=find;
+}
+
void sed_main(void)
{
- struct arg_list *al;
- char **args = toys.optargs;
+ char **args = toys.optargs, **aa;
+ if (FLAG(tarxform)) toys.optflags |= FLAG_z;
if (!FLAG(z)) TT.delim = '\n';
// Lie to autoconf when it asks stupid questions, so configure regexes
@@ -1006,13 +1097,18 @@ void sed_main(void)
(TT.e = xzalloc(sizeof(struct arg_list)))->arg = *(args++);
}
- // Option parsing infrastructure can't interlace "-e blah -f blah -e blah"
- // so handle all -e, then all -f. (At least the behavior's consistent.)
-
- for (al = TT.e; al; al = al->next) parse_pattern(&al->arg, strlen(al->arg));
+ // -e and -f care about order, so use argv[] to recreate original order
+ for (aa = toys.argv+1; *aa; aa++) {
+ if (TT.e && instr(TT.e->arg, *aa)) {
+ parse_pattern(&TT.e->arg, strlen(TT.e->arg));
+ free(llist_pop(&TT.e));
+ }
+ if (TT.f && instr(TT.f->arg, *aa)) {
+ do_lines(xopenro(TT.f->arg), TT.delim, parse_pattern);
+ free(llist_pop(&TT.f));
+ }
+ }
parse_pattern(0, 0);
- for (al = TT.f; al; al = al->next)
- do_lines(xopenro(al->arg), TT.delim, parse_pattern);
dlist_terminate(TT.pattern);
if (TT.nextlen) error_exit("no }");
@@ -1028,5 +1124,5 @@ void sed_main(void)
sed_line(0, 0);
}
- // todo: need to close fd when done for TOYBOX_FREE?
+ // TODO: need to close fd when done for TOYBOX_FREE?
}
diff --git a/toys/posix/sleep.c b/toys/posix/sleep.c
index 73f03fb4..0eb22702 100644
--- a/toys/posix/sleep.c
+++ b/toys/posix/sleep.c
@@ -11,7 +11,7 @@ config SLEEP
bool "sleep"
default y
help
- usage: sleep DURATION
+ usage: sleep DURATION...
Wait before exiting.
@@ -24,7 +24,10 @@ config SLEEP
void sleep_main(void)
{
struct timespec ts;
+ char **args;
- xparsetimespec(*toys.optargs, &ts);
- toys.exitval = !!nanosleep(&ts, NULL);
+ for (args = toys.optargs; !toys.exitval && *args; args++) {
+ xparsetimespec(*args, &ts);
+ toys.exitval = !!nanosleep(&ts, NULL);
+ }
}
diff --git a/toys/posix/sort.c b/toys/posix/sort.c
index c06b2a5f..c0d312de 100644
--- a/toys/posix/sort.c
+++ b/toys/posix/sort.c
@@ -7,7 +7,7 @@
* Deviations from POSIX: Lots.
* We invented -x
-USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMCcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
config SORT
bool "sort"
@@ -21,7 +21,8 @@ config SORT
-u Unique lines only
-n Numeric order (instead of alphabetical)
-b Ignore leading blanks (or trailing blanks in second part of key)
- -c Check whether input is sorted
+ -C Check whether input is sorted
+ -c Warn if input is unsorted
-d Dictionary order (use alphanumeric and whitespace chars only)
-f Force uppercase (case insensitive sort)
-i Ignore nonprinting characters
@@ -282,9 +283,12 @@ static void sort_lines(char **pline, long len)
*pline = 0;
// handle -c here so we don't allocate more memory than necessary.
- if (FLAG(c)) {
- if (TT.lines && compare_keys((void *)&TT.lines, &line)>-!!FLAG(u))
- error_exit("%s: Check line %u\n", TT.name, TT.linecount);
+ if (FLAG(C)||FLAG(c)) {
+ if (TT.lines && compare_keys((void *)&TT.lines, &line)>-FLAG(u)) {
+ toys.exitval = 1;
+ if (FLAG(C)) xexit();
+ error_exit("%s: Check line %u", TT.name, TT.linecount+1);
+ }
free(TT.lines);
TT.lines = (void *)line;
} else {
@@ -299,7 +303,7 @@ static void sort_lines(char **pline, long len)
static void sort_read(int fd, char *name)
{
TT.name = name;
- do_lines(fd, FLAG(z) ? '\0' : '\n', sort_lines);
+ do_lines(fd, '\n'*!FLAG(z), sort_lines);
}
void sort_main(void)
@@ -339,10 +343,9 @@ void sort_main(void)
flag = 1<<(optlist-temp2+strlen(optlist)-1);
// Was it a flag that can apply to a key?
- if (!temp2 || flag>FLAG_x || (flag&(FLAG_u|FLAG_c|FLAG_s|FLAG_z))) {
- toys.exitval = 2;
+ if (!temp2 || flag>FLAG_x || (flag&(FLAG_u|FLAG_c|FLAG_s|FLAG_z)))
error_exit("Unknown key option.");
- }
+
// b after , means strip _trailing_ space, not leading.
if (idx && flag==FLAG_b) flag = FLAG_bb;
key->flags |= flag;
@@ -362,7 +365,7 @@ void sort_main(void)
// The compare (-c) logic was handled in sort_read(),
// so if we got here, we're done.
- if (FLAG(c)) goto exit_now;
+ if (FLAG(C)||FLAG(c)) goto exit_now;
// Perform the actual sort
qsort(TT.lines, TT.linecount, sizeof(char *), compare_keys);
diff --git a/toys/posix/tail.c b/toys/posix/tail.c
index f34e10cc..514bd5b8 100644
--- a/toys/posix/tail.c
+++ b/toys/posix/tail.c
@@ -36,8 +36,7 @@ GLOBALS(
struct {
char *path;
int fd;
- dev_t dev;
- ino_t ino;
+ struct dev_ino di;
} *F;
)
@@ -159,12 +158,12 @@ static void tail_continue()
continue;
}
- if (fd<0 || sb.st_dev!=TT.F[i].dev || sb.st_ino!=TT.F[i].ino) {
+ if (fd<0 || !same_dev_ino(&sb, &TT.F[i].di)) {
if (fd>=0) close(fd);
if (-1 == (TT.F[i].fd = fd = open(path, O_RDONLY))) continue;
error_msg("following new file: %s\n", path);
- TT.F[i].dev = sb.st_dev;
- TT.F[i].ino = sb.st_ino;
+ TT.F[i].di.dev = sb.st_dev;
+ TT.F[i].di.ino = sb.st_ino;
} else if (sb.st_size <= (pos = lseek(fd, 0, SEEK_CUR))) {
if (pos == sb.st_size) continue;
error_msg("file truncated: %s\n", path);
@@ -201,8 +200,8 @@ static void do_tail(int fd, char *name)
if (FLAG(F)) {
if (fd != -1) {
if (fstat(fd, &sb)) perror_exit("%s", name);
- TT.F[TT.file_no].dev = sb.st_dev;
- TT.F[TT.file_no].ino = sb.st_ino;
+ TT.F[TT.file_no].di.dev = sb.st_dev;
+ TT.F[TT.file_no].di.ino = sb.st_ino;
}
TT.F[TT.file_no].fd = fd;
TT.F[TT.file_no].path = s;
@@ -290,8 +289,8 @@ void tail_main(void)
if (!FLAG(n) && !FLAG(c)) {
char *arg = *args;
- // handle old "-42" style arguments, else default to last 10 lines
- if (arg && *arg == '-' && arg[1]) {
+ // handle old "-42" / "+42" style arguments, else default to last 10 lines
+ if (arg && (*arg == '-' || *arg == '+') && arg[1]) {
TT.n = atolx(*(args++));
toys.optc--;
} else TT.n = -10;
@@ -302,7 +301,7 @@ void tail_main(void)
TT.ss = TT.s ? xparsemillitime(TT.s) : 1000;
loopfiles_rw(args,
- O_RDONLY|WARN_ONLY|LOOPFILES_ANYWAY|(O_CLOEXEC*!(FLAG(f) || FLAG(F))),
+ O_RDONLY|WARN_ONLY|LOOPFILES_ANYWAY|O_CLOEXEC*!(FLAG(f) || FLAG(F)),
0, do_tail);
// Wait for more data when following files
diff --git a/toys/posix/tar.c b/toys/posix/tar.c
index 9cf1a5c9..ea8edbf7 100644
--- a/toys/posix/tar.c
+++ b/toys/posix/tar.c
@@ -14,10 +14,13 @@
*
* Toybox will never implement the "pax" command as a matter of policy.
*
+ * TODO: --wildcard state changes aren't positional.
+ * We always --verbatim-files-from
* Why --exclude pattern but no --include? tar cvzf a.tgz dir --include '*.txt'
- *
+ * No --no-null because the args infrastructure isn't ready.
+ * Until args.c learns about no- toggles, --no-thingy always wins over --thingy
-USE_TAR(NEWTOY(tar, "&(strip-components)#(selinux)(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_TAR(NEWTOY(tar, "&(no-ignore-case)(ignore-case)(no-anchored)(anchored)(no-wildcards)(wildcards)(no-wildcards-match-slash)(wildcards-match-slash)(show-transformed-names)(selinux)(restrict)(full-time)(no-recursion)(null)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(sort);:(mode):(mtime):(group):(owner):(to-command):~(strip-components)(strip)#~(transform)(xform)*o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*I(use-compress-program):C(directory):f(file):as[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
config TAR
bool "tar"
@@ -30,9 +33,10 @@ config TAR
Options:
c Create x Extract t Test (list)
f tar FILE (default -) C Change to DIR first v Verbose display
- o Ignore owner h Follow symlinks m Ignore mtime
J xz compression j bzip2 compression z gzip compression
+ o Ignore owner h Follow symlinks m Ignore mtime
O Extract to stdout X exclude names in FILE T include names in FILE
+ s Sort dirs (--sort)
--exclude FILENAME to exclude --full-time Show seconds with -tv
--mode MODE Adjust permissions --owner NAME[:UID] Set file ownership
@@ -40,32 +44,41 @@ config TAR
--sparse Record sparse files --selinux Save/restore labels
--restrict All under one dir --no-recursion Skip dir contents
--numeric-owner Use numeric uid/gid, not user/group names
+ --null Filenames in -T FILE are null-separated, not newline
--strip-components NUM Ignore first NUM directory components when extracting
+ --xform=SED Modify filenames via SED expression (ala s/find/replace/g)
-I PROG Filter through PROG to compress or PROG -d to decompress
+
+ Filename filter types. Create command line args aren't filtered, extract
+ defaults to --anchored, --exclude defaults to --wildcards-match-slash,
+ use no- prefix to disable:
+
+ --anchored Match name not path --ignore-case Case insensitive
+ --wildcards Expand *?[] like shell --wildcards-match-slash
*/
#define FOR_tar
#include "toys.h"
GLOBALS(
- char *f, *C;
- struct arg_list *T, *X;
- char *I, *to_command, *owner, *group, *mtime, *mode;
+ char *f, *C, *I;
+ struct arg_list *T, *X, *xform;
+ long strip;
+ char *to_command, *owner, *group, *mtime, *mode, *sort;
struct arg_list *exclude;
- long strip_components;
struct double_list *incl, *excl, *seen;
struct string_list *dirs;
- char *cwd;
- int fd, ouid, ggid, hlc, warn, adev, aino, sparselen, pid;
+ char *cwd, **xfsed;
+ int fd, ouid, ggid, hlc, warn, sparselen, pid, xfpipe[2];
+ struct dev_ino archive_di;
long long *sparse;
time_t mtt;
// hardlinks seen so far (hlc many)
struct {
char *arg;
- ino_t ino;
- dev_t dev;
+ struct dev_ino di;
} *hlx;
// Parsed information about a tar header.
@@ -80,8 +93,9 @@ GLOBALS(
} hdr;
)
+// The on-disk 512 byte record structure.
struct tar_hdr {
- char name[100], mode[8], uid[8], gid[8],size[12], mtime[12], chksum[8],
+ char name[100], mode[8], uid[8], gid[8], size[12], mtime[12], chksum[8],
type, link[100], magic[8], uname[32], gname[32], major[8], minor[8],
prefix[155], padd[12];
};
@@ -158,14 +172,51 @@ static void maybe_prefix_block(char *data, int check, int type)
if (len>check) write_prefix_block(data, len+1, type);
}
+static int do_filter(char *pattern, char *name, long long flags)
+{
+ int ign = !!(flags&FLAG_ignore_case), wild = !!(flags&FLAG_wildcards),
+ slash = !!(flags&FLAG_wildcards_match_slash), len;
+
+ if (wild || slash) {
+ // 1) match can end with / 2) maybe case insensitive 2) maybe * matches /
+ if (!fnmatch(pattern, name, FNM_LEADING_DIR+FNM_CASEFOLD*ign+FNM_PATHNAME*slash))
+ return 1;
+ } else {
+ len = strlen(pattern);
+ if (!(ign ? strncasecmp : strncmp)(pattern, name, len))
+ if (!name[len] || name[len]=='/') return 1;
+ }
+
+ return 0;
+}
+
static struct double_list *filter(struct double_list *lst, char *name)
{
struct double_list *end = lst;
+ long long flags = toys.optflags;
+ char *ss;
- if (lst)
- // constant is FNM_LEADING_DIR
- do if (!fnmatch(lst->data, name, 1<<3)) return lst;
- while (end != (lst = lst->next));
+ if (!lst || !*name) return 0;
+
+ // --wildcards-match-slash implies --wildcards because I couldn't figure
+ // out a graceful way to explain why it DIDN'T in the help text. We don't
+ // do the positional enable/disable thing (would need to annotate at list
+ // creation, maybe a TODO item).
+
+ // Set defaults for filter type, and apply --no-flags
+ if (lst == TT.excl) flags |= FLAG_wildcards_match_slash;
+ else flags |= FLAG_anchored;
+ flags &= (~(flags&(FLAG_no_ignore_case|FLAG_no_anchored|FLAG_no_wildcards|FLAG_no_wildcards_match_slash)))>>1;
+ if (flags&FLAG_no_wildcards) flags &= ~FLAG_wildcards_match_slash;
+
+ // The +1 instead of ++ is in case of conseutive slashes
+ do {
+ if (do_filter(lst->data, name, flags)) return lst;
+ if (!(flags & FLAG_anchored)) for (ss = name; *ss; ss++) {
+ if (*ss!='/' || !ss[1]) continue;
+ if (do_filter(lst->data, ss+1, flags)) return lst;
+ }
+ } while (end != (lst = lst->next));
return 0;
}
@@ -187,6 +238,28 @@ static void alloread(void *buf, int len)
(*b)[len] = 0;
}
+static char *xform(char **name, char type)
+{
+ char buf[9], *end;
+ off_t len;
+
+ if (!TT.xform) return 0;
+
+ buf[8] = 0;
+ if (dprintf(TT.xfpipe[0], "%s%c%c", *name, type, 0) != strlen(*name)+2
+ || readall(TT.xfpipe[1], buf, 8) != 8
+ || !(len = estrtol(buf, &end, 16)) || errno ||*end) error_exit("bad xform");
+ xreadall(TT.xfpipe[1], *name = xmalloc(len+1), len);
+ (*name)[len] = 0;
+
+ return *name;
+}
+
+int dirtree_sort(struct dirtree **aa, struct dirtree **bb)
+{
+ return (FLAG(ignore_case) ? strcasecmp : strcmp)(aa[0]->name, bb[0]->name);
+}
+
// callback from dirtree to create archive
static int add_to_tar(struct dirtree *node)
{
@@ -194,44 +267,58 @@ static int add_to_tar(struct dirtree *node)
struct tar_hdr hdr;
struct passwd *pw = pw;
struct group *gr = gr;
- int i, fd = -1, norecurse = FLAG(no_recursion);
- char *name, *lnk, *hname;
+ int i, fd = -1, recurse = 0;
+ char *name, *lnk, *hname, *xfname = 0;
if (!dirtree_notdotdot(node)) return 0;
- if (TT.adev == st->st_dev && TT.aino == st->st_ino) {
+ if (same_dev_ino(st, &TT.archive_di)) {
error_msg("'%s' file is the archive; not dumped", node->name);
return 0;
}
i = 1;
name = hname = dirtree_path(node, &i);
+ if (filter(TT.excl, name)) goto done;
- // exclusion defaults to --no-anchored and --wildcards-match-slash
- for (lnk = name; *lnk;) {
- if (filter(TT.excl, lnk)) {
- norecurse++;
+ if ((FLAG(s)|FLAG(sort)) && !FLAG(no_recursion)) {
+ if (S_ISDIR(st->st_mode) && !node->again) {
+ free(name);
- goto done;
+ return DIRTREE_BREADTH;
+ } else if ((node->again&DIRTREE_BREADTH) && node->child) {
+ struct dirtree *dt, **sort = xmalloc(sizeof(void *)*node->extra);
+
+ for (node->extra = 0, dt = node->child; dt; dt = dt->next)
+ sort[node->extra++] = dt;
+ qsort(sort, node->extra--, sizeof(void *), (void *)dirtree_sort);
+ node->child = *sort;
+ for (i = 0; i<node->extra; i++) sort[i]->next = sort[i+1];
+ sort[i]->next = 0;
+ free(sort);
+
+ // fall through to add directory
}
- while (*lnk && *lnk!='/') lnk++;
- while (*lnk=='/') lnk++;
}
// Consume the 1 extra byte alocated in dirtree_path()
- if (S_ISDIR(st->st_mode) && name[i-1] != '/') strcat(name, "/");
+ if (S_ISDIR(st->st_mode) && (lnk = name+strlen(name))[-1] != '/')
+ strcpy(lnk, "/");
// remove leading / and any .. entries from saved name
- if (!FLAG(P)) while (*hname == '/') hname++;
- for (lnk = hname;;) {
- if (!(lnk = strstr(lnk, ".."))) break;
- if (lnk == hname || lnk[-1] == '/') {
- if (!lnk[2]) goto done;
- if (lnk[2]=='/') {
- lnk = hname = lnk+3;
- continue;
+ if (!FLAG(P)) {
+ while (*hname == '/') hname++;
+ for (lnk = hname;;) {
+ if (!(lnk = strstr(lnk, ".."))) break;
+ if (lnk == hname || lnk[-1] == '/') {
+ if (!lnk[2]) goto done;
+ if (lnk[2]=='/') {
+ lnk = hname = lnk+3;
+ continue;
+ }
}
+ lnk += 2;
}
- lnk += 2;
+ if (!*hname) hname = "./";
}
if (!*hname) goto done;
@@ -241,13 +328,12 @@ static int add_to_tar(struct dirtree *node)
TT.warn = 0;
}
+ // Override dentry data from command line and fill out header data
if (TT.owner) st->st_uid = TT.ouid;
if (TT.group) st->st_gid = TT.ggid;
if (TT.mode) st->st_mode = string_to_mode(TT.mode, st->st_mode);
if (TT.mtime) st->st_mtime = TT.mtt;
-
memset(&hdr, 0, sizeof(hdr));
- strncpy(hdr.name, hname, sizeof(hdr.name));
ITOO(hdr.mode, st->st_mode &07777);
ITOO(hdr.uid, st->st_uid);
ITOO(hdr.gid, st->st_gid);
@@ -255,40 +341,39 @@ static int add_to_tar(struct dirtree *node)
ITOO(hdr.mtime, st->st_mtime);
strcpy(hdr.magic, "ustar ");
- // Hard link or symlink? i=0 neither, i=1 hardlink, i=2 symlink
-
// Are there hardlinks to a non-directory entry?
+ lnk = 0;
if (st->st_nlink>1 && !S_ISDIR(st->st_mode)) {
// Have we seen this dev&ino before?
- for (i = 0; i<TT.hlc; i++) {
- if (st->st_ino == TT.hlx[i].ino && st->st_dev == TT.hlx[i].dev)
- break;
- }
- if (i != TT.hlc) {
- lnk = TT.hlx[i].arg;
- i = 1;
- } else {
+ for (i = 0; i<TT.hlc; i++) if (same_dev_ino(st, &TT.hlx[i].di)) break;
+ if (i != TT.hlc) lnk = TT.hlx[i].arg;
+ else {
// first time we've seen it. Store as normal file, but remember it.
if (!(TT.hlc&255))
TT.hlx = xrealloc(TT.hlx, sizeof(*TT.hlx)*(TT.hlc+256));
TT.hlx[TT.hlc].arg = xstrdup(hname);
- TT.hlx[TT.hlc].ino = st->st_ino;
- TT.hlx[TT.hlc].dev = st->st_dev;
+ TT.hlx[TT.hlc].di.ino = st->st_ino;
+ TT.hlx[TT.hlc].di.dev = st->st_dev;
TT.hlc++;
- i = 0;
}
- } else i = 0;
+ }
- // Handle file types
- if (i || S_ISLNK(st->st_mode)) {
- hdr.type = '1'+!i;
- if (!i && !(lnk = xreadlink(name))) {
+ xfname = xform(&hname, 'r');
+ strncpy(hdr.name, hname, sizeof(hdr.name));
+
+ // Handle file types: 0=reg, 1=hardlink, 2=sym, 3=chr, 4=blk, 5=dir, 6=fifo
+ if (lnk || S_ISLNK(st->st_mode)) {
+ hdr.type = '1'+!lnk;
+ if (lnk) {
+ if (!xform(&lnk, 'h')) lnk = xstrdup(lnk);
+ } else if (!(lnk = xreadlink(name))) {
perror_msg("readlink");
goto done;
- }
+ } else xform(&lnk, 's');
+
maybe_prefix_block(lnk, sizeof(hdr.link), 'K');
strncpy(hdr.link, lnk, sizeof(hdr.link));
- if (!i) free(lnk);
+ free(lnk);
} else if (S_ISREG(st->st_mode)) {
hdr.type = '0';
ITOO(hdr.size, st->st_size);
@@ -348,6 +433,7 @@ static int add_to_tar(struct dirtree *node)
// Before we write the header, make sure we can read the file
if ((fd = open(name, O_RDONLY)) < 0) {
perror_msg("can't open '%s'", name);
+ free(name);
return 0;
}
@@ -420,10 +506,13 @@ static int add_to_tar(struct dirtree *node)
if (st->st_size%512) writeall(TT.fd, toybuf, (512-(st->st_size%512)));
close(fd);
}
+ recurse = !FLAG(no_recursion);
+
done:
+ free(xfname);
free(name);
- return (DIRTREE_RECURSE|(FLAG(h)?DIRTREE_SYMFOLLOW:0))*!norecurse;
+ return recurse*(DIRTREE_RECURSE|DIRTREE_SYMFOLLOW*FLAG(h));
}
static void wsettime(char *s, long long sec)
@@ -518,18 +607,9 @@ error:
close(fd);
}
-static void extract_to_disk(void)
+static void extract_to_disk(char *name)
{
- char *name = TT.hdr.name;
- int ala = TT.hdr.mode, strip;
-
- for (strip = 0; strip < TT.strip_components; strip++) {
- char *s = strchr(name, '/');
-
- if (s && s[1]) name = s+1;
- else if (S_ISDIR(ala)) return;
- else break;
- }
+ int ala = TT.hdr.mode;
if (dirflush(name, S_ISDIR(ala))) {
if (S_ISREG(ala) && !TT.hdr.link_target) skippy(TT.hdr.size);
@@ -573,7 +653,7 @@ static void extract_to_disk(void)
if (TT.owner) TT.hdr.uid = TT.ouid;
else if (!FLAG(numeric_owner) && *TT.hdr.uname) {
struct passwd *pw = bufgetpwnamuid(TT.hdr.uname, 0);
- if (pw && (TT.owner || !FLAG(numeric_owner))) TT.hdr.uid = pw->pw_uid;
+ if (pw) TT.hdr.uid = pw->pw_uid;
}
if (TT.group) TT.hdr.gid = TT.ggid;
@@ -610,7 +690,7 @@ static void unpack_tar(char *first)
struct tar_hdr tar;
int i, sefd = -1, and = 0;
unsigned maj, min;
- char *s;
+ char *s, *name;
for (;;) {
if (first) {
@@ -776,9 +856,31 @@ static void unpack_tar(char *first)
}
}
- // Skip excluded files
- if (filter(TT.excl, TT.hdr.name) || (TT.incl && !delete))
+ // Skip excluded files, filtering on the untransformed name.
+ if (filter(TT.excl, name = TT.hdr.name) || (TT.incl && !delete)) {
skippy(TT.hdr.size);
+ goto done;
+ }
+
+ // We accept --show-transformed but always do, so it's a NOP.
+ name = TT.hdr.name;
+ if (xform(&name, 'r')) {
+ free(TT.hdr.name);
+ TT.hdr.name = name;
+ }
+ if ((i = "\0hs"[stridx("12", tar.type)+1])) xform(&TT.hdr.link_target, i);
+
+ for (i = 0; i<TT.strip; i++) {
+ char *s = strchr(name, '/');
+
+ if (s && s[1]) name = s+1;
+ else {
+ if (S_ISDIR(TT.hdr.mode)) *name = 0;
+ break;
+ }
+ }
+
+ if (!*name) skippy(TT.hdr.size);
else if (FLAG(t)) {
if (FLAG(v)) {
struct tm *lc = localtime(TT.mtime ? &TT.mtt : &TT.hdr.mtime);
@@ -796,12 +898,13 @@ static void unpack_tar(char *first)
printf(" %d-%02d-%02d %02d:%02d%s ", 1900+lc->tm_year, 1+lc->tm_mon,
lc->tm_mday, lc->tm_hour, lc->tm_min, FLAG(full_time) ? perm : "");
}
- printf("%s", TT.hdr.name);
- if (TT.hdr.link_target) printf(" -> %s", TT.hdr.link_target);
+ printf("%s", name);
+ if (TT.hdr.link_target)
+ printf(" %s %s", tar.type=='2' ? "->" : "link to", TT.hdr.link_target);
xputc('\n');
skippy(TT.hdr.size);
} else {
- if (FLAG(v)) printf("%s\n", TT.hdr.name);
+ if (FLAG(v)) printf("%s\n", name);
if (FLAG(O)) sendfile_sparse(1);
else if (FLAG(to_command)) {
if (S_ISREG(TT.hdr.mode)) {
@@ -810,7 +913,7 @@ static void unpack_tar(char *first)
xsetenv("TAR_FILETYPE", "f");
xsetenv(xmprintf("TAR_MODE=%o", TT.hdr.mode), 0);
xsetenv(xmprintf("TAR_SIZE=%lld", TT.hdr.ssize), 0);
- xsetenv("TAR_FILENAME", TT.hdr.name);
+ xsetenv("TAR_FILENAME", name);
xsetenv("TAR_UNAME", TT.hdr.uname);
xsetenv("TAR_GNAME", TT.hdr.gname);
xsetenv(xmprintf("TAR_MTIME=%llo", (long long)TT.hdr.mtime), 0);
@@ -818,14 +921,15 @@ static void unpack_tar(char *first)
xsetenv(xmprintf("TAR_GID=%o", TT.hdr.gid), 0);
pid = xpopen((char *[]){"sh", "-c", TT.to_command, NULL}, &fd, 0);
- // todo: short write exits tar here, other skips data.
+ // TODO: short write exits tar here, other skips data.
sendfile_sparse(fd);
fd = xpclose_both(pid, 0);
if (fd) error_msg("%d: Child returned %d", pid, fd);
}
- } else extract_to_disk();
+ } else extract_to_disk(name);
}
+done:
if (sefd != -1) {
// zero length write resets fscreate context to default
(void)write(sefd, 0, 0);
@@ -848,7 +952,7 @@ static void trim2list(void *list, char *pline)
dlist_add(list, n);
if (i && n[i-1]=='\n') i--;
- while (i && n[i-1] == '/') i--;
+ while (i>1 && n[i-1] == '/') i--;
n[i] = 0;
}
@@ -858,11 +962,15 @@ static void do_XT(char **pline, long len)
if (pline) trim2list(TT.X ? &TT.excl : &TT.incl, *pline);
}
+static char *get_archiver()
+{
+ return TT.I ? : FLAG(z) ? "gzip" : FLAG(j) ? "bzip2" : "xz";
+}
+
void tar_main(void)
{
- char *s, **args = toys.optargs,
- *archiver = FLAG(I) ? TT.I : (FLAG(z) ? "gzip" : (FLAG(J) ? "xz":"bzip2"));
- int len = 0;
+ char *s, **xfsed, **args = toys.optargs;
+ int len = 0, ii;
// Needed when extracting to command
signal(SIGPIPE, SIG_IGN);
@@ -885,12 +993,16 @@ void tar_main(void)
}
if (TT.mtime) xparsedate(TT.mtime, &TT.mtt, (void *)&s, 1);
+ // TODO: collect filter types here and annotate saved include/exclude?
+
// Collect file list.
for (; TT.exclude; TT.exclude = TT.exclude->next)
trim2list(&TT.excl, TT.exclude->arg);
for (;TT.X; TT.X = TT.X->next) do_lines(xopenro(TT.X->arg), '\n', do_XT);
for (args = toys.optargs; *args; args++) trim2list(&TT.incl, *args);
- for (;TT.T; TT.T = TT.T->next) do_lines(xopenro(TT.T->arg), '\n', do_XT);
+ // -T is always --verbatim-files-from: no quote removal or -arg handling
+ for (;TT.T; TT.T = TT.T->next)
+ do_lines(xopenro(TT.T->arg), '\n'*!FLAG(null), do_XT);
// If include file list empty, don't create empty archive
if (FLAG(c)) {
@@ -898,6 +1010,23 @@ void tar_main(void)
TT.fd = 1;
}
+ if (TT.xform) {
+ struct arg_list *al;
+
+ for (ii = 0, al = TT.xform; al; al = al->next) ii++;
+ xfsed = xmalloc((ii+2)*2*sizeof(char *));
+ xfsed[0] = "sed";
+ xfsed[1] = "--tarxform";
+ for (ii = 2, al = TT.xform; al; al = al->next) {
+ xfsed[ii++] = "-e";
+ xfsed[ii++] = al->arg;
+ }
+ xfsed[ii] = 0;
+ TT.xfpipe[0] = TT.xfpipe[1] = -1;
+ xpopen_both(xfsed, TT.xfpipe);
+ free(xfsed);
+ }
+
// nommu reentry for nonseekable input skips this, parent did it for us
if (toys.stacktop) {
if (TT.f && strcmp(TT.f, "-"))
@@ -915,8 +1044,8 @@ void tar_main(void)
struct stat st;
if (!fstat(TT.fd, &st)) {
- TT.aino = st.st_ino;
- TT.adev = st.st_dev;
+ TT.archive_di.ino = st.st_ino;
+ TT.archive_di.dev = st.st_dev;
}
}
@@ -930,7 +1059,7 @@ void tar_main(void)
if (len!=512 || !is_tar_header(hdr)) {
// detect gzip and bzip signatures
if (SWAP_BE16(*(short *)hdr)==0x1f8b) toys.optflags |= FLAG_z;
- else if (!memcmp(hdr, "BZh", 3)) toys.optflags |= FLAG_j;
+ else if (!smemcmp(hdr, "BZh", 3)) toys.optflags |= FLAG_j;
else if (peek_be(hdr, 7) == 0xfd377a585a0000UL) toys.optflags |= FLAG_J;
else error_exit("Not tar");
@@ -942,11 +1071,11 @@ void tar_main(void)
if (FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J)) {
int pipefd[2] = {hdr ? -1 : TT.fd, -1}, i, pid;
struct string_list *zcat = FLAG(I) ? 0 : find_in_path(getenv("PATH"),
- FLAG(j) ? "bzcat" : FLAG(J) ? "xzcat" : "zcat");
+ FLAG(z) ? "zcat" : FLAG(j) ? "bzcat" : "xzcat");
// Toybox provides more decompressors than compressors, so try them first
TT.pid = xpopen_both(zcat ? (char *[]){zcat->str, 0} :
- (char *[]){archiver, "-d", 0}, pipefd);
+ (char *[]){get_archiver(), "-d", 0}, pipefd);
if (CFG_TOYBOX_FREE) llist_traverse(zcat, free);
if (!hdr) {
@@ -987,7 +1116,7 @@ void tar_main(void)
unpack_tar(hdr);
dirflush(0, 0);
// Shut up archiver about inability to write all trailing NULs to pipe buf
- if (TT.pid>0) kill(TT.pid, 9);
+ while (0<read(TT.fd, toybuf, sizeof(toybuf)));
// Each time a TT.incl entry is seen it's moved to the end of the list,
// with TT.seen pointing to first seen list entry. Anything between
@@ -1018,16 +1147,23 @@ void tar_main(void)
if (FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J)) {
int pipefd[2] = {-1, TT.fd};
- xpopen_both((char *[]){archiver, 0}, pipefd);
+ TT.pid = xpopen_both((char *[]){get_archiver(), 0}, pipefd);
close(TT.fd);
TT.fd = pipefd[0];
}
do {
TT.warn = 1;
- dirtree_flagread(dl->data, FLAG(h) ? DIRTREE_SYMFOLLOW : 0, add_to_tar);
+ dirtree_flagread(dl->data,
+ DIRTREE_SYMFOLLOW*FLAG(h)|DIRTREE_BREADTH*(FLAG(sort)|FLAG(s)),
+ add_to_tar);
} while (TT.incl != (dl = dl->next));
writeall(TT.fd, toybuf, 1024);
+ close(TT.fd);
+ }
+ if (TT.pid) {
+ TT.pid = xpclose_both(TT.pid, 0);
+ if (TT.pid) toys.exitval = TT.pid;
}
if (toys.exitval) error_msg("had errors");
diff --git a/toys/posix/test.c b/toys/posix/test.c
index 3fadf3ad..cdb13e2a 100644
--- a/toys/posix/test.c
+++ b/toys/posix/test.c
@@ -4,10 +4,11 @@
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
*
- * TODO sh [[ ]] options: < aaa<bbb > bbb>aaa ~= regex
+ * Deviations from posix: -k, [[ < > =~ ]]
USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY([[, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
config TEST
bool "test"
@@ -15,23 +16,25 @@ config TEST
help
usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
- Return true or false by performing tests. (With no arguments return false.)
+ Return true or false by performing tests. No arguments is false, one argument
+ is true if not empty string.
--- Tests with a single argument (after the option):
PATH is/has:
-b block device -f regular file -p fifo -u setuid bit
- -c char device -g setgid -r read bit -w write bit
- -d directory -h symlink -S socket -x execute bit
+ -c char device -g setgid -r readable -w writable
+ -d directory -h symlink -S socket -x executable
-e exists -L symlink -s nonzero size -k sticky bit
STRING is:
- -n nonzero size -z zero size (STRING by itself implies -n)
+ -n nonzero size -z zero size
FD (integer file descriptor) is:
- -t a TTY
+ -t a TTY -T open
--- Tests with one argument on each side of an operator:
Two strings:
- = are identical != differ
-
+ = are identical != differ =~ string matches regex
+ Alphabetical sort:
+ < first is lower > first higher
Two integers:
-eq equal -gt first > second -lt first < second
-ne not equal -ge first >= second -le first <= second
@@ -57,8 +60,24 @@ static int do_test(char **args, int *count)
if (*count>=3) {
*count = 3;
char *s = args[1], *ss = "eqnegtgeltle";
+ // TODO shell integration case insensitivity
if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
+ if (!strcmp(s, "=~")) {
+ regex_t reg;
+
+ // TODO: regex needs integrated quoting support with the shell.
+ // Ala [[ abc =~ "1"* ]] matches but [[ abc =~ 1"*" ]] does not
+ xregcomp(&reg, args[2], REG_NOSUB); // REG_EXTENDED? REG_ICASE?
+ i = regexec(&reg, args[0], 0, 0, 0);
+ regfree(&reg);
+
+ return !i;
+ }
+ if ((*s=='<' || *s=='>') && !s[1]) {
+ i = strcmp(args[0], args[2]);
+ return (*s=='<') ? i<0 : i>0;
+ }
if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
long long a = atolx(args[0]), b = atolx(args[2]);
@@ -74,12 +93,13 @@ static int do_test(char **args, int *count)
if (*count>=2 && *s == '-' && s[1] && !s[2]) {
*count = 2;
c = s[1];
+ if (c=='a') c = 'e';
if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
struct stat st;
- // stat or lstat, then handle rwx and s
+ if (i>=13) return !access(args[1], 1<<(i-13));
+ // stat or lstat, check s
if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
- if (i>=13) return !!(st.st_mode&(0111<<(i-13)));
if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
// handle file type checking and SUID/SGID
@@ -89,6 +109,7 @@ static int do_test(char **args, int *count)
} else if (c == 'z') return !*args[1];
else if (c == 'n') return *args[1];
else if (c == 't') return isatty(atolx(args[1]));
+ else if (c == 'T') return -1 != fcntl(atolx(args[1]), F_GETFL);
}
return *count = 0;
}
@@ -98,13 +119,14 @@ static int do_test(char **args, int *count)
#define OR 4 // test before -o succeeded since ( so force true
void test_main(void)
{
- char *s;
+ char *s = (void *)1;
int pos, paren, pstack, result = 0;
toys.exitval = 2;
- if (CFG_TOYBOX && !strcmp("[", toys.which->name))
- if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
- error_exit("Missing ']'");
+ if (CFG_TOYBOX && *toys.which->name=='[') {
+ if (toys.optc) for (s = toys.optargs[--toys.optc]; *s==']'; s++);
+ if (*s) error_exit("Missing ']'");
+ }
// loop through command line arguments
if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
diff --git a/toys/posix/time.c b/toys/posix/time.c
index 1becad1b..078aca58 100644
--- a/toys/posix/time.c
+++ b/toys/posix/time.c
@@ -31,7 +31,7 @@ void time_main(void)
long long sec[3];
int stat, ii, idx, nano[3];
pid_t pid;
- char *labels[] = {"\nreal"+!!FLAG(p), "user", "sys"}, **label = labels,
+ char *labels[] = {"\nreal"+FLAG(p), "user", "sys"}, **label = labels,
*vlabels[] ={"Real", "User", "System"}, tab = toys.optflags ? ' ' : '\t';
if (FLAG(v)) label = vlabels;
diff --git a/toys/posix/touch.c b/toys/posix/touch.c
index 5e33d7f7..e687ff81 100644
--- a/toys/posix/touch.c
+++ b/toys/posix/touch.c
@@ -71,7 +71,6 @@ void touch_main(void)
if (!strcmp(s, "-")) {
if (!futimens(1, ts)) continue;
} else {
- // cheat: FLAG_h is rightmost flag, so its value is 1
if (!utimensat(AT_FDCWD, s, ts, FLAG(h)*AT_SYMLINK_NOFOLLOW)) continue;
if (FLAG(c)) continue;
if (access(s, F_OK) && (-1!=(fd = open(s, O_CREAT, 0666)))) {
diff --git a/toys/posix/ulimit.c b/toys/posix/ulimit.c
index 37970983..642f8871 100644
--- a/toys/posix/ulimit.c
+++ b/toys/posix/ulimit.c
@@ -22,7 +22,6 @@ USE_ULIMIT(OLDTOY(prlimit, ulimit, TOYFLAG_USR|TOYFLAG_BIN))
config ULIMIT
bool "ulimit"
default y
- depends on TOYBOX_PRLIMIT
help
usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]
diff --git a/toys/posix/uname.c b/toys/posix/uname.c
index fe44cedd..d11e1e5c 100644
--- a/toys/posix/uname.c
+++ b/toys/posix/uname.c
@@ -4,7 +4,7 @@
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/uname.html
-USE_UNAME(NEWTOY(uname, "aomvrns", TOYFLAG_BIN))
+USE_UNAME(NEWTOY(uname, "paomvrns", TOYFLAG_BIN))
USE_ARCH(NEWTOY(arch, 0, TOYFLAG_USR|TOYFLAG_BIN))
USE_LINUX32(NEWTOY(linux32, 0, TOYFLAG_USR|TOYFLAG_BIN))
@@ -60,6 +60,7 @@ void uname_main(void)
}
xputsn(c);
}
+ if (FLAG(p)) xputsn(" unknown"+!needspace);
xputc('\n');
}
diff --git a/toys/posix/uuencode.c b/toys/posix/uuencode.c
index 160954d8..3bede303 100644
--- a/toys/posix/uuencode.c
+++ b/toys/posix/uuencode.c
@@ -24,19 +24,19 @@ void uuencode_main(void)
{
char *name = toys.optargs[toys.optc-1], buf[(76/4)*3];
- int i, m = FLAG(m), fd = 0;
+ int i, fd = 0;
if (toys.optc > 1) fd = xopenro(toys.optargs[0]);
base64_init(toybuf);
- xprintf("begin%s 744 %s\n", m ? "-base64" : "", name);
+ xprintf("begin%s 744 %s\n", FLAG(m) ? "-base64" : "", name);
for (;;) {
char *in;
- if (!(i = xread(fd, buf, m ? sizeof(buf) : 45))) break;
+ if (!(i = xread(fd, buf, FLAG(m) ? sizeof(buf) : 45))) break;
- if (!m) xputc(i+32);
+ if (!FLAG(m)) xputc(i+32);
in = buf;
for (in = buf; in-buf < i; ) {
@@ -49,10 +49,11 @@ void uuencode_main(void)
if (j < bytes) x |= (*(in++) & 0x0ff) << (8*(2-j));
out = (x>>((3-j)*6)) & 0x3f;
- xputc(m ? (j > bytes ? '=' : toybuf[out]) : (out ? out + 0x20 : 0x60));
+ xputc(FLAG(m) ? (j > bytes ? '=' : toybuf[out])
+ : (out ? out + 0x20 : 0x60));
}
}
xputc('\n');
}
- xputs(m ? "====" : "end");
+ xputs(FLAG(m) ? "====" : "end");
}
diff --git a/toys/posix/who.c b/toys/posix/who.c
index 2515af38..24353852 100644
--- a/toys/posix/who.c
+++ b/toys/posix/who.c
@@ -16,7 +16,6 @@ USE_WHO(NEWTOY(who, "a", TOYFLAG_USR|TOYFLAG_BIN))
config WHO
bool "who"
default y
- depends on TOYBOX_UTMPX
help
usage: who
diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c
index cc3ee3de..67fa3a05 100644
--- a/toys/posix/xargs.c
+++ b/toys/posix/xargs.c
@@ -9,7 +9,7 @@
* TODO: -L Max number of lines of input per command
* TODO: -x Exit if can't fit everything in one command
-USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0(null)=1optr(no-run-if-empty)n#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
config XARGS
bool "xargs"
@@ -184,7 +184,7 @@ void xargs_main(void)
if (!(pid = XVFORK())) {
close(0);
- xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY);
+ xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY|O_CLOEXEC);
xexec(out);
}
TT.np++;