aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2015-12-11 15:09:45 -0800
committerElliott Hughes <enh@google.com>2015-12-11 15:09:45 -0800
commit0188665ed89e5db3feb0d0dca29c6b6105d79d2d (patch)
treed7faef1b467bea9ca1eed0479d14a4a450709c81
parent8755cdc71e66e675251846554fb5013d7485dd5a (diff)
parentaaecbbac2f94b7a93eb2df7f9db78828cbb7b647 (diff)
downloadtoybox-0188665ed89e5db3feb0d0dca29c6b6105d79d2d.tar.gz
Merge remote-tracking branch 'toybox/master' into HEAD
-rw-r--r--README5
-rw-r--r--lib/args.c10
-rw-r--r--lib/dirtree.c21
-rw-r--r--lib/lib.c14
-rw-r--r--lib/lib.h8
-rw-r--r--scripts/mkflags.c22
-rwxr-xr-xtests/blkid.test4
-rwxr-xr-xtests/find.test8
-rwxr-xr-xtests/sed.test7
-rw-r--r--toys.h2
-rw-r--r--toys/example/test_many_options.c22
-rw-r--r--toys/other/blkid.c2
-rw-r--r--toys/other/free.c23
-rw-r--r--toys/posix/cpio.c57
-rw-r--r--toys/posix/find.c32
-rw-r--r--toys/posix/ls.c3
-rw-r--r--toys/posix/ps.c777
-rw-r--r--toys/posix/tail.c2
18 files changed, 670 insertions, 349 deletions
diff --git a/README b/README
index 5a4a2cac..5fc9ec0b 100644
--- a/README
+++ b/README
@@ -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.
diff --git a/lib/args.c b/lib/args.c
index 594a1b47..1f42cdde 100644
--- a/lib/args.c
+++ b/lib/args.c
@@ -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;
}
diff --git a/lib/lib.c b/lib/lib.c
index b3703052..a16439e7 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -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
diff --git a/lib/lib.h b/lib/lib.h
index 301c50f4..750e1240 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -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
diff --git a/toys.h b/toys.h
index 9c33ff21..688d5585 100644
--- a/toys.h
+++ b/toys.h
@@ -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;