diff options
author | Elliott Hughes <enh@google.com> | 2015-12-11 15:09:45 -0800 |
---|---|---|
committer | Elliott Hughes <enh@google.com> | 2015-12-11 15:09:45 -0800 |
commit | 0188665ed89e5db3feb0d0dca29c6b6105d79d2d (patch) | |
tree | d7faef1b467bea9ca1eed0479d14a4a450709c81 | |
parent | 8755cdc71e66e675251846554fb5013d7485dd5a (diff) | |
parent | aaecbbac2f94b7a93eb2df7f9db78828cbb7b647 (diff) | |
download | toybox-0188665ed89e5db3feb0d0dca29c6b6105d79d2d.tar.gz |
Merge remote-tracking branch 'toybox/master' into HEAD
-rw-r--r-- | README | 5 | ||||
-rw-r--r-- | lib/args.c | 10 | ||||
-rw-r--r-- | lib/dirtree.c | 21 | ||||
-rw-r--r-- | lib/lib.c | 14 | ||||
-rw-r--r-- | lib/lib.h | 8 | ||||
-rw-r--r-- | scripts/mkflags.c | 22 | ||||
-rwxr-xr-x | tests/blkid.test | 4 | ||||
-rwxr-xr-x | tests/find.test | 8 | ||||
-rwxr-xr-x | tests/sed.test | 7 | ||||
-rw-r--r-- | toys.h | 2 | ||||
-rw-r--r-- | toys/example/test_many_options.c | 22 | ||||
-rw-r--r-- | toys/other/blkid.c | 2 | ||||
-rw-r--r-- | toys/other/free.c | 23 | ||||
-rw-r--r-- | toys/posix/cpio.c | 57 | ||||
-rw-r--r-- | toys/posix/find.c | 32 | ||||
-rw-r--r-- | toys/posix/ls.c | 3 | ||||
-rw-r--r-- | toys/posix/ps.c | 777 | ||||
-rw-r--r-- | toys/posix/tail.c | 2 |
18 files changed, 670 insertions, 349 deletions
@@ -130,3 +130,8 @@ requirements. video: http://elinux.org/ELC_2015_Presentations outline: http://landley.net/talks/celf-2015.txt + +--- Code of conduct + +We're using twitter's https://engineering.twitter.com/opensource/code-of-conduct +except email rob@landley.net with complaints. @@ -97,8 +97,8 @@ struct opts { struct opts *next; long *arg; // Pointer into union "this" to store arguments at. int c; // Argument character to match - int flags; // |=1, ^=2 - unsigned dex[3]; // which bits to disable/enable/exclude in toys.optflags + int flags; // |=1, ^=2, " "=4, ;=8 + unsigned long long dex[3]; // bits to disable/enable/exclude in toys.optflags char type; // Type of arguments to store union "this" union { long l; @@ -142,7 +142,7 @@ static int gotflag(struct getoptflagstate *gof, struct opts *opt) // Might enabling this switch off something else? if (toys.optflags & opt->dex[0]) { struct opts *clr; - unsigned i = 1; + unsigned long long i = 1; // Forget saved argument for flag we switch back off for (clr=gof->opts, i=1; clr; clr = clr->next, i<<=1) @@ -326,7 +326,7 @@ void parse_optflaglist(struct getoptflagstate *gof) // (This goes right to left so we need the whole list before we can start.) idx = 0; for (new = gof->opts; new; new = new->next) { - unsigned u = 1<<idx++; + unsigned long long u = 1L<<idx++; if (new->c == 1) new->c = 0; new->dex[1] = u; @@ -378,7 +378,7 @@ void get_optflags(void) { struct getoptflagstate gof; struct opts *catch; - long saveflags; + unsigned long long saveflags; char *letters[]={"s",""}; // Option parsing is a two stage process: parse the option string into diff --git a/lib/dirtree.c b/lib/dirtree.c index 1e898161..8b9f2993 100644 --- a/lib/dirtree.c +++ b/lib/dirtree.c @@ -32,7 +32,7 @@ struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, int flags) if (name) { // open code this because haven't got node to call dirtree_parentfd() on yet - int fd = parent ? parent->data : AT_FDCWD; + int fd = parent ? parent->dirfd : AT_FDCWD; if (fstatat(fd, name, &st, AT_SYMLINK_NOFOLLOW*!(flags&DIRTREE_SYMFOLLOW))) goto error; @@ -48,10 +48,7 @@ struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, int flags) memcpy(&(dt->st), &st, sizeof(struct stat)); strcpy(dt->name, name); - if (linklen) { - dt->symlink = memcpy(len+(char *)dt, libbuf, linklen); - dt->data = --linklen; - } + if (linklen) dt->symlink = memcpy(len+(char *)dt, libbuf, linklen); } return dt; @@ -96,7 +93,7 @@ char *dirtree_path(struct dirtree *node, int *plen) int dirtree_parentfd(struct dirtree *node) { - return node->parent ? node->parent->data : AT_FDCWD; + return node->parent ? node->parent->dirfd : AT_FDCWD; } // Handle callback for a node in the tree. Returns saved node(s) or NULL. @@ -118,7 +115,7 @@ struct dirtree *dirtree_handle_callback(struct dirtree *new, if (S_ISDIR(new->st.st_mode)) { if (flags & (DIRTREE_RECURSE|DIRTREE_COMEAGAIN)) { - new->data = openat(dirtree_parentfd(new), new->name, O_CLOEXEC); + new->dirfd = openat(dirtree_parentfd(new), new->name, O_CLOEXEC); flags = dirtree_recurse(new, callback, flags); } } @@ -132,8 +129,8 @@ struct dirtree *dirtree_handle_callback(struct dirtree *new, return (flags & DIRTREE_ABORT)==DIRTREE_ABORT ? DIRTREE_ABORTVAL : new; } -// Recursively read/process children of directory node (with dirfd in data), -// filtering through callback(). +// Recursively read/process children of directory node, filtering through +// callback(). Uses and closes supplied ->dirfd. int dirtree_recurse(struct dirtree *node, int (*callback)(struct dirtree *node), int flags) @@ -142,13 +139,13 @@ int dirtree_recurse(struct dirtree *node, struct dirent *entry; DIR *dir; - if (node->data == -1 || !(dir = fdopendir(node->data))) { + if (node->dirfd == -1 || !(dir = fdopendir(node->dirfd))) { if (!(flags & DIRTREE_SHUTUP)) { char *path = dirtree_path(node, 0); perror_msg("No %s", path); free(path); } - close(node->data); + close(node->dirfd); return flags; } @@ -174,7 +171,7 @@ int dirtree_recurse(struct dirtree *node, // This closes filehandle as well, so note it closedir(dir); - node->data = -1; + node->dirfd = -1; return flags; } @@ -411,14 +411,16 @@ off_t fdlength(int fd) } // Read contents of file as a single nul-terminated string. -// malloc new one if buf=len=0 -char *readfileat(int dirfd, char *name, char *ibuf, off_t len) +// measure file size if !len, allocate buffer if !buf +// note: for existing buffers use len = size-1, will set buf[len] = 0 +char *readfileat(int dirfd, char *name, char *ibuf, off_t *plen) { + off_t len = *plen-!!ibuf; int fd; char *buf; if (-1 == (fd = openat(dirfd, name, O_RDONLY))) return 0; - if (len<1) { + if (!len) { len = fdlength(fd); // proc files don't report a length, so try 1 page minimum. if (len<4096) len = 4096; @@ -426,11 +428,11 @@ char *readfileat(int dirfd, char *name, char *ibuf, off_t len) if (!ibuf) buf = xmalloc(len+1); else buf = ibuf; - len = readall(fd, buf, len-1); + *plen = len = readall(fd, buf, len); close(fd); if (len<0) { if (ibuf != buf) free(buf); - buf = 0; + buf = 0; } else buf[len] = 0; return buf; @@ -438,7 +440,7 @@ char *readfileat(int dirfd, char *name, char *ibuf, off_t len) char *readfile(char *name, char *ibuf, off_t len) { - return readfileat(AT_FDCWD, name, ibuf, len); + return readfileat(AT_FDCWD, name, ibuf, &len); } // Sleep for this many thousandths of a second @@ -67,7 +67,7 @@ struct dirtree { long extra; // place for user to store their stuff (can be pointer) struct stat st; char *symlink; - int data; // dirfd for directory, linklen for symlink + int dirfd; char again; char name[]; }; @@ -154,7 +154,7 @@ ssize_t writeall(int fd, void *buf, size_t len); off_t lskip(int fd, off_t offset); int mkpathat(int atfd, char *dir, mode_t lastmode, int flags); struct string_list **splitpath(char *path, struct string_list **list); -char *readfileat(int dirfd, char *name, char *buf, off_t len); +char *readfileat(int dirfd, char *name, char *buf, off_t *len); char *readfile(char *name, char *buf, off_t len); void msleep(long miliseconds); int64_t peek_le(void *ptr, unsigned size); @@ -256,14 +256,14 @@ void names_to_pid(char **names, int (*callback)(pid_t pid, char *name)); pid_t xvforkwrap(pid_t pid); #define XVFORK() xvforkwrap(vfork()) -#define WOULD_EXIT(y, x) { jmp_buf _noexit; \ +#define WOULD_EXIT(y, x) do { jmp_buf _noexit; \ int _noexit_res; \ toys.rebound = &_noexit; \ _noexit_res = setjmp(_noexit); \ if (!_noexit_res) do {x;} while(0); \ toys.rebound = 0; \ y = _noexit_res; \ -} +} while(0); #define NOEXIT(x) WOULD_EXIT(_noexit_res, x) diff --git a/scripts/mkflags.c b/scripts/mkflags.c index b57f0577..e7ed684a 100644 --- a/scripts/mkflags.c +++ b/scripts/mkflags.c @@ -116,8 +116,8 @@ int main(int argc, char *argv[]) // See "intentionally crappy", above. if (!(out = outbuf)) return 1; - printf("#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1\n" - "#else\n#define FORCED_FLAG 0\n#endif\n\n"); + printf("#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1\n#define FORCED_FLAGLL 1LL\n" + "#else\n#define FORCED_FLAG 0\n#define FORCED_FLAGLL 0\n#endif\n\n"); for (;;) { struct flag *flist, *aflist, *offlist; @@ -173,27 +173,33 @@ int main(int argc, char *argv[]) out += strlen(out); while (aflist) { + char *llstr = bit>31 ? "LL" : ""; + + // Output flag macro for bare longopts if (aflist->lopt) { if (flist && flist->lopt && !strcmp(flist->lopt->command, aflist->lopt->command)) { - sprintf(out, "#define FLAG_%s (1<<%d)\n", flist->lopt->command, bit); + sprintf(out, "#define FLAG_%s (1%s<<%d)\n", flist->lopt->command, + llstr, bit); flist->lopt = flist->lopt->next; - } else sprintf(out, "#define FLAG_%s (FORCED_FLAG<<%d)\n", - aflist->lopt->command, bit); + } else sprintf(out, "#define FLAG_%s (FORCED_FLAG%s<<%d)\n", + aflist->lopt->command, llstr, bit); aflist->lopt = aflist->lopt->next; if (!aflist->command) { aflist = aflist->next; bit++; if (flist) flist = flist->next; } + // Output normal flag macro } else if (aflist->command) { if (flist && (!flist->command || *aflist->command == *flist->command)) { if (aflist->command) - sprintf(out, "#define FLAG_%c (1<<%d)\n", *aflist->command, bit); + sprintf(out, "#define FLAG_%c (1%s<<%d)\n", *aflist->command, + llstr, bit); flist = flist->next; - } else sprintf(out, "#define FLAG_%c (FORCED_FLAG<<%d)\n", - *aflist->command, bit); + } else sprintf(out, "#define FLAG_%c (FORCED_FLAG%s<<%d)\n", + *aflist->command, llstr, bit); bit++; aflist = aflist->next; } diff --git a/tests/blkid.test b/tests/blkid.test index 3f676df8..25ba9d11 100755 --- a/tests/blkid.test +++ b/tests/blkid.test @@ -16,10 +16,10 @@ testing "blkid ext2" 'bzcat "$BDIR"/ext2.bz2 | blkid -' \ '-: LABEL="myext2" UUID="e59093ba-4135-4fdb-bcc4-f20beae4dfaf" TYPE="ext2"\n' \ "" "" testing "blkid ext3" 'bzcat "$BDIR"/ext3.bz2 | blkid -' \ - '-: LABEL="myext3" UUID="79d1c877-1a0f-4e7d-b21d-fc32ae3ef101" TYPE="ext2"\n' \ + '-: LABEL="myext3" UUID="79d1c877-1a0f-4e7d-b21d-fc32ae3ef101" TYPE="ext3"\n' \ "" "" testing "blkid ext4" 'bzcat "$BDIR"/ext4.bz2 | blkid -' \ - '-: LABEL="myext4" UUID="dc4b7c00-c0c0-4600-af7e-0335f09770fa" TYPE="ext2"\n' \ + '-: LABEL="myext4" UUID="dc4b7c00-c0c0-4600-af7e-0335f09770fa" TYPE="ext4"\n' \ "" "" testing "blkid f2fs" 'bzcat "$BDIR"/f2fs.bz2 | blkid -' \ '-: LABEL="" UUID="b53d3619-c204-4c0b-8504-36363578491c" TYPE="f2fs"\n' \ diff --git a/tests/find.test b/tests/find.test index 2f17bf76..71a35067 100755 --- a/tests/find.test +++ b/tests/find.test @@ -58,8 +58,14 @@ testing "find -perm (exact success)" \ "find perm -type f -perm 0444" "perm/all-read-only\n" "" "" testing "find -perm (exact failure)" \ "find perm -type f -perm 0400" "" "" "" -testing "find -perm (at least)" \ +testing "find -perm (min success)" \ "find perm -type f -perm -0400" "perm/all-read-only\n" "" "" +testing "find -perm (min failure)" \ + "find perm -type f -perm -0600" "" "" "" +testing "find -perm (any success)" \ + "find perm -type f -perm -0444" "perm/all-read-only\n" "" "" +testing "find -perm (any failure)" \ + "find perm -type f -perm -0222" "" "" "" # Still fails diff --git a/tests/sed.test b/tests/sed.test index 805184e6..8c5bb5d4 100755 --- a/tests/sed.test +++ b/tests/sed.test @@ -117,9 +117,10 @@ testing "sed delimiter in regex [char range] doesn't count" "sed -e 's/[/]//'" \ testing "sed delete regex range start line after trigger" \ "sed -e '/one/,/three/{' -e 'i meep' -e '1D;}'" \ "meep\nmeep\ntwo\nmeep\nthree" "" "one\ntwo\nthree" -testing "sed D further processing depends on whether line is blank" \ - "sed -e '/one/,/three/{' -e 'i meep' -e'N;2D;}'" \ - "meep\nmeep\ntwo\nthree\n" "" "one\ntwo\nthree\n" +testing "sed blank pattern repeats last pattern" \ + "sed -e '/^three/s//abc&def/'" \ + "one two three\nabcthreedef four five\nfive six seven\n" "" \ + "one two three\nthree four five\nfive six seven\n" # Different ways of parsing line continuations @@ -122,7 +122,7 @@ extern struct toy_context { struct toy_list *which; // Which entry in toy_list is this one? char **argv; // Original command line arguments char **optargs; // Arguments left over from get_optflags() - unsigned optflags; // Command line option flags from get_optflags() + unsigned long long optflags; // Command line option flags from get_optflags() int exitval; // Value error_exit feeds to exit() int optc; // Count of optargs int old_umask; // Old umask preserved by TOYFLAG_UMASK diff --git a/toys/example/test_many_options.c b/toys/example/test_many_options.c new file mode 100644 index 00000000..d2f5c846 --- /dev/null +++ b/toys/example/test_many_options.c @@ -0,0 +1,22 @@ +/* test_many_options.c - test more than 32 bits worth of option flags + * + * Copyright 2015 Rob Landley <rob@landley.net> + +USE_TEST_MANY_OPTIONS(NEWTOY(test_many_options, "ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba", TOYFLAG_USR|TOYFLAG_BIN)) + +config TEST_MANY_OPTIONS + bool "test_many_options" + default n + help + usage: test_many_options -[a-zA-Z] + + Print the optflags value of the command arguments, in hex. +*/ + +#define FOR_test_many_options +#include "toys.h" + +void test_many_options_main(void) +{ + xprintf("optflags=%llx\n", toys.optflags); +} diff --git a/toys/other/blkid.c b/toys/other/blkid.c index 8d3e7707..4883b607 100644 --- a/toys/other/blkid.c +++ b/toys/other/blkid.c @@ -34,8 +34,8 @@ struct fstype { }; static const struct fstype fstypes[] = { - {"swap", 0x4341505350415753LL, 8, 4086, 1036, 15, 1052}, {"ext2", 0xEF53, 2, 1080, 1128, 16, 1144}, // keep this first for ext3/4 check + {"swap", 0x4341505350415753LL, 8, 4086, 1036, 15, 1052}, // NTFS label actually 8/16 0x4d80 but horrible: 16 bit wide characters via // codepage, something called a uuid that's only 8 bytes long... {"ntfs", 0x5346544e, 4, 3, 0x48+(8<<24), 0, 0}, diff --git a/toys/other/free.c b/toys/other/free.c index cf4abc52..0a4d69b9 100644 --- a/toys/other/free.c +++ b/toys/other/free.c @@ -2,7 +2,8 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_FREE(NEWTOY(free, "tgmkb[!tgmkb]", TOYFLAG_USR|TOYFLAG_BIN)) +// Flag order is signifcant: b-t are units in order, FLAG_h-1 is unit mask +USE_FREE(NEWTOY(free, "htgmkb[!htgmkb]", TOYFLAG_USR|TOYFLAG_BIN)) config FREE bool "free" @@ -13,6 +14,7 @@ config FREE Display the total, free and used amount of physical memory and swap space. -bkmgt Output units (default is bytes) + -h Human readable */ #define FOR_free @@ -21,11 +23,19 @@ config FREE GLOBALS( unsigned bits; unsigned long long units; + char *buf; ) -static unsigned long long convert(unsigned long d) +static char *convert(unsigned long d) { - return (d*TT.units)>>TT.bits; + long long ll = d*TT.units; + char *s = TT.buf; + + if (toys.optflags & FLAG_h) human_readable(s, ll, 0); + else sprintf(s, "%llu",ll>>TT.bits); + TT.buf += strlen(TT.buf)+1; + + return s; } void free_main(void) @@ -34,12 +44,13 @@ void free_main(void) sysinfo(&in); TT.units = in.mem_unit ? in.mem_unit : 1; - for (TT.bits = 0; toys.optflags && !(toys.optflags&(1<<TT.bits)); TT.bits++); + while ((toys.optflags&(FLAG_h-1)) && !(toys.optflags&(1<<TT.bits))) TT.bits++; TT.bits *= 10; + TT.buf = toybuf; xprintf("\t\ttotal used free shared buffers\n" - "Mem:%17llu%12llu%12llu%12llu%12llu\n-/+ buffers/cache:%15llu%12llu\n" - "Swap:%16llu%12llu%12llu\n", convert(in.totalram), + "Mem:%17s%12s%12s%12s%12s\n-/+ buffers/cache:%15s%12s\n" + "Swap:%16s%12s%12s\n", convert(in.totalram), convert(in.totalram-in.freeram), convert(in.freeram), convert(in.sharedram), convert(in.bufferram), convert(in.totalram - in.freeram - in.bufferram), convert(in.freeram + in.bufferram), convert(in.totalswap), diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c index a442f0d1..712980cc 100644 --- a/toys/posix/cpio.c +++ b/toys/posix/cpio.c @@ -1,8 +1,10 @@ /* cpio.c - a basic cpio * - * Written 2013 AD by Isaac Dunham; this code is placed under the + * Written 2013 AD by Isaac Dunham; this code is placed under the * same license as toybox or as CC0, at your option. * + * Portions Copyright 2015 by Frontier Silicon Ltd. + * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cpio.html * and http://pubs.opengroup.org/onlinepubs/7908799/xcu/cpio.html * @@ -12,14 +14,16 @@ * Modern cpio expanded header to 110 bytes (first field 6 bytes, rest are 8). * In order: magic ino mode uid gid nlink mtime filesize devmajor devminor * rdevmajor rdevminor namesize check + * This is the equiavlent of mode -H newc when using GNU CPIO. -USE_CPIO(NEWTOY(cpio, "mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN)) +USE_CPIO(NEWTOY(cpio, "(no-preserve-owner)mduH: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] [ignored: -mdu -H newc] + usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner] + [ignored: -mdu -H newc] copy files into and out of a "newc" format cpio archive @@ -29,6 +33,7 @@ config CPIO -o create archive (stdin=list of files, stdout=archive) -t test files (list only, stdin=archive, stdout=list of files) -v verbose (list files during create/extract) + --no-preserve-owner (don't set ownership during extract) */ #define FOR_cpio @@ -104,7 +109,7 @@ void cpio_main(void) if (toys.optflags & (FLAG_i|FLAG_t)) for (;;) { char *name, *tofree, *data; - unsigned size, mode, uid, gid, timestamp; + unsigned size, mode, uid, gid, timestamp, ala = 0; int test = toys.optflags & FLAG_t, err = 0; // Read header and name. @@ -121,12 +126,16 @@ void cpio_main(void) size = x8u(toybuf+54); mode = x8u(toybuf+14); - uid = x8u(toybuf+30); - gid = x8u(toybuf+38); + uid = x8u(toybuf+22); + gid = x8u(toybuf+30); timestamp = x8u(toybuf+46); // unsigned 32 bit, so year 2100 problem if (toys.optflags & (FLAG_t|FLAG_v)) puts(name); + // If we need to reopen dir or mknod for write later, force u+w now + if (!(toys.optflags & FLAG_no_preserve_owner) + && !S_ISREG(mode) && !S_ISLNK(mode) && !geteuid()) ala = 0200; + if (!test && strrchr(name, '/') && mkpathat(AT_FDCWD, name, 0, 2)) { perror_msg("mkpath '%s'", name); test++; @@ -136,13 +145,14 @@ void cpio_main(void) // properly aligned with next file. if (S_ISDIR(mode)) { - if (!test) err = mkdir(name, mode); + if (!test) err = mkdir(name, ala|mode); } else if (S_ISLNK(mode)) { data = strpad(afd, size, 0); if (!test) err = symlink(data, name); free(data); // Can't get a filehandle to a symlink, so do special chown - if (!err && !getpid()) err = lchown(name, uid, gid); + if (!err && !geteuid() && !(toys.optflags & FLAG_no_preserve_owner)) + err = lchown(name, uid, gid); } else if (S_ISREG(mode)) { int fd = test ? 0 : open(name, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, mode); @@ -167,29 +177,36 @@ void cpio_main(void) if (!test) { // set owner, restore dropped suid bit - if (!getpid()) { + if (!geteuid() && !(toys.optflags & FLAG_no_preserve_owner)) { err = fchown(fd, uid, gid); if (!err) err = fchmod(fd, mode); } close(fd); } } else if (!test) - err = mknod(name, mode, makedev(x8u(toybuf+62), x8u(toybuf+70))); + err = mknod(name, ala|mode, 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) && !getpid()) { + // Creating 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? Use + // filehandle to check that we at least have the right type of entity + // open. If we forced it writeable, chmod it back, but do _not_ restore + // dropped suid/sgid bit. + if (ala) { int fd = open(name, O_WRONLY|O_NOFOLLOW); struct stat st; - if (fd != -1 && !fstat(fd, &st) && (st.st_mode&S_IFMT) == mode) - err = fchown(fd, uid, gid); - else err = 1; + // If we forced it writeable, change it back, but do _not_ restore + // dropped suid/sgid bit. + if (fd != -1 && !fstat(fd, &st) && (st.st_mode&S_IFMT)==(mode&S_IFMT)) { + if (!(err = fchown(fd, uid, gid))) + if ((ala|mode)!=mode) err = fchmod(fd, mode&~(S_ISUID|S_ISGID)); + } else err = 1; + + // If we forced it writeable, change it back, but do _not_ restore + // dropped suid/sgid bit. close(fd); } @@ -222,7 +239,7 @@ void cpio_main(void) if (len<1) break; if (name[len-1] == '\n') name[--len] = 0; nlen = len+1; - if (lstat(name, &st) || (S_ISREG(st.st_mode) + if (lstat(name, &st) || (S_ISREG(st.st_mode) && st.st_size && (fd = open(name, O_RDONLY))<0)) { perror_msg("%s", name); @@ -242,7 +259,7 @@ void cpio_main(void) // NUL Pad header up to 4 multiple bytes. llen = (llen + nlen) & 3; - if (llen) xwrite(afd, &zero, 4-llen); + if (llen) xwrite(afd, &zero, 4-llen); // Write out body for symlink or regular file llen = st.st_size; diff --git a/toys/posix/find.c b/toys/posix/find.c index f6701845..1e748feb 100644 --- a/toys/posix/find.c +++ b/toys/posix/find.c @@ -22,17 +22,17 @@ config FIND -H Follow command line symlinks -L Follow all symlinks Match filters: - -name PATTERN filename with wildcards -iname case insensitive -name - -path PATTERN path name with wildcards -ipath case insensitive -path - -user UNAME belongs to user UNAME -nouser user not in /etc/passwd - -group GROUP belongs to group GROUP -nogroup group not in /etc/group - -perm [-]MODE permissons (-=at least) -prune ignore contents of dir - -size N[c] 512 byte blocks (c=bytes) -xdev stay in this filesystem - -links N hardlink count -atime N accessed N days ago - -ctime N created N days ago -mtime N modified N days ago - -newer FILE newer mtime than FILE -mindepth # at least # dirs down - -depth ignore contents of dir -maxdepth # at most # dirs down - -inum N inode number N + -name PATTERN filename with wildcards -iname case insensitive -name + -path PATTERN path name with wildcards -ipath case insensitive -path + -user UNAME belongs to user UNAME -nouser user ID not known + -group GROUP belongs to group GROUP -nogroup group ID not known + -perm [-/]MODE permissions (-=min /=any) -prune ignore contents of dir + -size N[c] 512 byte blocks (c=bytes) -xdev only this filesystem + -links N hardlink count -atime N accessed N days ago + -ctime N created N days ago -mtime N modified N days ago + -newer FILE newer mtime than FILE -mindepth # at least # dirs down + -depth ignore contents of dir -maxdepth # at most # dirs down + -inum N inode number N -type [bcdflps] (block, char, dir, file, symlink, pipe, socket) Numbers N may be prefixed by a - (less than) or + (greater than): @@ -83,7 +83,7 @@ static int flush_exec(struct dirtree *new, struct exec_range *aa) // switch to directory for -execdir, or back to top if we have an -execdir // _and_ a normal -exec, or are at top of tree in -execdir - if (aa->dir && new->parent) rc = fchdir(new->parent->data); + if (aa->dir && new->parent) rc = fchdir(new->parent->dirfd); else if (TT.topdir != -1) rc = fchdir(TT.topdir); if (rc) { perror_msg("%s", new->name); @@ -278,11 +278,13 @@ static int do_find(struct dirtree *new) } else if (!strcmp(s, "perm")) { if (check) { char *m = ss[1]; - mode_t m1 = string_to_mode(m+(*m == '-'), 0), + int match_min = *m == '-', + match_any = *m == '/'; + mode_t m1 = string_to_mode(m+(match_min || match_any), 0), m2 = new->st.st_mode & 07777; - if (*m == '-') m2 &= m1; - test = m1 == m2; + if (match_min || match_any) m2 &= m1; + test = match_any ? !m1 || m2 : m1 == m2; } } else if (!strcmp(s, "type")) { if (check) { diff --git a/toys/posix/ls.c b/toys/posix/ls.c index 7d15935c..04f64152 100644 --- a/toys/posix/ls.c +++ b/toys/posix/ls.c @@ -12,6 +12,7 @@ config LS default y help usage: ls [-ACFHLRSZacdfhiklmnpqrstux1] [directory...] + list files what to show: @@ -317,7 +318,7 @@ static void listfiles(int dirfd, struct dirtree *indir) } else { // 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. - indir->data = dup(dirfd); + indir->dirfd = dup(dirfd); dirtree_recurse(indir, filter, DIRTREE_SYMFOLLOW*!!(flags&FLAG_L)); } diff --git a/toys/posix/ps.c b/toys/posix/ps.c index e42e7ec4..cbbc5177 100644 --- a/toys/posix/ps.c +++ b/toys/posix/ps.c @@ -25,23 +25,22 @@ * leaving 9 chars for cmd, so we're using that as our -l output. * * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference) - * TODO: finalize F, remove C - * switch -fl to -y, use "string" instead of constants to set, remove C - * TODO: --sort + * TODO: switch -fl to -y * TODO: way too many hardwired constants here, how can I generate them? * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l") * * Design issue: the -o fields are an ordered array, and the order is * significant. The array index is used in strawberry->which (consumed - * in do_ps()) and in the bitmasks enabling default fields in ps_main(). + * in do_ps()) and in the TT.bits bitmask. -USE_PS(NEWTOY(ps, "P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_TTOP(NEWTOY(ttop, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN)) config PS bool "ps" default y help - usage: ps [-AadeflnwZ] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-uU USER] + usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,] List processes. @@ -62,6 +61,7 @@ config PS Output modifiers: + -k Sort FIELDs in +increasing or -decreasting order (--sort) -n Show numeric USER and GROUP -w Wide output (don't truncate at terminal width) @@ -75,9 +75,10 @@ config PS Available -o FIELDs: ADDR Instruction pointer - CMD Command line (from /proc/pid/cmdline, including args) - CMDLINE Command line (from /proc/pid/cmdline, no args) - COMM Command name (from /proc/pid/stat, no args) + CMD Command name (original) + CMDLINE Command name (current argv[0]) + COMM Command line (with arguments) + CPU Which processor is process running on ETIME Elapsed time since process start F Process flags (PF_*) from linux source file include/sched.h (in octal rather than hex because posix) @@ -111,61 +112,149 @@ config PS USER User name VSZ Virtual memory size (1k units) WCHAN Waiting in kernel for + +config TTOP + bool "ttop" + default n + help + + usage: ttop [-mb] [ -d seconds ] [ -n iterations ] + + Provide a view of process activity in real time. + Keys + N/M/P/T show CPU usage, sort by pid/mem/cpu/time + S show memory + R reverse sort + H toggle threads + C,1 toggle SMP + Q,^C exit + + Options + -n Iterations before exiting + -d Delay between updates + -m Same as 's' key + -b Batch mode */ #define FOR_ps #include "toys.h" GLOBALS( - struct arg_list *G; - struct arg_list *g; - struct arg_list *U; - struct arg_list *u; - struct arg_list *t; - struct arg_list *s; - struct arg_list *p; - struct arg_list *o; - struct arg_list *P; - - struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU, *parsing; + union { + struct { + struct arg_list *G; + struct arg_list *g; + struct arg_list *U; + struct arg_list *u; + struct arg_list *t; + struct arg_list *s; + struct arg_list *p; + struct arg_list *o; + struct arg_list *P; + struct arg_list *k; + } ps; + struct { + long n; + long d; + } ttop; + }; + + struct sysinfo si; + struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU; unsigned width; dev_t tty; - void *fields; - long bits; - long long ticks; + void *fields, *kfields; + long long ticks, bits; size_t header_len; + int kcount; ) struct strawberry { struct strawberry *next, *prev; - short which, len; + short which, len, reverse; char *title; char forever[]; }; -static time_t get_uptime(void) -{ - struct sysinfo si; - - sysinfo(&si); - - return si.uptime; -} +// Data layout in toybuf +struct carveup { + long long slot[50]; // data from /proc, skippint #2 and #3 + unsigned short offset[4]; // offset of fields in str[] (skip name, always 0) + char state; + char str[]; // name, tty, wchan, attr, cmdline +}; -// Return 1 to display, 0 to skip +/* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt + * table 1-4) but we shift and repurpose fields, with the result being: + * + * 0 pid process id + * 1 ppid parent process id + * 2 pgrp pgrp of the process + * 3 sid session id + * 4 tty_nr tty the process uses + * 5 tty_pgrp pgrp of the tty + * 6 flags task flags + * 7 min_flt number of minor faults + * 8 cmin_flt number of minor faults with child's + * 9 maj_flt number of major faults + * 10 cmaj_flt number of major faults with child's + * 11 utime user mode jiffies + * 12 stime kernel mode jiffies + * 13 cutime user mode jiffies with child's + * 14 cstime kernel mode jiffies with child's + * 15 priority priority level + * 16 nice nice level + * 17 num_threads number of threads + * 18 vmlck locked memory + * 19 start_time time the process started after system boot + * 20 vsize virtual memory size + * 21 rss resident set memory size + * 22 rsslim current limit in bytes on the rss + * 23 start_code address above which program text can run + * 24 end_code address below which program text can run + * 25 start_stack address of the start of the main process stack + * 26 esp current value of ESP + * 27 eip current value of EIP + * 28 pending bitmap of pending signals + * 29 blocked bitmap of blocked signals + * 30 sigign bitmap of ignored signals + * 31 uid user id + * 32 ruid real user id + * 33 gid group id + * 34 rgid real group id + * 35 exit_signal signal to send to parent thread on exit + * 36 task_cpu which CPU the task is scheduled on + * 37 rt_priority realtime priority + * 38 policy scheduling policy (man sched_setscheduler) + * 39 blkio_ticks time spent waiting for block IO + * 40 gtime guest time of the task in jiffies + * 41 cgtime guest time of the task children in jiffies + * 42 start_data address above which program data+bss is placed + * 43 end_data address below which program data+bss is placed + * 44 start_brk address above which program heap can be expanded with brk() + * 45 argv0len length of argv[0] read from /proc/$PID/cmdline + * 46 uptime sysinfo.uptime when this entry was read + * 47 vsz Virtual Size + * 48 rss Resident Set Size + * 49 shr Shared memory + */ + +// Return 1 to keep, 0 to discard static int match_process(long long *slot) { - struct ptr_len *match[] = { - &TT.gg, &TT.GG, &TT.pp, &TT.PP, &TT.ss, &TT.tt, &TT.uu, &TT.UU + struct ptr_len match[] = { + {&TT.gg, 33}, {&TT.GG, 34}, {&TT.pp, 0}, {&TT.PP, 1}, {&TT.ss, 3}, + {&TT.tt, 4}, {&TT.uu, 31}, {&TT.UU, 32} }; - int i, j, mslot[] = {33, 34, 0, 1, 3, 4, 31, 32}; + int i, j; long *ll = 0; // Do we have -g -G -p -P -s -t -u -U options selecting processes? for (i = 0; i < ARRAY_LEN(match); i++) { - if (match[i]->len) { - ll = match[i]->ptr; - for (j = 0; j<match[i]->len; j++) if (ll[j] == slot[mslot[i]]) return 1; + struct ptr_len *mm = match[i].ptr; + if (mm->len) { + ll = mm->ptr; + for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1; } } @@ -181,155 +270,291 @@ static int match_process(long long *slot) return 1; } -// dirtree callback. -// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline -static int do_ps(struct dirtree *new) +static char *string_field(struct carveup *tb, struct strawberry *field) +{ + char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s; + long long ll, *slot = tb->slot; + int i; + + // Default: unsupported (5 "C") + sprintf(out, "-"); + + // stat#s: PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSZ, MAJFL, MINFL, PR + if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,34,0}, field->which))) + { + char *fmt = "%lld"; + + ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7,15})[i]]; + if (i==2) ll = 39-ll; + if (i==4) fmt = "%llx"; + else if (i==5) ll >>= 12; + else if (i==6) ll <<= 2; + else if (i==8) ll >>= 10; + else if (i==11) if (ll<-9) fmt="RT"; + sprintf(out, fmt, ll); + + // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP + } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20,0}, field->which))) + { + int id = slot[31+i/2]; // uid, ruid, gid, rgid + + // Even entries are numbers, odd are names + sprintf(out, "%d", id); + if (!(toys.optflags&FLAG_n) && i&1) { + if (i>3) { + struct group *gr = getgrgid(id); + + if (gr) out = gr->gr_name; + } else { + struct passwd *pw = getpwuid(id); + + if (pw) out = pw->pw_name; + } + } + // CMD TTY WCHAN LABEL (CMDLINE handled elsewhere) + } else if (-1!=(i = stridx((char[]){15,12,10,31,0}, field->which))) { + out = tb->str; + if (i) out += tb->offset[i-1]; + + // F (also assignment of i used by later tests) + // Posix doesn't specify what flags should say. Man page says + // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h + } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5); + // S STAT + else if (i==1 || i==27) { + s = out; + *s++ = tb->state; + if (i==27) { + // TODO l = multithreaded + if (slot[16]<0) *s++ = '<'; + else if (slot[16]>0) *s++ = 'N'; + if (slot[3]==*slot) *s++ = 's'; + if (slot[18]) *s++ = 'L'; + if (slot[5]==*slot) *s++ = '+'; + } + *s = 0; + // STIME + } else if (i==11) { + time_t t = time(0)-slot[46]+slot[19]/TT.ticks; + + // Padding behavior's a bit odd: default field size is just hh:mm. + // Increasing stime:size reveals more data at left until full, + // so move start address so yyyy-mm-dd hh:mm revealed on left at :16, + // then add :ss on right for :19. + strftime(out, 260, "%F %T", localtime(&t)); + out = out+strlen(out)-3-abs(field->len); + if (out<buf) out = buf; + + // TIME ELAPSED + } else if (i==13 || i==16) { + int unit = 60*60*24, j = TT.ticks; + time_t seconds = (i==16) ? (slot[46]*j)-slot[19] : slot[11]+slot[12]; + + seconds /= j; + for (s = 0, j = 0; j<4; j++) { + // TIME has 3 required fields, ETIME has 2. (Posix!) + if (!s && (seconds>unit || j == 1+(i==16))) s = out; + if (s) { + s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit)); + if ((*s = "-::"[j])) s++; + } + seconds %= unit; + unit /= j ? 60 : 24; + } + + // COMM - command line including arguments + // CMDLINE - command name from /proc/pid/cmdline (no arguments) + } else if (i==14 || i==32) { + // Use [real name] for kernel threads, max buf space 255+2+1 bytes + if (slot[45]<1) sprintf(out, "[%s]", tb->str); + else { + out = tb->str+tb->offset[3]; + if (slot[45]!=INT_MAX) out[slot[45]] = ' '*(i==14); + } + + // %CPU %VSZ + } else if (i==18 || i==33) { + if (i==18) { + ll = (slot[46]*TT.ticks-slot[19]); + i = ((slot[11]+slot[12])*1000)/ll; + } else i = (slot[23]*1000)/TT.si.totalram; + sprintf(out, "%d.%d", i/10, i%10); + } else if (i>=35 && i<=37) + human_readable(out, slot[i-35+47]*sysconf(_SC_PAGESIZE), 0); + + return out; +} + +// Display process data that get_ps() read from /proc, formatting with TT.fields +static void show_ps(struct carveup *tb) { struct strawberry *field; - long long *slot = (void *)(toybuf+1024), ll; - char *name, *s, state; - int nlen, i, fd, len, width = TT.width; + int i, len, width = TT.width; - if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP; - if (!(*slot = atol(new->name))) return 0; + // Loop through fields to display + for (field = TT.fields; field; field = field->next) { + char *out = string_field(tb, field); - // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars: - // 1000-ish total, but some forced zero so actually there's headroom. - sprintf(toybuf, "%lld/stat", *slot); - if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0; + // Output the field, appropriately padded + len = width - (field != TT.fields); + if (!field->next && field->len<0) i = 0; + else { + i = len<abs(field->len) ? len : field->len; + len = abs(i); + } + + // TODO test utf8 fontmetrics + width -= printf(" %*.*s" + (field == TT.fields), i, len, out); + if (!width) break; + } + xputc('\n'); +} - // parse oddball fields (name and state) - if (!(s = strchr(toybuf, '('))) return 0; - for (name = ++s; *s != ')'; s++) if (!*s) return 0; - nlen = s++-name; - if (1>sscanf(++s, " %c%n", &state, &i)) return 0; +// dirtree callback: read data about process to display, store, or discard it. +// Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra +// (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there). +static int get_ps(struct dirtree *new) +{ + struct { + char *name; + long long bits; + } fetch[] = {{"fd/", 1<<12}, {"wchan", 1<<10}, {"attr/current", 1<<31}, + {"cmdline", (1<<14)|(1LL<<32)}}; + struct carveup *tb = (void *)toybuf; + long long *slot = tb->slot; + char *name, *s, *buf = tb->str, *end = 0; + int i, j, fd, ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k); + off_t len; + + // Recurse one level into /proc children, skip non-numeric entries + if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|ksave; + if (!(*slot = atol(new->name))) return 0; + fd = dirtree_parentfd(new); + + len = 2048; + sprintf(buf, "%lld/stat", *slot); + if (!readfileat(fd, buf, buf, &len)) return 0; + + // parse oddball fields (name and state). Name can have embedded ')' so match + // _last_ ')' in stat (although VFS limits filenames to 255 bytes max). + // All remaining fields should be numeric. + if (!(name = strchr(buf, '('))) return 0; + for (s = ++name; *s; s++) if (*s == ')') end = s; + if (!end || end-name>255) return 0; + + // Move name right after slot[] array (pid already set, so stomping it's ok) + // and convert low chars to spaces while we're at it. + for (i = 0; i<end-name; i++) + if ((tb->str[i] = name[i]) < ' ') tb->str[i] = ' '; + buf = tb->str+i; + *buf++ = 0; + + // Parse numeric fields (starting at 4th field in slot[1]) + if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0; + for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break; + + // Now we've read the data, move status and name right after slot[] array, + // and convert low chars to spaces while we're at it. + for (i = 0; i<end-name; i++) + if ((tb->str[i] = name[i]) < ' ') tb->str[i] = ' '; + buf = tb->str+i; + *buf++ = 0; + len = sizeof(toybuf)-(buf-toybuf); - // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1]) - for (len = 1; len<100; len++) - if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break; // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch - // or numeric wchan, and the remaining two are always zero). + // or numeric wchan, and the remaining two are always zero), and vmlck into + // 18 (which is "obsolete, always 0" from stat) slot[31] = new->st.st_uid; slot[33] = new->st.st_gid; - // If RGROUP RUSER STAT RUID RGID - // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero, - // and vmlck into slot[18] (it_real_value, also always zero). + // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status" + // and save ruid, rgid, and vmlck. if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) { - char *out = toybuf+2048; + off_t temp = len; - sprintf(out, "%lld/status", *slot); - if (!readfileat(dirtree_parentfd(new), out, out, 2048)) *out = 0; - s = strstr(out, "\nUid:"); + sprintf(buf, "%lld/status", *slot); + if (!readfileat(fd, buf, buf, &temp)) *buf = 0; + s = strstr(buf, "\nUid:"); slot[32] = s ? atol(s+5) : new->st.st_uid; - s = strstr(out, "\nGid:"); + s = strstr(buf, "\nGid:"); slot[34] = s ? atol(s+5) : new->st.st_gid; - s = strstr(out, "\nVmLck:"); + s = strstr(buf, "\nVmLck:"); if (s) slot[18] = atoll(s+5); } - // skip processes we don't care about. + // We now know enough to skip processes we don't care about. if (!match_process(slot)) return 0; - // At this point 512 bytes at toybuf+512 are free (already parsed). - // Start of toybuf still has name in it. + // Fetch VIRT RES SHR (for top) + if (TT.bits & (7LL<<35)) { + off_t temp = len; - // Loop through fields - for (field = TT.fields; field; field = field->next) { - char *out = toybuf+2048, *scratch = toybuf+512; - - // Default: unsupported (5 "C") - sprintf(out, "-"); - - // PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL - if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which))) - { - char *fmt = "%lld"; - - ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]]; - if (i==2) ll--; - if (i==4) fmt = "%llx"; - else if (i==5) ll >>= 12; - else if (i==6) ll <<= 2; - else if (i==8) ll >>= 10; - sprintf(out, fmt, ll); - // UID USER RUID RUSER GID GROUP RGID RGROUP - } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which))) - { - int id = slot[31+i/2]; // uid, ruid, gid, rgid - - // Even entries are numbers, odd are names - sprintf(out, "%d", id); - if (!(toys.optflags&FLAG_n) && i&1) { - if (i>3) { - struct group *gr = getgrgid(id); - - if (gr) out = gr->gr_name; - } else { - struct passwd *pw = getpwuid(id); - - if (pw) out = pw->pw_name; + sprintf(buf, "%lld/statm", *slot); + if (!readfileat(fd, buf, buf, &temp)) *buf = 0; + + for (s = buf, i=0; i<3; i++) + if (!sscanf(s, " %lld%n", slot+47+i, &j)) slot[47+i] = 0; + else s += j; + } + + // /proc data is generated as it's read, so for maximum accuracy on slow + // systems (or ps | more) we re-fetch uptime as we fetch each /proc line. + sysinfo(&TT.si); + slot[46] = TT.si.uptime; + + // fetch remaining data while parentfd still available, appending to buf. + // (There's well over 3k of toybuf left. We could dynamically malloc, but + // it'd almost never get used, querying length of a proc file is awkward, + // fixed buffer is nommu friendly... Wait for somebody to complain. :) + for (j = 0; j<ARRAY_LEN(fetch); j++) { + tb->offset[j] = buf-(tb->str); + if (!(TT.bits&fetch[j].bits)) { + *buf++ = 0; + continue; + } + + // Determine remaining space, reserving minimum of 256 bytes/field and + // 260 bytes scratch space at the end (for output conversion later). + len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j); + sprintf(buf, "%lld/%s", *slot, fetch[j].name); + + // If it's not the TTY field, data we want is in a file. + // Last length saved in slot[] is command line (which has embedded NULs) + if (j) { + readfileat(fd, buf, buf, &len); + + // When command has no arguments, don't space over the NUL + if (len>0) { + int temp = 0; + + if (buf[len-1]=='\n') buf[--len] = 0; + + // Always escape spaces because an executable named esc[0m would be bad. + // Escaping low ascii does not affect utf8. + for (i=0; i<len; i++) { + if (!temp && !buf[i]) temp = i; + if (buf[i]<' ') buf[i] = ' '; } - } - - // F (also assignment of i used by later tests) - // Posix doesn't specify what flags should say. Man page says - // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h - } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5); - // S STAT - else if (i==1 || i==27) { - sprintf(out, "%c", state); - if (i==27) { - // TODO l = multithreaded - s = out+1; - if (slot[16]<0) *s++ = '<'; - else if (slot[16]>0) *s++ = 'N'; - if (slot[3]==*slot) *s++ = 's'; - if (slot[18]) *s++ = 'L'; - if (slot[5]==*slot) *s++ = '+'; - *s = 0; - } - // WCHAN - } else if (i==10) { - sprintf(scratch, "%lld/wchan", *slot); - readfileat(dirtree_parentfd(new), scratch, out, 2047); - - // LABEL - } else if (i==31) { - sprintf(scratch, "%lld/attr/current", *slot); - readfileat(dirtree_parentfd(new), scratch, out, 2047); - chomp(out); - - // STIME - } else if (i==11) { - time_t t = time(0) - get_uptime() + slot[19]/sysconf(_SC_CLK_TCK); - - // Padding behavior's a bit odd: default field size is just hh:mm. - // Increasing stime:size reveals more data at left until full - // yyyy-mm-dd hh:mm revealed at :16, then adds :ss at end for :19. But - // expanding last field just adds :ss. - strftime(scratch, 512, "%F %T", localtime(&t)); - out = scratch+strlen(scratch)-3-abs(field->len); - if (out<scratch) out = scratch; - - // TTY - } else if (i==12) { + if (temp) len = temp; // position of _first_ NUL + else len = INT_MAX; + } else *buf = len = 0; + // Store end of argv[0] so COMM and CMDLINE can differ. + slot[45] = len; + } else { int rdev = slot[4]; struct stat st; // Call no tty "?" rather than "0:0". - if (!rdev) strcpy(out, "?"); - else { + strcpy(buf, "?"); + if (rdev) { // Can we readlink() our way to a name? - for (i=0; i<3; i++) { - sprintf(scratch, "%lld/fd/%i", *slot, i); - fd = dirtree_parentfd(new); - if (!fstatat(fd, scratch, &st, 0) && S_ISCHR(st.st_mode) - && st.st_rdev == rdev - && 0<(len = readlinkat(fd, scratch, out, 2047))) + for (i = 0; i<3; i++) { + sprintf(buf, "%lld/fd/%i", *slot, i); + if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode) + && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len))) { - out[len] = 0; + buf[len] = 0; break; } } @@ -340,11 +565,11 @@ static int do_ps(struct dirtree *new) int tty_major = 0, maj = major(rdev), min = minor(rdev); if (fp) { - while (fscanf(fp, "%*s %256s %d %*s %*s", out, &tty_major) == 2) { + while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) { // TODO: we could parse the minor range too. if (tty_major == maj) { - sprintf(out + strlen(out), "%d", min); - if (!stat(out, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev) + sprintf(buf+strlen(buf), "%d", min); + if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev) break; } tty_major = 0; @@ -353,83 +578,32 @@ static int do_ps(struct dirtree *new) } // Really couldn't find it, so just show major:minor. - if (!tty_major) sprintf(out, "%d:%d", maj, min); + if (!tty_major) sprintf(buf, "%d:%d", maj, min); } - strstart(&out, "/dev/"); + s = buf; + if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);; } - - // TIME ELAPSED - } else if (i==13 || i==16) { - int unit = 60*60*24, j = sysconf(_SC_CLK_TCK); - time_t seconds = (i==16) ? (get_uptime()*j)-slot[19] : slot[11]+slot[12]; - - seconds /= j; - for (s = 0, j = 0; j<4; j++) { - // TIME has 3 required fields, ETIME has 2. (Posix!) - if (!s && (seconds>unit || j == 1+(i==16))) s = out; - if (s) { - s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit)); - if ((*s = "-::"[j])) s++; - } - seconds %= unit; - unit /= j ? 60 : 24; - } - - // COMM - command line including arguments - // Command line limited to 2k displayable. We could dynamically malloc, but - // it'd almost never get used, querying length of a proc file is awkward, - // fixed buffer is nommu friendly... Wait for somebody to complain. :) - // CMDLINE - command line from /proc/pid/cmdline without arguments - } else if (i==14 || i==32) { - int fd; - - len = 0; - sprintf(out, "%lld/cmdline", *slot); - fd = openat(dirtree_parentfd(new), out, O_RDONLY); - - if (fd != -1) { - if (0<(len = read(fd, out, 2047))) { - if (!out[len-1]) len--; - else out[len] = 0; - if (i==14) for (i = 0; i<len; i++) if (out[i] < ' ') out[i] = ' '; - } - close(fd); - } - - if (len<1) sprintf(out, "[%.*s]", nlen, name); - - // CMD - command name (without arguments) - } else if (i==15) { - sprintf(out, "%.*s", nlen, name); - - // %CPU - } else if (i==18) { - ll = (get_uptime()*sysconf(_SC_CLK_TCK)-slot[19]); - len = ((slot[11]+slot[12])*1000)/ll; - sprintf(out, "%d.%d", len/10, len%10); } + buf += strlen(buf)+1; + } - // Output the field, appropriately padded - len = width - (field != TT.fields); - if (!field->next && field->len<0) i = 0; - else { - i = len<abs(field->len) ? len : field->len; - len = abs(i); - } + // If we need to sort the output, add it to the list and return. + if (ksave) { + s = xmalloc(buf-toybuf); + new->extra = (long)s; + memcpy(s, toybuf, buf-toybuf); + TT.kcount++; - // TODO test utf8 fontmetrics - width -= printf(" %*.*s" + (field == TT.fields), i, len, out); - if (!width) break; - } - xputc('\n'); + // Otherwise display it now + } else show_ps(tb); - return 0; + return ksave; } // Traverse arg_list of csv, calling callback on each value -void comma_args(struct arg_list *al, char *err, - char *(*callback)(char *str, int len)) +void comma_args(struct arg_list *al, void *data, char *err, + char *(*callback)(void *data, char *str, int len)) { char *next, *arg; int len; @@ -437,27 +611,29 @@ void comma_args(struct arg_list *al, char *err, while (al) { arg = al->arg; while ((next = comma_iterate(&arg, &len))) - if ((next = callback(next, len))) + if ((next = callback(data, next, len))) perror_exit("%s '%s'\n%*c", err, al->arg, (int)(5+strlen(toys.which->name)+strlen(err)+next-al->arg), '^'); al = al->next; } } -static char *parse_o(char *type, int length) +static char *parse_ko(void *data, char *type, int length) { struct strawberry *field; char *width, *title, *end, *s, *typos[] = { "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ", "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP", "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL", - "GID", "STAT", "RUID", "RGID", "MINFL", "LABEL", "CMDLINE" + "GID", "STAT", "RUID", "RGID", "MINFL", "LABEL", "CMDLINE", "%VSZ", + "PR", "VIRT", "RES", "SHR", "TIME+" }; // TODO: Android uses -30 for LABEL, but ideally it would auto-size. signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5, -6,5,-8,8,-27,-27,11,-8, 4,5,-8,-8,-8,6,5,6, - 8,-5,4,4,6,-30,-27}; + 8,-5,4,4,6,-30,-27,5, + 2,4,4,4,9}; int i, j, k; // Get title, length of title, type, end of type, and display width @@ -490,6 +666,12 @@ static char *parse_o(char *type, int length) } // Find type + if (*(struct strawberry **)data == TT.kfields) { + field->reverse = 1; + if (*type == '-') field->reverse = -1; + else if (*type != '+') type--; + type++; + } for (i = 0; i<ARRAY_LEN(typos); i++) { field->which = i; for (j = 0; j<2; j++) { @@ -507,21 +689,23 @@ static char *parse_o(char *type, int length) if (!field->title) field->title = typos[field->which]; if (!field->len) field->len = widths[field->which]; else if (widths[field->which]<0) field->len *= -1; - dlist_add_nomalloc((void *)&TT.fields, (void *)field); - - // Print padded header. - TT.header_len += - snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len, - " %*s" + (field == TT.fields), field->len, field->title); - TT.bits |= (i = 1<<field->which); + dlist_add_nomalloc(data, (void *)field); + + // Print padded header for -o. + if (*(struct strawberry **)data == TT.fields) { + TT.header_len += + snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len, + " %*s" + (field == TT.fields), field->len, field->title); + TT.bits |= 1LL<<field->which; + } return 0; } // Parse -p -s -t -u -U -g -G -static char *parse_rest(char *str, int len) +static char *parse_rest(void *data, char *str, int len) { - struct ptr_len *pl = TT.parsing; + struct ptr_len *pl = (struct ptr_len *)data; long *ll = pl->ptr; char *end; int num = 0; @@ -597,10 +781,47 @@ static char *parse_rest(char *str, int len) return str; } +// sort for -k +static int ksort(void *aa, void *bb) +{ + struct strawberry *field; + long long lla, llb; + char *out, *end; + int ret = 0; + + for (field = TT.kfields; field; field = field->next) { + + // Convert fields to string version, saving first in toybuf + out = string_field(*(void **)aa, field); + memccpy(toybuf, out, 0, 2048); + toybuf[2048] = 0; + out = string_field(*(void **)bb, field); + + // Numeric comparison? + llb = strtoll(out, &end, 10); + if (!*end) { + lla = strtoll(toybuf, &end, 10); + if (!*end) { + if (lla<llb) ret = -1; + if (lla>llb) ret = 1; + } + } + + // String compare + if (*end) ret = strcmp(toybuf, out); + + if (ret) return ret*field->reverse; + } + + return 0; +} + void ps_main(void) { + struct dirtree *dt; int i; + TT.ticks = sysconf(_SC_CLK_TCK); TT.width = 99999; if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0); @@ -616,31 +837,25 @@ void ps_main(void) } // parse command line options other than -o - TT.parsing = &TT.PP; - comma_args(TT.P, "bad -P", parse_rest); - TT.parsing = &TT.pp; - comma_args(TT.p, "bad -p", parse_rest); - TT.parsing = &TT.tt; - comma_args(TT.t, "bad -t", parse_rest); - TT.parsing = &TT.ss; - comma_args(TT.s, "bad -s", parse_rest); - TT.parsing = &TT.uu; - comma_args(TT.u, "bad -u", parse_rest); - TT.parsing = &TT.UU; - comma_args(TT.U, "bad -u", parse_rest); - TT.parsing = &TT.gg; - comma_args(TT.g, "bad -g", parse_rest); - TT.parsing = &TT.GG; - comma_args(TT.G, "bad -G", parse_rest); + comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest); + comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest); + comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest); + comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest); + comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest); + comma_args(TT.ps.U, &TT.UU, "bad -u", parse_rest); + comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest); + comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest); + comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko); + dlist_terminate(TT.kfields); // Parse manual field selection, or default/-f/-l, plus -Z, // constructing the header line in toybuf as we go. if (toys.optflags&FLAG_Z) { struct arg_list Z = { 0, "LABEL" }; - comma_args(&Z, "-Z", parse_o); + comma_args(&Z, &TT.fields, "-Z", parse_ko); } - if (TT.o) comma_args(TT.o, "bad -o field", parse_o); + if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko); else { struct arg_list al; @@ -653,12 +868,39 @@ void ps_main(void) al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE"; else al.arg = "PID,TTY,TIME,CMD"; - comma_args(&al, 0, parse_o); + comma_args(&al, &TT.fields, 0, parse_ko); } dlist_terminate(TT.fields); printf("%s\n", toybuf); - dirtree_read("/proc", do_ps); + dt = dirtree_read("/proc", get_ps); + + if (toys.optflags&FLAG_k) { + struct carveup **tbsort = xmalloc(TT.kcount*sizeof(struct carveup *)); + + // descend into child list + *tbsort = (void *)dt; + dt = dt->child; + free(*tbsort); + + // populate array + i = 0; + while (dt) { + void *temp = dt->next; + + tbsort[i++] = (void *)dt->extra; + free(dt); + dt = temp; + } + + // Sort and show + qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort); + for (i = 0; i<TT.kcount; i++) { + show_ps(tbsort[i]); + free(tbsort[i]); + } + if (CFG_TOYBOX_FREE) free(tbsort); + } if (CFG_TOYBOX_FREE) { free(TT.gg.ptr); @@ -672,3 +914,12 @@ void ps_main(void) llist_traverse(TT.fields, free); } } + +#define CLEANUP_ps +#define FOR_top +#include "generated/flags.h" + +void ttop_main(void) +{ + ps_main(); +} diff --git a/toys/posix/tail.c b/toys/posix/tail.c index 80556e2b..910b88f3 100644 --- a/toys/posix/tail.c +++ b/toys/posix/tail.c @@ -74,7 +74,7 @@ static int try_lseek(int fd, long bytes, long lines) { struct line_list *list = 0, *temp; int flag = 0, chunk = sizeof(toybuf); - ssize_t pos = lseek(fd, 0, SEEK_END); + off_t pos = lseek(fd, 0, SEEK_END); // If lseek() doesn't work on this stream, return now. if (pos<0) return 0; |