Merge remote-tracking branch 'toybox/master' into HEAD

Change-Id: I5d330bde83d1c87e58350d31f614023db209bf9b
diff --git a/Android.bp b/Android.bp
index 388a05b..d855545 100644
--- a/Android.bp
+++ b/Android.bp
@@ -232,6 +232,7 @@
         "-Wall",
         "-Werror",
         "-Wno-char-subscripts",
+        "-Wno-deprecated-declarations",
         "-Wno-missing-field-initializers",
         "-Wno-sign-compare",
         "-Wno-string-plus-int",
diff --git a/configure b/configure
index 58e754a..06d7cba 100755
--- a/configure
+++ b/configure
@@ -22,7 +22,11 @@
 [ -z "$OPTIMIZE" ] && OPTIMIZE="-Os -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables -fno-strict-aliasing"
 
 # We accept LDFLAGS, but by default don't have anything in it
-[ -z "$LDOPTIMIZE" ] && LDOPTIMIZE="-Wl,--gc-sections"
+if [ "$(uname)" != "Darwin" ]
+then
+  [ -z "$LDOPTIMIZE" ] && LDOPTIMIZE="-Wl,--gc-sections"
+  LDASNEEDED="-Wl,--as-needed"
+fi
 
 # The makefile provides defaults for these, so this only gets used if
 # you call scripts/make.sh and friends directly.
diff --git a/generated/flags.h b/generated/flags.h
index 6fa962f..5470c1a 100644
--- a/generated/flags.h
+++ b/generated/flags.h
@@ -1144,15 +1144,17 @@
 #undef FLAG_c
 #endif
 
-// head ?n#<0=10c#<0qv[-nc] ?n#<0=10c#<0qv[-nc]
+// head ?n(lines)#<0=10c(bytes)#<0qv[-nc] ?n(lines)#<0=10c(bytes)#<0qv[-nc]
 #undef OPTSTR_head
-#define OPTSTR_head "?n#<0=10c#<0qv[-nc]"
+#define OPTSTR_head "?n(lines)#<0=10c(bytes)#<0qv[-nc]"
 #ifdef CLEANUP_head
 #undef CLEANUP_head
 #undef FOR_head
 #undef FLAG_v
 #undef FLAG_q
+#undef FLAG_bytes
 #undef FLAG_c
+#undef FLAG_lines
 #undef FLAG_n
 #endif
 
@@ -1795,12 +1797,13 @@
 #undef FLAG_L
 #endif
 
-// mktemp >1uqd(directory)p(tmpdir): >1uqd(directory)p(tmpdir):
+// mktemp >1uqd(directory)p(tmpdir):t >1uqd(directory)p(tmpdir):t
 #undef OPTSTR_mktemp
-#define OPTSTR_mktemp ">1uqd(directory)p(tmpdir):"
+#define OPTSTR_mktemp ">1uqd(directory)p(tmpdir):t"
 #ifdef CLEANUP_mktemp
 #undef CLEANUP_mktemp
 #undef FOR_mktemp
+#undef FLAG_t
 #undef FLAG_tmpdir
 #undef FLAG_p
 #undef FLAG_directory
@@ -4338,7 +4341,9 @@
 #endif
 #define FLAG_v (1<<0)
 #define FLAG_q (1<<1)
+#define FLAG_bytes (1<<2)
 #define FLAG_c (1<<2)
+#define FLAG_lines (1<<3)
 #define FLAG_n (1<<3)
 #endif
 
@@ -4877,12 +4882,13 @@
 #ifndef TT
 #define TT this.mktemp
 #endif
-#define FLAG_tmpdir (1<<0)
-#define FLAG_p (1<<0)
-#define FLAG_directory (1<<1)
-#define FLAG_d (1<<1)
-#define FLAG_q (1<<2)
-#define FLAG_u (1<<3)
+#define FLAG_t (1<<0)
+#define FLAG_tmpdir (1<<1)
+#define FLAG_p (1<<1)
+#define FLAG_directory (1<<2)
+#define FLAG_d (1<<2)
+#define FLAG_q (1<<3)
+#define FLAG_u (1<<4)
 #endif
 
 #ifdef FOR_modinfo
diff --git a/generated/globals.h b/generated/globals.h
index 692e2a0..9ffc771 100644
--- a/generated/globals.h
+++ b/generated/globals.h
@@ -1209,7 +1209,6 @@
     } pgrep;
   };
 
-  struct sysinfo si;
   struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
   struct dirtree *threadparent;
   unsigned width, height;
diff --git a/generated/help.h b/generated/help.h
index 0bdbce6..b13939d 100644
--- a/generated/help.h
+++ b/generated/help.h
@@ -88,7 +88,7 @@
 
 #define HELP_mount "usage: mount [-afFrsvw] [-t TYPE] [-o OPTION,] [[DEVICE] DIR]\n\nMount new filesystem(s) on directories. With no arguments, display existing\nmounts.\n\n-a	mount all entries in /etc/fstab (with -t, only entries of that TYPE)\n-O	only mount -a entries that have this option\n-f	fake it (don't actually mount)\n-r	read only (same as -o ro)\n-w	read/write (default, same as -o rw)\n-t	specify filesystem type\n-v	verbose\n\nOPTIONS is a comma separated list of options, which can also be supplied\nas --longopts.\n\nThis mount autodetects loopback mounts (a file on a directory) and\nbind mounts (file on file, directory on directory), so you don't need\nto say --bind or --loop. You can also \"mount -a /path\" to mount everything\nin /etc/fstab under /path, even if it's noauto.\n\n\n"
 
-#define HELP_mktemp "usage: mktemp [-dqu] [-p DIR] [TEMPLATE]\n\nSafely create a new file \"DIR/TEMPLATE\" and print its name.\n\n-d	Create directory instead of file (--directory)\n-p	Put new file in DIR (--tmpdir)\n-q	Quiet, no error messages\n-u	Don't create anything, just print what would be created\n\nEach X in TEMPLATE is replaced with a random printable character. The\ndefault TEMPLATE is tmp.XXXXXX, and the default DIR is $TMPDIR if set,\nelse \"/tmp\".\n\n"
+#define HELP_mktemp "usage: mktemp [-dqu] [-p DIR] [TEMPLATE]\n\nSafely create a new file \"DIR/TEMPLATE\" and print its name.\n\n-d	Create directory instead of file (--directory)\n-p	Put new file in DIR (--tmpdir)\n-q	Quiet, no error messages\n-t	Prepend $TMPDIR or /tmp if unset\n-u	Don't create anything, just print what would be created\n\nEach X in TEMPLATE is replaced with a random printable character. The\ndefault TEMPLATE is tmp.XXXXXXXXXX.\n\n"
 
 #define HELP_mknod_z "usage: mknod [-Z CONTEXT] ...\n\n-Z	Set security context to created file\n\n"
 
@@ -136,10 +136,10 @@
 
 #define HELP_which "usage: which [-a] filename ...\n\nSearch $PATH for executable files matching filename(s).\n\n-a	Show all matches\n\n"
 
-#define HELP_w "usage: w\n\nShow who is logged on and since how long they logged in.\n\n"
-
 #define HELP_watch "usage: watch [-teb] [-n SEC] PROG ARGS\n\nRun PROG every -n seconds, showing output. Hit q to quit.\n\n-n  Loop period in seconds (default 2)\n-t  Don't print header\n-e  Exit on error\n-b  Beep on command error\n-x	Exec command directly (vs \"sh -c\")\n\n"
 
+#define HELP_w "usage: w\n\nShow who is logged on and since how long they logged in.\n\n"
+
 #define HELP_vmstat "usage: vmstat [-n] [DELAY [COUNT]]\n\nPrint virtual memory statistics, repeating each DELAY seconds, COUNT times.\n(With no DELAY, prints one line. With no COUNT, repeats until killed.)\n\nShow processes running and blocked, kilobytes swapped, free, buffered, and\ncached, kilobytes swapped in and out per second, file disk blocks input and\noutput per second, interrupts and context switches per second, percent\nof CPU time spent running user code, system code, idle, and awaiting I/O.\nFirst line is since system started, later lines are since last line.\n\n-n	Display the header only once\n\n"
 
 #define HELP_vconfig "usage: vconfig COMMAND [OPTIONS]\n\nCreate and remove virtual ethernet devices\n\nadd             [interface-name] [vlan_id]\nrem             [vlan-name]\nset_flag        [interface-name] [flag-num]       [0 | 1]\nset_egress_map  [vlan-name]      [skb_priority]   [vlan_qos]\nset_ingress_map [vlan-name]      [skb_priority]   [vlan_qos]\nset_name_type   [name-type]\n\n"
@@ -318,16 +318,14 @@
 
 #define HELP_useradd "usage: useradd [-SDH] [-h DIR] [-s SHELL] [-G GRP] [-g NAME] [-u UID] USER [GROUP]\n\nCreate new user, or add USER to GROUP\n\n-D       Don't assign a password\n-g NAME  Real name\n-G GRP   Add user to existing group\n-h DIR   Home directory\n-H       Don't create home directory\n-s SHELL Login shell\n-S       Create a system user\n-u UID   User id\n\n"
 
-#define HELP_tr "usage: tr [-cds] SET1 [SET2]\n\nTranslate, squeeze, or delete characters from stdin, writing to stdout\n\n-c/-C  Take complement of SET1\n-d     Delete input characters coded SET1\n-s     Squeeze multiple output characters of SET2 into one character\n\n"
-
 #define HELP_traceroute "usage: traceroute [-46FUIldnvr] [-f 1ST_TTL] [-m MAXTTL] [-p PORT] [-q PROBES]\n[-s SRC_IP] [-t TOS] [-w WAIT_SEC] [-g GATEWAY] [-i IFACE] [-z PAUSE_MSEC] HOST [BYTES]\n\ntraceroute6 [-dnrv] [-m MAXTTL] [-p PORT] [-q PROBES][-s SRC_IP] [-t TOS] [-w WAIT_SEC]\n  [-i IFACE] HOST [BYTES]\n\nTrace the route to HOST\n\n-4,-6 Force IP or IPv6 name resolution\n-F    Set the don't fragment bit (supports IPV4 only)\n-U    Use UDP datagrams instead of ICMP ECHO (supports IPV4 only)\n-I    Use ICMP ECHO instead of UDP datagrams (supports IPV4 only)\n-l    Display the TTL value of the returned packet (supports IPV4 only)\n-d    Set SO_DEBUG options to socket\n-n    Print numeric addresses\n-v    verbose\n-r    Bypass routing tables, send directly to HOST\n-m    Max time-to-live (max number of hops)(RANGE 1 to 255)\n-p    Base UDP port number used in probes(default 33434)(RANGE 1 to 65535)\n-q    Number of probes per TTL (default 3)(RANGE 1 to 255)\n-s    IP address to use as the source address\n-t    Type-of-service in probe packets (default 0)(RANGE 0 to 255)\n-w    Time in seconds to wait for a response (default 3)(RANGE 0 to 86400)\n-g    Loose source route gateway (8 max) (supports IPV4 only)\n-z    Pause Time in milisec (default 0)(RANGE 0 to 86400) (supports IPV4 only)\n-f    Start from the 1ST_TTL hop (instead from 1)(RANGE 1 to 255) (supports IPV4 only)\n-i    Specify a network interface to operate with\n\n"
 
+#define HELP_tr "usage: tr [-cds] SET1 [SET2]\n\nTranslate, squeeze, or delete characters from stdin, writing to stdout\n\n-c/-C  Take complement of SET1\n-d     Delete input characters coded SET1\n-s     Squeeze multiple output characters of SET2 into one character\n\n"
+
 #define HELP_tftpd "usage: tftpd [-cr] [-u USER] [DIR]\n\nTransfer file from/to tftp server.\n\n-r	read only\n-c	Allow file creation via upload\n-u	run as USER\n-l	Log to syslog (inetd mode requires this)\n\n"
 
 #define HELP_tftp "usage: tftp [OPTIONS] HOST [PORT]\n\nTransfer file from/to tftp server.\n\n-l FILE Local FILE\n-r FILE Remote FILE\n-g    Get file\n-p    Put file\n-b SIZE Transfer blocks of SIZE octets(8 <= SIZE <= 65464)\n\n"
 
-#define HELP_test "usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]\n\nReturn true or false by performing tests. (With no arguments return false.)\n\n--- Tests with a single argument (after the option):\nPATH is/has:\n  -b  block device   -f  regular file   -p  fifo           -u  setuid bit\n  -c  char device    -g  setgid         -r  read bit       -w  write bit\n  -d  directory      -h  symlink        -S  socket         -x  execute bit\n  -e  exists         -L  symlink        -s  nonzero size\nSTRING is:\n  -n  nonzero size   -z  zero size      (STRING by itself implies -n)\nFD (integer file descriptor) is:\n  -t  a TTY\n\n--- Tests with one argument on each side of an operator:\nTwo strings:\n  =  are identical   !=  differ\nTwo integers:\n  -eq  equal         -gt  first > second    -lt  first < second\n  -ne  not equal     -ge  first >= second   -le  first <= second\n\n--- Modify or combine tests:\n  ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)\n  ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)\n\n"
-
 #define HELP_telnetd "Handle incoming telnet connections\n\n-l LOGIN  Exec LOGIN on connect\n-f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue\n-K Close connection as soon as login exits\n-p PORT   Port to listen on\n-b ADDR[:PORT]  Address to bind to\n-F Run in foreground\n-i Inetd mode\n-w SEC    Inetd 'wait' mode, linger time SEC\n-S Log to syslog (implied by -i or without -F and -w)\n\n"
 
 #define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server\n\n"
@@ -416,10 +414,10 @@
 
 #define HELP_dhcpd "usage: dhcpd [-46fS] [-i IFACE] [-P N] [CONFFILE]\n\n -f    Run in foreground\n -i Interface to use\n -S    Log to syslog too\n -P N  Use port N (default ipv4 67, ipv6 547)\n -4, -6    Run as a DHCPv4 or DHCPv6 server\n\n"
 
-#define HELP_dhcp "usage: dhcp [-fbnqvoCRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n            [-H HOSTNAME] [-V VENDOR] [-x OPT:VAL] [-O OPT]\n\n     Configure network dynamicaly using DHCP.\n\n   -i Interface to use (default eth0)\n   -p Create pidfile\n   -s Run PROG at DHCP events (default /usr/share/dhcp/default.script)\n   -B Request broadcast replies\n   -t Send up to N discover packets\n   -T Pause between packets (default 3 seconds)\n   -A Wait N seconds after failure (default 20)\n   -f Run in foreground\n   -b Background if lease is not obtained\n   -n Exit if lease is not obtained\n   -q Exit after obtaining lease\n   -R Release IP on exit\n   -S Log to syslog too\n   -a Use arping to validate offered address\n   -O Request option OPT from server (cumulative)\n   -o Don't request any options (unless -O is given)\n   -r Request this IP address\n   -x OPT:VAL  Include option OPT in sent packets (cumulative)\n   -F Ask server to update DNS mapping for NAME\n   -H Send NAME as client hostname (default none)\n   -V VENDOR Vendor identifier (default 'toybox VERSION')\n   -C Don't send MAC as client identifier\n   -v Verbose\n\n   Signals:\n   USR1  Renew current lease\n   USR2  Release current lease\n\n\n"
-
 #define HELP_dhcp6 "usage: dhcp6 [-fbnqvR] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n\n      Configure network dynamicaly using DHCP.\n\n    -i Interface to use (default eth0)\n    -p Create pidfile\n    -s Run PROG at DHCP events\n    -t Send up to N Solicit packets\n    -T Pause between packets (default 3 seconds)\n    -A Wait N seconds after failure (default 20)\n    -f Run in foreground\n    -b Background if lease is not obtained\n    -n Exit if lease is not obtained\n    -q Exit after obtaining lease\n    -R Release IP on exit\n    -S Log to syslog too\n    -r Request this IP address\n    -v Verbose\n\n    Signals:\n    USR1  Renew current lease\n    USR2  Release current lease\n\n"
 
+#define HELP_dhcp "usage: dhcp [-fbnqvoCRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n            [-H HOSTNAME] [-V VENDOR] [-x OPT:VAL] [-O OPT]\n\n     Configure network dynamicaly using DHCP.\n\n   -i Interface to use (default eth0)\n   -p Create pidfile\n   -s Run PROG at DHCP events (default /usr/share/dhcp/default.script)\n   -B Request broadcast replies\n   -t Send up to N discover packets\n   -T Pause between packets (default 3 seconds)\n   -A Wait N seconds after failure (default 20)\n   -f Run in foreground\n   -b Background if lease is not obtained\n   -n Exit if lease is not obtained\n   -q Exit after obtaining lease\n   -R Release IP on exit\n   -S Log to syslog too\n   -a Use arping to validate offered address\n   -O Request option OPT from server (cumulative)\n   -o Don't request any options (unless -O is given)\n   -r Request this IP address\n   -x OPT:VAL  Include option OPT in sent packets (cumulative)\n   -F Ask server to update DNS mapping for NAME\n   -H Send NAME as client hostname (default none)\n   -V VENDOR Vendor identifier (default 'toybox VERSION')\n   -C Don't send MAC as client identifier\n   -v Verbose\n\n   Signals:\n   USR1  Renew current lease\n   USR2  Release current lease\n\n\n"
+
 #define HELP_dd "usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [bs=N] [count=N] [skip=N]\n        [seek=N] [conv=notrunc|noerror|sync|fsync] [status=noxfer|none]\n\nCopy/convert files.\n\nif=FILE		Read from FILE instead of stdin\nof=FILE		Write to FILE instead of stdout\nbs=N		Read and write N bytes at a time\nibs=N		Read N bytes at a time\nobs=N		Write N bytes at a time\ncount=N		Copy only N input blocks\nskip=N		Skip N input blocks\nseek=N		Skip N output blocks\nconv=notrunc	Don't truncate output file\nconv=noerror	Continue after read errors\nconv=sync	Pad blocks with zeros\nconv=fsync	Physically write data out before finishing\nstatus=noxfer	Don't show transfer rate\nstatus=none	Don't show transfer rate or records in/out\n\nNumbers may be suffixed by c (*1), w (*2), b (*512), kD (*1000), k (*1024),\nMD (*1000*1000), M (*1024*1024), GD (*1000*1000*1000) or G (*1024*1024*1024).\n\n"
 
 #define HELP_crontab "usage: crontab [-u user] FILE\n               [-u user] [-e | -l | -r]\n               [-c dir]\n\nFiles used to schedule the execution of programs.\n\n-c crontab dir\n-e edit user's crontab\n-l list user's crontab\n-r delete user's crontab\n-u user\nFILE Replace crontab by FILE ('-': stdin)\n\n"
@@ -464,6 +462,8 @@
 
 #define HELP_time "usage: time [-pv] COMMAND [ARGS...]\n\nRun command line and report real, user, and system time elapsed in seconds.\n(real = clock on the wall, user = cpu used by command's code,\nsystem = cpu used by OS on behalf of command.)\n\n-p	posix mode (default)\n-v	verbose mode\n\n"
 
+#define HELP_test "usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]\n\nReturn true or false by performing tests. (With no arguments return false.)\n\n--- Tests with a single argument (after the option):\nPATH is/has:\n  -b  block device   -f  regular file   -p  fifo           -u  setuid bit\n  -c  char device    -g  setgid         -r  read bit       -w  write bit\n  -d  directory      -h  symlink        -S  socket         -x  execute bit\n  -e  exists         -L  symlink        -s  nonzero size\nSTRING is:\n  -n  nonzero size   -z  zero size      (STRING by itself implies -n)\nFD (integer file descriptor) is:\n  -t  a TTY\n\n--- Tests with one argument on each side of an operator:\nTwo strings:\n  =  are identical   !=  differ\nTwo integers:\n  -eq  equal         -gt  first > second    -lt  first < second\n  -ne  not equal     -ge  first >= second   -le  first <= second\n\n--- Modify or combine tests:\n  ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)\n  ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)\n\n"
+
 #define HELP_tee "usage: tee [-ai] [file...]\n\nCopy stdin to each listed file, and also to stdout.\nFilename \"-\" is a synonym for stdout.\n\n-a	append to files\n-i	ignore SIGINT\n\n"
 
 #define HELP_tail_seek "This version uses lseek, which is faster on large files.\n\n"
diff --git a/generated/newtoys.h b/generated/newtoys.h
index 9d258ea..58def1e 100644
--- a/generated/newtoys.h
+++ b/generated/newtoys.h
@@ -96,7 +96,7 @@
 USE_GUNZIP(NEWTOY(gunzip, "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GZIP(NEWTOY(gzip,     "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(OLDTOY(halt, reboot, TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_HEAD(NEWTOY(head, "?n#<0=10c#<0qv[-nc]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_HEAD(NEWTOY(head, "?n(lines)#<0=10c(bytes)#<0qv[-nc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_HELLO(NEWTOY(hello, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_HELP(NEWTOY(help, ""USE_HELP_EXTRAS("ah"), TOYFLAG_BIN))
 USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
@@ -156,7 +156,7 @@
 USE_MKNOD(NEWTOY(mknod, "<2>4m(mode):"USE_MKNOD_Z("Z:"), TOYFLAG_BIN|TOYFLAG_UMASK))
 USE_MKPASSWD(NEWTOY(mkpasswd, ">2S:m:P#=0<0", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MKSWAP(NEWTOY(mkswap, "<1>1L:", TOYFLAG_SBIN))
-USE_MKTEMP(NEWTOY(mktemp, ">1uqd(directory)p(tmpdir):", TOYFLAG_BIN))
+USE_MKTEMP(NEWTOY(mktemp, ">1uqd(directory)p(tmpdir):t", TOYFLAG_BIN))
 USE_MODINFO(NEWTOY(modinfo, "<1b:k:F:0", TOYFLAG_BIN))
 USE_MODPROBE(NEWTOY(modprobe, "alrqvsDbd*", TOYFLAG_SBIN))
 USE_MORE(NEWTOY(more, 0, TOYFLAG_USR|TOYFLAG_BIN))
@@ -248,7 +248,7 @@
 USE_TEE(NEWTOY(tee, "ia", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TELNET(NEWTOY(telnet, "<1>2", TOYFLAG_BIN))
 USE_TELNETD(NEWTOY(telnetd, "w#<0b:p#<0>65535=23f:l:FSKi[!wi]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_TEST(NEWTOY(test, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP))
 USE_TFTP(NEWTOY(tftp, "<1b#<8>65464r:l:g|p|[!gp]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TFTPD(NEWTOY(tftpd, "rcu:l", TOYFLAG_BIN))
 USE_TIME(NEWTOY(time, "<1^pv", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/lib/lib.c b/lib/lib.c
index 03f0a24..11acba0 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -1191,17 +1191,6 @@
   return 0;
 }
 
-// Posix inexplicably hasn't got this, so find str in line.
-char *strnstr(char *line, char *str)
-{
-  long len = strlen(str);
-  char *s;
-
-  for (s = line; *s; s++) if (!strncasecmp(s, str, len)) break;
-
-  return *s ? s : 0;
-}
-
 int dev_minor(int dev)
 {
   return ((dev&0xfff00000)>>12)|(dev&0xff);
diff --git a/lib/lib.h b/lib/lib.h
index e630bbc..14bb7cf 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -114,8 +114,8 @@
 // xwrap.c
 void xstrncpy(char *dest, char *src, size_t size);
 void xstrncat(char *dest, char *src, size_t size);
-void _xexit(void) noreturn;
-void xexit(void) noreturn;
+void _xexit(void) __attribute__((__noreturn__));
+void xexit(void) __attribute__((__noreturn__));
 void *xmmap(void *addr, size_t length, int prot, int flags, int fd, off_t off);
 void *xmalloc(size_t size);
 void *xzalloc(size_t size);
@@ -183,9 +183,9 @@
 void verror_msg(char *msg, int err, va_list va);
 void error_msg(char *msg, ...) printf_format;
 void perror_msg(char *msg, ...) printf_format;
-void error_exit(char *msg, ...) printf_format noreturn;
-void perror_exit(char *msg, ...) printf_format noreturn;
-void help_exit(char *msg, ...) printf_format noreturn;
+void error_exit(char *msg, ...) printf_format __attribute__((__noreturn__));
+void perror_exit(char *msg, ...) printf_format __attribute__((__noreturn__));
+void help_exit(char *msg, ...) printf_format __attribute__((__noreturn__));
 void error_msg_raw(char *msg);
 void perror_msg_raw(char *msg);
 void error_exit_raw(char *msg);
@@ -237,7 +237,6 @@
 void create_uuid(char *uuid);
 char *show_uuid(char *uuid);
 char *next_printf(char *s, char **start);
-char *strnstr(char *line, char *str);
 int dev_minor(int dev);
 int dev_major(int dev);
 int dev_makedev(int major, int minor);
diff --git a/lib/portability.c b/lib/portability.c
index 2ba3d29..b9d65ba 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -45,58 +45,6 @@
 }
 
 #if defined(__APPLE__)
-ssize_t getdelim(char **linep, size_t *np, int delim, FILE *stream)
-{
-  int ch;
-  size_t new_len;
-  ssize_t i = 0;
-  char *line, *new_line;
-
-  // Invalid input
-  if (!linep || !np) {
-    errno = EINVAL;
-    return -1;
-  }
-
-  if (*linep == NULL || *np == 0) {
-    *np = 1024;
-    *linep = calloc(1, *np);
-    if (*linep == NULL) return -1;
-  }
-  line = *linep;
-
-  while ((ch = getc(stream)) != EOF) {
-    if (i > *np) {
-      // Need more space
-      new_len = *np + 1024;
-      new_line = realloc(*linep, new_len);
-      if (!new_line) return -1;
-      *np = new_len;
-      line = *linep = new_line;
-    }
-
-    line[i++] = ch;
-    if (ch == delim) break;
-  }
-
-  if (i > *np) {
-    // Need more space
-    new_len  = i + 2;
-    new_line = realloc(*linep, new_len);
-    if (!new_line) return -1;
-    *np = new_len;
-    line = *linep = new_line;
-  }
-  line[i] = '\0';
-
-  return i > 0 ? i : -1;
-}
-
-ssize_t getline(char **linep, size_t *np, FILE *stream)
-{
-  return getdelim(linep, np, '\n', stream);
-}
-
 extern char **environ;
 
 int clearenv(void)
diff --git a/lib/portability.h b/lib/portability.h
index c2b29b6..21d0b8a 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -10,10 +10,8 @@
 // Test for gcc (using compiler builtin #define)
 
 #ifdef __GNUC__
-#define noreturn	__attribute__((noreturn))
 #define printf_format	__attribute__((format(printf, 1, 2)))
 #else
-#define noreturn
 #define printf_format
 #endif
 
@@ -22,9 +20,10 @@
 
 // This isn't in the spec, but it's how we determine what libc we're using.
 
-#include <features.h>
-
-// Types various replacement prototypes need
+// Types various replacement prototypes need.
+// This also lets us determine what libc we're using. Systems that
+// have <features.h> will transitively include it, and ones that don't --
+// macOS -- won't break.
 #include <sys/types.h>
 
 // Various constants old build environments might not have even if kernel does
@@ -89,43 +88,8 @@
 char *dirname(char *path);
 char *__xpg_basename(char *path);
 static inline char *basename(char *path) { return __xpg_basename(path); }
-
-// When building under obsolete glibc (Ubuntu 8.04-ish), hold its hand a bit.
-#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 10
-#define fstatat fstatat64
-int fstatat64(int dirfd, const char *pathname, void *buf, int flags);
-int readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz);
-char *stpcpy(char *dest, const char *src);
-#include <sys/stat.h>
-int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
-int openat(int dirfd, const char *pathname, int flags, ...);
-#include <dirent.h>
-DIR *fdopendir(int fd);
-#include <unistd.h>
-int fchownat(int dirfd, const char *pathname,
-                    uid_t owner, gid_t group, int flags);
-int isblank(int c);
-int unlinkat(int dirfd, const char *pathname, int flags);
-#include <stdio.h>
-ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);
-
-// Straight from posix-2008, things old glibc had but didn't prototype
-
-int faccessat(int fd, const char *path, int amode, int flag);
-int linkat(int fd1, const char *path1, int fd2, const char *path2, int flag);
-int mkdirat(int fd, const char *path, mode_t mode);
-int symlinkat(const char *path1, int fd, const char *path2);
-int mknodat(int fd, const char *path, mode_t mode, dev_t dev);
-#include <sys/time.h>
-int futimens(int fd, const struct timespec times[2]);
-int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
-
-#ifndef MNT_DETACH
-#define MNT_DETACH 2
-#endif
-#endif // Old glibc
-
-#endif // glibc in general
+char *strcasestr(const char *haystack, const char *needle);
+#endif // defined(glibc)
 
 #if !defined(__GLIBC__)
 // POSIX basename.
@@ -134,20 +98,28 @@
 
 // Work out how to do endianness
 
-#ifndef __APPLE__
-#include <byteswap.h>
-#include <endian.h>
+#ifdef __APPLE__
 
-#if __BYTE_ORDER == __BIG_ENDIAN
+#include <libkern/OSByteOrder.h>
+
+#ifdef __BIG_ENDIAN__
 #define IS_BIG_ENDIAN 1
 #else
 #define IS_BIG_ENDIAN 0
 #endif
 
+#define bswap_16(x) OSSwapInt16(x)
+#define bswap_32(x) OSSwapInt32(x)
+#define bswap_64(x) OSSwapInt64(x)
+
 int clearenv(void);
+
 #else
 
-#ifdef __BIG_ENDIAN__
+#include <byteswap.h>
+#include <endian.h>
+
+#if __BYTE_ORDER == __BIG_ENDIAN
 #define IS_BIG_ENDIAN 1
 #else
 #define IS_BIG_ENDIAN 0
@@ -173,15 +145,19 @@
 #define SWAP_LE64(x) (x)
 #endif
 
-#if defined(__APPLE__) \
-    || (defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 10)
-ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);
-ssize_t getline(char **lineptr, size_t *n, FILE *stream);
-#endif
-
 // Linux headers not listed by POSIX or LSB
 #include <sys/mount.h>
+#ifdef __linux__
+#include <sys/statfs.h>
 #include <sys/swap.h>
+#include <sys/sysinfo.h>
+#endif
+
+#ifdef __APPLE__
+#include <util.h>
+#else
+#include <pty.h>
+#endif
 
 // Android is missing some headers and functions
 // "generated/config.h" is included first
diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh
index 533df60..3887b07 100755
--- a/scripts/genconfig.sh
+++ b/scripts/genconfig.sh
@@ -144,7 +144,7 @@
 toys()
 {
   grep 'TOY(.*)' "$@" | grep -v TOYFLAG_NOFORK | grep -v "0))" | \
-    sed -rn 's/([^:]*):.*(OLD|NEW)TOY\( *([a-zA-Z][^,]*) *,.*/\1:\3/p'
+    sed -En 's/([^:]*):.*(OLD|NEW)TOY\( *([a-zA-Z][^,]*) *,.*/\1:\3/p'
 }
 
 WORKING=
diff --git a/scripts/make.sh b/scripts/make.sh
index 306a7cd..640dcbe 100755
--- a/scripts/make.sh
+++ b/scripts/make.sh
@@ -115,7 +115,7 @@
   for i in util crypt m resolv selinux smack attr rt crypto z log
   do
     echo "int main(int argc, char *argv[]) {return 0;}" | \
-    ${CROSS_COMPILE}${CC} $CFLAGS $LDFLAGS -xc - -o generated/libprobe -Wl,--as-needed -l$i > /dev/null 2>/dev/null &&
+    ${CROSS_COMPILE}${CC} $CFLAGS $LDFLAGS -xc - -o generated/libprobe $LDASNEEDED -l$i > /dev/null 2>/dev/null &&
     echo -l$i >> generated/optlibs.dat
     echo -n .
   done
@@ -125,7 +125,7 @@
 
 # LINK needs optlibs.dat, above
 
-LINK="$(echo $LDOPTIMIZE $LDFLAGS -o "$UNSTRIPPED" -Wl,--as-needed $(cat generated/optlibs.dat))"
+LINK="$(echo $LDOPTIMIZE $LDFLAGS -o "$UNSTRIPPED" $LDASNEEDED $(cat generated/optlibs.dat))"
 genbuildsh > generated/build.sh && chmod +x generated/build.sh || exit 1
 
 #TODO: "make $SED && make" doesn't regenerate config.h because diff .config
diff --git a/scripts/runtest.sh b/scripts/runtest.sh
deleted file mode 100644
index 016658a..0000000
--- a/scripts/runtest.sh
+++ /dev/null
@@ -1,192 +0,0 @@
-# Simple test harness infrastructure
-#
-# Copyright 2005 by Rob Landley
-
-# This file defines two main functions, "testcmd" and "optional". The
-# first performs a test, the second enables/disables tests based on
-# configuration options.
-
-# The following environment variables enable optional behavior in "testing":
-#    DEBUG - Show every command run by test script.
-#    VERBOSE - Print the diff -u of each failed test case.
-#              If equal to "fail", stop after first failed test.
-#
-# The "testcmd" function takes five arguments:
-#	$1) Description to display when running command
-#	$2) Command line arguments to command
-#	$3) Expected result (on stdout)
-#	$4) Data written to file "input"
-#	$5) Data written to stdin
-#
-# The "testing" function is like testcmd but takes a complete command line
-# (I.E. you have to include the command name.) The variable $C is an absolute
-# path to the command being tested, which can bypass shell builtins.
-#
-# The exit value of testcmd is the exit value of the command it ran.
-#
-# The environment variable "FAILCOUNT" contains a cumulative total of the
-# number of failed tests.
-#
-# The "optional" function is used to skip certain tests (by setting the
-# environment variable SKIP), ala:
-#   optional CFG_THINGY
-#
-# The "optional" function checks the environment variable "OPTIONFLAGS",
-# which is either empty (in which case it always clears SKIP) or
-# else contains a colon-separated list of features (in which case the function
-# clears SKIP if the flag was found, or sets it to 1 if the flag was not found).
-
-export FAILCOUNT=0
-export SKIP=
-
-# Helper functions
-
-# Check config to see if option is enabled, set SKIP if not.
-
-SHOWPASS=PASS
-SHOWFAIL=FAIL
-SHOWSKIP=SKIP
-
-if tty -s <&1
-then
-  SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")"
-  SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")"
-  SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")"
-fi
-
-optional()
-{
-  option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"`
-  # Not set?
-  if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
-  then
-    SKIP=""
-    return
-  fi
-  SKIP=1
-}
-
-wrong_args()
-{
-  if [ $# -ne 5 ]
-  then
-    echo "Test $NAME has the wrong number of arguments ($# $*)" >&2
-    exit
-  fi
-}
-
-# The testing function
-
-testing()
-{
-  NAME="$CMDNAME $1"
-  wrong_args "$@"
-
-  [ -z "$1" ] && NAME=$2
-
-  [ -n "$DEBUG" ] && set -x
-
-  if [ -n "$SKIP" ] || ( [ -n "$SKIP_HOST" ] && [ -n "$TEST_HOST" ])
-  then
-    [ ! -z "$VERBOSE" ] && echo "$SHOWSKIP: $NAME"
-    return 0
-  fi
-
-  echo -ne "$3" > expected
-  echo -ne "$4" > input
-  echo -ne "$5" | ${EVAL:-eval} -- "$2" > actual
-  RETVAL=$?
-
-  # Catch segfaults
-  [ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] &&
-    echo "exited with signal (or returned $RETVAL)" >> actual
-
-  DIFF="$(diff -au${NOSPACE:+b} expected actual)"
-  if [ ! -z "$DIFF" ]
-  then
-    FAILCOUNT=$[$FAILCOUNT+1]
-    echo "$SHOWFAIL: $NAME"
-    if [ -n "$VERBOSE" ]
-    then
-      [ ! -z "$4" ] && echo "echo -ne \"$4\" > input"
-      echo "echo -ne '$5' |$EVAL $2"
-      echo "$DIFF"
-      [ "$VERBOSE" == fail ] && exit 1
-    fi
-  else
-    echo "$SHOWPASS: $NAME"
-  fi
-  rm -f input expected actual
-
-  [ -n "$DEBUG" ] && set +x
-
-  return 0
-}
-
-testcmd()
-{
-  wrong_args "$@"
-
-  testing "$1" "$C $2" "$3" "$4" "$5"
-}
-
-# Recursively grab an executable and all the libraries needed to run it.
-# Source paths beginning with / will be copied into destpath, otherwise
-# the file is assumed to already be there and only its library dependencies
-# are copied.
-
-mkchroot()
-{
-  [ $# -lt 2 ] && return
-
-  echo -n .
-
-  dest=$1
-  shift
-  for i in "$@"
-  do
-    [ "${i:0:1}" == "/" ] || i=$(which $i)
-    [ -f "$dest/$i" ] && continue
-    if [ -e "$i" ]
-    then
-      d=`echo "$i" | grep -o '.*/'` &&
-      mkdir -p "$dest/$d" &&
-      cat "$i" > "$dest/$i" &&
-      chmod +x "$dest/$i"
-    else
-      echo "Not found: $i"
-    fi
-    mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ')
-  done
-}
-
-# Set up a chroot environment and run commands within it.
-# Needed commands listed on command line
-# Script fed to stdin.
-
-dochroot()
-{
-  mkdir tmpdir4chroot
-  mount -t ramfs tmpdir4chroot tmpdir4chroot
-  mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev}
-  cp -L testing.sh tmpdir4chroot
-
-  # Copy utilities from command line arguments
-
-  echo -n "Setup chroot"
-  mkchroot tmpdir4chroot $*
-  echo
-
-  mknod tmpdir4chroot/dev/tty c 5 0
-  mknod tmpdir4chroot/dev/null c 1 3
-  mknod tmpdir4chroot/dev/zero c 1 5
-
-  # Copy script from stdin
-
-  cat > tmpdir4chroot/test.sh
-  chmod +x tmpdir4chroot/test.sh
-  chroot tmpdir4chroot /test.sh
-  umount -l tmpdir4chroot
-  rmdir tmpdir4chroot
-}
-
diff --git a/scripts/test.sh b/scripts/test.sh
index 1bf225a..f7ad4a1 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -1,5 +1,199 @@
 #!/bin/bash
 
+# Simple test harness infrastructure
+#
+# Copyright 2005 by Rob Landley
+
+# This file defines two main functions, "testcmd" and "optional". The
+# first performs a test, the second enables/disables tests based on
+# configuration options.
+
+# The following environment variables enable optional behavior in "testing":
+#    DEBUG - Show every command run by test script.
+#    VERBOSE - Print the diff -u of each failed test case.
+#              If equal to "fail", stop after first failed test.
+#
+# The "testcmd" function takes five arguments:
+#	$1) Description to display when running command
+#	$2) Command line arguments to command
+#	$3) Expected result (on stdout)
+#	$4) Data written to file "input"
+#	$5) Data written to stdin
+#
+# The "testing" function is like testcmd but takes a complete command line
+# (I.E. you have to include the command name.) The variable $C is an absolute
+# path to the command being tested, which can bypass shell builtins.
+#
+# The exit value of testcmd is the exit value of the command it ran.
+#
+# The environment variable "FAILCOUNT" contains a cumulative total of the
+# number of failed tests.
+#
+# The "optional" function is used to skip certain tests (by setting the
+# environment variable SKIP), ala:
+#   optional CFG_THINGY
+#
+# The "optional" function checks the environment variable "OPTIONFLAGS",
+# which is either empty (in which case it always clears SKIP) or
+# else contains a colon-separated list of features (in which case the function
+# clears SKIP if the flag was found, or sets it to 1 if the flag was not found).
+
+export FAILCOUNT=0
+export SKIP=
+
+# Helper functions
+
+# Check config to see if option is enabled, set SKIP if not.
+
+SHOWPASS=PASS
+SHOWFAIL=FAIL
+SHOWSKIP=SKIP
+
+if tty -s <&1
+then
+  SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")"
+  SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")"
+  SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")"
+fi
+
+optional()
+{
+  option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"`
+  # Not set?
+  if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
+  then
+    SKIP=""
+    return
+  fi
+  SKIP=1
+}
+
+wrong_args()
+{
+  if [ $# -ne 5 ]
+  then
+    echo "Test $NAME has the wrong number of arguments ($# $*)" >&2
+    exit
+  fi
+}
+
+# The testing function
+
+testing()
+{
+  NAME="$CMDNAME $1"
+  wrong_args "$@"
+
+  [ -z "$1" ] && NAME=$2
+
+  [ -n "$DEBUG" ] && set -x
+
+  if [ -n "$SKIP" ] || ( [ -n "$SKIP_HOST" ] && [ -n "$TEST_HOST" ])
+  then
+    [ ! -z "$VERBOSE" ] && echo "$SHOWSKIP: $NAME"
+    return 0
+  fi
+
+  echo -ne "$3" > expected
+  echo -ne "$4" > input
+  echo -ne "$5" | ${EVAL:-eval} -- "$2" > actual
+  RETVAL=$?
+
+  # Catch segfaults
+  [ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] &&
+    echo "exited with signal (or returned $RETVAL)" >> actual
+
+  DIFF="$(diff -au${NOSPACE:+b} expected actual)"
+  if [ ! -z "$DIFF" ]
+  then
+    FAILCOUNT=$[$FAILCOUNT+1]
+    echo "$SHOWFAIL: $NAME"
+    if [ -n "$VERBOSE" ]
+    then
+      [ ! -z "$4" ] && echo "echo -ne \"$4\" > input"
+      echo "echo -ne '$5' |$EVAL $2"
+      echo "$DIFF"
+      [ "$VERBOSE" == fail ] && exit 1
+    fi
+  else
+    echo "$SHOWPASS: $NAME"
+  fi
+  rm -f input expected actual
+
+  [ -n "$DEBUG" ] && set +x
+
+  return 0
+}
+
+testcmd()
+{
+  wrong_args "$@"
+
+  X="$1"
+  [ -z "$X" ] && X="$CMDNAME $2"
+  testing "$X" "$C $2" "$3" "$4" "$5"
+}
+
+# Recursively grab an executable and all the libraries needed to run it.
+# Source paths beginning with / will be copied into destpath, otherwise
+# the file is assumed to already be there and only its library dependencies
+# are copied.
+
+mkchroot()
+{
+  [ $# -lt 2 ] && return
+
+  echo -n .
+
+  dest=$1
+  shift
+  for i in "$@"
+  do
+    [ "${i:0:1}" == "/" ] || i=$(which $i)
+    [ -f "$dest/$i" ] && continue
+    if [ -e "$i" ]
+    then
+      d=`echo "$i" | grep -o '.*/'` &&
+      mkdir -p "$dest/$d" &&
+      cat "$i" > "$dest/$i" &&
+      chmod +x "$dest/$i"
+    else
+      echo "Not found: $i"
+    fi
+    mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ')
+  done
+}
+
+# Set up a chroot environment and run commands within it.
+# Needed commands listed on command line
+# Script fed to stdin.
+
+dochroot()
+{
+  mkdir tmpdir4chroot
+  mount -t ramfs tmpdir4chroot tmpdir4chroot
+  mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev}
+  cp -L testing.sh tmpdir4chroot
+
+  # Copy utilities from command line arguments
+
+  echo -n "Setup chroot"
+  mkchroot tmpdir4chroot $*
+  echo
+
+  mknod tmpdir4chroot/dev/tty c 5 0
+  mknod tmpdir4chroot/dev/null c 1 3
+  mknod tmpdir4chroot/dev/zero c 1 5
+
+  # Copy script from stdin
+
+  cat > tmpdir4chroot/test.sh
+  chmod +x tmpdir4chroot/test.sh
+  chroot tmpdir4chroot /test.sh
+  umount -l tmpdir4chroot
+  rmdir tmpdir4chroot
+}
+
 TOPDIR="$PWD"
 FILES="$PWD"/tests/files
 
@@ -23,8 +217,8 @@
 cd testdir
 export LC_COLLATE=C
 
-. "$TOPDIR"/scripts/runtest.sh
-[ -f "$TOPDIR/generated/config.h" ] && export OPTIONFLAGS=:$(echo $(sed -nr 's/^#define CFG_(.*) 1/\1/p' "$TOPDIR/generated/config.h") | sed 's/ /:/g')
+[ -f "$TOPDIR/generated/config.h" ] &&
+  export OPTIONFLAGS=:$(echo $(sed -nr 's/^#define CFG_(.*) 1/\1/p' "$TOPDIR/generated/config.h") | sed 's/ /:/g')
 
 do_test()
 {
diff --git a/tests/README.txt b/tests/README.txt
new file mode 100644
index 0000000..15d2ea2
--- /dev/null
+++ b/tests/README.txt
@@ -0,0 +1,8 @@
+The build infrastructure adds a "make test_NAME" target for each NAME.test
+file in this directory, and "make tests" iterates through all of them.
+
+Individual tests boil down to a call to "scripts/test.sh NAME", and
+testing all is "scripts/test.sh" with no arguments.
+
+The test infrastructure, including the shell functions each test calls
+(mostly "testcmd" and "optional") is described in scripts/test.sh
diff --git a/tests/mktemp.test b/tests/mktemp.test
new file mode 100755
index 0000000..d09d2c4
--- /dev/null
+++ b/tests/mktemp.test
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+# mktemp by default should use tmp.XXXXXXXXXX as the template,
+# and $TMPDIR as the directory.
+testing "mktemp" "TMPDIR=/t mktemp -u | grep -q '^/t/tmp\...........$' && echo yes" "yes\n" "" ""
+
+# mktemp with a template should *not* use $TMPDIR.
+testing "mktemp TEMPLATE" "TMPDIR=/t mktemp -u hello.XXXXXXXX | grep -q '^hello\.........$' && echo yes" "yes\n" "" ""
+
+# mktemp with -t and a template should use $TMPDIR.
+testing "mktemp -t TEMPLATE" "TMPDIR=/t mktemp -u -t hello.XXXXXXXX | grep -q '^/t/hello\.........$' && echo yes" "yes\n" "" ""
+
+# mktemp with -p DIR and a template should use DIR.
+testing "mktemp -p DIR TEMPLATE" "TMPDIR=/t mktemp -u -p DIR hello.XXXXXXXX | grep -q '^DIR/hello\.........$' && echo yes" "yes\n" "" ""
+
+# mktemp -p DIR and -t: -t wins.
+testing "mktemp -p DIR -t TEMPLATE" "TMPDIR=/t mktemp -u -p DIR -t hello.XXXXXXXX | grep -q '^/t/hello\.........$' && echo yes" "yes\n" "" ""
diff --git a/tests/test.test b/tests/test.test
old mode 100755
new mode 100644
index 40a9086..7f574f0
--- a/tests/test.test
+++ b/tests/test.test
@@ -4,6 +4,13 @@
 
 #testing "name" "command" "result" "infile" "stdin"
 
+testcmd '0 args' '; echo $?'  '1\n' '' ''
+testcmd '1 arg' '== ; echo $?' '0\n' '' ''
+testcmd '2 args' '-e == ; echo $?' '1\n' '' ''
+testcmd '3 args' '-e == -e ; echo $?' '0\n' '' ''
+testcmd '' '\( == \) ; echo $?' '1\n' '' ''
+testcmd '' '\( == \( ; echo $?' '0\n' '' ''
+
 # TODO: Should also have device and socket files
 
 mkdir d
@@ -14,15 +21,10 @@
 
 type_test()
 {
-  result=""
   for i in d f L s p n
   do
-    if test $* $i
-    then
-      result=$result$i
-    fi
+    "$C" $* $i && echo -n $i
   done
-  printf "%s" $result
 }
 
 testing "-b" "type_test -b" "" "" ""
@@ -35,33 +37,52 @@
 testing "-S" "type_test -S" "" "" ""
 testing "-p" "type_test -p" "p" "" ""
 testing "-e" "type_test -e" "dfLsp" "" ""
-testing "! -e" "type_test ! -e" "n" "" ""
+testing "! -e" 'type_test ! -e' "n" "" ""
 
 rm f L s p
 rmdir d
 
 # TODO: Test rwx gu t
 
-testing "test" "test "" || test a && echo yes" "yes\n" "" ""
-testing "-n" "test -n "" || test -n a && echo yes" "yes\n" "" ""
-testing "-z" "test -n a || test -n "" && echo yes" "yes\n" "" ""
-testing "a = b" "test a = b || test "" = "" && echo yes" "yes\n" "" ""
-testing "a != b" "test "" != "" || test a = b && echo yes" "yes\n" "" ""
+testcmd "" "'' || echo yes" "yes\n" "" ""
+testcmd "" "a && echo yes" "yes\n" "" ""
+testcmd "-n" "-n '' || echo yes" "yes\n" "" ""
+testcmd "-n2" "-n a && echo yes" "yes\n" "" ""
+testcmd "-z" "-z '' && echo yes" "yes\n" "" ""
+testcmd "-z2" "-z a || echo yes" "yes\n" "" ""
+testcmd "" "a = b || echo yes" "yes\n" "" ""
+testcmd "" "'' = '' && echo yes" "yes\n" "" ""
+testcmd "a != b" "a != b && echo yes" "yes\n" "" ""
+testcmd "a != b" "a != a || echo yes" "yes\n" "" ""
 
 arith_test()
 {
-  test -1 $1 1 && echo l
-  test 0 $1 0 && echo e
-  test -3 $1 -5 && echo g
+  $C -1 $1 1 && echo -n l
+  $C 0 $1 0 && echo -n e
+  $C -3 $1 -5 && echo -n g
 }
 
-testing "-eq" "arith_test -eq" "e\n" "" ""
-testing "-ne" "arith_test -ne" "l\ng\n" "" ""
-testing "-gt" "arith_test -gt" "g\n" "" ""
-testing "-ge" "arith_test -ge" "e\ng\n" "" ""
-testing "-lt" "arith_test -lt" "l\n" "" ""
-testing "-le" "arith_test -le" "l\ne\n" "" ""
+testing "-eq" "arith_test -eq" "e" "" ""
+testing "-ne" "arith_test -ne" "lg" "" ""
+testing "-gt" "arith_test -gt" "g" "" ""
+testing "-ge" "arith_test -ge" "eg" "" ""
+testing "-lt" "arith_test -lt" "l" "" ""
+testing "-le" "arith_test -le" "le" "" ""
 
 # test ! = -o a
 # test ! \( = -o a \)
 # test \( ! = \) -o a
+# test \( \)
+
+#testing "" "[ -a -eq -a ] && echo yes" "yes\n" "" ""
+
+# -e == -a
+# -e == -a -o -d != -o
+# \( "x" \) -a \) == \)
+# \( ! ! ! -e \) \)
+
+#  // () -a (() -a () -o ()) -o ()
+#  // x -a ( x -o x ) -a x
+#  // x -o ( x -a x ) -a x -o x
+
+# trailing ! and (
diff --git a/toys.h b/toys.h
index 0e8d468..5e71e3f 100644
--- a/toys.h
+++ b/toys.h
@@ -62,10 +62,7 @@
 #include <wctype.h>
 
 // LSB 4.1 headers
-#include <pty.h>
 #include <sys/ioctl.h>
-#include <sys/statfs.h>
-#include <sys/sysinfo.h>
 
 #include "lib/lib.h"
 #include "lib/lsm.h"
diff --git a/toys/lsb/mktemp.c b/toys/lsb/mktemp.c
index 21bb9b3..ae97e49 100644
--- a/toys/lsb/mktemp.c
+++ b/toys/lsb/mktemp.c
@@ -4,7 +4,7 @@
  *
  * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/mktemp.html
 
-USE_MKTEMP(NEWTOY(mktemp, ">1uqd(directory)p(tmpdir):", TOYFLAG_BIN))
+USE_MKTEMP(NEWTOY(mktemp, ">1uqd(directory)p(tmpdir):t", TOYFLAG_BIN))
 
 config MKTEMP
   bool "mktemp"
@@ -17,11 +17,11 @@
     -d	Create directory instead of file (--directory)
     -p	Put new file in DIR (--tmpdir)
     -q	Quiet, no error messages
+    -t	Prepend $TMPDIR or /tmp if unset
     -u	Don't create anything, just print what would be created
 
     Each X in TEMPLATE is replaced with a random printable character. The
-    default TEMPLATE is tmp.XXXXXX, and the default DIR is $TMPDIR if set,
-    else "/tmp".
+    default TEMPLATE is tmp.XXXXXXXXXX.
 */
 
 #define FOR_mktemp
@@ -33,24 +33,26 @@
 
 void mktemp_main(void)
 {
-  int d_flag = toys.optflags & FLAG_d;
   char *template = *toys.optargs;
 
-  if (!template) template = "tmp.XXXXXX";
+  if (!template) {
+    toys.optflags |= FLAG_t;
+    template = "tmp.XXXXXXXXXX";
+  }
 
-  if (!TT.p) TT.p = getenv("TMPDIR");
+  if (!TT.p || (toys.optflags & FLAG_t)) TT.p = getenv("TMPDIR");
   if (!TT.p || !*TT.p) TT.p = "/tmp";
 
-  template = strchr(template, '/') ? xstrdup(template)
-             : xmprintf("%s/%s", TT.p, template);
+  // TODO: coreutils cleans paths, so -p /t/// would result in /t/xxx...
+  template = (strchr(template, '/') || !(toys.optflags & (FLAG_p|FLAG_t)))
+      ? xstrdup(template) : xmprintf("%s/%s", TT.p, template);
 
-  if (d_flag ? !mkdtemp(template) : mkstemp(template) == -1) {
+  if (toys.optflags & FLAG_u) {
+    xputs(mktemp(template));
+  } else if (toys.optflags & FLAG_d ? !mkdtemp(template) : mkstemp(template) == -1) {
     if (toys.optflags & FLAG_q) toys.exitval = 1;
     else perror_exit("Failed to create %s %s/%s",
-                     d_flag ? "directory" : "file", TT.p, template);
-  } else {
-    if (toys.optflags & FLAG_u) unlink(template);
-    xputs(template);
+        toys.optflags & FLAG_d ? "directory" : "file", TT.p, template);
   }
 
   if (CFG_TOYBOX_FREE) free(template);
diff --git a/toys/lsb/passwd.c b/toys/lsb/passwd.c
index 0f51c0c..7302483 100644
--- a/toys/lsb/passwd.c
+++ b/toys/lsb/passwd.c
@@ -46,8 +46,8 @@
 
   if (strlen(new) < 6) msg = "too short";
   if (*new) {
-    if (strnstr(new, user) || strnstr(user, new)) msg = "user";
-    if (*old && (strnstr(new, old) || strnstr(old, new))) msg = "old";
+    if (strcasestr(new, user) || strcasestr(user, new)) msg = "user";
+    if (*old && (strcasestr(new, old) || strcasestr(old, new))) msg = "old";
   }
   if (msg) xprintf("BAD PASSWORD: %s\n",msg);
 }
diff --git a/toys/pending/mdev.c b/toys/pending/mdev.c
index 0493f1a..d0bb068 100644
--- a/toys/pending/mdev.c
+++ b/toys/pending/mdev.c
@@ -280,7 +280,8 @@
   // Circa 2.6.25 the entries more than 2 deep are all either redundant
   // (mouse#, event#) or unnamed (every usb_* entry is called "device").
 
-  return (node->parent && node->parent->parent) ? 0 : DIRTREE_RECURSE;
+  if (node->parent && node->parent->parent) return 0;
+  return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW;
 }
 
 void mdev_main(void)
diff --git a/toys/pending/test.c b/toys/pending/test.c
deleted file mode 100644
index d887fc9..0000000
--- a/toys/pending/test.c
+++ /dev/null
@@ -1,217 +0,0 @@
-/* test.c - evaluate expression
- *
- * Copyright 2013 Rob Landley <rob@landley.net>
- *
- * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
-
-USE_TEST(NEWTOY(test, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-
-config TEST
-  bool "test"
-  default n
-  help
-    usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
-
-    Return true or false by performing tests. (With no arguments return false.)
-
-    --- Tests with a single argument (after the option):
-    PATH is/has:
-      -b  block device   -f  regular file   -p  fifo           -u  setuid bit
-      -c  char device    -g  setgid         -r  read bit       -w  write bit
-      -d  directory      -h  symlink        -S  socket         -x  execute bit
-      -e  exists         -L  symlink        -s  nonzero size
-    STRING is:
-      -n  nonzero size   -z  zero size      (STRING by itself implies -n)
-    FD (integer file descriptor) is:
-      -t  a TTY
-
-    --- Tests with one argument on each side of an operator:
-    Two strings:
-      =  are identical   !=  differ
-    Two integers:
-      -eq  equal         -gt  first > second    -lt  first < second
-      -ne  not equal     -ge  first >= second   -le  first <= second
-
-    --- Modify or combine tests:
-      ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)
-      ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)
-*/
-
-#include "toys.h"
-
-int get_stat(struct stat *st, int *link, char* s)
-{
-  if (lstat(s, st) == -1) return 0;
-  *link = S_ISLNK(st->st_mode);
-  if (*link && (stat(s, st) == -1)) return 0;
-  return 1;
-}
-
-// basic expression without !, -o, -a, (
-int test_basic(int optb, int opte)
-{
-  int id, val;
-  char *s, *err_fmt = "Bad flag '%s'", *err_int = "Bad integer '%s'";
-
-  if (optb == opte) val = 0;
-  else if (optb + 1 == opte) val = !!*toys.optargs[optb];
-  else if (optb + 2 == opte && toys.optargs[optb][0] == '-') {
-    char c = toys.optargs[optb][1];
-    struct stat st;
-    int link;
-
-    if (!c || toys.optargs[optb][2]) error_exit(err_fmt, toys.optargs[optb]);
-    s = toys.optargs[optb + 1];
-    if (c == 'b') val = get_stat(&st, &link, s) && S_ISBLK(st.st_mode);
-    else if (c == 'c') val = get_stat(&st, &link, s) && S_ISCHR(st.st_mode);
-    else if (c == 'd') val = get_stat(&st, &link, s) && S_ISDIR(st.st_mode);
-    else if (c == 'e') val = get_stat(&st, &link, s);
-    else if (c == 'f') val = get_stat(&st, &link, s) && S_ISREG(st.st_mode);
-    else if (c == 'g') val = get_stat(&st, &link, s) && (st.st_mode & S_ISGID);
-    else if (c == 'h' || c == 'L') val = get_stat(&st, &link, s) && link;
-    else if (c == 'p') val = get_stat(&st, &link, s) && S_ISFIFO(st.st_mode);
-    else if (c == 'S') val = get_stat(&st, &link, s) && S_ISSOCK(st.st_mode);
-    else if (c == 's') val = get_stat(&st, &link, s) && st.st_size != 0;
-    else if (c == 'u') val = get_stat(&st, &link, s) && (st.st_mode & S_ISUID);
-    else if (c == 'r') val = access(s, R_OK) != -1;
-    else if (c == 'w') val = access(s, W_OK) != -1;
-    else if (c == 'x') val = access(s, X_OK) != -1;
-    else if (c == 'z') val = !*s;
-    else if (c == 'n') val = !!*s;
-    else if (c == 't') {
-      struct termios termios;
-      val = tcgetattr(atoi(s), &termios) != -1;
-    }
-    else error_exit(err_fmt, toys.optargs[optb]);
-  }
-  else if (optb + 3 == opte) {
-    if (*toys.optargs[optb + 1] == '-') {
-      char *end_a, *end_b;
-      long a, b;
-      int errno_a, errno_b;
-
-      errno = 0;
-      a = strtol(toys.optargs[optb], &end_a, 10);
-      errno_a = errno;
-      b = strtol(toys.optargs[optb + 2], &end_b, 10);
-      errno_b = errno;
-      s = toys.optargs[optb + 1] + 1;
-      if (!strcmp("eq", s)) val = a == b;
-      else if (!strcmp("ne", s)) val = a != b;
-      else if (!strcmp("gt", s)) val = a > b;
-      else if (!strcmp("ge", s)) val = a >= b;
-      else if (!strcmp("lt", s)) val = a < b;
-      else if (!strcmp("le", s)) val = a <= b;
-      else error_exit(err_fmt, toys.optargs[optb + 1]);
-      if (!*toys.optargs[optb] || *end_a || errno_a)
-        error_exit(err_int, toys.optargs[optb]);
-      if (!*toys.optargs[optb + 2] || *end_b || errno_b)
-        error_exit(err_int, toys.optargs[optb + 2]);
-    }
-    else {
-      int result = strcmp(toys.optargs[optb], toys.optargs[optb + 2]);
-
-      s = toys.optargs[optb + 1];
-      if (!strcmp("=", s)) val = !result;
-      else if (!strcmp("!=", s)) val = !!result;
-      else error_exit(err_fmt, toys.optargs[optb + 1]);
-    }
-  }
-  else error_exit("Syntax error");
-
-  return val;
-}
-
-int test_sub(int optb, int opte)
-{
-  int not, and = 1, or = 0, i, expr;
-  char *err_syntax = "Syntax error";
-
-  for (;;) {
-    not = 0;
-    while (optb < opte && !strcmp("!", toys.optargs[optb])) {
-      not = !not;
-      optb++;
-    }
-    if (optb < opte && !strcmp("(", toys.optargs[optb])) {
-      int par = 1;
-
-      for (i = optb + 1; par && i < opte; i++) {
-        if (!strcmp(")", toys.optargs[i])) par--;
-        else if (!strcmp("(", toys.optargs[i])) par++;
-      }
-      if (par) error_exit("Missing ')'");
-      expr = not ^ test_sub(optb + 1, i - 1);
-      optb = i;
-    }
-    else {
-      for (i = 0; i < 4; ++i) {
-        if (optb + i == opte || !strcmp("-a", toys.optargs[optb + i])
-            || !strcmp("-o", toys.optargs[optb + i])) break;
-      }
-      if (i == 4) error_exit_raw(err_syntax);
-      expr = not ^ test_basic(optb, optb + i);
-      optb += i;
-    }
-
-    if (optb == opte) {
-      return or || (and && expr);
-    }
-    else if (!strcmp("-o", toys.optargs[optb])) {
-      or = or || (and && expr);
-      and = 1;
-      optb++;
-    }
-    else if (!strcmp("-a", toys.optargs[optb])) {
-      and = and && expr;
-      optb++;
-    }
-    else error_exit_raw(err_syntax);
-  }
-}
-
-int test_few_args(int optb, int opte)
-{
-  if (optb == opte) return 0;
-  else if (optb + 1 == opte) return !!*toys.optargs[optb];
-  else if (optb + 2 == opte) {
-    if (!strcmp("!", toys.optargs[optb])) return !*toys.optargs[optb + 1];
-    else if (toys.optargs[optb][0] == '-' &&
-             stridx("bcdefghLpSsurwxznt", toys.optargs[optb][1]) != -1 &&
-             !toys.optargs[optb][2]) return test_basic(optb, opte);
-  }
-  else if (optb + 3 == opte) {
-    if (!strcmp("-eq", toys.optargs[optb + 1]) ||
-        !strcmp("-ne", toys.optargs[optb + 1]) ||
-        !strcmp("-gt", toys.optargs[optb + 1]) ||
-        !strcmp("-ge", toys.optargs[optb + 1]) ||
-        !strcmp("-lt", toys.optargs[optb + 1]) ||
-        !strcmp("-le", toys.optargs[optb + 1]) ||
-        !strcmp("=", toys.optargs[optb + 1]) ||
-        !strcmp("!=", toys.optargs[optb + 1])) return test_basic(optb, opte);
-    else if (!strcmp("!", toys.optargs[optb]))
-      return !test_few_args(optb + 1, opte);
-    else if (!strcmp("(", toys.optargs[optb]) &&
-             !strcmp(")", toys.optargs[optb + 2]))
-      return !!*toys.optargs[optb + 1];
-  }
-  else {
-    if (!strcmp("!", toys.optargs[optb])) return !test_few_args(optb + 1, opte);
-    else if (!strcmp("(", toys.optargs[optb]) &&
-             !strcmp(")", toys.optargs[optb + 3]))
-      return test_few_args(optb + 1, opte - 1);
-  }
-  return test_sub(optb, opte);
-}
-
-void test_main(void)
-{
-  int optc = toys.optc;
-
-  toys.exitval = 2;
-  if (!strcmp("[", toys.which->name))
-    if (!optc || strcmp("]", toys.optargs[--optc])) error_exit("Missing ']'");
-  if (optc <= 4) toys.exitval = !test_few_args(0, optc);
-  else toys.exitval = !test_sub(0, optc);
-  return;
-}
diff --git a/toys/posix/grep.c b/toys/posix/grep.c
index f0332ce..14cebf9 100644
--- a/toys/posix/grep.c
+++ b/toys/posix/grep.c
@@ -152,7 +152,7 @@
             fseek.arg = s = line;
             break;
           }
-          if (toys.optflags & FLAG_i) s = strnstr(line, seek->arg);
+          if (toys.optflags & FLAG_i) s = strcasestr(line, seek->arg);
           else s = strstr(line, seek->arg);
           if (s) break;
         }
diff --git a/toys/posix/head.c b/toys/posix/head.c
index 0cec403..7e34a71 100644
--- a/toys/posix/head.c
+++ b/toys/posix/head.c
@@ -6,7 +6,7 @@
  *
  * Deviations from posix: -c
 
-USE_HEAD(NEWTOY(head, "?n#<0=10c#<0qv[-nc]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_HEAD(NEWTOY(head, "?n(lines)#<0=10c(bytes)#<0qv[-nc]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config HEAD
   bool "head"
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index 833ecab..731998d 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -207,7 +207,6 @@
     } pgrep;
   };
 
-  struct sysinfo si;
   struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
   struct dirtree *threadparent;
   unsigned width, height;
@@ -260,13 +259,15 @@
  SLOT_policy,   /*man sched_setscheduler*/SLOT_blkioticks,// IO wait time
  SLOT_gtime,    /*guest jiffies of task*/ SLOT_cgtime,    // gtime+child
  SLOT_startbss, /*data/bss address*/      SLOT_endbss,    // end addr data+bss
+// end of /proc/$PID/stat fields
  SLOT_upticks,  /*uptime-starttime*/      SLOT_argv0len,  // argv[0] length
- SLOT_uptime,   /*si.uptime @read time*/  SLOT_vsz,       // Virtual mem Size
- SLOT_shr,      /*Shared memory*/         SLOT_pcy,       // Android sched pol
- SLOT_rchar,    /*All bytes read*/        SLOT_wchar,     // All bytes written
- SLOT_rbytes,   /*Disk bytes read*/       SLOT_wbytes,    // Disk bytes written
- SLOT_swap,     /*Swap pages used*/       SLOT_bits,      // 32 or 64
- SLOT_tid,      /*Thread ID*/             SLOT_tcount,    // Thread count
+ SLOT_uptime,   /*sysinfo.uptime*/        SLOT_totalram,  // sysinfo.totalram
+ SLOT_vsz,      /*Virtual mem Size*/      SLOT_shr,       // Shared memory
+ SLOT_pcy,      /*Android sched pol*/     SLOT_rchar,     // All bytes read
+ SLOT_wchar,    /*All bytes written*/     SLOT_rbytes,    // Disk bytes read
+ SLOT_wbytes,   /*Disk bytes written*/    SLOT_swap,      // Swap pages used
+ SLOT_bits,     /*32 or 64*/              SLOT_tid,       // Thread ID
+ SLOT_tcount,   /*Thread count*/
 
  SLOT_count /* Size of array */
 };
@@ -577,7 +578,7 @@
   } else if (which <= PS__CPU) {
     ll = slot[sl&63]*1000;
     if (which==PS__VSZ || which==PS__MEM)
-      ll /= TT.si.totalram/((which==PS__VSZ) ? 1024 : 4096);
+      ll /= slot[SLOT_totalram]/((which==PS__VSZ) ? 1024 : 4096);
     else if (slot[SLOT_upticks]) ll /= slot[SLOT_upticks];
     sl = ll;
     if (which==PS_C) sl += 5;
@@ -703,6 +704,7 @@
   struct procpid *tb = (void *)toybuf;
   long long *slot = tb->slot;
   char *name, *s, *buf = tb->str, *end = 0;
+  struct sysinfo si;
   int i, j, fd;
   off_t len;
 
@@ -798,8 +800,9 @@
 
   // /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[SLOT_uptime] = TT.si.uptime;
+  sysinfo(&si);
+  slot[SLOT_uptime] = si.uptime;
+  slot[SLOT_totalram] = si.totalram;
   slot[SLOT_upticks] = slot[SLOT_uptime]*TT.ticks - slot[SLOT_starttime];
 
   // Do we need to read "statm"?
diff --git a/toys/posix/test.c b/toys/posix/test.c
new file mode 100644
index 0000000..052b8de
--- /dev/null
+++ b/toys/posix/test.c
@@ -0,0 +1,159 @@
+/* test.c - evaluate expression
+ *
+ * Copyright 2018 Rob Landley <rob@landley.net>
+ *
+ * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
+
+USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP))
+
+config TEST
+  bool "test"
+  default y
+  help
+    usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
+
+    Return true or false by performing tests. (With no arguments return false.)
+
+    --- Tests with a single argument (after the option):
+    PATH is/has:
+      -b  block device   -f  regular file   -p  fifo           -u  setuid bit
+      -c  char device    -g  setgid         -r  read bit       -w  write bit
+      -d  directory      -h  symlink        -S  socket         -x  execute bit
+      -e  exists         -L  symlink        -s  nonzero size
+    STRING is:
+      -n  nonzero size   -z  zero size      (STRING by itself implies -n)
+    FD (integer file descriptor) is:
+      -t  a TTY
+
+    --- Tests with one argument on each side of an operator:
+    Two strings:
+      =  are identical   !=  differ
+    Two integers:
+      -eq  equal         -gt  first > second    -lt  first < second
+      -ne  not equal     -ge  first >= second   -le  first <= second
+
+    --- Modify or combine tests:
+      ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)
+      ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)
+*/
+
+#include "toys.h"
+
+// Consume 3, 2, or 1 argument test, returning result and *count used.
+int do_test(char **args, int *count)
+{
+  char c, *s;
+  int i;
+
+  if (*count>=3) {
+    *count = 3;
+    char *s = args[1], *ss = "eqnegtgeltle";
+    if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
+    if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
+    if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
+      long long a = atolx(args[0]), b = atolx(args[2]);
+
+      if (!i) return a == b;
+      if (i==2) return a != b;
+      if (i==4) return a > b;
+      if (i==6) return a >= b;
+      if (i==8) return a < b;
+      if (i==10) return a<= b;
+    }
+  }
+  s = *args;
+  if (*count>=2 && *s == '-' && s[1] && !s[2]) {
+    *count = 2;
+    c = s[1];
+    if (-1 != (i = stridx("hLbcdefgpSusxwr", c))) {
+      struct stat st;
+
+      // stat or lstat, then handle rwx and s
+      if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
+      if (i>=12) return !!(st.st_mode&(0x111<<(i-12)));
+      if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
+
+      // handle file type checking and SUID/SGID
+      if ((i = (unsigned short []){80,80,48,16,32,0,64,2,8,96,4}[i]<<9)>=4096)
+        return (st.st_mode&S_IFMT) == i;
+      else return (st.st_mode & i) == i;
+    } else if (c == 'z') return !*args[1];
+    else if (c == 'n') return *args[1];
+    else if (c == 't') return isatty(atolx(args[1]));
+  }
+  return *count = 0;
+}
+
+#define NOT 1  // Most recent test had an odd number of preceding !
+#define AND 2  // test before -a failed since -o or ( so force false
+#define OR  4  // test before -o succeeded since ( so force true
+void test_main(void)
+{
+  char *s;
+  int pos, paren, pstack, result = 0;
+
+  toys.exitval = 2;
+  if (!strcmp("[", toys.which->name))
+    if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
+      error_exit("Missing ']'");
+
+  // loop through command line arguments
+  if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
+    int len = toys.optc-pos;
+
+    if (!toys.optargs[pos]) perror_exit("need arg @%d", pos);
+
+    // Evaluate next test
+    result = do_test(toys.optargs+pos, &len);
+    pos += len;
+    // Single argument could be ! ( or nonempty
+    if (!len) {
+      if (toys.optargs[pos+1]) {
+        if (!strcmp("!", toys.optargs[pos])) {
+          pstack ^= NOT;
+          continue;
+        }
+        if (!strcmp("(", toys.optargs[pos])) {
+          if (++paren>9) perror_exit("bad (");
+          pstack <<= 3;
+          continue;
+        }
+      }
+      result = *toys.optargs[pos++];
+    }
+    s = toys.optargs[pos];
+    for (;;) {
+
+      // Handle pending ! -a -o (the else means -o beats -a)
+      if (pstack&NOT) result = !result;
+      pstack &= ~NOT;
+      if (pstack&OR) result = 1;
+      else if (pstack&AND) result = 0;
+
+      // Do it again for every )
+      if (!paren || !s || strcmp(")", s)) break;
+      paren--;
+      pstack >>= 3;
+      s = toys.optargs[++pos];
+    }
+
+    // Out of arguments?
+    if (!s) {
+      if (paren) perror_exit("need )");
+      break;
+    }
+
+    // are we followed by -a or -o?
+
+    if (!strcmp("-a", s)) {
+      if (!result) pstack |= AND;
+    } else if (!strcmp("-o", s)) {
+      // -o flushes -a even if previous test was false
+      pstack &=~AND;
+      if (result) pstack |= OR;
+    } else error_exit("too many arguments");
+  }
+
+  // Invert C logic to get shell logic
+  toys.exitval = !result;
+}
diff --git a/toys/posix/uname.c b/toys/posix/uname.c
index 2e17d38..a133841 100644
--- a/toys/posix/uname.c
+++ b/toys/posix/uname.c
@@ -41,12 +41,13 @@
 void uname_main(void)
 {
   int i, flags = toys.optflags, needspace=0;
+  struct utsname u;
 
-  uname((void *)toybuf);
+  uname(&u);
 
   if (!flags) flags = FLAG_s;
   for (i=0; i<5; i++) {
-    char *c = toybuf+(65*i);
+    char *c = ((char *) &u)+(sizeof(u.sysname)*i);
 
     if (flags & ((1<<i)|FLAG_a)) {
       int len = strlen(c);