diff options
Diffstat (limited to 'toys/posix')
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(®, args[2], REG_NOSUB); // REG_EXTENDED? REG_ICASE? + i = regexec(®, args[0], 0, 0, 0); + regfree(®); + + 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++; |