Snap for 7547121 from 9f54fe5a3b9a93d723e320ec5e5413f55ea0e7aa to mainline-permission-release

Change-Id: Ie945105085e65cf2764e480e513d9e6f0a6fb058
diff --git a/.config-device b/.config-device
index 883ea02..c871d24 100644
--- a/.config-device
+++ b/.config-device
@@ -44,9 +44,11 @@
 # CONFIG_ARPING is not set
 # CONFIG_ARP is not set
 # CONFIG_ASCII is not set
+# CONFIG_BASE32 is not set
 CONFIG_BASE64=y
 CONFIG_BASENAME=y
 # CONFIG_BC is not set
+CONFIG_BLKDISCARD=y
 CONFIG_BLKID=y
 CONFIG_BLOCKDEV=y
 # CONFIG_BOOTCHARTD is not set
@@ -65,6 +67,7 @@
 CONFIG_CHOWN=y
 CONFIG_CHROOT=y
 CONFIG_CHRT=y
+# CONFIG_CHSH is not set
 # CONFIG_CHVT is not set
 CONFIG_CKSUM=y
 CONFIG_CLEAR=y
@@ -246,6 +249,7 @@
 CONFIG_PS=y
 CONFIG_PWDX=y
 CONFIG_PWD=y
+# CONFIG_PWGEN is not set
 # CONFIG_READAHEAD is not set
 CONFIG_READELF=y
 CONFIG_READLINK=y
@@ -260,6 +264,7 @@
 CONFIG_RMMOD=y
 CONFIG_RM=y
 # CONFIG_ROUTE is not set
+CONFIG_RTCWAKE=y
 CONFIG_RUNCON=y
 CONFIG_SED=y
 CONFIG_SENDEVENT=y
@@ -270,6 +275,7 @@
 CONFIG_SHA1SUM=y
 CONFIG_SHA224SUM=y
 CONFIG_SHA256SUM=y
+# CONFIG_SHA3SUM is not set
 CONFIG_SHA384SUM=y
 CONFIG_SHA512SUM=y
 # CONFIG_SH is not set
@@ -305,6 +311,7 @@
 # CONFIG_TELNETD is not set
 # CONFIG_TELNET is not set
 CONFIG_TEST=y
+CONFIG_TEST_GLUE=y
 # CONFIG_TFTPD is not set
 # CONFIG_TFTP is not set
 CONFIG_TIMEOUT=y
@@ -321,6 +328,7 @@
 CONFIG_ULIMIT=y
 CONFIG_UMOUNT=y
 CONFIG_UNAME=y
+# CONFIG_UNICODE is not set
 CONFIG_UNIQ=y
 CONFIG_UNIX2DOS=y
 CONFIG_UNLINK=y
@@ -336,6 +344,7 @@
 CONFIG_VI=y
 CONFIG_VMSTAT=y
 CONFIG_WATCH=y
+# CONFIG_WATCHDOG is not set
 CONFIG_WC=y
 # CONFIG_WGET is not set
 CONFIG_WHICH=y
diff --git a/.config-linux b/.config-linux
index 5347792..a2aab08 100644
--- a/.config-linux
+++ b/.config-linux
@@ -45,9 +45,11 @@
 # CONFIG_ARPING is not set
 # CONFIG_ARP is not set
 # CONFIG_ASCII is not set
+# CONFIG_BASE32 is not set
 # CONFIG_BASE64 is not set
 CONFIG_BASENAME=y
 # CONFIG_BC is not set
+# CONFIG_BLKDISCARD is not set
 # CONFIG_BLKID is not set
 # CONFIG_BLOCKDEV is not set
 # CONFIG_BOOTCHARTD is not set
@@ -66,13 +68,14 @@
 # CONFIG_CHOWN is not set
 # CONFIG_CHROOT is not set
 # CONFIG_CHRT is not set
+# CONFIG_CHSH is not set
 # CONFIG_CHVT is not set
 # CONFIG_CKSUM is not set
 # CONFIG_CLEAR is not set
 CONFIG_CMP=y
 CONFIG_COMM=y
 # CONFIG_COUNT is not set
-# CONFIG_CPIO is not set
+CONFIG_CPIO=y
 CONFIG_CP_PRESERVE=y
 CONFIG_CP=y
 # CONFIG_CRC32 is not set
@@ -110,7 +113,7 @@
 # CONFIG_FALLOCATE is not set
 # CONFIG_FALSE is not set
 # CONFIG_FDISK is not set
-# CONFIG_FGREP is not set
+CONFIG_FGREP=y
 # CONFIG_FILE is not set
 CONFIG_FIND=y
 # CONFIG_FLOCK is not set
@@ -217,7 +220,7 @@
 # CONFIG_NETCAT_LISTEN is not set
 # CONFIG_NETSTAT is not set
 # CONFIG_NICE is not set
-# CONFIG_NL is not set
+CONFIG_NL=y
 # CONFIG_NOHUP is not set
 CONFIG_NPROC=y
 # CONFIG_NSENTER is not set
@@ -236,10 +239,11 @@
 CONFIG_PKILL=y
 # CONFIG_PMAP is not set
 # CONFIG_PRINTENV is not set
-# CONFIG_PRINTF is not set
+CONFIG_PRINTF=y
 CONFIG_PS=y
 # CONFIG_PWDX is not set
 CONFIG_PWD=y
+# CONFIG_PWGEN is not set
 # CONFIG_READAHEAD is not set
 # CONFIG_READELF is not set
 CONFIG_READLINK=y
@@ -254,6 +258,7 @@
 # CONFIG_RMMOD is not set
 CONFIG_RM=y
 # CONFIG_ROUTE is not set
+# CONFIG_RTCWAKE is not set
 # CONFIG_RUNCON is not set
 CONFIG_SED=y
 # CONFIG_SENDEVENT is not set
@@ -264,6 +269,7 @@
 CONFIG_SHA1SUM=y
 # CONFIG_SHA224SUM is not set
 CONFIG_SHA256SUM=y
+# CONFIG_SHA3SUM is not set
 # CONFIG_SHA384SUM is not set
 CONFIG_SHA512SUM=y
 # CONFIG_SH is not set
@@ -295,6 +301,7 @@
 # CONFIG_TELNETD is not set
 # CONFIG_TELNET is not set
 CONFIG_TEST=y
+CONFIG_TEST_GLUE=y
 # CONFIG_TFTPD is not set
 # CONFIG_TFTP is not set
 # CONFIG_TIME is not set
@@ -310,6 +317,7 @@
 # CONFIG_ULIMIT is not set
 # CONFIG_UMOUNT is not set
 CONFIG_UNAME=y
+# CONFIG_UNICODE is not set
 CONFIG_UNIQ=y
 CONFIG_UNIX2DOS=y
 # CONFIG_UNLINK is not set
@@ -325,6 +333,7 @@
 # CONFIG_VI is not set
 # CONFIG_VMSTAT is not set
 # CONFIG_WATCH is not set
+# CONFIG_WATCHDOG is not set
 CONFIG_WC=y
 # CONFIG_WGET is not set
 CONFIG_WHICH=y
diff --git a/.config-mac b/.config-mac
index 00d838c..db2d134 100644
--- a/.config-mac
+++ b/.config-mac
@@ -45,9 +45,11 @@
 # CONFIG_ARPING is not set
 # CONFIG_ARP is not set
 # CONFIG_ASCII is not set
+# CONFIG_BASE32 is not set
 # CONFIG_BASE64 is not set
 CONFIG_BASENAME=y
 # CONFIG_BC is not set
+# CONFIG_BLKDISCARD is not set
 # CONFIG_BLKID is not set
 # CONFIG_BLOCKDEV is not set
 # CONFIG_BOOTCHARTD is not set
@@ -66,13 +68,14 @@
 # CONFIG_CHOWN is not set
 # CONFIG_CHROOT is not set
 # CONFIG_CHRT is not set
+# CONFIG_CHSH is not set
 # CONFIG_CHVT is not set
 # CONFIG_CKSUM is not set
 # CONFIG_CLEAR is not set
 CONFIG_CMP=y
 CONFIG_COMM=y
 # CONFIG_COUNT is not set
-# CONFIG_CPIO is not set
+CONFIG_CPIO=y
 CONFIG_CP_PRESERVE=y
 CONFIG_CP=y
 # CONFIG_CRC32 is not set
@@ -110,7 +113,7 @@
 # CONFIG_FALLOCATE is not set
 # CONFIG_FALSE is not set
 # CONFIG_FDISK is not set
-# CONFIG_FGREP is not set
+CONFIG_FGREP=y
 # CONFIG_FILE is not set
 CONFIG_FIND=y
 # CONFIG_FLOCK is not set
@@ -217,7 +220,7 @@
 # CONFIG_NETCAT_LISTEN is not set
 # CONFIG_NETSTAT is not set
 # CONFIG_NICE is not set
-# CONFIG_NL is not set
+CONFIG_NL=y
 # CONFIG_NOHUP is not set
 # CONFIG_NPROC is not set
 # CONFIG_NSENTER is not set
@@ -236,10 +239,11 @@
 # CONFIG_PKILL is not set
 # CONFIG_PMAP is not set
 # CONFIG_PRINTENV is not set
-# CONFIG_PRINTF is not set
+CONFIG_PRINTF=y
 # CONFIG_PS is not set
 # CONFIG_PWDX is not set
 CONFIG_PWD=y
+# CONFIG_PWGEN is not set
 # CONFIG_READAHEAD is not set
 # CONFIG_READELF is not set
 CONFIG_READLINK=y
@@ -254,6 +258,7 @@
 # CONFIG_RMMOD is not set
 CONFIG_RM=y
 # CONFIG_ROUTE is not set
+# CONFIG_RTCWAKE is not set
 # CONFIG_RUNCON is not set
 CONFIG_SED=y
 # CONFIG_SENDEVENT is not set
@@ -264,6 +269,7 @@
 CONFIG_SHA1SUM=y
 # CONFIG_SHA224SUM is not set
 CONFIG_SHA256SUM=y
+# CONFIG_SHA3SUM is not set
 # CONFIG_SHA384SUM is not set
 CONFIG_SHA512SUM=y
 # CONFIG_SH is not set
@@ -295,6 +301,7 @@
 # CONFIG_TELNETD is not set
 # CONFIG_TELNET is not set
 CONFIG_TEST=y
+CONFIG_TEST_GLUE=y
 # CONFIG_TFTPD is not set
 # CONFIG_TFTP is not set
 # CONFIG_TIME is not set
@@ -310,6 +317,7 @@
 # CONFIG_ULIMIT is not set
 # CONFIG_UMOUNT is not set
 CONFIG_UNAME=y
+# CONFIG_UNICODE is not set
 CONFIG_UNIQ=y
 CONFIG_UNIX2DOS=y
 # CONFIG_UNLINK is not set
@@ -325,6 +333,7 @@
 # CONFIG_VI is not set
 # CONFIG_VMSTAT is not set
 # CONFIG_WATCH is not set
+# CONFIG_WATCHDOG is not set
 CONFIG_WC=y
 # CONFIG_WGET is not set
 CONFIG_WHICH=y
diff --git a/.github/workflows/toybox.yml b/.github/workflows/toybox.yml
new file mode 100644
index 0000000..1b463b2
--- /dev/null
+++ b/.github/workflows/toybox.yml
@@ -0,0 +1,50 @@
+name: toybox CI
+
+on:
+  schedule:
+    - cron:  '0 2 * * *'
+  push:
+    branches: [ master ]
+
+jobs:
+  MacOS-11_0:
+    runs-on: macos-11.0
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Setup
+      run: brew install gnu-sed
+    - name: Configure
+      run: make macos_defconfig
+    - name: Build
+      run: make
+    - name: Test
+      run: VERBOSE=1 make tests
+
+  MacOS-10_15:
+    runs-on: macos-10.15
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Setup
+      run: brew install gnu-sed
+    - name: Configure
+      run: make macos_defconfig
+    - name: Build
+      run: make
+    - name: Test
+      run: VERBOSE=1 make tests
+
+  Ubuntu-20_04:
+    runs-on: ubuntu-20.04
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Setup
+      run: sudo apt-get install build-essential
+    - name: Configure
+      run: make defconfig
+    - name: Build
+      run: make
+    - name: Test
+      run: VERBOSE=1 make tests
diff --git a/Android.bp b/Android.bp
index 9617f03..3beb024 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,21 +1,4 @@
-//
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
 /*
-
    --- To sync with upstream:
 
     # Update source and regenerate generated files.
@@ -51,86 +34,394 @@
 
 */
 
+package {
+    default_applicable_licenses: ["external_toybox_license"],
+}
+
+license {
+    name: "external_toybox_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-0BSD",
+        "SPDX-license-identifier-CC0-1.0",
+        "SPDX-license-identifier-Unlicense",
+        "legacy_unencumbered",
+    ],
+    license_text: [
+        "LICENSE",
+    ],
+}
+
+all_srcs = [
+    "lib/args.c",
+    "lib/commas.c",
+    "lib/dirtree.c",
+    "lib/env.c",
+    "lib/help.c",
+    "lib/lib.c",
+    "lib/linestack.c",
+    "lib/llist.c",
+    "lib/net.c",
+    "lib/portability.c",
+    "lib/tty.c",
+    "lib/xwrap.c",
+    "main.c",
+    "toys/lsb/gzip.c",
+    "toys/lsb/hostname.c",
+    "toys/lsb/md5sum.c",
+    "toys/lsb/mktemp.c",
+    "toys/lsb/seq.c",
+    "toys/net/microcom.c",
+    "toys/other/dos2unix.c",
+    "toys/other/readlink.c",
+    "toys/other/setsid.c",
+    "toys/other/stat.c",
+    "toys/other/timeout.c",
+    "toys/other/truncate.c",
+    "toys/other/which.c",
+    "toys/other/xxd.c",
+    "toys/other/yes.c",
+    "toys/pending/dd.c",
+    "toys/pending/diff.c",
+    "toys/pending/expr.c",
+    "toys/pending/getopt.c",
+    "toys/pending/tr.c",
+    "toys/posix/basename.c",
+    "toys/posix/cat.c",
+    "toys/posix/chmod.c",
+    "toys/posix/cmp.c",
+    "toys/posix/comm.c",
+    "toys/posix/cp.c",
+    "toys/posix/cpio.c",
+    "toys/posix/cut.c",
+    "toys/posix/date.c",
+    "toys/posix/dirname.c",
+    "toys/posix/du.c",
+    "toys/posix/echo.c",
+    "toys/posix/env.c",
+    "toys/posix/find.c",
+    "toys/posix/getconf.c",
+    "toys/posix/grep.c",
+    "toys/posix/head.c",
+    "toys/posix/id.c",
+    "toys/posix/ln.c",
+    "toys/posix/ls.c",
+    "toys/posix/mkdir.c",
+    "toys/posix/nl.c",
+    "toys/posix/od.c",
+    "toys/posix/paste.c",
+    "toys/posix/patch.c",
+    "toys/posix/printf.c",
+    "toys/posix/pwd.c",
+    "toys/posix/rm.c",
+    "toys/posix/rmdir.c",
+    "toys/posix/sed.c",
+    "toys/posix/sleep.c",
+    "toys/posix/sort.c",
+    "toys/posix/tail.c",
+    "toys/posix/tar.c",
+    "toys/posix/tee.c",
+    "toys/posix/test.c",
+    "toys/posix/touch.c",
+    "toys/posix/true.c",
+    "toys/posix/uname.c",
+    "toys/posix/uniq.c",
+    "toys/posix/wc.c",
+    "toys/posix/xargs.c",
+]
+
+linux_srcs = [
+    "toys/posix/ps.c",
+    "toys/other/taskset.c",
+]
+
+device_srcs = [
+    "toys/android/getenforce.c",
+    "toys/android/load_policy.c",
+    "toys/android/log.c",
+    "toys/android/restorecon.c",
+    "toys/android/runcon.c",
+    "toys/android/sendevent.c",
+    "toys/android/setenforce.c",
+    "toys/lsb/dmesg.c",
+    "toys/lsb/killall.c",
+    "toys/lsb/mknod.c",
+    "toys/lsb/mount.c",
+    "toys/lsb/pidof.c",
+    "toys/lsb/sync.c",
+    "toys/lsb/umount.c",
+    "toys/net/ifconfig.c",
+    "toys/net/netcat.c",
+    "toys/net/netstat.c",
+    "toys/net/ping.c",
+    "toys/net/rfkill.c",
+    "toys/net/tunctl.c",
+    "toys/other/acpi.c",
+    "toys/other/base64.c",
+    "toys/other/blkdiscard.c",
+    "toys/other/blkid.c",
+    "toys/other/blockdev.c",
+    "toys/other/chcon.c",
+    "toys/other/chroot.c",
+    "toys/other/chrt.c",
+    "toys/other/clear.c",
+    "toys/other/devmem.c",
+    "toys/other/fallocate.c",
+    "toys/other/flock.c",
+    "toys/other/fmt.c",
+    "toys/other/free.c",
+    "toys/other/freeramdisk.c",
+    "toys/other/fsfreeze.c",
+    "toys/other/fsync.c",
+    "toys/other/help.c",
+    "toys/other/hwclock.c",
+    "toys/other/i2ctools.c",
+    "toys/other/inotifyd.c",
+    "toys/other/insmod.c",
+    "toys/other/ionice.c",
+    "toys/other/losetup.c",
+    "toys/other/lsattr.c",
+    "toys/other/lsmod.c",
+    "toys/other/lspci.c",
+    "toys/other/lsusb.c",
+    "toys/other/makedevs.c",
+    "toys/other/mkswap.c",
+    "toys/other/modinfo.c",
+    "toys/other/mountpoint.c",
+    "toys/other/nbd_client.c",
+    "toys/other/nsenter.c",
+    "toys/other/partprobe.c",
+    "toys/other/pivot_root.c",
+    "toys/other/pmap.c",
+    "toys/other/printenv.c",
+    "toys/other/pwdx.c",
+    "toys/other/rev.c",
+    "toys/other/rmmod.c",
+    "toys/other/rtcwake.c",
+    "toys/other/setfattr.c",
+    "toys/other/swapoff.c",
+    "toys/other/swapon.c",
+    "toys/other/sysctl.c",
+    "toys/other/tac.c",
+    "toys/other/uptime.c",
+    "toys/other/usleep.c",
+    "toys/other/uuidgen.c",
+    "toys/other/vconfig.c",
+    "toys/other/vmstat.c",
+    "toys/other/watch.c",
+    "toys/pending/getfattr.c",
+    "toys/pending/lsof.c",
+    "toys/pending/modprobe.c",
+    "toys/pending/more.c",
+    "toys/pending/readelf.c",
+    "toys/pending/stty.c",
+    "toys/pending/traceroute.c",
+    "toys/pending/vi.c",
+    "toys/posix/cal.c",
+    "toys/posix/chgrp.c",
+    "toys/posix/cksum.c",
+    "toys/posix/df.c",
+    "toys/posix/expand.c",
+    "toys/posix/false.c",
+    "toys/posix/file.c",
+    "toys/posix/iconv.c",
+    "toys/posix/kill.c",
+    "toys/posix/mkfifo.c",
+    "toys/posix/nice.c",
+    "toys/posix/nohup.c",
+    "toys/posix/renice.c",
+    "toys/posix/split.c",
+    "toys/posix/strings.c",
+    "toys/posix/time.c",
+    "toys/posix/tty.c",
+    "toys/posix/ulimit.c",
+    "toys/posix/unlink.c",
+    "toys/posix/uudecode.c",
+    "toys/posix/uuencode.c",
+]
+
+toybox_symlinks = [
+    "[",
+    "acpi",
+    "base64",
+    "basename",
+    "blockdev",
+    "cal",
+    "cat",
+    "chattr",
+    "chcon",
+    "chgrp",
+    "chmod",
+    "chown",
+    "chroot",
+    "chrt",
+    "cksum",
+    "clear",
+    "comm",
+    "cmp",
+    "cp",
+    "cpio",
+    "cut",
+    "date",
+    "dd",
+    "devmem",
+    "df",
+    "diff",
+    "dirname",
+    "dmesg",
+    "dos2unix",
+    "du",
+    "echo",
+    "egrep",
+    "env",
+    "expand",
+    "expr",
+    "fallocate",
+    "false",
+    "fgrep",
+    "file",
+    "find",
+    "flock",
+    "fmt",
+    "free",
+    "fsync",
+    "getconf",
+    "getenforce",
+    "grep",
+    "groups",
+    "gunzip",
+    "gzip",
+    "head",
+    "hostname",
+    "hwclock",
+    "i2cdetect",
+    "i2cdump",
+    "i2cget",
+    "i2cset",
+    "iconv",
+    "id",
+    "ifconfig",
+    "inotifyd",
+    "insmod",
+    "install",
+    "ionice",
+    "iorenice",
+    "kill",
+    "killall",
+    "load_policy",
+    "ln",
+    "log",
+    "logname",
+    "losetup",
+    "ls",
+    "lsattr",
+    "lsmod",
+    "lsof",
+    "lspci",
+    "lsusb",
+    "md5sum",
+    "mkdir",
+    "mkfifo",
+    "mknod",
+    "mkswap",
+    "mktemp",
+    "microcom",
+    "modinfo",
+    "more",
+    "mount",
+    "mountpoint",
+    "mv",
+    "nc",
+    "netcat",
+    "netstat",
+    "nice",
+    "nl",
+    "nohup",
+    "nproc",
+    "nsenter",
+    "od",
+    "paste",
+    "patch",
+    "pgrep",
+    "pidof",
+    "pkill",
+    "pmap",
+    "printenv",
+    "printf",
+    "ps",
+    "pwd",
+    "readelf",
+    "readlink",
+    "realpath",
+    "renice",
+    "restorecon",
+    "rm",
+    "rmdir",
+    "rmmod",
+    "rtcwake",
+    "runcon",
+    "sed",
+    "sendevent",
+    "seq",
+    "setenforce",
+    "setsid",
+    "sha1sum",
+    "sha224sum",
+    "sha256sum",
+    "sha384sum",
+    "sha512sum",
+    "sleep",
+    "sort",
+    "split",
+    "stat",
+    "strings",
+    "stty",
+    "swapoff",
+    "swapon",
+    "sync",
+    "sysctl",
+    "tac",
+    "tail",
+    "tar",
+    "taskset",
+    "tee",
+    "test",
+    "time",
+    "timeout",
+    "top",
+    "touch",
+    "tr",
+    "true",
+    "truncate",
+    "tty",
+    "ulimit",
+    "umount",
+    "uname",
+    "uniq",
+    "unix2dos",
+    "unlink",
+    "unshare",
+    "uptime",
+    "usleep",
+    "uudecode",
+    "uuencode",
+    "uuidgen",
+    "vmstat",
+    "watch",
+    "wc",
+    "which",
+    "whoami",
+    "xargs",
+    "xxd",
+    "yes",
+    "zcat",
+]
+
 cc_defaults {
     name: "toybox-defaults",
-    srcs: [
-        "lib/args.c",
-        "lib/commas.c",
-        "lib/dirtree.c",
-        "lib/env.c",
-        "lib/help.c",
-        "lib/lib.c",
-        "lib/linestack.c",
-        "lib/llist.c",
-        "lib/net.c",
-        "lib/portability.c",
-        "lib/tty.c",
-        "lib/xwrap.c",
-        "main.c",
-        "toys/lsb/gzip.c",
-        "toys/lsb/hostname.c",
-        "toys/lsb/md5sum.c",
-        "toys/lsb/mktemp.c",
-        "toys/lsb/seq.c",
-        "toys/net/microcom.c",
-        "toys/other/dos2unix.c",
-        "toys/other/readlink.c",
-        "toys/other/realpath.c",
-        "toys/other/setsid.c",
-        "toys/other/stat.c",
-        "toys/other/timeout.c",
-        "toys/other/truncate.c",
-        "toys/other/which.c",
-        "toys/other/xxd.c",
-        "toys/other/yes.c",
-        "toys/pending/dd.c",
-        "toys/pending/diff.c",
-        "toys/pending/expr.c",
-        "toys/pending/getopt.c",
-        "toys/pending/tr.c",
-        "toys/posix/basename.c",
-        "toys/posix/cat.c",
-        "toys/posix/chmod.c",
-        "toys/posix/cmp.c",
-        "toys/posix/comm.c",
-        "toys/posix/cp.c",
-        "toys/posix/cut.c",
-        "toys/posix/date.c",
-        "toys/posix/dirname.c",
-        "toys/posix/du.c",
-        "toys/posix/echo.c",
-        "toys/posix/env.c",
-        "toys/posix/find.c",
-        "toys/posix/getconf.c",
-        "toys/posix/grep.c",
-        "toys/posix/head.c",
-        "toys/posix/id.c",
-        "toys/posix/ln.c",
-        "toys/posix/ls.c",
-        "toys/posix/mkdir.c",
-        "toys/posix/od.c",
-        "toys/posix/paste.c",
-        "toys/posix/patch.c",
-        "toys/posix/pwd.c",
-        "toys/posix/rm.c",
-        "toys/posix/rmdir.c",
-        "toys/posix/sed.c",
-        "toys/posix/sleep.c",
-        "toys/posix/sort.c",
-        "toys/posix/tail.c",
-        "toys/posix/tar.c",
-        "toys/posix/tee.c",
-        "toys/posix/test.c",
-        "toys/posix/touch.c",
-        "toys/posix/true.c",
-        "toys/posix/uname.c",
-        "toys/posix/uniq.c",
-        "toys/posix/wc.c",
-        "toys/posix/xargs.c",
-    ],
+    srcs: all_srcs,
 
     cflags: [
-        "-std=gnu11",
         "-Os",
         "-Wall",
         "-Werror",
@@ -149,20 +440,15 @@
         "-DTOYBOX_VENDOR=\"-android\"",
     ],
 
-    // This doesn't actually prevent us from dragging in libc++ at runtime
-    // because libnetd_client.so is C++.
-    stl: "none",
-
-    shared_libs: [
-        "libcrypto",
-        "libz",
-    ],
-
     target: {
         linux_glibc: {
             local_include_dirs: ["android/linux"],
         },
 
+        linux_bionic: {
+            local_include_dirs: ["android/linux"],
+        },
+
         darwin: {
             local_include_dirs: ["android/mac"],
             cflags: [
@@ -179,305 +465,12 @@
         },
 
         linux: {
-            srcs: [
-                "toys/posix/ps.c",
-                "toys/other/taskset.c",
-            ],
+            srcs: linux_srcs,
         },
 
         android: {
             local_include_dirs: ["android/device"],
-            srcs: [
-                "toys/android/getenforce.c",
-                "toys/android/load_policy.c",
-                "toys/android/log.c",
-                "toys/android/restorecon.c",
-                "toys/android/runcon.c",
-                "toys/android/sendevent.c",
-                "toys/android/setenforce.c",
-                "toys/lsb/dmesg.c",
-                "toys/lsb/killall.c",
-                "toys/lsb/mknod.c",
-                "toys/lsb/mount.c",
-                "toys/lsb/pidof.c",
-                "toys/lsb/sync.c",
-                "toys/lsb/umount.c",
-                "toys/net/ifconfig.c",
-                "toys/net/netcat.c",
-                "toys/net/netstat.c",
-                "toys/net/ping.c",
-                "toys/net/rfkill.c",
-                "toys/net/tunctl.c",
-                "toys/other/acpi.c",
-                "toys/other/base64.c",
-                "toys/other/blkid.c",
-                "toys/other/blockdev.c",
-                "toys/other/chcon.c",
-                "toys/other/chroot.c",
-                "toys/other/chrt.c",
-                "toys/other/clear.c",
-                "toys/other/devmem.c",
-                "toys/other/fallocate.c",
-                "toys/other/flock.c",
-                "toys/other/fmt.c",
-                "toys/other/free.c",
-                "toys/other/freeramdisk.c",
-                "toys/other/fsfreeze.c",
-                "toys/other/fsync.c",
-                "toys/other/help.c",
-                "toys/other/hwclock.c",
-                "toys/other/i2ctools.c",
-                "toys/other/inotifyd.c",
-                "toys/other/insmod.c",
-                "toys/other/ionice.c",
-                "toys/other/losetup.c",
-                "toys/other/lsattr.c",
-                "toys/other/lsmod.c",
-                "toys/other/lspci.c",
-                "toys/other/lsusb.c",
-                "toys/other/makedevs.c",
-                "toys/other/mkswap.c",
-                "toys/other/modinfo.c",
-                "toys/other/mountpoint.c",
-                "toys/other/nbd_client.c",
-                "toys/other/nsenter.c",
-                "toys/other/partprobe.c",
-                "toys/other/pivot_root.c",
-                "toys/other/pmap.c",
-                "toys/other/printenv.c",
-                "toys/other/pwdx.c",
-                "toys/other/rev.c",
-                "toys/other/rmmod.c",
-                "toys/other/setfattr.c",
-                "toys/other/swapoff.c",
-                "toys/other/swapon.c",
-                "toys/other/sysctl.c",
-                "toys/other/tac.c",
-                "toys/other/uptime.c",
-                "toys/other/usleep.c",
-                "toys/other/uuidgen.c",
-                "toys/other/vconfig.c",
-                "toys/other/vmstat.c",
-                "toys/other/watch.c",
-                "toys/pending/getfattr.c",
-                "toys/pending/lsof.c",
-                "toys/pending/modprobe.c",
-                "toys/pending/more.c",
-                "toys/pending/readelf.c",
-                "toys/pending/stty.c",
-                "toys/pending/traceroute.c",
-                "toys/pending/vi.c",
-                "toys/posix/cal.c",
-                "toys/posix/chgrp.c",
-                "toys/posix/cksum.c",
-                "toys/posix/cpio.c",
-                "toys/posix/df.c",
-                "toys/posix/expand.c",
-                "toys/posix/false.c",
-                "toys/posix/file.c",
-                "toys/posix/iconv.c",
-                "toys/posix/kill.c",
-                "toys/posix/mkfifo.c",
-                "toys/posix/nice.c",
-                "toys/posix/nl.c",
-                "toys/posix/nohup.c",
-                "toys/posix/printf.c",
-                "toys/posix/renice.c",
-                "toys/posix/split.c",
-                "toys/posix/strings.c",
-                "toys/posix/time.c",
-                "toys/posix/tty.c",
-                "toys/posix/ulimit.c",
-                "toys/posix/unlink.c",
-                "toys/posix/uudecode.c",
-                "toys/posix/uuencode.c",
-            ],
-
-            // not usable on Android?: freeramdisk fsfreeze makedevs nbd-client
-            //                         partprobe pivot_root pwdx rev rfkill vconfig
-            // currently prefer external/e2fsprogs: blkid
-            // currently prefer external/iputils: ping ping6
-
-            symlinks: [
-                "acpi",
-                "base64",
-                "basename",
-                "blockdev",
-                "cal",
-                "cat",
-                "chattr",
-                "chcon",
-                "chgrp",
-                "chmod",
-                "chown",
-                "chroot",
-                "chrt",
-                "cksum",
-                "clear",
-                "comm",
-                "cmp",
-                "cp",
-                "cpio",
-                "cut",
-                "date",
-                "dd",
-                "devmem",
-                "df",
-                "diff",
-                "dirname",
-                "dmesg",
-                "dos2unix",
-                "du",
-                "echo",
-                "egrep",
-                "env",
-                "expand",
-                "expr",
-                "fallocate",
-                "false",
-                "fgrep",
-                "file",
-                "find",
-                "flock",
-                "fmt",
-                "free",
-                "fsync",
-                "getconf",
-                "getenforce",
-                "grep",
-                "groups",
-                "gunzip",
-                "gzip",
-                "head",
-                "hostname",
-                "hwclock",
-                "i2cdetect",
-                "i2cdump",
-                "i2cget",
-                "i2cset",
-                "iconv",
-                "id",
-                "ifconfig",
-                "inotifyd",
-                "insmod",
-                "install",
-                "ionice",
-                "iorenice",
-                "kill",
-                "killall",
-                "load_policy",
-                "ln",
-                "log",
-                "logname",
-                "losetup",
-                "ls",
-                "lsattr",
-                "lsmod",
-                "lsof",
-                "lspci",
-                "lsusb",
-                "md5sum",
-                "mkdir",
-                "mkfifo",
-                "mknod",
-                "mkswap",
-                "mktemp",
-                "microcom",
-                "modinfo",
-                "more",
-                "mount",
-                "mountpoint",
-                "mv",
-                "nc",
-                "netcat",
-                "netstat",
-                "nice",
-                "nl",
-                "nohup",
-                "nproc",
-                "nsenter",
-                "od",
-                "paste",
-                "patch",
-                "pgrep",
-                "pidof",
-                "pkill",
-                "pmap",
-                "printenv",
-                "printf",
-                "ps",
-                "pwd",
-                "readelf",
-                "readlink",
-                "realpath",
-                "renice",
-                "restorecon",
-                "rm",
-                "rmdir",
-                "rmmod",
-                "runcon",
-                "sed",
-                "sendevent",
-                "seq",
-                "setenforce",
-                "setsid",
-                "sha1sum",
-                "sha224sum",
-                "sha256sum",
-                "sha384sum",
-                "sha512sum",
-                "sleep",
-                "sort",
-                "split",
-                "stat",
-                "strings",
-                "stty",
-                "swapoff",
-                "swapon",
-                "sync",
-                "sysctl",
-                "tac",
-                "tail",
-                "tar",
-                "taskset",
-                "tee",
-                "test",
-                "time",
-                "timeout",
-                "top",
-                "touch",
-                "tr",
-                "true",
-                "truncate",
-                "tty",
-                "ulimit",
-                "umount",
-                "uname",
-                "uniq",
-                "unix2dos",
-                "unlink",
-                "unshare",
-                "uptime",
-                "usleep",
-                "uudecode",
-                "uuencode",
-                "uuidgen",
-                "vmstat",
-                "watch",
-                "wc",
-                "which",
-                "whoami",
-                "xargs",
-                "xxd",
-                "yes",
-                "zcat",
-            ],
-
-            shared_libs: [
-                "liblog",
-                "libprocessgroup",
-                "libselinux",
-            ],
+            srcs: device_srcs,
         },
     },
 }
@@ -486,20 +479,80 @@
 // toybox for /system, /vendor, and /recovery
 //###########################################
 
+cc_defaults {
+    name: "toybox-shared-defaults",
+    defaults: ["toybox-defaults"],
+
+    // This doesn't actually prevent us from dragging in libc++ at runtime
+    // on the device because libnetd_client.so is C++, but it improves host
+    // startup time.
+    stl: "none",
+
+    shared_libs: [
+        "libcrypto",
+        "libz",
+    ],
+
+    target: {
+        android: {
+            shared_libs: [
+                "liblog",
+                "libprocessgroup",
+                "libselinux",
+            ],
+            symlinks: toybox_symlinks,
+        },
+    },
+}
+
 cc_binary {
     name: "toybox",
-    defaults: ["toybox-defaults"],
+    defaults: ["toybox-shared-defaults"],
     host_supported: true,
     recovery_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_binary {
     name: "toybox_vendor",
-    defaults: ["toybox-defaults"],
+    defaults: ["toybox-shared-defaults"],
     vendor: true,
 }
 
 //###########################################
+// Static toybox binaries for legacy devices
+//###########################################
+
+cc_binary {
+    name: "toybox-static",
+    defaults: ["toybox-defaults"],
+    static_executable: true,
+    compile_multilib: "both",
+    multilib: {
+        lib32: { suffix: "32", },
+        lib64: { suffix: "64", },
+    },
+    stl: "libc++_static",
+    static_libs: [
+        "libc",
+        "libm",
+        "libz",
+        "libbase",
+        "libcgrouprc",
+        "libcgrouprc_format",
+        "libcrypto_static",
+        "liblog",
+        "libprocessgroup",
+        "libselinux",
+    ],
+    dist: {
+        targets: [
+            "sdk",
+        ],
+    },
+}
+
+//###########################################
 // Running the toybox tests
 //###########################################
 
@@ -510,10 +563,9 @@
     test_suites: ["general-tests"],
     host_supported: true,
     device_supported: false,
-    test_config: "toybox-tests.xml",
+    require_root:true,
     data: [
         "tests/**/*",
         "scripts/runtest.sh",
     ],
 }
-
diff --git a/Config.in b/Config.in
index 362b22f..b626f3c 100644
--- a/Config.in
+++ b/Config.in
@@ -13,19 +13,14 @@
 	bool
 	default y
 	help
-	  usage: toybox [--long | --help | --version | [command] [arguments...]]
+	  usage: toybox [--long | --help | --version | [COMMAND] [ARGUMENTS...]]
 
-	  With no arguments, shows available commands. First argument is
-	  name of a command to run, followed by any arguments to that command.
+	  With no arguments, "toybox" shows available COMMAND names. Add --long
+	  to include suggested install path for each command, see
+	  https://landley.net/toybox/faq.html#install for details.
 
-	  --long	Show path to each command
-
-	  To install command symlinks with paths, try:
-	    for i in $(/bin/toybox --long); do ln -s /bin/toybox $i; done
-	  or all in one directory:
-	    for i in $(./toybox); do ln -s toybox $i; done; PATH=$PWD:$PATH
-
-	  Most toybox commands also understand the following arguments:
+	  First argument is name of a COMMAND to run, followed by any ARGUMENTS
+	  to that command. Most toybox commands also understand:
 
 	  --help		Show command help (only)
 	  --version	Show toybox version (only)
@@ -111,12 +106,6 @@
 	  optstring. (Use TOYFLAG_NOHELP to disable.) Produces the same output
 	  as "help command". --version shows toybox version.
 
-config TOYBOX_I18N
-	bool "Internationalization support"
-	default y
-	help
-	  Support for UTF-8 character sets, and some locale support.
-
 config TOYBOX_FREE
 	bool "Free memory unnecessarily"
 	default n
@@ -193,6 +182,6 @@
 	  to force every program to either include all nommu code in every
 	  instance ever built, or drop nommu support altogether.
 
-	  Building a toolchain scripts/mcm-buildall.sh patches musl to fix this.
+	  Building a scripts/mcm-buildall.sh toolchain patches musl to fix this.
 
 endmenu
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..313792c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: UNENCUMBERED
+}
diff --git a/Makefile b/Makefile
index 69d2487..52602d2 100644
--- a/Makefile
+++ b/Makefile
@@ -13,13 +13,11 @@
 
 KCONFIG_CONFIG ?= .config
 
-toybox_stuff: $(KCONFIG_CONFIG) *.[ch] lib/*.[ch] toys/*/*.c scripts/*.sh
-
-toybox generated/unstripped/toybox: toybox_stuff
+toybox generated/unstripped/toybox: $(KCONFIG_CONFIG) *.[ch] lib/*.[ch] toys/*/*.c scripts/*.sh Config.in
 	scripts/make.sh
 
 .PHONY: clean distclean baseline bloatcheck install install_flat \
-	uinstall uninstall_flat tests help toybox_stuff change \
+	uninstall uninstall_flat tests help change \
 	list list_working list_pending root run_root
 
 include kconfig/Makefile
@@ -65,7 +63,8 @@
 	@echo root cleaned
 
 clean::
-	@rm -rf toybox generated change .singleconfig* cross-log-*.*
+	@chmod -fR 700 generated || true
+	@rm -rf toybox generated change .singleconfig*
 	@echo cleaned
 
 # If singlemake was in generated/ "make clean; make test_ls" wouldn't work.
@@ -80,8 +79,7 @@
 	scripts/mkroot.sh $(MAKEFLAGS)
 
 run_root:
-	C=$$(basename "$$CROSS_COMPILE" | sed 's/-.*//'); \
-        cd root/"$${C:-host}" && ./qemu-*.sh $(MAKEFLAGS) || exit 1
+	cd root/"$${CROSS:-host}" && ./qemu-*.sh
 
 help::
 	@cat scripts/help.txt
diff --git a/NOTICE b/NOTICE
deleted file mode 120000
index 7a694c9..0000000
--- a/NOTICE
+++ /dev/null
@@ -1 +0,0 @@
-LICENSE
\ No newline at end of file
diff --git a/README b/README
index 4b3dee0..d24936f 100644
--- a/README
+++ b/README
@@ -30,15 +30,16 @@
   LDFLAGS="--static" CROSS_COMPILE=armv5l- make defconfig toybox
   PREFIX=/path/to/root/filesystem/bin make install_flat
 
-The file "configure" defines default values for many environment
-variables that control the toybox build; if you set a value for any of
-these, your value is used instead of the default in that file.
+The file "configure" defines default values for many environment variables
+that control the toybox build; if export any these variables into your
+environment, your value is used instead of the default in that file.
 
 The CROSS_COMPILE argument above is optional, the default builds a version of
 toybox to run on the current machine. Cross compiling requires an appropriately
-prefixed cross compiler toolchain, several example toolchains are available at:
+prefixed cross compiler toolchain, several example toolchains (built using
+the file "scripts/mcm-buildall.sh" in the toybox source) are available at:
 
-  http://landley.net/aboriginal/bin
+  https://mkroot.musl.cc/latest/
 
 For the "CROSS_COMPILE=armv5l-" example above, download
 cross-compiler-armv5l.tar.bz2, extract it, and add its "bin" subdirectory to
@@ -47,11 +48,13 @@
 
 For more about cross compiling, see:
 
+  https://landley.net/toybox/faq.html#cross
   http://landley.net/writing/docs/cross-compiling.html
   http://landley.net/aboriginal/architectures.html
 
-For a more thorough description of the toybox build process, see
-http://landley.net/toybox/code.html#building
+For a more thorough description of the toybox build process, see:
+
+  http://landley.net/toybox/code.html#building
 
 --- Using toybox
 
@@ -61,82 +64,85 @@
 
 The special "toybox" command treats its first argument as the command to run.
 With no arguments, it lists available commands. This allows you to use toybox
-without installing it. This is the only command that can have an arbitrary
+without installing it, and is the only command that can have an arbitrary
 suffix (hence "toybox-armv5l").
 
-The "help" command provides information about each command (ala "help cat").
+The "help" command provides information about each command (ala "help cat"),
+and "help toybox" provides general information about toybox.
 
 --- Configuring toybox
 
 It works like the Linux kernel: allnoconfig, defconfig, and menuconfig edit
 a ".config" file that selects which features to include in the resulting
-binary. You can save and re-use your .config file, although may want to
+binary. You can save and re-use your .config file, but may want to
 run "make oldconfig" to re-run the dependency resolver when migrating to
 new versions.
 
 The maximum sane configuration is "make defconfig": allyesconfig isn't
-recommended for toybox because it enables unfinished commands and debug code.
+recommended as a starting point for toybox because it enables unfinished
+commands, debug code, and optional dependencies your build environment may
+not provide.
 
 --- Creating a Toybox-based Linux system
 
-Toybox is not a complete operating system, it's a program that runs under
-an operating system. Booting a simple system to a shell prompt requires
-three packages: an operating system kernel (Linux*) to drive the hardware,
-one or more programs for the system to run (toybox), and a C library ("libc")
-to tie them together (toybox has been tested with musl, uClibc, glibc,
-and bionic).
+Toybox has a built-in simple system builder (scripts/mkroot.sh) with a
+Makefile target:
 
-The C library is part of a "toolchain", which is an integrated suite
-of compiler, assembler, and linker, plus the standard headers and libraries
-necessary to build C programs. (And miscellaneous binaries like nm and objdump.)
+  make root
+  sudo chroot root/host/fs /init
 
-Static linking (with the --static option) copies the shared library contents
-into the program, resulting in larger but more portable programs, which
-can run even if they're the only file in the filesystem. Otherwise,
-the "dynamically" linked programs require the library files to be present on
-the target system ("man ldd" and "man ld.so" for details).
+Type "exit" to get back out. If you install appropriate cross compilers and
+point it at Linux source code, it can build simple three-package systems
+that boot to a shell prompt under qemu:
 
-An example toybox-based system is Aboriginal Linux:
+  make root CROSS_COMPILE=sh4-linux-musl- LINUX=~/linux
+  cd root/sh4
+  ./qemu-sh4.sh
 
-  http://landley.net/aboriginal/about.html
+By calling scripts/mkroot.sh directly you can add additional packages
+to the build, see scripts/root/dropbear as an example.
 
-That's designed to run under qemu, emulating several different hardware
-architectures (x86, x86-64, arm, mips, sparc, powerpc, sh4). Each toybox
-release is regression tested by building Linux From Scratch under this
-toybox-based system on each supported architecture, using QEMU to emulate
-big and little endian systems with different word size and alignment
-requirements. (The eventual goal is to replace Linux From Scratch with
-the Android Open Source Project.)
+The FAQ explains this in a lot more detail:
 
-* Or something providing the same API such as FreeBSD's Linux emulation layer.
+  https://landley.net/toybox/faq.html#system
+  https://landley.net/toybox/faq.html#mkroot
 
 --- Presentations
 
 1) "Why Toybox?" talk at the Embedded Linux Conference in 2013
 
-    video: http://youtu.be/SGmtP5Lg_t0
     outline: http://landley.net/talks/celf-2013.txt
-    linked from http://landley.net/toybox/ in nav bar on left as "Why is it?"
-    - march 21, 2013 entry has section links.
+    video: http://youtu.be/SGmtP5Lg_t0
+
+    The https://landley.net/toybox/about.html page has nav links breaking that
+    talk down into sections.
 
 2) "Why Public Domain?" The rise and fall of copyleft, Ohio LinuxFest 2013
 
-    audio: https://archive.org/download/OhioLinuxfest2013/24-Rob_Landley-The_Rise_and_Fall_of_Copyleft.mp3
     outline: http://landley.net/talks/ohio-2013.txt
+    audio: https://archive.org/download/OhioLinuxfest2013/24-Rob_Landley-The_Rise_and_Fall_of_Copyleft.mp3
 
 3) Why did I do Aboriginal Linux (which led me here)
 
     260 slide presentation:
-    https://speakerdeck.com/landley/developing-for-non-x86-targets-using-qemu
+      https://speakerdeck.com/landley/developing-for-non-x86-targets-using-qemu
 
     How and why to make android self-hosting:
       http://landley.net/aboriginal/about.html#selfhost
 
+    More backstory than strictly necessary:
+      https://landley.net/aboriginal/history.html
+
 4) What's new with toybox (ELC 2015 status update):
 
     video: http://elinux.org/ELC_2015_Presentations
     outline: http://landley.net/talks/celf-2015.txt
 
+5) Toybox vs BusyBox (2019 ELC talk):
+
+    outline: http://landley.net/talks/elc-2019.txt
+    video: https://www.youtube.com/watch?v=MkJkyMuBm3g
+
 --- Contributing
 
 The three important URLs for communicating with the toybox project are:
@@ -155,9 +161,9 @@
 Then send a file attachment. The list holds messages from non-subscribers
 for moderation, but I usually get to them in a day or two.
 
-Although I do accept pull requests on github, I download the patches and
-apply them with "git am" (which avoids gratuitous merge commits). Closing
-the pull request is then the submitter's responsibility.
+I download github pull requests as patches and apply them with "git am"
+(which avoids gratuitous merge commits). Sometimes I even remember to close
+the pull request.
 
 If I haven't responded to your patch after one week, feel free to remind
 me of it.
@@ -167,3 +173,6 @@
 list) and then be pulled into android's toybox repo from there. (They
 generally resync on fridays). The exception is patches to their build scripts
 (Android.mk and the checked-in generated/* files) which go directly to AOSP.
+
+(As for the other meaning of "contributing", https://patreon.com/landley is
+always welcome but I warn you up front I'm terrible about updating it.)
diff --git a/android/device/generated/config.h b/android/device/generated/config.h
index 4c0fc10..d7d0b83 100644
--- a/android/device/generated/config.h
+++ b/android/device/generated/config.h
@@ -62,12 +62,16 @@
 #define USE_ARP(...)
 #define CFG_ASCII 0
 #define USE_ASCII(...)
+#define CFG_BASE32 0
+#define USE_BASE32(...)
 #define CFG_BASE64 1
 #define USE_BASE64(...) __VA_ARGS__
 #define CFG_BASENAME 1
 #define USE_BASENAME(...) __VA_ARGS__
 #define CFG_BC 0
 #define USE_BC(...)
+#define CFG_BLKDISCARD 1
+#define USE_BLKDISCARD(...) __VA_ARGS__
 #define CFG_BLKID 1
 #define USE_BLKID(...) __VA_ARGS__
 #define CFG_BLOCKDEV 1
@@ -104,6 +108,8 @@
 #define USE_CHROOT(...) __VA_ARGS__
 #define CFG_CHRT 1
 #define USE_CHRT(...) __VA_ARGS__
+#define CFG_CHSH 0
+#define USE_CHSH(...)
 #define CFG_CHVT 0
 #define USE_CHVT(...)
 #define CFG_CKSUM 1
@@ -466,6 +472,8 @@
 #define USE_PWDX(...) __VA_ARGS__
 #define CFG_PWD 1
 #define USE_PWD(...) __VA_ARGS__
+#define CFG_PWGEN 0
+#define USE_PWGEN(...)
 #define CFG_READAHEAD 0
 #define USE_READAHEAD(...)
 #define CFG_READELF 1
@@ -494,6 +502,8 @@
 #define USE_RM(...) __VA_ARGS__
 #define CFG_ROUTE 0
 #define USE_ROUTE(...)
+#define CFG_RTCWAKE 1
+#define USE_RTCWAKE(...) __VA_ARGS__
 #define CFG_RUNCON 1
 #define USE_RUNCON(...) __VA_ARGS__
 #define CFG_SED 1
@@ -514,6 +524,8 @@
 #define USE_SHA224SUM(...) __VA_ARGS__
 #define CFG_SHA256SUM 1
 #define USE_SHA256SUM(...) __VA_ARGS__
+#define CFG_SHA3SUM 0
+#define USE_SHA3SUM(...)
 #define CFG_SHA384SUM 1
 #define USE_SHA384SUM(...) __VA_ARGS__
 #define CFG_SHA512SUM 1
@@ -584,6 +596,8 @@
 #define USE_TELNET(...)
 #define CFG_TEST 1
 #define USE_TEST(...) __VA_ARGS__
+#define CFG_TEST_GLUE 1
+#define USE_TEST_GLUE(...) __VA_ARGS__
 #define CFG_TFTPD 0
 #define USE_TFTPD(...)
 #define CFG_TFTP 0
@@ -616,6 +630,8 @@
 #define USE_UMOUNT(...) __VA_ARGS__
 #define CFG_UNAME 1
 #define USE_UNAME(...) __VA_ARGS__
+#define CFG_UNICODE 0
+#define USE_UNICODE(...)
 #define CFG_UNIQ 1
 #define USE_UNIQ(...) __VA_ARGS__
 #define CFG_UNIX2DOS 1
@@ -646,6 +662,8 @@
 #define USE_VMSTAT(...) __VA_ARGS__
 #define CFG_WATCH 1
 #define USE_WATCH(...) __VA_ARGS__
+#define CFG_WATCHDOG 0
+#define USE_WATCHDOG(...)
 #define CFG_WC 1
 #define USE_WC(...) __VA_ARGS__
 #define CFG_WGET 0
diff --git a/android/device/generated/flags.h b/android/device/generated/flags.h
index fcf9e60..6a71e69 100644
--- a/android/device/generated/flags.h
+++ b/android/device/generated/flags.h
@@ -73,6 +73,17 @@
 #undef FOR_ascii
 #endif
 
+// base32   diw#<0=76[!dw]
+#undef OPTSTR_base32
+#define OPTSTR_base32 "diw#<0=76[!dw]"
+#ifdef CLEANUP_base32
+#undef CLEANUP_base32
+#undef FOR_base32
+#undef FLAG_w
+#undef FLAG_i
+#undef FLAG_d
+#endif
+
 // base64 diw#<0=76[!dw] diw#<0=76[!dw]
 #undef OPTSTR_base64
 #define OPTSTR_base64 "diw#<0=76[!dw]"
@@ -107,6 +118,19 @@
 #undef FLAG_i
 #endif
 
+// blkdiscard <1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz] <1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]
+#undef OPTSTR_blkdiscard
+#define OPTSTR_blkdiscard "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]"
+#ifdef CLEANUP_blkdiscard
+#undef CLEANUP_blkdiscard
+#undef FOR_blkdiscard
+#undef FLAG_z
+#undef FLAG_s
+#undef FLAG_o
+#undef FLAG_l
+#undef FLAG_f
+#endif
+
 // blkid ULs*[!LU] ULs*[!LU]
 #undef OPTSTR_blkid
 #define OPTSTR_blkid "ULs*[!LU]"
@@ -240,9 +264,9 @@
 #undef FLAG_h
 #endif
 
-// chgrp <2hPLHRfv[-HLP] <2hPLHRfv[-HLP]
+// chgrp <2h(no-dereference)PLHRfv[-HLP] <2h(no-dereference)PLHRfv[-HLP]
 #undef OPTSTR_chgrp
-#define OPTSTR_chgrp "<2hPLHRfv[-HLP]"
+#define OPTSTR_chgrp "<2h(no-dereference)PLHRfv[-HLP]"
 #ifdef CLEANUP_chgrp
 #undef CLEANUP_chgrp
 #undef FOR_chgrp
@@ -255,14 +279,14 @@
 #undef FLAG_h
 #endif
 
-// chmod <2?vRf[-vf] <2?vRf[-vf]
+// chmod <2?vfR[-vf] <2?vfR[-vf]
 #undef OPTSTR_chmod
-#define OPTSTR_chmod "<2?vRf[-vf]"
+#define OPTSTR_chmod "<2?vfR[-vf]"
 #ifdef CLEANUP_chmod
 #undef CLEANUP_chmod
 #undef FOR_chmod
-#undef FLAG_f
 #undef FLAG_R
+#undef FLAG_f
 #undef FLAG_v
 #endif
 
@@ -290,6 +314,15 @@
 #undef FLAG_m
 #endif
 
+// chsh   s:
+#undef OPTSTR_chsh
+#define OPTSTR_chsh "s:"
+#ifdef CLEANUP_chsh
+#undef CLEANUP_chsh
+#undef FOR_chsh
+#undef FLAG_s
+#endif
+
 // chvt   <1
 #undef OPTSTR_chvt
 #define OPTSTR_chvt "<1"
@@ -348,13 +381,14 @@
 #undef FOR_count
 #endif
 
-// cp <2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni] <2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]
+// cp <1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu] <1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]
 #undef OPTSTR_cp
-#define OPTSTR_cp "<2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]"
+#define OPTSTR_cp "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]"
 #ifdef CLEANUP_cp
 #undef CLEANUP_cp
 #undef FOR_cp
 #undef FLAG_T
+#undef FLAG_t
 #undef FLAG_i
 #undef FLAG_f
 #undef FLAG_F
@@ -364,6 +398,7 @@
 #undef FLAG_s
 #undef FLAG_a
 #undef FLAG_d
+#undef FLAG_u
 #undef FLAG_r
 #undef FLAG_p
 #undef FLAG_P
@@ -374,9 +409,9 @@
 #undef FLAG_preserve
 #endif
 
-// cpio (no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF] (no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]
+// cpio (quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF] (quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]
 #undef OPTSTR_cpio
-#define OPTSTR_cpio "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]"
+#define OPTSTR_cpio "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]"
 #ifdef CLEANUP_cpio
 #undef CLEANUP_cpio
 #undef FOR_cpio
@@ -390,8 +425,8 @@
 #undef FLAG_u
 #undef FLAG_d
 #undef FLAG_m
-#undef FLAG_trailer
 #undef FLAG_no_preserve_owner
+#undef FLAG_quiet
 #endif
 
 // crc32    
@@ -448,14 +483,15 @@
 #undef FLAG_b
 #endif
 
-// date d:D:r:u[!dr] d:D:r:u[!dr]
+// date d:D:I(iso)(iso-8601):;r:u(utc)[!dr] d:D:I(iso)(iso-8601):;r:u(utc)[!dr]
 #undef OPTSTR_date
-#define OPTSTR_date "d:D:r:u[!dr]"
+#define OPTSTR_date "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]"
 #ifdef CLEANUP_date
 #undef CLEANUP_date
 #undef FOR_date
 #undef FLAG_u
 #undef FLAG_r
+#undef FLAG_I
 #undef FLAG_D
 #undef FLAG_d
 #endif
@@ -536,16 +572,18 @@
 #undef FLAG_Z
 #endif
 
-// demo_number   D#=3<3hdbs
+// demo_number   D#=3<3M#<0hcdbs
 #undef OPTSTR_demo_number
-#define OPTSTR_demo_number "D#=3<3hdbs"
+#define OPTSTR_demo_number "D#=3<3M#<0hcdbs"
 #ifdef CLEANUP_demo_number
 #undef CLEANUP_demo_number
 #undef FOR_demo_number
 #undef FLAG_s
 #undef FLAG_b
 #undef FLAG_d
+#undef FLAG_c
 #undef FLAG_h
+#undef FLAG_M
 #undef FLAG_D
 #endif
 
@@ -573,9 +611,9 @@
 #undef FOR_devmem
 #endif
 
-// df HPkhit*a[-HPkh] HPkhit*a[-HPkh]
+// df HPkhit*a[-HPh] HPkhit*a[-HPh]
 #undef OPTSTR_df
-#define OPTSTR_df "HPkhit*a[-HPkh]"
+#define OPTSTR_df "HPkhit*a[-HPh]"
 #ifdef CLEANUP_df
 #undef CLEANUP_df
 #undef FOR_df
@@ -722,12 +760,13 @@
 #undef FOR_dos2unix
 #endif
 
-// du d#<0=-1hmlcaHkKLsx[-HL][-kKmh] d#<0=-1hmlcaHkKLsx[-HL][-kKmh]
+// du d#<0=-1hmlcaHkKLsxb[-HL][-kKmh] d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]
 #undef OPTSTR_du
-#define OPTSTR_du "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]"
+#define OPTSTR_du "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]"
 #ifdef CLEANUP_du
 #undef CLEANUP_du
 #undef FOR_du
+#undef FLAG_b
 #undef FLAG_x
 #undef FLAG_s
 #undef FLAG_L
@@ -775,15 +814,34 @@
 #undef FLAG_s
 #endif
 
-// env ^0iu* ^0iu*
+// env ^i0u* ^i0u*
 #undef OPTSTR_env
-#define OPTSTR_env "^0iu*"
+#define OPTSTR_env "^i0u*"
 #ifdef CLEANUP_env
 #undef CLEANUP_env
 #undef FOR_env
 #undef FLAG_u
-#undef FLAG_i
 #undef FLAG_0
+#undef FLAG_i
+#endif
+
+// eval    
+#undef OPTSTR_eval
+#define OPTSTR_eval 0
+#ifdef CLEANUP_eval
+#undef CLEANUP_eval
+#undef FOR_eval
+#endif
+
+// exec   ^cla:
+#undef OPTSTR_exec
+#define OPTSTR_exec "^cla:"
+#ifdef CLEANUP_exec
+#undef CLEANUP_exec
+#undef FOR_exec
+#undef FLAG_a
+#undef FLAG_l
+#undef FLAG_c
 #endif
 
 // exit    
@@ -803,6 +861,16 @@
 #undef FLAG_t
 #endif
 
+// export   np
+#undef OPTSTR_export
+#define OPTSTR_export "np"
+#ifdef CLEANUP_export
+#undef CLEANUP_export
+#undef FOR_export
+#undef FLAG_p
+#undef FLAG_n
+#endif
+
 // expr    
 #undef OPTSTR_expr
 #define OPTSTR_expr 0
@@ -1355,15 +1423,16 @@
 #undef FOR_insmod
 #endif
 
-// install <1cdDpsvm:o:g: <1cdDpsvm:o:g:
+// install <1cdDpsvt:m:o:g: <1cdDpsvt:m:o:g:
 #undef OPTSTR_install
-#define OPTSTR_install "<1cdDpsvm:o:g:"
+#define OPTSTR_install "<1cdDpsvt:m:o:g:"
 #ifdef CLEANUP_install
 #undef CLEANUP_install
 #undef FOR_install
 #undef FLAG_g
 #undef FLAG_o
 #undef FLAG_m
+#undef FLAG_t
 #undef FLAG_v
 #undef FLAG_s
 #undef FLAG_p
@@ -1455,6 +1524,19 @@
 #undef FLAG_a
 #endif
 
+// jobs   lnprs
+#undef OPTSTR_jobs
+#define OPTSTR_jobs "lnprs"
+#ifdef CLEANUP_jobs
+#undef CLEANUP_jobs
+#undef FOR_jobs
+#undef FLAG_s
+#undef FLAG_r
+#undef FLAG_p
+#undef FLAG_n
+#undef FLAG_l
+#endif
+
 // kill ?ls:  ?ls: 
 #undef OPTSTR_kill
 #define OPTSTR_kill "?ls: "
@@ -1551,15 +1633,15 @@
 #undef FLAG_p
 #endif
 
-// logger   st:p:
+// logger   t:p:s
 #undef OPTSTR_logger
-#define OPTSTR_logger "st:p:"
+#define OPTSTR_logger "t:p:s"
 #ifdef CLEANUP_logger
 #undef CLEANUP_logger
 #undef FOR_logger
+#undef FLAG_s
 #undef FLAG_p
 #undef FLAG_t
-#undef FLAG_s
 #endif
 
 // login   >1f:ph:
@@ -1751,9 +1833,9 @@
 #undef FLAG_s
 #endif
 
-// microcom <1>1s:X <1>1s:X
+// microcom <1>1s#=115200X <1>1s#=115200X
 #undef OPTSTR_microcom
-#define OPTSTR_microcom "<1>1s:X"
+#define OPTSTR_microcom "<1>1s#=115200X"
 #ifdef CLEANUP_microcom
 #undef CLEANUP_microcom
 #undef FOR_microcom
@@ -1920,13 +2002,14 @@
 #undef FLAG_q
 #endif
 
-// mv <2vnF(remove-destination)fiT[-ni] <2vnF(remove-destination)fiT[-ni]
+// mv <1vnF(remove-destination)fit:T[-ni] <1vnF(remove-destination)fit:T[-ni]
 #undef OPTSTR_mv
-#define OPTSTR_mv "<2vnF(remove-destination)fiT[-ni]"
+#define OPTSTR_mv "<1vnF(remove-destination)fit:T[-ni]"
 #ifdef CLEANUP_mv
 #undef CLEANUP_mv
 #undef FOR_mv
 #undef FLAG_T
+#undef FLAG_t
 #undef FLAG_i
 #undef FLAG_f
 #undef FLAG_F
@@ -1944,9 +2027,9 @@
 #undef FLAG_n
 #endif
 
-// netcat ^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U] ^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]
+// netcat ^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U] ^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]
 #undef OPTSTR_netcat
-#define OPTSTR_netcat "^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]"
+#define OPTSTR_netcat "^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]"
 #ifdef CLEANUP_netcat
 #undef CLEANUP_netcat
 #undef FOR_netcat
@@ -1962,6 +2045,7 @@
 #undef FLAG_w
 #undef FLAG_L
 #undef FLAG_l
+#undef FLAG_E
 #undef FLAG_t
 #endif
 
@@ -2170,9 +2254,9 @@
 #undef FLAG_s
 #endif
 
-// ping <1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46] <1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]
+// ping <1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46] <1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]
 #undef OPTSTR_ping
-#define OPTSTR_ping "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]"
+#define OPTSTR_ping "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]"
 #ifdef CLEANUP_ping
 #undef CLEANUP_ping
 #undef FOR_ping
@@ -2230,13 +2314,14 @@
 #undef FLAG_x
 #endif
 
-// printenv 0(null) 0(null)
+// printenv (null)0 (null)0
 #undef OPTSTR_printenv
-#define OPTSTR_printenv "0(null)"
+#define OPTSTR_printenv "(null)0"
 #ifdef CLEANUP_printenv
 #undef CLEANUP_printenv
 #undef FOR_printenv
 #undef FLAG_0
+#undef FLAG_null
 #endif
 
 // printf <1?^ <1?^
@@ -2296,6 +2381,26 @@
 #undef FLAG_a
 #endif
 
+// pwgen   >2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]
+#undef OPTSTR_pwgen
+#define OPTSTR_pwgen ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]"
+#ifdef CLEANUP_pwgen
+#undef CLEANUP_pwgen
+#undef FOR_pwgen
+#undef FLAG_0
+#undef FLAG_A
+#undef FLAG_v
+#undef FLAG_1
+#undef FLAG_C
+#undef FLAG_h
+#undef FLAG_B
+#undef FLAG_s
+#undef FLAG_y
+#undef FLAG_n
+#undef FLAG_c
+#undef FLAG_r
+#endif
+
 // readahead    
 #undef OPTSTR_readahead
 #define OPTSTR_readahead 0
@@ -2304,9 +2409,9 @@
 #undef FOR_readahead
 #endif
 
-// readelf <1(dyn-syms)adhlnp:SsWx: <1(dyn-syms)adhlnp:SsWx:
+// readelf <1(dyn-syms)adehlnp:SsWx: <1(dyn-syms)adehlnp:SsWx:
 #undef OPTSTR_readelf
-#define OPTSTR_readelf "<1(dyn-syms)adhlnp:SsWx:"
+#define OPTSTR_readelf "<1(dyn-syms)adehlnp:SsWx:"
 #ifdef CLEANUP_readelf
 #undef CLEANUP_readelf
 #undef FOR_readelf
@@ -2318,6 +2423,7 @@
 #undef FLAG_n
 #undef FLAG_l
 #undef FLAG_h
+#undef FLAG_e
 #undef FLAG_d
 #undef FLAG_a
 #undef FLAG_dyn_syms
@@ -2417,9 +2523,9 @@
 #undef FLAG_f
 #endif
 
-// rmdir <1(ignore-fail-on-non-empty)p <1(ignore-fail-on-non-empty)p
+// rmdir <1(ignore-fail-on-non-empty)p(parents) <1(ignore-fail-on-non-empty)p(parents)
 #undef OPTSTR_rmdir
-#define OPTSTR_rmdir "<1(ignore-fail-on-non-empty)p"
+#define OPTSTR_rmdir "<1(ignore-fail-on-non-empty)p(parents)"
 #ifdef CLEANUP_rmdir
 #undef CLEANUP_rmdir
 #undef FOR_rmdir
@@ -2448,6 +2554,24 @@
 #undef FLAG_n
 #endif
 
+// rtcwake (list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu] (list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]
+#undef OPTSTR_rtcwake
+#define OPTSTR_rtcwake "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]"
+#ifdef CLEANUP_rtcwake
+#undef CLEANUP_rtcwake
+#undef FOR_rtcwake
+#undef FLAG_v
+#undef FLAG_u
+#undef FLAG_t
+#undef FLAG_s
+#undef FLAG_m
+#undef FLAG_l
+#undef FLAG_d
+#undef FLAG_a
+#undef FLAG_auto
+#undef FLAG_list_modes
+#endif
+
 // runcon <2 <2
 #undef OPTSTR_runcon
 #define OPTSTR_runcon "<2"
@@ -2456,12 +2580,13 @@
 #undef FOR_runcon
 #endif
 
-// sed (help)(version)e*f*i:;nErz(null-data)[+Er] (help)(version)e*f*i:;nErz(null-data)[+Er]
+// sed (help)(version)e*f*i:;nErz(null-data)s[+Er] (help)(version)e*f*i:;nErz(null-data)s[+Er]
 #undef OPTSTR_sed
-#define OPTSTR_sed "(help)(version)e*f*i:;nErz(null-data)[+Er]"
+#define OPTSTR_sed "(help)(version)e*f*i:;nErz(null-data)s[+Er]"
 #ifdef CLEANUP_sed
 #undef CLEANUP_sed
 #undef FOR_sed
+#undef FLAG_s
 #undef FLAG_z
 #undef FLAG_r
 #undef FLAG_E
@@ -2492,6 +2617,14 @@
 #undef FLAG_f
 #endif
 
+// set    
+#undef OPTSTR_set
+#define OPTSTR_set 0
+#ifdef CLEANUP_set
+#undef CLEANUP_set
+#undef FOR_set
+#endif
+
 // setenforce <1>1 <1>1
 #undef OPTSTR_setenforce
 #define OPTSTR_setenforce "<1>1"
@@ -2523,9 +2656,9 @@
 #undef FLAG_w
 #endif
 
-// sh   (noediting)(noprofile)(norc)sc:i
+// sh   0(noediting)(noprofile)(norc)sc:i
 #undef OPTSTR_sh
-#define OPTSTR_sh "(noediting)(noprofile)(norc)sc:i"
+#define OPTSTR_sh "0(noediting)(noprofile)(norc)sc:i"
 #ifdef CLEANUP_sh
 #undef CLEANUP_sh
 #undef FOR_sh
@@ -2548,6 +2681,25 @@
 #undef FLAG_b
 #endif
 
+// sha3sum   bSa#<128>512=224
+#undef OPTSTR_sha3sum
+#define OPTSTR_sha3sum "bSa#<128>512=224"
+#ifdef CLEANUP_sha3sum
+#undef CLEANUP_sha3sum
+#undef FOR_sha3sum
+#undef FLAG_a
+#undef FLAG_S
+#undef FLAG_b
+#endif
+
+// shift   >1
+#undef OPTSTR_shift
+#define OPTSTR_shift ">1"
+#ifdef CLEANUP_shift
+#undef CLEANUP_shift
+#undef FOR_shift
+#endif
+
 // shred   <1zxus#<1n#<1o#<0f
 #undef OPTSTR_shred
 #define OPTSTR_shred "<1zxus#<1n#<1o#<0f"
@@ -2645,6 +2797,14 @@
 #undef FLAG_g
 #endif
 
+// source   <1
+#undef OPTSTR_source
+#define OPTSTR_source "<1"
+#ifdef CLEANUP_source
+#undef CLEANUP_source
+#undef FOR_source
+#endif
+
 // split >2a#<1=2>9b#<1l#<1[!bl] >2a#<1=2>9b#<1l#<1[!bl]
 #undef OPTSTR_split
 #define OPTSTR_split ">2a#<1=2>9b#<1l#<1[!bl]"
@@ -2809,9 +2969,9 @@
 #undef FLAG_f
 #endif
 
-// tar &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa] &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]
+// tar &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa] &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]
 #undef OPTSTR_tar
-#define OPTSTR_tar "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]"
+#define OPTSTR_tar "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]"
 #ifdef CLEANUP_tar
 #undef CLEANUP_tar
 #undef FOR_tar
@@ -2821,11 +2981,13 @@
 #undef FLAG_T
 #undef FLAG_X
 #undef FLAG_m
+#undef FLAG_P
 #undef FLAG_O
 #undef FLAG_S
 #undef FLAG_z
 #undef FLAG_j
 #undef FLAG_J
+#undef FLAG_I
 #undef FLAG_v
 #undef FLAG_t
 #undef FLAG_x
@@ -3149,6 +3311,14 @@
 #undef FLAG_o
 #endif
 
+// unicode   <1
+#undef OPTSTR_unicode
+#define OPTSTR_unicode "<1"
+#ifdef CLEANUP_unicode
+#undef CLEANUP_unicode
+#undef FOR_unicode
+#endif
+
 // uniq f#s#w#zicdu f#s#w#zicdu
 #undef OPTSTR_uniq
 #define OPTSTR_uniq "f#s#w#zicdu"
@@ -3181,6 +3351,17 @@
 #undef FOR_unlink
 #endif
 
+// unset   fvn[!fv]
+#undef OPTSTR_unset
+#define OPTSTR_unset "fvn[!fv]"
+#ifdef CLEANUP_unset
+#undef CLEANUP_unset
+#undef FOR_unset
+#undef FLAG_n
+#undef FLAG_v
+#undef FLAG_f
+#endif
+
 // unshare <1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user); <1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);
 #undef OPTSTR_unshare
 #define OPTSTR_unshare "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);"
@@ -3301,6 +3482,15 @@
 #undef FOR_w
 #endif
 
+// wait   n
+#undef OPTSTR_wait
+#define OPTSTR_wait "n"
+#ifdef CLEANUP_wait
+#undef CLEANUP_wait
+#undef FOR_wait
+#undef FLAG_n
+#endif
+
 // watch ^<1n%<100=2000tebx ^<1n%<100=2000tebx
 #undef OPTSTR_watch
 #define OPTSTR_watch "^<1n%<100=2000tebx"
@@ -3314,6 +3504,17 @@
 #undef FLAG_n
 #endif
 
+// watchdog   <1>1Ft#=4<1T#=60<1
+#undef OPTSTR_watchdog
+#define OPTSTR_watchdog "<1>1Ft#=4<1T#=60<1"
+#ifdef CLEANUP_watchdog
+#undef CLEANUP_watchdog
+#undef FOR_watchdog
+#undef FLAG_T
+#undef FLAG_t
+#undef FLAG_F
+#endif
+
 // wc mcwl mcwl
 #undef OPTSTR_wc
 #define OPTSTR_wc "mcwl"
@@ -3354,9 +3555,9 @@
 #undef FLAG_a
 #endif
 
-// xargs ^E:P#optrn#<1(max-args)s#0[!0E] ^E:P#optrn#<1(max-args)s#0[!0E]
+// xargs ^E:P#<0=1optrn#<1(max-args)s#0[!0E] ^E:P#<0=1optrn#<1(max-args)s#0[!0E]
 #undef OPTSTR_xargs
-#define OPTSTR_xargs "^E:P#optrn#<1(max-args)s#0[!0E]"
+#define OPTSTR_xargs "^E:P#<0=1optrn#<1(max-args)s#0[!0E]"
 #ifdef CLEANUP_xargs
 #undef CLEANUP_xargs
 #undef FOR_xargs
@@ -3479,6 +3680,15 @@
 #endif
 #endif
 
+#ifdef FOR_base32
+#ifndef TT
+#define TT this.base32
+#endif
+#define FLAG_w (FORCED_FLAG<<0)
+#define FLAG_i (FORCED_FLAG<<1)
+#define FLAG_d (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_base64
 #ifndef TT
 #define TT this.base64
@@ -3507,6 +3717,17 @@
 #define FLAG_i (FORCED_FLAG<<4)
 #endif
 
+#ifdef FOR_blkdiscard
+#ifndef TT
+#define TT this.blkdiscard
+#endif
+#define FLAG_z (1<<0)
+#define FLAG_s (1<<1)
+#define FLAG_o (1<<2)
+#define FLAG_l (1<<3)
+#define FLAG_f (1<<4)
+#endif
+
 #ifdef FOR_blkid
 #ifndef TT
 #define TT this.blkid
@@ -3633,8 +3854,8 @@
 #ifndef TT
 #define TT this.chmod
 #endif
-#define FLAG_f (1<<0)
-#define FLAG_R (1<<1)
+#define FLAG_R (1<<0)
+#define FLAG_f (1<<1)
 #define FLAG_v (1<<2)
 #endif
 
@@ -3658,6 +3879,13 @@
 #define FLAG_m (1<<7)
 #endif
 
+#ifdef FOR_chsh
+#ifndef TT
+#define TT this.chsh
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_chvt
 #ifndef TT
 #define TT this.chvt
@@ -3709,23 +3937,25 @@
 #define TT this.cp
 #endif
 #define FLAG_T (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_f (1<<2)
-#define FLAG_F (1<<3)
-#define FLAG_n (1<<4)
-#define FLAG_v (1<<5)
-#define FLAG_l (1<<6)
-#define FLAG_s (1<<7)
-#define FLAG_a (1<<8)
-#define FLAG_d (1<<9)
-#define FLAG_r (1<<10)
-#define FLAG_p (1<<11)
-#define FLAG_P (1<<12)
-#define FLAG_L (1<<13)
-#define FLAG_H (1<<14)
-#define FLAG_R (1<<15)
-#define FLAG_D (1<<16)
-#define FLAG_preserve (1<<17)
+#define FLAG_t (1<<1)
+#define FLAG_i (1<<2)
+#define FLAG_f (1<<3)
+#define FLAG_F (1<<4)
+#define FLAG_n (1<<5)
+#define FLAG_v (1<<6)
+#define FLAG_l (1<<7)
+#define FLAG_s (1<<8)
+#define FLAG_a (1<<9)
+#define FLAG_d (1<<10)
+#define FLAG_u (1<<11)
+#define FLAG_r (1<<12)
+#define FLAG_p (1<<13)
+#define FLAG_P (1<<14)
+#define FLAG_L (1<<15)
+#define FLAG_H (1<<16)
+#define FLAG_R (1<<17)
+#define FLAG_D (1<<18)
+#define FLAG_preserve (1<<19)
 #endif
 
 #ifdef FOR_cpio
@@ -3742,8 +3972,8 @@
 #define FLAG_u (1<<7)
 #define FLAG_d (1<<8)
 #define FLAG_m (1<<9)
-#define FLAG_trailer (1<<10)
-#define FLAG_no_preserve_owner (1<<11)
+#define FLAG_no_preserve_owner (1<<10)
+#define FLAG_quiet (1<<11)
 #endif
 
 #ifdef FOR_crc32
@@ -3798,8 +4028,9 @@
 #endif
 #define FLAG_u (1<<0)
 #define FLAG_r (1<<1)
-#define FLAG_D (1<<2)
-#define FLAG_d (1<<3)
+#define FLAG_I (1<<2)
+#define FLAG_D (1<<3)
+#define FLAG_d (1<<4)
 #endif
 
 #ifdef FOR_dd
@@ -3879,8 +4110,10 @@
 #define FLAG_s (FORCED_FLAG<<0)
 #define FLAG_b (FORCED_FLAG<<1)
 #define FLAG_d (FORCED_FLAG<<2)
-#define FLAG_h (FORCED_FLAG<<3)
-#define FLAG_D (FORCED_FLAG<<4)
+#define FLAG_c (FORCED_FLAG<<3)
+#define FLAG_h (FORCED_FLAG<<4)
+#define FLAG_M (FORCED_FLAG<<5)
+#define FLAG_D (FORCED_FLAG<<6)
 #endif
 
 #ifdef FOR_demo_scankey
@@ -4036,18 +4269,19 @@
 #ifndef TT
 #define TT this.du
 #endif
-#define FLAG_x (1<<0)
-#define FLAG_s (1<<1)
-#define FLAG_L (1<<2)
-#define FLAG_K (1<<3)
-#define FLAG_k (1<<4)
-#define FLAG_H (1<<5)
-#define FLAG_a (1<<6)
-#define FLAG_c (1<<7)
-#define FLAG_l (1<<8)
-#define FLAG_m (1<<9)
-#define FLAG_h (1<<10)
-#define FLAG_d (1<<11)
+#define FLAG_b (1<<0)
+#define FLAG_x (1<<1)
+#define FLAG_s (1<<2)
+#define FLAG_L (1<<3)
+#define FLAG_K (1<<4)
+#define FLAG_k (1<<5)
+#define FLAG_H (1<<6)
+#define FLAG_a (1<<7)
+#define FLAG_c (1<<8)
+#define FLAG_l (1<<9)
+#define FLAG_m (1<<10)
+#define FLAG_h (1<<11)
+#define FLAG_d (1<<12)
 #endif
 
 #ifdef FOR_dumpleases
@@ -4082,8 +4316,23 @@
 #define TT this.env
 #endif
 #define FLAG_u (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_0 (1<<2)
+#define FLAG_0 (1<<1)
+#define FLAG_i (1<<2)
+#endif
+
+#ifdef FOR_eval
+#ifndef TT
+#define TT this.eval
+#endif
+#endif
+
+#ifdef FOR_exec
+#ifndef TT
+#define TT this.exec
+#endif
+#define FLAG_a (FORCED_FLAG<<0)
+#define FLAG_l (FORCED_FLAG<<1)
+#define FLAG_c (FORCED_FLAG<<2)
 #endif
 
 #ifdef FOR_exit
@@ -4099,6 +4348,14 @@
 #define FLAG_t (1<<0)
 #endif
 
+#ifdef FOR_export
+#ifndef TT
+#define TT this.export
+#endif
+#define FLAG_p (FORCED_FLAG<<0)
+#define FLAG_n (FORCED_FLAG<<1)
+#endif
+
 #ifdef FOR_expr
 #ifndef TT
 #define TT this.expr
@@ -4566,12 +4823,13 @@
 #define FLAG_g (1<<0)
 #define FLAG_o (1<<1)
 #define FLAG_m (1<<2)
-#define FLAG_v (1<<3)
-#define FLAG_s (1<<4)
-#define FLAG_p (1<<5)
-#define FLAG_D (1<<6)
-#define FLAG_d (1<<7)
-#define FLAG_c (1<<8)
+#define FLAG_t (1<<3)
+#define FLAG_v (1<<4)
+#define FLAG_s (1<<5)
+#define FLAG_p (1<<6)
+#define FLAG_D (1<<7)
+#define FLAG_d (1<<8)
+#define FLAG_c (1<<9)
 #endif
 
 #ifdef FOR_ionice
@@ -4645,6 +4903,17 @@
 #define FLAG_a (FORCED_FLAG<<9)
 #endif
 
+#ifdef FOR_jobs
+#ifndef TT
+#define TT this.jobs
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_r (FORCED_FLAG<<1)
+#define FLAG_p (FORCED_FLAG<<2)
+#define FLAG_n (FORCED_FLAG<<3)
+#define FLAG_l (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_kill
 #ifndef TT
 #define TT this.kill
@@ -4727,9 +4996,9 @@
 #ifndef TT
 #define TT this.logger
 #endif
-#define FLAG_p (FORCED_FLAG<<0)
-#define FLAG_t (FORCED_FLAG<<1)
-#define FLAG_s (FORCED_FLAG<<2)
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_p (FORCED_FLAG<<1)
+#define FLAG_t (FORCED_FLAG<<2)
 #endif
 
 #ifdef FOR_login
@@ -5037,11 +5306,12 @@
 #define TT this.mv
 #endif
 #define FLAG_T (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_f (1<<2)
-#define FLAG_F (1<<3)
-#define FLAG_n (1<<4)
-#define FLAG_v (1<<5)
+#define FLAG_t (1<<1)
+#define FLAG_i (1<<2)
+#define FLAG_f (1<<3)
+#define FLAG_F (1<<4)
+#define FLAG_n (1<<5)
+#define FLAG_v (1<<6)
 #endif
 
 #ifdef FOR_nbd_client
@@ -5068,7 +5338,8 @@
 #define FLAG_w (1<<9)
 #define FLAG_L (1<<10)
 #define FLAG_l (1<<11)
-#define FLAG_t (1<<12)
+#define FLAG_E (1<<12)
+#define FLAG_t (1<<13)
 #endif
 
 #ifdef FOR_netstat
@@ -5303,6 +5574,7 @@
 #define TT this.printenv
 #endif
 #define FLAG_0 (1<<0)
+#define FLAG_null (1<<1)
 #endif
 
 #ifdef FOR_printf
@@ -5354,6 +5626,24 @@
 #define FLAG_a (1<<0)
 #endif
 
+#ifdef FOR_pwgen
+#ifndef TT
+#define TT this.pwgen
+#endif
+#define FLAG_0 (FORCED_FLAG<<0)
+#define FLAG_A (FORCED_FLAG<<1)
+#define FLAG_v (FORCED_FLAG<<2)
+#define FLAG_1 (FORCED_FLAG<<3)
+#define FLAG_C (FORCED_FLAG<<4)
+#define FLAG_h (FORCED_FLAG<<5)
+#define FLAG_B (FORCED_FLAG<<6)
+#define FLAG_s (FORCED_FLAG<<7)
+#define FLAG_y (FORCED_FLAG<<8)
+#define FLAG_n (FORCED_FLAG<<9)
+#define FLAG_c (FORCED_FLAG<<10)
+#define FLAG_r (FORCED_FLAG<<11)
+#endif
+
 #ifdef FOR_readahead
 #ifndef TT
 #define TT this.readahead
@@ -5372,9 +5662,10 @@
 #define FLAG_n (1<<5)
 #define FLAG_l (1<<6)
 #define FLAG_h (1<<7)
-#define FLAG_d (1<<8)
-#define FLAG_a (1<<9)
-#define FLAG_dyn_syms (1<<10)
+#define FLAG_e (1<<8)
+#define FLAG_d (1<<9)
+#define FLAG_a (1<<10)
+#define FLAG_dyn_syms (1<<11)
 #endif
 
 #ifdef FOR_readlink
@@ -5478,6 +5769,22 @@
 #define FLAG_n (FORCED_FLAG<<2)
 #endif
 
+#ifdef FOR_rtcwake
+#ifndef TT
+#define TT this.rtcwake
+#endif
+#define FLAG_v (1<<0)
+#define FLAG_u (1<<1)
+#define FLAG_t (1<<2)
+#define FLAG_s (1<<3)
+#define FLAG_m (1<<4)
+#define FLAG_l (1<<5)
+#define FLAG_d (1<<6)
+#define FLAG_a (1<<7)
+#define FLAG_auto (1<<8)
+#define FLAG_list_modes (1<<9)
+#endif
+
 #ifdef FOR_runcon
 #ifndef TT
 #define TT this.runcon
@@ -5488,15 +5795,16 @@
 #ifndef TT
 #define TT this.sed
 #endif
-#define FLAG_z (1<<0)
-#define FLAG_r (1<<1)
-#define FLAG_E (1<<2)
-#define FLAG_n (1<<3)
-#define FLAG_i (1<<4)
-#define FLAG_f (1<<5)
-#define FLAG_e (1<<6)
-#define FLAG_version (1<<7)
-#define FLAG_help (1<<8)
+#define FLAG_s (1<<0)
+#define FLAG_z (1<<1)
+#define FLAG_r (1<<2)
+#define FLAG_E (1<<3)
+#define FLAG_n (1<<4)
+#define FLAG_i (1<<5)
+#define FLAG_f (1<<6)
+#define FLAG_e (1<<7)
+#define FLAG_version (1<<8)
+#define FLAG_help (1<<9)
 #endif
 
 #ifdef FOR_sendevent
@@ -5514,6 +5822,12 @@
 #define FLAG_f (1<<2)
 #endif
 
+#ifdef FOR_set
+#ifndef TT
+#define TT this.set
+#endif
+#endif
+
 #ifdef FOR_setenforce
 #ifndef TT
 #define TT this.setenforce
@@ -5560,6 +5874,21 @@
 #define FLAG_b (1<<2)
 #endif
 
+#ifdef FOR_sha3sum
+#ifndef TT
+#define TT this.sha3sum
+#endif
+#define FLAG_a (FORCED_FLAG<<0)
+#define FLAG_S (FORCED_FLAG<<1)
+#define FLAG_b (FORCED_FLAG<<2)
+#endif
+
+#ifdef FOR_shift
+#ifndef TT
+#define TT this.shift
+#endif
+#endif
+
 #ifdef FOR_shred
 #ifndef TT
 #define TT this.shred
@@ -5645,6 +5974,12 @@
 #define FLAG_g (1<<19)
 #endif
 
+#ifdef FOR_source
+#ifndef TT
+#define TT this.source
+#endif
+#endif
+
 #ifdef FOR_split
 #ifndef TT
 #define TT this.split
@@ -5791,31 +6126,33 @@
 #define FLAG_T (1<<3)
 #define FLAG_X (1<<4)
 #define FLAG_m (1<<5)
-#define FLAG_O (1<<6)
-#define FLAG_S (1<<7)
-#define FLAG_z (1<<8)
-#define FLAG_j (1<<9)
-#define FLAG_J (1<<10)
-#define FLAG_v (1<<11)
-#define FLAG_t (1<<12)
-#define FLAG_x (1<<13)
-#define FLAG_h (1<<14)
-#define FLAG_c (1<<15)
-#define FLAG_k (1<<16)
-#define FLAG_p (1<<17)
-#define FLAG_o (1<<18)
-#define FLAG_to_command (1<<19)
-#define FLAG_owner (1<<20)
-#define FLAG_group (1<<21)
-#define FLAG_mtime (1<<22)
-#define FLAG_mode (1<<23)
-#define FLAG_exclude (1<<24)
-#define FLAG_overwrite (1<<25)
-#define FLAG_no_same_permissions (1<<26)
-#define FLAG_numeric_owner (1<<27)
-#define FLAG_no_recursion (1<<28)
-#define FLAG_full_time (1<<29)
-#define FLAG_restrict (1<<30)
+#define FLAG_P (1<<6)
+#define FLAG_O (1<<7)
+#define FLAG_S (1<<8)
+#define FLAG_z (1<<9)
+#define FLAG_j (1<<10)
+#define FLAG_J (1<<11)
+#define FLAG_I (1<<12)
+#define FLAG_v (1<<13)
+#define FLAG_t (1<<14)
+#define FLAG_x (1<<15)
+#define FLAG_h (1<<16)
+#define FLAG_c (1<<17)
+#define FLAG_k (1<<18)
+#define FLAG_p (1<<19)
+#define FLAG_o (1<<20)
+#define FLAG_to_command (1<<21)
+#define FLAG_owner (1<<22)
+#define FLAG_group (1<<23)
+#define FLAG_mtime (1<<24)
+#define FLAG_mode (1<<25)
+#define FLAG_exclude (1<<26)
+#define FLAG_overwrite (1<<27)
+#define FLAG_no_same_permissions (1<<28)
+#define FLAG_numeric_owner (1<<29)
+#define FLAG_no_recursion (1<<30)
+#define FLAG_full_time (1LL<<31)
+#define FLAG_restrict (1LL<<32)
 #endif
 
 #ifdef FOR_taskset
@@ -6075,6 +6412,12 @@
 #define FLAG_o (1<<6)
 #endif
 
+#ifdef FOR_unicode
+#ifndef TT
+#define TT this.unicode
+#endif
+#endif
+
 #ifdef FOR_uniq
 #ifndef TT
 #define TT this.uniq
@@ -6101,6 +6444,15 @@
 #endif
 #endif
 
+#ifdef FOR_unset
+#ifndef TT
+#define TT this.unset
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#define FLAG_v (FORCED_FLAG<<1)
+#define FLAG_f (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_unshare
 #ifndef TT
 #define TT this.unshare
@@ -6197,6 +6549,13 @@
 #endif
 #endif
 
+#ifdef FOR_wait
+#ifndef TT
+#define TT this.wait
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_watch
 #ifndef TT
 #define TT this.watch
@@ -6208,6 +6567,15 @@
 #define FLAG_n (1<<4)
 #endif
 
+#ifdef FOR_watchdog
+#ifndef TT
+#define TT this.watchdog
+#endif
+#define FLAG_T (FORCED_FLAG<<0)
+#define FLAG_t (FORCED_FLAG<<1)
+#define FLAG_F (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_wc
 #ifndef TT
 #define TT this.wc
diff --git a/android/device/generated/globals.h b/android/device/generated/globals.h
index 4a201bf..7d5e3be 100644
--- a/android/device/generated/globals.h
+++ b/android/device/generated/globals.h
@@ -7,7 +7,7 @@
 // toys/example/demo_number.c
 
 struct demo_number_data {
-  long D;
+  long M, D;
 };
 
 // toys/example/hello.c
@@ -73,10 +73,10 @@
 struct md5sum_data {
   int sawline;
 
+  unsigned *md5table;
   // Crypto variables blanked after summing
-  unsigned state[5];
-  unsigned oldstate[5];
-  uint64_t count;
+  unsigned state[5], oldstate[5];
+  unsigned long long count;
   union {
     char c[64];
     unsigned i[16];
@@ -124,7 +124,7 @@
 struct seq_data {
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 };
 
 // toys/lsb/su.c
@@ -159,10 +159,10 @@
 // toys/net/microcom.c
 
 struct microcom_data {
-  char *s;
+  long s;
 
-  int fd;
-  struct termios original_stdin_state, original_fd_state;
+  int fd, stok;
+  struct termios old_stdin, old_fd;
 };
 
 // toys/net/netcat.c
@@ -177,7 +177,7 @@
 struct netstat_data {
   struct num_cache *inodes;
   int wpad;
-};;
+};
 
 // toys/net/ping.c
 
@@ -214,8 +214,15 @@
 
 struct base64_data {
   long w;
-
   unsigned total;
+  unsigned n;  // number of bits used in encoding. 5 for base32, 6 for base64
+  unsigned align;  // number of bits to align to
+};
+
+// toys/other/blkdiscard.c
+
+struct blkdiscard_data {
+  long o, l;
 };
 
 // toys/other/blkid.c
@@ -270,15 +277,17 @@
   char *data;
   long long len, base;
   int numlen, undo, undolen;
-  unsigned height;
+  unsigned rows, cols;
+  long long pos;
+  char keybuf[16];
+  char input[80];
+  char *search;
 };
 
 // toys/other/hwclock.c
 
 struct hwclock_data {
   char *f;
-
-  int utc;
 };
 
 // toys/other/ionice.c
@@ -375,12 +384,32 @@
   char *c;
 };
 
+// toys/other/pwgen.c
+
+struct pwgen_data {
+  char *r;
+};
+
+// toys/other/rtcwake.c
+
+struct rtcwake_data {
+  long t, s;
+  char *m, *d;
+};
+
 // toys/other/setfattr.c
 
 struct setfattr_data {
   char *x, *v, *n;
 };
 
+// toys/other/sha3sum.c
+
+struct sha3sum_data {
+  long a;
+  unsigned long long rc[24];
+};
+
 // toys/other/shred.c
 
 struct shred_data {
@@ -448,6 +477,14 @@
   pid_t pid, oldpid;
 };
 
+// toys/other/watchdog.c
+
+struct watchdog_data {
+  long T, t;
+
+  int fd;
+};
+
 // toys/other/xxd.c
 
 struct xxd_data {
@@ -496,11 +533,10 @@
 
 struct bootchartd_data {
   char buf[32];
-  long smpl_period_usec;
+  long msec;
   int proc_accounting;
-  int is_login;
 
-  pid_t cur_pid;
+  pid_t pid;
 };
 
 // toys/pending/brctl.c
@@ -509,6 +545,12 @@
     int sockfd;
 };
 
+// toys/pending/chsh.c
+
+struct chsh_data {
+  char *s;
+};
+
 // toys/pending/crond.c
 
 struct crond_data {
@@ -542,7 +584,7 @@
     unsigned long long offset;
   } in, out;
   unsigned conv, iflag, oflag;
-};;
+};
 
 // toys/pending/dhcp.c
 
@@ -578,7 +620,7 @@
 struct dhcpd_data {
     char *iface;
     long port;
-};;
+};
 
 // toys/pending/diff.c
 
@@ -653,17 +695,12 @@
 // toys/pending/getty.c
 
 struct getty_data {
-  char *issue_str;
-  char *login_str;
-  char *init_str;
-  char *host_str; 
-  long timeout;
-  
-  char *tty_name;  
-  int  speeds[20];
-  int  sc;              
+  char *f, *l, *I, *H;
+  long t;
+
+  char *tty_name, buff[128];
+  int speeds[20], sc;
   struct termios termios;
-  char buff[128];
 };
 
 // toys/pending/groupadd.c
@@ -770,11 +807,9 @@
 struct modprobe_data {
   struct arg_list *dirs;
 
-  struct arg_list *probes;
-  struct arg_list *dbase[256];
+  struct arg_list *probes, *dbase[256];
   char *cmdopts;
-  int nudeps;
-  uint8_t symreq;
+  int nudeps, symreq;
 };
 
 // toys/pending/more.c
@@ -787,7 +822,7 @@
 // toys/pending/openvt.c
 
 struct openvt_data {
-  unsigned long vt_num;
+  long c;
 };
 
 // toys/pending/readelf.c
@@ -796,53 +831,96 @@
   char *x, *p;
 
   char *elf, *shstrtab, *f;
-  long long shoff, phoff, size;
-  int bits, shnum, shentsize, phentsize;
-  int64_t (*elf_int)(void *ptr, unsigned size);
+  unsigned long long shoff, phoff, size, shstrtabsz;
+  int bits, endian, shnum, shentsize, phentsize;
 };
 
 // toys/pending/route.c
 
 struct route_data {
-  char *family;
+  char *A;
 };
 
 // toys/pending/sh.c
 
 struct sh_data {
-  char *c;
+  union {
+    struct {
+      char *c;
+    } sh;
+    struct {
+      char *a;
+    } exec;
+  };
 
-  long lineno;
-  char **locals, *subshell_env;
-  struct double_list functions;
-  unsigned options, jobcnt, loc_ro, loc_magic;
-  int hfd;  // next high filehandle (>= 10)
+  // keep SECONDS here: used to work around compiler limitation in run_command()
+  long long SECONDS;
+  char *isexec, *wcpat;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
 
-  // Running jobs.
-  struct sh_job {
-    struct sh_job *next, *prev;
-    unsigned jobno;
+  // Callable function array
+  struct sh_function {
+    char *name;
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
+      struct sh_pipeline *next, *prev, *end;
+      int count, here, type, lineno;
+      struct sh_arg {
+        char **v;
+        int c;
+      } arg[1];
+    } *pipeline;
+    unsigned long refcount;
+  } **functions;
+  long funcslen;
 
-    // Every pipeline has at least one set of arguments or it's Not A Thing
-    struct sh_arg {
-      char **v;
-      int c;
-    } pipeline;
+  // runtime function call stack
+  struct sh_fcall {
+    struct sh_fcall *next, *prev;
 
-    // null terminated array of running processes in pipeline
-    struct sh_process {
-      struct sh_process *next, *prev;
-      struct arg_list *delete;   // expanded strings
-      int *urd, envlen, pid, exit;  // undo redirects, child PID, exit status
-      struct sh_arg arg;
-    } *procs, *proc;
-  } *jobs, *job;
+    // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+    struct sh_vars {
+      long flags;
+      char *str;
+    } *vars;
+    long varslen, shift;
+
+    struct sh_function *func; // TODO wire this up
+    struct sh_pipeline *pl;
+    char *ifs;
+    struct sh_arg arg;
+    struct arg_list *delete;
+
+    // Runtime stack of nested if/else/fi and for/do/done contexts.
+    struct sh_blockstack {
+      struct sh_blockstack *next;
+      struct sh_pipeline *start, *middle;
+      struct sh_process *pp;       // list of processes piping in to us
+      int run, loop, *urd, pout, pipe;
+      struct sh_arg farg;          // for/select arg stack, case wildcard deck
+      struct arg_list *fdelete;    // farg's cleanup list
+      char *fvar;                  // for/select's iteration variable name
+    } *blk;
+  } *ff;
+
+// TODO ctrl-Z suspend should stop script
+  struct sh_process {
+    struct sh_process *next, *prev; // | && ||
+    struct arg_list *delete;   // expanded strings
+    // undo redirects, a=b at start, child PID, exit status, has !, job #
+    int *urd, envlen, pid, exit, not, job, dash;
+    long long when; // when job backgrounded/suspended
+    struct sh_arg *raw, arg;
+  } *pp; // currently running process
+
+  // job list, command line for $*, scratch space for do_wildcard_files()
+  struct sh_arg jobs, *wcdeck;
 };
 
 // toys/pending/stty.c
 
 struct stty_data {
-  char *device;
+  char *F;
 
   int fd, col;
   unsigned output_cols;
@@ -890,20 +968,13 @@
 // toys/pending/telnet.c
 
 struct telnet_data {
-  int port;
-  int sfd;
-  char buff[128];
-  int pbuff;
-  char iac[256];
-  int piac;
-  char *ttype;
-  struct termios def_term;
+  int sock;
+  char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
+  struct termios old_term;
   struct termios raw_term;
-  uint8_t term_ok;
-  uint8_t term_mode;
-  uint8_t flags;
-  unsigned win_width;
-  unsigned win_height;
+  uint8_t mode;
+  int echo, sga;
+  int state, request;
 };
 
 // toys/pending/telnetd.c
@@ -984,37 +1055,28 @@
 // toys/pending/vi.c
 
 struct vi_data {
-    char *s;
-    int cur_col;
-    int cur_row;
-    int scr_row;
-    int drawn_row;
-    int drawn_col;
-    unsigned screen_height;
-    unsigned screen_width;
-    int vi_mode;
-    int count0;
-    int count1;
-    int vi_mov_flag;
-    int modified;
-    char vi_reg;
-    char *last_search;
-    int tabstop;
-    int list;
-    struct str_line {
-      int alloc;
-      int len;
-      char *data;
-    } *il;
-    size_t screen; //offset in slices must be higher than cursor
-    size_t cursor; //offset in slices
-    //yank buffer
-    struct yank_buf {
-      char reg;
-      int alloc;
-      char* data;
-    } yank;
+  char *s;
+  int vi_mode, tabstop, list;
+  int cur_col, cur_row, scr_row;
+  int drawn_row, drawn_col;
+  int count0, count1, vi_mov_flag;
+  unsigned screen_height, screen_width;
+  char vi_reg, *last_search;
+  struct str_line {
+    int alloc;
+    int len;
+    char *data;
+  } *il;
+  size_t screen, cursor; //offsets
+  //yank buffer
+  struct yank_buf {
+    char reg;
+    int alloc;
+    char* data;
+  } yank;
 
+  int modified;
+  size_t filesize;
 // mem_block contains RO data that is either original file as mmap
 // or heap allocated inserted data
 //
@@ -1048,10 +1110,6 @@
       const char *data;
     } *node;
   } *slices;
-
-  size_t filesize;
-  int fd; //file_handle
-
 };
 
 // toys/pending/wget.c
@@ -1106,11 +1164,11 @@
   union {
     // install's options
     struct {
-      char *g, *o, *m;
+      char *g, *o, *m, *t;
     } i;
     // cp's options
     struct {
-      char *preserve;
+      char *t, *preserve;
     } c;
   };
 
@@ -1125,7 +1183,7 @@
 // toys/posix/cpio.c
 
 struct cpio_data {
-  char *F, *p, *H;
+  char *F, *H;
 };
 
 // toys/posix/cut.c
@@ -1141,7 +1199,7 @@
 // toys/posix/date.c
 
 struct date_data {
-  char *r, *D, *d;
+  char *r, *I, *D, *d;
 
   unsigned nano;
 };
@@ -1151,9 +1209,7 @@
 struct df_data {
   struct arg_list *t;
 
-  long units;
-  int column_widths[5];
-  int header_shown;
+  int units, width[6];
 };
 
 // toys/posix/du.c
@@ -1170,7 +1226,7 @@
 
 struct env_data {
   struct arg_list *u;
-};;
+};
 
 // toys/posix/expand.c
 
@@ -1360,7 +1416,7 @@
   dev_t tty;
   void *fields, *kfields;
   long long ticks, bits, time;
-  int kcount, forcek, sortpos;
+  int kcount, forcek, sortpos, pidlen;
   int (*match_process)(long long *slot);
   void (*show_process)(void *tb);
 };
@@ -1429,7 +1485,7 @@
 struct tar_data {
   char *f, *C;
   struct arg_list *T, *X;
-  char *to_command, *owner, *group, *mtime, *mode;
+  char *I, *to_command, *owner, *group, *mtime, *mode;
   struct arg_list *exclude;
 
   struct double_list *incl, *excl, *seen;
@@ -1462,6 +1518,7 @@
 
 struct tee_data {
   void *outputs;
+  int out;
 };
 
 // toys/posix/touch.c
@@ -1502,7 +1559,7 @@
   long s, n, P;
   char *E;
 
-  long entries, bytes;
+  long entries, bytes, np;
   char delim;
   FILE *tty;
 };
@@ -1535,6 +1592,7 @@
 	struct tunctl_data tunctl;
 	struct acpi_data acpi;
 	struct base64_data base64;
+	struct blkdiscard_data blkdiscard;
 	struct blkid_data blkid;
 	struct blockdev_data blockdev;
 	struct chrt_data chrt;
@@ -1556,7 +1614,10 @@
 	struct modinfo_data modinfo;
 	struct nsenter_data nsenter;
 	struct oneit_data oneit;
+	struct pwgen_data pwgen;
+	struct rtcwake_data rtcwake;
 	struct setfattr_data setfattr;
+	struct sha3sum_data sha3sum;
 	struct shred_data shred;
 	struct stat_data stat;
 	struct swapon_data swapon;
@@ -1565,12 +1626,14 @@
 	struct timeout_data timeout;
 	struct truncate_data truncate;
 	struct watch_data watch;
+	struct watchdog_data watchdog;
 	struct xxd_data xxd;
 	struct arp_data arp;
 	struct arping_data arping;
 	struct bc_data bc;
 	struct bootchartd_data bootchartd;
 	struct brctl_data brctl;
+	struct chsh_data chsh;
 	struct crond_data crond;
 	struct crontab_data crontab;
 	struct dd_data dd;
diff --git a/android/device/generated/help.h b/android/device/generated/help.h
index 55613a5..ebddd3b 100644
--- a/android/device/generated/help.h
+++ b/android/device/generated/help.h
@@ -1,4 +1,4 @@
-#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a toolchain scripts/mcm-buildall.sh patches musl to fix this."
+#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a scripts/mcm-buildall.sh toolchain patches musl to fix this."
 
 #define HELP_toybox_uid_usr "When commands like useradd/groupadd allocate user IDs, start here."
 
@@ -12,8 +12,6 @@
 
 #define HELP_toybox_free "When a program exits, the operating system will clean up after it\n(free memory, close files, etc). To save size, toybox usually relies\non this behavior. If you're running toybox under a debugger or\nwithout a real OS (ala newlib+libgloss), enable this to make toybox\nclean up after itself."
 
-#define HELP_toybox_i18n "Support for UTF-8 character sets, and some locale support."
-
 #define HELP_toybox_help_dashdash "Support --help argument in all commands, even ones with a NULL\noptstring. (Use TOYFLAG_NOHELP to disable.) Produces the same output\nas \"help command\". --version shows toybox version."
 
 #define HELP_toybox_help "Include help text for each command."
@@ -32,7 +30,7 @@
 
 #define HELP_toybox_suid "Support for the Set User ID bit, to install toybox suid root and drop\npermissions for commands which do not require root access. To use\nthis change ownership of the file to the root user and set the suid\nbit in the file permissions:\n\nchown root:root toybox; chmod +s toybox\n\nprompt \"Security Blanket\"\ndefault TOYBOX_LSM_NONE\nhelp\nSelect a Linux Security Module to complicate your system\nuntil you can't find holes in it."
 
-#define HELP_toybox "usage: toybox [--long | --help | --version | [command] [arguments...]]\n\nWith no arguments, shows available commands. First argument is\nname of a command to run, followed by any arguments to that command.\n\n--long	Show path to each command\n\nTo install command symlinks with paths, try:\n  for i in $(/bin/toybox --long); do ln -s /bin/toybox $i; done\nor all in one directory:\n  for i in $(./toybox); do ln -s toybox $i; done; PATH=$PWD:$PATH\n\nMost toybox commands also understand the following arguments:\n\n--help		Show command help (only)\n--version	Show toybox version (only)\n\nThe filename \"-\" means stdin/stdout, and \"--\" stops argument parsing.\n\nNumerical arguments accept a single letter suffix for\nkilo, mega, giga, tera, peta, and exabytes, plus an additional\n\"d\" to indicate decimal 1000's instead of 1024.\n\nDurations can be decimal fractions and accept minute (\"m\"), hour (\"h\"),\nor day (\"d\") suffixes (so 0.1m = 6s)."
+#define HELP_toybox "usage: toybox [--long | --help | --version | [COMMAND] [ARGUMENTS...]]\n\nWith no arguments, \"toybox\" shows available COMMAND names. Add --long\nto include suggested install path for each command, see\nhttps://landley.net/toybox/faq.html#install for details.\n\nFirst argument is name of a COMMAND to run, followed by any ARGUMENTS\nto that command. Most toybox commands also understand:\n\n--help		Show command help (only)\n--version	Show toybox version (only)\n\nThe filename \"-\" means stdin/stdout, and \"--\" stops argument parsing.\n\nNumerical arguments accept a single letter suffix for\nkilo, mega, giga, tera, peta, and exabytes, plus an additional\n\"d\" to indicate decimal 1000's instead of 1024.\n\nDurations can be decimal fractions and accept minute (\"m\"), hour (\"h\"),\nor day (\"d\") suffixes (so 0.1m = 6s)."
 
 #define HELP_setenforce "usage: setenforce [enforcing|permissive|1|0]\n\nSets whether SELinux is enforcing (1) or permissive (0)."
 
@@ -62,7 +60,7 @@
 
 #define HELP_demo_scankey "usage: demo_scankey\n\nMove a letter around the screen. Hit ESC to exit."
 
-#define HELP_demo_number "usage: demo_number [-hsbi] NUMBER...\n\n-b	Use \"B\" for single byte units (HR_B)\n-d	Decimal units\n-h	Human readable\n-s	Space between number and units (HR_SPACE)"
+#define HELP_demo_number "usage: demo_number [-hsbi] [-D LEN] NUMBER...\n\n-D	output field is LEN chars\n-M	input units (index into bkmgtpe)\n-c	Comma comma down do be do down down\n-b	Use \"B\" for single byte units (HR_B)\n-d	Decimal units\n-h	Human readable\n-s	Space between number and units (HR_SPACE)"
 
 #define HELP_demo_many_options "usage: demo_many_options -[a-zA-Z]\n\nPrint the optflags value of the command arguments, in hex."
 
@@ -70,7 +68,7 @@
 
 #define HELP_sync "usage: sync\n\nWrite pending cached data to disk (synchronize), blocking until done."
 
-#define HELP_su "usage: su [-lp] [-u UID] [-g GID,...] [-s SHELL] [-c CMD] [USER [COMMAND...]]\n\nSwitch user, prompting for password of new user when not run as root.\n\nWith one argument, switch to USER and run user's shell from /etc/passwd.\nWith no arguments, USER is root. If COMMAND line provided after USER,\nexec() it as new USER (bypasing shell). If -u or -g specified, first\nargument (if any) isn't USER (it's COMMAND).\n\nfirst argument is USER name to switch to (which must exist).\nNon-root users are prompted for new user's password.\n\n-s	Shell to use (default is user's shell from /etc/passwd)\n-c	Command line to pass to -s shell (ala sh -c \"CMD\")\n-l	Reset environment as if new login.\n-u	Switch to UID instead of USER\n-g	Switch to GID (only root allowed, can be comma separated list)\n-p	Preserve environment (except for $PATH and $IFS)"
+#define HELP_su "usage: su [-lp] [-u UID] [-g GID,...] [-s SHELL] [-c CMD] [USER [COMMAND...]]\n\nSwitch user, prompting for password of new user when not run as root.\n\nWith one argument, switch to USER and run user's shell from /etc/passwd.\nWith no arguments, USER is root. If COMMAND line provided after USER,\nexec() it as new USER (bypassing shell). If -u or -g specified, first\nargument (if any) isn't USER (it's COMMAND).\n\nfirst argument is USER name to switch to (which must exist).\nNon-root users are prompted for new user's password.\n\n-s	Shell to use (default is user's shell from /etc/passwd)\n-c	Command line to pass to -s shell (ala sh -c \"CMD\")\n-l	Reset environment as if new login.\n-u	Switch to UID instead of USER\n-g	Switch to GID (only root allowed, can be comma separated list)\n-p	Preserve environment (except for $PATH and $IFS)"
 
 #define HELP_seq "usage: seq [-w|-f fmt_str] [-s sep_str] [first] [increment] last\n\nCount from first to last, by increment. Omitted arguments default\nto 1. Two arguments are used as first and last. Arguments can be\nnegative or floating point.\n\n-f	Use fmt_str as a printf-style floating point format string\n-s	Use sep_str as separator, default is a newline character\n-w	Pad to equal width with leading zeroes"
 
@@ -116,7 +114,7 @@
 
 #define HELP_tunctl "usage: tunctl [-dtT] [-u USER] NAME\n\nCreate and delete tun/tap virtual ethernet devices.\n\n-T	Use tap (ethernet frames) instead of tun (ip packets)\n-d	Delete tun/tap device\n-t	Create tun/tap device\n-u	Set owner (user who can read/write device without root access)"
 
-#define HELP_sntp "usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]\n\nSimple Network Time Protocol client. Query SERVER and display time.\n\n-p	Use PORT (default 123)\n-s	Set system clock suddenly\n-a	Adjust system clock gradually\n-S	Serve time instead of querying (bind to SERVER address if specified)\n-m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)\n-M	Multicast server on ADDRESS (deault 224.0.0.1)\n-t	TTL (multicast only, default 1)\n-d	Daemonize (run in background re-querying )\n-D	Daemonize but stay in foreground: re-query time every 1000 seconds\n-r	Retry shift (every 1<<SHIFT seconds)\n-q	Quiet (don't display time)"
+#define HELP_sntp "usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]\n\nSimple Network Time Protocol client. Query SERVER and display time.\n\n-p	Use PORT (default 123)\n-s	Set system clock suddenly\n-a	Adjust system clock gradually\n-S	Serve time instead of querying (bind to SERVER address if specified)\n-m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)\n-M	Multicast server on ADDRESS (default 224.0.0.1)\n-t	TTL (multicast only, default 1)\n-d	Daemonize (run in background re-querying )\n-D	Daemonize but stay in foreground: re-query time every 1000 seconds\n-r	Retry shift (every 1<<SHIFT seconds)\n-q	Quiet (don't display time)"
 
 #define HELP_rfkill "usage: rfkill COMMAND [DEVICE]\n\nEnable/disable wireless devices.\n\nCommands:\nlist [DEVICE]   List current state\nblock DEVICE    Disable device\nunblock DEVICE  Enable device\n\nDEVICE is an index number, or one of:\nall, wlan(wifi), bluetooth, uwb(ultrawideband), wimax, wwan, gps, fm."
 
@@ -124,9 +122,9 @@
 
 #define HELP_netstat "usage: netstat [-pWrxwutneal]\n\nDisplay networking information. Default is netstat -tuwx\n\n-r	Routing table\n-a	All sockets (not just connected)\n-l	Listening server sockets\n-t	TCP sockets\n-u	UDP sockets\n-w	Raw sockets\n-x	Unix sockets\n-e	Extended info\n-n	Don't resolve names\n-W	Wide display\n-p	Show PID/program name of sockets"
 
-#define HELP_netcat "usage: netcat [-46Ut] [-lL COMMAND...] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME}\n\nForward stdin/stdout to a file or network connection.\n\n-4	Force IPv4\n-6	Force IPv6\n-L	Listen for multiple incoming connections (server mode)\n-U	Use a UNIX domain socket\n-W	SECONDS timeout for more data on an idle connection\n-f	Use FILENAME (ala /dev/ttyS0) instead of network\n-l	Listen for one incoming connection\n-p	Local port number\n-q	Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet\n-s	Local source address\n-t	Allocate tty (must come before -l or -L)\n-u	Use UDP\n-w	SECONDS timeout to establish connection\n\nUse \"stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho\" with\nnetcat -f to connect to a serial port.\n\nThe command line after -l or -L is executed (as a child process) to handle\neach incoming connection. If blank -l waits for a connection and forwards\nit to stdin/stdout. If no -p specified, -l prints port it bound to and\nbackgrounds itself (returning immediately).\n\nFor a quick-and-dirty server, try something like:\nnetcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l"
+#define HELP_netcat "usage: netcat [-46ELUlt] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME|COMMAND...}\n\nForward stdin/stdout to a file or network connection.\n\n-4	Force IPv4\n-6	Force IPv6\n-E	Forward stderr\n-L	Listen and background each incoming connection (server mode)\n-U	Use a UNIX domain socket\n-W	SECONDS timeout for more data on an idle connection\n-f	Use FILENAME (ala /dev/ttyS0) instead of network\n-l	Listen for one incoming connection, then exit\n-p	Local port number\n-q	Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet\n-s	Local source address\n-t	Allocate tty\n-u	Use UDP\n-w	SECONDS timeout to establish connection\n\nUse \"stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho\" with\nnetcat -f to connect to a serial port.\n\nWhen listening the COMMAND line is executed as a child process to handle\nan incoming connection. With no COMMAND -l forwards the connection\nto stdin/stdout. If no -p specified, -l prints the port it bound to and\nbackgrounds itself (returning immediately).\n\nFor a quick-and-dirty server, try something like:\nnetcat -s 127.0.0.1 -p 1234 -tL sh -l"
 
-#define HELP_microcom "usage: microcom [-s SPEED] [-X] DEVICE\n\nSimple serial console.\n\n-s	Set baud rate to SPEED\n-X	Ignore ^@ (send break) and ^] (exit)"
+#define HELP_microcom "usage: microcom [-s SPEED] [-X] DEVICE\n\nSimple serial console.\n\n-s	Set baud rate to SPEED (default 115200)\n-X	Ignore ^@ (send break) and ^] (exit)"
 
 #define HELP_ifconfig "usage: ifconfig [-aS] [INTERFACE [ACTION...]]\n\nDisplay or configure network interface.\n\nWith no arguments, display active interfaces. First argument is interface\nto operate on, one argument by itself displays that interface.\n\n-a	All interfaces displayed, not just active ones\n-S	Short view, one line per interface\n\nStandard ACTIONs to perform on an INTERFACE:\n\nADDR[/MASK]        - set IPv4 address (1.2.3.4/5) and activate interface\nadd|del ADDR[/LEN] - add/remove IPv6 address (1111::8888/128)\nup|down            - activate or deactivate interface\n\nAdvanced ACTIONs (default values usually suffice):\n\ndefault          - remove IPv4 address\nnetmask ADDR     - set IPv4 netmask via 255.255.255.0 instead of /24\ntxqueuelen LEN   - number of buffered packets before output blocks\nmtu LEN          - size of outgoing packets (Maximum Transmission Unit)\nbroadcast ADDR   - Set broadcast address\npointopoint ADDR - PPP and PPPOE use this instead of \"route add default gw\"\nhw TYPE ADDR     - set hardware (mac) address (type = ether|infiniband)\n\nFlags you can set on an interface (or -remove by prefixing with -):\n\narp       - don't use Address Resolution Protocol to map LAN routes\npromisc   - don't discard packets that aren't to this LAN hardware address\nmulticast - force interface into multicast mode if the driver doesn't\nallmulti  - promisc for multicast packets"
 
@@ -140,6 +138,8 @@
 
 #define HELP_which "usage: which [-a] filename ...\n\nSearch $PATH for executable files matching filename(s).\n\n-a	Show all matches"
 
+#define HELP_watchdog "usage: watchdog [-F] [-t UPDATE] [-T DEADLINE] DEV\n\nStart the watchdog timer at DEV with optional timeout parameters.\n\n-F	run in the foreground (do not daemonize)\n-t	poke watchdog every UPDATE seconds (default 4)\n-T	reboot if not poked for DEADLINE seconds (default 60)"
+
 #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\")"
 
 #define HELP_w "usage: w\n\nShow who is logged on and since how long they logged in."
@@ -172,14 +172,18 @@
 
 #define HELP_swapoff "usage: swapoff swapregion\n\nDisable swapping on a given swapregion."
 
-#define HELP_stat "usage: stat [-tfL] [-c FORMAT] FILE...\n\nDisplay status of files or filesystems.\n\n-c	Output specified FORMAT string instead of default\n-f	Display filesystem status instead of file status\n-L	Follow symlinks\n-t	terse (-c \"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\")\n	      (with -f = -c \"%n %i %l %t %s %S %b %f %a %c %d\")\n\nThe valid format escape sequences for files:\n%a  Access bits (octal) |%A  Access bits (flags)|%b  Size/512\n%B  Bytes per %b (512)  |%C  Security context   |%d  Device ID (dec)\n%D  Device ID (hex)     |%f  All mode bits (hex)|%F  File type\n%g  Group ID            |%G  Group name         |%h  Hard links\n%i  Inode               |%m  Mount point        |%n  Filename\n%N  Long filename       |%o  I/O block size     |%s  Size (bytes)\n%t  Devtype major (hex) |%T  Devtype minor (hex)|%u  User ID\n%U  User name           |%x  Access time        |%X  Access unix time\n%y  Modification time   |%Y  Mod unix time      |%z  Creation time\n%Z  Creation unix time\n\nThe valid format escape sequences for filesystems:\n%a  Available blocks    |%b  Total blocks       |%c  Total inodes\n%d  Free inodes         |%f  Free blocks        |%i  File system ID\n%l  Max filename length |%n  File name          |%s  Fragment size\n%S  Best transfer size  |%t  FS type (hex)      |%T  FS type (driver name)"
+#define HELP_stat "usage: stat [-tfL] [-c FORMAT] FILE...\n\nDisplay status of files or filesystems.\n\n-c	Output specified FORMAT string instead of default\n-f	Display filesystem status instead of file status\n-L	Follow symlinks\n-t	terse (-c \"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\")\n	      (with -f = -c \"%n %i %l %t %s %S %b %f %a %c %d\")\n\nThe valid format escape sequences for files:\n%a  Access bits (octal) |%A  Access bits (flags)|%b  Size/512\n%B  Bytes per %b (512)  |%C  Security context   |%d  Device ID (dec)\n%D  Device ID (hex)     |%f  All mode bits (hex)|%F  File type\n%g  Group ID            |%G  Group name         |%h  Hard links\n%i  Inode               |%m  Mount point        |%n  Filename\n%N  Long filename       |%o  I/O block size     |%s  Size (bytes)\n%t  Devtype major (hex) |%T  Devtype minor (hex)|%u  User ID\n%U  User name           |%x  Access time        |%X  Access unix time\n%y  Modification time   |%Y  Mod unix time      |%z  Creation time\n%Z  Creation unix time\n\nThe valid format escape sequences for filesystems:\n%a  Available blocks    |%b  Total blocks       |%c  Total inodes\n%d  Free inodes         |%f  Free blocks        |%i  File system ID\n%l  Max filename length |%n  File name          |%s  Best transfer size\n%S  Actual block size   |%t  FS type (hex)      |%T  FS type (driver name)"
 
 #define HELP_shred "usage: shred [-fuz] [-n COUNT] [-s SIZE] FILE...\n\nSecurely delete a file by overwriting its contents with random data.\n\n-f		Force (chmod if necessary)\n-n COUNT	Random overwrite iterations (default 1)\n-o OFFSET	Start at OFFSET\n-s SIZE		Use SIZE instead of detecting file size\n-u		Unlink (actually delete file when done)\n-x		Use exact size (default without -s rounds up to next 4k)\n-z		Zero at end\n\nNote: data journaling filesystems render this command useless, you must\noverwrite all free space (fill up disk) to erase old data on those."
 
+#define HELP_sha3sum "usage: sha3sum [-S] [-a BITS] [FILE...]\n\nHash function du jour.\n\n-a	Produce a hash BITS long (default 224)\n-b	Brief (hash only, no filename)\n-S	Use SHAKE termination byte instead of SHA3 (ask FIPS why)"
+
 #define HELP_setsid "usage: setsid [-cdw] command [args...]\n\nRun process in a new session.\n\n-d	Detach from tty\n-c	Control tty (become foreground process & receive keyboard signals)\n-w	Wait for child (and exit with its status)"
 
 #define HELP_setfattr "usage: setfattr [-h] [-x|-n NAME] [-v VALUE] FILE...\n\nWrite POSIX extended attributes.\n\n-h	Do not dereference symlink\n-n	Set given attribute\n-x	Remove given attribute\n-v	Set value for attribute -n (default is empty)"
 
+#define HELP_rtcwake "usage: rtcwake [-aluv] [-d FILE] [-m MODE] [-s SECS] [-t UNIX]\n\nEnter the given sleep state until the given time.\n\n-a	RTC uses time specified in /etc/adjtime\n-d FILE	Device to use (default /dev/rtc)\n-l	RTC uses local time\n-m	Mode (--list-modes to see those supported by your kernel):\n	  standby  S1: default              mem     S3: suspend to RAM\n	  disk     S4: suspend to disk      off     S5: power off\n	  disable  Cancel current alarm     freeze  stop processes/processors\n	  no       just set wakeup time     on      just poll RTC for alarm\n	  show     just show current alarm\n-s SECS	Wake SECS seconds from now\n-t UNIX	Wake UNIX seconds from epoch\n-u	RTC uses UTC\n-v	Verbose"
+
 #define HELP_rmmod "usage: rmmod [-wf] [MODULE]\n\nUnload the module named MODULE from the Linux kernel.\n-f	Force unload of a module\n-w	Wait until the module is no longer used"
 
 #define HELP_rev "usage: rev [FILE...]\n\nOutput each line reversed, when no files are given stdin is used."
@@ -194,6 +198,8 @@
 
 #define HELP_readahead "usage: readahead FILE...\n\nPreload files into disk cache."
 
+#define HELP_pwgen "usage: pwgen [-cAn0yrsBhC1v] [LENGTH] [COUNT]\n\nGenerate human-readable random passwords. When output is to tty produces\na screenfull to defeat shoulder surfing (pick one and clear the screen).\n\n-c  --capitalize                  Permit capital letters.\n-A  --no-capitalize               Don't include capital letters.\n-n  --numerals                    Permit numbers.\n-0  --no-numerals                 Don't include numbers.\n-y  --symbols                     Permit special characters ($#%...).\n-r <chars>  --remove=<chars>      Don't include the given characters.\n-s  --secure                      Generate more random passwords.\n-B  --ambiguous                   Avoid ambiguous characters (e.g. 0, O).\n-h  --help                        Print this help message.\n-C                                Print the output in columns.\n-1                                Print the output one line each.\n-v                                Don't include vowels."
+
 #define HELP_pwdx "usage: pwdx PID...\n\nPrint working directory of processes listed on command line."
 
 #define HELP_printenv "usage: printenv [-0] [env_var...]\n\nPrint environment variables.\n\n-0	Use \\0 as delimiter instead of \\n"
@@ -204,7 +210,7 @@
 
 #define HELP_partprobe "usage: partprobe DEVICE...\n\nTell the kernel about partition table changes\n\nAsk the kernel to re-read the partition table on the specified devices."
 
-#define HELP_oneit "usage: oneit [-p] [-c /dev/tty0] command [...]\n\nSimple init program that runs a single supplied command line with a\ncontrolling tty (so CTRL-C can kill it).\n\n-c	Which console device to use (/dev/console doesn't do CTRL-C, etc)\n-p	Power off instead of rebooting when command exits\n-r	Restart child when it exits\n-3	Write 32 bit PID of each exiting reparented process to fd 3 of child\n	(Blocking writes, child must read to avoid eventual deadlock.)\n\nSpawns a single child process (because PID 1 has signals blocked)\nin its own session, reaps zombies until the child exits, then\nreboots the system (or powers off with -p, or restarts the child with -r).\n\nResponds to SIGUSR1 by halting the system, SIGUSR2 by powering off,\nand SIGTERM or SIGINT reboot."
+#define HELP_oneit "usage: oneit [-prn3] [-c CONSOLE] [COMMAND...]\n\nSimple init program that runs a single supplied command line with a\ncontrolling tty (so CTRL-C can kill it).\n\n-c	Which console device to use (/dev/console doesn't do CTRL-C, etc)\n-p	Power off instead of rebooting when command exits\n-r	Restart child when it exits\n-n	No reboot, just relaunch command line\n-3	Write 32 bit PID of each exiting reparented process to fd 3 of child\n	(Blocking writes, child must read to avoid eventual deadlock.)\n\nSpawns a single child process (because PID 1 has signals blocked)\nin its own session, reaps zombies until the child exits, then\nreboots the system (or powers off with -p, or restarts the child with -r).\n\nResponds to SIGUSR1 by halting the system, SIGUSR2 by powering off,\nand SIGTERM or SIGINT reboot."
 
 #define HELP_nsenter "usage: nsenter [-t pid] [-F] [-i] [-m] [-n] [-p] [-u] [-U] COMMAND...\n\nRun COMMAND in an existing (set of) namespace(s).\n\n-t	PID to take namespaces from    (--target)\n-F	don't fork, even if -p is used (--no-fork)\n\nThe namespaces to switch are:\n\n-i	SysV IPC: message queues, semaphores, shared memory (--ipc)\n-m	Mount/unmount tree (--mount)\n-n	Network address, sockets, routing, iptables (--net)\n-p	Process IDs and init, will fork unless -F is used (--pid)\n-u	Host and domain names (--uts)\n-U	UIDs, GIDs, capabilities (--user)\n\nIf -t isn't specified, each namespace argument must provide a path\nto a namespace file, ala \"-i=/proc/$PID/ns/ipc\""
 
@@ -230,7 +236,7 @@
 
 #define HELP_lspci_text "usage: lspci [-n] [-i FILE ]\n\n-n	Numeric output (repeat for readable and numeric)\n-i	PCI ID database (default /usr/share/misc/pci.ids)"
 
-#define HELP_lspci "usage: lspci [-ekm]\n\nList PCI devices.\n\n-e	Print all 6 digits in class\n-k	Print kernel driver\n-m	Machine parseable format"
+#define HELP_lspci "usage: lspci [-ekm]\n\nList PCI devices.\n\n-e	Print all 6 digits in class\n-k	Print kernel driver\n-m	Machine readable format"
 
 #define HELP_lsmod "usage: lsmod\n\nDisplay the currently loaded modules, their sizes and their dependencies."
 
@@ -258,9 +264,9 @@
 
 #define HELP_i2cdetect "usage: i2cdetect [-ary] BUS [FIRST LAST]\nusage: i2cdetect -F BUS\nusage: i2cdetect -l\n\nDetect i2c devices.\n\n-a	All addresses (0x00-0x7f rather than 0x03-0x77)\n-F	Show functionality\n-l	List all buses\n-r	Probe with SMBus Read Byte\n-y	Answer \"yes\" to confirmation prompts (for script use)"
 
-#define HELP_hwclock "usage: hwclock [-rswtluf]\n\nGet/set the hardware clock.\n\n-f FILE	Use specified device file instead of /dev/rtc (--rtc)\n-l	Hardware clock uses localtime (--localtime)\n-r	Show hardware clock time (--show)\n-s	Set system time from hardware clock (--hctosys)\n-t	Set the system time based on the current timezone (--systz)\n-u	Hardware clock uses UTC (--utc)\n-w	Set hardware clock from system time (--systohc)"
+#define HELP_hwclock "usage: hwclock [-rswtluf]\n\nGet/set the hardware clock.\n\n-f FILE	Use specified device file instead of /dev/rtc0 (--rtc)\n-l	Hardware clock uses localtime (--localtime)\n-r	Show hardware clock time (--show)\n-s	Set system time from hardware clock (--hctosys)\n-t	Set the system time based on the current timezone (--systz)\n-u	Hardware clock uses UTC (--utc)\n-w	Set hardware clock from system time (--systohc)"
 
-#define HELP_hexedit "usage: hexedit FILENAME\n\nHexadecimal file editor. All changes are written to disk immediately.\n\n-r	Read only (display but don't edit)\n\nKeys:\nArrows        Move left/right/up/down by one line/column\nPg Up/Pg Dn   Move up/down by one page\n0-9, a-f      Change current half-byte to hexadecimal value\nu             Undo\nq/^c/^d/<esc> Quit"
+#define HELP_hexedit "usage: hexedit FILE\n\nHexadecimal file editor/viewer. All changes are written to disk immediately.\n\n-r	Read only (display but don't edit)\n\nKeys:\nArrows         Move left/right/up/down by one line/column\nPgUp/PgDn      Move up/down by one page\nHome/End       Start/end of line (start/end of file with ctrl)\n0-9, a-f       Change current half-byte to hexadecimal value\n^J or :        Jump (+/- for relative offset, otherwise absolute address)\n^F or /        Find string (^G/n: next, ^D/p: previous match)\nu              Undo\nq/^C/^Q/Esc    Quit"
 
 #define HELP_help "usage: help [-ahu] [COMMAND]\n\n-a	All commands\n-u	Usage only\n-h	HTML output\n\nShow usage information for toybox commands.\nRun \"toybox\" with no arguments for a list of available commands."
 
@@ -286,7 +292,7 @@
 
 #define HELP_dos2unix "usage: dos2unix [FILE...]\n\nConvert newline format from dos \"\\r\\n\" to unix \"\\n\".\nIf no files listed copy from stdin, \"-\" is a synonym for stdin."
 
-#define HELP_devmem "usage: devmem ADDR [WIDTH [DATA]]\n\nRead/write physical address via /dev/mem.\n\nWIDTH is 1, 2, 4, or 8 bytes (default 4)."
+#define HELP_devmem "usage: devmem ADDR [WIDTH [DATA]]\n\nRead/write physical address. WIDTH is 1, 2, 4, or 8 bytes (default 4).\nPrefix ADDR with 0x for hexadecimal, output is in same base as address."
 
 #define HELP_count "usage: count\n\nCopy stdin to stdout, displaying simple progress indicator to stderr."
 
@@ -310,6 +316,10 @@
 
 #define HELP_blkid "usage: blkid [-s TAG] [-UL] DEV...\n\nPrint type, label and UUID of filesystem on a block device or image.\n\n-U	Show UUID only (or device with that UUID)\n-L	Show LABEL only (or device with that LABEL)\n-s TAG	Only show matching tags (default all)"
 
+#define HELP_blkdiscard "usage: blkdiscard [-olszf] DEVICE\n\nDiscard device sectors.\n\n-o, --offset OFF	Byte offset to start discarding at (default 0)\n-l, --length LEN	Bytes to discard (default all)\n-s, --secure		Perform secure discard\n-z, --zeroout		Zero-fill rather than discard\n-f, --force		Disable check for mounted filesystem\n\nOFF and LEN must be aligned to the device sector size.\nBy default entire device is discarded.\nWARNING: All discarded data is permanently lost!"
+
+#define HELP_base32 "usage: base32 [-di] [-w COLUMNS] [FILE...]\n\nEncode or decode in base32.\n\n-d	Decode\n-i	Ignore non-alphabetic characters\n-w	Wrap output at COLUMNS (default 76 or 0 for no wrap)"
+
 #define HELP_base64 "usage: base64 [-di] [-w COLUMNS] [FILE...]\n\nEncode or decode in base64.\n\n-d	Decode\n-i	Ignore non-alphabetic characters\n-w	Wrap output at COLUMNS (default 76 or 0 for no wrap)"
 
 #define HELP_ascii "usage: ascii\n\nDisplay ascii character set."
@@ -326,6 +336,8 @@
 
 #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"
 
+#define HELP_unicode "usage: unicode [[min]-max]\n\nConvert between Unicode code points and UTF-8, in both directions."
+
 #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 ms (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"
 
 #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"
@@ -336,7 +348,7 @@
 
 #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)"
 
-#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server"
+#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server."
 
 #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP            IP to listen on, 0 = all\nPORT          Port to listen on\nPROG ARGS     Program to run\n-l NAME       Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N          Handle up to N (> 0) connections simultaneously\n-b N          (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG]    (TCP Only) Allow only up to N (> 0) connections from the same IP\n              New connections from this IP address are closed\n              immediately. MSG is written to the peer before close\n-h            Look up peer's hostname\n-E            Don't set up environment variables\n-v            Verbose"
 
@@ -346,19 +358,39 @@
 
 #define HELP_stty "usage: stty [-ag] [-F device] SETTING...\n\nGet/set terminal configuration.\n\n-F	Open device instead of stdin\n-a	Show all current settings (default differences from \"sane\")\n-g	Show all current settings usable as input to stty\n\nSpecial characters (syntax ^c or undef): intr quit erase kill eof eol eol2\nswtch start stop susp rprnt werase lnext discard\n\nControl/input/output/local settings as shown by -a, '-' prefix to disable\n\nCombo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane\n\nN	set input and output speed (ispeed N or ospeed N for just one)\ncols N	set number of columns\nrows N	set number of rows\nline N	set line discipline\nmin N	set minimum chars per read\ntime N	set read timeout\nspeed	show speed only\nsize	show size only"
 
+#define HELP_wait "usage: wait [-n] [ID...]\n\nWait for background processes to exit, returning its exit code.\nID can be PID or job, with no IDs waits for all backgrounded processes.\n\n-n	Wait for next process to exit"
+
+#define HELP_source "usage: source FILE [ARGS...]\n\nRead FILE and execute commands. Any ARGS become positional parameters."
+
+#define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
+
+#define HELP_local "usage: local [NAME[=VALUE]...]\n\nCreate a local variable that lasts until return from this function.\nWith no arguments lists local variables in current function context.\nTODO: implement \"declare\" options."
+
+#define HELP_jobs "usage: jobs [-lnprs] [%JOB | -x COMMAND...]\n\nList running/stopped background jobs.\n\n-l Include process ID in list\n-n Show only new/changed processes\n-p Show process IDs only\n-r Show running processes\n-s Show stopped processes"
+
+#define HELP_export "usage: export [-n] [NAME[=VALUE]...]\n\nMake variables available to child processes. NAME exports existing local\nvariable(s), NAME=VALUE sets and exports.\n\n-n	Unexport. Turn listed variable(s) into local variables.\n\nWith no arguments list exported variables/attributes as \"declare\" statements."
+
+#define HELP_exec "usage: exec [-cl] [-a NAME] COMMAND...\n\n-a	set argv[0] to NAME\n-c	clear environment\n-l	prepend - to argv[0]"
+
+#define HELP_eval "usage: eval COMMAND...\n\nExecute (combined) arguments as a shell command."
+
+#define HELP_unset "usage: unset [-fvn] NAME...\n\n-f	NAME is a function\n-v	NAME is a variable\n-n	dereference NAME and unset that"
+
+#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f	NAME is a function\n-v	NAME is a variable\n-n	don't follow name reference\n\nOPTIONs:\n  history - enable command history"
+
 #define HELP_exit "usage: exit [status]\n\nExit shell.  If no return value supplied on command line, use value\nof most recent command, or 0 if none."
 
 #define HELP_cd "usage: cd [-PL] [path]\n\nChange current directory.  With no arguments, go $HOME.\n\n-P	Physical path: resolve symlinks in path\n-L	Local path: .. trims directories off $PWD (default)"
 
 #define HELP_sh "usage: sh [-c command] [script]\n\nCommand shell.  Runs a shell script, or reads input interactively\nand responds to it.\n\n-c	command line to execute\n-i	interactive mode (default when STDIN is a tty)"
 
-#define HELP_route "usage: route [-ne] [-A [46]] [add|del TARGET [OPTIONS]]\n\nDisplay, add or delete network routes in the \"Forwarding Information Base\".\n\n-n	Show numerical addresses (no DNS lookups)\n-e	display netstat fields\n\nRouting means sending packets out a network interface to an address.\nThe kernel can tell where to send packets one hop away by examining each\ninterface's address and netmask, so the most common use of this command\nis to identify a \"gateway\" that forwards other traffic.\n\nAssigning an address to an interface automatically creates an appropriate\nnetwork route (\"ifconfig eth0 10.0.2.15/8\" does \"route add 10.0.0.0/8 eth0\"\nfor you), although some devices (such as loopback) won't show it in the\ntable. For machines more than one hop away, you need to specify a gateway\n(ala \"route add default gw 10.0.2.2\").\n\nThe address \"default\" is a wildcard address (0.0.0.0/0) matching all\npackets without a more specific route.\n\nAvailable OPTIONS include:\nreject   - blocking route (force match failure)\ndev NAME - force packets out this interface (ala \"eth0\")\nnetmask  - old way of saying things like ADDR/24\ngw ADDR  - forward packets to gateway ADDR"
+#define HELP_route "usage: route [-ne] [-A [inet|inet6]] [add|del TARGET [OPTIONS]]\n\nDisplay, add or delete network routes in the \"Forwarding Information Base\",\nwhich send packets out a network interface to an address.\n\n-n	Show numerical addresses (no DNS lookups)\n-e	display netstat fields\n\nAssigning an address to an interface automatically creates an appropriate\nnetwork route (\"ifconfig eth0 10.0.2.15/8\" does \"route add 10.0.0.0/8 eth0\"\nfor you), although some devices (such as loopback) won't show it in the\ntable. For machines more than one hop away, you need to specify a gateway\n(ala \"route add default gw 10.0.2.2\").\n\nThe address \"default\" is a wildcard address (0.0.0.0/0) matching all\npackets without a more specific route.\n\nAvailable OPTIONS include:\nreject   - blocking route (force match failure)\ndev NAME - force matching packets out this interface (ala \"eth0\")\nnetmask  - old way of saying things like ADDR/24\ngw ADDR  - forward packets to gateway ADDR"
 
-#define HELP_readelf "usage: readelf [-adhlnSsW] [-p SECTION] [-x SECTION] [file...]\n\nDisplays information about ELF files.\n\n-a	Equivalent to -dhlnSs\n-d	Show dynamic section\n-h	Show ELF header\n-l	Show program headers\n-n	Show notes\n-p S	Dump strings found in named/numbered section\n-S	Show section headers\n-s	Show symbol tables (.dynsym and .symtab)\n-W	Don't truncate fields (default in toybox)\n-x S	Hex dump of named/numbered section\n\n--dyn-syms	Show just .dynsym symbol table"
+#define HELP_readelf "usage: readelf [-adehlnSs] [-p SECTION] [-x SECTION] [file...]\n\nDisplays information about ELF files.\n\n-a	Equivalent to -dhlnSs\n-d	Show dynamic section\n-e	Headers (equivalent to -hlS)\n-h	Show ELF header\n-l	Show program headers\n-n	Show notes\n-p S	Dump strings found in named/numbered section\n-S	Show section headers\n-s	Show symbol tables (.dynsym and .symtab)\n-x S	Hex dump of named/numbered section\n\n--dyn-syms	Show just .dynsym symbol table"
 
-#define HELP_deallocvt "usage: deallocvt [N]\n\nDeallocate unused virtual terminal /dev/ttyN, or all unused consoles."
+#define HELP_deallocvt "usage: deallocvt [NUM]\n\nDeallocate unused virtual terminals, either a specific /dev/ttyNUM, or all."
 
-#define HELP_openvt "usage: openvt [-c N] [-sw] [command [command_options]]\n\nstart a program on a new virtual terminal (VT)\n\n-c N  Use VT N\n-s    Switch to new VT\n-w    Wait for command to exit\n\nif -sw used together, switch back to originating VT when command completes"
+#define HELP_openvt "usage: openvt [-c NUM] [-sw] [COMMAND...]\n\nStart a program on a new virtual terminal.\n\n-c NUM  Use VT NUM\n-s    Switch to new VT\n-w    Wait for command to exit\n\nTogether -sw switch back to originating VT when command completes."
 
 #define HELP_more "usage: more [FILE...]\n\nView FILE(s) (or stdin) one screenfull at a time."
 
@@ -400,7 +432,7 @@
 
 #define HELP_groupadd "usage: groupadd [-S] [-g GID] [USER] GROUP\n\nAdd a group or add a user to a group\n\n  -g GID Group id\n  -S     Create a system group"
 
-#define HELP_getty "usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]\n\n-h    Enable hardware RTS/CTS flow control\n-L    Set CLOCAL (ignore Carrier Detect state)\n-m    Get baud rate from modem's CONNECT status message\n-n    Don't prompt for login name\n-w    Wait for CR or LF before sending /etc/issue\n-i    Don't display /etc/issue\n-f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue\n-l LOGIN  Invoke LOGIN instead of /bin/login\n-t SEC    Terminate after SEC if no login name is read\n-I INITSTR  Send INITSTR before anything else\n-H HOST    Log HOST into the utmp file as the hostname"
+#define HELP_getty "usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]\n\nWait for a modem to dial into serial port, adjust baud rate, call login.\n\n-h    Enable hardware RTS/CTS flow control\n-L    Set CLOCAL (ignore Carrier Detect state)\n-m    Get baud rate from modem's CONNECT status message\n-n    Don't prompt for login name\n-w    Wait for CR or LF before sending /etc/issue\n-i    Don't display /etc/issue\n-f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue\n-l LOGIN  Invoke LOGIN instead of /bin/login\n-t SEC    Terminate after SEC if no login name is read\n-I INITSTR  Send INITSTR before anything else\n-H HOST    Log HOST into the utmp file as the hostname"
 
 #define HELP_getopt "usage: getopt [OPTIONS] [--] ARG...\n\nParse command-line options for use in shell scripts.\n\n-a	Allow long options starting with a single -.\n-l OPTS	Specify long options.\n-n NAME	Command name for error messages.\n-o OPTS	Specify short options.\n-T	Test whether this is a modern getopt.\n-u	Output options unquoted."
 
@@ -430,6 +462,8 @@
 
 #define HELP_crond "usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]\n\nA daemon to execute scheduled commands.\n\n-b Background (default)\n-c crontab dir\n-d Set log level, log to stderr\n-f Foreground\n-l Set log level. 0 is the most verbose, default 8\n-S Log to syslog (default)\n-L Log to file"
 
+#define HELP_chsh "usage: chsh [-s SHELL] [USER]\n\nChange user's login shell.\n\n-s	Use SHELL instead of prompting\n\nNon-root users can only change their own shell to one listed in /etc/shells."
+
 #define HELP_brctl "usage: brctl COMMAND [BRIDGE [INTERFACE]]\n\nManage ethernet bridges\n\nCommands:\nshow                  Show a list of bridges\naddbr BRIDGE          Create BRIDGE\ndelbr BRIDGE          Delete BRIDGE\naddif BRIDGE IFACE    Add IFACE to BRIDGE\ndelif BRIDGE IFACE    Delete IFACE from BRIDGE\nsetageing BRIDGE TIME Set ageing time\nsetfd BRIDGE TIME     Set bridge forward delay\nsethello BRIDGE TIME  Set hello time\nsetmaxage BRIDGE TIME Set max message age\nsetpathcost BRIDGE PORT COST   Set path cost\nsetportprio BRIDGE PORT PRIO   Set port priority\nsetbridgeprio BRIDGE PRIO      Set bridge priority\nstp BRIDGE [1/yes/on|0/no/off] STP on/off"
 
 #define HELP_bootchartd "usage: bootchartd {start [PROG ARGS]}|stop|init\n\nCreate /var/log/bootlog.tgz with boot chart data\n\nstart: start background logging; with PROG, run PROG,\n       then kill logging with USR1\nstop:  send USR1 to all bootchartd processes\ninit:  start background logging; stop when getty/xdm is seen\n      (for init scripts)\n\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
@@ -440,7 +474,7 @@
 
 #define HELP_arp "usage: arp\n[-vn] [-H HWTYPE] [-i IF] -a [HOSTNAME]\n[-v]              [-i IF] -d HOSTNAME [pub]\n[-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]\n[-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub\n[-v]  [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub\n\nManipulate ARP cache\n\n-a    Display (all) hosts\n-s    Set new ARP entry\n-d    Delete a specified entry\n-v    Verbose\n-n    Don't resolve names\n-i IF Network interface\n-D    Read <hwaddr> from given device\n-A,-p AF  Protocol family\n-H    HWTYPE Hardware address type"
 
-#define HELP_xargs "usage: xargs [-0prt] [-s NUM] [-n NUM] [-E STR] COMMAND...\n\nRun command line one or more times, appending arguments from stdin.\n\nIf COMMAND exits with 255, don't launch another even if arguments remain.\n\n-0	Each argument is NULL terminated, no whitespace or quote processing\n-E	Stop at line matching string\n-n	Max number of arguments per command\n-o	Open tty for COMMAND's stdin (default /dev/null)\n-p	Prompt for y/n from tty before running each command\n-r	Don't run command with empty input (otherwise always run command once)\n-s	Size in bytes per command line\n-t	Trace, print command line to stderr"
+#define HELP_xargs "usage: xargs [-0prt] [-snE STR] COMMAND...\n\nRun command line one or more times, appending arguments from stdin.\n\nIf COMMAND exits with 255, don't launch another even if arguments remain.\n\n-0	Each argument is NULL terminated, no whitespace or quote processing\n-E	Stop at line matching string\n-n	Max number of arguments per command\n-o	Open tty for COMMAND's stdin (default /dev/null)\n-p	Prompt for y/n from tty before running each command\n-P	Parallel processes (default 1)\n-r	Don't run with empty input (otherwise always run command once)\n-s	Size in bytes per command line\n-t	Trace, print command line to stderr"
 
 #define HELP_who "usage: who\n\nPrint information about logged in users."
 
@@ -458,7 +492,7 @@
 
 #define HELP_arch "usage: arch\n\nPrint machine (hardware) name, same as uname -m."
 
-#define HELP_ulimit "usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]\n\nPrint or set resource limits for process number PID. If no LIMIT specified\n(or read-only -ap selected) display current value (sizes in bytes).\nDefault is ulimit -P $PPID -Sf\" (show soft filesize of your shell).\n\n-S  Set/show soft limit          -H  Set/show hard (maximum) limit\n-a  Show all limits              -c  Core file size\n-d  Process data segment         -e  Max scheduling priority\n-f  Output file size             -i  Pending signal count\n-l  Locked memory                -m  Resident Set Size\n-n  Number of open files         -p  Pipe buffer\n-q  Posix message queue          -r  Max Real-time priority\n-R  Realtime latency (usec)      -s  Stack size\n-t  Total CPU time (in seconds)  -u  Maximum processes (under this UID)\n-v  Virtual memory size          -P  PID to affect (default $PPID)"
+#define HELP_ulimit "usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]\n\nPrint or set resource limits for process number PID. If no LIMIT specified\n(or read-only -ap selected) display current value (sizes in bytes).\nDefault is ulimit -P $PPID -Sf\" (show soft filesize of your shell).\n\n-P  PID to affect (default $PPID)  -a  Show all limits\n-S  Set/show soft limit            -H  Set/show hard (maximum) limit\n\n-c  Core file size (blocks)        -d  Process data segment (KiB)\n-e  Max scheduling priority        -f  File size (KiB)\n-i  Pending signal count           -l  Locked memory (KiB)\n-m  Resident Set Size (KiB)        -n  Number of open files\n-p  Pipe buffer (512 bytes)        -q  POSIX message queues\n-r  Max realtime priority          -R  Realtime latency (us)\n-s  Stack size (KiB)               -t  Total CPU time (s)\n-u  Maximum processes (this UID)   -v  Virtual memory size (KiB)"
 
 #define HELP_tty "usage: tty [-s]\n\nShow filename of terminal connected to stdin.\n\nPrints \"not a tty\" and exits with nonzero status if no terminal\nis connected to stdin.\n\n-s	Silent, exit code only"
 
@@ -468,11 +502,11 @@
 
 #define HELP_time "usage: time [-pv] COMMAND...\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 format output (default)\n-v	Verbose"
 
-#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)"
+#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   -k  sticky bit\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\n\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)"
 
 #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"
 
-#define HELP_tar "usage: tar [-cxt] [-fvohmjkOS] [-XTCf NAME] [FILE...]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc  Create                x  Extract               t  Test (list)\nf  tar FILE (default -)  C  Change to DIR first   v  Verbose display\no  Ignore owner          h  Follow symlinks       m  Ignore mtime\nJ  xz compression        j  bzip2 compression     z  gzip compression\nO  Extract to stdout     X  exclude names in FILE T  include names in FILE\n\n--exclude        FILENAME to exclude    --full-time   Show seconds with -tv\n--mode MODE      Adjust modes           --mtime TIME  Override timestamps\n--owner NAME     Set file owner to NAME --group NAME  Set file group to NAME\n--sparse         Record sparse files\n--restrict       All archive contents must extract under one subdirctory\n--numeric-owner  Save/use/display uid and gid, not user/group name\n--no-recursion   Don't store directory contents"
+#define HELP_tar "usage: tar [-cxt] [-fvohmjkOS] [-XTCf NAME] [FILE...]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc  Create                x  Extract               t  Test (list)\nf  tar FILE (default -)  C  Change to DIR first   v  Verbose display\no  Ignore owner          h  Follow symlinks       m  Ignore mtime\nJ  xz compression        j  bzip2 compression     z  gzip compression\nO  Extract to stdout     X  exclude names in FILE T  include names in FILE\n\n--exclude        FILENAME to exclude    --full-time   Show seconds with -tv\n--mode MODE      Adjust modes           --mtime TIME  Override timestamps\n--owner NAME     Set file owner to NAME --group NAME  Set file group to NAME\n--sparse         Record sparse files\n--restrict       All archive contents must extract under one subdirectory\n--numeric-owner  Save/use/display uid and gid, not user/group name\n--no-recursion   Don't store directory contents\n-I PROG          Filter through PROG to compress or PROG -d to decompress"
 
 #define HELP_tail "usage: tail [-n|c NUMBER] [-f] [FILE...]\n\nCopy last lines from files to stdout. If no files listed, copy from\nstdin. Filename \"-\" is a synonym for stdin.\n\n-n	Output the last NUMBER lines (default 10), +X counts from start\n-c	Output the last NUMBER bytes, +NUMBER counts from start\n-f	Follow FILE(s), waiting for more data to be appended"
 
@@ -484,7 +518,7 @@
 
 #define HELP_sleep "usage: sleep DURATION\n\nWait before exiting.\n\nDURATION can be a decimal fraction. An optional suffix can be \"m\"\n(minutes), \"h\" (hours), \"d\" (days), or \"s\" (seconds, the default)."
 
-#define HELP_sed "usage: sed [-inrzE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]\n\nStream editor. Apply one or more editing SCRIPTs to each line of input\n(from FILE or stdin) producing output (by default to stdout).\n\n-e	Add SCRIPT to list\n-f	Add contents of SCRIPT_FILE to list\n-i	Edit each file in place (-iEXT keeps backup file with extension EXT)\n-n	No default output (use the p command to output matched lines)\n-r	Use extended regular expression syntax\n-E	POSIX alias for -r\n-s	Treat input files separately (implied by -i)\n-z	Use \\0 rather than \\n as the input line separator\n\nA SCRIPT is a series of one or more COMMANDs separated by newlines or\nsemicolons. All -e SCRIPTs are concatenated together as if separated\nby newlines, followed by all lines from -f SCRIPT_FILEs, in order.\nIf no -e or -f SCRIPTs are specified, the first argument is the SCRIPT.\n\nEach COMMAND may be preceded by an address which limits the command to\napply only to the specified line(s). Commands without an address apply to\nevery line. Addresses are of the form:\n\n  [ADDRESS[,ADDRESS]][!]COMMAND\n\nThe ADDRESS may be a decimal line number (starting at 1), a /regular\nexpression/ within a pair of forward slashes, or the character \"$\" which\nmatches the last line of input. (In -s or -i mode this matches the last\nline of each file, otherwise just the last line of the last file.) A single\naddress matches one line, a pair of comma separated addresses match\neverything from the first address to the second address (inclusive). If\nboth addresses are regular expressions, more than one range of lines in\neach file can match. The second address can be +N to end N lines later.\n\nREGULAR EXPRESSIONS in sed are started and ended by the same character\n(traditionally / but anything except a backslash or a newline works).\nBackslashes may be used to escape the delimiter if it occurs in the\nregex, and for the usual printf escapes (\\abcefnrtv and octal, hex,\nand unicode). An empty regex repeats the previous one. ADDRESS regexes\n(above) require the first delimiter to be escaped with a backslash when\nit isn't a forward slash (to distinguish it from the COMMANDs below).\n\nSed mostly operates on individual lines one at a time. It reads each line,\nprocesses it, and either writes it to the output or discards it before\nreading the next line. Sed can remember one additional line in a separate\nbuffer (using the h, H, g, G, and x commands), and can read the next line\nof input early (using the n and N command), but other than that command\nscripts operate on individual lines of text.\n\nEach COMMAND starts with a single character. The following commands take\nno arguments:\n\n  !  Run this command when the test _didn't_ match.\n\n  {  Start a new command block, continuing until a corresponding \"}\".\n     Command blocks may nest. If the block has an address, commands within\n     the block are only run for lines within the block's address range.\n\n  }  End command block (this command cannot have an address)\n\n  d  Delete this line and move on to the next one\n     (ignores remaining COMMANDs)\n\n  D  Delete one line of input and restart command SCRIPT (same as \"d\"\n     unless you've glued lines together with \"N\" or similar)\n\n  g  Get remembered line (overwriting current line)\n\n  G  Get remembered line (appending to current line)\n\n  h  Remember this line (overwriting remembered line)\n\n  H  Remember this line (appending to remembered line, if any)\n\n  l  Print line, escaping \\abfrtv (but not newline), octal escaping other\n     nonprintable characters, wrapping lines to terminal width with a\n     backslash, and appending $ to actual end of line.\n\n  n  Print default output and read next line, replacing current line\n     (If no next line available, quit processing script)\n\n  N  Append next line of input to this line, separated by a newline\n     (This advances the line counter for address matching and \"=\", if no\n     next line available quit processing script without default output)\n\n  p  Print this line\n\n  P  Print this line up to first newline (from \"N\")\n\n  q  Quit (print default output, no more commands processed or lines read)\n\n  x  Exchange this line with remembered line (overwrite in both directions)\n\n  =  Print the current line number (followed by a newline)\n\nThe following commands (may) take an argument. The \"text\" arguments (to\nthe \"a\", \"b\", and \"c\" commands) may end with an unescaped \"\\\" to append\nthe next line (for which leading whitespace is not skipped), and also\ntreat \";\" as a literal character (use \"\\;\" instead).\n\n  a [text]   Append text to output before attempting to read next line\n\n  b [label]  Branch, jumps to :label (or with no label, to end of SCRIPT)\n\n  c [text]   Delete line, output text at end of matching address range\n             (ignores remaining COMMANDs)\n\n  i [text]   Print text\n\n  r [file]   Append contents of file to output before attempting to read\n             next line.\n\n  s/S/R/F    Search for regex S, replace matched text with R using flags F.\n             The first character after the \"s\" (anything but newline or\n             backslash) is the delimiter, escape with \\ to use normally.\n\n             The replacement text may contain \"&\" to substitute the matched\n             text (escape it with backslash for a literal &), or \\1 through\n             \\9 to substitute a parenthetical subexpression in the regex.\n             You can also use the normal backslash escapes such as \\n and\n             a backslash at the end of the line appends the next line.\n\n             The flags are:\n\n             [0-9]    A number, substitute only that occurrence of pattern\n             g        Global, substitute all occurrences of pattern\n             i        Ignore case when matching\n             p        Print the line if match was found and replaced\n             w [file] Write (append) line to file if match replaced\n\n  t [label]  Test, jump to :label only if an \"s\" command found a match in\n             this line since last test (replacing with same text counts)\n\n  T [label]  Test false, jump only if \"s\" hasn't found a match.\n\n  w [file]   Write (append) line to file\n\n  y/old/new/ Change each character in 'old' to corresponding character\n             in 'new' (with standard backslash escapes, delimiter can be\n             any repeated character except \\ or \\n)\n\n  : [label]  Labeled target for jump commands\n\n  #  Comment, ignore rest of this line of SCRIPT\n\nDeviations from POSIX: allow extended regular expressions with -r,\nediting in place with -i, separate with -s, NUL-separated input with -z,\nprintf escapes in text, line continuations, semicolons after all commands,\n2-address anywhere an address is allowed, \"T\" command, multiline\ncontinuations for [abc], \\; to end [abc] argument before end of line."
+#define HELP_sed "usage: sed [-inrszE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]\n\nStream editor. Apply editing SCRIPTs to lines of input.\n\n-e	Add SCRIPT to list\n-f	Add contents of SCRIPT_FILE to list\n-i	Edit each file in place (-iEXT keeps backup file with extension EXT)\n-n	No default output (use the p command to output matched lines)\n-r	Use extended regular expression syntax\n-E	POSIX alias for -r\n-s	Treat input files separately (implied by -i)\n-z	Use \\0 rather than \\n as input line separator\n\nA SCRIPT is one or more COMMANDs separated by newlines or semicolons.\nAll -e SCRIPTs are combined as if separated by newlines, followed by all -f\nSCRIPT_FILEs. If no -e or -f then first argument is the SCRIPT.\n\nCOMMANDs apply to every line unless prefixed with an ADDRESS of the form:\n\n  [ADDRESS[,ADDRESS]][!]COMMAND\n\nADDRESS is a line number (starting at 1), a /REGULAR EXPRESSION/, or $ for\nlast line (-s or -i makes it last line of each file). One address matches one\nline, ADDRESS,ADDRESS matches from first to second inclusive. Two regexes can\nmatch multiple ranges. ADDRESS,+N ends N lines later. ! inverts the match.\n\nREGULAR EXPRESSIONS start and end with the same character (anything but\nbackslash or newline). To use the delimiter in the regex escape it with a\nbackslash, and printf escapes (\\abcefnrtv and octal, hex, and unicode) work.\nAn empty regex repeats the previous one. ADDRESS regexes require any\nfirst delimiter except / to be \\escaped to distinguish it from COMMANDs.\n\nSed reads each line of input, processes it, and writes it out or discards it\nbefore reading the next. Sed can remember one additional line in a separate\nbuffer (the h, H, g, G, and x commands), and can read the next line of input\nearly (the n and N commands), but otherwise operates on individual lines.\n\nEach COMMAND starts with a single character. Commands with no arguments are:\n\n  !  Run this command when the ADDRESS _didn't_ match.\n  {  Start new command block, continuing until a corresponding \"}\".\n     Command blocks nest and can have ADDRESSes applying to the whole block.\n  }  End command block (this COMMAND cannot have an address)\n  d  Delete this line and move on to the next one\n     (ignores remaining COMMANDs)\n  D  Delete one line of input and restart command SCRIPT (same as \"d\"\n     unless you've glued lines together with \"N\" or similar)\n  g  Get remembered line (overwriting current line)\n  G  Get remembered line (appending to current line)\n  h  Remember this line (overwriting remembered line)\n  H  Remember this line (appending to remembered line, if any)\n  l  Print line escaping \\abfrtv (but not \\n), octal escape other nonprintng\n     chars, wrap lines to terminal width with \\, append $ to end of line.\n  n  Print default output and read next line over current line (quit at EOF)\n  N  Append \\n and next line of input to this line. Quit at EOF without\n     default output. Advances line counter for ADDRESS and \"=\".\n  p  Print this line\n  P  Print this line up to first newline (from \"N\")\n  q  Quit (print default output, no more commands processed or lines read)\n  x  Exchange this line with remembered line (overwrite in both directions)\n  =  Print the current line number (plus newline)\n  #  Comment, ignores rest of this line of SCRIPT (until newline)\n\nCommands that take an argument:\n\n  : LABEL    Target for jump commands\n  a TEXT     Append text to output before reading next line\n  b LABEL    Branch, jumps to :LABEL (with no LABEL to end of SCRIPT)\n  c TEXT     Delete matching ADDRESS range and output TEXT instead\n  i TEXT     Insert text (output immediately)\n  r FILE     Append contents of FILE to output before reading next line.\n  s/S/R/F    Search for regex S replace match with R using flags F. Delimiter\n             is anything but \\n or \\, escape with \\ to use in S or R. Printf\n             escapes work. Unescaped & in R becomes full matched text, \\1\n             through \\9 = parenthetical subexpression from S. \\ at end of\n             line appends next line of SCRIPT. The flags in F are:\n             [0-9]    A number N, substitute only Nth match\n             g        Global, substitute all matches\n             i/I      Ignore case when matching\n             p        Print resulting line when match found and replaced\n             w [file] Write (append) line to file when match replaced\n  t LABEL    Test, jump if s/// command matched this line since last test\n  T LABEL    Test false, jump to :LABEL only if no s/// found a match\n  w FILE     Write (append) line to file\n  y/old/new/ Change each character in 'old' to corresponding character\n             in 'new' (with standard backslash escapes, delimiter can be\n             any repeated character except \\ or \\n)\n\nThe TEXT arguments (to a c i) may end with an unescaped \"\\\" to append\nthe next line (leading whitespace is not skipped), and treat \";\" as a\nliteral character (use \"\\;\" instead)."
 
 #define HELP_rmdir "usage: rmdir [-p] [DIR...]\n\nRemove one or more directories.\n\n-p	Remove path\n--ignore-fail-on-non-empty	Ignore failures caused by non-empty directories"
 
@@ -552,7 +586,7 @@
 
 #define HELP_getconf "usage: getconf -a [PATH] | -l | NAME [PATH]\n\nGet system configuration values. Values from pathconf(3) require a path.\n\n-a	Show all (defaults to \"/\" if no path given)\n-l	List available value names (grouped by source)"
 
-#define HELP_find "usage: find [-HL] [DIR...] [<options>]\n\nSearch directories for matching files.\nDefault: search \".\", match all, -print matches.\n\n-H  Follow command line symlinks         -L  Follow all symlinks\n\nMatch filters:\n-name  PATTERN   filename with wildcards  (-iname case insensitive)\n-path  PATTERN   path name with wildcards (-ipath case insensitive)\n-user  UNAME     belongs to user UNAME     -nouser     user ID not known\n-group GROUP     belongs to group GROUP    -nogroup    group ID not known\n-perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents\n-size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem\n-links N         hardlink count            -atime N[u] accessed N units ago\n-ctime N[u]      created N units ago       -mtime N[u] modified N units ago\n-newer FILE      newer mtime than FILE     -mindepth N at least N dirs down\n-depth           ignore contents of dir    -maxdepth N at most N dirs down\n-inum N          inode number N            -empty      empty files and dirs\n-type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)\n-true            always true               -false      always false\n-context PATTERN security context\n-newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)\n\nNumbers N may be prefixed by a - (less than) or + (greater than). Units for\n-Xtime are d (days, default), h (hours), m (minutes), or s (seconds).\n\nCombine matches with:\n!, -a, -o, ( )    not, and, or, group expressions\n\nActions:\n-print  Print match with newline  -print0        Print match with null\n-exec   Run command with path     -execdir       Run command in file's dir\n-ok     Ask before exec           -okdir         Ask before execdir\n-delete Remove matching file/dir  -printf FORMAT Print using format string\n\nCommands substitute \"{}\" with matched file. End with \";\" to run each file,\nor \"+\" (next argument after \"{}\") to collect and run with multiple files.\n\n-printf FORMAT characters are \\ escapes and:\n%b  512 byte blocks used\n%f  basename            %g  textual gid          %G  numeric gid\n%i  decimal inode       %l  target of symlink    %m  octal mode\n%M  ls format type/mode %p  path to file         %P  path to file minus DIR\n%s  size in bytes       %T@ mod time as unixtime\n%u  username            %U  numeric uid          %Z  security context"
+#define HELP_find "usage: find [-HL] [DIR...] [<options>]\n\nSearch directories for matching files.\nDefault: search \".\", match all, -print matches.\n\n-H  Follow command line symlinks         -L  Follow all symlinks\n\nMatch filters:\n-name  PATTERN   filename with wildcards  (-iname case insensitive)\n-path  PATTERN   path name with wildcards (-ipath case insensitive)\n-user  UNAME     belongs to user UNAME     -nouser     user ID not known\n-group GROUP     belongs to group GROUP    -nogroup    group ID not known\n-perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents\n-size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem\n-links N         hardlink count            -atime N[u] accessed N units ago\n-ctime N[u]      created N units ago       -mtime N[u] modified N units ago\n-newer FILE      newer mtime than FILE     -mindepth N at least N dirs down\n-depth           ignore contents of dir    -maxdepth N at most N dirs down\n-inum N          inode number N            -empty      empty files and dirs\n-type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)\n-true            always true               -false      always false\n-context PATTERN security context          -executable access(X_OK) perm+ACL\n-newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)\n\nNumbers N may be prefixed by a - (less than) or + (greater than). Units for\n-Xtime are d (days, default), h (hours), m (minutes), or s (seconds).\n\nCombine matches with:\n!, -a, -o, ( )    not, and, or, group expressions\n\nActions:\n-print  Print match with newline  -print0        Print match with null\n-exec   Run command with path     -execdir       Run command in file's dir\n-ok     Ask before exec           -okdir         Ask before execdir\n-delete Remove matching file/dir  -printf FORMAT Print using format string\n\nCommands substitute \"{}\" with matched file. End with \";\" to run each file,\nor \"+\" (next argument after \"{}\") to collect and run with multiple files.\n\n-printf FORMAT characters are \\ escapes and:\n%b  512 byte blocks used\n%f  basename            %g  textual gid          %G  numeric gid\n%i  decimal inode       %l  target of symlink    %m  octal mode\n%M  ls format type/mode %p  path to file         %P  path to file minus DIR\n%s  size in bytes       %T@ mod time as unixtime\n%u  username            %U  numeric uid          %Z  security context"
 
 #define HELP_file "usage: file [-bhLs] [FILE...]\n\nExamine the given files and describe their content types.\n\n-b	Brief (no filename)\n-h	Don't follow symlinks (default)\n-L	Follow symlinks\n-s	Show block/char device contents"
 
@@ -564,25 +598,23 @@
 
 #define HELP_echo "usage: echo [-neE] [ARG...]\n\nWrite each argument to stdout, with one space between each, followed\nby a newline.\n\n-n	No trailing newline\n-E	Print escape sequences literally (default)\n-e	Process the following escape sequences:\n	\\\\	Backslash\n	\\0NNN	Octal values (1 to 3 digits)\n	\\a	Alert (beep/flash)\n	\\b	Backspace\n	\\c	Stop output here (avoids trailing newline)\n	\\f	Form feed\n	\\n	Newline\n	\\r	Carriage return\n	\\t	Horizontal tab\n	\\v	Vertical tab\n	\\xHH	Hexadecimal values (1 to 2 digits)"
 
-#define HELP_du "usage: du [-d N] [-askxHLlmc] [FILE...]\n\nShow disk usage, space consumed by files and directories.\n\nSize in:\n-k	1024 byte blocks (default)\n-K	512 byte blocks (posix)\n-m	Megabytes\n-h	Human readable (e.g., 1K 243M 2G)\n\nWhat to show:\n-a	All files, not just directories\n-H	Follow symlinks on cmdline\n-L	Follow all symlinks\n-s	Only total size of each argument\n-x	Don't leave this filesystem\n-c	Cumulative total\n-d N	Only depth < N\n-l	Disable hardlink filter"
+#define HELP_du "usage: du [-d N] [-askxHLlmc] [FILE...]\n\nShow disk usage, space consumed by files and directories.\n\nSize in:\n-b	Apparent bytes (directory listing size, not space used)\n-k	1024 byte blocks (default)\n-K	512 byte blocks (posix)\n-m	Megabytes\n-h	Human readable (e.g., 1K 243M 2G)\n\nWhat to show:\n-a	All files, not just directories\n-H	Follow symlinks on cmdline\n-L	Follow all symlinks\n-s	Only total size of each argument\n-x	Don't leave this filesystem\n-c	Cumulative total\n-d N	Only depth < N\n-l	Disable hardlink filter"
 
 #define HELP_dirname "usage: dirname PATH...\n\nShow directory portion of path."
 
-#define HELP_df "usage: df [-HPkhi] [-t type] [FILE...]\n\nThe \"disk free\" command shows total/used/available disk space for\neach filesystem listed on the command line, or all currently mounted\nfilesystems.\n\n-a	Show all (including /proc and friends)\n-P	The SUSv3 \"Pedantic\" option\n-k	Sets units back to 1024 bytes (the default without -P)\n-h	Human readable (K=1024)\n-H	Human readable (k=1000)\n-i	Show inodes instead of blocks\n-t type	Display only filesystems of this type\n\nPedantic provides a slightly less useful output format dictated by Posix,\nand sets the units to 512 bytes instead of the default 1024 bytes."
+#define HELP_df "usage: df [-HPkhi] [-t type] [FILE...]\n\nThe \"disk free\" command shows total/used/available disk space for\neach filesystem listed on the command line, or all currently mounted\nfilesystems.\n\n-a	Show all (including /proc and friends)\n-P	The SUSv3 \"Pedantic\" option\n-k	Sets units back to 1024 bytes (the default without -P)\n-h	Human readable (K=1024)\n-H	Human readable (k=1000)\n-i	Show inodes instead of blocks\n-t type	Display only filesystems of this type\n\nPedantic provides a slightly less useful output format dictated by POSIX,\nand sets the units to 512 bytes instead of the default 1024 bytes."
 
-#define HELP_date "usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\nSupported input formats:\n\nMMDDhhmm[[CC]YY][.ss]     POSIX\n@UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01\nYYYY-MM-DD [hh:mm[:ss]]   ISO 8601\nhh:mm[:ss]                24-hour time today\n\nAll input formats can be preceded by TZ=\"id\" to set the input time zone\nseparately from the output time zone. Otherwise $TZ sets both.\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start sunday)   %W Week of year (0-53 start monday)\n%V Week of year (1-53 start monday, week < 4 days not part of this year)\n\n%F \"%Y-%m-%d\"     %R \"%H:%M\"        %T \"%H:%M:%S\"    %z numeric timezone\n%D \"%m/%d/%y\"     %r \"%I:%M:%S %p\"  %h \"%b\"          %s unix epoch time\n%x locale date    %X locale time    %c locale date/time"
+#define HELP_date "usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-I RES	ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\nSupported input formats:\n\nMMDDhhmm[[CC]YY][.ss]     POSIX\n@UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01\nYYYY-MM-DD [hh:mm[:ss]]   ISO 8601\nhh:mm[:ss]                24-hour time today\n\nAll input formats can be followed by fractional seconds, and/or a UTC\noffset such as -0800.\n\nAll input formats can be preceded by TZ=\"id\" to set the input time zone\nseparately from the output time zone. Otherwise $TZ sets both.\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start Sunday)   %W Week of year (0-53 start Monday)\n%V Week of year (1-53 start Monday, week < 4 days not part of this year)\n\n%F \"%Y-%m-%d\"   %R \"%H:%M\"        %T \"%H:%M:%S\"        %z  timezone (-0800)\n%D \"%m/%d/%y\"   %r \"%I:%M:%S %p\"  %h \"%b\"              %:z timezone (-08:00)\n%x locale date  %X locale time    %c locale date/time  %s  unix epoch time"
 
 #define HELP_cut "usage: cut [-Ds] [-bcfF LIST] [-dO DELIM] [FILE...]\n\nPrint selected parts of lines from each FILE to standard output.\n\nEach selection LIST is comma separated, either numbers (counting from 1)\nor dash separated ranges (inclusive, with X- meaning to end of line and -X\nfrom start). By default selection ranges are sorted and collated, use -D\nto prevent that.\n\n-b	Select bytes\n-c	Select UTF-8 characters\n-C	Select unicode columns\n-d	Use DELIM (default is TAB for -f, run of whitespace for -F)\n-D	Don't sort/collate selections or match -fF lines without delimiter\n-f	Select fields (words) separated by single DELIM character\n-F	Select fields separated by DELIM regex\n-O	Output delimiter (default one space for -F, input delim for -f)\n-s	Skip lines without delimiters"
 
-#define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -mdu -H newc]\n\nCopy files into and out of a \"newc\" format cpio archive.\n\n-F FILE	Use archive FILE instead of stdin/stdout\n-p DEST	Copy-pass mode, copy stdin file list to directory DEST\n-i	Extract from archive into file system (stdin=archive)\n-o	Create archive (stdin=list of files, stdout=archive)\n-t	Test files (list only, stdin=archive, stdout=list of files)\n-v	Verbose\n--no-preserve-owner (don't set ownership during extract)\n--trailer Add legacy trailer (prevents concatenation)"
+#define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -m -H newc]\n\nCopy files into and out of a \"newc\" format cpio archive.\n\n-F FILE	Use archive FILE instead of stdin/stdout\n-p DEST	Copy-pass mode, copy stdin file list to directory DEST\n-i	Extract from archive into file system (stdin=archive)\n-o	Create archive (stdin=list of files, stdout=archive)\n-t	Test files (list only, stdin=archive, stdout=list of files)\n-d	Create directories if needed\n-u	unlink existing files when extracting\n-v	Verbose\n--no-preserve-owner (don't set ownership during extract)"
 
-#define HELP_install "usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST\n\nCopy files and set attributes.\n\n-d	Act like mkdir -p\n-D	Create leading directories for DEST\n-g	Make copy belong to GROUP\n-m	Set permissions to MODE\n-o	Make copy belong to USER\n-p	Preserve timestamps\n-s	Call \"strip -p\"\n-v	Verbose"
+#define HELP_install "usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [-t TARGET] [SOURCE...] [DEST]\n\nCopy files and set attributes.\n\n-d	Act like mkdir -p\n-D	Create leading directories for DEST\n-g	Make copy belong to GROUP\n-m	Set permissions to MODE\n-o	Make copy belong to USER\n-p	Preserve timestamps\n-s	Call \"strip -p\"\n-t	Copy files to TARGET dir (no DEST)\n-v	Verbose"
 
-#define HELP_mv "usage: mv [-finTv] SOURCE... DEST\n\n-f	Force copy by deleting destination file\n-i	Interactive, prompt before overwriting existing DEST\n-n	No clobber (don't overwrite DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose"
+#define HELP_mv "usage: mv [-finTv] [-t TARGET] SOURCE... [DEST]\n\n-f	Force copy by deleting destination file\n-i	Interactive, prompt before overwriting existing DEST\n-n	No clobber (don't overwrite DEST)\n-t	Move to TARGET dir (no DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose"
 
-#define HELP_cp_preserve "--preserve takes either a comma separated list of attributes, or the first\nletter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above\n\nusage: cp [--preserve=motcxa] [-adfHiLlnPpRrsTv] SOURCE... DEST\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n-v	Verbose\n-T	DEST always treated as file, max 2 arguments\n-s	Symlink instead of copy\n-r	Synonym for -R\n-R	Recurse into subdirectories (DEST must be a directory)\n-p	Preserve timestamps, ownership, and mode\n-P	Do not follow symlinks [default]\n-n	No clobber (don't overwrite DEST)\n-l	Hard link instead of copy\n-L	Follow all symlinks\n-i	Interactive, prompt before overwriting existing DEST\n-H	Follow symlinks listed on command line\n-f	Delete destination files we can't write to\n-F	Delete any existing destination file first (--remove-destination)\n-d	Don't dereference symlinks\n-D	Create leading dirs under DEST (--parents)\n-a	Same as -dpr"
-
-#define HELP_cp "usage: cp [--preserve=motcxa] [-adfHiLlnPpRrsTv] SOURCE... DEST\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n-v	Verbose\n-T	DEST always treated as file, max 2 arguments\n-s	Symlink instead of copy\n-r	Synonym for -R\n-R	Recurse into subdirectories (DEST must be a directory)\n-p	Preserve timestamps, ownership, and mode\n-P	Do not follow symlinks [default]\n-n	No clobber (don't overwrite DEST)\n-l	Hard link instead of copy\n-L	Follow all symlinks\n-i	Interactive, prompt before overwriting existing DEST\n-H	Follow symlinks listed on command line\n-f	Delete destination files we can't write to\n-F	Delete any existing destination file first (--remove-destination)\n-d	Don't dereference symlinks\n-D	Create leading dirs under DEST (--parents)\n-a	Same as -dpr\n--preserve takes either a comma separated list of attributes, or the first\nletter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above"
+#define HELP_cp "usage: cp [-adfHiLlnPpRrsTv] [--preserve=motcxa] [-t TARGET] SOURCE... [DEST]\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n\n-a	Same as -dpr\n-D	Create leading dirs under DEST (--parents)\n-d	Don't dereference symlinks\n-F	Delete any existing destination file first (--remove-destination)\n-f	Delete destination files we can't write to\n-H	Follow symlinks listed on command line\n-i	Interactive, prompt before overwriting existing DEST\n-L	Follow all symlinks\n-l	Hard link instead of copy\n-n	No clobber (don't overwrite DEST)\n-u	Update (keep newest mtime)\n-P	Do not follow symlinks\n-p	Preserve timestamps, ownership, and mode\n-R	Recurse into subdirectories (DEST must be a directory)\n-r	Synonym for -R\n-s	Symlink instead of copy\n-t	Copy to TARGET dir (no DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose\n\nArguments to --preserve are the first letter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above"
 
 #define HELP_comm "usage: comm [-123] FILE1 FILE2\n\nRead FILE1 and FILE2, which should be ordered, and produce three text\ncolumns as output: lines only in FILE1; lines only in FILE2; and lines\nin both files. Filename \"-\" is a synonym for stdin.\n\n-1	Suppress the output column of lines unique to FILE1\n-2	Suppress the output column of lines unique to FILE2\n-3	Suppress the output column of lines duplicated in FILE1 and FILE2"
 
@@ -592,7 +624,7 @@
 
 #define HELP_cksum "usage: cksum [-IPLN] [FILE...]\n\nFor each file, output crc32 checksum value, length and name of file.\nIf no files listed, copy from stdin.  Filename \"-\" is a synonym for stdin.\n\n-H	Hexadecimal checksum (defaults to decimal)\n-L	Little endian (defaults to big endian)\n-P	Pre-inversion\n-I	Skip post-inversion\n-N	Do not include length in CRC calculation (or output)"
 
-#define HELP_chmod "usage: chmod [-R] MODE FILE...\n\nChange mode of listed file[s] (recursively with -R).\n\nMODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo]\n\nStanzas are applied in order: For each category (u = user,\ng = group, o = other, a = all three, if none specified default is a),\nset (+), clear (-), or copy (=), r = read, w = write, x = execute.\ns = u+s = suid, g+s = sgid, o+s = sticky. (+t is an alias for o+s).\nsuid/sgid: execute as the user/group who owns the file.\nsticky: can't delete files you don't own out of this directory\nX = x for directories or if any category already has x set.\n\nOr MODE can be an octal value up to 7777	ug uuugggooo	top +\nbit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1	sstrwxrwxrwx	bottom\n\nExamples:\nchmod u+w file - allow owner of \"file\" to write to it.\nchmod 744 file - user can read/write/execute, everyone else read only"
+#define HELP_chmod "usage: chmod [-R] MODE FILE...\n\nChange mode of listed file[s] (recursively with -R).\n\nMODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo]\n\nStanzas are applied in order: For each category (u = user,\ng = group, o = other, a = all three, if none specified default is a),\nset (+), clear (-), or copy (=), r = read, w = write, x = execute.\ns = u+s = suid, g+s = sgid, +t = sticky. (o+s ignored so a+s doesn't set +t)\nsuid/sgid: execute as the user/group who owns the file.\nsticky: can't delete files you don't own out of this directory\nX = x for directories or if any category already has x set.\n\nOr MODE can be an octal value up to 7777	ug uuugggooo	top +\nbit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1	sstrwxrwxrwx	bottom\n\nExamples:\nchmod u+w file - allow owner of \"file\" to write to it.\nchmod 744 file - user can read/write/execute, everyone else read only"
 
 #define HELP_chown "see: chgrp"
 
diff --git a/android/device/generated/newtoys.h b/android/device/generated/newtoys.h
index 6b93bea..40ac3d4 100644
--- a/android/device/generated/newtoys.h
+++ b/android/device/generated/newtoys.h
@@ -2,8 +2,9 @@
 USE_SH(OLDTOY(-bash, sh, 0))
 USE_SH(OLDTOY(-sh, sh, 0))
 USE_SH(OLDTOY(-toysh, sh, 0))
-USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
+USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
 USE_ACPI(NEWTOY(acpi, "abctV", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GROUPADD(OLDTOY(addgroup, groupadd, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USERADD(OLDTOY(adduser, useradd, TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
@@ -11,10 +12,12 @@
 USE_ARP(NEWTOY(arp, "vi:nDsdap:A:H:[+Ap][!sd]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_ARPING(NEWTOY(arping, "<1>1s:I:w#<0c#<0AUDbqf[+AU][+Df]", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_ASCII(NEWTOY(ascii, 0, TOYFLAG_USR|TOYFLAG_BIN))
+USE_BASE32(NEWTOY(base32, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_BASE64(NEWTOY(base64, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_BASENAME(NEWTOY(basename, "^<1as:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
 USE_BC(NEWTOY(bc, "i(interactive)l(mathlib)q(quiet)s(standard)w(warn)", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_BLKDISCARD(NEWTOY(blkdiscard, "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]", TOYFLAG_BIN))
 USE_BLKID(NEWTOY(blkid, "ULs*[!LU]", TOYFLAG_BIN))
 USE_BLOCKDEV(NEWTOY(blockdev, "<1>1(setro)(setrw)(getro)(getss)(getbsz)(setbsz)#<0(getsz)(getsize)(getsize64)(getra)(setra)#<0(flushbufs)(rereadpt)",TOYFLAG_SBIN))
 USE_BOOTCHARTD(NEWTOY(bootchartd, 0, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN))
@@ -27,34 +30,35 @@
 USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK))
 USE_CHATTR(NEWTOY(chattr, "?p#v#R", TOYFLAG_BIN))
 USE_CHCON(NEWTOY(chcon, "<2hvR", TOYFLAG_USR|TOYFLAG_BIN))
-USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv[-HLP]", TOYFLAG_BIN))
-USE_CHMOD(NEWTOY(chmod, "<2?vRf[-vf]", TOYFLAG_BIN))
+USE_CHGRP(NEWTOY(chgrp, "<2h(no-dereference)PLHRfv[-HLP]", TOYFLAG_BIN))
+USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
 USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
 USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
 USE_CHRT(NEWTOY(chrt, "^mp#<0iRbrfo[!ibrfo]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_CKSUM(NEWTOY(cksum, "HIPLN", TOYFLAG_BIN))
 USE_CLEAR(NEWTOY(clear, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_CMP(NEWTOY(cmp, "<1>2ls(silent)(quiet)[!ls]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 USE_COMM(NEWTOY(comm, "<2>2321", TOYFLAG_USR|TOYFLAG_BIN))
 USE_COUNT(NEWTOY(count, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]", TOYFLAG_BIN))
-USE_CPIO(NEWTOY(cpio, "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
+USE_CP(NEWTOY(cp, "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]", TOYFLAG_BIN))
+USE_CPIO(NEWTOY(cpio, "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
 USE_CRC32(NEWTOY(crc32, 0, TOYFLAG_BIN))
 USE_CROND(NEWTOY(crond, "fbSl#<0=8d#<0L:c:[-bf][-LS][-ld]", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
 USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_CUT(NEWTOY(cut, "b*|c*|f*|F*|C*|O(output-delimiter):d:sDn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_DATE(NEWTOY(date, "d:D:r:u[!dr]", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]", TOYFLAG_BIN))
 USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEALLOCVT(NEWTOY(deallocvt, ">1", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_GROUPDEL(OLDTOY(delgroup, groupdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USERDEL(OLDTOY(deluser, userdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_DEMO_MANY_OPTIONS(NEWTOY(demo_many_options, "ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba", TOYFLAG_BIN))
-USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3hdbs", TOYFLAG_BIN))
+USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3M#<0hcdbs", TOYFLAG_BIN))
 USE_DEMO_SCANKEY(NEWTOY(demo_scankey, 0, TOYFLAG_BIN))
 USE_DEMO_UTF8TOWC(NEWTOY(demo_utf8towc, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEVMEM(NEWTOY(devmem, "<1>3", TOYFLAG_USR|TOYFLAG_BIN))
-USE_DF(NEWTOY(df, "HPkhit*a[-HPkh]", TOYFLAG_SBIN))
+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN))
 USE_DHCP(NEWTOY(dhcp, "V:H:F:x*r:O*A#<0=20T#<0=3t#<0=3s:p:i:SBRCaovqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCP6(NEWTOY(dhcp6, "r:A#<0T#<0t#<0s:p:i:SRvqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCPD(NEWTOY(dhcpd, ">1P#<0>65535fi:S46[!46]", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
@@ -63,20 +67,23 @@
 USE_DMESG(NEWTOY(dmesg, "w(follow)CSTtrs#<1n#c[!Ttr][!Cc][!Sw]", TOYFLAG_BIN))
 USE_DNSDOMAINNAME(NEWTOY(dnsdomainname, ">0", TOYFLAG_BIN))
 USE_DOS2UNIX(NEWTOY(dos2unix, 0, TOYFLAG_BIN))
-USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_DUMPLEASES(NEWTOY(dumpleases, ">0arf:[!ar]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
-USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_LINEBUF))
+USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_EJECT(NEWTOY(eject, ">1stT[!tT]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_ENV(NEWTOY(env, "^0iu*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_ENV(NEWTOY(env, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
 USE_EXPAND(NEWTOY(expand, "t*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
 USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_FACTOR(NEWTOY(factor, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_FALLOCATE(NEWTOY(fallocate, ">1l#|o#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FALSE(NEWTOY(false, NULL, TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
 USE_FDISK(NEWTOY(fdisk, "C#<0H#<0S#<0b#<512ul", TOYFLAG_SBIN))
-USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_FILE(NEWTOY(file, "<1bhLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FIND(NEWTOY(find, "?^HL[-HL]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FLOCK(NEWTOY(flock, "<1>1nsux[-sux]", TOYFLAG_USR|TOYFLAG_BIN))
@@ -94,8 +101,8 @@
 USE_GETENFORCE(NEWTOY(getenforce, ">0", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_GETFATTR(NEWTOY(getfattr, "(only-values)dhn:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GETOPT(NEWTOY(getopt, "^a(alternative)n:(name)o:(options)l*(long)(longoptions)Tu", TOYFLAG_USR|TOYFLAG_BIN))
-USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN))
-USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh", TOYFLAG_SBIN))
+USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_GROUPADD(NEWTOY(groupadd, "<1>2g#<0S", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_GROUPDEL(NEWTOY(groupdel, "<1>2", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_GROUPS(NEWTOY(groups, NULL, TOYFLAG_USR|TOYFLAG_BIN))
@@ -120,7 +127,7 @@
 USE_INIT(NEWTOY(init, "", TOYFLAG_SBIN))
 USE_INOTIFYD(NEWTOY(inotifyd, "<2", TOYFLAG_USR|TOYFLAG_BIN))
 USE_INSMOD(NEWTOY(insmod, "<1", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_INSTALL(NEWTOY(install, "<1cdDpsvt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IONICE(NEWTOY(ionice, "^tc#<0>3=2n#<0>7=5p#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IORENICE(NEWTOY(iorenice, "?<1>3", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IOTOP(NEWTOY(iotop, ">0AaKO" "Hk*o*p*u*s#<1=7d%<100=3000m#n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
@@ -132,6 +139,7 @@
 USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_KILL(NEWTOY(kill, "?ls: ", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_KILLALL(NEWTOY(killall, "?s:ilqvw", TOYFLAG_USR|TOYFLAG_BIN))
 USE_KILLALL5(NEWTOY(killall5, "?o*ls: [!lo][!ls]", TOYFLAG_SBIN))
@@ -141,7 +149,7 @@
 USE_LN(NEWTOY(ln, "<1rt:Tvnfs", TOYFLAG_BIN))
 USE_LOAD_POLICY(NEWTOY(load_policy, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_LOG(NEWTOY(log, "<1p:t:", TOYFLAG_USR|TOYFLAG_SBIN))
-USE_LOGGER(NEWTOY(logger, "st:p:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_LOGGER(NEWTOY(logger, "t:p:s", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LOGIN(NEWTOY(login, ">1f:ph:", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_LOGNAME(NEWTOY(logname, ">0", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LOGWRAPPER(NEWTOY(logwrapper, 0, TOYFLAG_NOHELP|TOYFLAG_USR|TOYFLAG_BIN))
@@ -157,7 +165,7 @@
 USE_MCOOKIE(NEWTOY(mcookie, "v(verbose)V(version)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MD5SUM(NEWTOY(md5sum, "bc(check)s(status)[!bc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MDEV(NEWTOY(mdev, "s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_UMASK))
-USE_MICROCOM(NEWTOY(microcom, "<1>1s:X", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MICROCOM(NEWTOY(microcom, "<1>1s#=115200X", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MIX(NEWTOY(mix, "c:d:l#r#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MKDIR(NEWTOY(mkdir, "<1"USE_MKDIR_Z("Z:")"vp(parent)(parents)m:", TOYFLAG_BIN|TOYFLAG_UMASK))
 USE_MKE2FS(NEWTOY(mke2fs, "<1>2g:Fnqm#N#i#b#", TOYFLAG_SBIN))
@@ -171,11 +179,11 @@
 USE_MORE(NEWTOY(more, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_MOUNT(NEWTOY(mount, "?O:afnrvwt:o*[-rw]", TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_MOUNTPOINT(NEWTOY(mountpoint, "<1qdx[-dx]", TOYFLAG_BIN))
-USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fiT[-ni]", TOYFLAG_BIN))
+USE_MV(NEWTOY(mv, "<1vnF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
 USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, TOYFLAG_USR|TOYFLAG_BIN))
 USE_NBD_CLIENT(NEWTOY(nbd_client, "<3>3ns", 0))
 USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN))
-USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
+USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tElL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
 USE_NETSTAT(NEWTOY(netstat, "pWrxwutneal", TOYFLAG_BIN))
 USE_NICE(NEWTOY(nice, "^<1n#", TOYFLAG_BIN))
 USE_NL(NEWTOY(nl, "v#=1l#w#<0=6Eb:n:s:", TOYFLAG_USR|TOYFLAG_BIN))
@@ -191,20 +199,21 @@
 USE_PATCH(NEWTOY(patch, ">2(no-backup-if-mismatch)(dry-run)"USE_TOYBOX_DEBUG("x")"F#g#fulp#d:i:Rs(quiet)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIDOF(NEWTOY(pidof, "<1so:x", TOYFLAG_BIN))
-USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIVOT_ROOT(NEWTOY(pivot_root, "<2>2", TOYFLAG_SBIN))
 USE_PKILL(NEWTOY(pkill,    "?Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PMAP(NEWTOY(pmap, "<1xq", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(OLDTOY(poweroff, reboot, TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_PRINTENV(NEWTOY(printenv, "0(null)", TOYFLAG_BIN))
+USE_PRINTENV(NEWTOY(printenv, "(null)0", TOYFLAG_BIN))
 USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_ULIMIT(OLDTOY(prlimit, ulimit, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*Tu*U*g*G*wZ[!ol][+Ae][!oO]", TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_PWD(NEWTOY(pwd, ">0LP[-LP]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_PWDX(NEWTOY(pwdx, "<1a", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PWGEN(NEWTOY(pwgen, ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READAHEAD(NEWTOY(readahead, NULL, TOYFLAG_BIN))
-USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adhlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REALPATH(NEWTOY(realpath, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(NEWTOY(reboot, "fn", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
@@ -214,28 +223,33 @@
 USE_REV(NEWTOY(rev, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_RFKILL(NEWTOY(rfkill, "<1>2", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
-USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p", TOYFLAG_BIN))
+USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p(parents)", TOYFLAG_BIN))
 USE_RMMOD(NEWTOY(rmmod, "<1wf", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN))
+USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN))
+USE_RTCWAKE(NEWTOY(rtcwake, "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_RUNCON(NEWTOY(runcon, "<2", TOYFLAG_USR|TOYFLAG_SBIN))
-USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
+USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
 USE_SENDEVENT(NEWTOY(sendevent, "<4>4", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_SEQ(NEWTOY(seq, "<1>3?f:s:w[!fw]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
 USE_SETENFORCE(NEWTOY(setenforce, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_SETFATTR(NEWTOY(setfattr, "hn:|v:x:|[!xv]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SETSID(NEWTOY(setsid, "^<1wcd[!dc]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
+USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
 USE_SHA1SUM(NEWTOY(sha1sum, "bc(check)s(status)[!bc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TOYBOX_LIBCRYPTO(USE_SHA224SUM(OLDTOY(sha224sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
 USE_TOYBOX_LIBCRYPTO(USE_SHA256SUM(OLDTOY(sha256sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
 USE_TOYBOX_LIBCRYPTO(USE_SHA384SUM(OLDTOY(sha384sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
+USE_SHA3SUM(NEWTOY(sha3sum, "bSa#<128>512=224", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TOYBOX_LIBCRYPTO(USE_SHA512SUM(OLDTOY(sha512sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
+USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
 USE_SHRED(NEWTOY(shred, "<1zxus#<1n#<1o#<0f", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SKELETON(NEWTOY(skeleton, "(walrus)(blubber):;(also):e@d*c#b:a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SKELETON_ALIAS(NEWTOY(skeleton_alias, "b#dq", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN))
 USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
 USE_SPLIT(NEWTOY(split, ">2a#<1=2>9b#<1l#<1[!bl]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_STAT(NEWTOY(stat, "<1c:(format)fLt", TOYFLAG_BIN))
 USE_STRINGS(NEWTOY(strings, "t:an#=4<1fo", TOYFLAG_USR|TOYFLAG_BIN))
@@ -250,7 +264,7 @@
 USE_SYSLOGD(NEWTOY(syslogd,">0l#<1>8=8R:b#<0>99=1s#<0=200m#<0>71582787=20O:p:f:a:nSKLD", TOYFLAG_SBIN|TOYFLAG_STAYROOT))
 USE_TAC(NEWTOY(tac, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TASKSET(NEWTOY(taskset, "<1^pa", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TEE(NEWTOY(tee, "ia", TOYFLAG_USR|TOYFLAG_BIN))
@@ -275,9 +289,11 @@
 USE_ULIMIT(NEWTOY(ulimit, ">1P#<1SHavutsrRqpnmlifedc[-SH][!apvutsrRqnmlifedc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UMOUNT(NEWTOY(umount, "cndDflrat*v[!na]", TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_UNAME(NEWTOY(uname, "oamvrns[+os]", TOYFLAG_BIN))
+USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN))
 USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK))
 USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UPTIME(NEWTOY(uptime, ">0ps", TOYFLAG_USR|TOYFLAG_BIN))
 USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
@@ -290,14 +306,16 @@
 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN))
 USE_W(NEWTOY(w, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
 USE_WC(NEWTOY(wc, "mcwl", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_WGET(NEWTOY(wget, "(no-check-certificate)O:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHICH(NEWTOY(which, "<1a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHO(NEWTOY(who, "a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHOAMI(OLDTOY(whoami, logname, TOYFLAG_USR|TOYFLAG_BIN))
-USE_XARGS(NEWTOY(xargs, "^E:P#optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_XXD(NEWTOY(xxd, ">1c#l#o#g#<1=2iprs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_XZCAT(NEWTOY(xzcat, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
 USE_ZCAT(NEWTOY(zcat,     "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/android/linux/generated/config.h b/android/linux/generated/config.h
index 7fdfaeb..79502c0 100644
--- a/android/linux/generated/config.h
+++ b/android/linux/generated/config.h
@@ -64,12 +64,16 @@
 #define USE_ARP(...)
 #define CFG_ASCII 0
 #define USE_ASCII(...)
+#define CFG_BASE32 0
+#define USE_BASE32(...)
 #define CFG_BASE64 0
 #define USE_BASE64(...)
 #define CFG_BASENAME 1
 #define USE_BASENAME(...) __VA_ARGS__
 #define CFG_BC 0
 #define USE_BC(...)
+#define CFG_BLKDISCARD 0
+#define USE_BLKDISCARD(...)
 #define CFG_BLKID 0
 #define USE_BLKID(...)
 #define CFG_BLOCKDEV 0
@@ -106,6 +110,8 @@
 #define USE_CHROOT(...)
 #define CFG_CHRT 0
 #define USE_CHRT(...)
+#define CFG_CHSH 0
+#define USE_CHSH(...)
 #define CFG_CHVT 0
 #define USE_CHVT(...)
 #define CFG_CKSUM 0
@@ -118,8 +124,8 @@
 #define USE_COMM(...) __VA_ARGS__
 #define CFG_COUNT 0
 #define USE_COUNT(...)
-#define CFG_CPIO 0
-#define USE_CPIO(...)
+#define CFG_CPIO 1
+#define USE_CPIO(...) __VA_ARGS__
 #define CFG_CP_PRESERVE 1
 #define USE_CP_PRESERVE(...) __VA_ARGS__
 #define CFG_CP 1
@@ -194,8 +200,8 @@
 #define USE_FALSE(...)
 #define CFG_FDISK 0
 #define USE_FDISK(...)
-#define CFG_FGREP 0
-#define USE_FGREP(...)
+#define CFG_FGREP 1
+#define USE_FGREP(...) __VA_ARGS__
 #define CFG_FILE 0
 #define USE_FILE(...)
 #define CFG_FIND 1
@@ -408,8 +414,8 @@
 #define USE_NETSTAT(...)
 #define CFG_NICE 0
 #define USE_NICE(...)
-#define CFG_NL 0
-#define USE_NL(...)
+#define CFG_NL 1
+#define USE_NL(...) __VA_ARGS__
 #define CFG_NOHUP 0
 #define USE_NOHUP(...)
 #define CFG_NPROC 1
@@ -446,14 +452,16 @@
 #define USE_PMAP(...)
 #define CFG_PRINTENV 0
 #define USE_PRINTENV(...)
-#define CFG_PRINTF 0
-#define USE_PRINTF(...)
+#define CFG_PRINTF 1
+#define USE_PRINTF(...) __VA_ARGS__
 #define CFG_PS 1
 #define USE_PS(...) __VA_ARGS__
 #define CFG_PWDX 0
 #define USE_PWDX(...)
 #define CFG_PWD 1
 #define USE_PWD(...) __VA_ARGS__
+#define CFG_PWGEN 0
+#define USE_PWGEN(...)
 #define CFG_READAHEAD 0
 #define USE_READAHEAD(...)
 #define CFG_READELF 0
@@ -482,6 +490,8 @@
 #define USE_RM(...) __VA_ARGS__
 #define CFG_ROUTE 0
 #define USE_ROUTE(...)
+#define CFG_RTCWAKE 0
+#define USE_RTCWAKE(...)
 #define CFG_RUNCON 0
 #define USE_RUNCON(...)
 #define CFG_SED 1
@@ -502,6 +512,8 @@
 #define USE_SHA224SUM(...)
 #define CFG_SHA256SUM 1
 #define USE_SHA256SUM(...) __VA_ARGS__
+#define CFG_SHA3SUM 0
+#define USE_SHA3SUM(...)
 #define CFG_SHA384SUM 0
 #define USE_SHA384SUM(...)
 #define CFG_SHA512SUM 1
@@ -564,6 +576,8 @@
 #define USE_TELNET(...)
 #define CFG_TEST 1
 #define USE_TEST(...) __VA_ARGS__
+#define CFG_TEST_GLUE 1
+#define USE_TEST_GLUE(...) __VA_ARGS__
 #define CFG_TFTPD 0
 #define USE_TFTPD(...)
 #define CFG_TFTP 0
@@ -594,6 +608,8 @@
 #define USE_UMOUNT(...)
 #define CFG_UNAME 1
 #define USE_UNAME(...) __VA_ARGS__
+#define CFG_UNICODE 0
+#define USE_UNICODE(...)
 #define CFG_UNIQ 1
 #define USE_UNIQ(...) __VA_ARGS__
 #define CFG_UNIX2DOS 1
@@ -624,6 +640,8 @@
 #define USE_VMSTAT(...)
 #define CFG_WATCH 0
 #define USE_WATCH(...)
+#define CFG_WATCHDOG 0
+#define USE_WATCHDOG(...)
 #define CFG_WC 1
 #define USE_WC(...) __VA_ARGS__
 #define CFG_WGET 0
diff --git a/android/linux/generated/flags.h b/android/linux/generated/flags.h
index a87b935..7f37168 100644
--- a/android/linux/generated/flags.h
+++ b/android/linux/generated/flags.h
@@ -73,6 +73,17 @@
 #undef FOR_ascii
 #endif
 
+// base32   diw#<0=76[!dw]
+#undef OPTSTR_base32
+#define OPTSTR_base32 "diw#<0=76[!dw]"
+#ifdef CLEANUP_base32
+#undef CLEANUP_base32
+#undef FOR_base32
+#undef FLAG_w
+#undef FLAG_i
+#undef FLAG_d
+#endif
+
 // base64   diw#<0=76[!dw]
 #undef OPTSTR_base64
 #define OPTSTR_base64 "diw#<0=76[!dw]"
@@ -107,6 +118,19 @@
 #undef FLAG_i
 #endif
 
+// blkdiscard   <1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]
+#undef OPTSTR_blkdiscard
+#define OPTSTR_blkdiscard "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]"
+#ifdef CLEANUP_blkdiscard
+#undef CLEANUP_blkdiscard
+#undef FOR_blkdiscard
+#undef FLAG_z
+#undef FLAG_s
+#undef FLAG_o
+#undef FLAG_l
+#undef FLAG_f
+#endif
+
 // blkid   ULs*[!LU]
 #undef OPTSTR_blkid
 #define OPTSTR_blkid "ULs*[!LU]"
@@ -240,9 +264,9 @@
 #undef FLAG_h
 #endif
 
-// chgrp   <2hPLHRfv[-HLP]
+// chgrp   <2h(no-dereference)PLHRfv[-HLP]
 #undef OPTSTR_chgrp
-#define OPTSTR_chgrp "<2hPLHRfv[-HLP]"
+#define OPTSTR_chgrp "<2h(no-dereference)PLHRfv[-HLP]"
 #ifdef CLEANUP_chgrp
 #undef CLEANUP_chgrp
 #undef FOR_chgrp
@@ -255,14 +279,14 @@
 #undef FLAG_h
 #endif
 
-// chmod <2?vRf[-vf] <2?vRf[-vf]
+// chmod <2?vfR[-vf] <2?vfR[-vf]
 #undef OPTSTR_chmod
-#define OPTSTR_chmod "<2?vRf[-vf]"
+#define OPTSTR_chmod "<2?vfR[-vf]"
 #ifdef CLEANUP_chmod
 #undef CLEANUP_chmod
 #undef FOR_chmod
-#undef FLAG_f
 #undef FLAG_R
+#undef FLAG_f
 #undef FLAG_v
 #endif
 
@@ -290,6 +314,15 @@
 #undef FLAG_m
 #endif
 
+// chsh   s:
+#undef OPTSTR_chsh
+#define OPTSTR_chsh "s:"
+#ifdef CLEANUP_chsh
+#undef CLEANUP_chsh
+#undef FOR_chsh
+#undef FLAG_s
+#endif
+
 // chvt   <1
 #undef OPTSTR_chvt
 #define OPTSTR_chvt "<1"
@@ -348,13 +381,14 @@
 #undef FOR_count
 #endif
 
-// cp <2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni] <2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]
+// cp <1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu] <1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]
 #undef OPTSTR_cp
-#define OPTSTR_cp "<2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]"
+#define OPTSTR_cp "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]"
 #ifdef CLEANUP_cp
 #undef CLEANUP_cp
 #undef FOR_cp
 #undef FLAG_T
+#undef FLAG_t
 #undef FLAG_i
 #undef FLAG_f
 #undef FLAG_F
@@ -364,6 +398,7 @@
 #undef FLAG_s
 #undef FLAG_a
 #undef FLAG_d
+#undef FLAG_u
 #undef FLAG_r
 #undef FLAG_p
 #undef FLAG_P
@@ -374,9 +409,9 @@
 #undef FLAG_preserve
 #endif
 
-// cpio   (no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]
+// cpio (quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF] (quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]
 #undef OPTSTR_cpio
-#define OPTSTR_cpio "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]"
+#define OPTSTR_cpio "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]"
 #ifdef CLEANUP_cpio
 #undef CLEANUP_cpio
 #undef FOR_cpio
@@ -390,8 +425,8 @@
 #undef FLAG_u
 #undef FLAG_d
 #undef FLAG_m
-#undef FLAG_trailer
 #undef FLAG_no_preserve_owner
+#undef FLAG_quiet
 #endif
 
 // crc32    
@@ -448,14 +483,15 @@
 #undef FLAG_b
 #endif
 
-// date d:D:r:u[!dr] d:D:r:u[!dr]
+// date d:D:I(iso)(iso-8601):;r:u(utc)[!dr] d:D:I(iso)(iso-8601):;r:u(utc)[!dr]
 #undef OPTSTR_date
-#define OPTSTR_date "d:D:r:u[!dr]"
+#define OPTSTR_date "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]"
 #ifdef CLEANUP_date
 #undef CLEANUP_date
 #undef FOR_date
 #undef FLAG_u
 #undef FLAG_r
+#undef FLAG_I
 #undef FLAG_D
 #undef FLAG_d
 #endif
@@ -536,16 +572,18 @@
 #undef FLAG_Z
 #endif
 
-// demo_number   D#=3<3hdbs
+// demo_number   D#=3<3M#<0hcdbs
 #undef OPTSTR_demo_number
-#define OPTSTR_demo_number "D#=3<3hdbs"
+#define OPTSTR_demo_number "D#=3<3M#<0hcdbs"
 #ifdef CLEANUP_demo_number
 #undef CLEANUP_demo_number
 #undef FOR_demo_number
 #undef FLAG_s
 #undef FLAG_b
 #undef FLAG_d
+#undef FLAG_c
 #undef FLAG_h
+#undef FLAG_M
 #undef FLAG_D
 #endif
 
@@ -573,9 +611,9 @@
 #undef FOR_devmem
 #endif
 
-// df   HPkhit*a[-HPkh]
+// df   HPkhit*a[-HPh]
 #undef OPTSTR_df
-#define OPTSTR_df "HPkhit*a[-HPkh]"
+#define OPTSTR_df "HPkhit*a[-HPh]"
 #ifdef CLEANUP_df
 #undef CLEANUP_df
 #undef FOR_df
@@ -722,12 +760,13 @@
 #undef FOR_dos2unix
 #endif
 
-// du d#<0=-1hmlcaHkKLsx[-HL][-kKmh] d#<0=-1hmlcaHkKLsx[-HL][-kKmh]
+// du d#<0=-1hmlcaHkKLsxb[-HL][-kKmh] d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]
 #undef OPTSTR_du
-#define OPTSTR_du "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]"
+#define OPTSTR_du "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]"
 #ifdef CLEANUP_du
 #undef CLEANUP_du
 #undef FOR_du
+#undef FLAG_b
 #undef FLAG_x
 #undef FLAG_s
 #undef FLAG_L
@@ -775,15 +814,34 @@
 #undef FLAG_s
 #endif
 
-// env ^0iu* ^0iu*
+// env ^i0u* ^i0u*
 #undef OPTSTR_env
-#define OPTSTR_env "^0iu*"
+#define OPTSTR_env "^i0u*"
 #ifdef CLEANUP_env
 #undef CLEANUP_env
 #undef FOR_env
 #undef FLAG_u
-#undef FLAG_i
 #undef FLAG_0
+#undef FLAG_i
+#endif
+
+// eval    
+#undef OPTSTR_eval
+#define OPTSTR_eval 0
+#ifdef CLEANUP_eval
+#undef CLEANUP_eval
+#undef FOR_eval
+#endif
+
+// exec   ^cla:
+#undef OPTSTR_exec
+#define OPTSTR_exec "^cla:"
+#ifdef CLEANUP_exec
+#undef CLEANUP_exec
+#undef FOR_exec
+#undef FLAG_a
+#undef FLAG_l
+#undef FLAG_c
 #endif
 
 // exit    
@@ -803,6 +861,16 @@
 #undef FLAG_t
 #endif
 
+// export   np
+#undef OPTSTR_export
+#define OPTSTR_export "np"
+#ifdef CLEANUP_export
+#undef CLEANUP_export
+#undef FOR_export
+#undef FLAG_p
+#undef FLAG_n
+#endif
+
 // expr    
 #undef OPTSTR_expr
 #define OPTSTR_expr 0
@@ -1355,15 +1423,16 @@
 #undef FOR_insmod
 #endif
 
-// install   <1cdDpsvm:o:g:
+// install   <1cdDpsvt:m:o:g:
 #undef OPTSTR_install
-#define OPTSTR_install "<1cdDpsvm:o:g:"
+#define OPTSTR_install "<1cdDpsvt:m:o:g:"
 #ifdef CLEANUP_install
 #undef CLEANUP_install
 #undef FOR_install
 #undef FLAG_g
 #undef FLAG_o
 #undef FLAG_m
+#undef FLAG_t
 #undef FLAG_v
 #undef FLAG_s
 #undef FLAG_p
@@ -1455,6 +1524,19 @@
 #undef FLAG_a
 #endif
 
+// jobs   lnprs
+#undef OPTSTR_jobs
+#define OPTSTR_jobs "lnprs"
+#ifdef CLEANUP_jobs
+#undef CLEANUP_jobs
+#undef FOR_jobs
+#undef FLAG_s
+#undef FLAG_r
+#undef FLAG_p
+#undef FLAG_n
+#undef FLAG_l
+#endif
+
 // kill   ?ls: 
 #undef OPTSTR_kill
 #define OPTSTR_kill "?ls: "
@@ -1551,15 +1633,15 @@
 #undef FLAG_p
 #endif
 
-// logger   st:p:
+// logger   t:p:s
 #undef OPTSTR_logger
-#define OPTSTR_logger "st:p:"
+#define OPTSTR_logger "t:p:s"
 #ifdef CLEANUP_logger
 #undef CLEANUP_logger
 #undef FOR_logger
+#undef FLAG_s
 #undef FLAG_p
 #undef FLAG_t
-#undef FLAG_s
 #endif
 
 // login   >1f:ph:
@@ -1751,9 +1833,9 @@
 #undef FLAG_s
 #endif
 
-// microcom <1>1s:X <1>1s:X
+// microcom <1>1s#=115200X <1>1s#=115200X
 #undef OPTSTR_microcom
-#define OPTSTR_microcom "<1>1s:X"
+#define OPTSTR_microcom "<1>1s#=115200X"
 #ifdef CLEANUP_microcom
 #undef CLEANUP_microcom
 #undef FOR_microcom
@@ -1920,13 +2002,14 @@
 #undef FLAG_q
 #endif
 
-// mv <2vnF(remove-destination)fiT[-ni] <2vnF(remove-destination)fiT[-ni]
+// mv <1vnF(remove-destination)fit:T[-ni] <1vnF(remove-destination)fit:T[-ni]
 #undef OPTSTR_mv
-#define OPTSTR_mv "<2vnF(remove-destination)fiT[-ni]"
+#define OPTSTR_mv "<1vnF(remove-destination)fit:T[-ni]"
 #ifdef CLEANUP_mv
 #undef CLEANUP_mv
 #undef FOR_mv
 #undef FLAG_T
+#undef FLAG_t
 #undef FLAG_i
 #undef FLAG_f
 #undef FLAG_F
@@ -1944,9 +2027,9 @@
 #undef FLAG_n
 #endif
 
-// netcat   ^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]
+// netcat   ^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]
 #undef OPTSTR_netcat
-#define OPTSTR_netcat "^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]"
+#define OPTSTR_netcat "^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]"
 #ifdef CLEANUP_netcat
 #undef CLEANUP_netcat
 #undef FOR_netcat
@@ -1962,6 +2045,7 @@
 #undef FLAG_w
 #undef FLAG_L
 #undef FLAG_l
+#undef FLAG_E
 #undef FLAG_t
 #endif
 
@@ -1993,7 +2077,7 @@
 #undef FLAG_n
 #endif
 
-// nl   v#=1l#w#<0=6Eb:n:s:
+// nl v#=1l#w#<0=6Eb:n:s: v#=1l#w#<0=6Eb:n:s:
 #undef OPTSTR_nl
 #define OPTSTR_nl "v#=1l#w#<0=6Eb:n:s:"
 #ifdef CLEANUP_nl
@@ -2170,9 +2254,9 @@
 #undef FLAG_s
 #endif
 
-// ping   <1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]
+// ping   <1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]
 #undef OPTSTR_ping
-#define OPTSTR_ping "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]"
+#define OPTSTR_ping "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]"
 #ifdef CLEANUP_ping
 #undef CLEANUP_ping
 #undef FOR_ping
@@ -2230,16 +2314,17 @@
 #undef FLAG_x
 #endif
 
-// printenv   0(null)
+// printenv   (null)0
 #undef OPTSTR_printenv
-#define OPTSTR_printenv "0(null)"
+#define OPTSTR_printenv "(null)0"
 #ifdef CLEANUP_printenv
 #undef CLEANUP_printenv
 #undef FOR_printenv
 #undef FLAG_0
+#undef FLAG_null
 #endif
 
-// printf   <1?^
+// printf <1?^ <1?^
 #undef OPTSTR_printf
 #define OPTSTR_printf "<1?^"
 #ifdef CLEANUP_printf
@@ -2296,6 +2381,26 @@
 #undef FLAG_a
 #endif
 
+// pwgen   >2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]
+#undef OPTSTR_pwgen
+#define OPTSTR_pwgen ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]"
+#ifdef CLEANUP_pwgen
+#undef CLEANUP_pwgen
+#undef FOR_pwgen
+#undef FLAG_0
+#undef FLAG_A
+#undef FLAG_v
+#undef FLAG_1
+#undef FLAG_C
+#undef FLAG_h
+#undef FLAG_B
+#undef FLAG_s
+#undef FLAG_y
+#undef FLAG_n
+#undef FLAG_c
+#undef FLAG_r
+#endif
+
 // readahead    
 #undef OPTSTR_readahead
 #define OPTSTR_readahead 0
@@ -2304,9 +2409,9 @@
 #undef FOR_readahead
 #endif
 
-// readelf   <1(dyn-syms)adhlnp:SsWx:
+// readelf   <1(dyn-syms)adehlnp:SsWx:
 #undef OPTSTR_readelf
-#define OPTSTR_readelf "<1(dyn-syms)adhlnp:SsWx:"
+#define OPTSTR_readelf "<1(dyn-syms)adehlnp:SsWx:"
 #ifdef CLEANUP_readelf
 #undef CLEANUP_readelf
 #undef FOR_readelf
@@ -2318,6 +2423,7 @@
 #undef FLAG_n
 #undef FLAG_l
 #undef FLAG_h
+#undef FLAG_e
 #undef FLAG_d
 #undef FLAG_a
 #undef FLAG_dyn_syms
@@ -2417,9 +2523,9 @@
 #undef FLAG_f
 #endif
 
-// rmdir <1(ignore-fail-on-non-empty)p <1(ignore-fail-on-non-empty)p
+// rmdir <1(ignore-fail-on-non-empty)p(parents) <1(ignore-fail-on-non-empty)p(parents)
 #undef OPTSTR_rmdir
-#define OPTSTR_rmdir "<1(ignore-fail-on-non-empty)p"
+#define OPTSTR_rmdir "<1(ignore-fail-on-non-empty)p(parents)"
 #ifdef CLEANUP_rmdir
 #undef CLEANUP_rmdir
 #undef FOR_rmdir
@@ -2448,6 +2554,24 @@
 #undef FLAG_n
 #endif
 
+// rtcwake   (list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]
+#undef OPTSTR_rtcwake
+#define OPTSTR_rtcwake "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]"
+#ifdef CLEANUP_rtcwake
+#undef CLEANUP_rtcwake
+#undef FOR_rtcwake
+#undef FLAG_v
+#undef FLAG_u
+#undef FLAG_t
+#undef FLAG_s
+#undef FLAG_m
+#undef FLAG_l
+#undef FLAG_d
+#undef FLAG_a
+#undef FLAG_auto
+#undef FLAG_list_modes
+#endif
+
 // runcon   <2
 #undef OPTSTR_runcon
 #define OPTSTR_runcon "<2"
@@ -2456,12 +2580,13 @@
 #undef FOR_runcon
 #endif
 
-// sed (help)(version)e*f*i:;nErz(null-data)[+Er] (help)(version)e*f*i:;nErz(null-data)[+Er]
+// sed (help)(version)e*f*i:;nErz(null-data)s[+Er] (help)(version)e*f*i:;nErz(null-data)s[+Er]
 #undef OPTSTR_sed
-#define OPTSTR_sed "(help)(version)e*f*i:;nErz(null-data)[+Er]"
+#define OPTSTR_sed "(help)(version)e*f*i:;nErz(null-data)s[+Er]"
 #ifdef CLEANUP_sed
 #undef CLEANUP_sed
 #undef FOR_sed
+#undef FLAG_s
 #undef FLAG_z
 #undef FLAG_r
 #undef FLAG_E
@@ -2492,6 +2617,14 @@
 #undef FLAG_f
 #endif
 
+// set    
+#undef OPTSTR_set
+#define OPTSTR_set 0
+#ifdef CLEANUP_set
+#undef CLEANUP_set
+#undef FOR_set
+#endif
+
 // setenforce   <1>1
 #undef OPTSTR_setenforce
 #define OPTSTR_setenforce "<1>1"
@@ -2523,9 +2656,9 @@
 #undef FLAG_w
 #endif
 
-// sh   (noediting)(noprofile)(norc)sc:i
+// sh   0(noediting)(noprofile)(norc)sc:i
 #undef OPTSTR_sh
-#define OPTSTR_sh "(noediting)(noprofile)(norc)sc:i"
+#define OPTSTR_sh "0(noediting)(noprofile)(norc)sc:i"
 #ifdef CLEANUP_sh
 #undef CLEANUP_sh
 #undef FOR_sh
@@ -2548,6 +2681,25 @@
 #undef FLAG_b
 #endif
 
+// sha3sum   bSa#<128>512=224
+#undef OPTSTR_sha3sum
+#define OPTSTR_sha3sum "bSa#<128>512=224"
+#ifdef CLEANUP_sha3sum
+#undef CLEANUP_sha3sum
+#undef FOR_sha3sum
+#undef FLAG_a
+#undef FLAG_S
+#undef FLAG_b
+#endif
+
+// shift   >1
+#undef OPTSTR_shift
+#define OPTSTR_shift ">1"
+#ifdef CLEANUP_shift
+#undef CLEANUP_shift
+#undef FOR_shift
+#endif
+
 // shred   <1zxus#<1n#<1o#<0f
 #undef OPTSTR_shred
 #define OPTSTR_shred "<1zxus#<1n#<1o#<0f"
@@ -2645,6 +2797,14 @@
 #undef FLAG_g
 #endif
 
+// source   <1
+#undef OPTSTR_source
+#define OPTSTR_source "<1"
+#ifdef CLEANUP_source
+#undef CLEANUP_source
+#undef FOR_source
+#endif
+
 // split   >2a#<1=2>9b#<1l#<1[!bl]
 #undef OPTSTR_split
 #define OPTSTR_split ">2a#<1=2>9b#<1l#<1[!bl]"
@@ -2809,9 +2969,9 @@
 #undef FLAG_f
 #endif
 
-// tar &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa] &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]
+// tar &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa] &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]
 #undef OPTSTR_tar
-#define OPTSTR_tar "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]"
+#define OPTSTR_tar "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]"
 #ifdef CLEANUP_tar
 #undef CLEANUP_tar
 #undef FOR_tar
@@ -2821,11 +2981,13 @@
 #undef FLAG_T
 #undef FLAG_X
 #undef FLAG_m
+#undef FLAG_P
 #undef FLAG_O
 #undef FLAG_S
 #undef FLAG_z
 #undef FLAG_j
 #undef FLAG_J
+#undef FLAG_I
 #undef FLAG_v
 #undef FLAG_t
 #undef FLAG_x
@@ -3149,6 +3311,14 @@
 #undef FLAG_o
 #endif
 
+// unicode   <1
+#undef OPTSTR_unicode
+#define OPTSTR_unicode "<1"
+#ifdef CLEANUP_unicode
+#undef CLEANUP_unicode
+#undef FOR_unicode
+#endif
+
 // uniq f#s#w#zicdu f#s#w#zicdu
 #undef OPTSTR_uniq
 #define OPTSTR_uniq "f#s#w#zicdu"
@@ -3181,6 +3351,17 @@
 #undef FOR_unlink
 #endif
 
+// unset   fvn[!fv]
+#undef OPTSTR_unset
+#define OPTSTR_unset "fvn[!fv]"
+#ifdef CLEANUP_unset
+#undef CLEANUP_unset
+#undef FOR_unset
+#undef FLAG_n
+#undef FLAG_v
+#undef FLAG_f
+#endif
+
 // unshare   <1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);
 #undef OPTSTR_unshare
 #define OPTSTR_unshare "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);"
@@ -3301,6 +3482,15 @@
 #undef FOR_w
 #endif
 
+// wait   n
+#undef OPTSTR_wait
+#define OPTSTR_wait "n"
+#ifdef CLEANUP_wait
+#undef CLEANUP_wait
+#undef FOR_wait
+#undef FLAG_n
+#endif
+
 // watch   ^<1n%<100=2000tebx
 #undef OPTSTR_watch
 #define OPTSTR_watch "^<1n%<100=2000tebx"
@@ -3314,6 +3504,17 @@
 #undef FLAG_n
 #endif
 
+// watchdog   <1>1Ft#=4<1T#=60<1
+#undef OPTSTR_watchdog
+#define OPTSTR_watchdog "<1>1Ft#=4<1T#=60<1"
+#ifdef CLEANUP_watchdog
+#undef CLEANUP_watchdog
+#undef FOR_watchdog
+#undef FLAG_T
+#undef FLAG_t
+#undef FLAG_F
+#endif
+
 // wc mcwl mcwl
 #undef OPTSTR_wc
 #define OPTSTR_wc "mcwl"
@@ -3354,9 +3555,9 @@
 #undef FLAG_a
 #endif
 
-// xargs ^E:P#optrn#<1(max-args)s#0[!0E] ^E:P#optrn#<1(max-args)s#0[!0E]
+// xargs ^E:P#<0=1optrn#<1(max-args)s#0[!0E] ^E:P#<0=1optrn#<1(max-args)s#0[!0E]
 #undef OPTSTR_xargs
-#define OPTSTR_xargs "^E:P#optrn#<1(max-args)s#0[!0E]"
+#define OPTSTR_xargs "^E:P#<0=1optrn#<1(max-args)s#0[!0E]"
 #ifdef CLEANUP_xargs
 #undef CLEANUP_xargs
 #undef FOR_xargs
@@ -3479,6 +3680,15 @@
 #endif
 #endif
 
+#ifdef FOR_base32
+#ifndef TT
+#define TT this.base32
+#endif
+#define FLAG_w (FORCED_FLAG<<0)
+#define FLAG_i (FORCED_FLAG<<1)
+#define FLAG_d (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_base64
 #ifndef TT
 #define TT this.base64
@@ -3507,6 +3717,17 @@
 #define FLAG_i (FORCED_FLAG<<4)
 #endif
 
+#ifdef FOR_blkdiscard
+#ifndef TT
+#define TT this.blkdiscard
+#endif
+#define FLAG_z (FORCED_FLAG<<0)
+#define FLAG_s (FORCED_FLAG<<1)
+#define FLAG_o (FORCED_FLAG<<2)
+#define FLAG_l (FORCED_FLAG<<3)
+#define FLAG_f (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_blkid
 #ifndef TT
 #define TT this.blkid
@@ -3633,8 +3854,8 @@
 #ifndef TT
 #define TT this.chmod
 #endif
-#define FLAG_f (1<<0)
-#define FLAG_R (1<<1)
+#define FLAG_R (1<<0)
+#define FLAG_f (1<<1)
 #define FLAG_v (1<<2)
 #endif
 
@@ -3658,6 +3879,13 @@
 #define FLAG_m (FORCED_FLAG<<7)
 #endif
 
+#ifdef FOR_chsh
+#ifndef TT
+#define TT this.chsh
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_chvt
 #ifndef TT
 #define TT this.chvt
@@ -3709,41 +3937,43 @@
 #define TT this.cp
 #endif
 #define FLAG_T (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_f (1<<2)
-#define FLAG_F (1<<3)
-#define FLAG_n (1<<4)
-#define FLAG_v (1<<5)
-#define FLAG_l (1<<6)
-#define FLAG_s (1<<7)
-#define FLAG_a (1<<8)
-#define FLAG_d (1<<9)
-#define FLAG_r (1<<10)
-#define FLAG_p (1<<11)
-#define FLAG_P (1<<12)
-#define FLAG_L (1<<13)
-#define FLAG_H (1<<14)
-#define FLAG_R (1<<15)
-#define FLAG_D (1<<16)
-#define FLAG_preserve (1<<17)
+#define FLAG_t (1<<1)
+#define FLAG_i (1<<2)
+#define FLAG_f (1<<3)
+#define FLAG_F (1<<4)
+#define FLAG_n (1<<5)
+#define FLAG_v (1<<6)
+#define FLAG_l (1<<7)
+#define FLAG_s (1<<8)
+#define FLAG_a (1<<9)
+#define FLAG_d (1<<10)
+#define FLAG_u (1<<11)
+#define FLAG_r (1<<12)
+#define FLAG_p (1<<13)
+#define FLAG_P (1<<14)
+#define FLAG_L (1<<15)
+#define FLAG_H (1<<16)
+#define FLAG_R (1<<17)
+#define FLAG_D (1<<18)
+#define FLAG_preserve (1<<19)
 #endif
 
 #ifdef FOR_cpio
 #ifndef TT
 #define TT this.cpio
 #endif
-#define FLAG_o (FORCED_FLAG<<0)
-#define FLAG_v (FORCED_FLAG<<1)
-#define FLAG_F (FORCED_FLAG<<2)
-#define FLAG_t (FORCED_FLAG<<3)
-#define FLAG_i (FORCED_FLAG<<4)
-#define FLAG_p (FORCED_FLAG<<5)
-#define FLAG_H (FORCED_FLAG<<6)
-#define FLAG_u (FORCED_FLAG<<7)
-#define FLAG_d (FORCED_FLAG<<8)
-#define FLAG_m (FORCED_FLAG<<9)
-#define FLAG_trailer (FORCED_FLAG<<10)
-#define FLAG_no_preserve_owner (FORCED_FLAG<<11)
+#define FLAG_o (1<<0)
+#define FLAG_v (1<<1)
+#define FLAG_F (1<<2)
+#define FLAG_t (1<<3)
+#define FLAG_i (1<<4)
+#define FLAG_p (1<<5)
+#define FLAG_H (1<<6)
+#define FLAG_u (1<<7)
+#define FLAG_d (1<<8)
+#define FLAG_m (1<<9)
+#define FLAG_no_preserve_owner (1<<10)
+#define FLAG_quiet (1<<11)
 #endif
 
 #ifdef FOR_crc32
@@ -3798,8 +4028,9 @@
 #endif
 #define FLAG_u (1<<0)
 #define FLAG_r (1<<1)
-#define FLAG_D (1<<2)
-#define FLAG_d (1<<3)
+#define FLAG_I (1<<2)
+#define FLAG_D (1<<3)
+#define FLAG_d (1<<4)
 #endif
 
 #ifdef FOR_dd
@@ -3879,8 +4110,10 @@
 #define FLAG_s (FORCED_FLAG<<0)
 #define FLAG_b (FORCED_FLAG<<1)
 #define FLAG_d (FORCED_FLAG<<2)
-#define FLAG_h (FORCED_FLAG<<3)
-#define FLAG_D (FORCED_FLAG<<4)
+#define FLAG_c (FORCED_FLAG<<3)
+#define FLAG_h (FORCED_FLAG<<4)
+#define FLAG_M (FORCED_FLAG<<5)
+#define FLAG_D (FORCED_FLAG<<6)
 #endif
 
 #ifdef FOR_demo_scankey
@@ -4036,18 +4269,19 @@
 #ifndef TT
 #define TT this.du
 #endif
-#define FLAG_x (1<<0)
-#define FLAG_s (1<<1)
-#define FLAG_L (1<<2)
-#define FLAG_K (1<<3)
-#define FLAG_k (1<<4)
-#define FLAG_H (1<<5)
-#define FLAG_a (1<<6)
-#define FLAG_c (1<<7)
-#define FLAG_l (1<<8)
-#define FLAG_m (1<<9)
-#define FLAG_h (1<<10)
-#define FLAG_d (1<<11)
+#define FLAG_b (1<<0)
+#define FLAG_x (1<<1)
+#define FLAG_s (1<<2)
+#define FLAG_L (1<<3)
+#define FLAG_K (1<<4)
+#define FLAG_k (1<<5)
+#define FLAG_H (1<<6)
+#define FLAG_a (1<<7)
+#define FLAG_c (1<<8)
+#define FLAG_l (1<<9)
+#define FLAG_m (1<<10)
+#define FLAG_h (1<<11)
+#define FLAG_d (1<<12)
 #endif
 
 #ifdef FOR_dumpleases
@@ -4082,8 +4316,23 @@
 #define TT this.env
 #endif
 #define FLAG_u (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_0 (1<<2)
+#define FLAG_0 (1<<1)
+#define FLAG_i (1<<2)
+#endif
+
+#ifdef FOR_eval
+#ifndef TT
+#define TT this.eval
+#endif
+#endif
+
+#ifdef FOR_exec
+#ifndef TT
+#define TT this.exec
+#endif
+#define FLAG_a (FORCED_FLAG<<0)
+#define FLAG_l (FORCED_FLAG<<1)
+#define FLAG_c (FORCED_FLAG<<2)
 #endif
 
 #ifdef FOR_exit
@@ -4099,6 +4348,14 @@
 #define FLAG_t (FORCED_FLAG<<0)
 #endif
 
+#ifdef FOR_export
+#ifndef TT
+#define TT this.export
+#endif
+#define FLAG_p (FORCED_FLAG<<0)
+#define FLAG_n (FORCED_FLAG<<1)
+#endif
+
 #ifdef FOR_expr
 #ifndef TT
 #define TT this.expr
@@ -4566,12 +4823,13 @@
 #define FLAG_g (FORCED_FLAG<<0)
 #define FLAG_o (FORCED_FLAG<<1)
 #define FLAG_m (FORCED_FLAG<<2)
-#define FLAG_v (FORCED_FLAG<<3)
-#define FLAG_s (FORCED_FLAG<<4)
-#define FLAG_p (FORCED_FLAG<<5)
-#define FLAG_D (FORCED_FLAG<<6)
-#define FLAG_d (FORCED_FLAG<<7)
-#define FLAG_c (FORCED_FLAG<<8)
+#define FLAG_t (FORCED_FLAG<<3)
+#define FLAG_v (FORCED_FLAG<<4)
+#define FLAG_s (FORCED_FLAG<<5)
+#define FLAG_p (FORCED_FLAG<<6)
+#define FLAG_D (FORCED_FLAG<<7)
+#define FLAG_d (FORCED_FLAG<<8)
+#define FLAG_c (FORCED_FLAG<<9)
 #endif
 
 #ifdef FOR_ionice
@@ -4645,6 +4903,17 @@
 #define FLAG_a (FORCED_FLAG<<9)
 #endif
 
+#ifdef FOR_jobs
+#ifndef TT
+#define TT this.jobs
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_r (FORCED_FLAG<<1)
+#define FLAG_p (FORCED_FLAG<<2)
+#define FLAG_n (FORCED_FLAG<<3)
+#define FLAG_l (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_kill
 #ifndef TT
 #define TT this.kill
@@ -4727,9 +4996,9 @@
 #ifndef TT
 #define TT this.logger
 #endif
-#define FLAG_p (FORCED_FLAG<<0)
-#define FLAG_t (FORCED_FLAG<<1)
-#define FLAG_s (FORCED_FLAG<<2)
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_p (FORCED_FLAG<<1)
+#define FLAG_t (FORCED_FLAG<<2)
 #endif
 
 #ifdef FOR_login
@@ -5037,11 +5306,12 @@
 #define TT this.mv
 #endif
 #define FLAG_T (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_f (1<<2)
-#define FLAG_F (1<<3)
-#define FLAG_n (1<<4)
-#define FLAG_v (1<<5)
+#define FLAG_t (1<<1)
+#define FLAG_i (1<<2)
+#define FLAG_f (1<<3)
+#define FLAG_F (1<<4)
+#define FLAG_n (1<<5)
+#define FLAG_v (1<<6)
 #endif
 
 #ifdef FOR_nbd_client
@@ -5068,7 +5338,8 @@
 #define FLAG_w (FORCED_FLAG<<9)
 #define FLAG_L (FORCED_FLAG<<10)
 #define FLAG_l (FORCED_FLAG<<11)
-#define FLAG_t (FORCED_FLAG<<12)
+#define FLAG_E (FORCED_FLAG<<12)
+#define FLAG_t (FORCED_FLAG<<13)
 #endif
 
 #ifdef FOR_netstat
@@ -5099,13 +5370,13 @@
 #ifndef TT
 #define TT this.nl
 #endif
-#define FLAG_s (FORCED_FLAG<<0)
-#define FLAG_n (FORCED_FLAG<<1)
-#define FLAG_b (FORCED_FLAG<<2)
-#define FLAG_E (FORCED_FLAG<<3)
-#define FLAG_w (FORCED_FLAG<<4)
-#define FLAG_l (FORCED_FLAG<<5)
-#define FLAG_v (FORCED_FLAG<<6)
+#define FLAG_s (1<<0)
+#define FLAG_n (1<<1)
+#define FLAG_b (1<<2)
+#define FLAG_E (1<<3)
+#define FLAG_w (1<<4)
+#define FLAG_l (1<<5)
+#define FLAG_v (1<<6)
 #endif
 
 #ifdef FOR_nohup
@@ -5303,6 +5574,7 @@
 #define TT this.printenv
 #endif
 #define FLAG_0 (FORCED_FLAG<<0)
+#define FLAG_null (FORCED_FLAG<<1)
 #endif
 
 #ifdef FOR_printf
@@ -5354,6 +5626,24 @@
 #define FLAG_a (FORCED_FLAG<<0)
 #endif
 
+#ifdef FOR_pwgen
+#ifndef TT
+#define TT this.pwgen
+#endif
+#define FLAG_0 (FORCED_FLAG<<0)
+#define FLAG_A (FORCED_FLAG<<1)
+#define FLAG_v (FORCED_FLAG<<2)
+#define FLAG_1 (FORCED_FLAG<<3)
+#define FLAG_C (FORCED_FLAG<<4)
+#define FLAG_h (FORCED_FLAG<<5)
+#define FLAG_B (FORCED_FLAG<<6)
+#define FLAG_s (FORCED_FLAG<<7)
+#define FLAG_y (FORCED_FLAG<<8)
+#define FLAG_n (FORCED_FLAG<<9)
+#define FLAG_c (FORCED_FLAG<<10)
+#define FLAG_r (FORCED_FLAG<<11)
+#endif
+
 #ifdef FOR_readahead
 #ifndef TT
 #define TT this.readahead
@@ -5372,9 +5662,10 @@
 #define FLAG_n (FORCED_FLAG<<5)
 #define FLAG_l (FORCED_FLAG<<6)
 #define FLAG_h (FORCED_FLAG<<7)
-#define FLAG_d (FORCED_FLAG<<8)
-#define FLAG_a (FORCED_FLAG<<9)
-#define FLAG_dyn_syms (FORCED_FLAG<<10)
+#define FLAG_e (FORCED_FLAG<<8)
+#define FLAG_d (FORCED_FLAG<<9)
+#define FLAG_a (FORCED_FLAG<<10)
+#define FLAG_dyn_syms (FORCED_FLAG<<11)
 #endif
 
 #ifdef FOR_readlink
@@ -5478,6 +5769,22 @@
 #define FLAG_n (FORCED_FLAG<<2)
 #endif
 
+#ifdef FOR_rtcwake
+#ifndef TT
+#define TT this.rtcwake
+#endif
+#define FLAG_v (FORCED_FLAG<<0)
+#define FLAG_u (FORCED_FLAG<<1)
+#define FLAG_t (FORCED_FLAG<<2)
+#define FLAG_s (FORCED_FLAG<<3)
+#define FLAG_m (FORCED_FLAG<<4)
+#define FLAG_l (FORCED_FLAG<<5)
+#define FLAG_d (FORCED_FLAG<<6)
+#define FLAG_a (FORCED_FLAG<<7)
+#define FLAG_auto (FORCED_FLAG<<8)
+#define FLAG_list_modes (FORCED_FLAG<<9)
+#endif
+
 #ifdef FOR_runcon
 #ifndef TT
 #define TT this.runcon
@@ -5488,15 +5795,16 @@
 #ifndef TT
 #define TT this.sed
 #endif
-#define FLAG_z (1<<0)
-#define FLAG_r (1<<1)
-#define FLAG_E (1<<2)
-#define FLAG_n (1<<3)
-#define FLAG_i (1<<4)
-#define FLAG_f (1<<5)
-#define FLAG_e (1<<6)
-#define FLAG_version (1<<7)
-#define FLAG_help (1<<8)
+#define FLAG_s (1<<0)
+#define FLAG_z (1<<1)
+#define FLAG_r (1<<2)
+#define FLAG_E (1<<3)
+#define FLAG_n (1<<4)
+#define FLAG_i (1<<5)
+#define FLAG_f (1<<6)
+#define FLAG_e (1<<7)
+#define FLAG_version (1<<8)
+#define FLAG_help (1<<9)
 #endif
 
 #ifdef FOR_sendevent
@@ -5514,6 +5822,12 @@
 #define FLAG_f (1<<2)
 #endif
 
+#ifdef FOR_set
+#ifndef TT
+#define TT this.set
+#endif
+#endif
+
 #ifdef FOR_setenforce
 #ifndef TT
 #define TT this.setenforce
@@ -5560,6 +5874,21 @@
 #define FLAG_b (1<<2)
 #endif
 
+#ifdef FOR_sha3sum
+#ifndef TT
+#define TT this.sha3sum
+#endif
+#define FLAG_a (FORCED_FLAG<<0)
+#define FLAG_S (FORCED_FLAG<<1)
+#define FLAG_b (FORCED_FLAG<<2)
+#endif
+
+#ifdef FOR_shift
+#ifndef TT
+#define TT this.shift
+#endif
+#endif
+
 #ifdef FOR_shred
 #ifndef TT
 #define TT this.shred
@@ -5645,6 +5974,12 @@
 #define FLAG_g (1<<19)
 #endif
 
+#ifdef FOR_source
+#ifndef TT
+#define TT this.source
+#endif
+#endif
+
 #ifdef FOR_split
 #ifndef TT
 #define TT this.split
@@ -5791,31 +6126,33 @@
 #define FLAG_T (1<<3)
 #define FLAG_X (1<<4)
 #define FLAG_m (1<<5)
-#define FLAG_O (1<<6)
-#define FLAG_S (1<<7)
-#define FLAG_z (1<<8)
-#define FLAG_j (1<<9)
-#define FLAG_J (1<<10)
-#define FLAG_v (1<<11)
-#define FLAG_t (1<<12)
-#define FLAG_x (1<<13)
-#define FLAG_h (1<<14)
-#define FLAG_c (1<<15)
-#define FLAG_k (1<<16)
-#define FLAG_p (1<<17)
-#define FLAG_o (1<<18)
-#define FLAG_to_command (1<<19)
-#define FLAG_owner (1<<20)
-#define FLAG_group (1<<21)
-#define FLAG_mtime (1<<22)
-#define FLAG_mode (1<<23)
-#define FLAG_exclude (1<<24)
-#define FLAG_overwrite (1<<25)
-#define FLAG_no_same_permissions (1<<26)
-#define FLAG_numeric_owner (1<<27)
-#define FLAG_no_recursion (1<<28)
-#define FLAG_full_time (1<<29)
-#define FLAG_restrict (1<<30)
+#define FLAG_P (1<<6)
+#define FLAG_O (1<<7)
+#define FLAG_S (1<<8)
+#define FLAG_z (1<<9)
+#define FLAG_j (1<<10)
+#define FLAG_J (1<<11)
+#define FLAG_I (1<<12)
+#define FLAG_v (1<<13)
+#define FLAG_t (1<<14)
+#define FLAG_x (1<<15)
+#define FLAG_h (1<<16)
+#define FLAG_c (1<<17)
+#define FLAG_k (1<<18)
+#define FLAG_p (1<<19)
+#define FLAG_o (1<<20)
+#define FLAG_to_command (1<<21)
+#define FLAG_owner (1<<22)
+#define FLAG_group (1<<23)
+#define FLAG_mtime (1<<24)
+#define FLAG_mode (1<<25)
+#define FLAG_exclude (1<<26)
+#define FLAG_overwrite (1<<27)
+#define FLAG_no_same_permissions (1<<28)
+#define FLAG_numeric_owner (1<<29)
+#define FLAG_no_recursion (1<<30)
+#define FLAG_full_time (1LL<<31)
+#define FLAG_restrict (1LL<<32)
 #endif
 
 #ifdef FOR_taskset
@@ -6075,6 +6412,12 @@
 #define FLAG_o (1<<6)
 #endif
 
+#ifdef FOR_unicode
+#ifndef TT
+#define TT this.unicode
+#endif
+#endif
+
 #ifdef FOR_uniq
 #ifndef TT
 #define TT this.uniq
@@ -6101,6 +6444,15 @@
 #endif
 #endif
 
+#ifdef FOR_unset
+#ifndef TT
+#define TT this.unset
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#define FLAG_v (FORCED_FLAG<<1)
+#define FLAG_f (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_unshare
 #ifndef TT
 #define TT this.unshare
@@ -6197,6 +6549,13 @@
 #endif
 #endif
 
+#ifdef FOR_wait
+#ifndef TT
+#define TT this.wait
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_watch
 #ifndef TT
 #define TT this.watch
@@ -6208,6 +6567,15 @@
 #define FLAG_n (FORCED_FLAG<<4)
 #endif
 
+#ifdef FOR_watchdog
+#ifndef TT
+#define TT this.watchdog
+#endif
+#define FLAG_T (FORCED_FLAG<<0)
+#define FLAG_t (FORCED_FLAG<<1)
+#define FLAG_F (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_wc
 #ifndef TT
 #define TT this.wc
diff --git a/android/linux/generated/globals.h b/android/linux/generated/globals.h
index 4a201bf..7d5e3be 100644
--- a/android/linux/generated/globals.h
+++ b/android/linux/generated/globals.h
@@ -7,7 +7,7 @@
 // toys/example/demo_number.c
 
 struct demo_number_data {
-  long D;
+  long M, D;
 };
 
 // toys/example/hello.c
@@ -73,10 +73,10 @@
 struct md5sum_data {
   int sawline;
 
+  unsigned *md5table;
   // Crypto variables blanked after summing
-  unsigned state[5];
-  unsigned oldstate[5];
-  uint64_t count;
+  unsigned state[5], oldstate[5];
+  unsigned long long count;
   union {
     char c[64];
     unsigned i[16];
@@ -124,7 +124,7 @@
 struct seq_data {
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 };
 
 // toys/lsb/su.c
@@ -159,10 +159,10 @@
 // toys/net/microcom.c
 
 struct microcom_data {
-  char *s;
+  long s;
 
-  int fd;
-  struct termios original_stdin_state, original_fd_state;
+  int fd, stok;
+  struct termios old_stdin, old_fd;
 };
 
 // toys/net/netcat.c
@@ -177,7 +177,7 @@
 struct netstat_data {
   struct num_cache *inodes;
   int wpad;
-};;
+};
 
 // toys/net/ping.c
 
@@ -214,8 +214,15 @@
 
 struct base64_data {
   long w;
-
   unsigned total;
+  unsigned n;  // number of bits used in encoding. 5 for base32, 6 for base64
+  unsigned align;  // number of bits to align to
+};
+
+// toys/other/blkdiscard.c
+
+struct blkdiscard_data {
+  long o, l;
 };
 
 // toys/other/blkid.c
@@ -270,15 +277,17 @@
   char *data;
   long long len, base;
   int numlen, undo, undolen;
-  unsigned height;
+  unsigned rows, cols;
+  long long pos;
+  char keybuf[16];
+  char input[80];
+  char *search;
 };
 
 // toys/other/hwclock.c
 
 struct hwclock_data {
   char *f;
-
-  int utc;
 };
 
 // toys/other/ionice.c
@@ -375,12 +384,32 @@
   char *c;
 };
 
+// toys/other/pwgen.c
+
+struct pwgen_data {
+  char *r;
+};
+
+// toys/other/rtcwake.c
+
+struct rtcwake_data {
+  long t, s;
+  char *m, *d;
+};
+
 // toys/other/setfattr.c
 
 struct setfattr_data {
   char *x, *v, *n;
 };
 
+// toys/other/sha3sum.c
+
+struct sha3sum_data {
+  long a;
+  unsigned long long rc[24];
+};
+
 // toys/other/shred.c
 
 struct shred_data {
@@ -448,6 +477,14 @@
   pid_t pid, oldpid;
 };
 
+// toys/other/watchdog.c
+
+struct watchdog_data {
+  long T, t;
+
+  int fd;
+};
+
 // toys/other/xxd.c
 
 struct xxd_data {
@@ -496,11 +533,10 @@
 
 struct bootchartd_data {
   char buf[32];
-  long smpl_period_usec;
+  long msec;
   int proc_accounting;
-  int is_login;
 
-  pid_t cur_pid;
+  pid_t pid;
 };
 
 // toys/pending/brctl.c
@@ -509,6 +545,12 @@
     int sockfd;
 };
 
+// toys/pending/chsh.c
+
+struct chsh_data {
+  char *s;
+};
+
 // toys/pending/crond.c
 
 struct crond_data {
@@ -542,7 +584,7 @@
     unsigned long long offset;
   } in, out;
   unsigned conv, iflag, oflag;
-};;
+};
 
 // toys/pending/dhcp.c
 
@@ -578,7 +620,7 @@
 struct dhcpd_data {
     char *iface;
     long port;
-};;
+};
 
 // toys/pending/diff.c
 
@@ -653,17 +695,12 @@
 // toys/pending/getty.c
 
 struct getty_data {
-  char *issue_str;
-  char *login_str;
-  char *init_str;
-  char *host_str; 
-  long timeout;
-  
-  char *tty_name;  
-  int  speeds[20];
-  int  sc;              
+  char *f, *l, *I, *H;
+  long t;
+
+  char *tty_name, buff[128];
+  int speeds[20], sc;
   struct termios termios;
-  char buff[128];
 };
 
 // toys/pending/groupadd.c
@@ -770,11 +807,9 @@
 struct modprobe_data {
   struct arg_list *dirs;
 
-  struct arg_list *probes;
-  struct arg_list *dbase[256];
+  struct arg_list *probes, *dbase[256];
   char *cmdopts;
-  int nudeps;
-  uint8_t symreq;
+  int nudeps, symreq;
 };
 
 // toys/pending/more.c
@@ -787,7 +822,7 @@
 // toys/pending/openvt.c
 
 struct openvt_data {
-  unsigned long vt_num;
+  long c;
 };
 
 // toys/pending/readelf.c
@@ -796,53 +831,96 @@
   char *x, *p;
 
   char *elf, *shstrtab, *f;
-  long long shoff, phoff, size;
-  int bits, shnum, shentsize, phentsize;
-  int64_t (*elf_int)(void *ptr, unsigned size);
+  unsigned long long shoff, phoff, size, shstrtabsz;
+  int bits, endian, shnum, shentsize, phentsize;
 };
 
 // toys/pending/route.c
 
 struct route_data {
-  char *family;
+  char *A;
 };
 
 // toys/pending/sh.c
 
 struct sh_data {
-  char *c;
+  union {
+    struct {
+      char *c;
+    } sh;
+    struct {
+      char *a;
+    } exec;
+  };
 
-  long lineno;
-  char **locals, *subshell_env;
-  struct double_list functions;
-  unsigned options, jobcnt, loc_ro, loc_magic;
-  int hfd;  // next high filehandle (>= 10)
+  // keep SECONDS here: used to work around compiler limitation in run_command()
+  long long SECONDS;
+  char *isexec, *wcpat;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
 
-  // Running jobs.
-  struct sh_job {
-    struct sh_job *next, *prev;
-    unsigned jobno;
+  // Callable function array
+  struct sh_function {
+    char *name;
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
+      struct sh_pipeline *next, *prev, *end;
+      int count, here, type, lineno;
+      struct sh_arg {
+        char **v;
+        int c;
+      } arg[1];
+    } *pipeline;
+    unsigned long refcount;
+  } **functions;
+  long funcslen;
 
-    // Every pipeline has at least one set of arguments or it's Not A Thing
-    struct sh_arg {
-      char **v;
-      int c;
-    } pipeline;
+  // runtime function call stack
+  struct sh_fcall {
+    struct sh_fcall *next, *prev;
 
-    // null terminated array of running processes in pipeline
-    struct sh_process {
-      struct sh_process *next, *prev;
-      struct arg_list *delete;   // expanded strings
-      int *urd, envlen, pid, exit;  // undo redirects, child PID, exit status
-      struct sh_arg arg;
-    } *procs, *proc;
-  } *jobs, *job;
+    // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+    struct sh_vars {
+      long flags;
+      char *str;
+    } *vars;
+    long varslen, shift;
+
+    struct sh_function *func; // TODO wire this up
+    struct sh_pipeline *pl;
+    char *ifs;
+    struct sh_arg arg;
+    struct arg_list *delete;
+
+    // Runtime stack of nested if/else/fi and for/do/done contexts.
+    struct sh_blockstack {
+      struct sh_blockstack *next;
+      struct sh_pipeline *start, *middle;
+      struct sh_process *pp;       // list of processes piping in to us
+      int run, loop, *urd, pout, pipe;
+      struct sh_arg farg;          // for/select arg stack, case wildcard deck
+      struct arg_list *fdelete;    // farg's cleanup list
+      char *fvar;                  // for/select's iteration variable name
+    } *blk;
+  } *ff;
+
+// TODO ctrl-Z suspend should stop script
+  struct sh_process {
+    struct sh_process *next, *prev; // | && ||
+    struct arg_list *delete;   // expanded strings
+    // undo redirects, a=b at start, child PID, exit status, has !, job #
+    int *urd, envlen, pid, exit, not, job, dash;
+    long long when; // when job backgrounded/suspended
+    struct sh_arg *raw, arg;
+  } *pp; // currently running process
+
+  // job list, command line for $*, scratch space for do_wildcard_files()
+  struct sh_arg jobs, *wcdeck;
 };
 
 // toys/pending/stty.c
 
 struct stty_data {
-  char *device;
+  char *F;
 
   int fd, col;
   unsigned output_cols;
@@ -890,20 +968,13 @@
 // toys/pending/telnet.c
 
 struct telnet_data {
-  int port;
-  int sfd;
-  char buff[128];
-  int pbuff;
-  char iac[256];
-  int piac;
-  char *ttype;
-  struct termios def_term;
+  int sock;
+  char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
+  struct termios old_term;
   struct termios raw_term;
-  uint8_t term_ok;
-  uint8_t term_mode;
-  uint8_t flags;
-  unsigned win_width;
-  unsigned win_height;
+  uint8_t mode;
+  int echo, sga;
+  int state, request;
 };
 
 // toys/pending/telnetd.c
@@ -984,37 +1055,28 @@
 // toys/pending/vi.c
 
 struct vi_data {
-    char *s;
-    int cur_col;
-    int cur_row;
-    int scr_row;
-    int drawn_row;
-    int drawn_col;
-    unsigned screen_height;
-    unsigned screen_width;
-    int vi_mode;
-    int count0;
-    int count1;
-    int vi_mov_flag;
-    int modified;
-    char vi_reg;
-    char *last_search;
-    int tabstop;
-    int list;
-    struct str_line {
-      int alloc;
-      int len;
-      char *data;
-    } *il;
-    size_t screen; //offset in slices must be higher than cursor
-    size_t cursor; //offset in slices
-    //yank buffer
-    struct yank_buf {
-      char reg;
-      int alloc;
-      char* data;
-    } yank;
+  char *s;
+  int vi_mode, tabstop, list;
+  int cur_col, cur_row, scr_row;
+  int drawn_row, drawn_col;
+  int count0, count1, vi_mov_flag;
+  unsigned screen_height, screen_width;
+  char vi_reg, *last_search;
+  struct str_line {
+    int alloc;
+    int len;
+    char *data;
+  } *il;
+  size_t screen, cursor; //offsets
+  //yank buffer
+  struct yank_buf {
+    char reg;
+    int alloc;
+    char* data;
+  } yank;
 
+  int modified;
+  size_t filesize;
 // mem_block contains RO data that is either original file as mmap
 // or heap allocated inserted data
 //
@@ -1048,10 +1110,6 @@
       const char *data;
     } *node;
   } *slices;
-
-  size_t filesize;
-  int fd; //file_handle
-
 };
 
 // toys/pending/wget.c
@@ -1106,11 +1164,11 @@
   union {
     // install's options
     struct {
-      char *g, *o, *m;
+      char *g, *o, *m, *t;
     } i;
     // cp's options
     struct {
-      char *preserve;
+      char *t, *preserve;
     } c;
   };
 
@@ -1125,7 +1183,7 @@
 // toys/posix/cpio.c
 
 struct cpio_data {
-  char *F, *p, *H;
+  char *F, *H;
 };
 
 // toys/posix/cut.c
@@ -1141,7 +1199,7 @@
 // toys/posix/date.c
 
 struct date_data {
-  char *r, *D, *d;
+  char *r, *I, *D, *d;
 
   unsigned nano;
 };
@@ -1151,9 +1209,7 @@
 struct df_data {
   struct arg_list *t;
 
-  long units;
-  int column_widths[5];
-  int header_shown;
+  int units, width[6];
 };
 
 // toys/posix/du.c
@@ -1170,7 +1226,7 @@
 
 struct env_data {
   struct arg_list *u;
-};;
+};
 
 // toys/posix/expand.c
 
@@ -1360,7 +1416,7 @@
   dev_t tty;
   void *fields, *kfields;
   long long ticks, bits, time;
-  int kcount, forcek, sortpos;
+  int kcount, forcek, sortpos, pidlen;
   int (*match_process)(long long *slot);
   void (*show_process)(void *tb);
 };
@@ -1429,7 +1485,7 @@
 struct tar_data {
   char *f, *C;
   struct arg_list *T, *X;
-  char *to_command, *owner, *group, *mtime, *mode;
+  char *I, *to_command, *owner, *group, *mtime, *mode;
   struct arg_list *exclude;
 
   struct double_list *incl, *excl, *seen;
@@ -1462,6 +1518,7 @@
 
 struct tee_data {
   void *outputs;
+  int out;
 };
 
 // toys/posix/touch.c
@@ -1502,7 +1559,7 @@
   long s, n, P;
   char *E;
 
-  long entries, bytes;
+  long entries, bytes, np;
   char delim;
   FILE *tty;
 };
@@ -1535,6 +1592,7 @@
 	struct tunctl_data tunctl;
 	struct acpi_data acpi;
 	struct base64_data base64;
+	struct blkdiscard_data blkdiscard;
 	struct blkid_data blkid;
 	struct blockdev_data blockdev;
 	struct chrt_data chrt;
@@ -1556,7 +1614,10 @@
 	struct modinfo_data modinfo;
 	struct nsenter_data nsenter;
 	struct oneit_data oneit;
+	struct pwgen_data pwgen;
+	struct rtcwake_data rtcwake;
 	struct setfattr_data setfattr;
+	struct sha3sum_data sha3sum;
 	struct shred_data shred;
 	struct stat_data stat;
 	struct swapon_data swapon;
@@ -1565,12 +1626,14 @@
 	struct timeout_data timeout;
 	struct truncate_data truncate;
 	struct watch_data watch;
+	struct watchdog_data watchdog;
 	struct xxd_data xxd;
 	struct arp_data arp;
 	struct arping_data arping;
 	struct bc_data bc;
 	struct bootchartd_data bootchartd;
 	struct brctl_data brctl;
+	struct chsh_data chsh;
 	struct crond_data crond;
 	struct crontab_data crontab;
 	struct dd_data dd;
diff --git a/android/linux/generated/help.h b/android/linux/generated/help.h
index 6621ed1..86fbdbf 100644
--- a/android/linux/generated/help.h
+++ b/android/linux/generated/help.h
@@ -1,4 +1,4 @@
-#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a toolchain scripts/mcm-buildall.sh patches musl to fix this."
+#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a scripts/mcm-buildall.sh toolchain patches musl to fix this."
 
 #define HELP_toybox_uid_usr "When commands like useradd/groupadd allocate user IDs, start here."
 
@@ -12,8 +12,6 @@
 
 #define HELP_toybox_free "When a program exits, the operating system will clean up after it\n(free memory, close files, etc). To save size, toybox usually relies\non this behavior. If you're running toybox under a debugger or\nwithout a real OS (ala newlib+libgloss), enable this to make toybox\nclean up after itself."
 
-#define HELP_toybox_i18n "Support for UTF-8 character sets, and some locale support."
-
 #define HELP_toybox_help_dashdash "Support --help argument in all commands, even ones with a NULL\noptstring. (Use TOYFLAG_NOHELP to disable.) Produces the same output\nas \"help command\". --version shows toybox version."
 
 #define HELP_toybox_help "Include help text for each command."
@@ -32,7 +30,7 @@
 
 #define HELP_toybox_suid "Support for the Set User ID bit, to install toybox suid root and drop\npermissions for commands which do not require root access. To use\nthis change ownership of the file to the root user and set the suid\nbit in the file permissions:\n\nchown root:root toybox; chmod +s toybox\n\nprompt \"Security Blanket\"\ndefault TOYBOX_LSM_NONE\nhelp\nSelect a Linux Security Module to complicate your system\nuntil you can't find holes in it."
 
-#define HELP_toybox "usage: toybox [--long | --help | --version | [command] [arguments...]]\n\nWith no arguments, shows available commands. First argument is\nname of a command to run, followed by any arguments to that command.\n\n--long	Show path to each command\n\nTo install command symlinks with paths, try:\n  for i in $(/bin/toybox --long); do ln -s /bin/toybox $i; done\nor all in one directory:\n  for i in $(./toybox); do ln -s toybox $i; done; PATH=$PWD:$PATH\n\nMost toybox commands also understand the following arguments:\n\n--help		Show command help (only)\n--version	Show toybox version (only)\n\nThe filename \"-\" means stdin/stdout, and \"--\" stops argument parsing.\n\nNumerical arguments accept a single letter suffix for\nkilo, mega, giga, tera, peta, and exabytes, plus an additional\n\"d\" to indicate decimal 1000's instead of 1024.\n\nDurations can be decimal fractions and accept minute (\"m\"), hour (\"h\"),\nor day (\"d\") suffixes (so 0.1m = 6s)."
+#define HELP_toybox "usage: toybox [--long | --help | --version | [COMMAND] [ARGUMENTS...]]\n\nWith no arguments, \"toybox\" shows available COMMAND names. Add --long\nto include suggested install path for each command, see\nhttps://landley.net/toybox/faq.html#install for details.\n\nFirst argument is name of a COMMAND to run, followed by any ARGUMENTS\nto that command. Most toybox commands also understand:\n\n--help		Show command help (only)\n--version	Show toybox version (only)\n\nThe filename \"-\" means stdin/stdout, and \"--\" stops argument parsing.\n\nNumerical arguments accept a single letter suffix for\nkilo, mega, giga, tera, peta, and exabytes, plus an additional\n\"d\" to indicate decimal 1000's instead of 1024.\n\nDurations can be decimal fractions and accept minute (\"m\"), hour (\"h\"),\nor day (\"d\") suffixes (so 0.1m = 6s)."
 
 #define HELP_setenforce "usage: setenforce [enforcing|permissive|1|0]\n\nSets whether SELinux is enforcing (1) or permissive (0)."
 
@@ -62,7 +60,7 @@
 
 #define HELP_demo_scankey "usage: demo_scankey\n\nMove a letter around the screen. Hit ESC to exit."
 
-#define HELP_demo_number "usage: demo_number [-hsbi] NUMBER...\n\n-b	Use \"B\" for single byte units (HR_B)\n-d	Decimal units\n-h	Human readable\n-s	Space between number and units (HR_SPACE)"
+#define HELP_demo_number "usage: demo_number [-hsbi] [-D LEN] NUMBER...\n\n-D	output field is LEN chars\n-M	input units (index into bkmgtpe)\n-c	Comma comma down do be do down down\n-b	Use \"B\" for single byte units (HR_B)\n-d	Decimal units\n-h	Human readable\n-s	Space between number and units (HR_SPACE)"
 
 #define HELP_demo_many_options "usage: demo_many_options -[a-zA-Z]\n\nPrint the optflags value of the command arguments, in hex."
 
@@ -70,7 +68,7 @@
 
 #define HELP_sync "usage: sync\n\nWrite pending cached data to disk (synchronize), blocking until done."
 
-#define HELP_su "usage: su [-lp] [-u UID] [-g GID,...] [-s SHELL] [-c CMD] [USER [COMMAND...]]\n\nSwitch user, prompting for password of new user when not run as root.\n\nWith one argument, switch to USER and run user's shell from /etc/passwd.\nWith no arguments, USER is root. If COMMAND line provided after USER,\nexec() it as new USER (bypasing shell). If -u or -g specified, first\nargument (if any) isn't USER (it's COMMAND).\n\nfirst argument is USER name to switch to (which must exist).\nNon-root users are prompted for new user's password.\n\n-s	Shell to use (default is user's shell from /etc/passwd)\n-c	Command line to pass to -s shell (ala sh -c \"CMD\")\n-l	Reset environment as if new login.\n-u	Switch to UID instead of USER\n-g	Switch to GID (only root allowed, can be comma separated list)\n-p	Preserve environment (except for $PATH and $IFS)"
+#define HELP_su "usage: su [-lp] [-u UID] [-g GID,...] [-s SHELL] [-c CMD] [USER [COMMAND...]]\n\nSwitch user, prompting for password of new user when not run as root.\n\nWith one argument, switch to USER and run user's shell from /etc/passwd.\nWith no arguments, USER is root. If COMMAND line provided after USER,\nexec() it as new USER (bypassing shell). If -u or -g specified, first\nargument (if any) isn't USER (it's COMMAND).\n\nfirst argument is USER name to switch to (which must exist).\nNon-root users are prompted for new user's password.\n\n-s	Shell to use (default is user's shell from /etc/passwd)\n-c	Command line to pass to -s shell (ala sh -c \"CMD\")\n-l	Reset environment as if new login.\n-u	Switch to UID instead of USER\n-g	Switch to GID (only root allowed, can be comma separated list)\n-p	Preserve environment (except for $PATH and $IFS)"
 
 #define HELP_seq "usage: seq [-w|-f fmt_str] [-s sep_str] [first] [increment] last\n\nCount from first to last, by increment. Omitted arguments default\nto 1. Two arguments are used as first and last. Arguments can be\nnegative or floating point.\n\n-f	Use fmt_str as a printf-style floating point format string\n-s	Use sep_str as separator, default is a newline character\n-w	Pad to equal width with leading zeroes"
 
@@ -116,7 +114,7 @@
 
 #define HELP_tunctl "usage: tunctl [-dtT] [-u USER] NAME\n\nCreate and delete tun/tap virtual ethernet devices.\n\n-T	Use tap (ethernet frames) instead of tun (ip packets)\n-d	Delete tun/tap device\n-t	Create tun/tap device\n-u	Set owner (user who can read/write device without root access)"
 
-#define HELP_sntp "usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]\n\nSimple Network Time Protocol client. Query SERVER and display time.\n\n-p	Use PORT (default 123)\n-s	Set system clock suddenly\n-a	Adjust system clock gradually\n-S	Serve time instead of querying (bind to SERVER address if specified)\n-m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)\n-M	Multicast server on ADDRESS (deault 224.0.0.1)\n-t	TTL (multicast only, default 1)\n-d	Daemonize (run in background re-querying )\n-D	Daemonize but stay in foreground: re-query time every 1000 seconds\n-r	Retry shift (every 1<<SHIFT seconds)\n-q	Quiet (don't display time)"
+#define HELP_sntp "usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]\n\nSimple Network Time Protocol client. Query SERVER and display time.\n\n-p	Use PORT (default 123)\n-s	Set system clock suddenly\n-a	Adjust system clock gradually\n-S	Serve time instead of querying (bind to SERVER address if specified)\n-m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)\n-M	Multicast server on ADDRESS (default 224.0.0.1)\n-t	TTL (multicast only, default 1)\n-d	Daemonize (run in background re-querying )\n-D	Daemonize but stay in foreground: re-query time every 1000 seconds\n-r	Retry shift (every 1<<SHIFT seconds)\n-q	Quiet (don't display time)"
 
 #define HELP_rfkill "usage: rfkill COMMAND [DEVICE]\n\nEnable/disable wireless devices.\n\nCommands:\nlist [DEVICE]   List current state\nblock DEVICE    Disable device\nunblock DEVICE  Enable device\n\nDEVICE is an index number, or one of:\nall, wlan(wifi), bluetooth, uwb(ultrawideband), wimax, wwan, gps, fm."
 
@@ -124,11 +122,11 @@
 
 #define HELP_netstat "usage: netstat [-pWrxwutneal]\n\nDisplay networking information. Default is netstat -tuwx\n\n-r	Routing table\n-a	All sockets (not just connected)\n-l	Listening server sockets\n-t	TCP sockets\n-u	UDP sockets\n-w	Raw sockets\n-x	Unix sockets\n-e	Extended info\n-n	Don't resolve names\n-W	Wide display\n-p	Show PID/program name of sockets"
 
-#define HELP_netcat_listen "usage: netcat [-t] [-lL COMMAND...]\n\n-l	Listen for one incoming connection\n-L	Listen for multiple incoming connections (server mode)\n-t	Allocate tty (must come before -l or -L)\n\nThe command line after -l or -L is executed (as a child process) to handle\neach incoming connection. If blank -l waits for a connection and forwards\nit to stdin/stdout. If no -p specified, -l prints port it bound to and\nbackgrounds itself (returning immediately).\n\nFor a quick-and-dirty server, try something like:\nnetcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l"
+#define HELP_netcat_listen "usage: netcat [-tElL]\n\n-l	Listen for one incoming connection, then exit\n-L	Listen and background each incoming connection (server mode)\n-t	Allocate tty\n-E	Forward stderr\n\nWhen listening the COMMAND line is executed as a child process to handle\nan incoming connection. With no COMMAND -l forwards the connection\nto stdin/stdout. If no -p specified, -l prints the port it bound to and\nbackgrounds itself (returning immediately).\n\nFor a quick-and-dirty server, try something like:\nnetcat -s 127.0.0.1 -p 1234 -tL sh -l"
 
-#define HELP_netcat "usage: netcat [-46U] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME}\n\nForward stdin/stdout to a file or network connection.\n\n-4	Force IPv4\n-6	Force IPv6\n-f	Use FILENAME (ala /dev/ttyS0) instead of network\n-p	Local port number\n-q	Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet\n-s	Local source address\n-u	Use UDP\n-U	Use a UNIX domain socket\n-w	SECONDS timeout to establish connection\n-W	SECONDS timeout for more data on an idle connection\n\nUse \"stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho\" with\nnetcat -f to connect to a serial port."
+#define HELP_netcat "usage: netcat [-46U] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME|COMMAND...}\n\nForward stdin/stdout to a file or network connection.\n\n-4	Force IPv4\n-6	Force IPv6\n-f	Use FILENAME (ala /dev/ttyS0) instead of network\n-p	Local port number\n-q	Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet\n-s	Local source address\n-u	Use UDP\n-U	Use a UNIX domain socket\n-w	SECONDS timeout to establish connection\n-W	SECONDS timeout for more data on an idle connection\n\nUse \"stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho\" with\nnetcat -f to connect to a serial port."
 
-#define HELP_microcom "usage: microcom [-s SPEED] [-X] DEVICE\n\nSimple serial console.\n\n-s	Set baud rate to SPEED\n-X	Ignore ^@ (send break) and ^] (exit)"
+#define HELP_microcom "usage: microcom [-s SPEED] [-X] DEVICE\n\nSimple serial console.\n\n-s	Set baud rate to SPEED (default 115200)\n-X	Ignore ^@ (send break) and ^] (exit)"
 
 #define HELP_ifconfig "usage: ifconfig [-aS] [INTERFACE [ACTION...]]\n\nDisplay or configure network interface.\n\nWith no arguments, display active interfaces. First argument is interface\nto operate on, one argument by itself displays that interface.\n\n-a	All interfaces displayed, not just active ones\n-S	Short view, one line per interface\n\nStandard ACTIONs to perform on an INTERFACE:\n\nADDR[/MASK]        - set IPv4 address (1.2.3.4/5) and activate interface\nadd|del ADDR[/LEN] - add/remove IPv6 address (1111::8888/128)\nup|down            - activate or deactivate interface\n\nAdvanced ACTIONs (default values usually suffice):\n\ndefault          - remove IPv4 address\nnetmask ADDR     - set IPv4 netmask via 255.255.255.0 instead of /24\ntxqueuelen LEN   - number of buffered packets before output blocks\nmtu LEN          - size of outgoing packets (Maximum Transmission Unit)\nbroadcast ADDR   - Set broadcast address\npointopoint ADDR - PPP and PPPOE use this instead of \"route add default gw\"\nhw TYPE ADDR     - set hardware (mac) address (type = ether|infiniband)\n\nFlags you can set on an interface (or -remove by prefixing with -):\n\narp       - don't use Address Resolution Protocol to map LAN routes\npromisc   - don't discard packets that aren't to this LAN hardware address\nmulticast - force interface into multicast mode if the driver doesn't\nallmulti  - promisc for multicast packets"
 
@@ -142,6 +140,8 @@
 
 #define HELP_which "usage: which [-a] filename ...\n\nSearch $PATH for executable files matching filename(s).\n\n-a	Show all matches"
 
+#define HELP_watchdog "usage: watchdog [-F] [-t UPDATE] [-T DEADLINE] DEV\n\nStart the watchdog timer at DEV with optional timeout parameters.\n\n-F	run in the foreground (do not daemonize)\n-t	poke watchdog every UPDATE seconds (default 4)\n-T	reboot if not poked for DEADLINE seconds (default 60)"
+
 #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\")"
 
 #define HELP_w "usage: w\n\nShow who is logged on and since how long they logged in."
@@ -174,14 +174,18 @@
 
 #define HELP_swapoff "usage: swapoff swapregion\n\nDisable swapping on a given swapregion."
 
-#define HELP_stat "usage: stat [-tfL] [-c FORMAT] FILE...\n\nDisplay status of files or filesystems.\n\n-c	Output specified FORMAT string instead of default\n-f	Display filesystem status instead of file status\n-L	Follow symlinks\n-t	terse (-c \"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\")\n	      (with -f = -c \"%n %i %l %t %s %S %b %f %a %c %d\")\n\nThe valid format escape sequences for files:\n%a  Access bits (octal) |%A  Access bits (flags)|%b  Size/512\n%B  Bytes per %b (512)  |%C  Security context   |%d  Device ID (dec)\n%D  Device ID (hex)     |%f  All mode bits (hex)|%F  File type\n%g  Group ID            |%G  Group name         |%h  Hard links\n%i  Inode               |%m  Mount point        |%n  Filename\n%N  Long filename       |%o  I/O block size     |%s  Size (bytes)\n%t  Devtype major (hex) |%T  Devtype minor (hex)|%u  User ID\n%U  User name           |%x  Access time        |%X  Access unix time\n%y  Modification time   |%Y  Mod unix time      |%z  Creation time\n%Z  Creation unix time\n\nThe valid format escape sequences for filesystems:\n%a  Available blocks    |%b  Total blocks       |%c  Total inodes\n%d  Free inodes         |%f  Free blocks        |%i  File system ID\n%l  Max filename length |%n  File name          |%s  Fragment size\n%S  Best transfer size  |%t  FS type (hex)      |%T  FS type (driver name)"
+#define HELP_stat "usage: stat [-tfL] [-c FORMAT] FILE...\n\nDisplay status of files or filesystems.\n\n-c	Output specified FORMAT string instead of default\n-f	Display filesystem status instead of file status\n-L	Follow symlinks\n-t	terse (-c \"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\")\n	      (with -f = -c \"%n %i %l %t %s %S %b %f %a %c %d\")\n\nThe valid format escape sequences for files:\n%a  Access bits (octal) |%A  Access bits (flags)|%b  Size/512\n%B  Bytes per %b (512)  |%C  Security context   |%d  Device ID (dec)\n%D  Device ID (hex)     |%f  All mode bits (hex)|%F  File type\n%g  Group ID            |%G  Group name         |%h  Hard links\n%i  Inode               |%m  Mount point        |%n  Filename\n%N  Long filename       |%o  I/O block size     |%s  Size (bytes)\n%t  Devtype major (hex) |%T  Devtype minor (hex)|%u  User ID\n%U  User name           |%x  Access time        |%X  Access unix time\n%y  Modification time   |%Y  Mod unix time      |%z  Creation time\n%Z  Creation unix time\n\nThe valid format escape sequences for filesystems:\n%a  Available blocks    |%b  Total blocks       |%c  Total inodes\n%d  Free inodes         |%f  Free blocks        |%i  File system ID\n%l  Max filename length |%n  File name          |%s  Best transfer size\n%S  Actual block size   |%t  FS type (hex)      |%T  FS type (driver name)"
 
 #define HELP_shred "usage: shred [-fuz] [-n COUNT] [-s SIZE] FILE...\n\nSecurely delete a file by overwriting its contents with random data.\n\n-f		Force (chmod if necessary)\n-n COUNT	Random overwrite iterations (default 1)\n-o OFFSET	Start at OFFSET\n-s SIZE		Use SIZE instead of detecting file size\n-u		Unlink (actually delete file when done)\n-x		Use exact size (default without -s rounds up to next 4k)\n-z		Zero at end\n\nNote: data journaling filesystems render this command useless, you must\noverwrite all free space (fill up disk) to erase old data on those."
 
+#define HELP_sha3sum "usage: sha3sum [-S] [-a BITS] [FILE...]\n\nHash function du jour.\n\n-a	Produce a hash BITS long (default 224)\n-b	Brief (hash only, no filename)\n-S	Use SHAKE termination byte instead of SHA3 (ask FIPS why)"
+
 #define HELP_setsid "usage: setsid [-cdw] command [args...]\n\nRun process in a new session.\n\n-d	Detach from tty\n-c	Control tty (become foreground process & receive keyboard signals)\n-w	Wait for child (and exit with its status)"
 
 #define HELP_setfattr "usage: setfattr [-h] [-x|-n NAME] [-v VALUE] FILE...\n\nWrite POSIX extended attributes.\n\n-h	Do not dereference symlink\n-n	Set given attribute\n-x	Remove given attribute\n-v	Set value for attribute -n (default is empty)"
 
+#define HELP_rtcwake "usage: rtcwake [-aluv] [-d FILE] [-m MODE] [-s SECS] [-t UNIX]\n\nEnter the given sleep state until the given time.\n\n-a	RTC uses time specified in /etc/adjtime\n-d FILE	Device to use (default /dev/rtc)\n-l	RTC uses local time\n-m	Mode (--list-modes to see those supported by your kernel):\n	  standby  S1: default              mem     S3: suspend to RAM\n	  disk     S4: suspend to disk      off     S5: power off\n	  disable  Cancel current alarm     freeze  stop processes/processors\n	  no       just set wakeup time     on      just poll RTC for alarm\n	  show     just show current alarm\n-s SECS	Wake SECS seconds from now\n-t UNIX	Wake UNIX seconds from epoch\n-u	RTC uses UTC\n-v	Verbose"
+
 #define HELP_rmmod "usage: rmmod [-wf] [MODULE]\n\nUnload the module named MODULE from the Linux kernel.\n-f	Force unload of a module\n-w	Wait until the module is no longer used"
 
 #define HELP_rev "usage: rev [FILE...]\n\nOutput each line reversed, when no files are given stdin is used."
@@ -196,6 +200,8 @@
 
 #define HELP_readahead "usage: readahead FILE...\n\nPreload files into disk cache."
 
+#define HELP_pwgen "usage: pwgen [-cAn0yrsBhC1v] [LENGTH] [COUNT]\n\nGenerate human-readable random passwords. When output is to tty produces\na screenfull to defeat shoulder surfing (pick one and clear the screen).\n\n-c  --capitalize                  Permit capital letters.\n-A  --no-capitalize               Don't include capital letters.\n-n  --numerals                    Permit numbers.\n-0  --no-numerals                 Don't include numbers.\n-y  --symbols                     Permit special characters ($#%...).\n-r <chars>  --remove=<chars>      Don't include the given characters.\n-s  --secure                      Generate more random passwords.\n-B  --ambiguous                   Avoid ambiguous characters (e.g. 0, O).\n-h  --help                        Print this help message.\n-C                                Print the output in columns.\n-1                                Print the output one line each.\n-v                                Don't include vowels."
+
 #define HELP_pwdx "usage: pwdx PID...\n\nPrint working directory of processes listed on command line."
 
 #define HELP_printenv "usage: printenv [-0] [env_var...]\n\nPrint environment variables.\n\n-0	Use \\0 as delimiter instead of \\n"
@@ -206,7 +212,7 @@
 
 #define HELP_partprobe "usage: partprobe DEVICE...\n\nTell the kernel about partition table changes\n\nAsk the kernel to re-read the partition table on the specified devices."
 
-#define HELP_oneit "usage: oneit [-p] [-c /dev/tty0] command [...]\n\nSimple init program that runs a single supplied command line with a\ncontrolling tty (so CTRL-C can kill it).\n\n-c	Which console device to use (/dev/console doesn't do CTRL-C, etc)\n-p	Power off instead of rebooting when command exits\n-r	Restart child when it exits\n-3	Write 32 bit PID of each exiting reparented process to fd 3 of child\n	(Blocking writes, child must read to avoid eventual deadlock.)\n\nSpawns a single child process (because PID 1 has signals blocked)\nin its own session, reaps zombies until the child exits, then\nreboots the system (or powers off with -p, or restarts the child with -r).\n\nResponds to SIGUSR1 by halting the system, SIGUSR2 by powering off,\nand SIGTERM or SIGINT reboot."
+#define HELP_oneit "usage: oneit [-prn3] [-c CONSOLE] [COMMAND...]\n\nSimple init program that runs a single supplied command line with a\ncontrolling tty (so CTRL-C can kill it).\n\n-c	Which console device to use (/dev/console doesn't do CTRL-C, etc)\n-p	Power off instead of rebooting when command exits\n-r	Restart child when it exits\n-n	No reboot, just relaunch command line\n-3	Write 32 bit PID of each exiting reparented process to fd 3 of child\n	(Blocking writes, child must read to avoid eventual deadlock.)\n\nSpawns a single child process (because PID 1 has signals blocked)\nin its own session, reaps zombies until the child exits, then\nreboots the system (or powers off with -p, or restarts the child with -r).\n\nResponds to SIGUSR1 by halting the system, SIGUSR2 by powering off,\nand SIGTERM or SIGINT reboot."
 
 #define HELP_nsenter "usage: nsenter [-t pid] [-F] [-i] [-m] [-n] [-p] [-u] [-U] COMMAND...\n\nRun COMMAND in an existing (set of) namespace(s).\n\n-t	PID to take namespaces from    (--target)\n-F	don't fork, even if -p is used (--no-fork)\n\nThe namespaces to switch are:\n\n-i	SysV IPC: message queues, semaphores, shared memory (--ipc)\n-m	Mount/unmount tree (--mount)\n-n	Network address, sockets, routing, iptables (--net)\n-p	Process IDs and init, will fork unless -F is used (--pid)\n-u	Host and domain names (--uts)\n-U	UIDs, GIDs, capabilities (--user)\n\nIf -t isn't specified, each namespace argument must provide a path\nto a namespace file, ala \"-i=/proc/$PID/ns/ipc\""
 
@@ -232,7 +238,7 @@
 
 #define HELP_lspci_text "usage: lspci [-n] [-i FILE ]\n\n-n	Numeric output (repeat for readable and numeric)\n-i	PCI ID database (default /usr/share/misc/pci.ids)"
 
-#define HELP_lspci "usage: lspci [-ekm]\n\nList PCI devices.\n\n-e	Print all 6 digits in class\n-k	Print kernel driver\n-m	Machine parseable format"
+#define HELP_lspci "usage: lspci [-ekm]\n\nList PCI devices.\n\n-e	Print all 6 digits in class\n-k	Print kernel driver\n-m	Machine readable format"
 
 #define HELP_lsmod "usage: lsmod\n\nDisplay the currently loaded modules, their sizes and their dependencies."
 
@@ -260,9 +266,9 @@
 
 #define HELP_i2cdetect "usage: i2cdetect [-ary] BUS [FIRST LAST]\nusage: i2cdetect -F BUS\nusage: i2cdetect -l\n\nDetect i2c devices.\n\n-a	All addresses (0x00-0x7f rather than 0x03-0x77)\n-F	Show functionality\n-l	List all buses\n-r	Probe with SMBus Read Byte\n-y	Answer \"yes\" to confirmation prompts (for script use)"
 
-#define HELP_hwclock "usage: hwclock [-rswtluf]\n\nGet/set the hardware clock.\n\n-f FILE	Use specified device file instead of /dev/rtc (--rtc)\n-l	Hardware clock uses localtime (--localtime)\n-r	Show hardware clock time (--show)\n-s	Set system time from hardware clock (--hctosys)\n-t	Set the system time based on the current timezone (--systz)\n-u	Hardware clock uses UTC (--utc)\n-w	Set hardware clock from system time (--systohc)"
+#define HELP_hwclock "usage: hwclock [-rswtluf]\n\nGet/set the hardware clock.\n\n-f FILE	Use specified device file instead of /dev/rtc0 (--rtc)\n-l	Hardware clock uses localtime (--localtime)\n-r	Show hardware clock time (--show)\n-s	Set system time from hardware clock (--hctosys)\n-t	Set the system time based on the current timezone (--systz)\n-u	Hardware clock uses UTC (--utc)\n-w	Set hardware clock from system time (--systohc)"
 
-#define HELP_hexedit "usage: hexedit FILENAME\n\nHexadecimal file editor. All changes are written to disk immediately.\n\n-r	Read only (display but don't edit)\n\nKeys:\nArrows        Move left/right/up/down by one line/column\nPg Up/Pg Dn   Move up/down by one page\n0-9, a-f      Change current half-byte to hexadecimal value\nu             Undo\nq/^c/^d/<esc> Quit"
+#define HELP_hexedit "usage: hexedit FILE\n\nHexadecimal file editor/viewer. All changes are written to disk immediately.\n\n-r	Read only (display but don't edit)\n\nKeys:\nArrows         Move left/right/up/down by one line/column\nPgUp/PgDn      Move up/down by one page\nHome/End       Start/end of line (start/end of file with ctrl)\n0-9, a-f       Change current half-byte to hexadecimal value\n^J or :        Jump (+/- for relative offset, otherwise absolute address)\n^F or /        Find string (^G/n: next, ^D/p: previous match)\nu              Undo\nq/^C/^Q/Esc    Quit"
 
 #define HELP_help "usage: help [-ahu] [COMMAND]\n\n-a	All commands\n-u	Usage only\n-h	HTML output\n\nShow usage information for toybox commands.\nRun \"toybox\" with no arguments for a list of available commands."
 
@@ -288,7 +294,7 @@
 
 #define HELP_dos2unix "usage: dos2unix [FILE...]\n\nConvert newline format from dos \"\\r\\n\" to unix \"\\n\".\nIf no files listed copy from stdin, \"-\" is a synonym for stdin."
 
-#define HELP_devmem "usage: devmem ADDR [WIDTH [DATA]]\n\nRead/write physical address via /dev/mem.\n\nWIDTH is 1, 2, 4, or 8 bytes (default 4)."
+#define HELP_devmem "usage: devmem ADDR [WIDTH [DATA]]\n\nRead/write physical address. WIDTH is 1, 2, 4, or 8 bytes (default 4).\nPrefix ADDR with 0x for hexadecimal, output is in same base as address."
 
 #define HELP_count "usage: count\n\nCopy stdin to stdout, displaying simple progress indicator to stderr."
 
@@ -312,6 +318,10 @@
 
 #define HELP_blkid "usage: blkid [-s TAG] [-UL] DEV...\n\nPrint type, label and UUID of filesystem on a block device or image.\n\n-U	Show UUID only (or device with that UUID)\n-L	Show LABEL only (or device with that LABEL)\n-s TAG	Only show matching tags (default all)"
 
+#define HELP_blkdiscard "usage: blkdiscard [-olszf] DEVICE\n\nDiscard device sectors.\n\n-o, --offset OFF	Byte offset to start discarding at (default 0)\n-l, --length LEN	Bytes to discard (default all)\n-s, --secure		Perform secure discard\n-z, --zeroout		Zero-fill rather than discard\n-f, --force		Disable check for mounted filesystem\n\nOFF and LEN must be aligned to the device sector size.\nBy default entire device is discarded.\nWARNING: All discarded data is permanently lost!"
+
+#define HELP_base32 "usage: base32 [-di] [-w COLUMNS] [FILE...]\n\nEncode or decode in base32.\n\n-d	Decode\n-i	Ignore non-alphabetic characters\n-w	Wrap output at COLUMNS (default 76 or 0 for no wrap)"
+
 #define HELP_base64 "usage: base64 [-di] [-w COLUMNS] [FILE...]\n\nEncode or decode in base64.\n\n-d	Decode\n-i	Ignore non-alphabetic characters\n-w	Wrap output at COLUMNS (default 76 or 0 for no wrap)"
 
 #define HELP_ascii "usage: ascii\n\nDisplay ascii character set."
@@ -328,6 +338,8 @@
 
 #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"
 
+#define HELP_unicode "usage: unicode [[min]-max]\n\nConvert between Unicode code points and UTF-8, in both directions."
+
 #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 ms (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"
 
 #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"
@@ -338,7 +350,7 @@
 
 #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)"
 
-#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server"
+#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server."
 
 #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP            IP to listen on, 0 = all\nPORT          Port to listen on\nPROG ARGS     Program to run\n-l NAME       Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N          Handle up to N (> 0) connections simultaneously\n-b N          (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG]    (TCP Only) Allow only up to N (> 0) connections from the same IP\n              New connections from this IP address are closed\n              immediately. MSG is written to the peer before close\n-h            Look up peer's hostname\n-E            Don't set up environment variables\n-v            Verbose"
 
@@ -348,19 +360,39 @@
 
 #define HELP_stty "usage: stty [-ag] [-F device] SETTING...\n\nGet/set terminal configuration.\n\n-F	Open device instead of stdin\n-a	Show all current settings (default differences from \"sane\")\n-g	Show all current settings usable as input to stty\n\nSpecial characters (syntax ^c or undef): intr quit erase kill eof eol eol2\nswtch start stop susp rprnt werase lnext discard\n\nControl/input/output/local settings as shown by -a, '-' prefix to disable\n\nCombo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane\n\nN	set input and output speed (ispeed N or ospeed N for just one)\ncols N	set number of columns\nrows N	set number of rows\nline N	set line discipline\nmin N	set minimum chars per read\ntime N	set read timeout\nspeed	show speed only\nsize	show size only"
 
+#define HELP_wait "usage: wait [-n] [ID...]\n\nWait for background processes to exit, returning its exit code.\nID can be PID or job, with no IDs waits for all backgrounded processes.\n\n-n	Wait for next process to exit"
+
+#define HELP_source "usage: source FILE [ARGS...]\n\nRead FILE and execute commands. Any ARGS become positional parameters."
+
+#define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
+
+#define HELP_local "usage: local [NAME[=VALUE]...]\n\nCreate a local variable that lasts until return from this function.\nWith no arguments lists local variables in current function context.\nTODO: implement \"declare\" options."
+
+#define HELP_jobs "usage: jobs [-lnprs] [%JOB | -x COMMAND...]\n\nList running/stopped background jobs.\n\n-l Include process ID in list\n-n Show only new/changed processes\n-p Show process IDs only\n-r Show running processes\n-s Show stopped processes"
+
+#define HELP_export "usage: export [-n] [NAME[=VALUE]...]\n\nMake variables available to child processes. NAME exports existing local\nvariable(s), NAME=VALUE sets and exports.\n\n-n	Unexport. Turn listed variable(s) into local variables.\n\nWith no arguments list exported variables/attributes as \"declare\" statements."
+
+#define HELP_exec "usage: exec [-cl] [-a NAME] COMMAND...\n\n-a	set argv[0] to NAME\n-c	clear environment\n-l	prepend - to argv[0]"
+
+#define HELP_eval "usage: eval COMMAND...\n\nExecute (combined) arguments as a shell command."
+
+#define HELP_unset "usage: unset [-fvn] NAME...\n\n-f	NAME is a function\n-v	NAME is a variable\n-n	dereference NAME and unset that"
+
+#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f	NAME is a function\n-v	NAME is a variable\n-n	don't follow name reference\n\nOPTIONs:\n  history - enable command history"
+
 #define HELP_exit "usage: exit [status]\n\nExit shell.  If no return value supplied on command line, use value\nof most recent command, or 0 if none."
 
 #define HELP_cd "usage: cd [-PL] [path]\n\nChange current directory.  With no arguments, go $HOME.\n\n-P	Physical path: resolve symlinks in path\n-L	Local path: .. trims directories off $PWD (default)"
 
 #define HELP_sh "usage: sh [-c command] [script]\n\nCommand shell.  Runs a shell script, or reads input interactively\nand responds to it.\n\n-c	command line to execute\n-i	interactive mode (default when STDIN is a tty)"
 
-#define HELP_route "usage: route [-ne] [-A [46]] [add|del TARGET [OPTIONS]]\n\nDisplay, add or delete network routes in the \"Forwarding Information Base\".\n\n-n	Show numerical addresses (no DNS lookups)\n-e	display netstat fields\n\nRouting means sending packets out a network interface to an address.\nThe kernel can tell where to send packets one hop away by examining each\ninterface's address and netmask, so the most common use of this command\nis to identify a \"gateway\" that forwards other traffic.\n\nAssigning an address to an interface automatically creates an appropriate\nnetwork route (\"ifconfig eth0 10.0.2.15/8\" does \"route add 10.0.0.0/8 eth0\"\nfor you), although some devices (such as loopback) won't show it in the\ntable. For machines more than one hop away, you need to specify a gateway\n(ala \"route add default gw 10.0.2.2\").\n\nThe address \"default\" is a wildcard address (0.0.0.0/0) matching all\npackets without a more specific route.\n\nAvailable OPTIONS include:\nreject   - blocking route (force match failure)\ndev NAME - force packets out this interface (ala \"eth0\")\nnetmask  - old way of saying things like ADDR/24\ngw ADDR  - forward packets to gateway ADDR"
+#define HELP_route "usage: route [-ne] [-A [inet|inet6]] [add|del TARGET [OPTIONS]]\n\nDisplay, add or delete network routes in the \"Forwarding Information Base\",\nwhich send packets out a network interface to an address.\n\n-n	Show numerical addresses (no DNS lookups)\n-e	display netstat fields\n\nAssigning an address to an interface automatically creates an appropriate\nnetwork route (\"ifconfig eth0 10.0.2.15/8\" does \"route add 10.0.0.0/8 eth0\"\nfor you), although some devices (such as loopback) won't show it in the\ntable. For machines more than one hop away, you need to specify a gateway\n(ala \"route add default gw 10.0.2.2\").\n\nThe address \"default\" is a wildcard address (0.0.0.0/0) matching all\npackets without a more specific route.\n\nAvailable OPTIONS include:\nreject   - blocking route (force match failure)\ndev NAME - force matching packets out this interface (ala \"eth0\")\nnetmask  - old way of saying things like ADDR/24\ngw ADDR  - forward packets to gateway ADDR"
 
-#define HELP_readelf "usage: readelf [-adhlnSsW] [-p SECTION] [-x SECTION] [file...]\n\nDisplays information about ELF files.\n\n-a	Equivalent to -dhlnSs\n-d	Show dynamic section\n-h	Show ELF header\n-l	Show program headers\n-n	Show notes\n-p S	Dump strings found in named/numbered section\n-S	Show section headers\n-s	Show symbol tables (.dynsym and .symtab)\n-W	Don't truncate fields (default in toybox)\n-x S	Hex dump of named/numbered section\n\n--dyn-syms	Show just .dynsym symbol table"
+#define HELP_readelf "usage: readelf [-adehlnSs] [-p SECTION] [-x SECTION] [file...]\n\nDisplays information about ELF files.\n\n-a	Equivalent to -dhlnSs\n-d	Show dynamic section\n-e	Headers (equivalent to -hlS)\n-h	Show ELF header\n-l	Show program headers\n-n	Show notes\n-p S	Dump strings found in named/numbered section\n-S	Show section headers\n-s	Show symbol tables (.dynsym and .symtab)\n-x S	Hex dump of named/numbered section\n\n--dyn-syms	Show just .dynsym symbol table"
 
-#define HELP_deallocvt "usage: deallocvt [N]\n\nDeallocate unused virtual terminal /dev/ttyN, or all unused consoles."
+#define HELP_deallocvt "usage: deallocvt [NUM]\n\nDeallocate unused virtual terminals, either a specific /dev/ttyNUM, or all."
 
-#define HELP_openvt "usage: openvt [-c N] [-sw] [command [command_options]]\n\nstart a program on a new virtual terminal (VT)\n\n-c N  Use VT N\n-s    Switch to new VT\n-w    Wait for command to exit\n\nif -sw used together, switch back to originating VT when command completes"
+#define HELP_openvt "usage: openvt [-c NUM] [-sw] [COMMAND...]\n\nStart a program on a new virtual terminal.\n\n-c NUM  Use VT NUM\n-s    Switch to new VT\n-w    Wait for command to exit\n\nTogether -sw switch back to originating VT when command completes."
 
 #define HELP_more "usage: more [FILE...]\n\nView FILE(s) (or stdin) one screenfull at a time."
 
@@ -402,7 +434,7 @@
 
 #define HELP_groupadd "usage: groupadd [-S] [-g GID] [USER] GROUP\n\nAdd a group or add a user to a group\n\n  -g GID Group id\n  -S     Create a system group"
 
-#define HELP_getty "usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]\n\n-h    Enable hardware RTS/CTS flow control\n-L    Set CLOCAL (ignore Carrier Detect state)\n-m    Get baud rate from modem's CONNECT status message\n-n    Don't prompt for login name\n-w    Wait for CR or LF before sending /etc/issue\n-i    Don't display /etc/issue\n-f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue\n-l LOGIN  Invoke LOGIN instead of /bin/login\n-t SEC    Terminate after SEC if no login name is read\n-I INITSTR  Send INITSTR before anything else\n-H HOST    Log HOST into the utmp file as the hostname"
+#define HELP_getty "usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]\n\nWait for a modem to dial into serial port, adjust baud rate, call login.\n\n-h    Enable hardware RTS/CTS flow control\n-L    Set CLOCAL (ignore Carrier Detect state)\n-m    Get baud rate from modem's CONNECT status message\n-n    Don't prompt for login name\n-w    Wait for CR or LF before sending /etc/issue\n-i    Don't display /etc/issue\n-f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue\n-l LOGIN  Invoke LOGIN instead of /bin/login\n-t SEC    Terminate after SEC if no login name is read\n-I INITSTR  Send INITSTR before anything else\n-H HOST    Log HOST into the utmp file as the hostname"
 
 #define HELP_getopt "usage: getopt [OPTIONS] [--] ARG...\n\nParse command-line options for use in shell scripts.\n\n-a	Allow long options starting with a single -.\n-l OPTS	Specify long options.\n-n NAME	Command name for error messages.\n-o OPTS	Specify short options.\n-T	Test whether this is a modern getopt.\n-u	Output options unquoted."
 
@@ -432,6 +464,8 @@
 
 #define HELP_crond "usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]\n\nA daemon to execute scheduled commands.\n\n-b Background (default)\n-c crontab dir\n-d Set log level, log to stderr\n-f Foreground\n-l Set log level. 0 is the most verbose, default 8\n-S Log to syslog (default)\n-L Log to file"
 
+#define HELP_chsh "usage: chsh [-s SHELL] [USER]\n\nChange user's login shell.\n\n-s	Use SHELL instead of prompting\n\nNon-root users can only change their own shell to one listed in /etc/shells."
+
 #define HELP_brctl "usage: brctl COMMAND [BRIDGE [INTERFACE]]\n\nManage ethernet bridges\n\nCommands:\nshow                  Show a list of bridges\naddbr BRIDGE          Create BRIDGE\ndelbr BRIDGE          Delete BRIDGE\naddif BRIDGE IFACE    Add IFACE to BRIDGE\ndelif BRIDGE IFACE    Delete IFACE from BRIDGE\nsetageing BRIDGE TIME Set ageing time\nsetfd BRIDGE TIME     Set bridge forward delay\nsethello BRIDGE TIME  Set hello time\nsetmaxage BRIDGE TIME Set max message age\nsetpathcost BRIDGE PORT COST   Set path cost\nsetportprio BRIDGE PORT PRIO   Set port priority\nsetbridgeprio BRIDGE PRIO      Set bridge priority\nstp BRIDGE [1/yes/on|0/no/off] STP on/off"
 
 #define HELP_bootchartd "usage: bootchartd {start [PROG ARGS]}|stop|init\n\nCreate /var/log/bootlog.tgz with boot chart data\n\nstart: start background logging; with PROG, run PROG,\n       then kill logging with USR1\nstop:  send USR1 to all bootchartd processes\ninit:  start background logging; stop when getty/xdm is seen\n      (for init scripts)\n\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
@@ -442,7 +476,7 @@
 
 #define HELP_arp "usage: arp\n[-vn] [-H HWTYPE] [-i IF] -a [HOSTNAME]\n[-v]              [-i IF] -d HOSTNAME [pub]\n[-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]\n[-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub\n[-v]  [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub\n\nManipulate ARP cache\n\n-a    Display (all) hosts\n-s    Set new ARP entry\n-d    Delete a specified entry\n-v    Verbose\n-n    Don't resolve names\n-i IF Network interface\n-D    Read <hwaddr> from given device\n-A,-p AF  Protocol family\n-H    HWTYPE Hardware address type"
 
-#define HELP_xargs "usage: xargs [-0prt] [-s NUM] [-n NUM] [-E STR] COMMAND...\n\nRun command line one or more times, appending arguments from stdin.\n\nIf COMMAND exits with 255, don't launch another even if arguments remain.\n\n-0	Each argument is NULL terminated, no whitespace or quote processing\n-E	Stop at line matching string\n-n	Max number of arguments per command\n-o	Open tty for COMMAND's stdin (default /dev/null)\n-p	Prompt for y/n from tty before running each command\n-r	Don't run command with empty input (otherwise always run command once)\n-s	Size in bytes per command line\n-t	Trace, print command line to stderr"
+#define HELP_xargs "usage: xargs [-0prt] [-snE STR] COMMAND...\n\nRun command line one or more times, appending arguments from stdin.\n\nIf COMMAND exits with 255, don't launch another even if arguments remain.\n\n-0	Each argument is NULL terminated, no whitespace or quote processing\n-E	Stop at line matching string\n-n	Max number of arguments per command\n-o	Open tty for COMMAND's stdin (default /dev/null)\n-p	Prompt for y/n from tty before running each command\n-P	Parallel processes (default 1)\n-r	Don't run with empty input (otherwise always run command once)\n-s	Size in bytes per command line\n-t	Trace, print command line to stderr"
 
 #define HELP_who "usage: who\n\nPrint information about logged in users."
 
@@ -460,7 +494,7 @@
 
 #define HELP_arch "usage: arch\n\nPrint machine (hardware) name, same as uname -m."
 
-#define HELP_ulimit "usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]\n\nPrint or set resource limits for process number PID. If no LIMIT specified\n(or read-only -ap selected) display current value (sizes in bytes).\nDefault is ulimit -P $PPID -Sf\" (show soft filesize of your shell).\n\n-S  Set/show soft limit          -H  Set/show hard (maximum) limit\n-a  Show all limits              -c  Core file size\n-d  Process data segment         -e  Max scheduling priority\n-f  Output file size             -i  Pending signal count\n-l  Locked memory                -m  Resident Set Size\n-n  Number of open files         -p  Pipe buffer\n-q  Posix message queue          -r  Max Real-time priority\n-R  Realtime latency (usec)      -s  Stack size\n-t  Total CPU time (in seconds)  -u  Maximum processes (under this UID)\n-v  Virtual memory size          -P  PID to affect (default $PPID)"
+#define HELP_ulimit "usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]\n\nPrint or set resource limits for process number PID. If no LIMIT specified\n(or read-only -ap selected) display current value (sizes in bytes).\nDefault is ulimit -P $PPID -Sf\" (show soft filesize of your shell).\n\n-P  PID to affect (default $PPID)  -a  Show all limits\n-S  Set/show soft limit            -H  Set/show hard (maximum) limit\n\n-c  Core file size (blocks)        -d  Process data segment (KiB)\n-e  Max scheduling priority        -f  File size (KiB)\n-i  Pending signal count           -l  Locked memory (KiB)\n-m  Resident Set Size (KiB)        -n  Number of open files\n-p  Pipe buffer (512 bytes)        -q  POSIX message queues\n-r  Max realtime priority          -R  Realtime latency (us)\n-s  Stack size (KiB)               -t  Total CPU time (s)\n-u  Maximum processes (this UID)   -v  Virtual memory size (KiB)"
 
 #define HELP_tty "usage: tty [-s]\n\nShow filename of terminal connected to stdin.\n\nPrints \"not a tty\" and exits with nonzero status if no terminal\nis connected to stdin.\n\n-s	Silent, exit code only"
 
@@ -470,11 +504,11 @@
 
 #define HELP_time "usage: time [-pv] COMMAND...\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 format output (default)\n-v	Verbose"
 
-#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)"
+#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   -k  sticky bit\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\n\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)"
 
 #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"
 
-#define HELP_tar "usage: tar [-cxt] [-fvohmjkOS] [-XTCf NAME] [FILE...]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc  Create                x  Extract               t  Test (list)\nf  tar FILE (default -)  C  Change to DIR first   v  Verbose display\no  Ignore owner          h  Follow symlinks       m  Ignore mtime\nJ  xz compression        j  bzip2 compression     z  gzip compression\nO  Extract to stdout     X  exclude names in FILE T  include names in FILE\n\n--exclude        FILENAME to exclude    --full-time   Show seconds with -tv\n--mode MODE      Adjust modes           --mtime TIME  Override timestamps\n--owner NAME     Set file owner to NAME --group NAME  Set file group to NAME\n--sparse         Record sparse files\n--restrict       All archive contents must extract under one subdirctory\n--numeric-owner  Save/use/display uid and gid, not user/group name\n--no-recursion   Don't store directory contents"
+#define HELP_tar "usage: tar [-cxt] [-fvohmjkOS] [-XTCf NAME] [FILE...]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc  Create                x  Extract               t  Test (list)\nf  tar FILE (default -)  C  Change to DIR first   v  Verbose display\no  Ignore owner          h  Follow symlinks       m  Ignore mtime\nJ  xz compression        j  bzip2 compression     z  gzip compression\nO  Extract to stdout     X  exclude names in FILE T  include names in FILE\n\n--exclude        FILENAME to exclude    --full-time   Show seconds with -tv\n--mode MODE      Adjust modes           --mtime TIME  Override timestamps\n--owner NAME     Set file owner to NAME --group NAME  Set file group to NAME\n--sparse         Record sparse files\n--restrict       All archive contents must extract under one subdirectory\n--numeric-owner  Save/use/display uid and gid, not user/group name\n--no-recursion   Don't store directory contents\n-I PROG          Filter through PROG to compress or PROG -d to decompress"
 
 #define HELP_tail "usage: tail [-n|c NUMBER] [-f] [FILE...]\n\nCopy last lines from files to stdout. If no files listed, copy from\nstdin. Filename \"-\" is a synonym for stdin.\n\n-n	Output the last NUMBER lines (default 10), +X counts from start\n-c	Output the last NUMBER bytes, +NUMBER counts from start\n-f	Follow FILE(s), waiting for more data to be appended"
 
@@ -486,7 +520,7 @@
 
 #define HELP_sleep "usage: sleep DURATION\n\nWait before exiting.\n\nDURATION can be a decimal fraction. An optional suffix can be \"m\"\n(minutes), \"h\" (hours), \"d\" (days), or \"s\" (seconds, the default)."
 
-#define HELP_sed "usage: sed [-inrzE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]\n\nStream editor. Apply one or more editing SCRIPTs to each line of input\n(from FILE or stdin) producing output (by default to stdout).\n\n-e	Add SCRIPT to list\n-f	Add contents of SCRIPT_FILE to list\n-i	Edit each file in place (-iEXT keeps backup file with extension EXT)\n-n	No default output (use the p command to output matched lines)\n-r	Use extended regular expression syntax\n-E	POSIX alias for -r\n-s	Treat input files separately (implied by -i)\n-z	Use \\0 rather than \\n as the input line separator\n\nA SCRIPT is a series of one or more COMMANDs separated by newlines or\nsemicolons. All -e SCRIPTs are concatenated together as if separated\nby newlines, followed by all lines from -f SCRIPT_FILEs, in order.\nIf no -e or -f SCRIPTs are specified, the first argument is the SCRIPT.\n\nEach COMMAND may be preceded by an address which limits the command to\napply only to the specified line(s). Commands without an address apply to\nevery line. Addresses are of the form:\n\n  [ADDRESS[,ADDRESS]][!]COMMAND\n\nThe ADDRESS may be a decimal line number (starting at 1), a /regular\nexpression/ within a pair of forward slashes, or the character \"$\" which\nmatches the last line of input. (In -s or -i mode this matches the last\nline of each file, otherwise just the last line of the last file.) A single\naddress matches one line, a pair of comma separated addresses match\neverything from the first address to the second address (inclusive). If\nboth addresses are regular expressions, more than one range of lines in\neach file can match. The second address can be +N to end N lines later.\n\nREGULAR EXPRESSIONS in sed are started and ended by the same character\n(traditionally / but anything except a backslash or a newline works).\nBackslashes may be used to escape the delimiter if it occurs in the\nregex, and for the usual printf escapes (\\abcefnrtv and octal, hex,\nand unicode). An empty regex repeats the previous one. ADDRESS regexes\n(above) require the first delimiter to be escaped with a backslash when\nit isn't a forward slash (to distinguish it from the COMMANDs below).\n\nSed mostly operates on individual lines one at a time. It reads each line,\nprocesses it, and either writes it to the output or discards it before\nreading the next line. Sed can remember one additional line in a separate\nbuffer (using the h, H, g, G, and x commands), and can read the next line\nof input early (using the n and N command), but other than that command\nscripts operate on individual lines of text.\n\nEach COMMAND starts with a single character. The following commands take\nno arguments:\n\n  !  Run this command when the test _didn't_ match.\n\n  {  Start a new command block, continuing until a corresponding \"}\".\n     Command blocks may nest. If the block has an address, commands within\n     the block are only run for lines within the block's address range.\n\n  }  End command block (this command cannot have an address)\n\n  d  Delete this line and move on to the next one\n     (ignores remaining COMMANDs)\n\n  D  Delete one line of input and restart command SCRIPT (same as \"d\"\n     unless you've glued lines together with \"N\" or similar)\n\n  g  Get remembered line (overwriting current line)\n\n  G  Get remembered line (appending to current line)\n\n  h  Remember this line (overwriting remembered line)\n\n  H  Remember this line (appending to remembered line, if any)\n\n  l  Print line, escaping \\abfrtv (but not newline), octal escaping other\n     nonprintable characters, wrapping lines to terminal width with a\n     backslash, and appending $ to actual end of line.\n\n  n  Print default output and read next line, replacing current line\n     (If no next line available, quit processing script)\n\n  N  Append next line of input to this line, separated by a newline\n     (This advances the line counter for address matching and \"=\", if no\n     next line available quit processing script without default output)\n\n  p  Print this line\n\n  P  Print this line up to first newline (from \"N\")\n\n  q  Quit (print default output, no more commands processed or lines read)\n\n  x  Exchange this line with remembered line (overwrite in both directions)\n\n  =  Print the current line number (followed by a newline)\n\nThe following commands (may) take an argument. The \"text\" arguments (to\nthe \"a\", \"b\", and \"c\" commands) may end with an unescaped \"\\\" to append\nthe next line (for which leading whitespace is not skipped), and also\ntreat \";\" as a literal character (use \"\\;\" instead).\n\n  a [text]   Append text to output before attempting to read next line\n\n  b [label]  Branch, jumps to :label (or with no label, to end of SCRIPT)\n\n  c [text]   Delete line, output text at end of matching address range\n             (ignores remaining COMMANDs)\n\n  i [text]   Print text\n\n  r [file]   Append contents of file to output before attempting to read\n             next line.\n\n  s/S/R/F    Search for regex S, replace matched text with R using flags F.\n             The first character after the \"s\" (anything but newline or\n             backslash) is the delimiter, escape with \\ to use normally.\n\n             The replacement text may contain \"&\" to substitute the matched\n             text (escape it with backslash for a literal &), or \\1 through\n             \\9 to substitute a parenthetical subexpression in the regex.\n             You can also use the normal backslash escapes such as \\n and\n             a backslash at the end of the line appends the next line.\n\n             The flags are:\n\n             [0-9]    A number, substitute only that occurrence of pattern\n             g        Global, substitute all occurrences of pattern\n             i        Ignore case when matching\n             p        Print the line if match was found and replaced\n             w [file] Write (append) line to file if match replaced\n\n  t [label]  Test, jump to :label only if an \"s\" command found a match in\n             this line since last test (replacing with same text counts)\n\n  T [label]  Test false, jump only if \"s\" hasn't found a match.\n\n  w [file]   Write (append) line to file\n\n  y/old/new/ Change each character in 'old' to corresponding character\n             in 'new' (with standard backslash escapes, delimiter can be\n             any repeated character except \\ or \\n)\n\n  : [label]  Labeled target for jump commands\n\n  #  Comment, ignore rest of this line of SCRIPT\n\nDeviations from POSIX: allow extended regular expressions with -r,\nediting in place with -i, separate with -s, NUL-separated input with -z,\nprintf escapes in text, line continuations, semicolons after all commands,\n2-address anywhere an address is allowed, \"T\" command, multiline\ncontinuations for [abc], \\; to end [abc] argument before end of line."
+#define HELP_sed "usage: sed [-inrszE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]\n\nStream editor. Apply editing SCRIPTs to lines of input.\n\n-e	Add SCRIPT to list\n-f	Add contents of SCRIPT_FILE to list\n-i	Edit each file in place (-iEXT keeps backup file with extension EXT)\n-n	No default output (use the p command to output matched lines)\n-r	Use extended regular expression syntax\n-E	POSIX alias for -r\n-s	Treat input files separately (implied by -i)\n-z	Use \\0 rather than \\n as input line separator\n\nA SCRIPT is one or more COMMANDs separated by newlines or semicolons.\nAll -e SCRIPTs are combined as if separated by newlines, followed by all -f\nSCRIPT_FILEs. If no -e or -f then first argument is the SCRIPT.\n\nCOMMANDs apply to every line unless prefixed with an ADDRESS of the form:\n\n  [ADDRESS[,ADDRESS]][!]COMMAND\n\nADDRESS is a line number (starting at 1), a /REGULAR EXPRESSION/, or $ for\nlast line (-s or -i makes it last line of each file). One address matches one\nline, ADDRESS,ADDRESS matches from first to second inclusive. Two regexes can\nmatch multiple ranges. ADDRESS,+N ends N lines later. ! inverts the match.\n\nREGULAR EXPRESSIONS start and end with the same character (anything but\nbackslash or newline). To use the delimiter in the regex escape it with a\nbackslash, and printf escapes (\\abcefnrtv and octal, hex, and unicode) work.\nAn empty regex repeats the previous one. ADDRESS regexes require any\nfirst delimiter except / to be \\escaped to distinguish it from COMMANDs.\n\nSed reads each line of input, processes it, and writes it out or discards it\nbefore reading the next. Sed can remember one additional line in a separate\nbuffer (the h, H, g, G, and x commands), and can read the next line of input\nearly (the n and N commands), but otherwise operates on individual lines.\n\nEach COMMAND starts with a single character. Commands with no arguments are:\n\n  !  Run this command when the ADDRESS _didn't_ match.\n  {  Start new command block, continuing until a corresponding \"}\".\n     Command blocks nest and can have ADDRESSes applying to the whole block.\n  }  End command block (this COMMAND cannot have an address)\n  d  Delete this line and move on to the next one\n     (ignores remaining COMMANDs)\n  D  Delete one line of input and restart command SCRIPT (same as \"d\"\n     unless you've glued lines together with \"N\" or similar)\n  g  Get remembered line (overwriting current line)\n  G  Get remembered line (appending to current line)\n  h  Remember this line (overwriting remembered line)\n  H  Remember this line (appending to remembered line, if any)\n  l  Print line escaping \\abfrtv (but not \\n), octal escape other nonprintng\n     chars, wrap lines to terminal width with \\, append $ to end of line.\n  n  Print default output and read next line over current line (quit at EOF)\n  N  Append \\n and next line of input to this line. Quit at EOF without\n     default output. Advances line counter for ADDRESS and \"=\".\n  p  Print this line\n  P  Print this line up to first newline (from \"N\")\n  q  Quit (print default output, no more commands processed or lines read)\n  x  Exchange this line with remembered line (overwrite in both directions)\n  =  Print the current line number (plus newline)\n  #  Comment, ignores rest of this line of SCRIPT (until newline)\n\nCommands that take an argument:\n\n  : LABEL    Target for jump commands\n  a TEXT     Append text to output before reading next line\n  b LABEL    Branch, jumps to :LABEL (with no LABEL to end of SCRIPT)\n  c TEXT     Delete matching ADDRESS range and output TEXT instead\n  i TEXT     Insert text (output immediately)\n  r FILE     Append contents of FILE to output before reading next line.\n  s/S/R/F    Search for regex S replace match with R using flags F. Delimiter\n             is anything but \\n or \\, escape with \\ to use in S or R. Printf\n             escapes work. Unescaped & in R becomes full matched text, \\1\n             through \\9 = parenthetical subexpression from S. \\ at end of\n             line appends next line of SCRIPT. The flags in F are:\n             [0-9]    A number N, substitute only Nth match\n             g        Global, substitute all matches\n             i/I      Ignore case when matching\n             p        Print resulting line when match found and replaced\n             w [file] Write (append) line to file when match replaced\n  t LABEL    Test, jump if s/// command matched this line since last test\n  T LABEL    Test false, jump to :LABEL only if no s/// found a match\n  w FILE     Write (append) line to file\n  y/old/new/ Change each character in 'old' to corresponding character\n             in 'new' (with standard backslash escapes, delimiter can be\n             any repeated character except \\ or \\n)\n\nThe TEXT arguments (to a c i) may end with an unescaped \"\\\" to append\nthe next line (leading whitespace is not skipped), and treat \";\" as a\nliteral character (use \"\\;\" instead)."
 
 #define HELP_rmdir "usage: rmdir [-p] [DIR...]\n\nRemove one or more directories.\n\n-p	Remove path\n--ignore-fail-on-non-empty	Ignore failures caused by non-empty directories"
 
@@ -558,7 +592,7 @@
 
 #define HELP_getconf "usage: getconf -a [PATH] | -l | NAME [PATH]\n\nGet system configuration values. Values from pathconf(3) require a path.\n\n-a	Show all (defaults to \"/\" if no path given)\n-l	List available value names (grouped by source)"
 
-#define HELP_find "usage: find [-HL] [DIR...] [<options>]\n\nSearch directories for matching files.\nDefault: search \".\", match all, -print matches.\n\n-H  Follow command line symlinks         -L  Follow all symlinks\n\nMatch filters:\n-name  PATTERN   filename with wildcards  (-iname case insensitive)\n-path  PATTERN   path name with wildcards (-ipath case insensitive)\n-user  UNAME     belongs to user UNAME     -nouser     user ID not known\n-group GROUP     belongs to group GROUP    -nogroup    group ID not known\n-perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents\n-size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem\n-links N         hardlink count            -atime N[u] accessed N units ago\n-ctime N[u]      created N units ago       -mtime N[u] modified N units ago\n-newer FILE      newer mtime than FILE     -mindepth N at least N dirs down\n-depth           ignore contents of dir    -maxdepth N at most N dirs down\n-inum N          inode number N            -empty      empty files and dirs\n-type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)\n-true            always true               -false      always false\n-context PATTERN security context\n-newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)\n\nNumbers N may be prefixed by a - (less than) or + (greater than). Units for\n-Xtime are d (days, default), h (hours), m (minutes), or s (seconds).\n\nCombine matches with:\n!, -a, -o, ( )    not, and, or, group expressions\n\nActions:\n-print  Print match with newline  -print0        Print match with null\n-exec   Run command with path     -execdir       Run command in file's dir\n-ok     Ask before exec           -okdir         Ask before execdir\n-delete Remove matching file/dir  -printf FORMAT Print using format string\n\nCommands substitute \"{}\" with matched file. End with \";\" to run each file,\nor \"+\" (next argument after \"{}\") to collect and run with multiple files.\n\n-printf FORMAT characters are \\ escapes and:\n%b  512 byte blocks used\n%f  basename            %g  textual gid          %G  numeric gid\n%i  decimal inode       %l  target of symlink    %m  octal mode\n%M  ls format type/mode %p  path to file         %P  path to file minus DIR\n%s  size in bytes       %T@ mod time as unixtime\n%u  username            %U  numeric uid          %Z  security context"
+#define HELP_find "usage: find [-HL] [DIR...] [<options>]\n\nSearch directories for matching files.\nDefault: search \".\", match all, -print matches.\n\n-H  Follow command line symlinks         -L  Follow all symlinks\n\nMatch filters:\n-name  PATTERN   filename with wildcards  (-iname case insensitive)\n-path  PATTERN   path name with wildcards (-ipath case insensitive)\n-user  UNAME     belongs to user UNAME     -nouser     user ID not known\n-group GROUP     belongs to group GROUP    -nogroup    group ID not known\n-perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents\n-size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem\n-links N         hardlink count            -atime N[u] accessed N units ago\n-ctime N[u]      created N units ago       -mtime N[u] modified N units ago\n-newer FILE      newer mtime than FILE     -mindepth N at least N dirs down\n-depth           ignore contents of dir    -maxdepth N at most N dirs down\n-inum N          inode number N            -empty      empty files and dirs\n-type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)\n-true            always true               -false      always false\n-context PATTERN security context          -executable access(X_OK) perm+ACL\n-newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)\n\nNumbers N may be prefixed by a - (less than) or + (greater than). Units for\n-Xtime are d (days, default), h (hours), m (minutes), or s (seconds).\n\nCombine matches with:\n!, -a, -o, ( )    not, and, or, group expressions\n\nActions:\n-print  Print match with newline  -print0        Print match with null\n-exec   Run command with path     -execdir       Run command in file's dir\n-ok     Ask before exec           -okdir         Ask before execdir\n-delete Remove matching file/dir  -printf FORMAT Print using format string\n\nCommands substitute \"{}\" with matched file. End with \";\" to run each file,\nor \"+\" (next argument after \"{}\") to collect and run with multiple files.\n\n-printf FORMAT characters are \\ escapes and:\n%b  512 byte blocks used\n%f  basename            %g  textual gid          %G  numeric gid\n%i  decimal inode       %l  target of symlink    %m  octal mode\n%M  ls format type/mode %p  path to file         %P  path to file minus DIR\n%s  size in bytes       %T@ mod time as unixtime\n%u  username            %U  numeric uid          %Z  security context"
 
 #define HELP_file "usage: file [-bhLs] [FILE...]\n\nExamine the given files and describe their content types.\n\n-b	Brief (no filename)\n-h	Don't follow symlinks (default)\n-L	Follow symlinks\n-s	Show block/char device contents"
 
@@ -570,25 +604,23 @@
 
 #define HELP_echo "usage: echo [-neE] [ARG...]\n\nWrite each argument to stdout, with one space between each, followed\nby a newline.\n\n-n	No trailing newline\n-E	Print escape sequences literally (default)\n-e	Process the following escape sequences:\n	\\\\	Backslash\n	\\0NNN	Octal values (1 to 3 digits)\n	\\a	Alert (beep/flash)\n	\\b	Backspace\n	\\c	Stop output here (avoids trailing newline)\n	\\f	Form feed\n	\\n	Newline\n	\\r	Carriage return\n	\\t	Horizontal tab\n	\\v	Vertical tab\n	\\xHH	Hexadecimal values (1 to 2 digits)"
 
-#define HELP_du "usage: du [-d N] [-askxHLlmc] [FILE...]\n\nShow disk usage, space consumed by files and directories.\n\nSize in:\n-k	1024 byte blocks (default)\n-K	512 byte blocks (posix)\n-m	Megabytes\n-h	Human readable (e.g., 1K 243M 2G)\n\nWhat to show:\n-a	All files, not just directories\n-H	Follow symlinks on cmdline\n-L	Follow all symlinks\n-s	Only total size of each argument\n-x	Don't leave this filesystem\n-c	Cumulative total\n-d N	Only depth < N\n-l	Disable hardlink filter"
+#define HELP_du "usage: du [-d N] [-askxHLlmc] [FILE...]\n\nShow disk usage, space consumed by files and directories.\n\nSize in:\n-b	Apparent bytes (directory listing size, not space used)\n-k	1024 byte blocks (default)\n-K	512 byte blocks (posix)\n-m	Megabytes\n-h	Human readable (e.g., 1K 243M 2G)\n\nWhat to show:\n-a	All files, not just directories\n-H	Follow symlinks on cmdline\n-L	Follow all symlinks\n-s	Only total size of each argument\n-x	Don't leave this filesystem\n-c	Cumulative total\n-d N	Only depth < N\n-l	Disable hardlink filter"
 
 #define HELP_dirname "usage: dirname PATH...\n\nShow directory portion of path."
 
-#define HELP_df "usage: df [-HPkhi] [-t type] [FILE...]\n\nThe \"disk free\" command shows total/used/available disk space for\neach filesystem listed on the command line, or all currently mounted\nfilesystems.\n\n-a	Show all (including /proc and friends)\n-P	The SUSv3 \"Pedantic\" option\n-k	Sets units back to 1024 bytes (the default without -P)\n-h	Human readable (K=1024)\n-H	Human readable (k=1000)\n-i	Show inodes instead of blocks\n-t type	Display only filesystems of this type\n\nPedantic provides a slightly less useful output format dictated by Posix,\nand sets the units to 512 bytes instead of the default 1024 bytes."
+#define HELP_df "usage: df [-HPkhi] [-t type] [FILE...]\n\nThe \"disk free\" command shows total/used/available disk space for\neach filesystem listed on the command line, or all currently mounted\nfilesystems.\n\n-a	Show all (including /proc and friends)\n-P	The SUSv3 \"Pedantic\" option\n-k	Sets units back to 1024 bytes (the default without -P)\n-h	Human readable (K=1024)\n-H	Human readable (k=1000)\n-i	Show inodes instead of blocks\n-t type	Display only filesystems of this type\n\nPedantic provides a slightly less useful output format dictated by POSIX,\nand sets the units to 512 bytes instead of the default 1024 bytes."
 
-#define HELP_date "usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\nSupported input formats:\n\nMMDDhhmm[[CC]YY][.ss]     POSIX\n@UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01\nYYYY-MM-DD [hh:mm[:ss]]   ISO 8601\nhh:mm[:ss]                24-hour time today\n\nAll input formats can be preceded by TZ=\"id\" to set the input time zone\nseparately from the output time zone. Otherwise $TZ sets both.\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start sunday)   %W Week of year (0-53 start monday)\n%V Week of year (1-53 start monday, week < 4 days not part of this year)\n\n%F \"%Y-%m-%d\"     %R \"%H:%M\"        %T \"%H:%M:%S\"    %z numeric timezone\n%D \"%m/%d/%y\"     %r \"%I:%M:%S %p\"  %h \"%b\"          %s unix epoch time\n%x locale date    %X locale time    %c locale date/time"
+#define HELP_date "usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-I RES	ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\nSupported input formats:\n\nMMDDhhmm[[CC]YY][.ss]     POSIX\n@UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01\nYYYY-MM-DD [hh:mm[:ss]]   ISO 8601\nhh:mm[:ss]                24-hour time today\n\nAll input formats can be followed by fractional seconds, and/or a UTC\noffset such as -0800.\n\nAll input formats can be preceded by TZ=\"id\" to set the input time zone\nseparately from the output time zone. Otherwise $TZ sets both.\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start Sunday)   %W Week of year (0-53 start Monday)\n%V Week of year (1-53 start Monday, week < 4 days not part of this year)\n\n%F \"%Y-%m-%d\"   %R \"%H:%M\"        %T \"%H:%M:%S\"        %z  timezone (-0800)\n%D \"%m/%d/%y\"   %r \"%I:%M:%S %p\"  %h \"%b\"              %:z timezone (-08:00)\n%x locale date  %X locale time    %c locale date/time  %s  unix epoch time"
 
 #define HELP_cut "usage: cut [-Ds] [-bcfF LIST] [-dO DELIM] [FILE...]\n\nPrint selected parts of lines from each FILE to standard output.\n\nEach selection LIST is comma separated, either numbers (counting from 1)\nor dash separated ranges (inclusive, with X- meaning to end of line and -X\nfrom start). By default selection ranges are sorted and collated, use -D\nto prevent that.\n\n-b	Select bytes\n-c	Select UTF-8 characters\n-C	Select unicode columns\n-d	Use DELIM (default is TAB for -f, run of whitespace for -F)\n-D	Don't sort/collate selections or match -fF lines without delimiter\n-f	Select fields (words) separated by single DELIM character\n-F	Select fields separated by DELIM regex\n-O	Output delimiter (default one space for -F, input delim for -f)\n-s	Skip lines without delimiters"
 
-#define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -mdu -H newc]\n\nCopy files into and out of a \"newc\" format cpio archive.\n\n-F FILE	Use archive FILE instead of stdin/stdout\n-p DEST	Copy-pass mode, copy stdin file list to directory DEST\n-i	Extract from archive into file system (stdin=archive)\n-o	Create archive (stdin=list of files, stdout=archive)\n-t	Test files (list only, stdin=archive, stdout=list of files)\n-v	Verbose\n--no-preserve-owner (don't set ownership during extract)\n--trailer Add legacy trailer (prevents concatenation)"
+#define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -m -H newc]\n\nCopy files into and out of a \"newc\" format cpio archive.\n\n-F FILE	Use archive FILE instead of stdin/stdout\n-p DEST	Copy-pass mode, copy stdin file list to directory DEST\n-i	Extract from archive into file system (stdin=archive)\n-o	Create archive (stdin=list of files, stdout=archive)\n-t	Test files (list only, stdin=archive, stdout=list of files)\n-d	Create directories if needed\n-u	unlink existing files when extracting\n-v	Verbose\n--no-preserve-owner (don't set ownership during extract)"
 
-#define HELP_install "usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST\n\nCopy files and set attributes.\n\n-d	Act like mkdir -p\n-D	Create leading directories for DEST\n-g	Make copy belong to GROUP\n-m	Set permissions to MODE\n-o	Make copy belong to USER\n-p	Preserve timestamps\n-s	Call \"strip -p\"\n-v	Verbose"
+#define HELP_install "usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [-t TARGET] [SOURCE...] [DEST]\n\nCopy files and set attributes.\n\n-d	Act like mkdir -p\n-D	Create leading directories for DEST\n-g	Make copy belong to GROUP\n-m	Set permissions to MODE\n-o	Make copy belong to USER\n-p	Preserve timestamps\n-s	Call \"strip -p\"\n-t	Copy files to TARGET dir (no DEST)\n-v	Verbose"
 
-#define HELP_mv "usage: mv [-finTv] SOURCE... DEST\n\n-f	Force copy by deleting destination file\n-i	Interactive, prompt before overwriting existing DEST\n-n	No clobber (don't overwrite DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose"
+#define HELP_mv "usage: mv [-finTv] [-t TARGET] SOURCE... [DEST]\n\n-f	Force copy by deleting destination file\n-i	Interactive, prompt before overwriting existing DEST\n-n	No clobber (don't overwrite DEST)\n-t	Move to TARGET dir (no DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose"
 
-#define HELP_cp_preserve "--preserve takes either a comma separated list of attributes, or the first\nletter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above\n\nusage: cp [--preserve=motcxa] [-adfHiLlnPpRrsTv] SOURCE... DEST\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n-v	Verbose\n-T	DEST always treated as file, max 2 arguments\n-s	Symlink instead of copy\n-r	Synonym for -R\n-R	Recurse into subdirectories (DEST must be a directory)\n-p	Preserve timestamps, ownership, and mode\n-P	Do not follow symlinks [default]\n-n	No clobber (don't overwrite DEST)\n-l	Hard link instead of copy\n-L	Follow all symlinks\n-i	Interactive, prompt before overwriting existing DEST\n-H	Follow symlinks listed on command line\n-f	Delete destination files we can't write to\n-F	Delete any existing destination file first (--remove-destination)\n-d	Don't dereference symlinks\n-D	Create leading dirs under DEST (--parents)\n-a	Same as -dpr"
-
-#define HELP_cp "usage: cp [--preserve=motcxa] [-adfHiLlnPpRrsTv] SOURCE... DEST\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n-v	Verbose\n-T	DEST always treated as file, max 2 arguments\n-s	Symlink instead of copy\n-r	Synonym for -R\n-R	Recurse into subdirectories (DEST must be a directory)\n-p	Preserve timestamps, ownership, and mode\n-P	Do not follow symlinks [default]\n-n	No clobber (don't overwrite DEST)\n-l	Hard link instead of copy\n-L	Follow all symlinks\n-i	Interactive, prompt before overwriting existing DEST\n-H	Follow symlinks listed on command line\n-f	Delete destination files we can't write to\n-F	Delete any existing destination file first (--remove-destination)\n-d	Don't dereference symlinks\n-D	Create leading dirs under DEST (--parents)\n-a	Same as -dpr\n--preserve takes either a comma separated list of attributes, or the first\nletter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above"
+#define HELP_cp "usage: cp [-adfHiLlnPpRrsTv] [--preserve=motcxa] [-t TARGET] SOURCE... [DEST]\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n\n-a	Same as -dpr\n-D	Create leading dirs under DEST (--parents)\n-d	Don't dereference symlinks\n-F	Delete any existing destination file first (--remove-destination)\n-f	Delete destination files we can't write to\n-H	Follow symlinks listed on command line\n-i	Interactive, prompt before overwriting existing DEST\n-L	Follow all symlinks\n-l	Hard link instead of copy\n-n	No clobber (don't overwrite DEST)\n-u	Update (keep newest mtime)\n-P	Do not follow symlinks\n-p	Preserve timestamps, ownership, and mode\n-R	Recurse into subdirectories (DEST must be a directory)\n-r	Synonym for -R\n-s	Symlink instead of copy\n-t	Copy to TARGET dir (no DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose\n\nArguments to --preserve are the first letter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above"
 
 #define HELP_comm "usage: comm [-123] FILE1 FILE2\n\nRead FILE1 and FILE2, which should be ordered, and produce three text\ncolumns as output: lines only in FILE1; lines only in FILE2; and lines\nin both files. Filename \"-\" is a synonym for stdin.\n\n-1	Suppress the output column of lines unique to FILE1\n-2	Suppress the output column of lines unique to FILE2\n-3	Suppress the output column of lines duplicated in FILE1 and FILE2"
 
@@ -598,7 +630,7 @@
 
 #define HELP_cksum "usage: cksum [-IPLN] [FILE...]\n\nFor each file, output crc32 checksum value, length and name of file.\nIf no files listed, copy from stdin.  Filename \"-\" is a synonym for stdin.\n\n-H	Hexadecimal checksum (defaults to decimal)\n-L	Little endian (defaults to big endian)\n-P	Pre-inversion\n-I	Skip post-inversion\n-N	Do not include length in CRC calculation (or output)"
 
-#define HELP_chmod "usage: chmod [-R] MODE FILE...\n\nChange mode of listed file[s] (recursively with -R).\n\nMODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo]\n\nStanzas are applied in order: For each category (u = user,\ng = group, o = other, a = all three, if none specified default is a),\nset (+), clear (-), or copy (=), r = read, w = write, x = execute.\ns = u+s = suid, g+s = sgid, o+s = sticky. (+t is an alias for o+s).\nsuid/sgid: execute as the user/group who owns the file.\nsticky: can't delete files you don't own out of this directory\nX = x for directories or if any category already has x set.\n\nOr MODE can be an octal value up to 7777	ug uuugggooo	top +\nbit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1	sstrwxrwxrwx	bottom\n\nExamples:\nchmod u+w file - allow owner of \"file\" to write to it.\nchmod 744 file - user can read/write/execute, everyone else read only"
+#define HELP_chmod "usage: chmod [-R] MODE FILE...\n\nChange mode of listed file[s] (recursively with -R).\n\nMODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo]\n\nStanzas are applied in order: For each category (u = user,\ng = group, o = other, a = all three, if none specified default is a),\nset (+), clear (-), or copy (=), r = read, w = write, x = execute.\ns = u+s = suid, g+s = sgid, +t = sticky. (o+s ignored so a+s doesn't set +t)\nsuid/sgid: execute as the user/group who owns the file.\nsticky: can't delete files you don't own out of this directory\nX = x for directories or if any category already has x set.\n\nOr MODE can be an octal value up to 7777	ug uuugggooo	top +\nbit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1	sstrwxrwxrwx	bottom\n\nExamples:\nchmod u+w file - allow owner of \"file\" to write to it.\nchmod 744 file - user can read/write/execute, everyone else read only"
 
 #define HELP_chown "see: chgrp"
 
diff --git a/android/linux/generated/newtoys.h b/android/linux/generated/newtoys.h
index 6b93bea..40ac3d4 100644
--- a/android/linux/generated/newtoys.h
+++ b/android/linux/generated/newtoys.h
@@ -2,8 +2,9 @@
 USE_SH(OLDTOY(-bash, sh, 0))
 USE_SH(OLDTOY(-sh, sh, 0))
 USE_SH(OLDTOY(-toysh, sh, 0))
-USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
+USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
 USE_ACPI(NEWTOY(acpi, "abctV", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GROUPADD(OLDTOY(addgroup, groupadd, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USERADD(OLDTOY(adduser, useradd, TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
@@ -11,10 +12,12 @@
 USE_ARP(NEWTOY(arp, "vi:nDsdap:A:H:[+Ap][!sd]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_ARPING(NEWTOY(arping, "<1>1s:I:w#<0c#<0AUDbqf[+AU][+Df]", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_ASCII(NEWTOY(ascii, 0, TOYFLAG_USR|TOYFLAG_BIN))
+USE_BASE32(NEWTOY(base32, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_BASE64(NEWTOY(base64, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_BASENAME(NEWTOY(basename, "^<1as:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
 USE_BC(NEWTOY(bc, "i(interactive)l(mathlib)q(quiet)s(standard)w(warn)", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_BLKDISCARD(NEWTOY(blkdiscard, "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]", TOYFLAG_BIN))
 USE_BLKID(NEWTOY(blkid, "ULs*[!LU]", TOYFLAG_BIN))
 USE_BLOCKDEV(NEWTOY(blockdev, "<1>1(setro)(setrw)(getro)(getss)(getbsz)(setbsz)#<0(getsz)(getsize)(getsize64)(getra)(setra)#<0(flushbufs)(rereadpt)",TOYFLAG_SBIN))
 USE_BOOTCHARTD(NEWTOY(bootchartd, 0, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN))
@@ -27,34 +30,35 @@
 USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK))
 USE_CHATTR(NEWTOY(chattr, "?p#v#R", TOYFLAG_BIN))
 USE_CHCON(NEWTOY(chcon, "<2hvR", TOYFLAG_USR|TOYFLAG_BIN))
-USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv[-HLP]", TOYFLAG_BIN))
-USE_CHMOD(NEWTOY(chmod, "<2?vRf[-vf]", TOYFLAG_BIN))
+USE_CHGRP(NEWTOY(chgrp, "<2h(no-dereference)PLHRfv[-HLP]", TOYFLAG_BIN))
+USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
 USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
 USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
 USE_CHRT(NEWTOY(chrt, "^mp#<0iRbrfo[!ibrfo]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_CKSUM(NEWTOY(cksum, "HIPLN", TOYFLAG_BIN))
 USE_CLEAR(NEWTOY(clear, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_CMP(NEWTOY(cmp, "<1>2ls(silent)(quiet)[!ls]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 USE_COMM(NEWTOY(comm, "<2>2321", TOYFLAG_USR|TOYFLAG_BIN))
 USE_COUNT(NEWTOY(count, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]", TOYFLAG_BIN))
-USE_CPIO(NEWTOY(cpio, "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
+USE_CP(NEWTOY(cp, "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]", TOYFLAG_BIN))
+USE_CPIO(NEWTOY(cpio, "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
 USE_CRC32(NEWTOY(crc32, 0, TOYFLAG_BIN))
 USE_CROND(NEWTOY(crond, "fbSl#<0=8d#<0L:c:[-bf][-LS][-ld]", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
 USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_CUT(NEWTOY(cut, "b*|c*|f*|F*|C*|O(output-delimiter):d:sDn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_DATE(NEWTOY(date, "d:D:r:u[!dr]", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]", TOYFLAG_BIN))
 USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEALLOCVT(NEWTOY(deallocvt, ">1", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_GROUPDEL(OLDTOY(delgroup, groupdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USERDEL(OLDTOY(deluser, userdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_DEMO_MANY_OPTIONS(NEWTOY(demo_many_options, "ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba", TOYFLAG_BIN))
-USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3hdbs", TOYFLAG_BIN))
+USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3M#<0hcdbs", TOYFLAG_BIN))
 USE_DEMO_SCANKEY(NEWTOY(demo_scankey, 0, TOYFLAG_BIN))
 USE_DEMO_UTF8TOWC(NEWTOY(demo_utf8towc, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEVMEM(NEWTOY(devmem, "<1>3", TOYFLAG_USR|TOYFLAG_BIN))
-USE_DF(NEWTOY(df, "HPkhit*a[-HPkh]", TOYFLAG_SBIN))
+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN))
 USE_DHCP(NEWTOY(dhcp, "V:H:F:x*r:O*A#<0=20T#<0=3t#<0=3s:p:i:SBRCaovqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCP6(NEWTOY(dhcp6, "r:A#<0T#<0t#<0s:p:i:SRvqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCPD(NEWTOY(dhcpd, ">1P#<0>65535fi:S46[!46]", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
@@ -63,20 +67,23 @@
 USE_DMESG(NEWTOY(dmesg, "w(follow)CSTtrs#<1n#c[!Ttr][!Cc][!Sw]", TOYFLAG_BIN))
 USE_DNSDOMAINNAME(NEWTOY(dnsdomainname, ">0", TOYFLAG_BIN))
 USE_DOS2UNIX(NEWTOY(dos2unix, 0, TOYFLAG_BIN))
-USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_DUMPLEASES(NEWTOY(dumpleases, ">0arf:[!ar]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
-USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_LINEBUF))
+USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_EJECT(NEWTOY(eject, ">1stT[!tT]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_ENV(NEWTOY(env, "^0iu*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_ENV(NEWTOY(env, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
 USE_EXPAND(NEWTOY(expand, "t*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
 USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_FACTOR(NEWTOY(factor, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_FALLOCATE(NEWTOY(fallocate, ">1l#|o#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FALSE(NEWTOY(false, NULL, TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
 USE_FDISK(NEWTOY(fdisk, "C#<0H#<0S#<0b#<512ul", TOYFLAG_SBIN))
-USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_FILE(NEWTOY(file, "<1bhLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FIND(NEWTOY(find, "?^HL[-HL]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FLOCK(NEWTOY(flock, "<1>1nsux[-sux]", TOYFLAG_USR|TOYFLAG_BIN))
@@ -94,8 +101,8 @@
 USE_GETENFORCE(NEWTOY(getenforce, ">0", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_GETFATTR(NEWTOY(getfattr, "(only-values)dhn:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GETOPT(NEWTOY(getopt, "^a(alternative)n:(name)o:(options)l*(long)(longoptions)Tu", TOYFLAG_USR|TOYFLAG_BIN))
-USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN))
-USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh", TOYFLAG_SBIN))
+USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_GROUPADD(NEWTOY(groupadd, "<1>2g#<0S", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_GROUPDEL(NEWTOY(groupdel, "<1>2", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_GROUPS(NEWTOY(groups, NULL, TOYFLAG_USR|TOYFLAG_BIN))
@@ -120,7 +127,7 @@
 USE_INIT(NEWTOY(init, "", TOYFLAG_SBIN))
 USE_INOTIFYD(NEWTOY(inotifyd, "<2", TOYFLAG_USR|TOYFLAG_BIN))
 USE_INSMOD(NEWTOY(insmod, "<1", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_INSTALL(NEWTOY(install, "<1cdDpsvt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IONICE(NEWTOY(ionice, "^tc#<0>3=2n#<0>7=5p#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IORENICE(NEWTOY(iorenice, "?<1>3", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IOTOP(NEWTOY(iotop, ">0AaKO" "Hk*o*p*u*s#<1=7d%<100=3000m#n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
@@ -132,6 +139,7 @@
 USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_KILL(NEWTOY(kill, "?ls: ", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_KILLALL(NEWTOY(killall, "?s:ilqvw", TOYFLAG_USR|TOYFLAG_BIN))
 USE_KILLALL5(NEWTOY(killall5, "?o*ls: [!lo][!ls]", TOYFLAG_SBIN))
@@ -141,7 +149,7 @@
 USE_LN(NEWTOY(ln, "<1rt:Tvnfs", TOYFLAG_BIN))
 USE_LOAD_POLICY(NEWTOY(load_policy, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_LOG(NEWTOY(log, "<1p:t:", TOYFLAG_USR|TOYFLAG_SBIN))
-USE_LOGGER(NEWTOY(logger, "st:p:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_LOGGER(NEWTOY(logger, "t:p:s", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LOGIN(NEWTOY(login, ">1f:ph:", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_LOGNAME(NEWTOY(logname, ">0", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LOGWRAPPER(NEWTOY(logwrapper, 0, TOYFLAG_NOHELP|TOYFLAG_USR|TOYFLAG_BIN))
@@ -157,7 +165,7 @@
 USE_MCOOKIE(NEWTOY(mcookie, "v(verbose)V(version)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MD5SUM(NEWTOY(md5sum, "bc(check)s(status)[!bc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MDEV(NEWTOY(mdev, "s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_UMASK))
-USE_MICROCOM(NEWTOY(microcom, "<1>1s:X", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MICROCOM(NEWTOY(microcom, "<1>1s#=115200X", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MIX(NEWTOY(mix, "c:d:l#r#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MKDIR(NEWTOY(mkdir, "<1"USE_MKDIR_Z("Z:")"vp(parent)(parents)m:", TOYFLAG_BIN|TOYFLAG_UMASK))
 USE_MKE2FS(NEWTOY(mke2fs, "<1>2g:Fnqm#N#i#b#", TOYFLAG_SBIN))
@@ -171,11 +179,11 @@
 USE_MORE(NEWTOY(more, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_MOUNT(NEWTOY(mount, "?O:afnrvwt:o*[-rw]", TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_MOUNTPOINT(NEWTOY(mountpoint, "<1qdx[-dx]", TOYFLAG_BIN))
-USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fiT[-ni]", TOYFLAG_BIN))
+USE_MV(NEWTOY(mv, "<1vnF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
 USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, TOYFLAG_USR|TOYFLAG_BIN))
 USE_NBD_CLIENT(NEWTOY(nbd_client, "<3>3ns", 0))
 USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN))
-USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
+USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tElL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
 USE_NETSTAT(NEWTOY(netstat, "pWrxwutneal", TOYFLAG_BIN))
 USE_NICE(NEWTOY(nice, "^<1n#", TOYFLAG_BIN))
 USE_NL(NEWTOY(nl, "v#=1l#w#<0=6Eb:n:s:", TOYFLAG_USR|TOYFLAG_BIN))
@@ -191,20 +199,21 @@
 USE_PATCH(NEWTOY(patch, ">2(no-backup-if-mismatch)(dry-run)"USE_TOYBOX_DEBUG("x")"F#g#fulp#d:i:Rs(quiet)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIDOF(NEWTOY(pidof, "<1so:x", TOYFLAG_BIN))
-USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIVOT_ROOT(NEWTOY(pivot_root, "<2>2", TOYFLAG_SBIN))
 USE_PKILL(NEWTOY(pkill,    "?Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PMAP(NEWTOY(pmap, "<1xq", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(OLDTOY(poweroff, reboot, TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_PRINTENV(NEWTOY(printenv, "0(null)", TOYFLAG_BIN))
+USE_PRINTENV(NEWTOY(printenv, "(null)0", TOYFLAG_BIN))
 USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_ULIMIT(OLDTOY(prlimit, ulimit, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*Tu*U*g*G*wZ[!ol][+Ae][!oO]", TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_PWD(NEWTOY(pwd, ">0LP[-LP]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_PWDX(NEWTOY(pwdx, "<1a", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PWGEN(NEWTOY(pwgen, ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READAHEAD(NEWTOY(readahead, NULL, TOYFLAG_BIN))
-USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adhlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REALPATH(NEWTOY(realpath, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(NEWTOY(reboot, "fn", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
@@ -214,28 +223,33 @@
 USE_REV(NEWTOY(rev, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_RFKILL(NEWTOY(rfkill, "<1>2", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
-USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p", TOYFLAG_BIN))
+USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p(parents)", TOYFLAG_BIN))
 USE_RMMOD(NEWTOY(rmmod, "<1wf", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN))
+USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN))
+USE_RTCWAKE(NEWTOY(rtcwake, "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_RUNCON(NEWTOY(runcon, "<2", TOYFLAG_USR|TOYFLAG_SBIN))
-USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
+USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
 USE_SENDEVENT(NEWTOY(sendevent, "<4>4", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_SEQ(NEWTOY(seq, "<1>3?f:s:w[!fw]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
 USE_SETENFORCE(NEWTOY(setenforce, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_SETFATTR(NEWTOY(setfattr, "hn:|v:x:|[!xv]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SETSID(NEWTOY(setsid, "^<1wcd[!dc]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
+USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
 USE_SHA1SUM(NEWTOY(sha1sum, "bc(check)s(status)[!bc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TOYBOX_LIBCRYPTO(USE_SHA224SUM(OLDTOY(sha224sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
 USE_TOYBOX_LIBCRYPTO(USE_SHA256SUM(OLDTOY(sha256sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
 USE_TOYBOX_LIBCRYPTO(USE_SHA384SUM(OLDTOY(sha384sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
+USE_SHA3SUM(NEWTOY(sha3sum, "bSa#<128>512=224", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TOYBOX_LIBCRYPTO(USE_SHA512SUM(OLDTOY(sha512sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
+USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
 USE_SHRED(NEWTOY(shred, "<1zxus#<1n#<1o#<0f", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SKELETON(NEWTOY(skeleton, "(walrus)(blubber):;(also):e@d*c#b:a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SKELETON_ALIAS(NEWTOY(skeleton_alias, "b#dq", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN))
 USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
 USE_SPLIT(NEWTOY(split, ">2a#<1=2>9b#<1l#<1[!bl]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_STAT(NEWTOY(stat, "<1c:(format)fLt", TOYFLAG_BIN))
 USE_STRINGS(NEWTOY(strings, "t:an#=4<1fo", TOYFLAG_USR|TOYFLAG_BIN))
@@ -250,7 +264,7 @@
 USE_SYSLOGD(NEWTOY(syslogd,">0l#<1>8=8R:b#<0>99=1s#<0=200m#<0>71582787=20O:p:f:a:nSKLD", TOYFLAG_SBIN|TOYFLAG_STAYROOT))
 USE_TAC(NEWTOY(tac, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TASKSET(NEWTOY(taskset, "<1^pa", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TEE(NEWTOY(tee, "ia", TOYFLAG_USR|TOYFLAG_BIN))
@@ -275,9 +289,11 @@
 USE_ULIMIT(NEWTOY(ulimit, ">1P#<1SHavutsrRqpnmlifedc[-SH][!apvutsrRqnmlifedc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UMOUNT(NEWTOY(umount, "cndDflrat*v[!na]", TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_UNAME(NEWTOY(uname, "oamvrns[+os]", TOYFLAG_BIN))
+USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN))
 USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK))
 USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UPTIME(NEWTOY(uptime, ">0ps", TOYFLAG_USR|TOYFLAG_BIN))
 USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
@@ -290,14 +306,16 @@
 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN))
 USE_W(NEWTOY(w, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
 USE_WC(NEWTOY(wc, "mcwl", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_WGET(NEWTOY(wget, "(no-check-certificate)O:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHICH(NEWTOY(which, "<1a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHO(NEWTOY(who, "a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHOAMI(OLDTOY(whoami, logname, TOYFLAG_USR|TOYFLAG_BIN))
-USE_XARGS(NEWTOY(xargs, "^E:P#optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_XXD(NEWTOY(xxd, ">1c#l#o#g#<1=2iprs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_XZCAT(NEWTOY(xzcat, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
 USE_ZCAT(NEWTOY(zcat,     "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/android/mac/generated/config.h b/android/mac/generated/config.h
index 07b3de5..a3c54d8 100644
--- a/android/mac/generated/config.h
+++ b/android/mac/generated/config.h
@@ -64,12 +64,16 @@
 #define USE_ARP(...)
 #define CFG_ASCII 0
 #define USE_ASCII(...)
+#define CFG_BASE32 0
+#define USE_BASE32(...)
 #define CFG_BASE64 0
 #define USE_BASE64(...)
 #define CFG_BASENAME 1
 #define USE_BASENAME(...) __VA_ARGS__
 #define CFG_BC 0
 #define USE_BC(...)
+#define CFG_BLKDISCARD 0
+#define USE_BLKDISCARD(...)
 #define CFG_BLKID 0
 #define USE_BLKID(...)
 #define CFG_BLOCKDEV 0
@@ -106,6 +110,8 @@
 #define USE_CHROOT(...)
 #define CFG_CHRT 0
 #define USE_CHRT(...)
+#define CFG_CHSH 0
+#define USE_CHSH(...)
 #define CFG_CHVT 0
 #define USE_CHVT(...)
 #define CFG_CKSUM 0
@@ -118,8 +124,8 @@
 #define USE_COMM(...) __VA_ARGS__
 #define CFG_COUNT 0
 #define USE_COUNT(...)
-#define CFG_CPIO 0
-#define USE_CPIO(...)
+#define CFG_CPIO 1
+#define USE_CPIO(...) __VA_ARGS__
 #define CFG_CP_PRESERVE 1
 #define USE_CP_PRESERVE(...) __VA_ARGS__
 #define CFG_CP 1
@@ -194,8 +200,8 @@
 #define USE_FALSE(...)
 #define CFG_FDISK 0
 #define USE_FDISK(...)
-#define CFG_FGREP 0
-#define USE_FGREP(...)
+#define CFG_FGREP 1
+#define USE_FGREP(...) __VA_ARGS__
 #define CFG_FILE 0
 #define USE_FILE(...)
 #define CFG_FIND 1
@@ -408,8 +414,8 @@
 #define USE_NETSTAT(...)
 #define CFG_NICE 0
 #define USE_NICE(...)
-#define CFG_NL 0
-#define USE_NL(...)
+#define CFG_NL 1
+#define USE_NL(...) __VA_ARGS__
 #define CFG_NOHUP 0
 #define USE_NOHUP(...)
 #define CFG_NPROC 0
@@ -446,14 +452,16 @@
 #define USE_PMAP(...)
 #define CFG_PRINTENV 0
 #define USE_PRINTENV(...)
-#define CFG_PRINTF 0
-#define USE_PRINTF(...)
+#define CFG_PRINTF 1
+#define USE_PRINTF(...) __VA_ARGS__
 #define CFG_PS 0
 #define USE_PS(...)
 #define CFG_PWDX 0
 #define USE_PWDX(...)
 #define CFG_PWD 1
 #define USE_PWD(...) __VA_ARGS__
+#define CFG_PWGEN 0
+#define USE_PWGEN(...)
 #define CFG_READAHEAD 0
 #define USE_READAHEAD(...)
 #define CFG_READELF 0
@@ -482,6 +490,8 @@
 #define USE_RM(...) __VA_ARGS__
 #define CFG_ROUTE 0
 #define USE_ROUTE(...)
+#define CFG_RTCWAKE 0
+#define USE_RTCWAKE(...)
 #define CFG_RUNCON 0
 #define USE_RUNCON(...)
 #define CFG_SED 1
@@ -502,6 +512,8 @@
 #define USE_SHA224SUM(...)
 #define CFG_SHA256SUM 1
 #define USE_SHA256SUM(...) __VA_ARGS__
+#define CFG_SHA3SUM 0
+#define USE_SHA3SUM(...)
 #define CFG_SHA384SUM 0
 #define USE_SHA384SUM(...)
 #define CFG_SHA512SUM 1
@@ -564,6 +576,8 @@
 #define USE_TELNET(...)
 #define CFG_TEST 1
 #define USE_TEST(...) __VA_ARGS__
+#define CFG_TEST_GLUE 1
+#define USE_TEST_GLUE(...) __VA_ARGS__
 #define CFG_TFTPD 0
 #define USE_TFTPD(...)
 #define CFG_TFTP 0
@@ -594,6 +608,8 @@
 #define USE_UMOUNT(...)
 #define CFG_UNAME 1
 #define USE_UNAME(...) __VA_ARGS__
+#define CFG_UNICODE 0
+#define USE_UNICODE(...)
 #define CFG_UNIQ 1
 #define USE_UNIQ(...) __VA_ARGS__
 #define CFG_UNIX2DOS 1
@@ -624,6 +640,8 @@
 #define USE_VMSTAT(...)
 #define CFG_WATCH 0
 #define USE_WATCH(...)
+#define CFG_WATCHDOG 0
+#define USE_WATCHDOG(...)
 #define CFG_WC 1
 #define USE_WC(...) __VA_ARGS__
 #define CFG_WGET 0
diff --git a/android/mac/generated/flags.h b/android/mac/generated/flags.h
index 7b026e6..5f4854d 100644
--- a/android/mac/generated/flags.h
+++ b/android/mac/generated/flags.h
@@ -73,6 +73,17 @@
 #undef FOR_ascii
 #endif
 
+// base32   diw#<0=76[!dw]
+#undef OPTSTR_base32
+#define OPTSTR_base32 "diw#<0=76[!dw]"
+#ifdef CLEANUP_base32
+#undef CLEANUP_base32
+#undef FOR_base32
+#undef FLAG_w
+#undef FLAG_i
+#undef FLAG_d
+#endif
+
 // base64   diw#<0=76[!dw]
 #undef OPTSTR_base64
 #define OPTSTR_base64 "diw#<0=76[!dw]"
@@ -107,6 +118,19 @@
 #undef FLAG_i
 #endif
 
+// blkdiscard   <1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]
+#undef OPTSTR_blkdiscard
+#define OPTSTR_blkdiscard "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]"
+#ifdef CLEANUP_blkdiscard
+#undef CLEANUP_blkdiscard
+#undef FOR_blkdiscard
+#undef FLAG_z
+#undef FLAG_s
+#undef FLAG_o
+#undef FLAG_l
+#undef FLAG_f
+#endif
+
 // blkid   ULs*[!LU]
 #undef OPTSTR_blkid
 #define OPTSTR_blkid "ULs*[!LU]"
@@ -240,9 +264,9 @@
 #undef FLAG_h
 #endif
 
-// chgrp   <2hPLHRfv[-HLP]
+// chgrp   <2h(no-dereference)PLHRfv[-HLP]
 #undef OPTSTR_chgrp
-#define OPTSTR_chgrp "<2hPLHRfv[-HLP]"
+#define OPTSTR_chgrp "<2h(no-dereference)PLHRfv[-HLP]"
 #ifdef CLEANUP_chgrp
 #undef CLEANUP_chgrp
 #undef FOR_chgrp
@@ -255,14 +279,14 @@
 #undef FLAG_h
 #endif
 
-// chmod <2?vRf[-vf] <2?vRf[-vf]
+// chmod <2?vfR[-vf] <2?vfR[-vf]
 #undef OPTSTR_chmod
-#define OPTSTR_chmod "<2?vRf[-vf]"
+#define OPTSTR_chmod "<2?vfR[-vf]"
 #ifdef CLEANUP_chmod
 #undef CLEANUP_chmod
 #undef FOR_chmod
-#undef FLAG_f
 #undef FLAG_R
+#undef FLAG_f
 #undef FLAG_v
 #endif
 
@@ -290,6 +314,15 @@
 #undef FLAG_m
 #endif
 
+// chsh   s:
+#undef OPTSTR_chsh
+#define OPTSTR_chsh "s:"
+#ifdef CLEANUP_chsh
+#undef CLEANUP_chsh
+#undef FOR_chsh
+#undef FLAG_s
+#endif
+
 // chvt   <1
 #undef OPTSTR_chvt
 #define OPTSTR_chvt "<1"
@@ -348,13 +381,14 @@
 #undef FOR_count
 #endif
 
-// cp <2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni] <2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]
+// cp <1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu] <1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]
 #undef OPTSTR_cp
-#define OPTSTR_cp "<2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]"
+#define OPTSTR_cp "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]"
 #ifdef CLEANUP_cp
 #undef CLEANUP_cp
 #undef FOR_cp
 #undef FLAG_T
+#undef FLAG_t
 #undef FLAG_i
 #undef FLAG_f
 #undef FLAG_F
@@ -364,6 +398,7 @@
 #undef FLAG_s
 #undef FLAG_a
 #undef FLAG_d
+#undef FLAG_u
 #undef FLAG_r
 #undef FLAG_p
 #undef FLAG_P
@@ -374,9 +409,9 @@
 #undef FLAG_preserve
 #endif
 
-// cpio   (no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]
+// cpio (quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF] (quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]
 #undef OPTSTR_cpio
-#define OPTSTR_cpio "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]"
+#define OPTSTR_cpio "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]"
 #ifdef CLEANUP_cpio
 #undef CLEANUP_cpio
 #undef FOR_cpio
@@ -390,8 +425,8 @@
 #undef FLAG_u
 #undef FLAG_d
 #undef FLAG_m
-#undef FLAG_trailer
 #undef FLAG_no_preserve_owner
+#undef FLAG_quiet
 #endif
 
 // crc32    
@@ -448,14 +483,15 @@
 #undef FLAG_b
 #endif
 
-// date d:D:r:u[!dr] d:D:r:u[!dr]
+// date d:D:I(iso)(iso-8601):;r:u(utc)[!dr] d:D:I(iso)(iso-8601):;r:u(utc)[!dr]
 #undef OPTSTR_date
-#define OPTSTR_date "d:D:r:u[!dr]"
+#define OPTSTR_date "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]"
 #ifdef CLEANUP_date
 #undef CLEANUP_date
 #undef FOR_date
 #undef FLAG_u
 #undef FLAG_r
+#undef FLAG_I
 #undef FLAG_D
 #undef FLAG_d
 #endif
@@ -536,16 +572,18 @@
 #undef FLAG_Z
 #endif
 
-// demo_number   D#=3<3hdbs
+// demo_number   D#=3<3M#<0hcdbs
 #undef OPTSTR_demo_number
-#define OPTSTR_demo_number "D#=3<3hdbs"
+#define OPTSTR_demo_number "D#=3<3M#<0hcdbs"
 #ifdef CLEANUP_demo_number
 #undef CLEANUP_demo_number
 #undef FOR_demo_number
 #undef FLAG_s
 #undef FLAG_b
 #undef FLAG_d
+#undef FLAG_c
 #undef FLAG_h
+#undef FLAG_M
 #undef FLAG_D
 #endif
 
@@ -573,9 +611,9 @@
 #undef FOR_devmem
 #endif
 
-// df   HPkhit*a[-HPkh]
+// df   HPkhit*a[-HPh]
 #undef OPTSTR_df
-#define OPTSTR_df "HPkhit*a[-HPkh]"
+#define OPTSTR_df "HPkhit*a[-HPh]"
 #ifdef CLEANUP_df
 #undef CLEANUP_df
 #undef FOR_df
@@ -722,12 +760,13 @@
 #undef FOR_dos2unix
 #endif
 
-// du d#<0=-1hmlcaHkKLsx[-HL][-kKmh] d#<0=-1hmlcaHkKLsx[-HL][-kKmh]
+// du d#<0=-1hmlcaHkKLsxb[-HL][-kKmh] d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]
 #undef OPTSTR_du
-#define OPTSTR_du "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]"
+#define OPTSTR_du "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]"
 #ifdef CLEANUP_du
 #undef CLEANUP_du
 #undef FOR_du
+#undef FLAG_b
 #undef FLAG_x
 #undef FLAG_s
 #undef FLAG_L
@@ -775,15 +814,34 @@
 #undef FLAG_s
 #endif
 
-// env ^0iu* ^0iu*
+// env ^i0u* ^i0u*
 #undef OPTSTR_env
-#define OPTSTR_env "^0iu*"
+#define OPTSTR_env "^i0u*"
 #ifdef CLEANUP_env
 #undef CLEANUP_env
 #undef FOR_env
 #undef FLAG_u
-#undef FLAG_i
 #undef FLAG_0
+#undef FLAG_i
+#endif
+
+// eval    
+#undef OPTSTR_eval
+#define OPTSTR_eval 0
+#ifdef CLEANUP_eval
+#undef CLEANUP_eval
+#undef FOR_eval
+#endif
+
+// exec   ^cla:
+#undef OPTSTR_exec
+#define OPTSTR_exec "^cla:"
+#ifdef CLEANUP_exec
+#undef CLEANUP_exec
+#undef FOR_exec
+#undef FLAG_a
+#undef FLAG_l
+#undef FLAG_c
 #endif
 
 // exit    
@@ -803,6 +861,16 @@
 #undef FLAG_t
 #endif
 
+// export   np
+#undef OPTSTR_export
+#define OPTSTR_export "np"
+#ifdef CLEANUP_export
+#undef CLEANUP_export
+#undef FOR_export
+#undef FLAG_p
+#undef FLAG_n
+#endif
+
 // expr    
 #undef OPTSTR_expr
 #define OPTSTR_expr 0
@@ -1355,15 +1423,16 @@
 #undef FOR_insmod
 #endif
 
-// install   <1cdDpsvm:o:g:
+// install   <1cdDpsvt:m:o:g:
 #undef OPTSTR_install
-#define OPTSTR_install "<1cdDpsvm:o:g:"
+#define OPTSTR_install "<1cdDpsvt:m:o:g:"
 #ifdef CLEANUP_install
 #undef CLEANUP_install
 #undef FOR_install
 #undef FLAG_g
 #undef FLAG_o
 #undef FLAG_m
+#undef FLAG_t
 #undef FLAG_v
 #undef FLAG_s
 #undef FLAG_p
@@ -1455,6 +1524,19 @@
 #undef FLAG_a
 #endif
 
+// jobs   lnprs
+#undef OPTSTR_jobs
+#define OPTSTR_jobs "lnprs"
+#ifdef CLEANUP_jobs
+#undef CLEANUP_jobs
+#undef FOR_jobs
+#undef FLAG_s
+#undef FLAG_r
+#undef FLAG_p
+#undef FLAG_n
+#undef FLAG_l
+#endif
+
 // kill   ?ls: 
 #undef OPTSTR_kill
 #define OPTSTR_kill "?ls: "
@@ -1551,15 +1633,15 @@
 #undef FLAG_p
 #endif
 
-// logger   st:p:
+// logger   t:p:s
 #undef OPTSTR_logger
-#define OPTSTR_logger "st:p:"
+#define OPTSTR_logger "t:p:s"
 #ifdef CLEANUP_logger
 #undef CLEANUP_logger
 #undef FOR_logger
+#undef FLAG_s
 #undef FLAG_p
 #undef FLAG_t
-#undef FLAG_s
 #endif
 
 // login   >1f:ph:
@@ -1751,9 +1833,9 @@
 #undef FLAG_s
 #endif
 
-// microcom <1>1s:X <1>1s:X
+// microcom <1>1s#=115200X <1>1s#=115200X
 #undef OPTSTR_microcom
-#define OPTSTR_microcom "<1>1s:X"
+#define OPTSTR_microcom "<1>1s#=115200X"
 #ifdef CLEANUP_microcom
 #undef CLEANUP_microcom
 #undef FOR_microcom
@@ -1920,13 +2002,14 @@
 #undef FLAG_q
 #endif
 
-// mv <2vnF(remove-destination)fiT[-ni] <2vnF(remove-destination)fiT[-ni]
+// mv <1vnF(remove-destination)fit:T[-ni] <1vnF(remove-destination)fit:T[-ni]
 #undef OPTSTR_mv
-#define OPTSTR_mv "<2vnF(remove-destination)fiT[-ni]"
+#define OPTSTR_mv "<1vnF(remove-destination)fit:T[-ni]"
 #ifdef CLEANUP_mv
 #undef CLEANUP_mv
 #undef FOR_mv
 #undef FLAG_T
+#undef FLAG_t
 #undef FLAG_i
 #undef FLAG_f
 #undef FLAG_F
@@ -1944,9 +2027,9 @@
 #undef FLAG_n
 #endif
 
-// netcat   ^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]
+// netcat   ^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]
 #undef OPTSTR_netcat
-#define OPTSTR_netcat "^tlLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]"
+#define OPTSTR_netcat "^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]"
 #ifdef CLEANUP_netcat
 #undef CLEANUP_netcat
 #undef FOR_netcat
@@ -1962,6 +2045,7 @@
 #undef FLAG_w
 #undef FLAG_L
 #undef FLAG_l
+#undef FLAG_E
 #undef FLAG_t
 #endif
 
@@ -1993,7 +2077,7 @@
 #undef FLAG_n
 #endif
 
-// nl   v#=1l#w#<0=6Eb:n:s:
+// nl v#=1l#w#<0=6Eb:n:s: v#=1l#w#<0=6Eb:n:s:
 #undef OPTSTR_nl
 #define OPTSTR_nl "v#=1l#w#<0=6Eb:n:s:"
 #ifdef CLEANUP_nl
@@ -2170,9 +2254,9 @@
 #undef FLAG_s
 #endif
 
-// ping   <1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]
+// ping   <1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]
 #undef OPTSTR_ping
-#define OPTSTR_ping "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]"
+#define OPTSTR_ping "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]"
 #ifdef CLEANUP_ping
 #undef CLEANUP_ping
 #undef FOR_ping
@@ -2230,16 +2314,17 @@
 #undef FLAG_x
 #endif
 
-// printenv   0(null)
+// printenv   (null)0
 #undef OPTSTR_printenv
-#define OPTSTR_printenv "0(null)"
+#define OPTSTR_printenv "(null)0"
 #ifdef CLEANUP_printenv
 #undef CLEANUP_printenv
 #undef FOR_printenv
 #undef FLAG_0
+#undef FLAG_null
 #endif
 
-// printf   <1?^
+// printf <1?^ <1?^
 #undef OPTSTR_printf
 #define OPTSTR_printf "<1?^"
 #ifdef CLEANUP_printf
@@ -2296,6 +2381,26 @@
 #undef FLAG_a
 #endif
 
+// pwgen   >2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]
+#undef OPTSTR_pwgen
+#define OPTSTR_pwgen ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]"
+#ifdef CLEANUP_pwgen
+#undef CLEANUP_pwgen
+#undef FOR_pwgen
+#undef FLAG_0
+#undef FLAG_A
+#undef FLAG_v
+#undef FLAG_1
+#undef FLAG_C
+#undef FLAG_h
+#undef FLAG_B
+#undef FLAG_s
+#undef FLAG_y
+#undef FLAG_n
+#undef FLAG_c
+#undef FLAG_r
+#endif
+
 // readahead    
 #undef OPTSTR_readahead
 #define OPTSTR_readahead 0
@@ -2304,9 +2409,9 @@
 #undef FOR_readahead
 #endif
 
-// readelf   <1(dyn-syms)adhlnp:SsWx:
+// readelf   <1(dyn-syms)adehlnp:SsWx:
 #undef OPTSTR_readelf
-#define OPTSTR_readelf "<1(dyn-syms)adhlnp:SsWx:"
+#define OPTSTR_readelf "<1(dyn-syms)adehlnp:SsWx:"
 #ifdef CLEANUP_readelf
 #undef CLEANUP_readelf
 #undef FOR_readelf
@@ -2318,6 +2423,7 @@
 #undef FLAG_n
 #undef FLAG_l
 #undef FLAG_h
+#undef FLAG_e
 #undef FLAG_d
 #undef FLAG_a
 #undef FLAG_dyn_syms
@@ -2417,9 +2523,9 @@
 #undef FLAG_f
 #endif
 
-// rmdir <1(ignore-fail-on-non-empty)p <1(ignore-fail-on-non-empty)p
+// rmdir <1(ignore-fail-on-non-empty)p(parents) <1(ignore-fail-on-non-empty)p(parents)
 #undef OPTSTR_rmdir
-#define OPTSTR_rmdir "<1(ignore-fail-on-non-empty)p"
+#define OPTSTR_rmdir "<1(ignore-fail-on-non-empty)p(parents)"
 #ifdef CLEANUP_rmdir
 #undef CLEANUP_rmdir
 #undef FOR_rmdir
@@ -2448,6 +2554,24 @@
 #undef FLAG_n
 #endif
 
+// rtcwake   (list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]
+#undef OPTSTR_rtcwake
+#define OPTSTR_rtcwake "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]"
+#ifdef CLEANUP_rtcwake
+#undef CLEANUP_rtcwake
+#undef FOR_rtcwake
+#undef FLAG_v
+#undef FLAG_u
+#undef FLAG_t
+#undef FLAG_s
+#undef FLAG_m
+#undef FLAG_l
+#undef FLAG_d
+#undef FLAG_a
+#undef FLAG_auto
+#undef FLAG_list_modes
+#endif
+
 // runcon   <2
 #undef OPTSTR_runcon
 #define OPTSTR_runcon "<2"
@@ -2456,12 +2580,13 @@
 #undef FOR_runcon
 #endif
 
-// sed (help)(version)e*f*i:;nErz(null-data)[+Er] (help)(version)e*f*i:;nErz(null-data)[+Er]
+// sed (help)(version)e*f*i:;nErz(null-data)s[+Er] (help)(version)e*f*i:;nErz(null-data)s[+Er]
 #undef OPTSTR_sed
-#define OPTSTR_sed "(help)(version)e*f*i:;nErz(null-data)[+Er]"
+#define OPTSTR_sed "(help)(version)e*f*i:;nErz(null-data)s[+Er]"
 #ifdef CLEANUP_sed
 #undef CLEANUP_sed
 #undef FOR_sed
+#undef FLAG_s
 #undef FLAG_z
 #undef FLAG_r
 #undef FLAG_E
@@ -2492,6 +2617,14 @@
 #undef FLAG_f
 #endif
 
+// set    
+#undef OPTSTR_set
+#define OPTSTR_set 0
+#ifdef CLEANUP_set
+#undef CLEANUP_set
+#undef FOR_set
+#endif
+
 // setenforce   <1>1
 #undef OPTSTR_setenforce
 #define OPTSTR_setenforce "<1>1"
@@ -2523,9 +2656,9 @@
 #undef FLAG_w
 #endif
 
-// sh   (noediting)(noprofile)(norc)sc:i
+// sh   0(noediting)(noprofile)(norc)sc:i
 #undef OPTSTR_sh
-#define OPTSTR_sh "(noediting)(noprofile)(norc)sc:i"
+#define OPTSTR_sh "0(noediting)(noprofile)(norc)sc:i"
 #ifdef CLEANUP_sh
 #undef CLEANUP_sh
 #undef FOR_sh
@@ -2548,6 +2681,25 @@
 #undef FLAG_b
 #endif
 
+// sha3sum   bSa#<128>512=224
+#undef OPTSTR_sha3sum
+#define OPTSTR_sha3sum "bSa#<128>512=224"
+#ifdef CLEANUP_sha3sum
+#undef CLEANUP_sha3sum
+#undef FOR_sha3sum
+#undef FLAG_a
+#undef FLAG_S
+#undef FLAG_b
+#endif
+
+// shift   >1
+#undef OPTSTR_shift
+#define OPTSTR_shift ">1"
+#ifdef CLEANUP_shift
+#undef CLEANUP_shift
+#undef FOR_shift
+#endif
+
 // shred   <1zxus#<1n#<1o#<0f
 #undef OPTSTR_shred
 #define OPTSTR_shred "<1zxus#<1n#<1o#<0f"
@@ -2645,6 +2797,14 @@
 #undef FLAG_g
 #endif
 
+// source   <1
+#undef OPTSTR_source
+#define OPTSTR_source "<1"
+#ifdef CLEANUP_source
+#undef CLEANUP_source
+#undef FOR_source
+#endif
+
 // split   >2a#<1=2>9b#<1l#<1[!bl]
 #undef OPTSTR_split
 #define OPTSTR_split ">2a#<1=2>9b#<1l#<1[!bl]"
@@ -2809,9 +2969,9 @@
 #undef FLAG_f
 #endif
 
-// tar &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa] &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]
+// tar &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa] &(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]
 #undef OPTSTR_tar
-#define OPTSTR_tar "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]"
+#define OPTSTR_tar "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]"
 #ifdef CLEANUP_tar
 #undef CLEANUP_tar
 #undef FOR_tar
@@ -2821,11 +2981,13 @@
 #undef FLAG_T
 #undef FLAG_X
 #undef FLAG_m
+#undef FLAG_P
 #undef FLAG_O
 #undef FLAG_S
 #undef FLAG_z
 #undef FLAG_j
 #undef FLAG_J
+#undef FLAG_I
 #undef FLAG_v
 #undef FLAG_t
 #undef FLAG_x
@@ -3149,6 +3311,14 @@
 #undef FLAG_o
 #endif
 
+// unicode   <1
+#undef OPTSTR_unicode
+#define OPTSTR_unicode "<1"
+#ifdef CLEANUP_unicode
+#undef CLEANUP_unicode
+#undef FOR_unicode
+#endif
+
 // uniq f#s#w#zicdu f#s#w#zicdu
 #undef OPTSTR_uniq
 #define OPTSTR_uniq "f#s#w#zicdu"
@@ -3181,6 +3351,17 @@
 #undef FOR_unlink
 #endif
 
+// unset   fvn[!fv]
+#undef OPTSTR_unset
+#define OPTSTR_unset "fvn[!fv]"
+#ifdef CLEANUP_unset
+#undef CLEANUP_unset
+#undef FOR_unset
+#undef FLAG_n
+#undef FLAG_v
+#undef FLAG_f
+#endif
+
 // unshare   <1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);
 #undef OPTSTR_unshare
 #define OPTSTR_unshare "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);"
@@ -3301,6 +3482,15 @@
 #undef FOR_w
 #endif
 
+// wait   n
+#undef OPTSTR_wait
+#define OPTSTR_wait "n"
+#ifdef CLEANUP_wait
+#undef CLEANUP_wait
+#undef FOR_wait
+#undef FLAG_n
+#endif
+
 // watch   ^<1n%<100=2000tebx
 #undef OPTSTR_watch
 #define OPTSTR_watch "^<1n%<100=2000tebx"
@@ -3314,6 +3504,17 @@
 #undef FLAG_n
 #endif
 
+// watchdog   <1>1Ft#=4<1T#=60<1
+#undef OPTSTR_watchdog
+#define OPTSTR_watchdog "<1>1Ft#=4<1T#=60<1"
+#ifdef CLEANUP_watchdog
+#undef CLEANUP_watchdog
+#undef FOR_watchdog
+#undef FLAG_T
+#undef FLAG_t
+#undef FLAG_F
+#endif
+
 // wc mcwl mcwl
 #undef OPTSTR_wc
 #define OPTSTR_wc "mcwl"
@@ -3354,9 +3555,9 @@
 #undef FLAG_a
 #endif
 
-// xargs ^E:P#optrn#<1(max-args)s#0[!0E] ^E:P#optrn#<1(max-args)s#0[!0E]
+// xargs ^E:P#<0=1optrn#<1(max-args)s#0[!0E] ^E:P#<0=1optrn#<1(max-args)s#0[!0E]
 #undef OPTSTR_xargs
-#define OPTSTR_xargs "^E:P#optrn#<1(max-args)s#0[!0E]"
+#define OPTSTR_xargs "^E:P#<0=1optrn#<1(max-args)s#0[!0E]"
 #ifdef CLEANUP_xargs
 #undef CLEANUP_xargs
 #undef FOR_xargs
@@ -3479,6 +3680,15 @@
 #endif
 #endif
 
+#ifdef FOR_base32
+#ifndef TT
+#define TT this.base32
+#endif
+#define FLAG_w (FORCED_FLAG<<0)
+#define FLAG_i (FORCED_FLAG<<1)
+#define FLAG_d (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_base64
 #ifndef TT
 #define TT this.base64
@@ -3507,6 +3717,17 @@
 #define FLAG_i (FORCED_FLAG<<4)
 #endif
 
+#ifdef FOR_blkdiscard
+#ifndef TT
+#define TT this.blkdiscard
+#endif
+#define FLAG_z (FORCED_FLAG<<0)
+#define FLAG_s (FORCED_FLAG<<1)
+#define FLAG_o (FORCED_FLAG<<2)
+#define FLAG_l (FORCED_FLAG<<3)
+#define FLAG_f (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_blkid
 #ifndef TT
 #define TT this.blkid
@@ -3633,8 +3854,8 @@
 #ifndef TT
 #define TT this.chmod
 #endif
-#define FLAG_f (1<<0)
-#define FLAG_R (1<<1)
+#define FLAG_R (1<<0)
+#define FLAG_f (1<<1)
 #define FLAG_v (1<<2)
 #endif
 
@@ -3658,6 +3879,13 @@
 #define FLAG_m (FORCED_FLAG<<7)
 #endif
 
+#ifdef FOR_chsh
+#ifndef TT
+#define TT this.chsh
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_chvt
 #ifndef TT
 #define TT this.chvt
@@ -3709,41 +3937,43 @@
 #define TT this.cp
 #endif
 #define FLAG_T (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_f (1<<2)
-#define FLAG_F (1<<3)
-#define FLAG_n (1<<4)
-#define FLAG_v (1<<5)
-#define FLAG_l (1<<6)
-#define FLAG_s (1<<7)
-#define FLAG_a (1<<8)
-#define FLAG_d (1<<9)
-#define FLAG_r (1<<10)
-#define FLAG_p (1<<11)
-#define FLAG_P (1<<12)
-#define FLAG_L (1<<13)
-#define FLAG_H (1<<14)
-#define FLAG_R (1<<15)
-#define FLAG_D (1<<16)
-#define FLAG_preserve (1<<17)
+#define FLAG_t (1<<1)
+#define FLAG_i (1<<2)
+#define FLAG_f (1<<3)
+#define FLAG_F (1<<4)
+#define FLAG_n (1<<5)
+#define FLAG_v (1<<6)
+#define FLAG_l (1<<7)
+#define FLAG_s (1<<8)
+#define FLAG_a (1<<9)
+#define FLAG_d (1<<10)
+#define FLAG_u (1<<11)
+#define FLAG_r (1<<12)
+#define FLAG_p (1<<13)
+#define FLAG_P (1<<14)
+#define FLAG_L (1<<15)
+#define FLAG_H (1<<16)
+#define FLAG_R (1<<17)
+#define FLAG_D (1<<18)
+#define FLAG_preserve (1<<19)
 #endif
 
 #ifdef FOR_cpio
 #ifndef TT
 #define TT this.cpio
 #endif
-#define FLAG_o (FORCED_FLAG<<0)
-#define FLAG_v (FORCED_FLAG<<1)
-#define FLAG_F (FORCED_FLAG<<2)
-#define FLAG_t (FORCED_FLAG<<3)
-#define FLAG_i (FORCED_FLAG<<4)
-#define FLAG_p (FORCED_FLAG<<5)
-#define FLAG_H (FORCED_FLAG<<6)
-#define FLAG_u (FORCED_FLAG<<7)
-#define FLAG_d (FORCED_FLAG<<8)
-#define FLAG_m (FORCED_FLAG<<9)
-#define FLAG_trailer (FORCED_FLAG<<10)
-#define FLAG_no_preserve_owner (FORCED_FLAG<<11)
+#define FLAG_o (1<<0)
+#define FLAG_v (1<<1)
+#define FLAG_F (1<<2)
+#define FLAG_t (1<<3)
+#define FLAG_i (1<<4)
+#define FLAG_p (1<<5)
+#define FLAG_H (1<<6)
+#define FLAG_u (1<<7)
+#define FLAG_d (1<<8)
+#define FLAG_m (1<<9)
+#define FLAG_no_preserve_owner (1<<10)
+#define FLAG_quiet (1<<11)
 #endif
 
 #ifdef FOR_crc32
@@ -3798,8 +4028,9 @@
 #endif
 #define FLAG_u (1<<0)
 #define FLAG_r (1<<1)
-#define FLAG_D (1<<2)
-#define FLAG_d (1<<3)
+#define FLAG_I (1<<2)
+#define FLAG_D (1<<3)
+#define FLAG_d (1<<4)
 #endif
 
 #ifdef FOR_dd
@@ -3879,8 +4110,10 @@
 #define FLAG_s (FORCED_FLAG<<0)
 #define FLAG_b (FORCED_FLAG<<1)
 #define FLAG_d (FORCED_FLAG<<2)
-#define FLAG_h (FORCED_FLAG<<3)
-#define FLAG_D (FORCED_FLAG<<4)
+#define FLAG_c (FORCED_FLAG<<3)
+#define FLAG_h (FORCED_FLAG<<4)
+#define FLAG_M (FORCED_FLAG<<5)
+#define FLAG_D (FORCED_FLAG<<6)
 #endif
 
 #ifdef FOR_demo_scankey
@@ -4036,18 +4269,19 @@
 #ifndef TT
 #define TT this.du
 #endif
-#define FLAG_x (1<<0)
-#define FLAG_s (1<<1)
-#define FLAG_L (1<<2)
-#define FLAG_K (1<<3)
-#define FLAG_k (1<<4)
-#define FLAG_H (1<<5)
-#define FLAG_a (1<<6)
-#define FLAG_c (1<<7)
-#define FLAG_l (1<<8)
-#define FLAG_m (1<<9)
-#define FLAG_h (1<<10)
-#define FLAG_d (1<<11)
+#define FLAG_b (1<<0)
+#define FLAG_x (1<<1)
+#define FLAG_s (1<<2)
+#define FLAG_L (1<<3)
+#define FLAG_K (1<<4)
+#define FLAG_k (1<<5)
+#define FLAG_H (1<<6)
+#define FLAG_a (1<<7)
+#define FLAG_c (1<<8)
+#define FLAG_l (1<<9)
+#define FLAG_m (1<<10)
+#define FLAG_h (1<<11)
+#define FLAG_d (1<<12)
 #endif
 
 #ifdef FOR_dumpleases
@@ -4082,8 +4316,23 @@
 #define TT this.env
 #endif
 #define FLAG_u (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_0 (1<<2)
+#define FLAG_0 (1<<1)
+#define FLAG_i (1<<2)
+#endif
+
+#ifdef FOR_eval
+#ifndef TT
+#define TT this.eval
+#endif
+#endif
+
+#ifdef FOR_exec
+#ifndef TT
+#define TT this.exec
+#endif
+#define FLAG_a (FORCED_FLAG<<0)
+#define FLAG_l (FORCED_FLAG<<1)
+#define FLAG_c (FORCED_FLAG<<2)
 #endif
 
 #ifdef FOR_exit
@@ -4099,6 +4348,14 @@
 #define FLAG_t (FORCED_FLAG<<0)
 #endif
 
+#ifdef FOR_export
+#ifndef TT
+#define TT this.export
+#endif
+#define FLAG_p (FORCED_FLAG<<0)
+#define FLAG_n (FORCED_FLAG<<1)
+#endif
+
 #ifdef FOR_expr
 #ifndef TT
 #define TT this.expr
@@ -4566,12 +4823,13 @@
 #define FLAG_g (FORCED_FLAG<<0)
 #define FLAG_o (FORCED_FLAG<<1)
 #define FLAG_m (FORCED_FLAG<<2)
-#define FLAG_v (FORCED_FLAG<<3)
-#define FLAG_s (FORCED_FLAG<<4)
-#define FLAG_p (FORCED_FLAG<<5)
-#define FLAG_D (FORCED_FLAG<<6)
-#define FLAG_d (FORCED_FLAG<<7)
-#define FLAG_c (FORCED_FLAG<<8)
+#define FLAG_t (FORCED_FLAG<<3)
+#define FLAG_v (FORCED_FLAG<<4)
+#define FLAG_s (FORCED_FLAG<<5)
+#define FLAG_p (FORCED_FLAG<<6)
+#define FLAG_D (FORCED_FLAG<<7)
+#define FLAG_d (FORCED_FLAG<<8)
+#define FLAG_c (FORCED_FLAG<<9)
 #endif
 
 #ifdef FOR_ionice
@@ -4645,6 +4903,17 @@
 #define FLAG_a (FORCED_FLAG<<9)
 #endif
 
+#ifdef FOR_jobs
+#ifndef TT
+#define TT this.jobs
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_r (FORCED_FLAG<<1)
+#define FLAG_p (FORCED_FLAG<<2)
+#define FLAG_n (FORCED_FLAG<<3)
+#define FLAG_l (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_kill
 #ifndef TT
 #define TT this.kill
@@ -4727,9 +4996,9 @@
 #ifndef TT
 #define TT this.logger
 #endif
-#define FLAG_p (FORCED_FLAG<<0)
-#define FLAG_t (FORCED_FLAG<<1)
-#define FLAG_s (FORCED_FLAG<<2)
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_p (FORCED_FLAG<<1)
+#define FLAG_t (FORCED_FLAG<<2)
 #endif
 
 #ifdef FOR_login
@@ -5037,11 +5306,12 @@
 #define TT this.mv
 #endif
 #define FLAG_T (1<<0)
-#define FLAG_i (1<<1)
-#define FLAG_f (1<<2)
-#define FLAG_F (1<<3)
-#define FLAG_n (1<<4)
-#define FLAG_v (1<<5)
+#define FLAG_t (1<<1)
+#define FLAG_i (1<<2)
+#define FLAG_f (1<<3)
+#define FLAG_F (1<<4)
+#define FLAG_n (1<<5)
+#define FLAG_v (1<<6)
 #endif
 
 #ifdef FOR_nbd_client
@@ -5068,7 +5338,8 @@
 #define FLAG_w (FORCED_FLAG<<9)
 #define FLAG_L (FORCED_FLAG<<10)
 #define FLAG_l (FORCED_FLAG<<11)
-#define FLAG_t (FORCED_FLAG<<12)
+#define FLAG_E (FORCED_FLAG<<12)
+#define FLAG_t (FORCED_FLAG<<13)
 #endif
 
 #ifdef FOR_netstat
@@ -5099,13 +5370,13 @@
 #ifndef TT
 #define TT this.nl
 #endif
-#define FLAG_s (FORCED_FLAG<<0)
-#define FLAG_n (FORCED_FLAG<<1)
-#define FLAG_b (FORCED_FLAG<<2)
-#define FLAG_E (FORCED_FLAG<<3)
-#define FLAG_w (FORCED_FLAG<<4)
-#define FLAG_l (FORCED_FLAG<<5)
-#define FLAG_v (FORCED_FLAG<<6)
+#define FLAG_s (1<<0)
+#define FLAG_n (1<<1)
+#define FLAG_b (1<<2)
+#define FLAG_E (1<<3)
+#define FLAG_w (1<<4)
+#define FLAG_l (1<<5)
+#define FLAG_v (1<<6)
 #endif
 
 #ifdef FOR_nohup
@@ -5303,6 +5574,7 @@
 #define TT this.printenv
 #endif
 #define FLAG_0 (FORCED_FLAG<<0)
+#define FLAG_null (FORCED_FLAG<<1)
 #endif
 
 #ifdef FOR_printf
@@ -5354,6 +5626,24 @@
 #define FLAG_a (FORCED_FLAG<<0)
 #endif
 
+#ifdef FOR_pwgen
+#ifndef TT
+#define TT this.pwgen
+#endif
+#define FLAG_0 (FORCED_FLAG<<0)
+#define FLAG_A (FORCED_FLAG<<1)
+#define FLAG_v (FORCED_FLAG<<2)
+#define FLAG_1 (FORCED_FLAG<<3)
+#define FLAG_C (FORCED_FLAG<<4)
+#define FLAG_h (FORCED_FLAG<<5)
+#define FLAG_B (FORCED_FLAG<<6)
+#define FLAG_s (FORCED_FLAG<<7)
+#define FLAG_y (FORCED_FLAG<<8)
+#define FLAG_n (FORCED_FLAG<<9)
+#define FLAG_c (FORCED_FLAG<<10)
+#define FLAG_r (FORCED_FLAG<<11)
+#endif
+
 #ifdef FOR_readahead
 #ifndef TT
 #define TT this.readahead
@@ -5372,9 +5662,10 @@
 #define FLAG_n (FORCED_FLAG<<5)
 #define FLAG_l (FORCED_FLAG<<6)
 #define FLAG_h (FORCED_FLAG<<7)
-#define FLAG_d (FORCED_FLAG<<8)
-#define FLAG_a (FORCED_FLAG<<9)
-#define FLAG_dyn_syms (FORCED_FLAG<<10)
+#define FLAG_e (FORCED_FLAG<<8)
+#define FLAG_d (FORCED_FLAG<<9)
+#define FLAG_a (FORCED_FLAG<<10)
+#define FLAG_dyn_syms (FORCED_FLAG<<11)
 #endif
 
 #ifdef FOR_readlink
@@ -5478,6 +5769,22 @@
 #define FLAG_n (FORCED_FLAG<<2)
 #endif
 
+#ifdef FOR_rtcwake
+#ifndef TT
+#define TT this.rtcwake
+#endif
+#define FLAG_v (FORCED_FLAG<<0)
+#define FLAG_u (FORCED_FLAG<<1)
+#define FLAG_t (FORCED_FLAG<<2)
+#define FLAG_s (FORCED_FLAG<<3)
+#define FLAG_m (FORCED_FLAG<<4)
+#define FLAG_l (FORCED_FLAG<<5)
+#define FLAG_d (FORCED_FLAG<<6)
+#define FLAG_a (FORCED_FLAG<<7)
+#define FLAG_auto (FORCED_FLAG<<8)
+#define FLAG_list_modes (FORCED_FLAG<<9)
+#endif
+
 #ifdef FOR_runcon
 #ifndef TT
 #define TT this.runcon
@@ -5488,15 +5795,16 @@
 #ifndef TT
 #define TT this.sed
 #endif
-#define FLAG_z (1<<0)
-#define FLAG_r (1<<1)
-#define FLAG_E (1<<2)
-#define FLAG_n (1<<3)
-#define FLAG_i (1<<4)
-#define FLAG_f (1<<5)
-#define FLAG_e (1<<6)
-#define FLAG_version (1<<7)
-#define FLAG_help (1<<8)
+#define FLAG_s (1<<0)
+#define FLAG_z (1<<1)
+#define FLAG_r (1<<2)
+#define FLAG_E (1<<3)
+#define FLAG_n (1<<4)
+#define FLAG_i (1<<5)
+#define FLAG_f (1<<6)
+#define FLAG_e (1<<7)
+#define FLAG_version (1<<8)
+#define FLAG_help (1<<9)
 #endif
 
 #ifdef FOR_sendevent
@@ -5514,6 +5822,12 @@
 #define FLAG_f (1<<2)
 #endif
 
+#ifdef FOR_set
+#ifndef TT
+#define TT this.set
+#endif
+#endif
+
 #ifdef FOR_setenforce
 #ifndef TT
 #define TT this.setenforce
@@ -5560,6 +5874,21 @@
 #define FLAG_b (1<<2)
 #endif
 
+#ifdef FOR_sha3sum
+#ifndef TT
+#define TT this.sha3sum
+#endif
+#define FLAG_a (FORCED_FLAG<<0)
+#define FLAG_S (FORCED_FLAG<<1)
+#define FLAG_b (FORCED_FLAG<<2)
+#endif
+
+#ifdef FOR_shift
+#ifndef TT
+#define TT this.shift
+#endif
+#endif
+
 #ifdef FOR_shred
 #ifndef TT
 #define TT this.shred
@@ -5645,6 +5974,12 @@
 #define FLAG_g (1<<19)
 #endif
 
+#ifdef FOR_source
+#ifndef TT
+#define TT this.source
+#endif
+#endif
+
 #ifdef FOR_split
 #ifndef TT
 #define TT this.split
@@ -5791,31 +6126,33 @@
 #define FLAG_T (1<<3)
 #define FLAG_X (1<<4)
 #define FLAG_m (1<<5)
-#define FLAG_O (1<<6)
-#define FLAG_S (1<<7)
-#define FLAG_z (1<<8)
-#define FLAG_j (1<<9)
-#define FLAG_J (1<<10)
-#define FLAG_v (1<<11)
-#define FLAG_t (1<<12)
-#define FLAG_x (1<<13)
-#define FLAG_h (1<<14)
-#define FLAG_c (1<<15)
-#define FLAG_k (1<<16)
-#define FLAG_p (1<<17)
-#define FLAG_o (1<<18)
-#define FLAG_to_command (1<<19)
-#define FLAG_owner (1<<20)
-#define FLAG_group (1<<21)
-#define FLAG_mtime (1<<22)
-#define FLAG_mode (1<<23)
-#define FLAG_exclude (1<<24)
-#define FLAG_overwrite (1<<25)
-#define FLAG_no_same_permissions (1<<26)
-#define FLAG_numeric_owner (1<<27)
-#define FLAG_no_recursion (1<<28)
-#define FLAG_full_time (1<<29)
-#define FLAG_restrict (1<<30)
+#define FLAG_P (1<<6)
+#define FLAG_O (1<<7)
+#define FLAG_S (1<<8)
+#define FLAG_z (1<<9)
+#define FLAG_j (1<<10)
+#define FLAG_J (1<<11)
+#define FLAG_I (1<<12)
+#define FLAG_v (1<<13)
+#define FLAG_t (1<<14)
+#define FLAG_x (1<<15)
+#define FLAG_h (1<<16)
+#define FLAG_c (1<<17)
+#define FLAG_k (1<<18)
+#define FLAG_p (1<<19)
+#define FLAG_o (1<<20)
+#define FLAG_to_command (1<<21)
+#define FLAG_owner (1<<22)
+#define FLAG_group (1<<23)
+#define FLAG_mtime (1<<24)
+#define FLAG_mode (1<<25)
+#define FLAG_exclude (1<<26)
+#define FLAG_overwrite (1<<27)
+#define FLAG_no_same_permissions (1<<28)
+#define FLAG_numeric_owner (1<<29)
+#define FLAG_no_recursion (1<<30)
+#define FLAG_full_time (1LL<<31)
+#define FLAG_restrict (1LL<<32)
 #endif
 
 #ifdef FOR_taskset
@@ -6075,6 +6412,12 @@
 #define FLAG_o (1<<6)
 #endif
 
+#ifdef FOR_unicode
+#ifndef TT
+#define TT this.unicode
+#endif
+#endif
+
 #ifdef FOR_uniq
 #ifndef TT
 #define TT this.uniq
@@ -6101,6 +6444,15 @@
 #endif
 #endif
 
+#ifdef FOR_unset
+#ifndef TT
+#define TT this.unset
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#define FLAG_v (FORCED_FLAG<<1)
+#define FLAG_f (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_unshare
 #ifndef TT
 #define TT this.unshare
@@ -6197,6 +6549,13 @@
 #endif
 #endif
 
+#ifdef FOR_wait
+#ifndef TT
+#define TT this.wait
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_watch
 #ifndef TT
 #define TT this.watch
@@ -6208,6 +6567,15 @@
 #define FLAG_n (FORCED_FLAG<<4)
 #endif
 
+#ifdef FOR_watchdog
+#ifndef TT
+#define TT this.watchdog
+#endif
+#define FLAG_T (FORCED_FLAG<<0)
+#define FLAG_t (FORCED_FLAG<<1)
+#define FLAG_F (FORCED_FLAG<<2)
+#endif
+
 #ifdef FOR_wc
 #ifndef TT
 #define TT this.wc
diff --git a/android/mac/generated/globals.h b/android/mac/generated/globals.h
index 4a201bf..7d5e3be 100644
--- a/android/mac/generated/globals.h
+++ b/android/mac/generated/globals.h
@@ -7,7 +7,7 @@
 // toys/example/demo_number.c
 
 struct demo_number_data {
-  long D;
+  long M, D;
 };
 
 // toys/example/hello.c
@@ -73,10 +73,10 @@
 struct md5sum_data {
   int sawline;
 
+  unsigned *md5table;
   // Crypto variables blanked after summing
-  unsigned state[5];
-  unsigned oldstate[5];
-  uint64_t count;
+  unsigned state[5], oldstate[5];
+  unsigned long long count;
   union {
     char c[64];
     unsigned i[16];
@@ -124,7 +124,7 @@
 struct seq_data {
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 };
 
 // toys/lsb/su.c
@@ -159,10 +159,10 @@
 // toys/net/microcom.c
 
 struct microcom_data {
-  char *s;
+  long s;
 
-  int fd;
-  struct termios original_stdin_state, original_fd_state;
+  int fd, stok;
+  struct termios old_stdin, old_fd;
 };
 
 // toys/net/netcat.c
@@ -177,7 +177,7 @@
 struct netstat_data {
   struct num_cache *inodes;
   int wpad;
-};;
+};
 
 // toys/net/ping.c
 
@@ -214,8 +214,15 @@
 
 struct base64_data {
   long w;
-
   unsigned total;
+  unsigned n;  // number of bits used in encoding. 5 for base32, 6 for base64
+  unsigned align;  // number of bits to align to
+};
+
+// toys/other/blkdiscard.c
+
+struct blkdiscard_data {
+  long o, l;
 };
 
 // toys/other/blkid.c
@@ -270,15 +277,17 @@
   char *data;
   long long len, base;
   int numlen, undo, undolen;
-  unsigned height;
+  unsigned rows, cols;
+  long long pos;
+  char keybuf[16];
+  char input[80];
+  char *search;
 };
 
 // toys/other/hwclock.c
 
 struct hwclock_data {
   char *f;
-
-  int utc;
 };
 
 // toys/other/ionice.c
@@ -375,12 +384,32 @@
   char *c;
 };
 
+// toys/other/pwgen.c
+
+struct pwgen_data {
+  char *r;
+};
+
+// toys/other/rtcwake.c
+
+struct rtcwake_data {
+  long t, s;
+  char *m, *d;
+};
+
 // toys/other/setfattr.c
 
 struct setfattr_data {
   char *x, *v, *n;
 };
 
+// toys/other/sha3sum.c
+
+struct sha3sum_data {
+  long a;
+  unsigned long long rc[24];
+};
+
 // toys/other/shred.c
 
 struct shred_data {
@@ -448,6 +477,14 @@
   pid_t pid, oldpid;
 };
 
+// toys/other/watchdog.c
+
+struct watchdog_data {
+  long T, t;
+
+  int fd;
+};
+
 // toys/other/xxd.c
 
 struct xxd_data {
@@ -496,11 +533,10 @@
 
 struct bootchartd_data {
   char buf[32];
-  long smpl_period_usec;
+  long msec;
   int proc_accounting;
-  int is_login;
 
-  pid_t cur_pid;
+  pid_t pid;
 };
 
 // toys/pending/brctl.c
@@ -509,6 +545,12 @@
     int sockfd;
 };
 
+// toys/pending/chsh.c
+
+struct chsh_data {
+  char *s;
+};
+
 // toys/pending/crond.c
 
 struct crond_data {
@@ -542,7 +584,7 @@
     unsigned long long offset;
   } in, out;
   unsigned conv, iflag, oflag;
-};;
+};
 
 // toys/pending/dhcp.c
 
@@ -578,7 +620,7 @@
 struct dhcpd_data {
     char *iface;
     long port;
-};;
+};
 
 // toys/pending/diff.c
 
@@ -653,17 +695,12 @@
 // toys/pending/getty.c
 
 struct getty_data {
-  char *issue_str;
-  char *login_str;
-  char *init_str;
-  char *host_str; 
-  long timeout;
-  
-  char *tty_name;  
-  int  speeds[20];
-  int  sc;              
+  char *f, *l, *I, *H;
+  long t;
+
+  char *tty_name, buff[128];
+  int speeds[20], sc;
   struct termios termios;
-  char buff[128];
 };
 
 // toys/pending/groupadd.c
@@ -770,11 +807,9 @@
 struct modprobe_data {
   struct arg_list *dirs;
 
-  struct arg_list *probes;
-  struct arg_list *dbase[256];
+  struct arg_list *probes, *dbase[256];
   char *cmdopts;
-  int nudeps;
-  uint8_t symreq;
+  int nudeps, symreq;
 };
 
 // toys/pending/more.c
@@ -787,7 +822,7 @@
 // toys/pending/openvt.c
 
 struct openvt_data {
-  unsigned long vt_num;
+  long c;
 };
 
 // toys/pending/readelf.c
@@ -796,53 +831,96 @@
   char *x, *p;
 
   char *elf, *shstrtab, *f;
-  long long shoff, phoff, size;
-  int bits, shnum, shentsize, phentsize;
-  int64_t (*elf_int)(void *ptr, unsigned size);
+  unsigned long long shoff, phoff, size, shstrtabsz;
+  int bits, endian, shnum, shentsize, phentsize;
 };
 
 // toys/pending/route.c
 
 struct route_data {
-  char *family;
+  char *A;
 };
 
 // toys/pending/sh.c
 
 struct sh_data {
-  char *c;
+  union {
+    struct {
+      char *c;
+    } sh;
+    struct {
+      char *a;
+    } exec;
+  };
 
-  long lineno;
-  char **locals, *subshell_env;
-  struct double_list functions;
-  unsigned options, jobcnt, loc_ro, loc_magic;
-  int hfd;  // next high filehandle (>= 10)
+  // keep SECONDS here: used to work around compiler limitation in run_command()
+  long long SECONDS;
+  char *isexec, *wcpat;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
 
-  // Running jobs.
-  struct sh_job {
-    struct sh_job *next, *prev;
-    unsigned jobno;
+  // Callable function array
+  struct sh_function {
+    char *name;
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
+      struct sh_pipeline *next, *prev, *end;
+      int count, here, type, lineno;
+      struct sh_arg {
+        char **v;
+        int c;
+      } arg[1];
+    } *pipeline;
+    unsigned long refcount;
+  } **functions;
+  long funcslen;
 
-    // Every pipeline has at least one set of arguments or it's Not A Thing
-    struct sh_arg {
-      char **v;
-      int c;
-    } pipeline;
+  // runtime function call stack
+  struct sh_fcall {
+    struct sh_fcall *next, *prev;
 
-    // null terminated array of running processes in pipeline
-    struct sh_process {
-      struct sh_process *next, *prev;
-      struct arg_list *delete;   // expanded strings
-      int *urd, envlen, pid, exit;  // undo redirects, child PID, exit status
-      struct sh_arg arg;
-    } *procs, *proc;
-  } *jobs, *job;
+    // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+    struct sh_vars {
+      long flags;
+      char *str;
+    } *vars;
+    long varslen, shift;
+
+    struct sh_function *func; // TODO wire this up
+    struct sh_pipeline *pl;
+    char *ifs;
+    struct sh_arg arg;
+    struct arg_list *delete;
+
+    // Runtime stack of nested if/else/fi and for/do/done contexts.
+    struct sh_blockstack {
+      struct sh_blockstack *next;
+      struct sh_pipeline *start, *middle;
+      struct sh_process *pp;       // list of processes piping in to us
+      int run, loop, *urd, pout, pipe;
+      struct sh_arg farg;          // for/select arg stack, case wildcard deck
+      struct arg_list *fdelete;    // farg's cleanup list
+      char *fvar;                  // for/select's iteration variable name
+    } *blk;
+  } *ff;
+
+// TODO ctrl-Z suspend should stop script
+  struct sh_process {
+    struct sh_process *next, *prev; // | && ||
+    struct arg_list *delete;   // expanded strings
+    // undo redirects, a=b at start, child PID, exit status, has !, job #
+    int *urd, envlen, pid, exit, not, job, dash;
+    long long when; // when job backgrounded/suspended
+    struct sh_arg *raw, arg;
+  } *pp; // currently running process
+
+  // job list, command line for $*, scratch space for do_wildcard_files()
+  struct sh_arg jobs, *wcdeck;
 };
 
 // toys/pending/stty.c
 
 struct stty_data {
-  char *device;
+  char *F;
 
   int fd, col;
   unsigned output_cols;
@@ -890,20 +968,13 @@
 // toys/pending/telnet.c
 
 struct telnet_data {
-  int port;
-  int sfd;
-  char buff[128];
-  int pbuff;
-  char iac[256];
-  int piac;
-  char *ttype;
-  struct termios def_term;
+  int sock;
+  char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
+  struct termios old_term;
   struct termios raw_term;
-  uint8_t term_ok;
-  uint8_t term_mode;
-  uint8_t flags;
-  unsigned win_width;
-  unsigned win_height;
+  uint8_t mode;
+  int echo, sga;
+  int state, request;
 };
 
 // toys/pending/telnetd.c
@@ -984,37 +1055,28 @@
 // toys/pending/vi.c
 
 struct vi_data {
-    char *s;
-    int cur_col;
-    int cur_row;
-    int scr_row;
-    int drawn_row;
-    int drawn_col;
-    unsigned screen_height;
-    unsigned screen_width;
-    int vi_mode;
-    int count0;
-    int count1;
-    int vi_mov_flag;
-    int modified;
-    char vi_reg;
-    char *last_search;
-    int tabstop;
-    int list;
-    struct str_line {
-      int alloc;
-      int len;
-      char *data;
-    } *il;
-    size_t screen; //offset in slices must be higher than cursor
-    size_t cursor; //offset in slices
-    //yank buffer
-    struct yank_buf {
-      char reg;
-      int alloc;
-      char* data;
-    } yank;
+  char *s;
+  int vi_mode, tabstop, list;
+  int cur_col, cur_row, scr_row;
+  int drawn_row, drawn_col;
+  int count0, count1, vi_mov_flag;
+  unsigned screen_height, screen_width;
+  char vi_reg, *last_search;
+  struct str_line {
+    int alloc;
+    int len;
+    char *data;
+  } *il;
+  size_t screen, cursor; //offsets
+  //yank buffer
+  struct yank_buf {
+    char reg;
+    int alloc;
+    char* data;
+  } yank;
 
+  int modified;
+  size_t filesize;
 // mem_block contains RO data that is either original file as mmap
 // or heap allocated inserted data
 //
@@ -1048,10 +1110,6 @@
       const char *data;
     } *node;
   } *slices;
-
-  size_t filesize;
-  int fd; //file_handle
-
 };
 
 // toys/pending/wget.c
@@ -1106,11 +1164,11 @@
   union {
     // install's options
     struct {
-      char *g, *o, *m;
+      char *g, *o, *m, *t;
     } i;
     // cp's options
     struct {
-      char *preserve;
+      char *t, *preserve;
     } c;
   };
 
@@ -1125,7 +1183,7 @@
 // toys/posix/cpio.c
 
 struct cpio_data {
-  char *F, *p, *H;
+  char *F, *H;
 };
 
 // toys/posix/cut.c
@@ -1141,7 +1199,7 @@
 // toys/posix/date.c
 
 struct date_data {
-  char *r, *D, *d;
+  char *r, *I, *D, *d;
 
   unsigned nano;
 };
@@ -1151,9 +1209,7 @@
 struct df_data {
   struct arg_list *t;
 
-  long units;
-  int column_widths[5];
-  int header_shown;
+  int units, width[6];
 };
 
 // toys/posix/du.c
@@ -1170,7 +1226,7 @@
 
 struct env_data {
   struct arg_list *u;
-};;
+};
 
 // toys/posix/expand.c
 
@@ -1360,7 +1416,7 @@
   dev_t tty;
   void *fields, *kfields;
   long long ticks, bits, time;
-  int kcount, forcek, sortpos;
+  int kcount, forcek, sortpos, pidlen;
   int (*match_process)(long long *slot);
   void (*show_process)(void *tb);
 };
@@ -1429,7 +1485,7 @@
 struct tar_data {
   char *f, *C;
   struct arg_list *T, *X;
-  char *to_command, *owner, *group, *mtime, *mode;
+  char *I, *to_command, *owner, *group, *mtime, *mode;
   struct arg_list *exclude;
 
   struct double_list *incl, *excl, *seen;
@@ -1462,6 +1518,7 @@
 
 struct tee_data {
   void *outputs;
+  int out;
 };
 
 // toys/posix/touch.c
@@ -1502,7 +1559,7 @@
   long s, n, P;
   char *E;
 
-  long entries, bytes;
+  long entries, bytes, np;
   char delim;
   FILE *tty;
 };
@@ -1535,6 +1592,7 @@
 	struct tunctl_data tunctl;
 	struct acpi_data acpi;
 	struct base64_data base64;
+	struct blkdiscard_data blkdiscard;
 	struct blkid_data blkid;
 	struct blockdev_data blockdev;
 	struct chrt_data chrt;
@@ -1556,7 +1614,10 @@
 	struct modinfo_data modinfo;
 	struct nsenter_data nsenter;
 	struct oneit_data oneit;
+	struct pwgen_data pwgen;
+	struct rtcwake_data rtcwake;
 	struct setfattr_data setfattr;
+	struct sha3sum_data sha3sum;
 	struct shred_data shred;
 	struct stat_data stat;
 	struct swapon_data swapon;
@@ -1565,12 +1626,14 @@
 	struct timeout_data timeout;
 	struct truncate_data truncate;
 	struct watch_data watch;
+	struct watchdog_data watchdog;
 	struct xxd_data xxd;
 	struct arp_data arp;
 	struct arping_data arping;
 	struct bc_data bc;
 	struct bootchartd_data bootchartd;
 	struct brctl_data brctl;
+	struct chsh_data chsh;
 	struct crond_data crond;
 	struct crontab_data crontab;
 	struct dd_data dd;
diff --git a/android/mac/generated/help.h b/android/mac/generated/help.h
index 6621ed1..86fbdbf 100644
--- a/android/mac/generated/help.h
+++ b/android/mac/generated/help.h
@@ -1,4 +1,4 @@
-#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a toolchain scripts/mcm-buildall.sh patches musl to fix this."
+#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a scripts/mcm-buildall.sh toolchain patches musl to fix this."
 
 #define HELP_toybox_uid_usr "When commands like useradd/groupadd allocate user IDs, start here."
 
@@ -12,8 +12,6 @@
 
 #define HELP_toybox_free "When a program exits, the operating system will clean up after it\n(free memory, close files, etc). To save size, toybox usually relies\non this behavior. If you're running toybox under a debugger or\nwithout a real OS (ala newlib+libgloss), enable this to make toybox\nclean up after itself."
 
-#define HELP_toybox_i18n "Support for UTF-8 character sets, and some locale support."
-
 #define HELP_toybox_help_dashdash "Support --help argument in all commands, even ones with a NULL\noptstring. (Use TOYFLAG_NOHELP to disable.) Produces the same output\nas \"help command\". --version shows toybox version."
 
 #define HELP_toybox_help "Include help text for each command."
@@ -32,7 +30,7 @@
 
 #define HELP_toybox_suid "Support for the Set User ID bit, to install toybox suid root and drop\npermissions for commands which do not require root access. To use\nthis change ownership of the file to the root user and set the suid\nbit in the file permissions:\n\nchown root:root toybox; chmod +s toybox\n\nprompt \"Security Blanket\"\ndefault TOYBOX_LSM_NONE\nhelp\nSelect a Linux Security Module to complicate your system\nuntil you can't find holes in it."
 
-#define HELP_toybox "usage: toybox [--long | --help | --version | [command] [arguments...]]\n\nWith no arguments, shows available commands. First argument is\nname of a command to run, followed by any arguments to that command.\n\n--long	Show path to each command\n\nTo install command symlinks with paths, try:\n  for i in $(/bin/toybox --long); do ln -s /bin/toybox $i; done\nor all in one directory:\n  for i in $(./toybox); do ln -s toybox $i; done; PATH=$PWD:$PATH\n\nMost toybox commands also understand the following arguments:\n\n--help		Show command help (only)\n--version	Show toybox version (only)\n\nThe filename \"-\" means stdin/stdout, and \"--\" stops argument parsing.\n\nNumerical arguments accept a single letter suffix for\nkilo, mega, giga, tera, peta, and exabytes, plus an additional\n\"d\" to indicate decimal 1000's instead of 1024.\n\nDurations can be decimal fractions and accept minute (\"m\"), hour (\"h\"),\nor day (\"d\") suffixes (so 0.1m = 6s)."
+#define HELP_toybox "usage: toybox [--long | --help | --version | [COMMAND] [ARGUMENTS...]]\n\nWith no arguments, \"toybox\" shows available COMMAND names. Add --long\nto include suggested install path for each command, see\nhttps://landley.net/toybox/faq.html#install for details.\n\nFirst argument is name of a COMMAND to run, followed by any ARGUMENTS\nto that command. Most toybox commands also understand:\n\n--help		Show command help (only)\n--version	Show toybox version (only)\n\nThe filename \"-\" means stdin/stdout, and \"--\" stops argument parsing.\n\nNumerical arguments accept a single letter suffix for\nkilo, mega, giga, tera, peta, and exabytes, plus an additional\n\"d\" to indicate decimal 1000's instead of 1024.\n\nDurations can be decimal fractions and accept minute (\"m\"), hour (\"h\"),\nor day (\"d\") suffixes (so 0.1m = 6s)."
 
 #define HELP_setenforce "usage: setenforce [enforcing|permissive|1|0]\n\nSets whether SELinux is enforcing (1) or permissive (0)."
 
@@ -62,7 +60,7 @@
 
 #define HELP_demo_scankey "usage: demo_scankey\n\nMove a letter around the screen. Hit ESC to exit."
 
-#define HELP_demo_number "usage: demo_number [-hsbi] NUMBER...\n\n-b	Use \"B\" for single byte units (HR_B)\n-d	Decimal units\n-h	Human readable\n-s	Space between number and units (HR_SPACE)"
+#define HELP_demo_number "usage: demo_number [-hsbi] [-D LEN] NUMBER...\n\n-D	output field is LEN chars\n-M	input units (index into bkmgtpe)\n-c	Comma comma down do be do down down\n-b	Use \"B\" for single byte units (HR_B)\n-d	Decimal units\n-h	Human readable\n-s	Space between number and units (HR_SPACE)"
 
 #define HELP_demo_many_options "usage: demo_many_options -[a-zA-Z]\n\nPrint the optflags value of the command arguments, in hex."
 
@@ -70,7 +68,7 @@
 
 #define HELP_sync "usage: sync\n\nWrite pending cached data to disk (synchronize), blocking until done."
 
-#define HELP_su "usage: su [-lp] [-u UID] [-g GID,...] [-s SHELL] [-c CMD] [USER [COMMAND...]]\n\nSwitch user, prompting for password of new user when not run as root.\n\nWith one argument, switch to USER and run user's shell from /etc/passwd.\nWith no arguments, USER is root. If COMMAND line provided after USER,\nexec() it as new USER (bypasing shell). If -u or -g specified, first\nargument (if any) isn't USER (it's COMMAND).\n\nfirst argument is USER name to switch to (which must exist).\nNon-root users are prompted for new user's password.\n\n-s	Shell to use (default is user's shell from /etc/passwd)\n-c	Command line to pass to -s shell (ala sh -c \"CMD\")\n-l	Reset environment as if new login.\n-u	Switch to UID instead of USER\n-g	Switch to GID (only root allowed, can be comma separated list)\n-p	Preserve environment (except for $PATH and $IFS)"
+#define HELP_su "usage: su [-lp] [-u UID] [-g GID,...] [-s SHELL] [-c CMD] [USER [COMMAND...]]\n\nSwitch user, prompting for password of new user when not run as root.\n\nWith one argument, switch to USER and run user's shell from /etc/passwd.\nWith no arguments, USER is root. If COMMAND line provided after USER,\nexec() it as new USER (bypassing shell). If -u or -g specified, first\nargument (if any) isn't USER (it's COMMAND).\n\nfirst argument is USER name to switch to (which must exist).\nNon-root users are prompted for new user's password.\n\n-s	Shell to use (default is user's shell from /etc/passwd)\n-c	Command line to pass to -s shell (ala sh -c \"CMD\")\n-l	Reset environment as if new login.\n-u	Switch to UID instead of USER\n-g	Switch to GID (only root allowed, can be comma separated list)\n-p	Preserve environment (except for $PATH and $IFS)"
 
 #define HELP_seq "usage: seq [-w|-f fmt_str] [-s sep_str] [first] [increment] last\n\nCount from first to last, by increment. Omitted arguments default\nto 1. Two arguments are used as first and last. Arguments can be\nnegative or floating point.\n\n-f	Use fmt_str as a printf-style floating point format string\n-s	Use sep_str as separator, default is a newline character\n-w	Pad to equal width with leading zeroes"
 
@@ -116,7 +114,7 @@
 
 #define HELP_tunctl "usage: tunctl [-dtT] [-u USER] NAME\n\nCreate and delete tun/tap virtual ethernet devices.\n\n-T	Use tap (ethernet frames) instead of tun (ip packets)\n-d	Delete tun/tap device\n-t	Create tun/tap device\n-u	Set owner (user who can read/write device without root access)"
 
-#define HELP_sntp "usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]\n\nSimple Network Time Protocol client. Query SERVER and display time.\n\n-p	Use PORT (default 123)\n-s	Set system clock suddenly\n-a	Adjust system clock gradually\n-S	Serve time instead of querying (bind to SERVER address if specified)\n-m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)\n-M	Multicast server on ADDRESS (deault 224.0.0.1)\n-t	TTL (multicast only, default 1)\n-d	Daemonize (run in background re-querying )\n-D	Daemonize but stay in foreground: re-query time every 1000 seconds\n-r	Retry shift (every 1<<SHIFT seconds)\n-q	Quiet (don't display time)"
+#define HELP_sntp "usage: sntp [-saSdDq] [-r SHIFT] [-mM[ADDRESS]] [-p PORT] [SERVER]\n\nSimple Network Time Protocol client. Query SERVER and display time.\n\n-p	Use PORT (default 123)\n-s	Set system clock suddenly\n-a	Adjust system clock gradually\n-S	Serve time instead of querying (bind to SERVER address if specified)\n-m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)\n-M	Multicast server on ADDRESS (default 224.0.0.1)\n-t	TTL (multicast only, default 1)\n-d	Daemonize (run in background re-querying )\n-D	Daemonize but stay in foreground: re-query time every 1000 seconds\n-r	Retry shift (every 1<<SHIFT seconds)\n-q	Quiet (don't display time)"
 
 #define HELP_rfkill "usage: rfkill COMMAND [DEVICE]\n\nEnable/disable wireless devices.\n\nCommands:\nlist [DEVICE]   List current state\nblock DEVICE    Disable device\nunblock DEVICE  Enable device\n\nDEVICE is an index number, or one of:\nall, wlan(wifi), bluetooth, uwb(ultrawideband), wimax, wwan, gps, fm."
 
@@ -124,11 +122,11 @@
 
 #define HELP_netstat "usage: netstat [-pWrxwutneal]\n\nDisplay networking information. Default is netstat -tuwx\n\n-r	Routing table\n-a	All sockets (not just connected)\n-l	Listening server sockets\n-t	TCP sockets\n-u	UDP sockets\n-w	Raw sockets\n-x	Unix sockets\n-e	Extended info\n-n	Don't resolve names\n-W	Wide display\n-p	Show PID/program name of sockets"
 
-#define HELP_netcat_listen "usage: netcat [-t] [-lL COMMAND...]\n\n-l	Listen for one incoming connection\n-L	Listen for multiple incoming connections (server mode)\n-t	Allocate tty (must come before -l or -L)\n\nThe command line after -l or -L is executed (as a child process) to handle\neach incoming connection. If blank -l waits for a connection and forwards\nit to stdin/stdout. If no -p specified, -l prints port it bound to and\nbackgrounds itself (returning immediately).\n\nFor a quick-and-dirty server, try something like:\nnetcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l"
+#define HELP_netcat_listen "usage: netcat [-tElL]\n\n-l	Listen for one incoming connection, then exit\n-L	Listen and background each incoming connection (server mode)\n-t	Allocate tty\n-E	Forward stderr\n\nWhen listening the COMMAND line is executed as a child process to handle\nan incoming connection. With no COMMAND -l forwards the connection\nto stdin/stdout. If no -p specified, -l prints the port it bound to and\nbackgrounds itself (returning immediately).\n\nFor a quick-and-dirty server, try something like:\nnetcat -s 127.0.0.1 -p 1234 -tL sh -l"
 
-#define HELP_netcat "usage: netcat [-46U] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME}\n\nForward stdin/stdout to a file or network connection.\n\n-4	Force IPv4\n-6	Force IPv6\n-f	Use FILENAME (ala /dev/ttyS0) instead of network\n-p	Local port number\n-q	Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet\n-s	Local source address\n-u	Use UDP\n-U	Use a UNIX domain socket\n-w	SECONDS timeout to establish connection\n-W	SECONDS timeout for more data on an idle connection\n\nUse \"stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho\" with\nnetcat -f to connect to a serial port."
+#define HELP_netcat "usage: netcat [-46U] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME|COMMAND...}\n\nForward stdin/stdout to a file or network connection.\n\n-4	Force IPv4\n-6	Force IPv6\n-f	Use FILENAME (ala /dev/ttyS0) instead of network\n-p	Local port number\n-q	Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet\n-s	Local source address\n-u	Use UDP\n-U	Use a UNIX domain socket\n-w	SECONDS timeout to establish connection\n-W	SECONDS timeout for more data on an idle connection\n\nUse \"stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho\" with\nnetcat -f to connect to a serial port."
 
-#define HELP_microcom "usage: microcom [-s SPEED] [-X] DEVICE\n\nSimple serial console.\n\n-s	Set baud rate to SPEED\n-X	Ignore ^@ (send break) and ^] (exit)"
+#define HELP_microcom "usage: microcom [-s SPEED] [-X] DEVICE\n\nSimple serial console.\n\n-s	Set baud rate to SPEED (default 115200)\n-X	Ignore ^@ (send break) and ^] (exit)"
 
 #define HELP_ifconfig "usage: ifconfig [-aS] [INTERFACE [ACTION...]]\n\nDisplay or configure network interface.\n\nWith no arguments, display active interfaces. First argument is interface\nto operate on, one argument by itself displays that interface.\n\n-a	All interfaces displayed, not just active ones\n-S	Short view, one line per interface\n\nStandard ACTIONs to perform on an INTERFACE:\n\nADDR[/MASK]        - set IPv4 address (1.2.3.4/5) and activate interface\nadd|del ADDR[/LEN] - add/remove IPv6 address (1111::8888/128)\nup|down            - activate or deactivate interface\n\nAdvanced ACTIONs (default values usually suffice):\n\ndefault          - remove IPv4 address\nnetmask ADDR     - set IPv4 netmask via 255.255.255.0 instead of /24\ntxqueuelen LEN   - number of buffered packets before output blocks\nmtu LEN          - size of outgoing packets (Maximum Transmission Unit)\nbroadcast ADDR   - Set broadcast address\npointopoint ADDR - PPP and PPPOE use this instead of \"route add default gw\"\nhw TYPE ADDR     - set hardware (mac) address (type = ether|infiniband)\n\nFlags you can set on an interface (or -remove by prefixing with -):\n\narp       - don't use Address Resolution Protocol to map LAN routes\npromisc   - don't discard packets that aren't to this LAN hardware address\nmulticast - force interface into multicast mode if the driver doesn't\nallmulti  - promisc for multicast packets"
 
@@ -142,6 +140,8 @@
 
 #define HELP_which "usage: which [-a] filename ...\n\nSearch $PATH for executable files matching filename(s).\n\n-a	Show all matches"
 
+#define HELP_watchdog "usage: watchdog [-F] [-t UPDATE] [-T DEADLINE] DEV\n\nStart the watchdog timer at DEV with optional timeout parameters.\n\n-F	run in the foreground (do not daemonize)\n-t	poke watchdog every UPDATE seconds (default 4)\n-T	reboot if not poked for DEADLINE seconds (default 60)"
+
 #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\")"
 
 #define HELP_w "usage: w\n\nShow who is logged on and since how long they logged in."
@@ -174,14 +174,18 @@
 
 #define HELP_swapoff "usage: swapoff swapregion\n\nDisable swapping on a given swapregion."
 
-#define HELP_stat "usage: stat [-tfL] [-c FORMAT] FILE...\n\nDisplay status of files or filesystems.\n\n-c	Output specified FORMAT string instead of default\n-f	Display filesystem status instead of file status\n-L	Follow symlinks\n-t	terse (-c \"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\")\n	      (with -f = -c \"%n %i %l %t %s %S %b %f %a %c %d\")\n\nThe valid format escape sequences for files:\n%a  Access bits (octal) |%A  Access bits (flags)|%b  Size/512\n%B  Bytes per %b (512)  |%C  Security context   |%d  Device ID (dec)\n%D  Device ID (hex)     |%f  All mode bits (hex)|%F  File type\n%g  Group ID            |%G  Group name         |%h  Hard links\n%i  Inode               |%m  Mount point        |%n  Filename\n%N  Long filename       |%o  I/O block size     |%s  Size (bytes)\n%t  Devtype major (hex) |%T  Devtype minor (hex)|%u  User ID\n%U  User name           |%x  Access time        |%X  Access unix time\n%y  Modification time   |%Y  Mod unix time      |%z  Creation time\n%Z  Creation unix time\n\nThe valid format escape sequences for filesystems:\n%a  Available blocks    |%b  Total blocks       |%c  Total inodes\n%d  Free inodes         |%f  Free blocks        |%i  File system ID\n%l  Max filename length |%n  File name          |%s  Fragment size\n%S  Best transfer size  |%t  FS type (hex)      |%T  FS type (driver name)"
+#define HELP_stat "usage: stat [-tfL] [-c FORMAT] FILE...\n\nDisplay status of files or filesystems.\n\n-c	Output specified FORMAT string instead of default\n-f	Display filesystem status instead of file status\n-L	Follow symlinks\n-t	terse (-c \"%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\")\n	      (with -f = -c \"%n %i %l %t %s %S %b %f %a %c %d\")\n\nThe valid format escape sequences for files:\n%a  Access bits (octal) |%A  Access bits (flags)|%b  Size/512\n%B  Bytes per %b (512)  |%C  Security context   |%d  Device ID (dec)\n%D  Device ID (hex)     |%f  All mode bits (hex)|%F  File type\n%g  Group ID            |%G  Group name         |%h  Hard links\n%i  Inode               |%m  Mount point        |%n  Filename\n%N  Long filename       |%o  I/O block size     |%s  Size (bytes)\n%t  Devtype major (hex) |%T  Devtype minor (hex)|%u  User ID\n%U  User name           |%x  Access time        |%X  Access unix time\n%y  Modification time   |%Y  Mod unix time      |%z  Creation time\n%Z  Creation unix time\n\nThe valid format escape sequences for filesystems:\n%a  Available blocks    |%b  Total blocks       |%c  Total inodes\n%d  Free inodes         |%f  Free blocks        |%i  File system ID\n%l  Max filename length |%n  File name          |%s  Best transfer size\n%S  Actual block size   |%t  FS type (hex)      |%T  FS type (driver name)"
 
 #define HELP_shred "usage: shred [-fuz] [-n COUNT] [-s SIZE] FILE...\n\nSecurely delete a file by overwriting its contents with random data.\n\n-f		Force (chmod if necessary)\n-n COUNT	Random overwrite iterations (default 1)\n-o OFFSET	Start at OFFSET\n-s SIZE		Use SIZE instead of detecting file size\n-u		Unlink (actually delete file when done)\n-x		Use exact size (default without -s rounds up to next 4k)\n-z		Zero at end\n\nNote: data journaling filesystems render this command useless, you must\noverwrite all free space (fill up disk) to erase old data on those."
 
+#define HELP_sha3sum "usage: sha3sum [-S] [-a BITS] [FILE...]\n\nHash function du jour.\n\n-a	Produce a hash BITS long (default 224)\n-b	Brief (hash only, no filename)\n-S	Use SHAKE termination byte instead of SHA3 (ask FIPS why)"
+
 #define HELP_setsid "usage: setsid [-cdw] command [args...]\n\nRun process in a new session.\n\n-d	Detach from tty\n-c	Control tty (become foreground process & receive keyboard signals)\n-w	Wait for child (and exit with its status)"
 
 #define HELP_setfattr "usage: setfattr [-h] [-x|-n NAME] [-v VALUE] FILE...\n\nWrite POSIX extended attributes.\n\n-h	Do not dereference symlink\n-n	Set given attribute\n-x	Remove given attribute\n-v	Set value for attribute -n (default is empty)"
 
+#define HELP_rtcwake "usage: rtcwake [-aluv] [-d FILE] [-m MODE] [-s SECS] [-t UNIX]\n\nEnter the given sleep state until the given time.\n\n-a	RTC uses time specified in /etc/adjtime\n-d FILE	Device to use (default /dev/rtc)\n-l	RTC uses local time\n-m	Mode (--list-modes to see those supported by your kernel):\n	  standby  S1: default              mem     S3: suspend to RAM\n	  disk     S4: suspend to disk      off     S5: power off\n	  disable  Cancel current alarm     freeze  stop processes/processors\n	  no       just set wakeup time     on      just poll RTC for alarm\n	  show     just show current alarm\n-s SECS	Wake SECS seconds from now\n-t UNIX	Wake UNIX seconds from epoch\n-u	RTC uses UTC\n-v	Verbose"
+
 #define HELP_rmmod "usage: rmmod [-wf] [MODULE]\n\nUnload the module named MODULE from the Linux kernel.\n-f	Force unload of a module\n-w	Wait until the module is no longer used"
 
 #define HELP_rev "usage: rev [FILE...]\n\nOutput each line reversed, when no files are given stdin is used."
@@ -196,6 +200,8 @@
 
 #define HELP_readahead "usage: readahead FILE...\n\nPreload files into disk cache."
 
+#define HELP_pwgen "usage: pwgen [-cAn0yrsBhC1v] [LENGTH] [COUNT]\n\nGenerate human-readable random passwords. When output is to tty produces\na screenfull to defeat shoulder surfing (pick one and clear the screen).\n\n-c  --capitalize                  Permit capital letters.\n-A  --no-capitalize               Don't include capital letters.\n-n  --numerals                    Permit numbers.\n-0  --no-numerals                 Don't include numbers.\n-y  --symbols                     Permit special characters ($#%...).\n-r <chars>  --remove=<chars>      Don't include the given characters.\n-s  --secure                      Generate more random passwords.\n-B  --ambiguous                   Avoid ambiguous characters (e.g. 0, O).\n-h  --help                        Print this help message.\n-C                                Print the output in columns.\n-1                                Print the output one line each.\n-v                                Don't include vowels."
+
 #define HELP_pwdx "usage: pwdx PID...\n\nPrint working directory of processes listed on command line."
 
 #define HELP_printenv "usage: printenv [-0] [env_var...]\n\nPrint environment variables.\n\n-0	Use \\0 as delimiter instead of \\n"
@@ -206,7 +212,7 @@
 
 #define HELP_partprobe "usage: partprobe DEVICE...\n\nTell the kernel about partition table changes\n\nAsk the kernel to re-read the partition table on the specified devices."
 
-#define HELP_oneit "usage: oneit [-p] [-c /dev/tty0] command [...]\n\nSimple init program that runs a single supplied command line with a\ncontrolling tty (so CTRL-C can kill it).\n\n-c	Which console device to use (/dev/console doesn't do CTRL-C, etc)\n-p	Power off instead of rebooting when command exits\n-r	Restart child when it exits\n-3	Write 32 bit PID of each exiting reparented process to fd 3 of child\n	(Blocking writes, child must read to avoid eventual deadlock.)\n\nSpawns a single child process (because PID 1 has signals blocked)\nin its own session, reaps zombies until the child exits, then\nreboots the system (or powers off with -p, or restarts the child with -r).\n\nResponds to SIGUSR1 by halting the system, SIGUSR2 by powering off,\nand SIGTERM or SIGINT reboot."
+#define HELP_oneit "usage: oneit [-prn3] [-c CONSOLE] [COMMAND...]\n\nSimple init program that runs a single supplied command line with a\ncontrolling tty (so CTRL-C can kill it).\n\n-c	Which console device to use (/dev/console doesn't do CTRL-C, etc)\n-p	Power off instead of rebooting when command exits\n-r	Restart child when it exits\n-n	No reboot, just relaunch command line\n-3	Write 32 bit PID of each exiting reparented process to fd 3 of child\n	(Blocking writes, child must read to avoid eventual deadlock.)\n\nSpawns a single child process (because PID 1 has signals blocked)\nin its own session, reaps zombies until the child exits, then\nreboots the system (or powers off with -p, or restarts the child with -r).\n\nResponds to SIGUSR1 by halting the system, SIGUSR2 by powering off,\nand SIGTERM or SIGINT reboot."
 
 #define HELP_nsenter "usage: nsenter [-t pid] [-F] [-i] [-m] [-n] [-p] [-u] [-U] COMMAND...\n\nRun COMMAND in an existing (set of) namespace(s).\n\n-t	PID to take namespaces from    (--target)\n-F	don't fork, even if -p is used (--no-fork)\n\nThe namespaces to switch are:\n\n-i	SysV IPC: message queues, semaphores, shared memory (--ipc)\n-m	Mount/unmount tree (--mount)\n-n	Network address, sockets, routing, iptables (--net)\n-p	Process IDs and init, will fork unless -F is used (--pid)\n-u	Host and domain names (--uts)\n-U	UIDs, GIDs, capabilities (--user)\n\nIf -t isn't specified, each namespace argument must provide a path\nto a namespace file, ala \"-i=/proc/$PID/ns/ipc\""
 
@@ -232,7 +238,7 @@
 
 #define HELP_lspci_text "usage: lspci [-n] [-i FILE ]\n\n-n	Numeric output (repeat for readable and numeric)\n-i	PCI ID database (default /usr/share/misc/pci.ids)"
 
-#define HELP_lspci "usage: lspci [-ekm]\n\nList PCI devices.\n\n-e	Print all 6 digits in class\n-k	Print kernel driver\n-m	Machine parseable format"
+#define HELP_lspci "usage: lspci [-ekm]\n\nList PCI devices.\n\n-e	Print all 6 digits in class\n-k	Print kernel driver\n-m	Machine readable format"
 
 #define HELP_lsmod "usage: lsmod\n\nDisplay the currently loaded modules, their sizes and their dependencies."
 
@@ -260,9 +266,9 @@
 
 #define HELP_i2cdetect "usage: i2cdetect [-ary] BUS [FIRST LAST]\nusage: i2cdetect -F BUS\nusage: i2cdetect -l\n\nDetect i2c devices.\n\n-a	All addresses (0x00-0x7f rather than 0x03-0x77)\n-F	Show functionality\n-l	List all buses\n-r	Probe with SMBus Read Byte\n-y	Answer \"yes\" to confirmation prompts (for script use)"
 
-#define HELP_hwclock "usage: hwclock [-rswtluf]\n\nGet/set the hardware clock.\n\n-f FILE	Use specified device file instead of /dev/rtc (--rtc)\n-l	Hardware clock uses localtime (--localtime)\n-r	Show hardware clock time (--show)\n-s	Set system time from hardware clock (--hctosys)\n-t	Set the system time based on the current timezone (--systz)\n-u	Hardware clock uses UTC (--utc)\n-w	Set hardware clock from system time (--systohc)"
+#define HELP_hwclock "usage: hwclock [-rswtluf]\n\nGet/set the hardware clock.\n\n-f FILE	Use specified device file instead of /dev/rtc0 (--rtc)\n-l	Hardware clock uses localtime (--localtime)\n-r	Show hardware clock time (--show)\n-s	Set system time from hardware clock (--hctosys)\n-t	Set the system time based on the current timezone (--systz)\n-u	Hardware clock uses UTC (--utc)\n-w	Set hardware clock from system time (--systohc)"
 
-#define HELP_hexedit "usage: hexedit FILENAME\n\nHexadecimal file editor. All changes are written to disk immediately.\n\n-r	Read only (display but don't edit)\n\nKeys:\nArrows        Move left/right/up/down by one line/column\nPg Up/Pg Dn   Move up/down by one page\n0-9, a-f      Change current half-byte to hexadecimal value\nu             Undo\nq/^c/^d/<esc> Quit"
+#define HELP_hexedit "usage: hexedit FILE\n\nHexadecimal file editor/viewer. All changes are written to disk immediately.\n\n-r	Read only (display but don't edit)\n\nKeys:\nArrows         Move left/right/up/down by one line/column\nPgUp/PgDn      Move up/down by one page\nHome/End       Start/end of line (start/end of file with ctrl)\n0-9, a-f       Change current half-byte to hexadecimal value\n^J or :        Jump (+/- for relative offset, otherwise absolute address)\n^F or /        Find string (^G/n: next, ^D/p: previous match)\nu              Undo\nq/^C/^Q/Esc    Quit"
 
 #define HELP_help "usage: help [-ahu] [COMMAND]\n\n-a	All commands\n-u	Usage only\n-h	HTML output\n\nShow usage information for toybox commands.\nRun \"toybox\" with no arguments for a list of available commands."
 
@@ -288,7 +294,7 @@
 
 #define HELP_dos2unix "usage: dos2unix [FILE...]\n\nConvert newline format from dos \"\\r\\n\" to unix \"\\n\".\nIf no files listed copy from stdin, \"-\" is a synonym for stdin."
 
-#define HELP_devmem "usage: devmem ADDR [WIDTH [DATA]]\n\nRead/write physical address via /dev/mem.\n\nWIDTH is 1, 2, 4, or 8 bytes (default 4)."
+#define HELP_devmem "usage: devmem ADDR [WIDTH [DATA]]\n\nRead/write physical address. WIDTH is 1, 2, 4, or 8 bytes (default 4).\nPrefix ADDR with 0x for hexadecimal, output is in same base as address."
 
 #define HELP_count "usage: count\n\nCopy stdin to stdout, displaying simple progress indicator to stderr."
 
@@ -312,6 +318,10 @@
 
 #define HELP_blkid "usage: blkid [-s TAG] [-UL] DEV...\n\nPrint type, label and UUID of filesystem on a block device or image.\n\n-U	Show UUID only (or device with that UUID)\n-L	Show LABEL only (or device with that LABEL)\n-s TAG	Only show matching tags (default all)"
 
+#define HELP_blkdiscard "usage: blkdiscard [-olszf] DEVICE\n\nDiscard device sectors.\n\n-o, --offset OFF	Byte offset to start discarding at (default 0)\n-l, --length LEN	Bytes to discard (default all)\n-s, --secure		Perform secure discard\n-z, --zeroout		Zero-fill rather than discard\n-f, --force		Disable check for mounted filesystem\n\nOFF and LEN must be aligned to the device sector size.\nBy default entire device is discarded.\nWARNING: All discarded data is permanently lost!"
+
+#define HELP_base32 "usage: base32 [-di] [-w COLUMNS] [FILE...]\n\nEncode or decode in base32.\n\n-d	Decode\n-i	Ignore non-alphabetic characters\n-w	Wrap output at COLUMNS (default 76 or 0 for no wrap)"
+
 #define HELP_base64 "usage: base64 [-di] [-w COLUMNS] [FILE...]\n\nEncode or decode in base64.\n\n-d	Decode\n-i	Ignore non-alphabetic characters\n-w	Wrap output at COLUMNS (default 76 or 0 for no wrap)"
 
 #define HELP_ascii "usage: ascii\n\nDisplay ascii character set."
@@ -328,6 +338,8 @@
 
 #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"
 
+#define HELP_unicode "usage: unicode [[min]-max]\n\nConvert between Unicode code points and UTF-8, in both directions."
+
 #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 ms (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"
 
 #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"
@@ -338,7 +350,7 @@
 
 #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)"
 
-#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server"
+#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server."
 
 #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP            IP to listen on, 0 = all\nPORT          Port to listen on\nPROG ARGS     Program to run\n-l NAME       Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N          Handle up to N (> 0) connections simultaneously\n-b N          (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG]    (TCP Only) Allow only up to N (> 0) connections from the same IP\n              New connections from this IP address are closed\n              immediately. MSG is written to the peer before close\n-h            Look up peer's hostname\n-E            Don't set up environment variables\n-v            Verbose"
 
@@ -348,19 +360,39 @@
 
 #define HELP_stty "usage: stty [-ag] [-F device] SETTING...\n\nGet/set terminal configuration.\n\n-F	Open device instead of stdin\n-a	Show all current settings (default differences from \"sane\")\n-g	Show all current settings usable as input to stty\n\nSpecial characters (syntax ^c or undef): intr quit erase kill eof eol eol2\nswtch start stop susp rprnt werase lnext discard\n\nControl/input/output/local settings as shown by -a, '-' prefix to disable\n\nCombo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane\n\nN	set input and output speed (ispeed N or ospeed N for just one)\ncols N	set number of columns\nrows N	set number of rows\nline N	set line discipline\nmin N	set minimum chars per read\ntime N	set read timeout\nspeed	show speed only\nsize	show size only"
 
+#define HELP_wait "usage: wait [-n] [ID...]\n\nWait for background processes to exit, returning its exit code.\nID can be PID or job, with no IDs waits for all backgrounded processes.\n\n-n	Wait for next process to exit"
+
+#define HELP_source "usage: source FILE [ARGS...]\n\nRead FILE and execute commands. Any ARGS become positional parameters."
+
+#define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
+
+#define HELP_local "usage: local [NAME[=VALUE]...]\n\nCreate a local variable that lasts until return from this function.\nWith no arguments lists local variables in current function context.\nTODO: implement \"declare\" options."
+
+#define HELP_jobs "usage: jobs [-lnprs] [%JOB | -x COMMAND...]\n\nList running/stopped background jobs.\n\n-l Include process ID in list\n-n Show only new/changed processes\n-p Show process IDs only\n-r Show running processes\n-s Show stopped processes"
+
+#define HELP_export "usage: export [-n] [NAME[=VALUE]...]\n\nMake variables available to child processes. NAME exports existing local\nvariable(s), NAME=VALUE sets and exports.\n\n-n	Unexport. Turn listed variable(s) into local variables.\n\nWith no arguments list exported variables/attributes as \"declare\" statements."
+
+#define HELP_exec "usage: exec [-cl] [-a NAME] COMMAND...\n\n-a	set argv[0] to NAME\n-c	clear environment\n-l	prepend - to argv[0]"
+
+#define HELP_eval "usage: eval COMMAND...\n\nExecute (combined) arguments as a shell command."
+
+#define HELP_unset "usage: unset [-fvn] NAME...\n\n-f	NAME is a function\n-v	NAME is a variable\n-n	dereference NAME and unset that"
+
+#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f	NAME is a function\n-v	NAME is a variable\n-n	don't follow name reference\n\nOPTIONs:\n  history - enable command history"
+
 #define HELP_exit "usage: exit [status]\n\nExit shell.  If no return value supplied on command line, use value\nof most recent command, or 0 if none."
 
 #define HELP_cd "usage: cd [-PL] [path]\n\nChange current directory.  With no arguments, go $HOME.\n\n-P	Physical path: resolve symlinks in path\n-L	Local path: .. trims directories off $PWD (default)"
 
 #define HELP_sh "usage: sh [-c command] [script]\n\nCommand shell.  Runs a shell script, or reads input interactively\nand responds to it.\n\n-c	command line to execute\n-i	interactive mode (default when STDIN is a tty)"
 
-#define HELP_route "usage: route [-ne] [-A [46]] [add|del TARGET [OPTIONS]]\n\nDisplay, add or delete network routes in the \"Forwarding Information Base\".\n\n-n	Show numerical addresses (no DNS lookups)\n-e	display netstat fields\n\nRouting means sending packets out a network interface to an address.\nThe kernel can tell where to send packets one hop away by examining each\ninterface's address and netmask, so the most common use of this command\nis to identify a \"gateway\" that forwards other traffic.\n\nAssigning an address to an interface automatically creates an appropriate\nnetwork route (\"ifconfig eth0 10.0.2.15/8\" does \"route add 10.0.0.0/8 eth0\"\nfor you), although some devices (such as loopback) won't show it in the\ntable. For machines more than one hop away, you need to specify a gateway\n(ala \"route add default gw 10.0.2.2\").\n\nThe address \"default\" is a wildcard address (0.0.0.0/0) matching all\npackets without a more specific route.\n\nAvailable OPTIONS include:\nreject   - blocking route (force match failure)\ndev NAME - force packets out this interface (ala \"eth0\")\nnetmask  - old way of saying things like ADDR/24\ngw ADDR  - forward packets to gateway ADDR"
+#define HELP_route "usage: route [-ne] [-A [inet|inet6]] [add|del TARGET [OPTIONS]]\n\nDisplay, add or delete network routes in the \"Forwarding Information Base\",\nwhich send packets out a network interface to an address.\n\n-n	Show numerical addresses (no DNS lookups)\n-e	display netstat fields\n\nAssigning an address to an interface automatically creates an appropriate\nnetwork route (\"ifconfig eth0 10.0.2.15/8\" does \"route add 10.0.0.0/8 eth0\"\nfor you), although some devices (such as loopback) won't show it in the\ntable. For machines more than one hop away, you need to specify a gateway\n(ala \"route add default gw 10.0.2.2\").\n\nThe address \"default\" is a wildcard address (0.0.0.0/0) matching all\npackets without a more specific route.\n\nAvailable OPTIONS include:\nreject   - blocking route (force match failure)\ndev NAME - force matching packets out this interface (ala \"eth0\")\nnetmask  - old way of saying things like ADDR/24\ngw ADDR  - forward packets to gateway ADDR"
 
-#define HELP_readelf "usage: readelf [-adhlnSsW] [-p SECTION] [-x SECTION] [file...]\n\nDisplays information about ELF files.\n\n-a	Equivalent to -dhlnSs\n-d	Show dynamic section\n-h	Show ELF header\n-l	Show program headers\n-n	Show notes\n-p S	Dump strings found in named/numbered section\n-S	Show section headers\n-s	Show symbol tables (.dynsym and .symtab)\n-W	Don't truncate fields (default in toybox)\n-x S	Hex dump of named/numbered section\n\n--dyn-syms	Show just .dynsym symbol table"
+#define HELP_readelf "usage: readelf [-adehlnSs] [-p SECTION] [-x SECTION] [file...]\n\nDisplays information about ELF files.\n\n-a	Equivalent to -dhlnSs\n-d	Show dynamic section\n-e	Headers (equivalent to -hlS)\n-h	Show ELF header\n-l	Show program headers\n-n	Show notes\n-p S	Dump strings found in named/numbered section\n-S	Show section headers\n-s	Show symbol tables (.dynsym and .symtab)\n-x S	Hex dump of named/numbered section\n\n--dyn-syms	Show just .dynsym symbol table"
 
-#define HELP_deallocvt "usage: deallocvt [N]\n\nDeallocate unused virtual terminal /dev/ttyN, or all unused consoles."
+#define HELP_deallocvt "usage: deallocvt [NUM]\n\nDeallocate unused virtual terminals, either a specific /dev/ttyNUM, or all."
 
-#define HELP_openvt "usage: openvt [-c N] [-sw] [command [command_options]]\n\nstart a program on a new virtual terminal (VT)\n\n-c N  Use VT N\n-s    Switch to new VT\n-w    Wait for command to exit\n\nif -sw used together, switch back to originating VT when command completes"
+#define HELP_openvt "usage: openvt [-c NUM] [-sw] [COMMAND...]\n\nStart a program on a new virtual terminal.\n\n-c NUM  Use VT NUM\n-s    Switch to new VT\n-w    Wait for command to exit\n\nTogether -sw switch back to originating VT when command completes."
 
 #define HELP_more "usage: more [FILE...]\n\nView FILE(s) (or stdin) one screenfull at a time."
 
@@ -402,7 +434,7 @@
 
 #define HELP_groupadd "usage: groupadd [-S] [-g GID] [USER] GROUP\n\nAdd a group or add a user to a group\n\n  -g GID Group id\n  -S     Create a system group"
 
-#define HELP_getty "usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]\n\n-h    Enable hardware RTS/CTS flow control\n-L    Set CLOCAL (ignore Carrier Detect state)\n-m    Get baud rate from modem's CONNECT status message\n-n    Don't prompt for login name\n-w    Wait for CR or LF before sending /etc/issue\n-i    Don't display /etc/issue\n-f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue\n-l LOGIN  Invoke LOGIN instead of /bin/login\n-t SEC    Terminate after SEC if no login name is read\n-I INITSTR  Send INITSTR before anything else\n-H HOST    Log HOST into the utmp file as the hostname"
+#define HELP_getty "usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]\n\nWait for a modem to dial into serial port, adjust baud rate, call login.\n\n-h    Enable hardware RTS/CTS flow control\n-L    Set CLOCAL (ignore Carrier Detect state)\n-m    Get baud rate from modem's CONNECT status message\n-n    Don't prompt for login name\n-w    Wait for CR or LF before sending /etc/issue\n-i    Don't display /etc/issue\n-f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue\n-l LOGIN  Invoke LOGIN instead of /bin/login\n-t SEC    Terminate after SEC if no login name is read\n-I INITSTR  Send INITSTR before anything else\n-H HOST    Log HOST into the utmp file as the hostname"
 
 #define HELP_getopt "usage: getopt [OPTIONS] [--] ARG...\n\nParse command-line options for use in shell scripts.\n\n-a	Allow long options starting with a single -.\n-l OPTS	Specify long options.\n-n NAME	Command name for error messages.\n-o OPTS	Specify short options.\n-T	Test whether this is a modern getopt.\n-u	Output options unquoted."
 
@@ -432,6 +464,8 @@
 
 #define HELP_crond "usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]\n\nA daemon to execute scheduled commands.\n\n-b Background (default)\n-c crontab dir\n-d Set log level, log to stderr\n-f Foreground\n-l Set log level. 0 is the most verbose, default 8\n-S Log to syslog (default)\n-L Log to file"
 
+#define HELP_chsh "usage: chsh [-s SHELL] [USER]\n\nChange user's login shell.\n\n-s	Use SHELL instead of prompting\n\nNon-root users can only change their own shell to one listed in /etc/shells."
+
 #define HELP_brctl "usage: brctl COMMAND [BRIDGE [INTERFACE]]\n\nManage ethernet bridges\n\nCommands:\nshow                  Show a list of bridges\naddbr BRIDGE          Create BRIDGE\ndelbr BRIDGE          Delete BRIDGE\naddif BRIDGE IFACE    Add IFACE to BRIDGE\ndelif BRIDGE IFACE    Delete IFACE from BRIDGE\nsetageing BRIDGE TIME Set ageing time\nsetfd BRIDGE TIME     Set bridge forward delay\nsethello BRIDGE TIME  Set hello time\nsetmaxage BRIDGE TIME Set max message age\nsetpathcost BRIDGE PORT COST   Set path cost\nsetportprio BRIDGE PORT PRIO   Set port priority\nsetbridgeprio BRIDGE PRIO      Set bridge priority\nstp BRIDGE [1/yes/on|0/no/off] STP on/off"
 
 #define HELP_bootchartd "usage: bootchartd {start [PROG ARGS]}|stop|init\n\nCreate /var/log/bootlog.tgz with boot chart data\n\nstart: start background logging; with PROG, run PROG,\n       then kill logging with USR1\nstop:  send USR1 to all bootchartd processes\ninit:  start background logging; stop when getty/xdm is seen\n      (for init scripts)\n\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
@@ -442,7 +476,7 @@
 
 #define HELP_arp "usage: arp\n[-vn] [-H HWTYPE] [-i IF] -a [HOSTNAME]\n[-v]              [-i IF] -d HOSTNAME [pub]\n[-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]\n[-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub\n[-v]  [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub\n\nManipulate ARP cache\n\n-a    Display (all) hosts\n-s    Set new ARP entry\n-d    Delete a specified entry\n-v    Verbose\n-n    Don't resolve names\n-i IF Network interface\n-D    Read <hwaddr> from given device\n-A,-p AF  Protocol family\n-H    HWTYPE Hardware address type"
 
-#define HELP_xargs "usage: xargs [-0prt] [-s NUM] [-n NUM] [-E STR] COMMAND...\n\nRun command line one or more times, appending arguments from stdin.\n\nIf COMMAND exits with 255, don't launch another even if arguments remain.\n\n-0	Each argument is NULL terminated, no whitespace or quote processing\n-E	Stop at line matching string\n-n	Max number of arguments per command\n-o	Open tty for COMMAND's stdin (default /dev/null)\n-p	Prompt for y/n from tty before running each command\n-r	Don't run command with empty input (otherwise always run command once)\n-s	Size in bytes per command line\n-t	Trace, print command line to stderr"
+#define HELP_xargs "usage: xargs [-0prt] [-snE STR] COMMAND...\n\nRun command line one or more times, appending arguments from stdin.\n\nIf COMMAND exits with 255, don't launch another even if arguments remain.\n\n-0	Each argument is NULL terminated, no whitespace or quote processing\n-E	Stop at line matching string\n-n	Max number of arguments per command\n-o	Open tty for COMMAND's stdin (default /dev/null)\n-p	Prompt for y/n from tty before running each command\n-P	Parallel processes (default 1)\n-r	Don't run with empty input (otherwise always run command once)\n-s	Size in bytes per command line\n-t	Trace, print command line to stderr"
 
 #define HELP_who "usage: who\n\nPrint information about logged in users."
 
@@ -460,7 +494,7 @@
 
 #define HELP_arch "usage: arch\n\nPrint machine (hardware) name, same as uname -m."
 
-#define HELP_ulimit "usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]\n\nPrint or set resource limits for process number PID. If no LIMIT specified\n(or read-only -ap selected) display current value (sizes in bytes).\nDefault is ulimit -P $PPID -Sf\" (show soft filesize of your shell).\n\n-S  Set/show soft limit          -H  Set/show hard (maximum) limit\n-a  Show all limits              -c  Core file size\n-d  Process data segment         -e  Max scheduling priority\n-f  Output file size             -i  Pending signal count\n-l  Locked memory                -m  Resident Set Size\n-n  Number of open files         -p  Pipe buffer\n-q  Posix message queue          -r  Max Real-time priority\n-R  Realtime latency (usec)      -s  Stack size\n-t  Total CPU time (in seconds)  -u  Maximum processes (under this UID)\n-v  Virtual memory size          -P  PID to affect (default $PPID)"
+#define HELP_ulimit "usage: ulimit [-P PID] [-SHRacdefilmnpqrstuv] [LIMIT]\n\nPrint or set resource limits for process number PID. If no LIMIT specified\n(or read-only -ap selected) display current value (sizes in bytes).\nDefault is ulimit -P $PPID -Sf\" (show soft filesize of your shell).\n\n-P  PID to affect (default $PPID)  -a  Show all limits\n-S  Set/show soft limit            -H  Set/show hard (maximum) limit\n\n-c  Core file size (blocks)        -d  Process data segment (KiB)\n-e  Max scheduling priority        -f  File size (KiB)\n-i  Pending signal count           -l  Locked memory (KiB)\n-m  Resident Set Size (KiB)        -n  Number of open files\n-p  Pipe buffer (512 bytes)        -q  POSIX message queues\n-r  Max realtime priority          -R  Realtime latency (us)\n-s  Stack size (KiB)               -t  Total CPU time (s)\n-u  Maximum processes (this UID)   -v  Virtual memory size (KiB)"
 
 #define HELP_tty "usage: tty [-s]\n\nShow filename of terminal connected to stdin.\n\nPrints \"not a tty\" and exits with nonzero status if no terminal\nis connected to stdin.\n\n-s	Silent, exit code only"
 
@@ -470,11 +504,11 @@
 
 #define HELP_time "usage: time [-pv] COMMAND...\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 format output (default)\n-v	Verbose"
 
-#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)"
+#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   -k  sticky bit\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\n\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)"
 
 #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"
 
-#define HELP_tar "usage: tar [-cxt] [-fvohmjkOS] [-XTCf NAME] [FILE...]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc  Create                x  Extract               t  Test (list)\nf  tar FILE (default -)  C  Change to DIR first   v  Verbose display\no  Ignore owner          h  Follow symlinks       m  Ignore mtime\nJ  xz compression        j  bzip2 compression     z  gzip compression\nO  Extract to stdout     X  exclude names in FILE T  include names in FILE\n\n--exclude        FILENAME to exclude    --full-time   Show seconds with -tv\n--mode MODE      Adjust modes           --mtime TIME  Override timestamps\n--owner NAME     Set file owner to NAME --group NAME  Set file group to NAME\n--sparse         Record sparse files\n--restrict       All archive contents must extract under one subdirctory\n--numeric-owner  Save/use/display uid and gid, not user/group name\n--no-recursion   Don't store directory contents"
+#define HELP_tar "usage: tar [-cxt] [-fvohmjkOS] [-XTCf NAME] [FILE...]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc  Create                x  Extract               t  Test (list)\nf  tar FILE (default -)  C  Change to DIR first   v  Verbose display\no  Ignore owner          h  Follow symlinks       m  Ignore mtime\nJ  xz compression        j  bzip2 compression     z  gzip compression\nO  Extract to stdout     X  exclude names in FILE T  include names in FILE\n\n--exclude        FILENAME to exclude    --full-time   Show seconds with -tv\n--mode MODE      Adjust modes           --mtime TIME  Override timestamps\n--owner NAME     Set file owner to NAME --group NAME  Set file group to NAME\n--sparse         Record sparse files\n--restrict       All archive contents must extract under one subdirectory\n--numeric-owner  Save/use/display uid and gid, not user/group name\n--no-recursion   Don't store directory contents\n-I PROG          Filter through PROG to compress or PROG -d to decompress"
 
 #define HELP_tail "usage: tail [-n|c NUMBER] [-f] [FILE...]\n\nCopy last lines from files to stdout. If no files listed, copy from\nstdin. Filename \"-\" is a synonym for stdin.\n\n-n	Output the last NUMBER lines (default 10), +X counts from start\n-c	Output the last NUMBER bytes, +NUMBER counts from start\n-f	Follow FILE(s), waiting for more data to be appended"
 
@@ -486,7 +520,7 @@
 
 #define HELP_sleep "usage: sleep DURATION\n\nWait before exiting.\n\nDURATION can be a decimal fraction. An optional suffix can be \"m\"\n(minutes), \"h\" (hours), \"d\" (days), or \"s\" (seconds, the default)."
 
-#define HELP_sed "usage: sed [-inrzE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]\n\nStream editor. Apply one or more editing SCRIPTs to each line of input\n(from FILE or stdin) producing output (by default to stdout).\n\n-e	Add SCRIPT to list\n-f	Add contents of SCRIPT_FILE to list\n-i	Edit each file in place (-iEXT keeps backup file with extension EXT)\n-n	No default output (use the p command to output matched lines)\n-r	Use extended regular expression syntax\n-E	POSIX alias for -r\n-s	Treat input files separately (implied by -i)\n-z	Use \\0 rather than \\n as the input line separator\n\nA SCRIPT is a series of one or more COMMANDs separated by newlines or\nsemicolons. All -e SCRIPTs are concatenated together as if separated\nby newlines, followed by all lines from -f SCRIPT_FILEs, in order.\nIf no -e or -f SCRIPTs are specified, the first argument is the SCRIPT.\n\nEach COMMAND may be preceded by an address which limits the command to\napply only to the specified line(s). Commands without an address apply to\nevery line. Addresses are of the form:\n\n  [ADDRESS[,ADDRESS]][!]COMMAND\n\nThe ADDRESS may be a decimal line number (starting at 1), a /regular\nexpression/ within a pair of forward slashes, or the character \"$\" which\nmatches the last line of input. (In -s or -i mode this matches the last\nline of each file, otherwise just the last line of the last file.) A single\naddress matches one line, a pair of comma separated addresses match\neverything from the first address to the second address (inclusive). If\nboth addresses are regular expressions, more than one range of lines in\neach file can match. The second address can be +N to end N lines later.\n\nREGULAR EXPRESSIONS in sed are started and ended by the same character\n(traditionally / but anything except a backslash or a newline works).\nBackslashes may be used to escape the delimiter if it occurs in the\nregex, and for the usual printf escapes (\\abcefnrtv and octal, hex,\nand unicode). An empty regex repeats the previous one. ADDRESS regexes\n(above) require the first delimiter to be escaped with a backslash when\nit isn't a forward slash (to distinguish it from the COMMANDs below).\n\nSed mostly operates on individual lines one at a time. It reads each line,\nprocesses it, and either writes it to the output or discards it before\nreading the next line. Sed can remember one additional line in a separate\nbuffer (using the h, H, g, G, and x commands), and can read the next line\nof input early (using the n and N command), but other than that command\nscripts operate on individual lines of text.\n\nEach COMMAND starts with a single character. The following commands take\nno arguments:\n\n  !  Run this command when the test _didn't_ match.\n\n  {  Start a new command block, continuing until a corresponding \"}\".\n     Command blocks may nest. If the block has an address, commands within\n     the block are only run for lines within the block's address range.\n\n  }  End command block (this command cannot have an address)\n\n  d  Delete this line and move on to the next one\n     (ignores remaining COMMANDs)\n\n  D  Delete one line of input and restart command SCRIPT (same as \"d\"\n     unless you've glued lines together with \"N\" or similar)\n\n  g  Get remembered line (overwriting current line)\n\n  G  Get remembered line (appending to current line)\n\n  h  Remember this line (overwriting remembered line)\n\n  H  Remember this line (appending to remembered line, if any)\n\n  l  Print line, escaping \\abfrtv (but not newline), octal escaping other\n     nonprintable characters, wrapping lines to terminal width with a\n     backslash, and appending $ to actual end of line.\n\n  n  Print default output and read next line, replacing current line\n     (If no next line available, quit processing script)\n\n  N  Append next line of input to this line, separated by a newline\n     (This advances the line counter for address matching and \"=\", if no\n     next line available quit processing script without default output)\n\n  p  Print this line\n\n  P  Print this line up to first newline (from \"N\")\n\n  q  Quit (print default output, no more commands processed or lines read)\n\n  x  Exchange this line with remembered line (overwrite in both directions)\n\n  =  Print the current line number (followed by a newline)\n\nThe following commands (may) take an argument. The \"text\" arguments (to\nthe \"a\", \"b\", and \"c\" commands) may end with an unescaped \"\\\" to append\nthe next line (for which leading whitespace is not skipped), and also\ntreat \";\" as a literal character (use \"\\;\" instead).\n\n  a [text]   Append text to output before attempting to read next line\n\n  b [label]  Branch, jumps to :label (or with no label, to end of SCRIPT)\n\n  c [text]   Delete line, output text at end of matching address range\n             (ignores remaining COMMANDs)\n\n  i [text]   Print text\n\n  r [file]   Append contents of file to output before attempting to read\n             next line.\n\n  s/S/R/F    Search for regex S, replace matched text with R using flags F.\n             The first character after the \"s\" (anything but newline or\n             backslash) is the delimiter, escape with \\ to use normally.\n\n             The replacement text may contain \"&\" to substitute the matched\n             text (escape it with backslash for a literal &), or \\1 through\n             \\9 to substitute a parenthetical subexpression in the regex.\n             You can also use the normal backslash escapes such as \\n and\n             a backslash at the end of the line appends the next line.\n\n             The flags are:\n\n             [0-9]    A number, substitute only that occurrence of pattern\n             g        Global, substitute all occurrences of pattern\n             i        Ignore case when matching\n             p        Print the line if match was found and replaced\n             w [file] Write (append) line to file if match replaced\n\n  t [label]  Test, jump to :label only if an \"s\" command found a match in\n             this line since last test (replacing with same text counts)\n\n  T [label]  Test false, jump only if \"s\" hasn't found a match.\n\n  w [file]   Write (append) line to file\n\n  y/old/new/ Change each character in 'old' to corresponding character\n             in 'new' (with standard backslash escapes, delimiter can be\n             any repeated character except \\ or \\n)\n\n  : [label]  Labeled target for jump commands\n\n  #  Comment, ignore rest of this line of SCRIPT\n\nDeviations from POSIX: allow extended regular expressions with -r,\nediting in place with -i, separate with -s, NUL-separated input with -z,\nprintf escapes in text, line continuations, semicolons after all commands,\n2-address anywhere an address is allowed, \"T\" command, multiline\ncontinuations for [abc], \\; to end [abc] argument before end of line."
+#define HELP_sed "usage: sed [-inrszE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]\n\nStream editor. Apply editing SCRIPTs to lines of input.\n\n-e	Add SCRIPT to list\n-f	Add contents of SCRIPT_FILE to list\n-i	Edit each file in place (-iEXT keeps backup file with extension EXT)\n-n	No default output (use the p command to output matched lines)\n-r	Use extended regular expression syntax\n-E	POSIX alias for -r\n-s	Treat input files separately (implied by -i)\n-z	Use \\0 rather than \\n as input line separator\n\nA SCRIPT is one or more COMMANDs separated by newlines or semicolons.\nAll -e SCRIPTs are combined as if separated by newlines, followed by all -f\nSCRIPT_FILEs. If no -e or -f then first argument is the SCRIPT.\n\nCOMMANDs apply to every line unless prefixed with an ADDRESS of the form:\n\n  [ADDRESS[,ADDRESS]][!]COMMAND\n\nADDRESS is a line number (starting at 1), a /REGULAR EXPRESSION/, or $ for\nlast line (-s or -i makes it last line of each file). One address matches one\nline, ADDRESS,ADDRESS matches from first to second inclusive. Two regexes can\nmatch multiple ranges. ADDRESS,+N ends N lines later. ! inverts the match.\n\nREGULAR EXPRESSIONS start and end with the same character (anything but\nbackslash or newline). To use the delimiter in the regex escape it with a\nbackslash, and printf escapes (\\abcefnrtv and octal, hex, and unicode) work.\nAn empty regex repeats the previous one. ADDRESS regexes require any\nfirst delimiter except / to be \\escaped to distinguish it from COMMANDs.\n\nSed reads each line of input, processes it, and writes it out or discards it\nbefore reading the next. Sed can remember one additional line in a separate\nbuffer (the h, H, g, G, and x commands), and can read the next line of input\nearly (the n and N commands), but otherwise operates on individual lines.\n\nEach COMMAND starts with a single character. Commands with no arguments are:\n\n  !  Run this command when the ADDRESS _didn't_ match.\n  {  Start new command block, continuing until a corresponding \"}\".\n     Command blocks nest and can have ADDRESSes applying to the whole block.\n  }  End command block (this COMMAND cannot have an address)\n  d  Delete this line and move on to the next one\n     (ignores remaining COMMANDs)\n  D  Delete one line of input and restart command SCRIPT (same as \"d\"\n     unless you've glued lines together with \"N\" or similar)\n  g  Get remembered line (overwriting current line)\n  G  Get remembered line (appending to current line)\n  h  Remember this line (overwriting remembered line)\n  H  Remember this line (appending to remembered line, if any)\n  l  Print line escaping \\abfrtv (but not \\n), octal escape other nonprintng\n     chars, wrap lines to terminal width with \\, append $ to end of line.\n  n  Print default output and read next line over current line (quit at EOF)\n  N  Append \\n and next line of input to this line. Quit at EOF without\n     default output. Advances line counter for ADDRESS and \"=\".\n  p  Print this line\n  P  Print this line up to first newline (from \"N\")\n  q  Quit (print default output, no more commands processed or lines read)\n  x  Exchange this line with remembered line (overwrite in both directions)\n  =  Print the current line number (plus newline)\n  #  Comment, ignores rest of this line of SCRIPT (until newline)\n\nCommands that take an argument:\n\n  : LABEL    Target for jump commands\n  a TEXT     Append text to output before reading next line\n  b LABEL    Branch, jumps to :LABEL (with no LABEL to end of SCRIPT)\n  c TEXT     Delete matching ADDRESS range and output TEXT instead\n  i TEXT     Insert text (output immediately)\n  r FILE     Append contents of FILE to output before reading next line.\n  s/S/R/F    Search for regex S replace match with R using flags F. Delimiter\n             is anything but \\n or \\, escape with \\ to use in S or R. Printf\n             escapes work. Unescaped & in R becomes full matched text, \\1\n             through \\9 = parenthetical subexpression from S. \\ at end of\n             line appends next line of SCRIPT. The flags in F are:\n             [0-9]    A number N, substitute only Nth match\n             g        Global, substitute all matches\n             i/I      Ignore case when matching\n             p        Print resulting line when match found and replaced\n             w [file] Write (append) line to file when match replaced\n  t LABEL    Test, jump if s/// command matched this line since last test\n  T LABEL    Test false, jump to :LABEL only if no s/// found a match\n  w FILE     Write (append) line to file\n  y/old/new/ Change each character in 'old' to corresponding character\n             in 'new' (with standard backslash escapes, delimiter can be\n             any repeated character except \\ or \\n)\n\nThe TEXT arguments (to a c i) may end with an unescaped \"\\\" to append\nthe next line (leading whitespace is not skipped), and treat \";\" as a\nliteral character (use \"\\;\" instead)."
 
 #define HELP_rmdir "usage: rmdir [-p] [DIR...]\n\nRemove one or more directories.\n\n-p	Remove path\n--ignore-fail-on-non-empty	Ignore failures caused by non-empty directories"
 
@@ -558,7 +592,7 @@
 
 #define HELP_getconf "usage: getconf -a [PATH] | -l | NAME [PATH]\n\nGet system configuration values. Values from pathconf(3) require a path.\n\n-a	Show all (defaults to \"/\" if no path given)\n-l	List available value names (grouped by source)"
 
-#define HELP_find "usage: find [-HL] [DIR...] [<options>]\n\nSearch directories for matching files.\nDefault: search \".\", match all, -print matches.\n\n-H  Follow command line symlinks         -L  Follow all symlinks\n\nMatch filters:\n-name  PATTERN   filename with wildcards  (-iname case insensitive)\n-path  PATTERN   path name with wildcards (-ipath case insensitive)\n-user  UNAME     belongs to user UNAME     -nouser     user ID not known\n-group GROUP     belongs to group GROUP    -nogroup    group ID not known\n-perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents\n-size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem\n-links N         hardlink count            -atime N[u] accessed N units ago\n-ctime N[u]      created N units ago       -mtime N[u] modified N units ago\n-newer FILE      newer mtime than FILE     -mindepth N at least N dirs down\n-depth           ignore contents of dir    -maxdepth N at most N dirs down\n-inum N          inode number N            -empty      empty files and dirs\n-type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)\n-true            always true               -false      always false\n-context PATTERN security context\n-newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)\n\nNumbers N may be prefixed by a - (less than) or + (greater than). Units for\n-Xtime are d (days, default), h (hours), m (minutes), or s (seconds).\n\nCombine matches with:\n!, -a, -o, ( )    not, and, or, group expressions\n\nActions:\n-print  Print match with newline  -print0        Print match with null\n-exec   Run command with path     -execdir       Run command in file's dir\n-ok     Ask before exec           -okdir         Ask before execdir\n-delete Remove matching file/dir  -printf FORMAT Print using format string\n\nCommands substitute \"{}\" with matched file. End with \";\" to run each file,\nor \"+\" (next argument after \"{}\") to collect and run with multiple files.\n\n-printf FORMAT characters are \\ escapes and:\n%b  512 byte blocks used\n%f  basename            %g  textual gid          %G  numeric gid\n%i  decimal inode       %l  target of symlink    %m  octal mode\n%M  ls format type/mode %p  path to file         %P  path to file minus DIR\n%s  size in bytes       %T@ mod time as unixtime\n%u  username            %U  numeric uid          %Z  security context"
+#define HELP_find "usage: find [-HL] [DIR...] [<options>]\n\nSearch directories for matching files.\nDefault: search \".\", match all, -print matches.\n\n-H  Follow command line symlinks         -L  Follow all symlinks\n\nMatch filters:\n-name  PATTERN   filename with wildcards  (-iname case insensitive)\n-path  PATTERN   path name with wildcards (-ipath case insensitive)\n-user  UNAME     belongs to user UNAME     -nouser     user ID not known\n-group GROUP     belongs to group GROUP    -nogroup    group ID not known\n-perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents\n-size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem\n-links N         hardlink count            -atime N[u] accessed N units ago\n-ctime N[u]      created N units ago       -mtime N[u] modified N units ago\n-newer FILE      newer mtime than FILE     -mindepth N at least N dirs down\n-depth           ignore contents of dir    -maxdepth N at most N dirs down\n-inum N          inode number N            -empty      empty files and dirs\n-type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)\n-true            always true               -false      always false\n-context PATTERN security context          -executable access(X_OK) perm+ACL\n-newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)\n\nNumbers N may be prefixed by a - (less than) or + (greater than). Units for\n-Xtime are d (days, default), h (hours), m (minutes), or s (seconds).\n\nCombine matches with:\n!, -a, -o, ( )    not, and, or, group expressions\n\nActions:\n-print  Print match with newline  -print0        Print match with null\n-exec   Run command with path     -execdir       Run command in file's dir\n-ok     Ask before exec           -okdir         Ask before execdir\n-delete Remove matching file/dir  -printf FORMAT Print using format string\n\nCommands substitute \"{}\" with matched file. End with \";\" to run each file,\nor \"+\" (next argument after \"{}\") to collect and run with multiple files.\n\n-printf FORMAT characters are \\ escapes and:\n%b  512 byte blocks used\n%f  basename            %g  textual gid          %G  numeric gid\n%i  decimal inode       %l  target of symlink    %m  octal mode\n%M  ls format type/mode %p  path to file         %P  path to file minus DIR\n%s  size in bytes       %T@ mod time as unixtime\n%u  username            %U  numeric uid          %Z  security context"
 
 #define HELP_file "usage: file [-bhLs] [FILE...]\n\nExamine the given files and describe their content types.\n\n-b	Brief (no filename)\n-h	Don't follow symlinks (default)\n-L	Follow symlinks\n-s	Show block/char device contents"
 
@@ -570,25 +604,23 @@
 
 #define HELP_echo "usage: echo [-neE] [ARG...]\n\nWrite each argument to stdout, with one space between each, followed\nby a newline.\n\n-n	No trailing newline\n-E	Print escape sequences literally (default)\n-e	Process the following escape sequences:\n	\\\\	Backslash\n	\\0NNN	Octal values (1 to 3 digits)\n	\\a	Alert (beep/flash)\n	\\b	Backspace\n	\\c	Stop output here (avoids trailing newline)\n	\\f	Form feed\n	\\n	Newline\n	\\r	Carriage return\n	\\t	Horizontal tab\n	\\v	Vertical tab\n	\\xHH	Hexadecimal values (1 to 2 digits)"
 
-#define HELP_du "usage: du [-d N] [-askxHLlmc] [FILE...]\n\nShow disk usage, space consumed by files and directories.\n\nSize in:\n-k	1024 byte blocks (default)\n-K	512 byte blocks (posix)\n-m	Megabytes\n-h	Human readable (e.g., 1K 243M 2G)\n\nWhat to show:\n-a	All files, not just directories\n-H	Follow symlinks on cmdline\n-L	Follow all symlinks\n-s	Only total size of each argument\n-x	Don't leave this filesystem\n-c	Cumulative total\n-d N	Only depth < N\n-l	Disable hardlink filter"
+#define HELP_du "usage: du [-d N] [-askxHLlmc] [FILE...]\n\nShow disk usage, space consumed by files and directories.\n\nSize in:\n-b	Apparent bytes (directory listing size, not space used)\n-k	1024 byte blocks (default)\n-K	512 byte blocks (posix)\n-m	Megabytes\n-h	Human readable (e.g., 1K 243M 2G)\n\nWhat to show:\n-a	All files, not just directories\n-H	Follow symlinks on cmdline\n-L	Follow all symlinks\n-s	Only total size of each argument\n-x	Don't leave this filesystem\n-c	Cumulative total\n-d N	Only depth < N\n-l	Disable hardlink filter"
 
 #define HELP_dirname "usage: dirname PATH...\n\nShow directory portion of path."
 
-#define HELP_df "usage: df [-HPkhi] [-t type] [FILE...]\n\nThe \"disk free\" command shows total/used/available disk space for\neach filesystem listed on the command line, or all currently mounted\nfilesystems.\n\n-a	Show all (including /proc and friends)\n-P	The SUSv3 \"Pedantic\" option\n-k	Sets units back to 1024 bytes (the default without -P)\n-h	Human readable (K=1024)\n-H	Human readable (k=1000)\n-i	Show inodes instead of blocks\n-t type	Display only filesystems of this type\n\nPedantic provides a slightly less useful output format dictated by Posix,\nand sets the units to 512 bytes instead of the default 1024 bytes."
+#define HELP_df "usage: df [-HPkhi] [-t type] [FILE...]\n\nThe \"disk free\" command shows total/used/available disk space for\neach filesystem listed on the command line, or all currently mounted\nfilesystems.\n\n-a	Show all (including /proc and friends)\n-P	The SUSv3 \"Pedantic\" option\n-k	Sets units back to 1024 bytes (the default without -P)\n-h	Human readable (K=1024)\n-H	Human readable (k=1000)\n-i	Show inodes instead of blocks\n-t type	Display only filesystems of this type\n\nPedantic provides a slightly less useful output format dictated by POSIX,\nand sets the units to 512 bytes instead of the default 1024 bytes."
 
-#define HELP_date "usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\nSupported input formats:\n\nMMDDhhmm[[CC]YY][.ss]     POSIX\n@UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01\nYYYY-MM-DD [hh:mm[:ss]]   ISO 8601\nhh:mm[:ss]                24-hour time today\n\nAll input formats can be preceded by TZ=\"id\" to set the input time zone\nseparately from the output time zone. Otherwise $TZ sets both.\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start sunday)   %W Week of year (0-53 start monday)\n%V Week of year (1-53 start monday, week < 4 days not part of this year)\n\n%F \"%Y-%m-%d\"     %R \"%H:%M\"        %T \"%H:%M:%S\"    %z numeric timezone\n%D \"%m/%d/%y\"     %r \"%I:%M:%S %p\"  %h \"%b\"          %s unix epoch time\n%x locale date    %X locale time    %c locale date/time"
+#define HELP_date "usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-I RES	ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\nSupported input formats:\n\nMMDDhhmm[[CC]YY][.ss]     POSIX\n@UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01\nYYYY-MM-DD [hh:mm[:ss]]   ISO 8601\nhh:mm[:ss]                24-hour time today\n\nAll input formats can be followed by fractional seconds, and/or a UTC\noffset such as -0800.\n\nAll input formats can be preceded by TZ=\"id\" to set the input time zone\nseparately from the output time zone. Otherwise $TZ sets both.\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start Sunday)   %W Week of year (0-53 start Monday)\n%V Week of year (1-53 start Monday, week < 4 days not part of this year)\n\n%F \"%Y-%m-%d\"   %R \"%H:%M\"        %T \"%H:%M:%S\"        %z  timezone (-0800)\n%D \"%m/%d/%y\"   %r \"%I:%M:%S %p\"  %h \"%b\"              %:z timezone (-08:00)\n%x locale date  %X locale time    %c locale date/time  %s  unix epoch time"
 
 #define HELP_cut "usage: cut [-Ds] [-bcfF LIST] [-dO DELIM] [FILE...]\n\nPrint selected parts of lines from each FILE to standard output.\n\nEach selection LIST is comma separated, either numbers (counting from 1)\nor dash separated ranges (inclusive, with X- meaning to end of line and -X\nfrom start). By default selection ranges are sorted and collated, use -D\nto prevent that.\n\n-b	Select bytes\n-c	Select UTF-8 characters\n-C	Select unicode columns\n-d	Use DELIM (default is TAB for -f, run of whitespace for -F)\n-D	Don't sort/collate selections or match -fF lines without delimiter\n-f	Select fields (words) separated by single DELIM character\n-F	Select fields separated by DELIM regex\n-O	Output delimiter (default one space for -F, input delim for -f)\n-s	Skip lines without delimiters"
 
-#define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -mdu -H newc]\n\nCopy files into and out of a \"newc\" format cpio archive.\n\n-F FILE	Use archive FILE instead of stdin/stdout\n-p DEST	Copy-pass mode, copy stdin file list to directory DEST\n-i	Extract from archive into file system (stdin=archive)\n-o	Create archive (stdin=list of files, stdout=archive)\n-t	Test files (list only, stdin=archive, stdout=list of files)\n-v	Verbose\n--no-preserve-owner (don't set ownership during extract)\n--trailer Add legacy trailer (prevents concatenation)"
+#define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -m -H newc]\n\nCopy files into and out of a \"newc\" format cpio archive.\n\n-F FILE	Use archive FILE instead of stdin/stdout\n-p DEST	Copy-pass mode, copy stdin file list to directory DEST\n-i	Extract from archive into file system (stdin=archive)\n-o	Create archive (stdin=list of files, stdout=archive)\n-t	Test files (list only, stdin=archive, stdout=list of files)\n-d	Create directories if needed\n-u	unlink existing files when extracting\n-v	Verbose\n--no-preserve-owner (don't set ownership during extract)"
 
-#define HELP_install "usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST\n\nCopy files and set attributes.\n\n-d	Act like mkdir -p\n-D	Create leading directories for DEST\n-g	Make copy belong to GROUP\n-m	Set permissions to MODE\n-o	Make copy belong to USER\n-p	Preserve timestamps\n-s	Call \"strip -p\"\n-v	Verbose"
+#define HELP_install "usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [-t TARGET] [SOURCE...] [DEST]\n\nCopy files and set attributes.\n\n-d	Act like mkdir -p\n-D	Create leading directories for DEST\n-g	Make copy belong to GROUP\n-m	Set permissions to MODE\n-o	Make copy belong to USER\n-p	Preserve timestamps\n-s	Call \"strip -p\"\n-t	Copy files to TARGET dir (no DEST)\n-v	Verbose"
 
-#define HELP_mv "usage: mv [-finTv] SOURCE... DEST\n\n-f	Force copy by deleting destination file\n-i	Interactive, prompt before overwriting existing DEST\n-n	No clobber (don't overwrite DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose"
+#define HELP_mv "usage: mv [-finTv] [-t TARGET] SOURCE... [DEST]\n\n-f	Force copy by deleting destination file\n-i	Interactive, prompt before overwriting existing DEST\n-n	No clobber (don't overwrite DEST)\n-t	Move to TARGET dir (no DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose"
 
-#define HELP_cp_preserve "--preserve takes either a comma separated list of attributes, or the first\nletter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above\n\nusage: cp [--preserve=motcxa] [-adfHiLlnPpRrsTv] SOURCE... DEST\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n-v	Verbose\n-T	DEST always treated as file, max 2 arguments\n-s	Symlink instead of copy\n-r	Synonym for -R\n-R	Recurse into subdirectories (DEST must be a directory)\n-p	Preserve timestamps, ownership, and mode\n-P	Do not follow symlinks [default]\n-n	No clobber (don't overwrite DEST)\n-l	Hard link instead of copy\n-L	Follow all symlinks\n-i	Interactive, prompt before overwriting existing DEST\n-H	Follow symlinks listed on command line\n-f	Delete destination files we can't write to\n-F	Delete any existing destination file first (--remove-destination)\n-d	Don't dereference symlinks\n-D	Create leading dirs under DEST (--parents)\n-a	Same as -dpr"
-
-#define HELP_cp "usage: cp [--preserve=motcxa] [-adfHiLlnPpRrsTv] SOURCE... DEST\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n-v	Verbose\n-T	DEST always treated as file, max 2 arguments\n-s	Symlink instead of copy\n-r	Synonym for -R\n-R	Recurse into subdirectories (DEST must be a directory)\n-p	Preserve timestamps, ownership, and mode\n-P	Do not follow symlinks [default]\n-n	No clobber (don't overwrite DEST)\n-l	Hard link instead of copy\n-L	Follow all symlinks\n-i	Interactive, prompt before overwriting existing DEST\n-H	Follow symlinks listed on command line\n-f	Delete destination files we can't write to\n-F	Delete any existing destination file first (--remove-destination)\n-d	Don't dereference symlinks\n-D	Create leading dirs under DEST (--parents)\n-a	Same as -dpr\n--preserve takes either a comma separated list of attributes, or the first\nletter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above"
+#define HELP_cp "usage: cp [-adfHiLlnPpRrsTv] [--preserve=motcxa] [-t TARGET] SOURCE... [DEST]\n\nCopy files from SOURCE to DEST.  If more than one SOURCE, DEST must\nbe a directory.\n\n-a	Same as -dpr\n-D	Create leading dirs under DEST (--parents)\n-d	Don't dereference symlinks\n-F	Delete any existing destination file first (--remove-destination)\n-f	Delete destination files we can't write to\n-H	Follow symlinks listed on command line\n-i	Interactive, prompt before overwriting existing DEST\n-L	Follow all symlinks\n-l	Hard link instead of copy\n-n	No clobber (don't overwrite DEST)\n-u	Update (keep newest mtime)\n-P	Do not follow symlinks\n-p	Preserve timestamps, ownership, and mode\n-R	Recurse into subdirectories (DEST must be a directory)\n-r	Synonym for -R\n-s	Symlink instead of copy\n-t	Copy to TARGET dir (no DEST)\n-T	DEST always treated as file, max 2 arguments\n-v	Verbose\n\nArguments to --preserve are the first letter(s) of:\n\n        mode - permissions (ignore umask for rwx, copy suid and sticky bit)\n   ownership - user and group\n  timestamps - file creation, modification, and access times.\n     context - security context\n       xattr - extended attributes\n         all - all of the above"
 
 #define HELP_comm "usage: comm [-123] FILE1 FILE2\n\nRead FILE1 and FILE2, which should be ordered, and produce three text\ncolumns as output: lines only in FILE1; lines only in FILE2; and lines\nin both files. Filename \"-\" is a synonym for stdin.\n\n-1	Suppress the output column of lines unique to FILE1\n-2	Suppress the output column of lines unique to FILE2\n-3	Suppress the output column of lines duplicated in FILE1 and FILE2"
 
@@ -598,7 +630,7 @@
 
 #define HELP_cksum "usage: cksum [-IPLN] [FILE...]\n\nFor each file, output crc32 checksum value, length and name of file.\nIf no files listed, copy from stdin.  Filename \"-\" is a synonym for stdin.\n\n-H	Hexadecimal checksum (defaults to decimal)\n-L	Little endian (defaults to big endian)\n-P	Pre-inversion\n-I	Skip post-inversion\n-N	Do not include length in CRC calculation (or output)"
 
-#define HELP_chmod "usage: chmod [-R] MODE FILE...\n\nChange mode of listed file[s] (recursively with -R).\n\nMODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo]\n\nStanzas are applied in order: For each category (u = user,\ng = group, o = other, a = all three, if none specified default is a),\nset (+), clear (-), or copy (=), r = read, w = write, x = execute.\ns = u+s = suid, g+s = sgid, o+s = sticky. (+t is an alias for o+s).\nsuid/sgid: execute as the user/group who owns the file.\nsticky: can't delete files you don't own out of this directory\nX = x for directories or if any category already has x set.\n\nOr MODE can be an octal value up to 7777	ug uuugggooo	top +\nbit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1	sstrwxrwxrwx	bottom\n\nExamples:\nchmod u+w file - allow owner of \"file\" to write to it.\nchmod 744 file - user can read/write/execute, everyone else read only"
+#define HELP_chmod "usage: chmod [-R] MODE FILE...\n\nChange mode of listed file[s] (recursively with -R).\n\nMODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo]\n\nStanzas are applied in order: For each category (u = user,\ng = group, o = other, a = all three, if none specified default is a),\nset (+), clear (-), or copy (=), r = read, w = write, x = execute.\ns = u+s = suid, g+s = sgid, +t = sticky. (o+s ignored so a+s doesn't set +t)\nsuid/sgid: execute as the user/group who owns the file.\nsticky: can't delete files you don't own out of this directory\nX = x for directories or if any category already has x set.\n\nOr MODE can be an octal value up to 7777	ug uuugggooo	top +\nbit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1	sstrwxrwxrwx	bottom\n\nExamples:\nchmod u+w file - allow owner of \"file\" to write to it.\nchmod 744 file - user can read/write/execute, everyone else read only"
 
 #define HELP_chown "see: chgrp"
 
diff --git a/android/mac/generated/newtoys.h b/android/mac/generated/newtoys.h
index 6b93bea..40ac3d4 100644
--- a/android/mac/generated/newtoys.h
+++ b/android/mac/generated/newtoys.h
@@ -2,8 +2,9 @@
 USE_SH(OLDTOY(-bash, sh, 0))
 USE_SH(OLDTOY(-sh, sh, 0))
 USE_SH(OLDTOY(-toysh, sh, 0))
-USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
+USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
 USE_ACPI(NEWTOY(acpi, "abctV", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GROUPADD(OLDTOY(addgroup, groupadd, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USERADD(OLDTOY(adduser, useradd, TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
@@ -11,10 +12,12 @@
 USE_ARP(NEWTOY(arp, "vi:nDsdap:A:H:[+Ap][!sd]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_ARPING(NEWTOY(arping, "<1>1s:I:w#<0c#<0AUDbqf[+AU][+Df]", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_ASCII(NEWTOY(ascii, 0, TOYFLAG_USR|TOYFLAG_BIN))
+USE_BASE32(NEWTOY(base32, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_BASE64(NEWTOY(base64, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_BASENAME(NEWTOY(basename, "^<1as:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
 USE_BC(NEWTOY(bc, "i(interactive)l(mathlib)q(quiet)s(standard)w(warn)", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_BLKDISCARD(NEWTOY(blkdiscard, "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]", TOYFLAG_BIN))
 USE_BLKID(NEWTOY(blkid, "ULs*[!LU]", TOYFLAG_BIN))
 USE_BLOCKDEV(NEWTOY(blockdev, "<1>1(setro)(setrw)(getro)(getss)(getbsz)(setbsz)#<0(getsz)(getsize)(getsize64)(getra)(setra)#<0(flushbufs)(rereadpt)",TOYFLAG_SBIN))
 USE_BOOTCHARTD(NEWTOY(bootchartd, 0, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN))
@@ -27,34 +30,35 @@
 USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK))
 USE_CHATTR(NEWTOY(chattr, "?p#v#R", TOYFLAG_BIN))
 USE_CHCON(NEWTOY(chcon, "<2hvR", TOYFLAG_USR|TOYFLAG_BIN))
-USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv[-HLP]", TOYFLAG_BIN))
-USE_CHMOD(NEWTOY(chmod, "<2?vRf[-vf]", TOYFLAG_BIN))
+USE_CHGRP(NEWTOY(chgrp, "<2h(no-dereference)PLHRfv[-HLP]", TOYFLAG_BIN))
+USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
 USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
 USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
 USE_CHRT(NEWTOY(chrt, "^mp#<0iRbrfo[!ibrfo]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_CKSUM(NEWTOY(cksum, "HIPLN", TOYFLAG_BIN))
 USE_CLEAR(NEWTOY(clear, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_CMP(NEWTOY(cmp, "<1>2ls(silent)(quiet)[!ls]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 USE_COMM(NEWTOY(comm, "<2>2321", TOYFLAG_USR|TOYFLAG_BIN))
 USE_COUNT(NEWTOY(count, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]", TOYFLAG_BIN))
-USE_CPIO(NEWTOY(cpio, "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
+USE_CP(NEWTOY(cp, "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]", TOYFLAG_BIN))
+USE_CPIO(NEWTOY(cpio, "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
 USE_CRC32(NEWTOY(crc32, 0, TOYFLAG_BIN))
 USE_CROND(NEWTOY(crond, "fbSl#<0=8d#<0L:c:[-bf][-LS][-ld]", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
 USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_CUT(NEWTOY(cut, "b*|c*|f*|F*|C*|O(output-delimiter):d:sDn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_DATE(NEWTOY(date, "d:D:r:u[!dr]", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]", TOYFLAG_BIN))
 USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEALLOCVT(NEWTOY(deallocvt, ">1", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_GROUPDEL(OLDTOY(delgroup, groupdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USERDEL(OLDTOY(deluser, userdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_DEMO_MANY_OPTIONS(NEWTOY(demo_many_options, "ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba", TOYFLAG_BIN))
-USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3hdbs", TOYFLAG_BIN))
+USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3M#<0hcdbs", TOYFLAG_BIN))
 USE_DEMO_SCANKEY(NEWTOY(demo_scankey, 0, TOYFLAG_BIN))
 USE_DEMO_UTF8TOWC(NEWTOY(demo_utf8towc, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEVMEM(NEWTOY(devmem, "<1>3", TOYFLAG_USR|TOYFLAG_BIN))
-USE_DF(NEWTOY(df, "HPkhit*a[-HPkh]", TOYFLAG_SBIN))
+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN))
 USE_DHCP(NEWTOY(dhcp, "V:H:F:x*r:O*A#<0=20T#<0=3t#<0=3s:p:i:SBRCaovqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCP6(NEWTOY(dhcp6, "r:A#<0T#<0t#<0s:p:i:SRvqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCPD(NEWTOY(dhcpd, ">1P#<0>65535fi:S46[!46]", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
@@ -63,20 +67,23 @@
 USE_DMESG(NEWTOY(dmesg, "w(follow)CSTtrs#<1n#c[!Ttr][!Cc][!Sw]", TOYFLAG_BIN))
 USE_DNSDOMAINNAME(NEWTOY(dnsdomainname, ">0", TOYFLAG_BIN))
 USE_DOS2UNIX(NEWTOY(dos2unix, 0, TOYFLAG_BIN))
-USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_DUMPLEASES(NEWTOY(dumpleases, ">0arf:[!ar]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
-USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_LINEBUF))
+USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_EJECT(NEWTOY(eject, ">1stT[!tT]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_ENV(NEWTOY(env, "^0iu*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_ENV(NEWTOY(env, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
 USE_EXPAND(NEWTOY(expand, "t*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
 USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_FACTOR(NEWTOY(factor, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_FALLOCATE(NEWTOY(fallocate, ">1l#|o#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FALSE(NEWTOY(false, NULL, TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
 USE_FDISK(NEWTOY(fdisk, "C#<0H#<0S#<0b#<512ul", TOYFLAG_SBIN))
-USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_FILE(NEWTOY(file, "<1bhLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FIND(NEWTOY(find, "?^HL[-HL]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_FLOCK(NEWTOY(flock, "<1>1nsux[-sux]", TOYFLAG_USR|TOYFLAG_BIN))
@@ -94,8 +101,8 @@
 USE_GETENFORCE(NEWTOY(getenforce, ">0", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_GETFATTR(NEWTOY(getfattr, "(only-values)dhn:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_GETOPT(NEWTOY(getopt, "^a(alternative)n:(name)o:(options)l*(long)(longoptions)Tu", TOYFLAG_USR|TOYFLAG_BIN))
-USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN))
-USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh", TOYFLAG_SBIN))
+USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 USE_GROUPADD(NEWTOY(groupadd, "<1>2g#<0S", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_GROUPDEL(NEWTOY(groupdel, "<1>2", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_GROUPS(NEWTOY(groups, NULL, TOYFLAG_USR|TOYFLAG_BIN))
@@ -120,7 +127,7 @@
 USE_INIT(NEWTOY(init, "", TOYFLAG_SBIN))
 USE_INOTIFYD(NEWTOY(inotifyd, "<2", TOYFLAG_USR|TOYFLAG_BIN))
 USE_INSMOD(NEWTOY(insmod, "<1", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_INSTALL(NEWTOY(install, "<1cdDpsvt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IONICE(NEWTOY(ionice, "^tc#<0>3=2n#<0>7=5p#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IORENICE(NEWTOY(iorenice, "?<1>3", TOYFLAG_USR|TOYFLAG_BIN))
 USE_IOTOP(NEWTOY(iotop, ">0AaKO" "Hk*o*p*u*s#<1=7d%<100=3000m#n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
@@ -132,6 +139,7 @@
 USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_KILL(NEWTOY(kill, "?ls: ", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_KILLALL(NEWTOY(killall, "?s:ilqvw", TOYFLAG_USR|TOYFLAG_BIN))
 USE_KILLALL5(NEWTOY(killall5, "?o*ls: [!lo][!ls]", TOYFLAG_SBIN))
@@ -141,7 +149,7 @@
 USE_LN(NEWTOY(ln, "<1rt:Tvnfs", TOYFLAG_BIN))
 USE_LOAD_POLICY(NEWTOY(load_policy, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_LOG(NEWTOY(log, "<1p:t:", TOYFLAG_USR|TOYFLAG_SBIN))
-USE_LOGGER(NEWTOY(logger, "st:p:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_LOGGER(NEWTOY(logger, "t:p:s", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LOGIN(NEWTOY(login, ">1f:ph:", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_LOGNAME(NEWTOY(logname, ">0", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LOGWRAPPER(NEWTOY(logwrapper, 0, TOYFLAG_NOHELP|TOYFLAG_USR|TOYFLAG_BIN))
@@ -157,7 +165,7 @@
 USE_MCOOKIE(NEWTOY(mcookie, "v(verbose)V(version)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MD5SUM(NEWTOY(md5sum, "bc(check)s(status)[!bc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MDEV(NEWTOY(mdev, "s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_UMASK))
-USE_MICROCOM(NEWTOY(microcom, "<1>1s:X", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MICROCOM(NEWTOY(microcom, "<1>1s#=115200X", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MIX(NEWTOY(mix, "c:d:l#r#", TOYFLAG_USR|TOYFLAG_BIN))
 USE_MKDIR(NEWTOY(mkdir, "<1"USE_MKDIR_Z("Z:")"vp(parent)(parents)m:", TOYFLAG_BIN|TOYFLAG_UMASK))
 USE_MKE2FS(NEWTOY(mke2fs, "<1>2g:Fnqm#N#i#b#", TOYFLAG_SBIN))
@@ -171,11 +179,11 @@
 USE_MORE(NEWTOY(more, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_MOUNT(NEWTOY(mount, "?O:afnrvwt:o*[-rw]", TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_MOUNTPOINT(NEWTOY(mountpoint, "<1qdx[-dx]", TOYFLAG_BIN))
-USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fiT[-ni]", TOYFLAG_BIN))
+USE_MV(NEWTOY(mv, "<1vnF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
 USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, TOYFLAG_USR|TOYFLAG_BIN))
 USE_NBD_CLIENT(NEWTOY(nbd_client, "<3>3ns", 0))
 USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN))
-USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
+USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tElL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
 USE_NETSTAT(NEWTOY(netstat, "pWrxwutneal", TOYFLAG_BIN))
 USE_NICE(NEWTOY(nice, "^<1n#", TOYFLAG_BIN))
 USE_NL(NEWTOY(nl, "v#=1l#w#<0=6Eb:n:s:", TOYFLAG_USR|TOYFLAG_BIN))
@@ -191,20 +199,21 @@
 USE_PATCH(NEWTOY(patch, ">2(no-backup-if-mismatch)(dry-run)"USE_TOYBOX_DEBUG("x")"F#g#fulp#d:i:Rs(quiet)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIDOF(NEWTOY(pidof, "<1so:x", TOYFLAG_BIN))
-USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIVOT_ROOT(NEWTOY(pivot_root, "<2>2", TOYFLAG_SBIN))
 USE_PKILL(NEWTOY(pkill,    "?Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PMAP(NEWTOY(pmap, "<1xq", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(OLDTOY(poweroff, reboot, TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_PRINTENV(NEWTOY(printenv, "0(null)", TOYFLAG_BIN))
+USE_PRINTENV(NEWTOY(printenv, "(null)0", TOYFLAG_BIN))
 USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_ULIMIT(OLDTOY(prlimit, ulimit, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*Tu*U*g*G*wZ[!ol][+Ae][!oO]", TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_PWD(NEWTOY(pwd, ">0LP[-LP]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_PWDX(NEWTOY(pwdx, "<1a", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PWGEN(NEWTOY(pwgen, ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READAHEAD(NEWTOY(readahead, NULL, TOYFLAG_BIN))
-USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adhlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REALPATH(NEWTOY(realpath, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_REBOOT(NEWTOY(reboot, "fn", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
@@ -214,28 +223,33 @@
 USE_REV(NEWTOY(rev, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_RFKILL(NEWTOY(rfkill, "<1>2", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
-USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p", TOYFLAG_BIN))
+USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p(parents)", TOYFLAG_BIN))
 USE_RMMOD(NEWTOY(rmmod, "<1wf", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
-USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN))
+USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN))
+USE_RTCWAKE(NEWTOY(rtcwake, "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_RUNCON(NEWTOY(runcon, "<2", TOYFLAG_USR|TOYFLAG_SBIN))
-USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
+USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
 USE_SENDEVENT(NEWTOY(sendevent, "<4>4", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_SEQ(NEWTOY(seq, "<1>3?f:s:w[!fw]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
 USE_SETENFORCE(NEWTOY(setenforce, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_SETFATTR(NEWTOY(setfattr, "hn:|v:x:|[!xv]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SETSID(NEWTOY(setsid, "^<1wcd[!dc]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
+USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
 USE_SHA1SUM(NEWTOY(sha1sum, "bc(check)s(status)[!bc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TOYBOX_LIBCRYPTO(USE_SHA224SUM(OLDTOY(sha224sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
 USE_TOYBOX_LIBCRYPTO(USE_SHA256SUM(OLDTOY(sha256sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
 USE_TOYBOX_LIBCRYPTO(USE_SHA384SUM(OLDTOY(sha384sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
+USE_SHA3SUM(NEWTOY(sha3sum, "bSa#<128>512=224", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TOYBOX_LIBCRYPTO(USE_SHA512SUM(OLDTOY(sha512sum, sha1sum, TOYFLAG_USR|TOYFLAG_BIN)))
+USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
 USE_SHRED(NEWTOY(shred, "<1zxus#<1n#<1o#<0f", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SKELETON(NEWTOY(skeleton, "(walrus)(blubber):;(also):e@d*c#b:a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SKELETON_ALIAS(NEWTOY(skeleton_alias, "b#dq", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN))
 USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
 USE_SPLIT(NEWTOY(split, ">2a#<1=2>9b#<1l#<1[!bl]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_STAT(NEWTOY(stat, "<1c:(format)fLt", TOYFLAG_BIN))
 USE_STRINGS(NEWTOY(strings, "t:an#=4<1fo", TOYFLAG_USR|TOYFLAG_BIN))
@@ -250,7 +264,7 @@
 USE_SYSLOGD(NEWTOY(syslogd,">0l#<1>8=8R:b#<0>99=1s#<0=200m#<0>71582787=20O:p:f:a:nSKLD", TOYFLAG_SBIN|TOYFLAG_STAYROOT))
 USE_TAC(NEWTOY(tac, NULL, TOYFLAG_USR|TOYFLAG_BIN))
 USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TASKSET(NEWTOY(taskset, "<1^pa", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN))
 USE_TEE(NEWTOY(tee, "ia", TOYFLAG_USR|TOYFLAG_BIN))
@@ -275,9 +289,11 @@
 USE_ULIMIT(NEWTOY(ulimit, ">1P#<1SHavutsrRqpnmlifedc[-SH][!apvutsrRqnmlifedc]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UMOUNT(NEWTOY(umount, "cndDflrat*v[!na]", TOYFLAG_BIN|TOYFLAG_STAYROOT))
 USE_UNAME(NEWTOY(uname, "oamvrns[+os]", TOYFLAG_BIN))
+USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN))
 USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK))
 USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UPTIME(NEWTOY(uptime, ">0ps", TOYFLAG_USR|TOYFLAG_BIN))
 USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
@@ -290,14 +306,16 @@
 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN))
 USE_W(NEWTOY(w, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
 USE_WC(NEWTOY(wc, "mcwl", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_WGET(NEWTOY(wget, "(no-check-certificate)O:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHICH(NEWTOY(which, "<1a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHO(NEWTOY(who, "a", TOYFLAG_USR|TOYFLAG_BIN))
 USE_WHOAMI(OLDTOY(whoami, logname, TOYFLAG_USR|TOYFLAG_BIN))
-USE_XARGS(NEWTOY(xargs, "^E:P#optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_XXD(NEWTOY(xxd, ">1c#l#o#g#<1=2iprs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_XZCAT(NEWTOY(xzcat, NULL, TOYFLAG_USR|TOYFLAG_BIN))
-USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
 USE_ZCAT(NEWTOY(zcat,     "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/configure b/configure
index 4735a7e..0b6501f 100755
--- a/configure
+++ b/configure
@@ -11,9 +11,6 @@
   exit $?
 fi
 
-# A synonym.
-[ -z "$CROSS_COMPILE" ] && CROSS_COMPILE="$CROSS"
-
 # CFLAGS and OPTIMIZE are different so you can add extra CFLAGS without
 # disabling default optimizations
 [ -z "$CFLAGS" ] && CFLAGS="-Wall -Wundef -Wno-char-subscripts -Werror=implicit-function-declaration"
diff --git a/kconfig/Makefile b/kconfig/Makefile
index d57c9df..2e8e5dc 100644
--- a/kconfig/Makefile
+++ b/kconfig/Makefile
@@ -29,8 +29,9 @@
 allnoconfig: $(obj)/conf $(KCONFIG_TOP)
 	$< -n $(KCONFIG_TOP) > /dev/null
 
+KCONFIG_ALLCONFIG ?= /dev/null
 defconfig: $(obj)/conf $(KCONFIG_TOP)
-	$< -D /dev/null $(KCONFIG_TOP) > /dev/null
+	$< -D $(KCONFIG_ALLCONFIG) $(KCONFIG_TOP) > /dev/null
 
 macos_defconfig: $(obj)/conf $(KCONFIG_TOP)
 	KCONFIG_ALLCONFIG=$(obj)/macos_miniconfig $< -n $(KCONFIG_TOP) > /dev/null
diff --git a/kconfig/freebsd_miniconfig b/kconfig/freebsd_miniconfig
index 98d3c1c..3a6fa80 100644
--- a/kconfig/freebsd_miniconfig
+++ b/kconfig/freebsd_miniconfig
@@ -125,4 +125,3 @@
 CONFIG_TOYBOX_FLOAT=y
 CONFIG_TOYBOX_HELP=y
 CONFIG_TOYBOX_HELP_DASHDASH=y
-CONFIG_TOYBOX_I18N=y
diff --git a/kconfig/macos_miniconfig b/kconfig/macos_miniconfig
index b9bf6a3..26c21b1 100644
--- a/kconfig/macos_miniconfig
+++ b/kconfig/macos_miniconfig
@@ -11,10 +11,10 @@
 CONFIG_CMP=y
 CONFIG_COMM=y
 CONFIG_CP=y
-CONFIG_CP_PRESERVE=y
 CONFIG_CPIO=y
 CONFIG_CUT=y
 CONFIG_DATE=y
+CONFIG_DF=y
 CONFIG_DIRNAME=y
 CONFIG_DU=y
 CONFIG_ECHO=y
@@ -115,4 +115,3 @@
 CONFIG_TOYBOX_FLOAT=y
 CONFIG_TOYBOX_HELP=y
 CONFIG_TOYBOX_HELP_DASHDASH=y
-CONFIG_TOYBOX_I18N=y
diff --git a/lib/args.c b/lib/args.c
index 89c82ca..ef23cc0 100644
--- a/lib/args.c
+++ b/lib/args.c
@@ -76,6 +76,7 @@
 //     >9 die if > # leftover arguments (default MAX_INT)
 //     ? Allow unknown arguments (pass them through to command).
 //     & first arg has imaginary dash (ala tar/ps/ar) which sets FLAGS_NODASH
+//     0 Include argv[0] in optargs
 //
 //   At the end: [groups] of previously seen options
 //     - Only one in group (switch off)    [-abc] means -ab=-b, -ba=-a, -abc=-c
@@ -130,8 +131,9 @@
 };
 
 // Use getoptflagstate to parse one command line option from argv
-static int gotflag(struct getoptflagstate *gof, struct opts *opt)
+static int gotflag(struct getoptflagstate *gof, struct opts *opt, int shrt)
 {
+  unsigned long long i;
   int type;
 
   // Did we recognize this option?
@@ -143,7 +145,6 @@
   // Might enabling this switch off something else?
   if (toys.optflags & opt->dex[0]) {
     struct opts *clr;
-    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)
@@ -158,7 +159,6 @@
 
   if (toys.optflags & gof->excludes) {
     struct opts *bad;
-    unsigned i = 1;
 
     for (bad=gof->opts, i=1; bad ;bad = bad->next, i<<=1) {
       if (opt == bad || !(i & toys.optflags)) continue;
@@ -168,7 +168,8 @@
   }
 
   // Does this option take an argument?
-  if (!gof->arg) {
+  if (!gof->arg || (shrt && !gof->arg[1])) {
+    gof->arg = 0;
     if (opt->flags & 8) return 0;
     gof->arg = "";
   } else gof->arg++;
@@ -227,17 +228,17 @@
 
 // Parse this command's options string into struct getoptflagstate, which
 // includes a struct opts linked list in reverse order (I.E. right-to-left)
-void parse_optflaglist(struct getoptflagstate *gof)
+static int parse_optflaglist(struct getoptflagstate *gof)
 {
   char *options = toys.which->options;
   long *nextarg = (long *)&this;
   struct opts *new = 0;
-  int idx;
+  int idx, rc = 0;
 
   // Parse option format string
   memset(gof, 0, sizeof(struct getoptflagstate));
   gof->maxargs = INT_MAX;
-  if (!options) return;
+  if (!options) return 0;
 
   // Parse leading special behavior indicators
   for (;;) {
@@ -246,6 +247,7 @@
     else if (*options == '>') gof->maxargs=*(++options)-'0';
     else if (*options == '?') gof->noerror++;
     else if (*options == '&') gof->nodash_now = 1;
+    else if (*options == '0') rc = 1;
     else break;
     options++;
   }
@@ -327,7 +329,7 @@
   // (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 long long u = 1L<<idx++;
+    unsigned long long u = 1LL<<idx++;
 
     if (new->c == 1) new->c = 0;
     new->dex[1] = u;
@@ -372,6 +374,8 @@
       }
     }
   }
+
+  return rc;
 }
 
 // Fill out toys.optflags, toys.optargs, and this[] from toys.argv
@@ -389,18 +393,17 @@
   toys.exitval = toys.which->flags >> 24;
 
   // Allocate memory for optargs
-  saveflags = 0;
+  saveflags = toys.optc = parse_optflaglist(&gof);
   while (toys.argv[saveflags++]);
   toys.optargs = xzalloc(sizeof(char *)*saveflags);
-
-  parse_optflaglist(&gof);
+  if (toys.optc) *toys.optargs = *toys.argv;
 
   if (toys.argv[1] && toys.argv[1][0] == '-') gof.nodash_now = 0;
 
   // Iterate through command line arguments, skipping argv[0]
   for (gof.argc=1; toys.argv[gof.argc]; gof.argc++) {
     gof.arg = toys.argv[gof.argc];
-    catch = NULL;
+    catch = 0;
 
     // Parse this argument
     if (gof.stopearly>1) goto notflag;
@@ -443,7 +446,7 @@
         }
 
         // Long option parsed, handle option.
-        gotflag(&gof, catch);
+        gotflag(&gof, catch, 0);
         continue;
       }
 
@@ -456,7 +459,7 @@
     // At this point, we have the args part of -args.  Loop through
     // each entry (could be -abc meaning -a -b -c)
     saveflags = toys.optflags;
-    while (*gof.arg) {
+    while (gof.arg && *gof.arg) {
 
       // Identify next option char.
       for (catch = gof.opts; catch; catch = catch->next)
@@ -464,7 +467,7 @@
           if (!((catch->flags&4) && gof.arg[1])) break;
 
       // Handle option char (advancing past what was used)
-      if (gotflag(&gof, catch) ) {
+      if (gotflag(&gof, catch, 1) ) {
         toys.optflags = saveflags;
         gof.arg = toys.argv[gof.argc];
         goto notflag;
diff --git a/lib/deflate.c b/lib/deflate.c
index eebcd3d..cdc4f8d 100644
--- a/lib/deflate.c
+++ b/lib/deflate.c
@@ -37,7 +37,7 @@
 };
 
 // malloc a struct bitbuf
-struct bitbuf *bitbuf_init(int fd, int size)
+static struct bitbuf *bitbuf_init(int fd, int size)
 {
   struct bitbuf *bb = xzalloc(sizeof(struct bitbuf)+size);
 
@@ -49,7 +49,7 @@
 
 // Advance bitpos without the overhead of recording bits
 // Loads more data when input buffer empty
-void bitbuf_skip(struct bitbuf *bb, int bits)
+static void bitbuf_skip(struct bitbuf *bb, int bits)
 {
   int pos = bb->bitpos + bits, len = bb->len << 3;
 
@@ -75,7 +75,7 @@
 }
 
 // Fetch the next X bits from the bitbuf, little endian
-unsigned bitbuf_get(struct bitbuf *bb, int bits)
+static unsigned bitbuf_get(struct bitbuf *bb, int bits)
 {
   int result = 0, offset = 0;
 
@@ -98,7 +98,7 @@
   return result;
 }
 
-void bitbuf_flush(struct bitbuf *bb)
+static void bitbuf_flush(struct bitbuf *bb)
 {
   if (!bb->bitpos) return;
 
@@ -107,7 +107,7 @@
   bb->bitpos = 0;
 }
 
-void bitbuf_put(struct bitbuf *bb, int data, int len)
+static void bitbuf_put(struct bitbuf *bb, int data, int len)
 {
   while (len) {
     int click = bb->bitpos >> 3, blow, blen;
@@ -403,7 +403,7 @@
   bitbuf_skip(bb, 6*8);
 
   // Skip extra, name, comment, header CRC fields
-  if (flags & 4) bitbuf_skip(bb, 16);
+  if (flags & 4) bitbuf_skip(bb, bitbuf_get(bb, 16) * 8);
   if (flags & 8) while (bitbuf_get(bb, 8));
   if (flags & 16) while (bitbuf_get(bb, 8));
   if (flags & 2) bitbuf_skip(bb, 16);
@@ -411,7 +411,7 @@
   return 1;
 }
 
-void gzip_crc(struct deflate *dd, char *data, int len)
+static void gzip_crc(struct deflate *dd, char *data, int len)
 {
   int i;
   unsigned crc, *crc_table = dd->crctable;
diff --git a/lib/dirtree.c b/lib/dirtree.c
index f4cc620..43ecbd8 100644
--- a/lib/dirtree.c
+++ b/lib/dirtree.c
@@ -52,13 +52,13 @@
   }
 
   // Allocate/populate return structure
-  dt = xmalloc((len = sizeof(struct dirtree)+len+1)+linklen);
-  memset(dt, 0, statless ? offsetof(struct dirtree, again)
-    : offsetof(struct dirtree, st));
+  memset(dt = xmalloc((len = sizeof(struct dirtree)+len+1)+linklen), 0,
+    statless ? sizeof(struct dirtree) : offsetof(struct dirtree, st));
   dt->parent = parent;
   dt->again = statless ? 2 : 0;
   if (!statless) memcpy(&dt->st, &st, sizeof(struct stat));
-  strcpy(dt->name, name ? name : "");
+  if (name) strcpy(dt->name, name);
+  else *dt->name = 0, dt->st.st_mode = S_IFDIR;
   if (linklen) dt->symlink = memcpy(len+(char *)dt, libbuf, linklen);
 
   return dt;
@@ -75,7 +75,7 @@
   return 0;
 }
 
-// Return path to this node, assembled recursively.
+// Return path to this node.
 
 // Initial call can pass in NULL to plen, or point to an int initialized to 0
 // to return the length of the path, or a value greater than 0 to allocate
@@ -83,20 +83,22 @@
 
 char *dirtree_path(struct dirtree *node, int *plen)
 {
+  struct dirtree *nn;
   char *path;
-  int len;
+  int ii, ll, len;
 
-  if (!node) {
-    path = xmalloc(*plen);
-    *plen = 0;
-    return path;
-  }
-
-  len = (plen ? *plen : 0)+strlen(node->name)+1;
-  path = dirtree_path(node->parent, &len);
-  if (len && path[len-1] != '/') path[len++]='/';
-  len = stpcpy(path+len, node->name) - path;
+  ll = len = plen ? *plen : 0;
+  if (!node->parent)
+    return strcpy(xmalloc(strlen(node->name)+ll+1), node->name);
+  for (nn = node; nn; nn = nn->parent)
+    if ((ii = strlen(nn->name))) len += ii+1-(nn->name[ii-1]=='/');
   if (plen) *plen = len;
+  path = xmalloc(len)+len-ll;
+  for (nn = node; nn; nn = nn->parent) if ((len = strlen(nn->name))) {
+    *--path = '/'*(nn != node);
+    if (nn->name[len-1]=='/') len--;
+    memcpy(path -= len, nn->name, len);
+  }
 
   return path;
 }
@@ -111,8 +113,8 @@
 // returns NULL. If !callback return top node unchanged.
 // If !new return DIRTREE_ABORTVAL
 
-struct dirtree *dirtree_handle_callback(struct dirtree *new,
-          int (*callback)(struct dirtree *node))
+static struct dirtree *dirtree_handle_callback(struct dirtree *new,
+  int (*callback)(struct dirtree *node))
 {
   int flags;
 
@@ -121,11 +123,11 @@
   flags = callback(new);
 
   if (S_ISDIR(new->st.st_mode) && (flags & (DIRTREE_RECURSE|DIRTREE_COMEAGAIN)))
-    flags = dirtree_recurse(new, callback,
+    flags = dirtree_recurse(new, callback, !*new->name ? AT_FDCWD :
       openat(dirtree_parentfd(new), new->name, O_CLOEXEC), flags);
 
-  // If this had children, it was callback's job to free them already.
-  if (!(flags & DIRTREE_SAVE)) {
+  // Free node that didn't request saving and has no saved children.
+  if (!new->child && !(flags & DIRTREE_SAVE)) {
     free(new);
     new = 0;
   }
@@ -141,10 +143,12 @@
 {
   struct dirtree *new, **ddt = &(node->child);
   struct dirent *entry;
-  DIR *dir;
+  DIR *dir = 0;
 
-  node->dirfd = dirfd;
-  if (node->dirfd == -1 || !(dir = fdopendir(node->dirfd))) {
+  // Why doesn't fdopendir() support AT_FDCWD?
+  if (AT_FDCWD == (node->dirfd = dirfd)) dir = opendir(".");
+  else if (node->dirfd != -1) dir = fdopendir(node->dirfd);
+  if (!dir) {
     if (!(flags & DIRTREE_SHUTUP)) {
       char *path = dirtree_path(node, 0);
       perror_msg_raw(path);
diff --git a/lib/env.c b/lib/env.c
index 614a504..facde63 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -8,7 +8,7 @@
 // Returns the number of bytes taken by the environment variables. For use
 // when calculating the maximum bytes of environment+argument data that can
 // be passed to exec for find(1) and xargs(1).
-long environ_bytes()
+long environ_bytes(void)
 {
   long bytes = sizeof(char *);
   char **ev;
@@ -34,60 +34,53 @@
 // Frees entries we set earlier. Use with libc getenv but not setenv/putenv.
 // if name has an equals and !val, act like putenv (name=val must be malloced!)
 // if !val unset name. (Name with = and val is an error)
-void xsetmyenv(int *envc, char ***env, char *name, char *val)
+// returns pointer to new name=value environment string, NULL if none
+char *xsetenv(char *name, char *val)
 {
-  unsigned i, len, ec;
+  unsigned i, j = 0, len;
   char *new;
 
   // If we haven't snapshot initial environment state yet, do so now.
-  if (!*envc) {
+  if (!toys.envc) {
+
     // envc is size +1 so even if env empty it's nonzero after initialization
-    while ((*env)[(*envc)++]);
-    memcpy(new = xmalloc(((*envc|0xff)+1)*sizeof(char *)), *env,
-      *envc*sizeof(char *));
-    *env = (void *)new;
+    while (environ[toys.envc++]);
+    memcpy(new = xmalloc(((toys.envc|31)+1)*sizeof(char *)), environ,
+      toys.envc*sizeof(char *));
+    environ = (void *)new;
   }
 
-  new = strchr(name, '=');
-  if (new) {
+  if (!(new = strchr(name, '='))) {
+    len = strlen(name);
+    if (val) new = xmprintf("%s=%s", name, val);
+  } else {
     len = new-name;
     if (val) error_exit("xsetenv %s to %s", name, val);
     new = name;
-  } else {
-    len = strlen(name);
-    if (val) new = xmprintf("%s=%s", name, val);
   }
 
-  ec = (*envc)-1;  // compensate for size +1 above
-  for (i = 0; (*env)[i]; i++) {
+  for (i = 0; environ[i]; i++) {
     // Drop old entry, freeing as appropriate. Assumes no duplicates.
-    if (!memcmp(name, (*env)[i], len) && (*env)[i][len]=='=') {
-      if (i>=ec) free((*env)[i]);
-      else {
-        // move old entries down, add at end of old data
-        *envc = ec--;
-        for (; new ? i<ec : !!(*env)[i]; i++) (*env)[i] = (*env)[i+1];
-        i = ec;
-      }
-      break;
+    if (!memcmp(name, environ[i], len) && environ[i][len]=='=') {
+      if (i<toys.envc-1) toys.envc--;
+      else free(environ[i]);
+      j++;
     }
+
+    // move data down to fill hole, including null terminator
+    if (j && !(environ[i] = environ[i+1])) break;
   }
 
-  if (!new) return;
+  if (!new) return 0;
 
   // resize and null terminate if expanding
-  if (!(*env)[i]) {
+  if (!j && !environ[i]) {
     len = i+1;
-    if (!(len&255)) *env = xrealloc(*env, (len+256)*sizeof(char *));
-    (*env)[len] = 0;
+    if (!(len&31)) environ = xrealloc(environ, (len+32)*sizeof(char *));
+    environ[len] = 0;
   }
-  (*env)[i] = new;
-}
 
-// xsetenv for normal environment (extern variables).
-void xsetenv(char *name, char *val)
-{
-  return xsetmyenv(&toys.envc, &environ, name, val);
+  return environ[i] = new;
 }
 
 void xunsetenv(char *name)
@@ -96,6 +89,27 @@
   xsetenv(name, 0);
 }
 
+// remove entry and return pointer instead of freeing
+char *xpop_env(char *name)
+{
+  int len, i;
+  char *s = 0;
+
+  for (len = 0; name[len] && name[len]!='='; len++);
+  for (i = 0; environ[i]; i++) {
+    if (!s && !strncmp(name, environ[i], len) && environ[i][len] == '=') {
+      s = environ[i];
+      if (toys.envc-1>i) {
+        s = xstrdup(s);
+        toys.envc--;
+      }
+    }
+    if (s) environ[i] = environ[i+1];
+  }
+
+  return s;
+}
+
 // reset environment for a user, optionally clearing most of it
 void reset_env(struct passwd *p, int clear)
 {
diff --git a/lib/help.c b/lib/help.c
index 86d6392..cd02302 100644
--- a/lib/help.c
+++ b/lib/help.c
@@ -21,6 +21,11 @@
   int i = toys.which-toy_list;
   char *s, *ss;
 
+  if (!(full&2))
+    fprintf(out, "Toybox %s" USE_TOYBOX(" multicall binary")
+                 ": https://landley.net/toybox"
+                 USE_TOYBOX(" (see toybox --help)") "\n\n", toybox_version);
+
   if (CFG_TOYBOX_HELP) {
     for (;;) {
       s = help_data;
diff --git a/lib/lib.c b/lib/lib.c
index b57a569..87bda4f 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -177,9 +177,11 @@
   // not-a-directory along the way, but the last one we must explicitly
   // test for. Might as well do it up front.
 
-  if (!fstatat(atfd, dir, &buf, 0) && !S_ISDIR(buf.st_mode)) {
-    errno = EEXIST;
-    return 1;
+  if (!fstatat(atfd, dir, &buf, 0)) {
+    if ((flags&MKPATHAT_MKLAST) && !S_ISDIR(buf.st_mode)) {
+      errno = EEXIST;
+      return 1;
+    } else return 0;
   }
 
   for (s = dir; ;s++) {
@@ -346,7 +348,28 @@
   return off-haystack;
 }
 
+// Convert wc to utf8, returning bytes written. Does not null terminate.
+int wctoutf8(char *s, unsigned wc)
+{
+  int len = (wc>0x7ff)+(wc>0xffff), i;
+
+  if (wc<128) {
+    *s = wc;
+    return 1;
+  } else {
+    i = len;
+    do {
+      s[1+i] = 0x80+(wc&0x3f);
+      wc >>= 6;
+    } while (i--);
+    *s = (((signed char) 0x80) >> (len+1)) | wc;
+  }
+
+  return 2+len;
+}
+
 // Convert utf8 sequence to a unicode wide character
+// returns bytes consumed, or -1 if err, or -2 if need more data.
 int utf8towc(wchar_t *wc, char *str, unsigned len)
 {
   unsigned result, mask, first;
@@ -375,37 +398,41 @@
   return str-s;
 }
 
+// Convert string to lower case, utf8 aware.
 char *strlower(char *s)
 {
   char *try, *new;
+  int len, mlen = (strlen(s)|7)+9;
+  wchar_t c;
 
-  if (!CFG_TOYBOX_I18N) {
-    try = new = xstrdup(s);
-    for (; *s; s++) *(new++) = tolower(*s);
-  } else {
-    // I can't guarantee the string _won't_ expand during reencoding, so...?
-    try = new = xmalloc(strlen(s)*2+1);
+  try = new = xmalloc(mlen);
 
-    while (*s) {
-      wchar_t c;
-      int len = utf8towc(&c, s, MB_CUR_MAX);
+  while (*s) {
 
-      if (len < 1) *(new++) = *(s++);
-      else {
-        s += len;
-        // squash title case too
-        c = towlower(c);
+    if (1>(len = utf8towc(&c, s, MB_CUR_MAX))) {
+      *(new++) = *(s++);
 
-        // if we had a valid utf8 sequence, convert it to lower case, and can't
-        // encode back to utf8, something is wrong with your libc. But just
-        // in case somebody finds an exploit...
-        len = wcrtomb(new, c, 0);
-        if (len < 1) error_exit("bad utf8 %x", (int)c);
-        new += len;
-      }
+      continue;
     }
-    *new = 0;
+
+    s += len;
+    // squash title case too
+    c = towlower(c);
+
+    // if we had a valid utf8 sequence, convert it to lower case, and can't
+    // encode back to utf8, something is wrong with your libc. But just
+    // in case somebody finds an exploit...
+    len = wcrtomb(new, c, 0);
+    if (len < 1) error_exit("bad utf8 %x", (int)c);
+    new += len;
+
+    // Case conversion can expand utf8 representation, but with extra mlen
+    // space above we should basically never need to realloc
+    if (mlen+4 > (len = new-try)) continue;
+    try = xrealloc(try, mlen = len+16);
+    new = try+len;
   }
+  *new = 0;
 
   return try;
 }
@@ -435,6 +462,29 @@
   return (idx == -1) ? 0 : to[idx];
 }
 
+// parse next character advancing pointer. echo requires leading 0 in octal esc
+int unescape2(char **c, int echo)
+{
+  int idx = *((*c)++), i, off;
+
+  if (idx != '\\' || !**c) return idx;
+  if (**c == 'c') return 31&*(++*c);
+  for (i = 0; i<4; i++) {
+    if (sscanf(*c, (char *[]){"0%3o%n"+!echo, "x%2x%n", "u%4x%n", "U%6x%n"}[i],
+        &idx, &off) > 0)
+    {
+      *c += off;
+
+      return idx;
+    }
+  }
+
+  if (-1 == (idx = stridx("\\abeEfnrtv'\"?0", **c))) return '\\';
+  ++*c;
+
+  return "\\\a\b\033\033\f\n\r\t\v'\"?"[idx];
+}
+
 // If string ends with suffix return pointer to start of suffix in string,
 // else NULL
 char *strend(char *str, char *suffix)
@@ -472,20 +522,16 @@
 {
   struct stat st;
   off_t base = 0, range = 1, expand = 1, old;
+  unsigned long long size;
 
   if (!fstat(fd, &st) && S_ISREG(st.st_mode)) return st.st_size;
 
   // If the ioctl works for this, return it.
-  // TODO: is blocksize still always 512, or do we stat for it?
-  // unsigned int size;
-  // if (ioctl(fd, BLKGETSIZE, &size) >= 0) return size*512L;
+  if (get_block_device_size(fd, &size)) return size;
 
   // If not, do a binary search for the last location we can read.  (Some
   // block devices don't do BLKGETSIZE right.)  This should probably have
   // a CONFIG option...
-
-  // If not, do a binary search for the last location we can read.
-
   old = lseek(fd, 0, SEEK_CUR);
   do {
     char temp;
@@ -865,35 +911,36 @@
   xexit();
 }
 
-// Install the same handler on every signal that defaults to killing the
-// process, calling the handler on the way out. Calling multiple times
-// adds the handlers to a list, to be called in order.
+// Install an atexit handler. Also install the same handler on every signal
+// that defaults to killing the process, calling the handler on the way out.
+// Calling multiple times adds the handlers to a list, to be called in LIFO
+// order.
 void sigatexit(void *handler)
 {
+  struct arg_list *al = 0;
+
   xsignal_all_killers(handler ? exit_signal : SIG_DFL);
-
   if (handler) {
-    struct arg_list *al = xmalloc(sizeof(struct arg_list));
-
+    al = xmalloc(sizeof(struct arg_list));
     al->next = toys.xexit;
     al->arg = handler;
-    toys.xexit = al;
-  } else {
-    llist_traverse(toys.xexit, free);
-    toys.xexit = 0;
-  }
+  } else llist_traverse(toys.xexit, free);
+  toys.xexit = al;
 }
 
-// Output a nicely formatted 80-column table of all the signals.
-void list_signals()
+// Output a nicely formatted table of all the signals.
+void list_signals(void)
 {
   int i = 0, count = 0;
+  unsigned cols = 80;
   char *name;
 
+  terminal_size(&cols, 0);
+  cols /= 16;
   for (; i<=NSIG; i++) {
     if ((name = num_to_sig(i))) {
       printf("%2d) SIG%-9s", i, name);
-      if (++count % 5 == 0) putchar('\n');
+      if (++count % cols == 0) putchar('\n');
     }
   }
   putchar('\n');
@@ -931,55 +978,51 @@
       umask(amask = umask(0));
     }
 
-    if (!*str || !(s = strchr(hows, *str))) goto barf;
-    if (!(dohow = *(str++))) goto barf;
+    // Repeated "hows" are allowed; something like "a=r+w+s" is valid.
+    for (;;) {
+      if (-1 == stridx(hows, dohow = *str)) goto barf;
+      while (*++str && (s = strchr(whats, *str))) dowhat |= 1<<(s-whats);
 
-    while (*str && (s = strchr(whats, *str))) {
-      dowhat |= 1<<(s-whats);
-      str++;
-    }
+      // Convert X to x for directory or if already executable somewhere
+      if ((dowhat&32) && (S_ISDIR(mode) || (mode&0111))) dowhat |= 1;
 
-    // Convert X to x for directory or if already executable somewhere
-    if ((dowhat&32) &&  (S_ISDIR(mode) || (mode&0111))) dowhat |= 1;
+      // Copy mode from another category?
+      if (!dowhat && -1 != (i = stridx(whys, *str))) {
+        dowhat = (mode>>(3*i))&7;
+        str++;
+      }
 
-    // Copy mode from another category?
-    if (!dowhat && *str && (s = strchr(whys, *str))) {
-      dowhat = (mode>>(3*(s-whys)))&7;
-      str++;
-    }
+      // Loop through what=xwrs and who=ogu to apply bits to the mode.
+      for (i=0; i<4; i++) {
+        for (j=0; j<3; j++) {
+          mode_t bit = 0;
+          int where = 1<<((3*i)+j);
 
-    // Are we ready to do a thing yet?
-    if (*str && *(str++) != ',') goto barf;
+          if (amask & where) continue;
 
-    // Loop through what=xwrs and who=ogu to apply bits to the mode.
-    for (i=0; i<4; i++) {
-      for (j=0; j<3; j++) {
-        mode_t bit = 0;
-        int where = 1<<((3*i)+j);
+          // Figure out new value at this location
+          if (i == 3) {
+            // suid and sticky
+            if (!j) bit = dowhat&16; // o+s = t but a+s doesn't set t, hence t
+            else if ((dowhat&8) && (dowho&(8|(1<<j)))) bit++;
+          } else {
+            if (!(dowho&(8|(1<<i)))) continue;
+            else if (dowhat&(1<<j)) bit++;
+          }
 
-        if (amask & where) continue;
-
-        // Figure out new value at this location
-        if (i == 3) {
-          // suid and sticky
-          if (!j) bit = dowhat&16; // o+s = t
-          else if ((dowhat&8) && (dowho&(8|(1<<j)))) bit++;
-        } else {
-          if (!(dowho&(8|(1<<i)))) continue;
-          else if (dowhat&(1<<j)) bit++;
+          // When selection active, modify bit
+          if (dohow == '=' || (bit && dohow == '-')) mode &= ~where;
+          if (bit && dohow != '-') mode |= where;
         }
-
-        // When selection active, modify bit
-
-        if (dohow == '=' || (bit && dohow == '-')) mode &= ~where;
-        if (bit && dohow != '-') mode |= where;
+      }
+      if (!*str) return mode|extrabits;
+      if (*str == ',') {
+        str++;
+        break;
       }
     }
-
-    if (!*str) break;
   }
 
-  return mode|extrabits;
 barf:
   error_exit("bad mode '%s'", modestr);
 }
@@ -1128,18 +1171,24 @@
 }
 
 // display first "dgt" many digits of number plus unit (kilo-exabytes)
-int human_readable_long(char *buf, unsigned long long num, int dgt, int style)
+int human_readable_long(char *buf, unsigned long long num, int dgt, int unit,
+  int style)
 {
   unsigned long long snap = 0;
-  int len, unit, divisor = (style&HR_1000) ? 1000 : 1024;
+  int len, divisor = (style&HR_1000) ? 1000 : 1024;
 
   // Divide rounding up until we have 3 or fewer digits. Since the part we
   // print is decimal, the test is 999 even when we divide by 1024.
-  // We can't run out of units because 1<<64 is 18 exabytes.
-  for (unit = 0; snprintf(0, 0, "%llu", num)>dgt; unit++)
+  // The largest unit we can detect is 1<<64 = 18 Exabytes, but we added
+  // Zettabyte and Yottabyte in case "unit" starts above zero.
+  for (;;unit++) {
+    if ((len = snprintf(0, 0, "%llu", num))<=dgt) break;
     num = ((snap = num)+(divisor/2))/divisor;
+  }
+  if (CFG_TOYBOX_DEBUG && unit>8) return sprintf(buf, "%.*s", dgt, "TILT");
+
   len = sprintf(buf, "%llu", num);
-  if (unit && len == 1) {
+  if (!(style & HR_NODOT) && unit && len == 1) {
     // Redo rounding for 1.2M case, this works with and without HR_1000.
     num = snap/divisor;
     snap -= num*divisor;
@@ -1149,7 +1198,7 @@
   }
   if (style & HR_SPACE) buf[len++] = ' ';
   if (unit) {
-    unit = " kMGTPE"[unit];
+    unit = " kMGTPEZY"[unit];
 
     if (!(style&HR_1000)) unit = toupper(unit);
     buf[len++] = unit;
@@ -1162,7 +1211,7 @@
 // Give 3 digit estimate + units ala 999M or 1.7T
 int human_readable(char *buf, unsigned long long num, int style)
 {
-  return human_readable_long(buf, num, 3, style);
+  return human_readable_long(buf, num, 3, 0, style);
 }
 
 // The qsort man page says you can use alphasort, the posix committee
diff --git a/lib/lib.h b/lib/lib.h
index 66b39d6..f9c0428 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -74,11 +74,11 @@
 // Don't warn about failure to stat
 #define DIRTREE_SHUTUP      16
 // Breadth first traversal, conserves filehandles at the expense of memory
-#define DIRTREE_BREADTH     32
+#define DIRTREE_BREADTH     32 // TODO not implemented yet
 // skip non-numeric entries
 #define DIRTREE_PROC        64
 // Return files we can't stat
-#define DIRTREE_STATLESS    128
+#define DIRTREE_STATLESS   128
 // Don't look at any more files in this directory.
 #define DIRTREE_ABORT      256
 
@@ -127,14 +127,15 @@
 char *xstrdup(char *s);
 void *xmemdup(void *s, long len);
 char *xmprintf(char *format, ...) printf_format;
+void xflush(int flush);
 void xprintf(char *format, ...) printf_format;
 void xputsl(char *s, int len);
 void xputsn(char *s);
 void xputs(char *s);
 void xputc(char c);
-void xflush(int flush);
+void xvdaemon(void);
 void xexec(char **argv);
-pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void));
+pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(char **argv));
 pid_t xpopen_both(char **argv, int *pipes);
 int xwaitpid(pid_t pid);
 int xpclose_both(pid_t pid, int *pipes);
@@ -187,7 +188,8 @@
 void xsignal(int signal, void *handler);
 time_t xvali_date(struct tm *tm, char *str);
 void xparsedate(char *str, time_t *t, unsigned *nano, int endian);
-char *xgetline(FILE *fp, int *len);
+char *xgetline(FILE *fp);
+time_t xmktime(struct tm *tm, int utc);
 
 // lib.c
 void verror_msg(char *msg, int err, va_list va);
@@ -228,11 +230,13 @@
 long long atolx(char *c);
 long long atolx_range(char *numstr, long long low, long long high);
 int stridx(char *haystack, char needle);
+int wctoutf8(char *s, unsigned wc);
 int utf8towc(wchar_t *wc, char *str, unsigned len);
 char *strlower(char *s);
 char *strafter(char *haystack, char *needle);
 char *chomp(char *s);
 int unescape(char c);
+int unescape2(char **c, int echo);
 char *strend(char *str, char *suffix);
 int strstart(char **a, char *b);
 int strcasestart(char **a, char *b);
@@ -268,24 +272,27 @@
 void do_lines(int fd, char delim, void (*call)(char **pline, long len));
 long long millitime(void);
 char *format_iso_time(char *buf, size_t len, struct timespec *ts);
-void reset_env(struct passwd *p, int clear);
 void loggit(int priority, char *format, ...);
 unsigned tar_cksum(void *data);
 int is_tar_header(void *pkt);
 char *elf_arch_name(int type);
 
-#define HR_SPACE 1 // Space between number and units
-#define HR_B     2 // Use "B" for single byte units
-#define HR_1000  4 // Use decimal instead of binary units
-int human_readable_long(char *buf, unsigned long long num, int dgt, int style);
+#define HR_SPACE  1 // Space between number and units
+#define HR_B      2 // Use "B" for single byte units
+#define HR_1000   4 // Use decimal instead of binary units
+#define HR_NODOT  8 // No tenths for single digit units
+int human_readable_long(char *buf, unsigned long long num, int dgt, int unit,
+  int style);
 int human_readable(char *buf, unsigned long long num, int style);
 
 // env.c
 
-long environ_bytes();
-void xsetenv(char *name, char *val);
+long environ_bytes(void);
+char *xsetenv(char *name, char *val);
 void xunsetenv(char *name);
+char *xpop_env(char *name); // because xpopenv() looks like xpopen_v()
 void xclearenv(void);
+void reset_env(struct passwd *p, int clear);
 
 // linestack.c
 
@@ -330,6 +337,7 @@
 #define KEY_ALT (1<<18)
 int scan_key(char *scratch, int timeout_ms);
 int scan_key_getsize(char *scratch, int timeout_ms, unsigned *xx, unsigned *yy);
+void xsetspeed(struct termios *tio, int speed);
 int set_terminal(int fd, int raw, int speed, struct termios *old);
 void xset_terminal(int fd, int raw, int speed, struct termios *old);
 void tty_esc(char *s);
@@ -396,7 +404,7 @@
 void generic_signal(int signal);
 void exit_signal(int signal);
 void sigatexit(void *handler);
-void list_signals();
+void list_signals(void);
 
 mode_t string_to_mode(char *mode_str, mode_t base);
 void mode_to_string(mode_t mode, char *buf);
diff --git a/lib/net.c b/lib/net.c
index 0235444..414c82a 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -114,7 +114,10 @@
       if (pollfds[i].revents & POLLIN) {
         int len = read(pollfds[i].fd, libbuf, sizeof(libbuf));
         if (len<1) pollfds[i].revents = POLLHUP;
-        else xwrite(i ? out2 : out1, libbuf, len);
+        else {
+          xwrite(i ? out2 : out1, libbuf, len);
+          continue;
+        }
       }
       if (pollfds[i].revents & POLLHUP) {
         // Close half-connection.  This is needed for things like
diff --git a/lib/password.c b/lib/password.c
index 432905c..3a39d9e 100644
--- a/lib/password.c
+++ b/lib/password.c
@@ -51,7 +51,7 @@
 {
   struct termios oldtermio;
   struct sigaction sa, oldsa;
-  int i, ret = 1;
+  int i, tty = tty_fd(), ret = 1;
 
   // NOP signal handler to return from the read. Use sigaction() instead
   // of xsignal() because we want to restore the old handler afterwards.
@@ -59,13 +59,12 @@
   sa.sa_handler = generic_signal;
   sigaction(SIGINT, &sa, &oldsa);
 
-  tcflush(0, TCIFLUSH);
-  xset_terminal(0, 1, 0, &oldtermio);
+  tcflush(tty, TCIFLUSH);
+  xset_terminal(tty, 1, 0, &oldtermio);
+  dprintf(tty, "%s", mesg);
 
-  xprintf("%s", mesg);
-
-  for (i=0; i < buflen-1; i++) {
-    if ((ret = read(0, buf+i, 1)) < 0 || (!ret && !i)) {
+  for (i = 0; i<buflen-1; i++) {
+    if ((ret = read(tty, buf+i, 1))<0 || (!ret&&!i) || *buf==4 || buf[i]==3) {
       i = 0;
       ret = 1;
 
@@ -74,11 +73,11 @@
       ret = 0;
 
       break;
-    } else if (buf[i] == 8 || buf[i] == 127) i -= i ? 2 : 1;
+    } else if (buf[i] == 8 || buf[i] == 127) i -= 2-!i;
   }
 
   // Restore terminal/signal state, terminate string
-  sigaction(SIGINT, &oldsa, NULL);
+  sigaction(SIGINT, &oldsa, 0);
   tcsetattr(0, TCSANOW, &oldtermio);
   buf[i] = 0;
   xputc('\n');
diff --git a/lib/portability.c b/lib/portability.c
index 28aaf82..6118d0f 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -49,7 +49,7 @@
 // Get list of mounted filesystems, including stat and statvfs info.
 // Returns a reversed list, which is good for finding overmounts and such.
 
-#if defined(__APPLE__) || defined(__FreeBSD__)
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
 
 #include <sys/mount.h>
 
@@ -126,14 +126,15 @@
 
   if (!typelist) return 1;
 
+  // leading "no" indicates whether entire list is inverted
   skip = strncmp(typelist, "no", 2);
 
   for (;;) {
     if (!(t = comma_iterate(&typelist, &len))) break;
     if (!skip) {
-      // If one -t starts with "no", the rest must too
-      if (strncmp(t, "no", 2)) error_exit("bad typelist");
-      if (!strncmp(t+2, ml->type, len-2)) {
+      // later "no" after first are ignored
+      strstart(&t, "no");
+      if (!strncmp(t, ml->type, len-2)) {
         skip = 1;
         break;
       }
@@ -187,7 +188,7 @@
 
 #endif
 
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__OpenBSD__)
 
 #include <sys/event.h>
 
@@ -331,7 +332,7 @@
   return fsetxattr(fd, name, value, size, 0, flags);
 }
 
-#else
+#elif !defined(__OpenBSD__)
 
 ssize_t xattr_get(const char *path, const char *name, void *value, size_t size)
 {
@@ -429,10 +430,21 @@
   SIGNIFY(VTALRM), SIGNIFY(XCPU), SIGNIFY(XFSZ),
   // Non-POSIX signals that cause termination
   SIGNIFY(PROF), SIGNIFY(IO),
-#ifdef __linux__
-  SIGNIFY(STKFLT), SIGNIFY(POLL), SIGNIFY(PWR),
-#elif defined(__APPLE__)
-  SIGNIFY(EMT), SIGNIFY(INFO),
+  // signals only present/absent on some targets (mips and macos)
+#ifdef SIGEMT
+  SIGNIFY(EMT),
+#endif
+#ifdef SIGINFO
+  SIGNIFY(INFO),
+#endif
+#ifdef SIGPOLL
+  SIGNIFY(POLL),
+#endif
+#ifdef SIGPWR
+  SIGNIFY(PWR),
+#endif
+#ifdef SIGSTKFLT
+  SIGNIFY(STKFLT),
 #endif
 
   // Note: sigatexit relies on all the signals with a default disposition that
@@ -452,9 +464,9 @@
 {
   int i;
 
-  for (i=0; signames[i].num != SIGCHLD; i++)
-    if (signames[i].num != SIGKILL)
-      xsignal(signames[i].num, handler ? exit_signal : SIG_DFL);
+  if (!handler) handler = SIG_DFL;
+  for (i = 0; signames[i].num != SIGCHLD; i++)
+    if (signames[i].num != SIGKILL) xsignal(signames[i].num, handler);
 }
 
 // Convert a string like "9", "KILL", "SIGHUP", or "SIGRTMIN+2" to a number.
@@ -522,6 +534,8 @@
   return ((dev&0xfff00000)>>12)|(dev&0xff);
 #elif defined(__APPLE__)
   return dev&0xffffff;
+#elif defined(__OpenBSD__)
+  return minor(dev);
 #else
 #error
 #endif
@@ -533,6 +547,8 @@
   return (dev&0xfff00)>>8;
 #elif defined(__APPLE__)
   return (dev>>24)&0xff;
+#elif defined(__OpenBSD__)
+  return major(dev);
 #else
 #error
 #endif
@@ -544,6 +560,8 @@
   return (minor&0xff)|((major&0xfff)<<8)|((minor&0xfff00)<<12);
 #elif defined(__APPLE__)
   return (minor&0xffffff)|((major&0xff)<<24);
+#elif defined(__OpenBSD__)
+  return makedev(major, minor);
 #else
 #error
 #endif
@@ -551,7 +569,7 @@
 
 char *fs_type_name(struct statfs *statfs)
 {
-#if defined(__APPLE__)
+#if defined(__APPLE__) || defined(__OpenBSD__)
   // macOS has an `f_type` field, but assigns values dynamically as filesystems
   // are registered. They do give you the name directly though, so use that.
   return statfs->f_fstypename;
@@ -575,3 +593,64 @@
   return s;
 #endif
 }
+
+#if defined(__APPLE__)
+#include <sys/disk.h>
+int get_block_device_size(int fd, unsigned long long* size)
+{
+  unsigned long block_size, block_count;
+
+  if (!ioctl(fd, DKIOCGETBLOCKSIZE, &block_size) &&
+      !ioctl(fd, DKIOCGETBLOCKCOUNT, &block_count)) {
+    *size = block_count * block_size;
+    return 1;
+  }
+  return 0;
+}
+#elif defined(__linux__)
+int get_block_device_size(int fd, unsigned long long* size)
+{
+  return (ioctl(fd, BLKGETSIZE64, size) >= 0);
+}
+#elif defined(__OpenBSD__)
+#include <sys/dkio.h>
+#include <sys/disklabel.h>
+int get_block_device_size(int fd, unsigned long long* size)
+{
+  struct disklabel lab;
+  int status = (ioctl(fd, DIOCGDINFO, &lab) >= 0);
+  *size = lab.d_secsize * lab.d_nsectors;
+  return status;
+}
+#endif
+
+// TODO copy_file_range
+// Return bytes copied from in to out. If bytes <0 copy all of in to out.
+// If consuemd isn't null, amount read saved there (return is written or error)
+long long sendfile_len(int in, int out, long long bytes, long long *consumed)
+{
+  long long total = 0, len, ww;
+
+  if (consumed) *consumed = 0;
+  if (in<0) return 0;
+  while (bytes != total) {
+    ww = 0;
+    len = bytes-total;
+    if (bytes<0 || len>sizeof(libbuf)) len = sizeof(libbuf);
+
+    errno = 0;
+#if CFG_TOYBOX_COPYFILERANGE
+    len = copy_file_range(in, 0, out, 0, bytes, 0);
+#else
+    ww = len = read(in, libbuf, len);
+#endif
+    if (len<1 && errno==EAGAIN) continue;
+    if (len<1) break;
+    if (consumed) *consumed += len;
+    if (ww && writeall(out, libbuf, len) != len) return -1;
+    total += len;
+  }
+
+  return total;
+}
+
diff --git a/lib/portability.h b/lib/portability.h
index d81ddea..bb792f1 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -4,8 +4,13 @@
 // in specific compiler, library, or OS versions, localize all that here
 // and in portability.c
 
+// Always use long file support.
+// This must come before we #include any system header file to take effect!
+#define _FILE_OFFSET_BITS 64
+
 // For musl
 #define _ALL_SOURCE
+#include <regex.h>
 #ifndef REG_STARTEND
 #define REG_STARTEND 0
 #endif
@@ -29,9 +34,6 @@
 #define printf_format
 #endif
 
-// Always use long file support.
-#define _FILE_OFFSET_BITS 64
-
 // This isn't in the spec, but it's how we determine what libc we're using.
 
 // Types various replacement prototypes need.
@@ -103,6 +105,8 @@
 char *__xpg_basename(char *path);
 static inline char *basename(char *path) { return __xpg_basename(path); }
 char *strcasestr(const char *haystack, const char *needle);
+void *memmem(const void *haystack, size_t haystack_length,
+  const void *needle, size_t needle_length);
 #endif // defined(glibc)
 
 // getopt_long(), getopt_long_only(), and struct option.
@@ -129,7 +133,7 @@
 #define bswap_32(x) OSSwapInt32(x)
 #define bswap_64(x) OSSwapInt64(x)
 
-#elif defined(__FreeBSD__)
+#elif defined(__FreeBSD__) || defined(__OpenBSD__)
 
 #include <sys/endian.h>
 
@@ -180,7 +184,7 @@
 
 #ifdef __APPLE__
 #include <util.h>
-#elif !defined(__FreeBSD__)
+#elif !defined(__FreeBSD__) && !defined(__OpenBSD__)
 #include <pty.h>
 #else
 #include <termios.h>
@@ -204,12 +208,24 @@
 ssize_t xattr_fset(int, const char*, const void*, size_t, int);
 #endif
 
+#if defined(__APPLE__)
 // macOS doesn't have these functions, but we can fake them.
-#ifdef __APPLE__
 int mknodat(int, const char*, mode_t, dev_t);
 int posix_fallocate(int, off_t, off_t);
+
+// macOS keeps newlocale(3) in the non-POSIX <xlocale.h> rather than <locale.h>.
+#include <xlocale.h>
 #endif
 
+#if defined(__APPLE__) || defined(__OpenBSD__)
+static inline long statfs_bsize(struct statfs *sf) { return sf->f_iosize; }
+static inline long statfs_frsize(struct statfs *sf) { return sf->f_bsize; }
+#else
+static inline long statfs_bsize(struct statfs *sf) { return sf->f_bsize; }
+static inline long statfs_frsize(struct statfs *sf) { return sf->f_frsize; }
+#endif
+
+
 // Android is missing some headers and functions
 // "generated/config.h" is included first
 #if CFG_TOYBOX_SHADOW
@@ -338,10 +354,6 @@
 int xnotify_add(struct xnotify *not, int fd, char *path);
 int xnotify_wait(struct xnotify *not, char **path);
 
-#ifdef __APPLE__
-#define f_frsize f_iosize
-#endif
-
 int sig_to_num(char *s);
 char *num_to_sig(int sig);
 
@@ -357,3 +369,5 @@
 int dev_makedev(int major, int minor);
 
 char *fs_type_name(struct statfs *statfs);
+
+int get_block_device_size(int fd, unsigned long long *size);
diff --git a/lib/toyflags.h b/lib/toyflags.h
index b158ba8..c883087 100644
--- a/lib/toyflags.h
+++ b/lib/toyflags.h
@@ -32,6 +32,9 @@
 // Suppress default --help processing
 #define TOYFLAG_NOHELP   (1<<10)
 
+// Line buffered stdout
+#define TOYFLAG_LINEBUF  (1<<11)
+
 // Error code to return if argument parsing fails (default 1)
 #define TOYFLAG_ARGFAIL(x) (x<<24)
 
diff --git a/lib/tty.c b/lib/tty.c
index a6b4576..d7e0f47 100644
--- a/lib/tty.c
+++ b/lib/tty.c
@@ -61,6 +61,20 @@
   return 0;
 }
 
+void xsetspeed(struct termios *tio, int speed)
+{
+  int i, speeds[] = {50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400,
+                    4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800,
+                    500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
+                    2500000, 3000000, 3500000, 4000000};
+
+  // Find speed in table, adjust to constant
+  for (i = 0; i < ARRAY_LEN(speeds); i++) if (speeds[i] == speed) break;
+  if (i == ARRAY_LEN(speeds)) error_exit("unknown speed: %d", speed);
+  cfsetspeed(tio, i+1+4081*(i>15));
+}
+
+
 // Reset terminal to known state, saving copy of old state if old != NULL.
 int set_terminal(int fd, int raw, int speed, struct termios *old)
 {
@@ -136,9 +150,11 @@
 
   // VT102/VT220 escapes.
   {KEY_HOME, "\033[1~"},
+  {KEY_HOME|KEY_CTRL, "\033[1;5~"},
   {KEY_INSERT, "\033[2~"},
   {KEY_DELETE, "\033[3~"},
   {KEY_END, "\033[4~"},
+  {KEY_END|KEY_CTRL, "\033[4;5~"},
   {KEY_PGUP, "\033[5~"},
   {KEY_PGDN, "\033[6~"},
   // "Normal" "PC" escapes (xterm).
@@ -147,6 +163,8 @@
   // "Application" "PC" escapes (gnome-terminal).
   {KEY_HOME, "\033[H"},
   {KEY_END, "\033[F"},
+  {KEY_HOME|KEY_CTRL, "\033[1;5H"},
+  {KEY_END|KEY_CTRL, "\033[1;5F"},
 
   {KEY_FN+1, "\033OP"}, {KEY_FN+2, "\033OQ"}, {KEY_FN+3, "\033OR"},
   {KEY_FN+4, "\033OS"}, {KEY_FN+5, "\033[15~"}, {KEY_FN+6, "\033[17~"},
diff --git a/lib/xwrap.c b/lib/xwrap.c
index 2f47ac6..64137da 100644
--- a/lib/xwrap.c
+++ b/lib/xwrap.c
@@ -96,7 +96,6 @@
 char *xstrndup(char *s, size_t n)
 {
   char *ret = strndup(s, n);
-
   if (!ret) error_exit("xstrndup");
 
   return ret;
@@ -127,8 +126,7 @@
   va_copy(va2, va);
 
   // How long is it?
-  len = vsnprintf(0, 0, format, va);
-  len++;
+  len = vsnprintf(0, 0, format, va)+1;
   va_end(va);
 
   // Allocate and do the sprintf()
@@ -159,14 +157,8 @@
 // Put string with length (does not append newline)
 void xputsl(char *s, int len)
 {
-  int out;
-
-  while (len != (out = fwrite(s, 1, len, stdout))) {
-    if (out<1) perror_exit("write");
-    len -= out;
-    s += out;
-  }
-  xflush(0);
+  xflush(1);
+  xwrite(1, s, len);
 }
 
 // xputs with no newline
@@ -188,6 +180,30 @@
   xflush(0);
 }
 
+// daemonize via vfork(). Does not chdir("/"), caller should do that first
+// note: restarts process from command_main()
+void xvdaemon(void)
+{
+  int fd;
+
+  // vfork and exec /proc/self/exe
+  if (toys.stacktop) {
+    xpopen_both(0, 0);
+    _exit(0);
+  }
+
+  // new session id, point fd 0-2 at /dev/null, detach from tty
+  setsid();
+  close(0);
+  xopen_stdio("/dev/null", O_RDWR);
+  dup2(0, 1);
+  if (-1 != (fd = open("/dev/tty", O_RDONLY))) {
+    ioctl(fd, TIOCNOTTY);
+    close(fd);
+  }
+  dup2(0, 2);
+}
+
 // This is called through the XVFORK macro because parent/child of vfork
 // share a stack, so child returning from a function would stomp the return
 // address parent would need. Solution: make vfork() an argument so processes
@@ -207,8 +223,8 @@
 void xexec(char **argv)
 {
   // Only recurse to builtin when we have multiplexer and !vfork context.
-  if (CFG_TOYBOX && !CFG_TOYBOX_NORECURSE && toys.stacktop && **argv != '/')
-    toy_exec(argv);
+  if (CFG_TOYBOX && !CFG_TOYBOX_NORECURSE)
+    if (toys.stacktop && !strchr(*argv, '/')) toy_exec(argv);
   execvp(argv[0], argv);
 
   toys.exitval = 126+(errno == ENOENT);
@@ -224,7 +240,7 @@
 //           If -1, replace with pipe handle connected to stdin/stdout.
 //           NULL treated as {0, 1}, I.E. leave stdin/stdout as is
 // return: pid of child process
-pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void))
+pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(char **argv))
 {
   int cestnepasun[4], pid;
 
@@ -269,7 +285,7 @@
         close(pipes[1]);
       }
     }
-    if (callback) callback();
+    if (callback) callback(argv);
     if (argv) xexec(argv);
 
     // In fork() case, force recursion because we know it's us.
@@ -287,6 +303,7 @@
       // setting high bit of argv[0][0] to let new process know
       **toys.argv |= 0x80;
       execv(s, toys.argv);
+      if ((s = getenv("_"))) execv(s, toys.argv);
       perror_msg_raw(s);
 
       _exit(127);
@@ -380,7 +397,7 @@
 {
   int fd = open(path, (flags^O_CLOEXEC)&~WARN_ONLY, mode);
 
-  if (fd == -1) ((mode&WARN_ONLY) ? perror_msg_raw : perror_exit_raw)(path);
+  if (fd == -1) ((flags&WARN_ONLY) ? perror_msg_raw : perror_exit_raw)(path);
   return fd;
 }
 
@@ -397,7 +414,7 @@
 
 void xclose(int fd)
 {
-  if (close(fd)) perror_exit("xclose");
+  if (fd != -1 && close(fd)) perror_exit("xclose");
 }
 
 int xdup(int fd)
@@ -813,29 +830,6 @@
   close(fd);
 }
 
-// Return bytes copied from in to out. If bytes <0 copy all of in to out.
-// If consuemd isn't null, amount read saved there (return is written or error)
-long long sendfile_len(int in, int out, long long bytes, long long *consumed)
-{
-  long long total = 0, len;
-
-  if (consumed) *consumed = 0;
-  if (in<0) return 0;
-  while (bytes != total) {
-    len = bytes-total;
-    if (bytes<0 || len>sizeof(libbuf)) len = sizeof(libbuf);
-
-    len = read(in, libbuf, len);
-    if (!len && errno==EAGAIN) continue;
-    if (len<1) break;
-    if (consumed) *consumed += len;
-    if (writeall(out, libbuf, len) != len) return -1;
-    total += len;
-  }
-
-  return total;
-}
-
 // error_exit if we couldn't copy all bytes
 long long xsendfile_len(int in, int out, long long bytes)
 {
@@ -942,7 +936,7 @@
 
   if ((rc = regcomp(preg, regex, cflags))) {
     regerror(rc, preg, libbuf, sizeof(libbuf));
-    error_exit("bad regex: %s", libbuf);
+    error_exit("bad regex '%s': %s", regex, libbuf);
   }
 }
 
@@ -995,6 +989,7 @@
   // Formats with seconds come first. Posix can't agree on whether 12 digits
   // has year before (touch -t) or year after (date), so support both.
   char *s = str, *p, *oldtz = 0, *formats[] = {"%Y-%m-%d %T", "%Y-%m-%dT%T",
+    "%a %b %e %H:%M:%S %Z %Y", // date(1) output format in POSIX/C locale.
     "%H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d", "%H:%M", "%m%d%H%M",
     endian ? "%m%d%H%M%y" : "%y%m%d%H%M",
     endian ? "%m%d%H%M%C%y" : "%C%y%m%d%H%M"};
@@ -1020,15 +1015,6 @@
     xvali_date(0, str);
   }
 
-  // Trailing Z means UTC timezone, don't expect libc to know this.
-  // (Trimming it off here means it won't show up in error messages.)
-  if ((i = strlen(str)) && toupper(str[i-1])=='Z') {
-    str[--i] = 0;
-    oldtz = getenv("TZ");
-    if (oldtz) oldtz = xstrdup(oldtz);
-    setenv("TZ", "UTC0", 1);
-  }
-
   // Try each format
   for (i = 0; i<ARRAY_LEN(formats); i++) {
     localtime_r(&now, &tm);
@@ -1036,6 +1022,7 @@
     tm.tm_isdst = -endian;
 
     if ((p = strptime(s, formats[i], &tm))) {
+      // Handle optional fractional seconds.
       if (*p == '.') {
         p++;
         // If format didn't already specify seconds, grab seconds
@@ -1051,6 +1038,27 @@
         }
       }
 
+      // Handle optional Z or +HH[[:]MM] timezone
+      if (*p && strchr("Z+-", *p)) {
+        unsigned hh, mm = 0, len;
+        char *tz, sign = *p++;
+
+        if (sign == 'Z') tz = "UTC0";
+        else if (sscanf(p, "%2u%2u%n",  &hh, &mm, &len) == 2
+              || sscanf(p, "%2u%n:%2u%n", &hh, &len, &mm, &len) > 0)
+        {
+          // flip sign because POSIX UTC offsets are backwards
+          sprintf(tz = libbuf, "UTC%c%02d:%02d", "+-"[sign=='+'], hh, mm);
+          p += len;
+        } else continue;
+
+        if (!oldtz) {
+          oldtz = getenv("TZ");
+          if (oldtz) oldtz = xstrdup(oldtz);
+        }
+        setenv("TZ", tz, 1);
+      }
+
       if (!*p) break;
     }
   }
@@ -1062,18 +1070,31 @@
   free(oldtz);
 }
 
-char *xgetline(FILE *fp, int *len)
+// Return line of text from file. Strips trailing newline (if any).
+char *xgetline(FILE *fp)
 {
   char *new = 0;
-  size_t linelen = 0;
+  size_t len = 0;
   long ll;
 
   errno = 0;
-  if (1>(ll = getline(&new, &linelen, fp))) {
-    if (errno) perror_msg("getline");
+  if (1>(ll = getline(&new, &len, fp))) {
+    if (errno && errno != EINTR) perror_msg("getline");
     new = 0;
-  } else if (new[linelen-1] == '\n') new[--linelen] = 0;
-  if (len) *len = ll;
+  } else if (new[ll-1] == '\n') new[--ll] = 0;
 
   return new;
 }
+
+time_t xmktime(struct tm *tm, int utc)
+{
+  char *old_tz = utc ? xtzset("UTC0") : 0;
+  time_t result;
+
+  if ((result = mktime(tm)) < 0) error_exit("mktime");
+  if (utc) {
+    free(xtzset(old_tz));
+    free(old_tz);
+  }
+  return result;
+}
diff --git a/main.c b/main.c
index 60cb2b0..d5abe6e 100644
--- a/main.c
+++ b/main.c
@@ -21,7 +21,7 @@
 
 struct toy_context toys;
 union global_union this;
-char toybuf[4096], libbuf[4096];
+char *toybox_version = TOYBOX_VERSION, toybuf[4096], libbuf[4096];
 
 struct toy_list *toy_find(char *name)
 {
@@ -64,11 +64,11 @@
 {
   toys.exitval = 127;
   toys.which = toy_list;
-  error_exit("Unknown command %s", name);
+  help_exit("Unknown command %s", name);
 }
 
 // Setup toybox global state for this command.
-static void toy_singleinit(struct toy_list *which, char *argv[])
+void toy_singleinit(struct toy_list *which, char *argv[])
 {
   toys.which = which;
   toys.argv = argv;
@@ -84,7 +84,7 @@
     }
 
     if (!strcmp(argv[1], "--version")) {
-      xputs("toybox "TOYBOX_VERSION);
+      xprintf("toybox %s\n", toybox_version);
       xexit();
     }
   }
@@ -95,15 +95,18 @@
     for (toys.optc = 0; toys.optargs[toys.optc]; toys.optc++);
   }
 
-  if (!(which->flags & TOYFLAG_NOFORK)) {
+  if (!(CFG_TOYBOX && which == toy_list) && !(which->flags & TOYFLAG_NOFORK)) {
     toys.old_umask = umask(0);
     if (!(which->flags & TOYFLAG_UMASK)) umask(toys.old_umask);
-    if (CFG_TOYBOX_I18N) {
-      // Deliberately try C.UTF-8 before the user's locale to work around users
-      // that choose non-UTF-8 locales. macOS doesn't support C.UTF-8 though.
-      if (!setlocale(LC_CTYPE, "C.UTF-8")) setlocale(LC_CTYPE, "");
-    }
-    setlinebuf(stdout);
+
+    // Try user's locale, but merge in the en_US.UTF-8 locale's character
+    // type data if the user's locale isn't UTF-8. (We can't merge in C.UTF-8
+    // because that locale doesn't exist on macOS.)
+    setlocale(LC_CTYPE, "");
+    if (strcmp("UTF-8", nl_langinfo(CODESET)))
+      uselocale(newlocale(LC_CTYPE_MASK, "en_US.UTF-8", NULL));
+
+    setvbuf(stdout, 0, (which->flags & TOYFLAG_LINEBUF) ? _IOLBF : _IONBF, 0);
   }
 }
 
@@ -143,10 +146,10 @@
 
 // Run an internal toybox command.
 // Only returns if it can't run command internally, otherwise xexit() when done.
-void toy_exec_which(struct toy_list *which, char *argv[])
+static void toy_exec_which(struct toy_list *which, char *argv[])
 {
   // Return if we can't find it (which includes no multiplexer case),
-  if (!which) return;
+  if (!which || (which->flags&TOYFLAG_NOFORK)) return;
 
   // Return if stack depth getting noticeable (proxy for leaked heap, etc).
 
@@ -169,35 +172,35 @@
 // Lookup internal toybox command to run via argv[0]
 void toy_exec(char *argv[])
 {
-  toy_exec_which(toy_find(basename(*argv)), argv);
+  toy_exec_which(toy_find(*argv), argv);
 }
 
 // Multiplexer command, first argument is command to run, rest are args to that.
 // If first argument starts with - output list of command install paths.
 void toybox_main(void)
 {
-  static char *toy_paths[] = {"usr/","bin/","sbin/",0};
+  char *toy_paths[] = {"usr/", "bin/", "sbin/", 0}, *s = toys.argv[1];
   int i, len = 0;
+  unsigned width = 80;
 
   // fast path: try to exec immediately.
   // (Leave toys.which null to disable suid return logic.)
   // Try dereferencing one layer of symlink
-  if (toys.argv[1]) {
-    toy_exec(toys.argv+1);
-    if (0<readlink(toys.argv[1], libbuf, sizeof(libbuf))) {
-      struct toy_list *tl= toy_find(basename(libbuf));
+  while (s) {
+    struct toy_list *tl = toy_find(basename(s));
 
-      if (tl == toy_list) unknown(basename(toys.argv[1]));
-      else toy_exec_which(tl, toys.argv+1);
-    }
+    if (tl==toy_list && s!=toys.argv[1]) unknown(basename(s));
+    toy_exec_which(toy_find(basename(s)), toys.argv+1);
+    s = (0<readlink(s, libbuf, sizeof(libbuf))) ? libbuf : 0;
   }
 
   // For early error reporting
   toys.which = toy_list;
 
-  if (toys.argv[1] && toys.argv[1][0] != '-') unknown(toys.argv[1]);
+  if (toys.argv[1] && strcmp(toys.argv[1], "--long")) unknown(toys.argv[1]);
 
-  // Output list of command.
+  // Output list of commands.
+  terminal_size(&width, 0);
   for (i = 1; i<ARRAY_LEN(toy_list); i++) {
     int fl = toy_list[i].flags;
     if (fl & TOYMASK_LOCATION) {
@@ -207,7 +210,7 @@
           if (fl & (1<<j)) len += printf("%s", toy_paths[j]);
       }
       len += printf("%s",toy_list[i].name);
-      if (++len > 65) len = 0;
+      if (++len > width-15) len = 0;
       xputc(len ? ' ' : '\n');
     }
   }
diff --git a/run-tests-on-android.sh b/run-tests-on-android.sh
index d85cca3..89c8c91 100755
--- a/run-tests-on-android.sh
+++ b/run-tests-on-android.sh
@@ -5,6 +5,9 @@
 #
 
 # Copy the toybox tests across.
+if [[ $(adb shell getprop ro.debuggable) == 1 ]]; then
+  adb shell su root rm -rf /data/local/tmp/toybox-tests/
+fi
 adb shell rm -rf /data/local/tmp/toybox-tests/
 adb shell mkdir /data/local/tmp/toybox-tests/
 adb push tests/ /data/local/tmp/toybox-tests/
diff --git a/scripts/cross.sh b/scripts/cross.sh
deleted file mode 100755
index 2570713..0000000
--- a/scripts/cross.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/bash
-
-# Convenience wrapper to set $CROSS_COMPILE from short name using "ccc"
-# symlink (Cross C Compiler) to a directory of cross compilers named
-# $TARGET-*-cross. Tested with scripts/mcm-buildall.sh output.
-
-# Usage: scripts/cross.sh $TARGET make distclean defconfig toybox
-# With no arguments, lists available targets. Use target "all" to iterate
-# through each $TARGET from the list.
-
-CCC="$(dirname "$(readlink -f "$0")")"/../ccc
-if [ ! -d "$CCC" ]
-then
-  echo "Create symlink 'ccc' to cross compiler directory, ala:"
-  echo "  ln -s ~/musl-cross-make/ccc ccc"
-  exit 1
-fi
-
-unset X Y
-
-# Display target list?
-list()
-{
-  ls "$CCC" | sed 's/-.*//' | sort -u | xargs
-}
-[ $# -eq 0 ] && list && exit
-
-X="$1"
-shift
-
-# build all targets?
-if [ "$X" == all ]
-then
-  for TARGET in $(list)
-  do
-    {
-      export TARGET
-      echo -en "\033]2;$TARGET $*\007"
-      "$0" $TARGET "$@" 2>&1 || mv cross-log-$TARGET.{txt,failed}
-    } | tee cross-log-$TARGET.txt
-  done
-
-  exit
-fi
-
-# Call command with CROSS_COMPILE= as its first argument
-
-Y=$(echo "$CCC/$X"-*cross)
-Z=$(basename "$Y")
-Y=$(readlink -f "$CCC"/$X-*cross)
-export TARGET="${Z/-*/}"
-X="$Y/bin/${Z/-cross/-}"
-[ ! -e "${X}cc" ] && echo "${X}cc not found" && exit 1
-
-CROSS_COMPILE="$X" "$@"
diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh
index b5637f8..955c82c 100755
--- a/scripts/genconfig.sh
+++ b/scripts/genconfig.sh
@@ -107,6 +107,12 @@
     #include <sys/random.h>
     int main(void) { char buf[100]; getrandom(buf, 100, 0); }
 EOF
+
+  probesymbol TOYBOX_COPYFILERANGE << EOF
+    #include <sys/syscall.h>
+    #include <unistd.h>
+    int main(void) { copyfilerange(0, 0, 1, 0, 123, 0); }
+EOF
 }
 
 genconfig()
@@ -151,6 +157,7 @@
 do
   [ "$NAME" == help ] && continue
   [ "$NAME" == install ] && continue
+  [ "$NAME" == sh ] && FILE="toys/*/*.c"
   echo -e "$NAME: $FILE *.[ch] lib/*.[ch]\n\tscripts/single.sh $NAME\n"
   echo -e "test_$NAME:\n\tscripts/test.sh $NAME\n"
   [ "${FILE/pending//}" != "$FILE" ] &&
diff --git a/scripts/help.txt b/scripts/help.txt
index 7d52916..5bf0d61 100644
--- a/scripts/help.txt
+++ b/scripts/help.txt
@@ -17,6 +17,11 @@
   install         - Install toybox into subdirectories of $PREFIX.
   uninstall_flat  - Remove toybox from $PREFIX directory.
   uninstall       - Remove toybox from subdirectories of $PREFIX.
+  root            - Build/package root filesystem under root/ controlled by
+                    CROSS= cross compile (see scripts/mcm-buildall.sh)
+                    LINUX= build kernel from this source, configured for qemu
+  run_root        - boot toyroot under qemu, I.E. cd root/$CROSS && ./qemu-*.sh
 
-example: CFLAGS="--static" CROSS_COMPILE=armv5l- make defconfig toybox install
+example: make defconfig toybox install CFLAGS="--static" CROSS_COMPILE=armv5l-
+or     : make root run_root CROSS=sh4 LINUX=~/linux
 
diff --git a/scripts/install.sh b/scripts/install.sh
index 92d8157..648a241 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -43,7 +43,7 @@
 then
   mkdir -p "${PREFIX}/${LONG_PATH}" &&
   rm -f "${PREFIX}/${LONG_PATH}/toybox" &&
-  cp toybox ${PREFIX}/${LONG_PATH} || exit 1
+  cp toybox"${TARGET:+-$TARGET}" ${PREFIX}/${LONG_PATH} || exit 1
 else
   rm -f "${PREFIX}/${LONG_PATH}/toybox" 2>/dev/null
 fi
@@ -86,13 +86,13 @@
   # Create link
   if [ -z "$UNINSTALL" ]
   then
-    ln $DO_FORCE $LINK_TYPE ${DOTPATH}toybox $i || EXIT=1
+    ln $DO_FORCE $LINK_TYPE ${DOTPATH}"toybox${TARGET:+-$TARGET}" $i || EXIT=1
   else
     rm -f $i || EXIT=1
   fi
 done
 
-[ -z "$AIRLOCK" ] && exit 0
+[ -z "$AIRLOCK" ] && exit $EXIT
 
 # --airlock creates a single directory you can point the $PATH to for cross
 # compiling, which contains just toybox and symlinks to toolchain binaries.
@@ -111,13 +111,10 @@
 # "gcc" should go away for llvm, but some things still hardwire it
 TOOLCHAIN="as cc ld gcc objdump"
 
-if [ ! -z "$AIRLOCK" ]
-then
-
-  # Tools needed to build packages
-  for i in $TOOLCHAIN $PENDING $HOST_EXTRA
-  do
-    if [ ! -f "$i" ]
+# Tools needed to build packages
+for i in $TOOLCHAIN $PENDING $HOST_EXTRA
+do
+  if [ ! -f "$i" ]
   then
     # Loop through each instance, populating fallback directories (used by
     # things like distcc, which require multiple instances of the same binary
@@ -145,8 +142,4 @@
   fi
 done
 
-
-
-fi
-
 exit $EXIT
diff --git a/scripts/make.sh b/scripts/make.sh
index e4333ba..5b2d5d8 100755
--- a/scripts/make.sh
+++ b/scripts/make.sh
@@ -47,7 +47,7 @@
 
 mkdir -p generated/unstripped
 
-if isnewer generated/Config.in toys
+if isnewer generated/Config.in toys || isnewer generated/Config.in Config.in
 then
   echo "Extract configuration information from toys/*.c files..."
   scripts/genconfig.sh
diff --git a/scripts/mcm-buildall.sh b/scripts/mcm-buildall.sh
index e82ce2d..cac4137 100755
--- a/scripts/mcm-buildall.sh
+++ b/scripts/mcm-buildall.sh
@@ -3,6 +3,8 @@
 # Script to build all cross and native compilers supported by musl-libc.
 # This isn't directly used by toybox, but is useful for testing.
 
+trap "exit 1" INT
+
 if [ ! -d litecross ]
 then
   echo Run this script in musl-cross-make directory to make "ccc" directory.
@@ -47,14 +49,16 @@
       HOST="$TARGET"
       export NATIVE=y
       LP="$OUTPUT/${RENAME:-$TARGET}-cross/bin:$LP"
+      [ -z "$(PATH="$LP" which $TARGET-cc)" ] &&
+         echo "no $TARGET-cc in $LP" && return
     fi
     COMMON_CONFIG="CC=\"$HOST-gcc -static --static\" CXX=\"$HOST-g++ -static --static\""
     export -n HOST
     OUTPUT="$OUTPUT/${RENAME:-$TARGET}-$TYPE"
   fi
 
-  if [ -e "$OUTPUT.sqf" ] || [ -e "$OUTPUT/bin/$TARGET-ld" ] ||
-     [ -e "$OUTPUT/bin/ld" ]
+  if [ -e "$OUTPUT.sqf" ] || [ -e "$OUTPUT/bin/$TARGET-cc" ] ||
+     [ -e "$OUTPUT/bin/cc" ]
   then
     return
   fi
@@ -65,11 +69,7 @@
   echo -en "\033]2;$TARGET-$TYPE\007"
 
   rm -rf build/"$TARGET" "$OUTPUT" &&
-  if [ -z "$CPUS" ]
-  then
-    CPUS="$(nproc)"
-    [ "$CPUS" != 1 ] && CPUS=$(($CPUS+1))
-  fi
+  [ -z "$CPUS" ] && CPUS=$(($(nproc)+1))
   set -x &&
   PATH="$LP" make OUTPUT="$OUTPUT" TARGET="$TARGET" \
     GCC_CONFIG="--disable-nls --disable-libquadmath --disable-decimal-float --disable-multilib --enable-languages=c,c++ $GCC_CONFIG" \
@@ -236,13 +236,14 @@
   # which is used to build the rest (in alphabetical order)
   for i in i686:: \
          aarch64:eabi: armv4l:eabihf:"--with-arch=armv5t --with-float=soft" \
-         "armv5l:eabihf:--with-arch=armv5t --with-float=vfp" \
-         "armv7l:eabihf:--with-arch=armv7-a --with-float=vfp" \
+         "armv5l:eabihf:--with-arch=armv5t --with-fpu=vfpv2 --with-float=hard" \
+         "armv7l:eabihf:--with-arch=armv7-a --with-fpu=vfpv3-d16 --with-float=hard" \
          "armv7m:eabi:--with-arch=armv7-m --with-mode=thumb --disable-libatomic --enable-default-pie" \
          armv7r:eabihf:"--with-arch=armv7-r --enable-default-pie" \
          i486:: m68k:: microblaze:: mips:: mips64:: mipsel:: powerpc:: \
          powerpc64:: powerpc64le:: s390x:: sh2eb:fdpic:--with-cpu=mj2 \
-         sh4::--enable-incomplete-targets x86_64:: x86_64@x32:x32:
+         sh4::--enable-incomplete-targets x86_64::--with-mtune=nocona \
+         x86_64@x32:x32:
   do
     make_tuple "$i"
   done
diff --git a/scripts/minicom.sh b/scripts/minicom.sh
deleted file mode 100755
index f47d096..0000000
--- a/scripts/minicom.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# If you want to use toybox netcat to talk to a serial port, use this.
-
-if [ ! -c "$1" ]
-then
-  echo "usage: minicom.sh /dev/ttyS0"
-  exit 1
-fi
-
-SPEED="$2"
-[ -z "$SPEED" ] && SPEED=115200
-
-stty $SPEED -F "$1"
-stty raw -echo -ctlecho -F "$1"
-stty raw -echo  # Need to do it on stdin, too.
-./toybox netcat -f "$1"
-stty cooked echo  # Put stdin back.
diff --git a/scripts/mkflags.c b/scripts/mkflags.c
index fff9dd4..7ea8770 100644
--- a/scripts/mkflags.c
+++ b/scripts/mkflags.c
@@ -22,21 +22,23 @@
 int chrtype(char c)
 {
   // Does this populate a GLOBALS() variable?
-  if (strchr("?&^-:#|@*; %", c)) return 1;
+  if (strchr("^-:#|@*; %.", c)) return 1;
 
   // Is this followed by a numeric argument in optstr?
   if (strchr("=<>", c)) return 2;
 
+  if (strchr("?&0", c)) return 3;
+
   return 0;
 }
 
 // replace chopped out USE_BLAH() sections with low-ascii characters
-// showing how many flags got skipped
+// showing how many flags got skipped so FLAG_ macros stay constant
 
 char *mark_gaps(char *flags, char *all)
 {
   char *n, *new, c;
-  int bare = 1;
+  int bare = 2;
 
   // Shell feeds in " " for blank args, leading space not meaningful.
   while (isspace(*flags)) flags++;
@@ -48,7 +50,8 @@
     if (*all == '(') {
       int len = 0;
 
-      while (all[len++] != ')');
+      if (bare) bare = 1;
+      while (all[len]) if (all[len++] == ')') break;
       if (strncmp(flags, all, len)) {
         // bare longopts need their own skip placeholders
         if (bare) *(new++) = 1;
@@ -61,7 +64,7 @@
       continue;
     }
     c = *(all++);
-    if (bare) bare = chrtype(c);
+    if (bare && !chrtype(c)) bare = 0;
     if (*flags == c) {
       *(new++) = c;
       flags++;
@@ -69,7 +72,7 @@
     }
 
     c = chrtype(c);
-    if (!c) *(new++) = 1;
+    if (!c || (!bare && c==3)) *(new++) = 1;
     else if (c==2) while (isdigit(*all)) all++;
   }
   *new = 0;
@@ -81,7 +84,7 @@
 
 struct flag *digest(char *string)
 {
-  struct flag *list = NULL;
+  struct flag *list = 0;
   char *err = string, c;
 
   while (*string) {
@@ -112,7 +115,7 @@
     }
 
     c = chrtype(*string);
-    if (c == 1) string++;
+    if (c == 1 || (c == 3 && !list)) string++;
     else if (c == 2) {
       if (string[1]=='-') string++;
       if (!isdigit(string[1])) {
diff --git a/scripts/mkroot.sh b/scripts/mkroot.sh
index 371ba48..0953f41 100755
--- a/scripts/mkroot.sh
+++ b/scripts/mkroot.sh
@@ -1,133 +1,138 @@
 #!/bin/bash
 
 # Clear environment variables by restarting script w/bare minimum passed through
-[ -z "$NOCLEAR" ] &&
-  exec env -i NOCLEAR=1 HOME="$HOME" PATH="$PATH" LINUX="$LINUX" \
-    CROSS_COMPILE="$CROSS_COMPILE" CROSS_SHORT="$CROSS_SHORT" "$0" "$@"
+[ -z "$NOCLEAR" ] && exec env -i NOCLEAR=1 HOME="$HOME" PATH="$PATH" \
+    "LINUX=$LINUX" "CROSS=$CROSS" CROSS_COMPILE="$CROSS_COMPILE" "$0" "$@"
 
-# assign command line NAME=VALUE args to env vars
+# assign command line NAME=VALUE args to env vars, keeping rest as packages
 while [ $# -ne 0 ]; do
-  X="${1/=*/}" Y="${1#*=}"
-  [ "${1/=/}" != "$1" ] && eval "export $X=\"\$Y\"" || echo "unknown $i"
+  [ "${1/=/}" != "$1" ] && eval "export ${1/=*/}=\"\${1#*=}\"" ||
+    { [ "$1" != '--' ] && PKG="${PKG:-plumbing} $1"; }
   shift
 done
 
-# If we're cross compiling, set appropriate environment variables.
-if [ -z "$CROSS_COMPILE" ]; then
-  echo "Building natively"
-  if ! cc --static -xc - -o /dev/null <<< "int main(void) {return 0;}"; then
-    echo "Warning: host compiler can't create static binaries." >&2
-    sleep 3
-  fi
-else
-  CROSS_PATH="$(dirname "$(which "${CROSS_COMPILE}cc")")"
-  CROSS_BASE="$(basename "$CROSS_COMPILE")"
-  [ -z "$CROSS_SHORT" ] && CROSS_SHORT="${CROSS_BASE/-*/}"
-  echo "Cross compiling to $CROSS_SHORT"
-  if [ -z "$CROSS_PATH" ]; then
-    echo "no ${CROSS_COMPILE}cc in path" >&2
-    exit 1
+die() { echo "$@" >&2; exit 1; }
+announce() { echo -e "\033]2;$CROSS $*\007\n=== $*"; }
+
+# Create target-independent working directories (cmdline can change locations)
+TOP="$PWD/root"
+mkdir -p ${BUILD:=$TOP/build} ${AIRLOCK:=$TOP/airlock} ${LOG:=$TOP/log} ||exit 1
+
+# set CROSS_COMPILE from $CROSS using ccc. Handle "all" w/log, list, and err chk
+if [ ! -z "$CROSS" ]; then
+  [ ! -d "${CCC:=$PWD/ccc}" ] && die "No ccc symlink to compiler directory."
+  CROSS_COMPILE="$(echo "$CCC/$CROSS"-*cross/bin/"$CROSS"*-cc | sed 's/cc$//')"
+  if [ "${CROSS::3}" == all ]; then
+    for i in $(ls "$CCC" | sed -n 's/-.*//p' | sort -u | xargs); do
+      { rm -f "$LOG/$i-log".{failed,success}
+        "$0" "$@" CROSS=$i ; [ $? -eq 0 ] && mv "$LOG/$i".{txt,success}
+      } |& tee "$LOG/$i.txt"
+      [ ! -e "$LOG/$i.success" ] &&
+        { mv "$LOG/$i".{txt,failed};[ "$CROSS" != allnonstop ] && exit 1; }
+    done
+    exit
+  elif [ ! -e "${CROSS_COMPILE}cc" ]; then
+    ls "$CCC" | sed -n 's/-.*//p' | sort -u | xargs
+    exit
   fi
 fi
 
-# set up directories (can override most of these paths on cmdline)
-TOP="$PWD/root"
-[ -z "$BUILD" ] && BUILD="$TOP/build"
-[ -z "$AIRLOCK" ] && AIRLOCK="$TOP/airlock"
-[ -z "$OUTPUT" ] && OUTPUT="$TOP/${CROSS_SHORT:-host}"
-[ -z "$ROOT" ] && ROOT="$OUTPUT/${CROSS_BASE}fs" && rm -rf "$ROOT"
+${CROSS_COMPILE}cc --static -xc - -o /dev/null <<< "int main(void){return 0;}"||
+  die "${CROSS_COMPILE}cc can't create static binaries"
+
+# Parse and sanity check $CROSS_COMPILE (if any)
+if [ ! -z "$CROSS_COMPILE" ]; then
+  CROSS_PATH="$(dirname "$(which "${CROSS_COMPILE}cc")")"
+  [ -z "$CROSS_PATH" ] && die "no ${CROSS_COMPILE}cc in path"
+  : ${CROSS_BASE:=$(basename "$CROSS_COMPILE")} ${CROSS:=${CROSS_BASE/-*/}}
+fi
+echo "Building for ${CROSS:=host}"
+
+# Create target-specific work/output directories
+: ${OUTPUT:=$TOP/$CROSS} ${PKGDIR:=$PWD/scripts/root}
 MYBUILD="$BUILD/${CROSS_BASE:-host-}tmp"
 rm -rf "$MYBUILD" && mkdir -p "$MYBUILD" || exit 1
+[ -z "$ROOT" ] && ROOT="$OUTPUT/fs" && rm -rf "$ROOT" # only blank if NOT set
 
-# Stabilize cross compiling by providing known $PATH contents
+# When cross compiling build everything under a host toybox with known behavior
 if [ ! -z "$CROSS_COMPILE" ]; then
   if [ ! -e "$AIRLOCK/toybox" ]; then
-    echo === Create airlock dir
+    announce "airlock"
     PREFIX="$AIRLOCK" KCONFIG_CONFIG="$TOP"/.airlock CROSS_COMPILE= \
-      make clean defconfig toybox install_airlock &&
-    rm "$TOP"/.airlock || exit 1
+      make clean defconfig toybox install_airlock && rm "$TOP"/.airlock ||exit 1
   fi
   export PATH="$CROSS_PATH:$AIRLOCK"
 fi
 
-### Create files and directories
+# Create new root filesystem's directory layout
 mkdir -p "$ROOT"/{etc,tmp,proc,sys,dev,home,mnt,root,usr/{bin,sbin,lib},var} &&
 chmod a+rwxt "$ROOT"/tmp && ln -s usr/{bin,sbin,lib} "$ROOT" || exit 1
 
-# init script. Runs as pid 1 from initramfs to set up and hand off system.
+# Write init script. Runs as pid 1 from initramfs to set up and hand off system.
 cat > "$ROOT"/init << 'EOF' &&
 #!/bin/sh
 
 export HOME=/home PATH=/bin:/sbin
 
-mountpoint -q proc || mount -t proc proc proc
-mountpoint -q sys || mount -t sysfs sys sys
 if ! mountpoint -q dev; then
   mount -t devtmpfs dev dev || mdev -s
+  [ $$ -eq 1 ] && exec >/dev/console 2>&1
   for i in ,fd /0,stdin /1,stdout /2,stderr
   do ln -sf /proc/self/fd${i/,*/} dev/${i/*,/}; done
   mkdir -p dev/{shm,pts}
   mountpoint -q dev/pts || mount -t devpts dev/pts dev/pts
   chmod +t /dev/shm
 fi
+mountpoint -q proc || mount -t proc proc proc
+mountpoint -q sys || mount -t sysfs sys sys
 
-if [ $$ -eq 1 ]; then
-  # Setup networking for QEMU (needs /proc)
+if [ $$ -eq 1 ]; then # Setup networking for QEMU (needs /proc)
+  ifconfig lo 127.0.0.1
   ifconfig eth0 10.0.2.15
   route add default gw 10.0.2.2
-  [ "$(date +%s)" -lt 1000 ] && rdate 10.0.2.2 # Ask QEMU what time it is
-  [ "$(date +%s)" -lt 10000000 ] && ntpd -nq -p north-america.pool.ntp.org
+  [ "$(date +%s)" -lt 1000 ] && timeout 2 sntp -sq 10.0.2.2 # Ask host
+  [ "$(date +%s)" -lt 10000000 ] && sntp -sq time.google.com
+
+  # Run package scripts (if any)
+  [ -e /etc/rc ] && for i in $(echo /etc/rc/* | sort); do . $i; done
 
   [ -z "$CONSOLE" ] && CONSOLE="$(</sys/class/tty/console/active)"
   [ -z "$HANDOFF" ] && HANDOFF=/bin/sh && echo Type exit when done.
-  exec /sbin/oneit -c /dev/"${CONSOLE:-console}" $HANDOFF
-else
+  echo 3 > /proc/sys/kernel/printk
+  exec oneit -c /dev/"${CONSOLE:-console}" $HANDOFF
+else # for chroot
   /bin/sh
   umount /dev/pts /dev /sys /proc
 fi
 EOF
 chmod +x "$ROOT"/init &&
 
-# passwd and group with kernel special accounts (root and nobody) + guest user
+# Google's nameserver, passwd+group with special (root/nobody) accounts + guest
+echo "nameserver 8.8.8.8" > "$ROOT"/etc/resolv.conf &&
 cat > "$ROOT"/etc/passwd << 'EOF' &&
-root::0:0:root:/root:/bin/sh
+root:x:0:0:root:/root:/bin/sh
 guest:x:500:500:guest:/home/guest:/bin/sh
 nobody:x:65534:65534:nobody:/proc/self:/dev/null
 EOF
-echo -e 'root:x:0:\nguest:x:500:\nnobody:x:65534:' > "$ROOT"/etc/group &&
-# Google's public nameserver.
-echo "nameserver 8.8.8.8" > "$ROOT"/etc/resolv.conf || exit 1
+echo -e 'root:x:0:\nguest:x:500:\nnobody:x:65534:' > "$ROOT"/etc/group || exit 1
 
-# Build toybox
-make clean
-make $([ -z .config ] && echo defconfig || echo silentoldconfig)
+# Build static toybox with existing .config if there is one, else defconfig+sh
+announce toybox
+[ -e .config ] && [ -z "$PENDING" ] && CONF=silentoldconfig || unset CONF
+for i in $PENDING sh route; do XX="$XX"$'\n'CONFIG_${i^^?}=y; done
+make clean ${CONF:-defconfig KCONFIG_ALLCONFIG=<(echo "$XX")} &&
 LDFLAGS=--static PREFIX="$ROOT" make toybox install || exit 1
 
-write_miniconfig()
-{
-  # Generic options for all targets
-  KCGLOB=EARLY_PRINTK,BINFMT_ELF,BINFMT_SCRIPT,NO_HZ,HIGH_RES_TIMERS,BLK_DEV,BLK_DEV_INITRD,RD_GZIP,BLK_DEV_LOOP,EXT4_FS,EXT4_USE_FOR_EXT2,VFAT_FS,FAT_DEFAULT_UTF8,MISC_FILESYSTEMS,SQUASHFS,SQUASHFS_XATTR,SQUASHFS_ZLIB,DEVTMPFS,DEVTMPFS_MOUNT,TMPFS,TMPFS_POSIX_ACL,NET,PACKET,UNIX,INET,IPV6,NETDEVICES,NET_CORE,NETCONSOLE,ETHERNET
-
-  echo "# make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG=$TARGET.miniconf"
-  echo "# make ARCH=$KARCH -j \$(nproc)"
-  echo "# boot $VMLINUX"
-  echo
-  echo "# CONFIG_EMBEDDED is not set"
-
-  # Expand list of =y symbols
-  for i in $KCGLOB $KCONF; do
-    echo "# architecture ${X:-independent}"
-    sed -E '/^$/d;s/([^,]*)($|,)/CONFIG_\1=y\n/g' <<< "$i"
-    X=specific
-  done
-  echo "$KERNEL_CONFIG"
-}
+# Build any packages listed on command line
+for i in $PKG; do
+  announce "$i"; PATH="$PKGDIR:$PATH" source $i; [ $? -ne 0 ] && die $i
+done
 
 if [ -z "$LINUX" ] || [ ! -d "$LINUX/kernel" ]; then
   echo 'No $LINUX directory, kernel build skipped.'
 else
-
   # Which architecture are we building a kernel for?
+  LINUX="$(realpath "$LINUX")"
   [ -z "$TARGET" ] && TARGET="${CROSS_BASE/-*/}"
   [ -z "$TARGET" ] && TARGET="$(uname -m)"
 
@@ -135,7 +140,6 @@
   # Each target needs board config, serial console, RTC, ethernet, block device.
 
   if [ "$TARGET" == armv5l ]; then
-
     # This could use the same VIRT board as armv7, but let's demonstrate a
     # different one requiring a separate device tree binary.
     QEMU="arm -M versatilepb -net nic,model=rtl8139 -net user"
@@ -164,6 +168,9 @@
     fi
     KARCH=x86 KARGS=ttyS0 VMLINUX=arch/x86/boot/bzImage
     KCONF=$KCONF,UNWINDER_FRAME_POINTER,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,NET_VENDOR_INTEL,E1000,SERIAL_8250,SERIAL_8250_CONSOLE,RTC_CLASS
+  elif [ "$TARGET" == m68k ]; then
+    QEMU="m68k -M q800" KARCH=m68k KARGS=ttyS0 VMLINUX=vmlinux
+    KCONF=MMU,M68040,M68KFPU_EMU,MAC,SCSI_MAC_ESP,MACINTOSH_DRIVERS,ADB,ADB_MACII,NET_CORE,MACSONIC,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE
   elif [ "$TARGET" == mips ] || [ "$TARGET" == mipsel ]; then
     QEMU="mips -M malta" KARCH=mips KARGS=ttyS0 VMLINUX=vmlinux
     KCONF=MIPS_MALTA,CPU_MIPS32_R2,SERIAL_8250,SERIAL_8250_CONSOLE,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,NET_VENDOR_AMD,PCNET32,POWER_RESET,POWER_RESET_SYSCON
@@ -172,54 +179,73 @@
   elif [ "$TARGET" == powerpc ]; then
     KARCH=powerpc QEMU="ppc -M g3beige" KARGS=ttyS0 VMLINUX=vmlinux
     KCONF=ALTIVEC,PPC_PMAC,PPC_OF_BOOT_TRAMPOLINE,IDE,IDE_GD,IDE_GD_ATA,BLK_DEV_IDE_PMAC,BLK_DEV_IDE_PMAC_ATA100FIRST,MACINTOSH_DRIVERS,ADB,ADB_CUDA,NET_VENDOR_NATSEMI,NET_VENDOR_8390,NE2K_PCI,SERIO,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE,BOOTX_TEXT
-
   elif [ "$TARGET" == powerpc64le ]; then
     KARCH=powerpc QEMU="ppc64 -M pseries -vga none" KARGS=/dev/hvc0
     VMLINUX=vmlinux
     KCONF=PPC64,PPC_PSERIES,CPU_LITTLE_ENDIAN,PPC_OF_BOOT_TRAMPOLINE,BLK_DEV_SD,SCSI_LOWLEVEL,SCSI_IBMVSCSI,ATA,NET_VENDOR_IBM,IBMVETH,HVC_CONSOLE,PPC_TRANSACTIONAL_MEM,PPC_DISABLE_WERROR,SECTION_MISMATCH_WARN_ONLY
-
-  elif [ "$TARGET" = s390x ] ; then
+  elif [ "$TARGET" = s390x ]; then
     QEMU="s390x" KARCH=s390 VMLINUX=arch/s390/boot/bzImage
     KCONF=MARCH_Z900,PACK_STACK,NET_CORE,VIRTIO_NET,VIRTIO_BLK,SCLP_TTY,SCLP_CONSOLE,SCLP_VT220_TTY,SCLP_VT220_CONSOLE,S390_GUEST
-  elif [ "$TARGET" == sh4 ] ; then
+  elif [ "$TARGET" == sh2eb ]; then
+    KARCH=sh VMLINUX=vmlinux KERNEL_CONFIG='CONFIG_MEMORY_START=0x10000000
+CONFIG_CMDLINE="console=ttyUL0 earlycon"' BUILTIN=1
+    KCONF=CPU_SUBTYPE_J2,CPU_BIG_ENDIAN,SH_JCORE_SOC,SMP,BINFMT_ELF_FDPIC,JCORE_EMAC,SERIAL_UARTLITE,SERIAL_UARTLITE_CONSOLE,HZ_100,CMDLINE_OVERWRITE,SPI,SPI_JCORE,MMC,PWRSEQ_SIMPLE,MMC_BLOCK,MMC_SPI
+  elif [ "$TARGET" == sh4 ]; then
     QEMU="sh4 -M r2d -serial null -serial mon:stdio" KARCH=sh
     KARGS="ttySC1 noiotrap" VMLINUX=arch/sh/boot/zImage
     KERNEL_CONFIG="CONFIG_MEMORY_START=0x0c000000"
     KCONF=CPU_SUBTYPE_SH7751R,MMU,VSYSCALL,SH_FPU,SH_RTS7751R2D,RTS7751R2D_PLUS,SERIAL_SH_SCI,SERIAL_SH_SCI_CONSOLE,PCI,NET_VENDOR_REALTEK,8139CP,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,PATA_PLATFORM,BINFMT_ELF_FDPIC,BINFMT_FLAT
 #see also SPI SPI_SH_SCI MFD_SM501 RTC_CLASS RTC_DRV_R9701 RTC_DRV_SH RTC_HCTOSYS
-  else
-    echo "Unknown \$TARGET"
-    exit 1
+  else die "Unknown \$TARGET $TARGET"
   fi
 
   # Write the qemu launch script
-  echo "qemu-system-$QEMU" '"$@"' -nographic -no-reboot -m 256 \
-       "-kernel $(basename "$VMLINUX") -initrd ${CROSS_BASE}root.cpio.gz" \
-       "-append \"panic=1 HOST=$TARGET console=$KARGS \$KARGS\"" \
-       ${DTB:+-dtb "$(basename "$DTB")"} > "$OUTPUT/qemu-$TARGET.sh" &&
-  chmod +x "$OUTPUT/qemu-$TARGET.sh" &&
-
-  echo "Build linux for $KARCH"
-  pushd "$LINUX" && make distclean && popd &&
-  cp -sfR "$LINUX" "$MYBUILD/linux" && pushd "$MYBUILD/linux" || exit 1
-  write_miniconfig > "$OUTPUT/miniconfig-$TARGET" &&
-  make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG="$OUTPUT/miniconfig-$TARGET" &&
-  make ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" -j $(nproc) || exit 1
-
-  # If we have a device tree binary, save it for QEMU.
-  if [ ! -z "$DTB" ]; then
-    cp "$DTB" "$OUTPUT/$(basename "$DTB")" || exit 1
+  if [ ! -z "$QEMU" ]; then
+    [ -z "$BUILTIN" ] && INITRD="-initrd ${CROSS_BASE}root.cpio.gz"
+    echo qemu-system-"$QEMU" '"$@"' $QEMU_MORE -nographic -no-reboot -m 256 \
+         -kernel $(basename $VMLINUX) $INITRD \
+         "-append \"panic=1 HOST=$TARGET console=$KARGS \$KARGS\"" \
+         ${DTB:+-dtb "$(basename "$DTB")"} ";echo -e '\e[?7h'" \
+         > "$OUTPUT/qemu-$TARGET.sh" &&
+    chmod +x "$OUTPUT/qemu-$TARGET.sh" || exit 1
   fi
 
-  cp "$VMLINUX" "$OUTPUT/$(basename "$VMLINUX")" && cd .. && rm -rf linux &&
-    popd || exit 1
+  announce "linux-$KARCH"
+  pushd "$LINUX" && make distclean && popd &&
+  cp -sfR "$LINUX" "$MYBUILD/linux" && pushd "$MYBUILD/linux" &&
+
+  # Write miniconfig
+  { echo "# make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG=$TARGET.miniconf"
+    echo -e "# make ARCH=$KARCH -j \$(nproc)\n# boot $VMLINUX\n\n"
+    echo "# CONFIG_EMBEDDED is not set"
+
+    # Expand list of =y symbols, first generic then architecture-specific
+    for i in BINFMT_ELF,BINFMT_SCRIPT,NO_HZ,HIGH_RES_TIMERS,BLK_DEV,BLK_DEV_INITRD,RD_GZIP,BLK_DEV_LOOP,EXT4_FS,EXT4_USE_FOR_EXT2,VFAT_FS,FAT_DEFAULT_UTF8,MISC_FILESYSTEMS,SQUASHFS,SQUASHFS_XATTR,SQUASHFS_ZLIB,DEVTMPFS,DEVTMPFS_MOUNT,TMPFS,TMPFS_POSIX_ACL,NET,PACKET,UNIX,INET,IPV6,NETDEVICES,NET_CORE,NETCONSOLE,ETHERNET,COMPAT_32BIT_TIME,EARLY_PRINTK,IKCONFIG,IKCONFIG_PROC $KCONF ; do
+      echo "# architecture ${X:-independent}"
+      sed -E '/^$/d;s/([^,]*)($|,)/CONFIG_\1=y\n/g' <<< "$i"
+      X=specific
+    done
+    [ ! -z "$BUILTIN" ] && echo -e CONFIG_INITRAMFS_SOURCE="\"$OUTPUT/fs\""
+    echo "$KERNEL_CONFIG"
+  } > "$OUTPUT/miniconfig-$TARGET" &&
+  make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG="$OUTPUT/miniconfig-$TARGET" &&
+
+  # Second config pass to remove stupid kernel defaults
+  # See http://lkml.iu.edu/hypermail/linux/kernel/1912.3/03493.html
+  sed -e 's/# CONFIG_EXPERT .*/CONFIG_EXPERT=y/' -e "$(sed -E -e '/^$/d' \
+    -e 's@([^,]*)($|,)@/^CONFIG_\1=y/d;$a# CONFIG_\1 is not set/\n@g' \
+       <<< VT,SCHED_DEBUG,DEBUG_MISC,X86_DEBUG_FPU)" -i .config &&
+  yes "" | make ARCH=$KARCH oldconfig > /dev/null &&
+
+  # Build kernel. Copy config, device tree binary, and kernel binary to output
+  make ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" -j $(nproc) &&
+  cp .config "$OUTPUT/linux-fullconfig" || exit 1
+  [ ! -z "$DTB" ] && { cp "$DTB" "$OUTPUT" || exit 1 ;}
+  cp "$VMLINUX" "$OUTPUT" && cd .. && rm -rf linux && popd || exit 1
 fi
 
-rmdir "$MYBUILD" "$BUILD" 2>/dev/null
-
-# package root filesystem for initramfs.
-# we do it here so module install can add files (not implemented yet)
-echo === create "${CROSS_BASE}root.cpio.gz"
-
-(cd "$ROOT" && find . | cpio -o -H newc | gzip) > \
-  "$OUTPUT/${CROSS_BASE}root.cpio.gz"
+# clean up and package root filesystem for initramfs.
+[ -z "$BUILTIN" ] && announce "${CROSS_BASE}root.cpio.gz" &&
+  (cd "$ROOT" && find . | cpio -o -H newc ${CROSS_COMPILE:+--no-preserve-owner} | gzip) \
+    > "$OUTPUT/$CROSS_BASE"root.cpio.gz
+rmdir "$MYBUILD" "$BUILD" 2>/dev/null # remove if empty
diff --git a/scripts/root/dropbear b/scripts/root/dropbear
new file mode 100755
index 0000000..a2a49dd
--- /dev/null
+++ b/scripts/root/dropbear
@@ -0,0 +1,58 @@
+#!/bin/echo Try "scripts/mkroot.sh dropbear"
+
+# Example overlay file, adding dropbear (which requires zlib)
+
+echo === download source
+
+download a4d316c404ff54ca545ea71a27af7dbc29817088 \
+  http://downloads.sf.net/libpng/zlib-1.2.8.tar.gz
+
+download 820ec2b8c869edbcf5ad1138777fc0f54349505c \
+  https://matt.ucc.asn.au/dropbear/releases/dropbear-2019.78.tar.bz2
+
+echo === Native build static zlib
+
+setupfor zlib
+# They keep checking in broken generated files.
+rm -f Makefile zconf.h &&
+CC=${CROSS_COMPILE}cc LD=${CROSS_COMPILE}ld AS=${CROSS_COMPILE}as ./configure &&
+make -j $(nproc) || exit 1
+
+# do _not_ cleanup zlib, we need the files we just built for dropbear
+cd ..
+
+echo === $HOST Native build static dropbear
+
+setupfor dropbear
+# Repeat after me: "autoconf is useless"
+echo 'echo "$@"' > config.sub &&
+ZLIB="$(echo ../zlib*)" &&
+CFLAGS="-I $ZLIB -O2" LDFLAGS="-L $ZLIB" ./configure --enable-static \
+  --disable-wtmp --host=${CROSS_BASE%-} &&
+sed -i 's@/usr/bin/dbclient@ssh@' options.h &&
+sed -i 's@\(#define NON_INETD_MODE\) 1@\1 0@' default_options.h &&
+make -j $(nproc) PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp" MULTI=1 SCPPROGRESS=1 &&
+${CROSS_COMPILE}strip dropbearmulti &&
+mkdir -p "$ROOT"/{bin,etc/{rc,dropbear},var/log} &&
+touch "$ROOT"/var/log/lastlog &&
+cp dropbearmulti "$ROOT"/bin || exit 1
+for i in "$ROOT"/bin/{ssh,dropbear,scp,dropbearkey}
+do
+  ln -s dropbearmulti $i || exit 1
+done
+cleanup
+
+rm -rf zlib-*
+
+# user root password root, user guest no password
+echo -e 'root:$1$939UTPzb$/PfVYAsF2Hqi/AQ3UBjbK/:::::::\nguest::::::::' > "$ROOT"/etc/shadow &&
+chmod 600 "$ROOT"/etc/shadow &&
+
+echo 'netcat -p 22 -L dropbear -iRB &' > "$ROOT"/etc/rc/dropbear &&
+
+# file to run on host to ssh into guest
+echo 'ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" ${1:+$1@}127.0.0.1 -p 2222' > "$OUTPUT"/ssh2dropbear.sh &&
+chmod +x "$OUTPUT"/ssh2dropbear.sh
+
+# Forward 127.0.0.1:2222 into qemu instance
+QEMU_MORE="-nic user,hostfwd=tcp:127.0.0.1:2222-:22"
diff --git a/scripts/root/plumbing b/scripts/root/plumbing
new file mode 100755
index 0000000..ff30280
--- /dev/null
+++ b/scripts/root/plumbing
@@ -0,0 +1,45 @@
+#!/bin/echo run this from "make root"
+
+# Plumbing to download files
+
+[ -z "$ROOT" ] && echo "no" && exit 1
+mkdir -p "${DOWNLOAD:=$PWD/root_download}" || exit 1
+
+### Functions to download, extract, and clean up after source packages.
+
+# Usage: download HASH URL
+# Grabs source from URL confirming SHA1 hash (Basically "wget $2")
+# If extracted source is in $DOWNLOAD (no version) build will use that instead
+download() {
+  FILE="$(basename "$2")"
+  [ -d "$DOWNLOAD/${FILE/-*/}" ] && echo "$FILE" local && return 0
+  X=0; while true; do
+    [ "$(sha1sum < "$DOWNLOAD/$FILE" 2>/dev/null)" == "$1  -" ] &&
+      echo "$FILE" confirmed && break
+    rm -f $DOWNLOAD/${FILE/-[0-9]*/}-[0-9]* || exit 1
+    [ $X -eq 0 ] && X=1 || exit 1
+    wget "$2" -O "$DOWNLOAD/$FILE"
+  done
+}
+
+# Usage: setupfor PACKAGE
+# Extracts source tarball (or snapshot a repo) to create disposable build dir.
+# Basically "tar xvzCf $MYBUILD $DOWNLOAD/$1.tar.gz && cd $NEWDIR"
+setupfor() {
+  PACKAGE="$(basename "$1")"
+  announce "$PACKAGE" && cd "$MYBUILD" && rm -rf "$PACKAGE" || exit 1
+  if [ -d "$DOWNLOAD/$PACKAGE" ]; then
+    cp -la "$DOWNLOAD/$PACKAGE/." "$PACKAGE" && cd "$PACKAGE" || exit 1
+  else
+    tar xvaf "$DOWNLOAD/$PACKAGE"-*.t* && cd "$PACKAGE"-* || exit 1
+  fi
+}
+
+# Usage: cleanup
+# Delete setupfor's dir, exiting if build failed (basically "rm -rf $PACKAGE")
+cleanup() {
+  [ $? -ne 0 ] && exit 1
+  [ -z "$PACKAGE" ] && exit 1
+  [ ! -z "$NO_CLEANUP" ] && return
+  cd .. && rm -rf "$PACKAGE"* || exit 1
+}
diff --git a/scripts/runtest.sh b/scripts/runtest.sh
index 75872c9..158ed7f 100644
--- a/scripts/runtest.sh
+++ b/scripts/runtest.sh
@@ -8,9 +8,9 @@
 
 # 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.
-#              "nopass" to not show successful tests
+#    VERBOSE - "all" continue after failed test
+#              "quiet" like all but just print FAIL (no diff -u).
+#              "nopass" don't show successful tests
 #
 # The "testcmd" function takes five arguments:
 #	$1) Description to display when running command
@@ -61,7 +61,7 @@
   # Not set?
   if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
   then
-    SKIP=""
+    unset SKIP
     return
   fi
   SKIP=1
@@ -69,7 +69,7 @@
 
 skipnot()
 {
-  if [ -z "$VERBOSE" ]
+  if [ "$VERBOSE" == quiet ]
   then
     eval "$@" 2>/dev/null
   else
@@ -81,7 +81,13 @@
 toyonly()
 {
   IS_TOYBOX="$("$C" --version 2>/dev/null)"
-  [ "${IS_TOYBOX/toybox/}" == "$IS_TOYBOX" ] && SKIPNEXT=1
+  # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf
+  # happy, so we have at least two things to check for.
+  case "$IS_TOYBOX" in
+    toybox*) ;;
+    This\ is\ not\ GNU*) ;;
+    *) SKIPNEXT=1 ;;
+  esac
 
   "$@"
 }
@@ -114,7 +120,7 @@
 
   if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ]
   then
-    [ ! -z "$VERBOSE" ] && printf "%s\n" "$SHOWSKIP: $NAME"
+    [ "$VERBOSE" != quiet ] && printf "%s\n" "$SHOWSKIP: $NAME"
     unset SKIPNEXT
     return 0
   fi
@@ -132,12 +138,12 @@
   then
     FAILCOUNT=$(($FAILCOUNT+1))
     printf "%s\n" "$SHOWFAIL: $NAME"
-    if [ -n "$VERBOSE" ]
+    if [ "$VERBOSE" != quiet ]
     then
       [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input"
       printf "%s\n" "echo -ne '$5' |$EVAL $2"
       printf "%s\n" "$DIFF"
-      [ "$VERBOSE" == fail ] && exit 1
+      [ "$VERBOSE" != all ] && exit 1
     fi
   else
     [ "$VERBOSE" != "nopass" ] && printf "%s\n" "$SHOWPASS: $NAME"
@@ -168,7 +174,7 @@
     echo "Expected '$CASE'"
     echo "Got '$A'"
   fi
-  [ "$VERBOSE" == fail ] && exit 1
+  [ "$VERBOSE" != all ] && [ "$VERBOSE" != quiet ] && exit 1
 }
 
 # txpect NAME COMMAND [I/O/E/Xstring]...
@@ -177,9 +183,12 @@
 # X means close stdin/stdout/stderr and match return code (blank means nonzero)
 txpect()
 {
+  local NAME CASE VERBOSITY LEN A B
+
   # Run command with redirection through fifos
-  NAME="$1"
+  NAME="$CMDNAME $1"
   CASE=
+  VERBOSITY=
 
   if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$
   then
@@ -195,29 +204,32 @@
   # Loop through challenge/response pairs, with 2 second timeout
   while [ $# -gt 0 ]
   do
-    [ "$VERBOSE" == xpect ] && echo "$1" >&2
+    VERBOSITY="$VERBOSITY"$'\n'"$1"
     LEN=$((${#1}-1))
     CASE="$1"
     A=
+    B=
     case ${1::1} in
 
       # send input to child
-      I) echo -en "${1:1}" >&$IN || { do_fail;break;} ;;
+      I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;;
 
+      R) LEN=0; B=1; ;&
       # check output from child
       [OE])
         [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN"
         O=$OUT
-        [ ${1::1} == 'E' ] && O=$ERR
+        [ "${1:$B:1}" == 'E' ] && O=$ERR
         A=
         read -t2 $LARG A <&$O
-        [ "$VERBOSE" == xpect ] && echo "$A" >&2
+        VERBOSITY="$VERBOSITY"$'\n'"$A"
         if [ $LEN -eq 0 ]
         then
           [ -z "$A" ] && { do_fail;break;}
         else
-          if [ "$A" != "${1:1}" ]
-          then
+          if [ ${1::1} == 'R' ] && [[ "$A" =~ "${1:2}" ]]; then true
+          elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true
+          else
             # Append the rest of the output if there is any.
             read -t.1 B <&$O
             A="$A$B"
@@ -246,7 +258,12 @@
   # In case we already closed it
   exec {IN}<&- {OUT}<&- {ERR}<&-
 
-  [ $# -eq 0 ] && do_pass
+  if [ $# -eq 0 ]
+  then
+    do_pass
+  else
+    [ "$VERBOSE" != quiet ] && echo "$VERBOSITY" >&2
+  fi
 }
 
 # Recursively grab an executable and all the libraries needed to run it.
diff --git a/scripts/test.sh b/scripts/test.sh
index d9955a5..cdfe3bd 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -4,7 +4,7 @@
 source scripts/portability.sh
 
 TOPDIR="$PWD"
-FILES="$PWD"/tests/files
+export FILES="$PWD"/tests/files
 
 trap 'kill $(jobs -p) 2>/dev/null; exit 1' INT
 
@@ -40,8 +40,9 @@
     [ ! -e "$C" ] && echo "$CMDNAME disabled" && return
   else
     C="$(which $CMDNAME 2>/dev/null)"
-    [ -z "$C" ] && "C=$CMDNAME"
+    [ -z "$C" ] && printf '%s\n' "$SHOWSKIP: no $CMDNAME" && return
   fi
+  C="$(dirname $(realpath "$C"))/$CMDNAME"
 
   . "$1"
 }
@@ -59,3 +60,5 @@
     do_test "$i"
   done
 fi
+
+[ $FAILCOUNT -eq 0 ]
diff --git a/tests/base32.test b/tests/base32.test
new file mode 100755
index 0000000..00bcffa
--- /dev/null
+++ b/tests/base32.test
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+# testing "name" "flags" "result" "infile" "stdin"
+
+testcmd "simple" "" "ONUW24DMMUFA====\n" "" "simple\n"
+testcmd "file" "input" "ONUW24DMMUFA====\n" "simple\n" ""
+testcmd "simple -d" "-d" "simple\n" "" "ONUW24DMMUFA====\n"
+testcmd "file -d" "-d input" "simple\n" "ONUW24DMMUFA====" ""
+testcmd "default wrap" "" \
+  "K5SSO5TFEBZGK4DMMFRWKZBAORUGKIDENFWGS5DINF2W2IDUNBSXSIDON5ZG2YLMNR4SA5LTMUQH\nO2LUNAQEM33MM5SXEJ3TEBBXE6LTORQWY4ZO\n" \
+  "" "We've replaced the dilithium they normally use with Folger's Crystals."
+testcmd "multiline -d " "-d" \
+ "We've replaced the dilithium they normally use with Folger's Crystals." "" \
+  "K5SSO5TFEBZGK4DMMFRWKZBAORUGKIDENFWGS5DINF2W2IDUNBSXSIDON5ZG2YLMNR4SA5LTMUQH\nO2LUNAQEM33MM5SXEJ3TEBBXE6LTORQWY4ZO\n"
+
+testcmd "-w" "-w 10" \
+  "JVQXEY3INF\nXGOIDUN4QH\nI2DFEBRGKY\nLUEBXWMIDB\nEBSGSZTGMV\nZGK3TUEBVW\nK5DUNRSSA3\n3GEBTGS43I\nFY======\n" \
+  "" "Marching to the beat of a different kettle of fish."
+
+testcmd "-w0" "-w0 input" \
+  "KZUWW2LOM5ZT6ICUNBSXEZJAMFUW4J3UEBXG6IDWNFVWS3THOMQGQZLSMUXCASTVON2CA5LTEBUG63TFON2CAZTBOJWWK4TTFYQFI2DFEB2G653OEB3WC4ZAMJ2XE3TJNZTSYIDUNBSSA5TJNRWGCZ3FOJZSA53FOJSSAZDFMFSC4ICUNBSXSIDENFSG4J3UEBXGKZLEEB2GQ33TMUQHG2DFMVYCAYLOPF3WC6JOEBKGQYLUE5ZSA33VOIQHG5DPOJ4SAYLOMQQHOZJHOJSSA43UNFRWW2LOM4QHI3ZANF2C4CQ=" \
+ "Vikings? There ain't no vikings here. Just us honest farmers. The town was burning, the villagers were dead. They didn't need those sheep anyway. That's our story and we're sticking to it.\n" ""
diff --git a/tests/cat.test b/tests/cat.test
index 9432d4e..ab05840 100755
--- a/tests/cat.test
+++ b/tests/cat.test
@@ -25,7 +25,7 @@
 
 skipnot [ -e /dev/full ]
 testing "> /dev/full" \
-        "cat - > /dev/full 2>stderr && echo ok; cat stderr; rm stderr" \
-        "cat: xwrite: No space left on device\n" "" "zero\n"
+        "cat - > /dev/full 2>/dev/null || echo failed" \
+        "failed\n" "" "zero\n"
 
 rm file1 file2
diff --git a/tests/chattr.test b/tests/chattr.test
index cefc84b..8f1c4b2 100755
--- a/tests/chattr.test
+++ b/tests/chattr.test
@@ -46,9 +46,11 @@
 
 # For the rest, just toggle the bits back and forth (where supported).
 # Note that some file system/kernel combinations do return success but
-# silently ignore your request: +T on 4.19 f2fs, or +F on 5.2i ext4,
+# silently ignore your request: +T on 4.19 f2fs, or +F on 5.2 ext4,
 # for example, so we're deliberately a bit selective here.
-for attr in "A" "c" "d" "e" "j" "P" "S" "s" "t" "u"; do
+# f2fs in 5.6+ kernels supports compression, but you can only enable
+# compression on a file while it's still empty, so we skip +c too.
+for attr in "A" "d" "e" "j" "P" "S" "s" "t" "u"; do
   echo "$_t" > testFile && chattr +$attr testFile 2>/dev/null || SKIPNEXT=1
   # Check that $attr is in the lsattr output, then that - turns it back off.
   testing "toggle $attr" "lsattr testFile | awk '{print \$1}' > attrs;
diff --git a/tests/chmod.test b/tests/chmod.test
index b2b5a48..6c4de0c 100755
--- a/tests/chmod.test
+++ b/tests/chmod.test
@@ -24,28 +24,20 @@
 touch file
 
 # We don't need to test all 512 permissions
-for u in 0 1 2 3 4 5 6 7
-do
-  for g in 0 3 6
-  do
-    for o in 0 7
-    do
-      if [ "$type" == file ]
-      then
-        type=dir
-        rm -rf "./$type" && mkdir $type
-        DASH=d
-      else
-        type=file
-        rm -f "./$type" && touch $type
-        DASH=-
-      fi
-      DASHES=$(num2perm $u$g$o)
-      testing "$u$g$o $type" "chmod $u$g$o $type && 
-        ls -ld $type | cut -d' ' -f 1 | cut -d. -f 1" "$DASH$DASHES\n" "" ""
-    done
-  done
-done
+for U in $(seq 0 7); do for G in 0 3 6; do for O in 0 7; do for T in dir file; do
+  chmod 777 $T 2>/dev/null
+  rm -rf $T
+  if [ "$T" == file ]; then
+    touch file
+    C=-
+  else
+    mkdir dir
+    C=d
+  fi
+  testing "$U$G$O $T" "chmod $U$G$O $T && ls -ld $T | cut -d' ' -f 1" \
+    "${C}$(num2perm $U$G$O)\n" "" ""
+done; done; done; done
+unset U G O T C
 
 rm -rf dir file && mkdir dir && touch file 640
 testing "750 dir 640 file" "chmod 750 dir 640 file &&
@@ -54,6 +46,7 @@
 
 chtest()
 {
+  chmod -fR 700 dir file 2>/dev/null
   rm -rf dir file && mkdir dir && touch file
   testing "$1 dir file" \
     "chmod $1 dir file && ls -ld dir file | cut -d' ' -f 1 | cut -d. -f 1" \
@@ -107,10 +100,32 @@
 chtest -r "d-wx--x--x\n--w-------\n"
 chtest -w "dr-xr-xr-x\n-r--r--r--\n"
 chtest -x "drw-r--r--\n-rw-r--r--\n"
+chtest a-w,a+x "dr-xr-xr-x\n-r-xr-xr-x\n"
+
+# macOS doesn't allow +s in /tmp
+touch s-supported
+chmod +s s-supported 2>/dev/null || SKIP=1
+rm s-supported
 chtest g+s "drwxr-sr-x\n-rw-r-Sr--\n"
 chtest u+s "drwsr-xr-x\n-rwSr--r--\n"
+chtest +s "drwsr-sr-x\n-rwSr-Sr--\n"
 chtest o+s "drwxr-xr-x\n-rw-r--r--\n"
+unset SKIP
+
 chtest +t  "drwxr-xr-t\n-rw-r--r-T\n"
+chtest a=r+w+x "drwxrwxrwx\n-rwxrwxrwx\n"
+
+# (chtest starts off with a directory that's +x...)
+testing "+X" \
+  "mkdir -m 000 Xd && touch Xf && chmod +X Xd Xf && ls -ld Xd Xf | cut -d' ' -f 1" \
+  "d--x--x--x\n-rw-r--r--\n" "" ""
+
+mkdir foo
+ln -s bar foo/baz
+# If you explicitly ask us, we'll try (and fail) to chmod a symlink.
+testing "-R symlink arg" 'chmod -R 750 foo/baz 2>/dev/null; echo $?' "1\n" "" ""
+# If you only imply that you might want us to do that, we'll skip it.
+testing "-R symlink recurse" 'chmod -R 750 foo; echo $?' "0\n" "" ""
 
 # Removing test files for cleanup purpose
 rm -rf dir file
diff --git a/tests/cksum.test b/tests/cksum.test
index d69f0cb..10478bc 100755
--- a/tests/cksum.test
+++ b/tests/cksum.test
@@ -17,13 +17,13 @@
 # Check the length suppression, both calculate the CRC on 'abc' but the second
 # option has length suppression on and has the length concatenated to 'abc'.
 testing "on abc including length" "cksum" "1219131554 3\n" "" 'abc'
-testing "on abc excluding length" "cksum -N" "1219131554\n" "" 'abc\x3'
+toyonly testing "on abc excluding length" "cksum -N" "1219131554\n" "" 'abc\x3'
 
 # cksum on no contents gives 0xffffffff (=4294967295)
 testing "on no data post-inversion" "echo -n "" | cksum" "4294967295 0\n" "" ""
 # If we do preinversion we will then get 0.
-testing "on no data pre+post-inversion" "echo -n "" | cksum -P" "0 0\n" "" ""
+toyonly testing "on no data pre+post-inversion" "echo -n "" | cksum -P" "0 0\n" "" ""
 # If we skip the post-inversion we also get 0
-testing "on no data no inversion" "echo -n "" | cksum -I" "0 0\n" "" ""
+toyonly testing "on no data no inversion" "echo -n "" | cksum -I" "0 0\n" "" ""
 # Two wrongs make a right.
-testing "on no data pre-inversion" "echo -n "" | cksum -PI" "4294967295 0\n" "" ""
+toyonly testing "on no data pre-inversion" "echo -n "" | cksum -PI" "4294967295 0\n" "" ""
diff --git a/tests/cp.test b/tests/cp.test
index dfb80ea..b0d5d3a 100755
--- a/tests/cp.test
+++ b/tests/cp.test
@@ -83,8 +83,7 @@
 rm -rf dir
 
 mkdir -p one/two/three/four
-touch one/two/three/five
-touch one/{six,seven,eight}
+touch one/two/three/five one/{six,seven,eight}
 testing "-r /abspath dest" \
 	"cp -r \"$(readlink -f one)\" dir && diff -r one dir && echo yes" \
 	"yes\n" "" ""
@@ -120,6 +119,28 @@
 testing "-T dir" "cp -T b dir 2>/dev/null || echo expected" "expected\n" "" ""
 rm b file
 
+mkdir -p b/c/d/ a/
+echo a > b/c/d/file
+testing "--parents b/c/d/file a/" "cp --parents b/c/d/file a/ && cat a/b/c/d/file" "a\n" "" ""
+rm -rf a/ b/
+
+echo a > file
+testing "-P file" "cp -P file fdst && stat -c %F fdst" "regular file\n" "" ""
+ln -s file lnk
+testing "-P symlink" "cp -P lnk ldst && stat -c %F ldst" "symbolic link\n" "" ""
+testing "follow symlink" "cp lnk ldst2 && stat -c %F ldst2" "regular file\n" "" ""
+rm file fdst lnk ldst ldst2
+
+mkdir sub
+testing "-t one arg" 'cp -t sub/ input && cat sub/input' 'yes\n' 'yes\n' ''
+toyonly testing "-Dt" 'cp -Dt sub2 input && cat sub2/input' 'and\n' 'and\n' ''
+rm -rf sub sub2
+
+testing '-u1' 'echo one>one; sleep .1; echo two>two; cp -u one two; cat two' \
+  'two\n' '' ''
+testing '-u2' 'echo two>two; sleep .1; echo one>one; cp -u one two; cat two' \
+  'one\n' '' ''
+
 # cp -r ../source destdir
 # cp -r one/two/three missing
 # cp -r one/two/three two
diff --git a/tests/cpio.test b/tests/cpio.test
index 5158efa..183dadd 100755
--- a/tests/cpio.test
+++ b/tests/cpio.test
@@ -29,11 +29,56 @@
 testing "archive magic" "cpio -o -H newc|dd ibs=2 count=3 2>/dev/null" "070701" "" "a\n"
 # check name length (8 bytes before the empty "crc")
 testing "name length" "cpio -o -H newc|dd ibs=2 skip=47 count=4 2>/dev/null" "00000002" "" "a\n"
+testing "-t" "cpio -o -H newc|cpio -it" "a\nbb\n" "" "a\nbb"
+testing "-t --quiet" "cpio -o -H newc|cpio -it --quiet" "a\nbb\n" "" "a\nbb"
+mkdir out
+testing "-p" "cpio -p out && find out | sort" "out\nout/a\nout/bb\n" "" "a\nbb"
+rm -rf out
+testing "-pd" "cpio -pd out && find out | sort" "out\nout/a\nout/bb\n" "" "a\nbb"
 rm a bb ccc dddd
 
 # archive dangling symlinks and empty files even if we cannot open them
 touch a; chmod a-rwx a; ln -s a/cant b
-testing "archives unreadable empty files" "cpio -o -H newc|cpio -it" "a\nb\n" "" "a\nb\n"
+toyonly testing "archives unreadable empty files" "cpio -o -H newc|cpio -it" "b\na\n" "" "b\na\n"
 chmod u+rw a; rm -f a b
 
+mkdir a
+echo "old" >a/b
+echo "a/b" | cpio -o -H newc >a.cpio
+rm -rf a
+testing "-i doesn't create leading directories" "cpio -i <a.cpio 2>/dev/null; [ -e a ] || echo yes" "yes\n" "" ""
+rm -rf a
+testing "-id creates leading directories" "cpio -id <a.cpio && cat a/b" "old\n" "" ""
+rm -rf a a.cpio
 
+mkdir a
+echo "old" >a/b
+find a | cpio -o -H newc >a.cpio
+testing "-i keeps existing files" "echo new >a/b && cpio -i <a.cpio 2>/dev/null; cat a/b" "new\n" "" ""
+testing "-id keeps existing files" "echo new >a/b && cpio -id <a.cpio 2>/dev/null; cat a/b" "new\n" "" ""
+testing "-iu replaces existing files; no error" "echo new >a/b && cpio -iu <a.cpio && cat a/b" "old\n" "" ""
+testing "-idu replaces existing files; no error" "echo new >a/b && cpio -idu <a.cpio && cat a/b" "old\n" "" ""
+testing "skip NUL" "for i in a b; do dd if=/dev/zero bs=512 count=1 2>/dev/null; cat a.cpio; done | cpio -t -H newc" \
+  "a\na/b\na\na/b\n" "" ""
+rm -rf a a.cpio
+
+testing "error on empty file" "cpio -i 2>/dev/null || echo err" "err\n" "" ""
+
+mkdir a
+touch a/file
+ln -s a/symlink a/symlink
+mkdir a/dir
+find a | cpio -o -H newc >a.cpio
+if [ "$(id -u)" -eq 0 ]; then
+  # We chown between user "root" and the last user in /etc/passwd,
+  # and group "root" and the last group in /etc/group.
+  USR="$(sed -n '$s/:.*//p' /etc/passwd)"
+  GRP="$(sed -n '$s/:.*//p' /etc/group)"
+  # Or if that fails, we assume we're on Android...
+  : "${USR:=shell}"
+  : "${GRP:=shell}"
+  chown -h "${USR}:${GRP}" a/file a/symlink a/dir
+fi
+skipnot [ $(id -u) -eq 0 ]
+testing "-t preserve ownership" "cpio -t <a.cpio >/dev/null && stat -c '%U:%G' a/file a/symlink a/dir" "${USR}:${GRP}\n${USR}:${GRP}\n${USR}:${GRP}\n" "" ""
+rm -rf a a.cpio
diff --git a/tests/date.test b/tests/date.test
index 2b86534..6aaf937 100644
--- a/tests/date.test
+++ b/tests/date.test
@@ -9,6 +9,9 @@
 tz=Europe/Berlin
 this_year=$(TZ=$tz date +%Y)
 
+# Use a consistent locale too.
+export LANG=C
+
 # Unix date parsing.
 testing "-d @0" "TZ=$tz date -d @0" "Thu Jan  1 01:00:00 CET 1970\n" "" ""
 testing "-d @0x123 invalid" "TZ=$tz date -d @0x123 2>/dev/null || echo expected error" "expected error\n" "" ""
@@ -50,7 +53,42 @@
 testing "just %" "touch -d 2012-01-23T12:34:56.123456789 f && date -r f +%" "%\n" "" ""
 rm -f f
 
+# Test --iso...
+testing "-I" "touch -d 2012-01-23T12:34:56.123456789Z f && date -r f -u -I" \
+  "2012-01-23\n" "" ""
+testing "-Id" "touch -d 2012-01-23T12:34:56.123456789Z f && date -r f -u -Id" \
+  "2012-01-23\n" "" ""
+testing "-Ih" "touch -d 2012-01-23T12:34:56.123456789Z f && date -r f -u -Ih" \
+  "2012-01-23T12+00:00\n" "" ""
+testing "-Im" "touch -d 2012-01-23T12:34:56.123456789Z f && date -r f -u -Im" \
+  "2012-01-23T12:34+00:00\n" "" ""
+testing "-Is" "touch -d 2012-01-23T12:34:56.123456789Z f && date -r f -u -Is" \
+  "2012-01-23T12:34:56+00:00\n" "" ""
+testing "-In" "touch -d 2012-01-23T12:34:56.123456789Z f && date -r f -u -In" \
+  "2012-01-23T12:34:56,123456789+00:00\n" "" ""
+rm -f f
+
 # Test embedded TZ to take a date in one time zone and display it in another.
 testing "TZ=" "TZ='America/Los_Angeles' date -d 'TZ=\"Europe/Berlin\" 2018-01-04 08:00'" "Wed Jan  3 23:00:00 PST 2018\n" "" ""
 testing "TZ=" "TZ='America/Los_Angeles' date -d 'TZ=\"Europe/Berlin\" 2018-10-04 08:00'" "Wed Oct  3 23:00:00 PDT 2018\n" "" ""
 testing "TZ= @" "TZ='America/Los_Angeles' date -d 'TZ=\"GMT\" @1533427200'" "Sat Aug  4 17:00:00 PDT 2018\n" "" ""
+
+# Test all supported UTC offset variants.
+testing "tz Z" \
+  "date -u -d 2020-08-01T12:34:56Z" "Sat Aug  1 12:34:56 UTC 2020\n" "" ""
+testing "tz -0800" \
+  "date -u -d 2020-08-01T12:34:56-0800" "Sat Aug  1 20:34:56 UTC 2020\n" "" ""
+testing "tz +0800" \
+  "date -u -d 2020-08-01T12:34:56+0800" "Sat Aug  1 04:34:56 UTC 2020\n" "" ""
+testing "tz +08:00" \
+  "date -u -d 2020-08-01T12:34:56+08:00" "Sat Aug  1 04:34:56 UTC 2020\n" "" ""
+testing "tz +8" \
+  "date -u -d 2020-08-01T12:34:56+8" "Sat Aug  1 04:34:56 UTC 2020\n" "" ""
+testing "tz +08" \
+  "date -u -d 2020-08-01T12:34:56+08" "Sat Aug  1 04:34:56 UTC 2020\n" "" ""
+testing "tz +008" \
+  "date -u -d 2020-08-01T12:34:56+008" "Sat Aug  1 12:26:56 UTC 2020\n" "" ""
+
+# Can we parse date's own output format?
+testing "round trip" 'TZ=$tz date -d "$(TZ=$tz date -d @1598476818)"' \
+  "Wed Aug 26 23:20:18 CEST 2020\n" "" ""
diff --git a/tests/echo.test b/tests/echo.test
index 80774e6..bd2c3ff 100755
--- a/tests/echo.test
+++ b/tests/echo.test
@@ -9,7 +9,7 @@
 
 #testing "name" "command" "result" "infile" "stdin"
 
-testcmd "echo" "&& echo yes" "\nyes\n" "" ""
+testcmd "echo" "&& $C yes" "\nyes\n" "" ""
 testcmd "1 2 3" "one  two	three" "one two three\n" "" ""
 testcmd "with spaces" "'one  two	three'" \
 	"one  two	three\n" "" ""
@@ -45,3 +45,9 @@
 # http://austingroupbugs.net/view.php?id=1222 added -E
 testcmd "-En" "-En 'one\ntwo'" 'one\\ntwo' "" ""
 testcmd "-eE" "-eE '\e'" '\\e\n' "" ""
+
+# This is how bash's built-in echo behaves, but now how /bin/echo behaves.
+toyonly testcmd "" "-e 'a\x123\ufb3bbc' | od -An -tx1" \
+  " 61 12 33 ef ac bb 62 63 0a\n" "" ""
+
+testcmd "trailing nul" "-ne 'a\0b\0' | od -An -tx1" " 61 00 62 00\n" "" ""
diff --git a/tests/factor.test b/tests/factor.test
index 2ec557a..b556c3a 100755
--- a/tests/factor.test
+++ b/tests/factor.test
@@ -4,9 +4,9 @@
 
 #testing "name" "command" "result" "infile" "stdin"
 
-testing "-32" "factor -32" "-32: -1 2 2 2 2 2\n" "" ""
-testing "0" "factor 0" "0: 0\n" "" ""
-testing "1" "factor 1" "1: 1\n" "" ""
+toyonly testing "-32" "factor -32" "-32: -1 2 2 2 2 2\n" "" ""
+toyonly testing "0" "factor 0" "0: 0\n" "" ""
+toyonly testing "1" "factor 1" "1: 1\n" "" ""
 testing "2" "factor 2" "2: 2\n" "" ""
 testing "3" "factor 3" "3: 3\n" "" ""
 testing "4" "factor 4" "4: 2 2\n" "" ""
diff --git a/tests/file.test b/tests/file.test
index 66c5b7e..2e97099 100755
--- a/tests/file.test
+++ b/tests/file.test
@@ -7,7 +7,7 @@
 touch empty
 echo "#!/bin/bash" > bash.script
 echo "#!  /bin/bash" > bash.script2
-echo "#!  /usr/bin/env python" > env.python.script
+echo "#!  /usr/bin/env python" > test.py
 echo "Hello, world!" > ascii
 echo "6465780a3033350038ca8f6ce910f94e" | xxd -r -p > android.dex
 ln -s $FILES/java.class symlink
@@ -17,25 +17,35 @@
 
 testing "directory" "file ." ".: directory\n" "" ""
 testing "empty" "file empty" "empty: empty\n" "" ""
-testing "bash.script" "file bash.script" "bash.script: /bin/bash script\n" "" ""
-testing "bash.script with spaces" "file bash.script2" "bash.script2: /bin/bash script\n" "" ""
-testing "env python script" "file env.python.script" "env.python.script: python script\n" "" ""
+testing "bash.script" "file bash.script | grep -o ' script'" " script\n" "" ""
+testing "bash.script with spaces" "file bash.script2 | grep -o ' script'" " script\n" "" ""
+testing "env python script" "file test.py | egrep -o '(python|script)' | sort" \
+  "python\nscript\n" "" ""
 testing "ascii" "file ascii" "ascii: ASCII text\n" "" ""
-testing "utf-8" "file $FILES/utf8/japan.txt | sed 's|$FILES/||'" \
-    "utf8/japan.txt: UTF-8 text\n" "" ""
-testing "java class" "file $FILES/java.class | sed 's|$FILES/||'" \
-    "java.class: Java class file, version 53.0 (Java 1.9)\n" "" ""
+testing "utf-8" \
+  "file $FILES/utf8/japan.txt | egrep -o '(UTF-8|text)' | LANG=c sort" \
+  "UTF-8\ntext\n" "" ""
+testing "java class" \
+  "file $FILES/java.class | egrep -o '(Java class|version 53.0)'"\
+  "Java class\nversion 53.0\n" "" ""
 testing "tar file" "file $FILES/tar/tar.tar | sed 's|$FILES/||'" \
     "tar/tar.tar: POSIX tar archive (GNU)\n" "" ""
-testing "gzip data" "file $FILES/tar/tar.tgz | sed 's|$FILES/||'" \
-    "tar/tar.tgz: gzip compressed data\n" "" ""
+testing "gzip data" "file $FILES/tar/tar.tgz | grep -o 'gzip compressed data'" \
+    "gzip compressed data\n" "" ""
 testing "bzip2 data" "file $FILES/tar/tar.tbz2 | sed 's|$FILES/||'" \
     "tar/tar.tbz2: bzip2 compressed data, block size = 900k\n" "" ""
-testing "zip file" "file $FILES/zip/example.zip | sed 's|$FILES/||'" \
-    "zip/example.zip: Zip archive data, requires at least v1.0 to extract\n" "" ""
+testing "7z file" "file $FILES/tar/tar.7z | sed 's|$FILES/||'" \
+    "tar/tar.7z: 7-zip archive data, version 0.4\n" "" ""
+testing "zip file" \
+  "file $FILES/zip/example.zip | egrep -o '(Zip archive data|at least v1.0 to extract)'" \
+    "Zip archive data\nat least v1.0 to extract\n" "" ""
+echo R0lGODlhIAAgAMZHAAAAABYWFiYmJioqKi4uLjIy | base64 -d > gif
+testing "gif file" "file gif" "gif: GIF image data, version 89a, 32 x 32\n" "" ""
+rm -f gif
 
 # TODO: check in a genuine minimal .dex
-testing "Android .dex" "file android.dex" "android.dex: Android dex file, version 035\n" "" ""
+testing "Android .dex" "file android.dex | egrep -o '(dex file|version 035)'" \
+  "dex file\nversion 035\n" "" ""
 
 # These actually test a lot of the ELF code: 32-/64-bit, arm/arm64, PT_INTERP,
 # the two kinds of NDK ELF note, BuildID, and stripped/not stripped.
@@ -49,14 +59,20 @@
 testing "broken symlink" "file dangler" "dangler: broken symbolic link to $BROKEN\n" "" ""
 testing "symlink" "file symlink" "symlink: symbolic link to $LINK\n" "" ""
 testing "symlink -h" "file -h symlink" "symlink: symbolic link to $LINK\n" "" ""
-testing "symlink -L" "file -L symlink" "symlink: Java class file, version 53.0 (Java 1.9)\n" "" ""
+testing "symlink -L" \
+  "file -L symlink | egrep -o '(symlink:|Java class|version 53.0)'" \
+  "symlink:\nJava class\nversion 53.0\n" "" ""
 
-testing "- pipe" "cat $FILES/java.class | file -" "-: Java class file, version 53.0 (Java 1.9)\n" "" ""
-testing "- redirect" "file - <$FILES/java.class" "-: Java class file, version 53.0 (Java 1.9)\n" "" ""
+# Some host versions say "-" some "/dev/stdin"...
+testing "- pipe" "cat $FILES/java.class | file - | egrep -o '(Java class|version 53.0)'" \
+  "Java class\nversion 53.0\n" "" ""
+testing "- redirect" \
+  "file - <$FILES/java.class | egrep -o '(Java class|version 53.0)'" \
+  "Java class\nversion 53.0\n" "" ""
 
 zero_dev="1/5"
 [ "$(uname)" == "Darwin" ] && zero_dev="3/3"
 testing "/dev/zero" "file /dev/zero" "/dev/zero: character special ($zero_dev)\n" "" ""
-testing "- </dev/zero" "file - </dev/zero" "-: data\n" "" ""
+testing "- </dev/zero" "file - </dev/zero | grep -ow data" "data\n" "" ""
 
-rm empty bash.script bash.script2 env.python.script ascii android.dex
+rm empty bash.script bash.script2 test.py ascii android.dex
diff --git a/tests/files/tar/long_path.tar b/tests/files/tar/long_path.tar
new file mode 100644
index 0000000..9bbc541
--- /dev/null
+++ b/tests/files/tar/long_path.tar
Binary files differ
diff --git a/tests/files/tar/tar.7z b/tests/files/tar/tar.7z
new file mode 100644
index 0000000..f438f51
--- /dev/null
+++ b/tests/files/tar/tar.7z
Binary files differ
diff --git a/tests/find.test b/tests/find.test
index 17d72fd..f427737 100755
--- a/tests/find.test
+++ b/tests/find.test
@@ -38,6 +38,8 @@
 testing "-type l -o -type d -type p -o -type f" \
 	"find dir -type l -o -type d -type p -o -type f | sort" \
 	"dir/file\ndir/link\n" "" ""
+testing "-type l,f" \
+	"find dir -type l,f | sort" "dir/file\ndir/link\n" "" ""
 
 # Testing short-circuit evaluations
 
@@ -138,4 +140,8 @@
 testing "-H broken" "find -H broken" "broken\n" "" ""
 testing "-L broken" "find -L broken" "broken\n" "" ""
 
+testing "one slash" 'find /etc/ -maxdepth 1 | grep /passwd\$' '/etc/passwd\n' \
+  '' ''
+testing 'empty arg' 'find "" dir -name file 2>/dev/null' 'dir/file\n' '' ''
+
 rm -rf dir
diff --git a/tests/grep.test b/tests/grep.test
index e3753c2..215491c 100755
--- a/tests/grep.test
+++ b/tests/grep.test
@@ -85,7 +85,7 @@
   "" ""
 rm -rf sub
 
-# -x exact match trumps -F's "empty string matches whole line" behavior
+# -x exact match overrides -F's "empty string matches whole line" behavior
 testing "-Fx ''" "grep -Fx '' input" "" "one one one\n" ""
 testing "-F ''" "grep -F '' input" "one one one\n" "one one one\n" ""
 testing "-F -e blah -e ''" "grep -F -e blah -e '' input" "one one one\n" \
@@ -138,7 +138,7 @@
   "one two three\none two\none\n"
 
 echo "one\ntwo\nthree" > test
-testing "-l trumps -C" "grep -l -C1 two test input" "test\ninput\n" \
+testing "-l overrides -C" "grep -l -C1 two test input" "test\ninput\n" \
   "three\ntwo\none\n" ""
 rm test
 
@@ -197,3 +197,8 @@
   "missing\nH\nthis is hello\nthis is world\nh\nmissing" ""
 testing "-Fix" "grep -Fix h input" "H\nh\n" \
   "missing\nH\nthis is HELLO\nthis is WORLD\nh\nmissing" ""
+testing "-f /dev/null" "grep -f /dev/null" "" "" "hello\n"
+testing "-z with \n in pattern" "grep -f input" "hi\nthere\n" "i\nt" "hi\nthere"
+
+testing "print zero length match" "grep '[0-9]*'" "abc\n" "" "abc\n"
+testing "-o skip zero length match" "grep -o '[0-9]*'" "1234\n" "" "a1234b"
diff --git a/tests/gunzip.test b/tests/gunzip.test
index 9f9ef5e..bf9b983 100644
--- a/tests/gunzip.test
+++ b/tests/gunzip.test
@@ -21,6 +21,13 @@
     test -f f.gz && cat f" "hello world\n" "" ""
 rm -f f f.gz
 
+# test FEXTRA support
+echo "1f8b08040000000000ff04000000ffff4bcbcfe70200a865327e04000000" | xxd -r -p > f1.gz
+testing "FEXTRA flag skipped properly" "gunzip f1.gz &&
+    ! test -f f1.gz && test -f f1 &&
+    cat f1" "foo\n" "" ""
+rm -f f1 f1.gz
+
 # -c	Output to stdout
 echo -n "foo " | gzip > f1.gz
 echo "bar" | gzip > f2.gz
diff --git a/tests/id.test b/tests/id.test
index b4b13a6..5eae928 100755
--- a/tests/id.test
+++ b/tests/id.test
@@ -5,13 +5,15 @@
 #testing "name" "command" "result" "infile" "stdin"
 
 # Systems with SELinux will have security context cruft,
-# and BSDs call the root group "wheel" instead.
-CLEAN="sed 's/ context=.*//g' | sed 's/wheel/root/g'"
+# BSDs call the root group "wheel" instead,
+# and Raspberry Pi OS has root also in the 117(lpadmin) group.
+CLEAN="sed 's/ context=.*//g' | sed 's/wheel/root/g' | \
+sed 's/117//g' | sed -E 's/\(?lpadmin\)?//g' | sed 's/[ ,]$//'"
 
 testing "0" "id 0 | $CLEAN" "uid=0(root) gid=0(root) groups=0(root)\n" "" ""
 testing "root" "id root | $CLEAN" \
   "uid=0(root) gid=0(root) groups=0(root)\n" "" ""
-testing "-G root" "id -G root" "0\n" "" ""
+testing "-G root" "id -G root | $CLEAN" "0\n" "" ""
 testing "-nG root" "id -nG root | $CLEAN" "root\n" "" ""
 testing "-g root" "id -g root" "0\n" "" ""
 testing "-ng root" "id -ng root | $CLEAN" "root\n" "" ""
diff --git a/tests/install.test b/tests/install.test
new file mode 100755
index 0000000..b1113c0
--- /dev/null
+++ b/tests/install.test
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# TODO: fill this out.
+# TODO: "make install" means something else, so no test_install, only callable
+# from "make tests"...
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+dd if=/dev/urandom of=random bs=64 count=1 2> /dev/null
+testing "install -D exists" \
+  "mkdir -p a; touch a/b; install -D random a/b && cmp random a/b && echo yes" \
+  "yes\n" "" ""
+rm -rf a random
+testing "install -D -t creates directory" \
+  "touch a; install -Dt b a && echo yes" \
+  "yes\n" "" ""
+rm -rf a b
diff --git a/tests/modinfo.test b/tests/modinfo.test
index aaa7a7b..261acfd 100644
--- a/tests/modinfo.test
+++ b/tests/modinfo.test
@@ -19,7 +19,7 @@
 # Find some modules to work with.
 MODULE_PATH1=$(find $MODULE_ROOT/lib/modules -name *.ko | head -1 2>/dev/null)
 MODULE1=$(basename -s .ko $MODULE_PATH1)
-MODULE_PATH2=$(find $MODULE_ROOT/lib/modules -name *.ko | tail -1 2>/dev/null)
+MODULE_PATH2=$(find $MODULE_ROOT/lib/modules -name *.ko | head -2 | tail -1 2>/dev/null)
 MODULE2=$(basename -s .ko $MODULE_PATH2)
 DASH_MODULE=$(basename -s .ko \
   $(find $MODULE_ROOT/lib/modules -name *-*.ko | tail -1 2>/dev/null))
diff --git a/tests/mount.test b/tests/mount.test
index 19ba565..4f7a667 100644
--- a/tests/mount.test
+++ b/tests/mount.test
@@ -47,8 +47,8 @@
    sleep 1 && umount /mnt && ! test -e /mnt/testDir" "" "" ""
 reCreateTmpFs
 testing "-rw $tmp_b_fs /mnt (read_write mode)" \
-  'mount -rw $tmp_b_fs /mnt >/dev/null && mkdir /mnt/testDir && \
-   sleep 1 && ! test -e /mnt/testDir && umount /mnt' "" "" ""
+  "mount -rw $tmp_b_fs /mnt >/dev/null && mkdir /mnt/testDir && \
+   sleep 1 && ! test -e /mnt/testDir && umount /mnt" "" "" ""
 reCreateTmpFs
 testing "$tmp_b_fs /mnt -t fs_type" \
   "mount $tmp_b_fs /mnt -t $tmp_b_fs_type >/dev/null 2>&1 &&
diff --git a/tests/mv.test b/tests/mv.test
index ed8922a..3731bf6 100755
--- a/tests/mv.test
+++ b/tests/mv.test
@@ -135,32 +135,16 @@
   "yes\n" "" ""
 rm -f file*
 
-# If there is stdin, it prompts.  If no stdin, it moves anyway and file2 won't
-# exist.
 touch file1 file2
 chmod 400 file1 file2
-testing "mv over unwritable file: no stdin" \
+testing "over unwritable file only prompts when stdin is a terminal" \
   "mv file2 file1 2>/dev/null && [ -e file1 -a ! -e file2 ] && echo yes" \
   "yes\n" "" ""
 rm -f file*
 
 touch file1 file2
-chmod 400 file1 file2
-testing "mv over unwritable file: answered YES" \
-  "mv file2 file1 2>/dev/null && [ -e file1 -a ! -e file2 ] && echo yes" \
-  "yes\n" "" "y\n"
-rm -f file*
-
-touch file1 file2
-chmod 400 file1 file2
-testing "mv over unwritable file: answered NO" \
-  "mv file2 file1 2>/dev/null && [ -e file1 -a -e file2 ] && echo yes" \
-  "yes\n" "" "n\n"
-rm -f file*
-
-touch file1 file2
 testing "interactive: no stdin" \
-  "mv -i file2 file1 2>/dev/null && [ -e file1 -a ! -e file2 ] && echo yes" \
+  "mv -i file2 file1 2>/dev/null && [ -e file1 -a -e file2 ] && echo yes" \
   "yes\n" "" ""
 rm -f file*
 
diff --git a/tests/netcat.test b/tests/netcat.test
new file mode 100755
index 0000000..81e6ec7
--- /dev/null
+++ b/tests/netcat.test
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+{ dd if=/dev/zero bs=4k count=1 2>/dev/null | tr '\0' a; echo b; } > testfile
+testing "more than buffer bytes left at end" \
+  "netcat -lp 1234 wc -c & sleep .1 && cat testfile | netcat 127.0.0.1 1234" \
+  "4098\n" "" ""
+rm -f testfile
diff --git a/tests/patch.test b/tests/patch.test
index 42eed93..5cc6001 100755
--- a/tests/patch.test
+++ b/tests/patch.test
@@ -86,3 +86,34 @@
 +hello
 '
 # todo bork bork2
+
+# We hit a bug, test the bugfix.
+testing "fuzz" "patch > /dev/null && cat input" \
+"blah blah
+ */
+package org.yaml.snakeyaml.representer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+" "blah blah
+ */
+package org.yaml.snakeyaml.representer;
+
+import java.beans.IntrospectionException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+" "--- a/x/input
++++ b/x/input
+@@ -15,7 +15,6 @@
+  */
+ package org.yaml.snakeyaml.representer;
+
+-import java.beans.IntrospectionException;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Iterator;
+"
diff --git a/tests/readelf.test b/tests/readelf.test
index 9c0e956..1e85e45 100755
--- a/tests/readelf.test
+++ b/tests/readelf.test
@@ -70,7 +70,7 @@
   [31] .strtab              STRTAB         0000000000000000 001b18 0001f4 00      0  0  1
 " "" ""
 
-NOSPACE=1 testing "-l" "readelf -lW $elf-short" "
+testing "-l" "readelf -lW $elf-short" "
 Elf file type is DYN (Shared object file)
 Entry point 0x1001
 There are 10 program headers, starting at offset 52
@@ -104,7 +104,8 @@
 " "" ""
 
 # binutils doesn't line up the column headers for 64-bit ELF files.
-NOSPACE=1 testing "-d" "readelf -dW $elf-full" "
+# TODO: binutils readelf lies about trailing NULL entires binutils ld produces
+NOSPACE=1 toyonly testing "-d" "readelf -dW $elf-full" "
 Dynamic section at offset 0xd98 contains 33 entries:
   Tag                Type                 Name/Value
  0x0000000000000001 (NEEDED)             Shared library: [libc.so]
@@ -143,7 +144,7 @@
 " "" ""
 
 # toybox does a better job of decoding Android's ELF notes than binutils.
-SKIP_HOST=1 testing "-n" "readelf -nW $elf-short" "
+toyonly testing "-n" "readelf -nW $elf-short" "
 Displaying notes found in: .note.android.ident
   Owner                 Data size	Description
   Android              0x00000004	NT_VERSION	API level 28
@@ -200,3 +201,20 @@
   0x000000f0 64656275 67646174 61000000 000000   debugdata......
 
 " "" ""
+
+# TODO: remove the sed when we handle symbol versions
+testing "-s" "readelf -s $elf-short | sed s/@.*//" "
+Symbol table '.dynsym' contains 11 entries:
+   Num:    Value  Size Type    Bind   Vis      Ndx Name
+     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
+     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_init
+     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail
+     3: 00000000     0 OBJECT  GLOBAL DEFAULT  UND __stack_chk_guard
+     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset
+     5: 000010d8    12 FUNC    GLOBAL DEFAULT   13 __aeabi_memclr
+     6: 000010d8    12 FUNC    GLOBAL DEFAULT   13 __aeabi_memclr4
+     7: 000010d8    12 FUNC    GLOBAL DEFAULT   13 __aeabi_memclr8
+     8: 000010c8    16 FUNC    GLOBAL DEFAULT   13 __aeabi_memset
+     9: 000010c8    16 FUNC    GLOBAL DEFAULT   13 __aeabi_memset4
+    10: 000010c8    16 FUNC    GLOBAL DEFAULT   13 __aeabi_memset8
+" "" ""
diff --git a/tests/rev.test b/tests/rev.test
index ede78c2..96da4d3 100755
--- a/tests/rev.test
+++ b/tests/rev.test
@@ -7,19 +7,19 @@
 echo -e "one" > file1 
 echo -e "two" > file2
 testing "rev" "rev && echo yes" "orez\nyes\n" "" "zero\n"
-testing "-" "rev - && echo yes" "orez\nyes\n" "" "zero\n"
+toyonly testing "-" "rev - && echo yes" "orez\nyes\n" "" "zero\n"
 testing "file1 file2" "rev file1 file2" "eno\nowt\n" "" ""
-testing "- file"      "rev - file1"     "orez\neno\n" "" "zero\n"
-testing "file -"      "rev file1 -"     "eno\norez\n" "" "zero\n"
-testing "no trailing newline" "rev -" "cba\nfed\n" "" "abc\ndef"
+toyonly testing "- file"      "rev - file1"     "orez\neno\n" "" "zero\n"
+toyonly testing "file -"      "rev file1 -"     "eno\norez\n" "" "zero\n"
+toyonly testing "no trailing newline" "rev input" "cba\nfed\n" "abc\ndef" ""
 
 testing "file1 notfound file2" \
-        "rev file1 notfound file2 2>stderr && echo ok ; cat stderr; rm stderr" \
-        "eno\nowt\nrev: notfound: No such file or directory\n" "" ""
+        "rev file1 notfound file2 2>stderr && echo ok ; grep -o 'notfound: No such file or directory' stderr; rm stderr" \
+        "eno\nowt\nnotfound: No such file or directory\n" "" ""
 
 testing "different input sizes"\
         "rev"\
         "\n1\n21\n321\n4321\n54321\n4321\n321\n21\n1\n\n"\
         "" "\n1\n12\n123\n1234\n12345\n1234\n123\n12\n1\n\n"
 
-rm file1 file2
\ No newline at end of file
+rm file1 file2
diff --git a/tests/rm.test b/tests/rm.test
index ab6a97d..9ab4c4b 100755
--- a/tests/rm.test
+++ b/tests/rm.test
@@ -53,10 +53,16 @@
 
 mkdir -p d1
 touch d1/f1.txt d1/f2.txt
-testing "-rv dir" \
-  "rm -rv d1 | sort" "rm 'd1/f1.txt'\nrm 'd1/f2.txt'\nrmdir 'd1'\n" "" ""
+testing "-rv dir" "rm -rv d1 | sed 's/emoved/m/;s/ directory/dir/' | sort" \
+  "rm 'd1/f1.txt'\nrm 'd1/f2.txt'\nrmdir 'd1'\n" "" ""
 rm -rf d1
 
-touch "'"
-testing "-v \\'" "rm -v \\'" "rm '''\n" "" "" # TODO: coreutils escapes quote
-rm -f \'
+touch "meep"
+testing "-v" "rm -v meep | sed 's/emoved/m/'" "rm 'meep'\n" "" ""
+rm -f meep
+
+skipnot [ $(id -u) -eq 0 ]
+testing "-f <readonly_filesystem>/<missing_file>" \
+  "rm -rf mnt_point && mkdir -p mnt_point &&
+  mount -t tmpfs -o ro none ./mnt_point && rm -f mnt_point/missing_file &&
+  echo yes; umount ./mnt_point; rm -rf mnt_point" "yes\n" "" ""
diff --git a/tests/rmdir.test b/tests/rmdir.test
index 5b36bbc..1731ecc 100755
--- a/tests/rmdir.test
+++ b/tests/rmdir.test
@@ -40,6 +40,10 @@
 	"yes\n" "" ""
 rm -rf temp
 
+skipnot [ $UID -eq 0 ]
+testing '-p abspath' \
+  'mkdir -p /test/2/3 && rmdir -p /test/2/3 && [ ! -e /test ] && echo yes' \
+  'yes\n' '' ''
 
 mkdir -p one/two/three
 testing "-p one/two/three" \
diff --git a/tests/sed.test b/tests/sed.test
index 98c109a..fd3b205 100755
--- a/tests/sed.test
+++ b/tests/sed.test
@@ -4,7 +4,7 @@
 
 testing 'as cat' 'sed ""' "one\ntwo\nthree" "" "one\ntwo\nthree"
 # This segfaults ubuntu 12.04's sed. No really.
-SKIP_HOST=1 testing 'sed - - twice' 'sed "" - -' "hello\n" "" "hello\n"
+testing 'sed - - twice' 'sed "" - -' "hello\n" "" "hello\n"
 testing '-n' 'sed -n ""' "" "" "one\ntwo\nthree"
 testing '-n p' 'sed -n p' "one\ntwo\nthree" "" "one\ntwo\nthree"
 testing 'explicit pattern' 'sed -e p -n' "one\ntwo\nthree" "" \
@@ -45,7 +45,7 @@
 	"sed -n '\t\txtp'" "tx\n" "" "tx\n"
 testing 'match n delim' "sed -n '\n\txnp'" "\tx\n" "" "\tx\n"
 testing 'match n delim disables \n newline' "sed -n '\n\nxnp'" "" "" "\nx\n"
-SKIP_HOST=1 testing 'match \n literal n' "sed -n '\n\nxnp'" "nx\n" "" "nx\n"
+toyonly testing 'match \n literal n' "sed -n '\n\nxnp'" "nx\n" "" "nx\n"
 testing 'end match does not check starting match line' \
 	"sed -n '/two/,/two/p'" "two\nthree" "" "one\ntwo\nthree"
 testing 'end match/start match mixing number/letter' \
@@ -82,7 +82,7 @@
         "" "one\ntwo\nthree\nfour\nfive\nsix"
 testing "c multiple continuation" \
 	"sed -e 'c\\' -e 'two\\' -e ''" "two\n\n" "" "hello"
-SKIP_HOST=1 testing "c empty continuation" "sed -e 'c\\'" "\n" "" "hello"
+toyonly testing "c empty continuation" "sed -e 'c\\'" "\n" "" "hello"
 testing "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"
@@ -115,6 +115,7 @@
 testing 'empty match' "sed -e 's/[^ac]*/A/g'" 'AaAcA' '' 'abcde'
 testing 's///#comment' "sed -e 's/TWO/four/i#comment'" "one\nfour\nthree" \
 	"" "one\ntwo\nthree"
+testing 's///num off end' 'sed -e s/e//2' 'e\n' '' 'e\n'
 
 testing 'N flushes pending a and advances match counter' \
 	"sed -e 'a woo' -e 'N;\$p'" 'woo\none\ntwo\none\ntwo' "" 'one\ntwo'
@@ -138,7 +139,7 @@
 
 testing "" "sed -e '/x/c\' -e 'y'" 'y\n' '' 'x\n'
 testing "" "sed -e 's/a[([]*b/X/'" 'X' '' 'a[(b'
-testing "" "sed 'y/a\\bc/de\f/'" "db\f" "" "abc"
+toyonly testing "" "sed 'y/a\\bc/de\f/'" "db\f" "" "abc"
 testing "[a-a] (for perl)" "sed '"'s/\([^a-zA-Z0-9.:_\-\/]\)/\\\1/g'"'" \
   'he\ llo' "" "he llo"
 
@@ -150,7 +151,7 @@
 
 # You have to match the first line of a range in order to activate
 # the range, numeric and ascii work the same way
-testing "skip start of range" "sed -e n -e '1,2s/b/c/'" "a\nb\n" "" "a\nb\n"
+toyonly testing "skip start of range" "sed -e n -e '1,2s/b/c/'" "a\nb\n" "" "a\nb\n"
 testing "range +1" "sed -ne '/blah/,+1p'" "blah\n6\n" "" \
   "1\n2\n3\n4\n5\nblah\n6\n7\n8\n9\n"
 testing "range +0" "sed -ne '/blah/,+0p'" "blah\n" "" \
@@ -158,6 +159,9 @@
 testing "range +3" "sed -ne '2,+3p'" "2\n3\n4\n5\n" "" \
   "1\n2\n3\n4\n5\nblah\n6\n7\n8\n9\n"
 
+testing "not -s" "sed -n 1p input -" "one" "one" "two"
+testing "-s" "sed -sn 1p input -" "one\ntwo" "one\n" "two"
+
 #echo meep | sed/sed -e '1a\' -e 'huh'
 #echo blah | sed/sed -f <(echo -e "1a\\\\\nboom")
 #echo merp | sed/sed "1a\\
@@ -182,10 +186,15 @@
 testing '\n too high' \
     'sed -E "s/(.*)/\2/p" 2>/dev/null || echo OK' "OK\n" "" "foo"
 
+toyonly testing 's///x' 'sed "s/(hello )?(world)/\2/x"' "world" "" "hello world"
+
 # Performance test
 X=x; Y=20; while [ $Y -gt 0 ]; do X=$X$X; Y=$(($Y-1)); done
-testing 'megabyte s/x/y/g (5 sec timeout)' "timeout 5 sed 's/x/y/g' | sha1sum" \
+testing 'megabyte s/x/y/g (20 sec timeout)' \
+  "timeout 20 sed 's/x/y/g' | sha1sum" \
   '138c1fa7c3f64186203b0192fb4abdb33cb4e98a  -\n' '' "$X\n"
 unset X Y
 
+testing 's i and I' 'sed s/o/0/ig' "f00l F00L" "" "fool FOOL"
+
 # -i with $ last line test
diff --git a/tests/seq.test b/tests/seq.test
index 15a208b..05d9b1e 100755
--- a/tests/seq.test
+++ b/tests/seq.test
@@ -69,3 +69,8 @@
 
 # TODO: busybox fails this too, but GNU seems to not use double for large ints.
 #testing "too large for double" "seq -s, 9007199254740991 1 9007199254740992" "9007199254740992\n" "" ""
+
+testing "INT_MIN" "seq -2147483648 -2147483647" "-2147483648\n-2147483647\n"\
+  "" ""
+
+testing "fast path" "timeout 10 seq 10000000 > /dev/null" "" "" ""
diff --git a/tests/sh.test b/tests/sh.test
old mode 100755
new mode 100644
index dd18af7..947e3ae
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -1,5 +1,37 @@
 #!/bin/echo no
 
+# TODO: categorize tests
+
+# TODO https://mywiki.wooledge.org/BashFAQ
+#   http://tiswww.case.edu/php/chet/bash/FAQ
+#   https://mywiki.wooledge.org/BashPitfalls#set_-euo_pipefail
+
+#        // ${#} ${#x} ${#@} ${#x[@]} ${#!} ${!#}
+#        // ${!} ${!@} ${!@Q} ${!x} ${!x@} ${!x@Q} ${!x#} ${!x[} ${!x[*]}
+
+# Looked like a prefix but wasn't: three chars (@ # -) are both paremeter name
+# and slice operator. When immediately followed by } it's parameter, otherwise
+# we did NOT have a prefix and it's an operator.
+#
+# ${#-} ${#-abc}
+# ${##} ${##0}
+# ${#@} ${#@Q}
+#
+# backslash not discarded: echo "abc\"def"
+
+# ${x:-y} use default
+# ${x:=y} assign default (error if positional)
+# ${x:?y} err if null
+# ${x:+y} alt value
+# ${x:off} ${x:off:len} off<0 from end (must ": -"), len<0 also from end must
+#   0-based indexing
+# ${@:off:len} positional parameters, off -1 = len, -len is error
+#   1-based indexing
+
+# [] wins over +()
+# touch 'AB[DEF]'; echo AB[+(DEF]) AB[+(DEF)?
+# AB[+(DEF]) AB[DEF]
+
 # Testing shell corner cases _within_ a shell script is kind of hard.
 
 [ -f testing.sh ] && . testing.sh
@@ -8,35 +40,107 @@
 
 #testing "name" "command" "result" "infile" "stdin"
 
+# texpect "name" "command" E/O/I"string"
+
+# Use "bash" name for host, "sh" for toybox
 [ -z "$SH" ] && { [ -z "$TEST_HOST" ] && SH="sh" || export SH="bash" ; }
+# Prompt changes for root/normal user
+[ $(id -u) -eq 0 ] && P='# ' || P='$ '
+# run sufficiently isolated shell child process to get predictable results
+SS="env -i PATH=${PATH@Q} PS1='\\$ ' $SH --noediting --noprofile --norc -is"
+
+shxpect() {
+  X="$1"
+  shift
+  txpect "$X" "$SS" E"$P" "$@" X0
+}
+
+shxpect "prompt and exit" I$'exit\n'
+shxpect "prompt and echo" I$'echo hello\n' O$'hello\n' E"$P"
+shxpect "redirect err" I$'echo > /dev/full\n' E E"$P"
+shxpect "wait for <(exit)" I$'cat <(echo hello 1>&2)\n' E$'hello\n' E"$P"
 
 # Test the sh -c stuff before changing EVAL
 testing '-c "" exit status 0' '$SH -c "" && echo $?' '0\n' '' ''
 testing '-c args' "\$SH -c 'echo \$0,\$1,\$2,\$3' one two three four five" \
   "one,two,three,four\n" "" ""
+testing '-c args2' "\$SH -c 'echo \${10}' a b c d e f g h i j k l" "k\n" "" ""
 testing '-c arg split' \
   "$SH -c 'for i in a\"\$@\"b;do echo =\$i=;done;echo \$0' 123 456 789" \
   "=a456=\n=789b=\n123\n" "" ""
+testing '-c arg split2' \
+  "$SH -c 'for i in a\"\$* \$@\"b; do echo =\$i=;done' one two three four five"\
+  "=atwo three four five two=\n=three=\n=four=\n=fiveb=\n" "" ""
+testing '-c arg count' "$SH -c 'echo \$#' 9 8 7 6 1 2 3 4" "7\n" "" ""
 testing "exec3" '$C -c "{ exec readlink /proc/self/fd/0;} < /proc/self/exe"' \
   "$(readlink -f $C)\n" "" ""
+testing 'arg shift' "$SH -c '"'for i in "" 2 1 1 1; do echo $? $1; shift $i; done'"' one two three four five" \
+  "0 two\n0 three\n0 five\n0\n1\n" "" ""
+testing '(subshell)' '$SH -c "(echo hello)"' 'hello\n' '' ''
+testing 'syntax' '$SH -c "if true; then echo hello | fi" 2>/dev/null || echo x'\
+  'x\n' '' ''
+
+# The bash man page is lying when it says $_ starts with an absolute path.
+ln -s $(which $SH) bash
+testing 'non-absolute $_' "./bash -c 'echo \$_'" './bash\n' '' ''
+rm bash
+
+shxpect '$_ preserved on assignment error' I$'true hello; a=1 b=2 c=${}\n' \
+  E E"$P" I$'echo $_\n' O$'hello\n'
+shxpect '$_ preserved on prefix error' I$'true hello; a=1 b=2 c=${} true\n' \
+  E E"$P" I$'echo $_\n' O$'hello\n'
+shxpect '$_ preserved on exec error' I$'true hello; ${}\n' \
+  E E"$P" I$'echo $_\n' O$'hello\n'
+shxpect '$_ abspath on exec' I$'env | grep ^_=\n' O$'_=/usr/bin/env\n'
+testing '$_ literal after exec' 'env >/dev/null; echo $_' 'env\n' '' ''
+shxpect '$_ no path for builtin' I$'true; echo $_\n' O$'true\n'
+testing 'prefix is local for builtins' 'abc=123; abc=def unset abc; echo $abc' \
+  '123\n' '' ''
+testing 'prefix localizes magic vars' \
+  'SECONDS=123; SECONDS=345 true; echo $SECONDS' '123\n' '' ''
+shxpect 'body evaluated before variable exports' I$'a=x${} y${}\n' RE'y${}'
+testing '$NOTHING clears $_' 'true; $NOTHING; echo $_' '\n' '' ''
 
 testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" ""
 testing 'simple script' '$SH input' 'input\n' 'echo $0' ''
 testing 'simple script2' '$SH ./input two;echo $?' './input+two\n42\n' \
   '\necho $0+$1\n\nexit 42' ''
+# this segfaults bash
+toyonly testing 'recursion guard' \
+  '$SH input 2>/dev/null; [ $? -lt 128 ] && echo pass' 'pass\n' \
+  'source input' ''
+testing '$LINENO 1' "$SH input" "1\n" 'echo $LINENO' ''
+
 mkdir sub
 echo echo hello > sub/script
-testing 'simple script in PATH' "PATH='$PWD/sub:$PATH' $SH script" \
+testing 'simple script in $PATH' "PATH='$PWD/sub:$PATH' $SH script" \
   'hello\n' '' ''
 rm -rf sub
 
+testing "script file" "chmod +x input; ./input" "hello\n" "#!$C\necho hello" ""
+testing 'IFS $*' "$SH -c 'IFS=xy; echo \"\$*\"' one two tyree" "twoxtyree\n" \
+  "" ""
+testing 'default exports' \
+  "env -i \"$(which $SH)\" --noprofile --norc -c env | sort" \
+  "PWD=$(pwd)\nSHLVL=1\n_=$(which env)\n" "" ""
+# toysh order of operations not matching bash
+#testing "leading assignment fail" \
+#  "{ \$SH -c 'X=\${a?blah} > walroid';ls walroid;} 2>/dev/null" '' '' ''
+testing "lineno" "$SH input" "5 one\n6 one\n5 two\n6 two\n" \
+  '#!/bin/bash\n\nfor i in one two\ndo\n  echo $LINENO $i\n  echo $LINENO $i\ndone\n' ""
+testing "eval0" "sh -c 'eval echo \$*' one two three" "two three\n" "" ""
+
+#########################################################################
 # Change EVAL to call sh -c for us, using "bash" explicitly for the host.
 export EVAL="$SH -c"
 
 testing "smoketest" "echo hello" "hello\n" "" ""
+testing "line break" $'ec\\\nho hello' 'hello\n' '' ''
 testing "eval" "eval echo hello" "hello\n" "" ""
 testing "eval2" "eval 'echo hello'; echo $?" "hello\n0\n" "" ""
 testing "eval3" 'X="echo hello"; eval "$X"' "hello\n" "" ""
+testing "eval4" 'eval printf '=%s=' \" hello \"' "= hello =" "" ""
+NOSPACE=1 testing "eval5" 'eval echo \" hello \" | wc' ' 1 1 8' "" ""
 testing "exec" "exec echo hello" "hello\n" "" ""
 testing "exec2" "exec echo hello; echo $?" "hello\n" "" "" 
 
@@ -48,6 +152,8 @@
 testing "||" "true || echo hello" "" "" ""
 testing "||2" "false || echo hello" "hello\n" "" ""
 testing "&& ||" "true && false && potato || echo hello" "hello\n" "" ""
+testing "&& after function" "x(){ false;};x && echo yes" "" "" ""
+testing "|| after function" "x(){ false;};x || echo yes" "yes\n" "" ""
 
 # redirection
 
@@ -57,8 +163,17 @@
 testing "redir4" "touch /not/exist 2>out||grep -o /not/exist out" \
   "/not/exist\n" "" ""
 testing "redir5" "ls out /not/exist &> out2 || wc -l < out2" "2\n" "" ""
-testing "redir6" "ls out /not/exist |& wc -l" "2\n" "" ""
-testing "redir7" 'echo -n $(<input)' "boing" "boing\n" ""
+testing "redir6" "ls out /not/exist &>>-abc || wc -l < ./-abc" "2\n" "" ""
+testing "redir7" "ls out /not/exist |& wc -l" "2\n" "" ""
+testing "redir8" 'echo -n $(<input)' "boing" "boing\n" ""
+shxpect "redir9" I$'echo hello > out 2>/does/not/exist\n' E E"$P" \
+  I$'wc -l < out\n' O$'0\n'
+testing "redir10" 'echo hello 3<&3' "hello\n" "" ""
+testing "redir11" 'if :;then echo one;fi {abc}<input; cat <&$abc' \
+  "one\npotato\n" "potato\n" ""
+rm -f out out2 ./-abc
+
+# expansion
 
 testing "tilde expansion" "echo ~" "$HOME\n" "" ""
 testing "tilde2" "echo ~/dir" "$HOME/dir\n" "" ""
@@ -77,15 +192,87 @@
 for i in /root /var/root /; do [ -e $i ] && EXPECT=$i && break; done
 testing "bracket+tilde" "echo {~,~root}/pwd" "$HOME/pwd $EXPECT/pwd\n" "" ""
 
+# Slices
+
+testing '${x#prefix}' 'x=abcde; echo ${x#abc}' 'de\n' '' ''
+testing '${x#short} ${x##long}' 'x=banana; echo ${x#b*n} ${x##b*n}' \
+  'ana a\n' '' ''
+toyonly testing '${x#utf8}' 'x=aそcde; echo ${x##a?c}' 'de\n' '' ''
+testing '${x%y}' 'x=potato; echo ${x%t*o} ${x%%t*o}' 'pota po\n' '' ''
+testing '${x^y}' 'x=aaaaa; echo ${x^a}' 'Aaaaa\n' '' ''
+testing '${x^^y}' 'x=abccdec; echo ${x^^c}; x=abcdec; echo ${x^^c}' \
+  'abCCdeC\nabCdeC\n' '' ''
+testing '${x,y}' 'x=BBB; echo ${x,B}' 'bBB\n' '' ''
+testing '${x,,y}' 'x=POTATO; echo ${x,,} ${x,,?} ${x,,*} ${x,,T}' \
+  'potato potato potato POtAtO\n' '' ''
+
+mkdir -p abc/def/ghi
+touch www
+testing 'wildcards' 'echo w[v-x]w w[x-v]w abc/*/ghi' \
+  'www w[x-v]w abc/def/ghi\n' '' ''
+
 #testing "backtick1" 'X=fred; echo `echo $x`' 'fred\n' "" ""
 #testing "backtick2" 'X=fred; echo `x=y; echo $x`' 'y\n' "" ""
-testing '$(( ) )' 'echo $((echo hello) | tr e x)' "hxllo\n" "" ""
+testing '$(( ) )' 'echo ab$((echo hello) | tr e x)cd' "abhxllocd\n" "" ""
+testing '$((x=y)) lifetime' 'a=boing; echo $a $a$((a=4))$a $a' 'boing boing44 4\n' '' ''
+
+testing 'quote' "echo \"'\"" "'\n" "" ""
+
+# Loops and flow control
+testing "case" 'for i in A C J B; do case "$i" in A) echo got A ;; B) echo and B ;; C) echo then C ;; *) echo default ;; esac; done' \
+  "got A\nthen C\ndefault\nand B\n" "" ""
+testing 'case;;&' 'case wow in w?w) echo ok;;& wow) echo no; esac' 'ok\nno\n' \
+  "" ""
+testing "case newlines" \
+  $'case i\n\nin\n\na) echo one\n\n;;\n\ni)\n\necho two\n\n;;\n\nesac' \
+  "two\n" "" ""
+testing 'loop in && ||' \
+  'false && for i in a b c; do echo $i; done || echo no' 'no\n' '' ''
+testing "continue" 'for i in a b c; do for j in d e f; do echo $i $j; continue 2; done; done' \
+  "a d\nb d\nc d\n" "" ""
+testing "piped loops that don't exit" \
+  'while X=$(($X+1)); do echo $X; done | while read i; do echo $i; done | head -n 5' \
+  '1\n2\n3\n4\n5\n' '' ''
+
+# <glinda>A variable occurred</glinda>
+
+testing "expand" 'echo $PWD' "$(pwd)\n" "" ""
+testing "expand2" 'echo "$PWD"' "$(pwd)\n" "" ""
+testing "expand3" 'echo "$"PWD' '$PWD\n' "" ""
+testing "expand4" 'P=x; echo "$P"WD' 'xWD\n' "" ""
+testing "dequote" "echo one 'two' ''three 'fo'ur '\\'" \
+  'one two three four \\\n' '' ''
 
 testing "leading variable assignment" 'abc=def env | grep ^abc=; echo $abc' \
   "abc=def\n\n" "" ""
 testing "leading variable assignments" \
   "abc=def ghi=jkl env | egrep '^(abc|ghi)=' | sort; echo \$abc \$ghi" \
   "abc=def\nghi=jkl\n\n" "" ""
+testing "leading assignment occurs after parsing" \
+  'abc=def; abc=ghi echo $abc' "def\n" "" ""
+testing "leading assignment space" 'X="abc  def"; Y=$X; echo "$Y"' \
+  "abc  def\n" "" ""
+testing "leading assignment space2" \
+  'chicken() { X="$@"; }; chicken a b c d e; echo "$X"' 'a b c d e\n' '' ''
+testing "leading assignment fail2" \
+  "{ 1blah=123 echo hello;} 2>/dev/null || echo no" "no\n" "" ""
+testing "leading assignment redirect" \
+  "blah=123 echo hello > walrus && ls walrus" "walrus\n" "" ""
+rm -f walrus
+
+testing "{1..5}" "echo {1..5}" "1 2 3 4 5\n" "" ""
+testing "{5..1}" "echo {5..1}" "5 4 3 2 1\n" "" ""
+testing "{5..1..2}" "echo {5..1..2}" "5 3 1\n" "" ""
+testing "{a..z..-3}" "echo {a..z..-3}" "a d g j m p s v y\n" "" ""
+
+mkfifo POIT
+testing 'background curly block' \
+  '{ sed s/ll/xx/ POIT; }& echo hello > POIT; wait' 'hexxo\n' '' ''
+rm -f POIT
+
+testing 'background pipe block' \
+  'if true; then { sleep .25;bzcat "$FILES"/blkid/ntfs.bz2; }& fi | wc -c' \
+  '8388608\n' '' ''
 
 #$ IFS=x X=xyxz; for i in abc${X}def; do echo =$i=; done
 #=abc=
@@ -108,7 +295,12 @@
 testing "IFS" 'IFS=x; A=abx; echo -n "$A"' "abx" "" ""
 testing "IFS2" 'IFS=x; A=abx; echo -n $A' "ab" "" ""
 testing "IFS3" 'IFS=x; echo "$(echo abx)"' "abx\n" "" ""
-testing "IFS4" "IFS=x; echo \"\$(echo ab' ')\"" "ab \n" "" ""
+testing "IFS4" 'IFS=x; echo $(echo abx)y' "ab y\n" "" ""
+testing "IFS5" 'IFS=xy; for i in abcxdefyghi; do echo =$i=; done' \
+  "=abc def ghi=\n" "" ""
+
+testing 'empty $! is blank' 'echo $!' "\n" "" ""
+testing '$! = jobs -p' 'true & [ $(jobs -p) = $! ] && echo yes' "yes\n" "" ""
 
 testing '$*' 'cc(){ for i in $*;do echo =$i=;done;};cc "" "" "" "" ""' \
   "" "" ""
@@ -135,6 +327,10 @@
   "==\n==\n==\n==\n==\n" "" ""
 testing "IFS10" 'IFS=bcd; A=abcde; for i in $A; do echo =$i=; done' \
   "=a=\n==\n==\n=e=\n" "" ""
+testing "IFS11" \
+  'IFS=x; chicken() { for i in $@$@; do echo =$i=; done;}; chicken one "" abc dxf ghi' \
+  "=one=\n==\n=abc=\n=d=\n=f=\n=ghione=\n==\n=abc=\n=d=\n=f=\n=ghi=\n" "" ""
+testing "IFS12" 'IFS=3;chicken(){ return 3;}; chicken;echo 3$?3' '3 3\n' "" ""
 
 testing "IFS combinations" \
   'IFS=" x"; A=" x " B=" x" C="x " D=x E="   "; for i in $A $B $C $D L$A L$B L$C L$D $A= $B= $C= $D= L$A= L$B= L$C= L$D=; do echo -n {$i}; done' \
@@ -144,6 +340,7 @@
 testing "! by itself" '!; echo $?' "1\n" "" ""
 testing "! true" '! true; echo $?' "1\n" "" ""
 testing "! ! true" '! ! true; echo $?' "0\n" "" ""
+testing "! syntax err" '! echo 2>/dev/null < doesnotexist; echo $?' "0\n" "" ""
 
 # The bash man page doesn't say quote removal here, and yet:
 testing "case quoting" 'case a in "a") echo hello;; esac' 'hello\n' "" ""
@@ -152,11 +349,11 @@
 #testing "subshell split 2"
 
 # variable assignment argument splitting only performed for "$@"
-testing "assignment splitting" 'X="one two"; Y=$X; echo $Y' "one two\n" "" ""
+testing "assignment nosplit" 'X="one two"; Y=$X; echo $Y' "one two\n" "" ""
 testing "argument splitting" \
   'chicken() { for i in a"$@"b;do echo =$i=;done;}; chicken 123 456 789' \
   "=a123=\n=456=\n=789b=\n" "" ""
-testing "assignment splitting2" 'pop(){ X="$@";};pop one two three; echo $X' \
+testing "assignment nosplit2" 'pop(){ X="$@";};pop one two three; echo $X' \
   "one two three\n" "" ""
 
 #testing "leading assignments don't affect current line" \
@@ -181,29 +378,181 @@
 NOSPACE=1 testing "parentheses and pipe" \
   '(echo two;echo three)|tee blah.txt;wc blah.txt' \
   "two\nthree\n2 2 10 blah.txt\n" "" ""
-#testing "pipe into parentheses" \
-#  'echo hello | (read i <input; echo $i; read i; echo $i)' \
-#  "there\nhello\n" "there\n" ""
+testing "pipe into parentheses" \
+  'echo hello | (read i <input; echo $i; read i; echo $i)' \
+  "there\nhello\n" "there\n" ""
 
-# texpect "name" "command" E/O/I"string"
-
-# Prompt changes for root/normal user
-[ $(id -u) -eq 0 ] && P='# ' || P='$ '
-# run sufficiently isolated shell child process to get predictable results
-SH="env -i PATH=${PATH@Q} PS1='\\$ ' $SH --noediting --noprofile --norc -is"
-
-txpect "prompt and exit" "$SH" "E$P" "Iexit\n" X0
-txpect "prompt and echo" "$SH" "E$P" "Iecho hello\n" "Ohello"$'\n' "E$P" X0
-txpect "redirect err" "$SH" "E$P" "Iecho > /dev/full\n" "E" "E$P" X0
-txpect "wait for <(exit)" "$SH" "E$P" "Icat <(echo hello 1>&2)\n" $'Ehello\n' \
-  "E$P" X0
+testing "\$''" $'echo $\'abc\\\'def\\nghi\'' "abc'def\nghi\n" '' ''
+testing "shift shift" 'shift; shift; shift; echo $? hello' "1 hello\n" "" ""
+testing 'search cross $*' 'chicken() { echo ${*/b c/ghi}; }; chicken a b c d' \
+  "a b c d\n" "" ""
+testing 'eval $IFS' 'IFS=x; X=x; eval abc=a${X}b 2>/dev/null; echo $abc' \
+  "\n" '' ''
+testing '${@:3:5}' 'chicken() { for i in "${@:3:5}"; do echo =$i=; done; } ; chicken ab cd ef gh ij kl mn op qr' \
+  '=ef=\n=gh=\n=ij=\n=kl=\n=mn=\n' '' ''
+testing '${@:3:5}' 'chicken() { for i in "${*:3:5}"; do unset IFS; echo =$i=; done; } ; IFS=x chicken ab cd ef gh ij kl mn op qr' \
+  '=efxghxijxklxmn=\n' '' ''
+testing 'sequence check' 'IFS=x; X=abxcd; echo ${X/bxc/g}' 'agd\n' '' ''
 
 # TODO: The txpect plumbing does not work right yet even on TEST_HOST
-#txpect "backtick0" "$SH" "E$P" 'IX=fred; echo `echo \\\\$x`'$'\n' 'Ofred' "E$P" X0
-#txpect "backtick1" "$SH" "E$P" 'IX=fred; echo `echo $x`'$'\n' 'Ofred'$'\n' "E$P" X0
-#txpect "backtick2" "$SH" "E$P" 'IX=fred; echo `x=y; echo $x`' $'Oy\n' "E$P" X0
+#txpect "backtick0" "$SS" "E$P" 'IX=fred; echo `echo \\\\$x`'$'\n' 'Ofred' "E$P" X0
+#txpect "backtick1" "$SS" "E$P" 'IX=fred; echo `echo $x`'$'\n' 'Ofred'$'\n' "E$P" X0
+#txpect "backtick2" "$SS" "E$P" 'IX=fred; echo `x=y; echo $x`' $'Oy\n' "E$P" X0
 
-# $@ $* $# $? $- $$ $! $0
+shxpect '${ with newline' I$'HELLO=abc; echo ${HELLO/b/\n' E"> " I$'}\n' O$'a c\n'
+
+shxpect 'here1' I$'POTATO=123; cat << EOF\n' E"> " \
+  I$'$POTATO\n' E"> " I$'EOF\n' O$'123\n'
+shxpect 'here2' I$'POTATO=123; cat << E"O"F\n' E"> " \
+  I$'$POTATO\n' E"> " I$'EOF\n' O$'$POTATO\n'
+testing 'here3' 'abc(){ cat <<< x"$@"yz;};abc one two "three  four"' \
+  "xone two three  fouryz\n" "" ""
+
+testing '${var}' 'X=abcdef; echo ${X}' 'abcdef\n' '' '' 
+testing '${#}' 'X=abcdef; echo ${#X}' "6\n" "" ""
+testing 'empty ${}' '{ echo ${};} 2>&1 | grep -o bad' 'bad\n' '' ''
+shxpect 'empty ${} syntax err abort' I$'echo ${}; echo hello\n' \
+  E I$'echo and\n' O$'and\n'
+testing '${$b}' '{ echo ${$b};} 2>&1 | grep -o bad' 'bad\n' '' ''
+testing '${!PATH*}' 'echo ${!PATH*}' 'PATH\n' '' ''
+testing '${!PATH@}' 'echo ${!PATH@}' 'PATH\n' '' ''
+#testing '${!PATH[@]}' 'echo ${!PATH[@]}' '0\n' '' ''
+testing '${!x}' 'X=abcdef Y=X; echo ${!Y}' 'abcdef\n' '' ''
+testing '${!x@}' 'ABC=def; def=ghi; echo ${!ABC@}' 'ABC\n' '' ''
+testing '${!x} err' '{ X=abcdef Y=X:2; echo ${!Y}; echo bang;} 2>/dev/null' \
+  '' '' ''
+testing '${!x*}' 'abcdef=1 abc=2 abcq=; echo "${!abc@}" | tr " " \\n | sort' \
+  'abc\nabcdef\nabcq\n' '' ''
+testing '${!x*} none' 'echo "${!abc*}"' '\n' '' ''
+testing '${!x*} err' '{ echo "${!abc*x}"; echo boing;} 2>/dev/null' '' '' ''
+testing '${!none@Q}' 'echo ${X@Q} ${!X@Q}; X=ABC; echo ${!X@Q}' '\n\n' '' ''
+testing '${!x@Q}' 'ABC=123 X=ABC; echo ${!X@Q}' "'123'\n" '' ''
+testing '${#@Q}' 'echo ${#@Q}' "'0'\n" '' ''
+testing '${!*}' 'xx() { echo ${!*};}; fruit=123; xx fruit' '123\n' '' ''
+testing '${!*} indirect' 'xx() { echo ${!a@Q};}; a=@; xx one two three' \
+  "'one' 'two' 'three'\n" '' ''
+testing '${!x@ } match' \
+  '{ ABC=def; def=ghi; echo ${!ABC@ }; } 2>&1 | grep -o bad' 'bad\n' '' ''
+testing '${!x@ } no match no err' 'echo ${!ABC@ }def' 'def\n' '' ''
+testing '${!x@ } no match no err2' 'ABC=def; echo ${!ABC@ }ghi' 'ghi\n' '' ''
+toyonly testing '${#x::}' 'ABC=abcdefghijklmno; echo ${#ABC:1:2}' '5\n' '' ''
+# TODO: ${!abc@x} does _not_ error? And ${PWD@q}
+testing '$""' 'ABC=def; echo $"$ABC"' 'def\n' '' ''
+testing '"$""" does not nest' 'echo "$"abc""' '$abc\n' '' ''
+testing '${\}}' 'ABC=ab}cd; echo ${ABC/\}/x}' 'abxcd\n' '' ''
+testing 'bad ${^}' '{ echo ${^};} 2>&1 | grep -o bad' 'bad\n' '' ''
+testing '${:}' 'ABC=def; echo ${ABC:1}' 'ef\n' '' ''
+testing '${a: }' 'ABC=def; echo ${ABC: 1}' 'ef\n' '' ''
+testing '${a :}' 'ABC=def; { echo ${ABC :1};} 2>&1 | grep -o bad' 'bad\n' '' ''
+testing '${::}' 'ABC=defghi; echo ${ABC:1:2}' 'ef\n' '' ''
+testing '${: : }' 'ABC=defghi; echo ${ABC: 1 : 2 }' 'ef\n' '' ''
+testing '${::} indirect' 'ABC=defghi:1:2; { echo ${!ABC};} 2>&1 | grep -o bad' \
+  'bad\n' '' ''
+testing '${::-}' 'ABC=defghi; echo ${ABC:1:-2}' 'efg\n' '' ''
+testing '${:-:-}' 'ABC=defghi; echo ${ABC:-3:2}' 'defghi\n' '' ''
+testing '${:-:-}2' 'echo ${ABC:-3:2}' '3:2\n' '' ''
+testing '${: -:}' 'ABC=defghi; echo ${ABC: -3:2}' 'gh\n' '' ''
+testing '${@%}' 'chicken() { for i in "${@%abc}"; do echo "=$i="; done;}; chicken 1abc 2abc 3abc' '=1=\n=2=\n=3=\n' '' ''
+testing '${*%}' 'chicken() { for i in "${*%abc}"; do echo "=$i="; done;}; chicken 1abc 2abc 3abc' '=1 2 3=\n' '' ''
+testing '${@@Q}' 'xx() { echo "${@@Q}"; }; xx one two three' \
+  "'one' 'two' 'three'\n" '' ''
+
+shxpect '${/newline/}' I$'x=$\'\na\';echo ${x/\n' E'> ' I$'/b}\n' O$'ba\n' E'> '
+
+shxpect 'line continuation' I$'echo "hello" \\\n' E'> ' I$'> blah\n' E"$P" \
+  I$'wc blah\n' O$'1 1 6 blah\n'
+shxpect 'line continuation2' I$'echo ABC\\\n' E'> ' I$'DEF\n' O$'ABCDEF\n'
+
+# Race condition (in bash, but not in toysh) can say 43.
+testing 'SECONDS' 'readonly SECONDS=41; sleep 1; echo $SECONDS' '42\n' '' ''
+# testing 'SECONDS2' 'readonly SECONDS; SECONDS=0; echo $SECONDS' '' '' '' #bash!
+testing 'SECONDS2' 'SECONDS=123+456; echo $SECONDS' '0\n' '' '' #bash!!
+testing '$LINENO 2' $'echo $LINENO\necho $LINENO' '0\n1\n' '' ''
+testing '$EUID' 'echo $EUID' "$(id -u)\n" '' ''
+testing '$UID' 'echo $UID' "$(id -ur)\n" '' ''
+
+testing 'readonly leading assignment' \
+  '{ readonly abc=123;abc=def echo hello; echo $?;} 2>output; grep -o readonly output' \
+  'hello\n0\nreadonly\n' '' ''
+testing 'readonly leading assignment2' \
+  'readonly boink=123; export boink; { boink=234 env | grep ^boink=;} 2>/dev/null; echo $?' 'boink=123\n0\n' '' ''
+testing 'readonly for' \
+  'readonly i; for i in one two three; do echo $i; done 2>/dev/null; echo $?' \
+  '1\n' '' ''
+testing 'readonly {}<' \
+  'readonly i; echo hello 2>/dev/null {i}</dev/null; echo $?' '1\n' '' ''
+testing '$_ 1' 'echo walrus; echo $_' 'walrus\nwalrus\n' '' ''
+testing '$_ 2' 'unset _; echo $_' '_\n' '' ''
+
+# wildcards
+
+touch walrus wallpapers
+testing 'IFS wildcards' \
+  'IFS=xy; ABC=abcywal*sxdef; echo $ABC | tr " " "\n" | sort' \
+  'abc\ndef\nwallpapers\nwalrus\n' '' ''
+rm -f walrus wallpapers
+
+# Force parsing granularity via interactive shxpect because bash parses all
+# of sh -c "str" in one go, meaning the "shopt -s extglob" won't take effect
+shxpect 'IFS +(extglob)' I$'shopt -s extglob\n' E"$P" \
+  I$'IFS=x; ABC=cxd; for i in +($ABC); do echo =$i=; done\n' \
+  O$'=+(c=\n' O$'=d)=\n'
+
+touch abc\)d
+shxpect 'IFS +(extglob) 2' I$'shopt -s extglob\n' E"$P" \
+  I$'ABC="c?d"; for i in ab+($ABC); do echo =$i=; done\n' \
+  O$'=abc)d=\n'
+rm abc\)d
+
+shxpect '[+(]) overlap priority' I$'shopt -s extglob\n' E"$P" \
+  I$'touch "AB[DEF]"; echo AB[+(DEF]) AB[+(DEF)? AB+([DEF)]\n' \
+  O$'AB[+(DEF]) AB[DEF] AB+([DEF)]\n' \
+  I$'X="("; Y=")"; echo AB[+${X}DEF${Y}?\n' O$'AB[DEF]\n'
+
+# TODO: syntax error takes out ': ${a?b}; echo $?' (I.E. never runs echo)
+shxpect '${a?b} sets err, stops cmdline eval' \
+  I$': ${a?b} ${c:=d}\n' E E"$P" I$'echo $?$c\n' O$'1\n'
+
+shxpect 'trace redirect' I$'set -x; echo one\n' E$'+ echo one\n'"$P" O$'one\n' \
+  I$'echo two 2>/dev/null\n' O$'two\n' E$'+ echo two\n'"$P" \
+  I$'{ echo three; } 2>/dev/null\n' O$'three\n' E"$P"
+
+testing 'source file' 'source input' 'hello\n' 'echo hello \\\n' ''
+testing '. file' '. input' 'hello\n' 'echo hello \\\n' ''
+testing 'source no newline' 'source input' 'hello \\\n' 'echo hello \\' ''
+testing 'source is live' \
+  'for i in one two three; do echo "echo $i" > input; source input; done' \
+  'one\ntwo\nthree\n' 'x' ''
+testing 'source is live in functions' \
+  'func() { source input; }; for i in one two three; do echo echo $i > input; func; done' \
+  'one\ntwo\nthree\n' 'x' ''
+testing 'subshell inheritance' \
+  'func() { source input; cat <(echo $xx; xx=456; echo $xx); echo $xx;}; echo local xx=123 > input; func; echo $xx' \
+  '123\n456\n123\n\n' 'x' ''
+testing 'semicolon vs newline' \
+  'source input 2>/dev/null || echo yes' 'one\nyes\n' \
+  'echo one\necho two; echo |' ''
+testing 'syntax err pops to source but encapsulating function continues' \
+  'func() { echo one; source <(echo -e "echo hello\necho |") 2>/dev/null; echo three;}; func; echo four' \
+  'one\nhello\nthree\nfour\n' '' ''
+testing '"exit shell" means exit eval but encapsulating function continues' \
+  'func() { eval "echo one; echo \${?potato}; echo and" 2>/dev/null; echo plus;}; func; echo then' \
+  'one\nplus\nthen\n' '' ''
+
+testing 'functions() {} in same PID' \
+  '{ echo $BASHPID; chicken() { echo $BASHPID;}; chicken;} | sort -u | wc -l' '1\n' '' ''
+testing 'functions() () different PID' \
+  '{ echo $BASHPID; chicken() ( echo $BASHPID;); chicken;} | sort -u | wc -l' '2\n' '' ''
+testing 'function() just wants any block span' \
+  'func() if true; then echo hello; fi; echo one; func; echo two' \
+  'one\nhello\ntwo\n' '' ''
+shxpect 'local creates a whiteout' \
+  I$'func() { local potato; echo ${potato?bang}; }; potato=123; func\n' \
+  E E"$P" I$'echo $?\n' O$'1\n'
+
+# TODO finish variable list from shell init
+
+# $# $? $- $$ $! $0
 # always exported: PWD SHLVL _
 #  ./bash -c 'echo $_' prints $BASH, but PATH search shows path? Hmmm...
 # ro: UID PPID EUID $
@@ -234,3 +583,15 @@
 #HISTCMD - history number
 #
 #TMOUT - used by read
+
+# does not match: ./sh -c 'echo {a..Z}' becomes a ` _ ^ ] \ [ Z
+
+# commit ec6639407b9e
+#-  IS_TOYBOX_RE='(toybox|This is not GNU).*'
+#-  [[ "$IS_TOYBOX" =~ $IS_TOYBOX_RE ]] || SKIPNEXT=1
+#+  case "$IS_TOYBOX" in
+#+    toybox*) ;;
+#+    This\ is\ not\ GNU*) ;;
+#+    *) SKIPNEXT=1 ;;
+#+  esac
+
diff --git a/tests/tar.test b/tests/tar.test
index 985cea5..70668c9 100644
--- a/tests/tar.test
+++ b/tests/tar.test
@@ -59,6 +59,11 @@
 testing "pass group" "tar c --owner root --group nobody --mtime @0 file | LST" \
   "-rw-rw-r-- root/nobody 0 1970-01-01 00:00 file\n" "" ""
 
+# Historically we output a "base 256" format that _we_ could decode but that
+# GNU tar choked on, so check the exact bytes with SUM, not a LST round trip.
+testing "huge values" "tar c --owner 9999999 --group 8888888 --mtime @0 file | SUM 3" \
+  "396b07fd2f80eeb312462e3bfb7dc1325dc6bcfb\n" "" ""
+
 touch -t 198701231234.56 file
 testing "pass mtime" \
   "tar c --owner root --group root file | LST --full-time" \
@@ -105,7 +110,7 @@
 testing "create hardlink to symlink" "$TAR dir/link dir/hlink | SUM 3" \
   "3bc16f8fb6fc8b05f691da8caf989a70ee99284a\n" "" ""
 
-skipnot mkfifo dir/fifo
+skipnot mkfifo dir/fifo 2>/dev/null
 testing "create dir/fifo" "$TAR dir/fifo | SUM 3" \
   "bd1365db6e8ead4c813333f9666994c1899924d9\n" "" ""
 
@@ -148,6 +153,9 @@
 testing "pass /dev/null" \
   "tar c --mtime @0 /dev/null 2>/dev/null | LST" \
   "crw-rw-rw- root/root 1,3 1970-01-01 00:00 dev/null\n" "" ""
+testing "--absolute-names" \
+  "tar c --mtime @0 --absolute-names /dev/null 2>/dev/null | LST" \
+  "crw-rw-rw- root/root 1,3 1970-01-01 00:00 /dev/null\n" "" ""
 
 # compression types
 testing "autodetect gzip" 'LST -f "$FILES"/tar/tar.tgz' \
@@ -158,15 +166,22 @@
   "drwxr-x--- enh/eng 0 2017-05-13 01:05 dir/\n-rw-r----- enh/eng 12 2017-05-13 01:05 dir/file\n" \
   "" ""
 
-skipnot mknod dir/char c 12 34
+# -I
+testing "-I gzip c" "$TAR -Igzip file | file - | grep -o 'gzip compressed'" \
+  "gzip compressed\n" "" ""
+testing "-I gzip t" 'LST -Igzip -f "$FILES"/tar/tar.tgz' \
+  "drwxr-x--- enh/eng 0 2017-05-13 01:05 dir/\n-rw-r----- enh/eng 12 2017-05-13 01:05 dir/file\n" \
+  "" ""
+
+skipnot mknod dir/char c 12 34 2>/dev/null
 testing "character special" "tar --mtime @0 -cf test.tar dir/char && rm -f dir/char && tar xf test.tar && ls -l dir/char" \
   "crw-rw---- 1 root root 12,  34 1970-01-01 00:00 dir/char\n" "" ""
 
-skipnot mknod dir/block b 23 45
+skipnot mknod dir/block b 23 45 2>/dev/null
 testing "block special" "tar --mtime @0 -cf test.tar dir/block && rm -f dir/block && tar xf test.tar && ls -l dir/block" \
   "brw-rw---- 1 root root 23,  45 1970-01-01 00:00 dir/block\n" "" ""
 
-skipnot chown nobody dir/file
+skipnot chown nobody dir/file 2>/dev/null
 testing "ownership" "$TAR dir/file | SUM 3" \
   "2d7b96c7025987215f5a41f10eaa84311160afdb\n" "" ""
 
@@ -244,6 +259,32 @@
   "791060574c569e5c059e2b90c1961a3575898f97\n" "" ""
 rm fweep2 fweep2.tar
 
+testcmd "longname" "tf $FILES/tar/long_path.tar" \
+  "$(printf 'long file name%86cTRAILING' ' ' | tr ' ' _)\n" "" ""
+
+mkdir -p links
+touch links/orig
+ln links/{orig,link1}
+ln links/{orig,link2}
+testcmd 'links' '-cf test.tar links' '' '' ''
+rm -rf links
+
+mkdir links
+for i in {0..12}; do touch links/orig$i; ln links/{orig,link}$i; done
+testcmd 'links2' '-cf test.tar links' '' '' ''
+rm -rf links
+
+install -m 000 -d folder/skip/oof &&
+testcmd 'exclude' '--exclude skip -cvf tar.tar folder && echo yes' \
+  'folder/\nyes\n' '' ''
+rm -rf folder tar.tar
+
+mkdir -p one/two; echo hello > one/two/three; tar czf test.tar one/two/three
+rm one/two/three; mkdir one/two/three
+testcmd 'replace dir with file' '-xf test.tar && cat one/two/three' \
+  'hello\n' '' ''
+rm -rf one test.tar
+
 if false
 then
 
diff --git a/tests/test.test b/tests/test.test
index 7f574f0..1295be4 100644
--- a/tests/test.test
+++ b/tests/test.test
@@ -42,7 +42,32 @@
 rm f L s p
 rmdir d
 
-# TODO: Test rwx gu t
+# test -rwx each bit position and failure
+touch walrus
+MASK=111
+for i in x w r k g u; do
+  [ $i == k ] && MASK=1000
+  # test everything off produces "off"
+  chmod 000 walrus
+  testcmd "-$i 0" "-$i walrus || echo yes" "yes\n" "" ""
+  chmod $((7777-$MASK)) walrus
+  testcmd "-$i inverted" "-$i walrus || echo yes" "yes\n" "" ""
+  MASK=$(($MASK<<1))
+done
+unset MASK
+# Test setuid setgid sticky enabled
+for i in uu+s gg+s k+t; do
+  chmod 000 walrus
+  chmod ${i:1}+s walrus
+  testcmd "-${i:0:1}" "-${i:0:1} walrus && echo yes" "yes\n" "" ""
+done
+# test each ugo+rwx bit position individually
+for i in 1 10 100; do for j in x w r; do
+  chmod $i walrus
+  testcmd "-$j $i" "-$j walrus && echo yes" "yes\n" "" ""
+  i=$((i<<1))
+done; done
+rm -f walrus
 
 testcmd "" "'' || echo yes" "yes\n" "" ""
 testcmd "" "a && echo yes" "yes\n" "" ""
diff --git a/tests/touch.test b/tests/touch.test
index 8be231b..6d131d7 100644
--- a/tests/touch.test
+++ b/tests/touch.test
@@ -35,7 +35,7 @@
 testing "-t -" "TZ=utc touch -t 200109090146.40 - > walrus && TZ=utc date -r walrus +%s" \
   "1000000000\n" "" ""
 
-SKIP_HOST=1 testing "-t nanoseconds" \
+toyonly testing "-t nanoseconds" \
   "touch -t 201201231234.56123456789 walrus && date -r walrus +%Y%m%d-%H%M%S.%N" \
   "20120123-123456.123456789\n" "" ""
 
diff --git a/tests/tr.test b/tests/tr.test
new file mode 100755
index 0000000..bb00a3f
--- /dev/null
+++ b/tests/tr.test
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+testing "" "tr 1 2" "223223223" "" "123123123"
+testing "-d" "tr -d 1" "232323" "" "123123123"
+testing "-s" "tr -s 1" "12223331222333" "" "111222333111222333"
+
+testing "no pathological flushing" "seq 10000000 | tr 1 2 > /dev/null" "" "" ""
diff --git a/tests/unicode.test b/tests/unicode.test
new file mode 100755
index 0000000..099231d
--- /dev/null
+++ b/tests/unicode.test
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+testing "text" "unicode 안녕 hi" "U+C548 : 안 : 0xec 0x95 0x88\nU+B155 : 녕 : 0xeb 0x85 0x95\nU+0068 : h\nU+0069 : i\n" "" ""
+testing "code points" "unicode 70 666" "U+0070 : p\nU+0666 : ٦ : 0xd9 0xa6\n" "" ""
+testing "ASCII controls" "unicode 0" "U+0000 : NUL\n" "" ""
+testing "del" "unicode 7f" "U+007F : DEL\n" "" ""
+testing "3-byte" "unicode 30b9" "U+30B9 : ス : 0xe3 0x82 0xb9\n" "" ""
+testing "4-byte" "unicode 10000" "U+10000 : 𐀀 : 0xf0 0x90 0x80 0x80\n" "" ""
+testing "range" "unicode 660-662" "U+0660 : ٠ : 0xd9 0xa0\nU+0661 : ١ : 0xd9 0xa1\nU+0662 : ٢ : 0xd9 0xa2\n" "" ""
diff --git a/tests/xargs.test b/tests/xargs.test
index afed8a1..dc3c7b3 100644
--- a/tests/xargs.test
+++ b/tests/xargs.test
@@ -61,6 +61,21 @@
   'for i in $(seq 1 100);do echo $X;done|{ xargs >/dev/null && echo yes;}' \
   "yes\n" "" ""
 
+# -P max-proc
+testing "max-proc=1" "xargs -n 1 -P 1" "one\ntwo\nthree\n" "" "one\ntwo\nthree\n"
+testing "max-proc=2" "xargs -n 1 -P 2" "y\ny\ny\n" "" "y\ny\ny\n"
+testing "max-proc=3" "xargs -n 1 -P 3" "y\ny\ny\n" "" "y\ny\ny\n"
+# 3 seconds vs 2 seconds vs 1 second parallel sleep
+testing "parallel sleep" "
+  xargs_sleep() {
+    ( yes | head -3 | tr y 1 | time -p xargs -n 1 \${*} sleep ) 2>&1 |
+      sed -n 's/^real *\([0-9]*\)[.]\(.*\)/\1\2/p'
+  } &&
+  A=\`xargs_sleep\` &&
+  B=\`xargs_sleep -P 2\` &&
+  C=\`xargs_sleep -P 0\` &&
+  [ \${A} -gt \${B} -a \${B} -gt \${C} ] && echo OK || echo FAIL" "OK\n" "" ""
+
 # TODO: what exactly is -x supposed to do? why does coreutils output "one"?
 #testing "-x" "xargs -x -s 9 || echo expected" "one\nexpected\n" "" "one\ntwo\nthree"
 
diff --git a/tests/xxd.test b/tests/xxd.test
index 48ed319..e9ec81e 100755
--- a/tests/xxd.test
+++ b/tests/xxd.test
@@ -33,7 +33,7 @@
 testing "-s" "xxd -s 13 file1" "0000000d: 7465 7874 0a                             text.\n" "" ""
 
 testing "-r" "echo -e '    00000000: 7468 6973 2069 7320 736f 6d65 2074 6578  this is some tex\n00000010: 740a                                     t.' | xxd -r" "this is some text\n" "" ""
-SKIP_HOST=1 testing "-r -i" "echo -e '0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65,\n  0x20, 0x74, 0x65, 0x78, 0x74, 0x0a' | xxd -ri" "this is some text\n" "" ""
+toyonly testing "-r -i" "echo -e '0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65,\n  0x20, 0x74, 0x65, 0x78, 0x74, 0x0a' | xxd -ri" "this is some text\n" "" ""
 testing "-r -p" "echo 7468697320697320736f6d6520746578740a | xxd -r -p" "this is some text\n" "" ""
 
 testing "-r garbage" "echo '0000: 68 65 6c6c 6fxxxx' | xxd -r -" "hello" "" ""
diff --git a/toybox-tests.xml b/toybox-tests.xml
deleted file mode 100644
index 25bdf42..0000000
--- a/toybox-tests.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<configuration description="Config for running toybox-tests through Atest or in Infra">
-    <option name="test-suite-tag" value="toybox-tests" />
-    <!-- Several of these tests assume they're run as root. -->
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-    <!-- This test requires a device, so it's not annotated with a null-device. -->
-    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
-        <option name="binary" value="run-tests-on-android.sh" />
-        <!-- Toybox tests script assumes a relative path with the tests/ folders. -->
-        <option name="relative-path-execution" value="true" />
-        <!-- Tests shouldn't be that long but set 15m to be safe. -->
-        <option name="per-binary-timeout" value="15m" />
-    </test>
-</configuration>
diff --git a/toys.h b/toys.h
index eb9208e..ead39b5 100644
--- a/toys.h
+++ b/toys.h
@@ -58,6 +58,7 @@
 
 // Internationalization support (also in POSIX and LSB)
 
+#include <langinfo.h>
 #include <locale.h>
 #include <wchar.h>
 #include <wctype.h>
@@ -82,6 +83,7 @@
 
 struct toy_list *toy_find(char *name);
 void toy_init(struct toy_list *which, char *argv[]);
+void toy_singleinit(struct toy_list *which, char *argv[]);
 void toy_exec(char *argv[]);
 
 // Array of available commands
@@ -101,23 +103,23 @@
   char **optargs;          // Arguments left over from get_optflags()
   unsigned long long optflags; // Command line option flags from get_optflags()
   int optc;                // Count of optargs
-  int envc;                // Count of original environ entries
-  int old_umask;           // Old umask preserved by TOYFLAG_UMASK
   short toycount;          // Total number of commands in this build
-  short signal;            // generic_signal() records what signal it saw here
-  int signalfd;            // and writes signal to this fd, if set
   char exitval;            // Value error_exit feeds to exit()
   char wasroot;            // dropped setuid
 
-  // This is at the end so toy_init() doesn't zero it.
+  // toy_init() should not zero past here.
   sigjmp_buf *rebound;     // siglongjmp here instead of exit when do_rebound
   struct arg_list *xexit;  // atexit() functions for xexit(), set by sigatexit()
   void *stacktop;          // nested toy_exec() call count, or 0 if vforked
+  int envc;                // Count of original environ entries
+  int old_umask;           // Old umask preserved by TOYFLAG_UMASK
+  short signal;            // generic_signal() records what signal it saw here
+  int signalfd;            // and writes signal to this fd, if set
 } toys;
 
 // Two big temporary buffers: one for use by commands, one for library functions
 
-extern char toybuf[4096], libbuf[4096];
+extern char *toybox_version, toybuf[4096], libbuf[4096];
 
 extern char **environ;
 
@@ -131,5 +133,5 @@
 #ifndef TOYBOX_VENDOR
 #define TOYBOX_VENDOR ""
 #endif
-#define TOYBOX_VERSION "0.8.3"TOYBOX_VENDOR
+#define TOYBOX_VERSION "0.8.4"TOYBOX_VENDOR
 #endif
diff --git a/toys/example/demo_number.c b/toys/example/demo_number.c
index 010320c..42b62b5 100644
--- a/toys/example/demo_number.c
+++ b/toys/example/demo_number.c
@@ -2,14 +2,17 @@
  *
  * Copyright 2015 Rob Landley <rob@landley.net>
 
-USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3hdbs", TOYFLAG_BIN))
+USE_DEMO_NUMBER(NEWTOY(demo_number, "D#=3<3M#<0hcdbs", TOYFLAG_BIN))
 
 config DEMO_NUMBER
   bool "demo_number"
   default n
   help
-    usage: demo_number [-hsbi] NUMBER...
+    usage: demo_number [-hsbi] [-D LEN] NUMBER...
 
+    -D	output field is LEN chars
+    -M	input units (index into bkmgtpe)
+    -c	Comma comma down do be do down down
     -b	Use "B" for single byte units (HR_B)
     -d	Decimal units
     -h	Human readable
@@ -20,7 +23,7 @@
 #include "toys.h"
 
 GLOBALS(
-  long D;
+  long M, D;
 )
 
 void demo_number_main(void)
@@ -31,7 +34,7 @@
     long long ll = atolx(*arg);
 
     if (toys.optflags) {
-      human_readable_long(toybuf, ll, TT.D, toys.optflags);
+      human_readable_long(toybuf, ll, TT.D, TT.M, toys.optflags);
       xputs(toybuf);
     } else printf("%lld\n", ll);
   }
diff --git a/toys/example/demo_utf8towc.c b/toys/example/demo_utf8towc.c
index 1ea8eda..c052254 100644
--- a/toys/example/demo_utf8towc.c
+++ b/toys/example/demo_utf8towc.c
@@ -22,8 +22,6 @@
   unsigned u, h;
   wchar_t wc1, wc2;
 
-  setlocale(LC_ALL, "en_US.UTF-8");
-
   memset(&mb, 0, sizeof(mb));
   for (u=1; u; u++) {
     char *str = (void *)&h;
diff --git a/toys/example/hello.c b/toys/example/hello.c
index 4cd5d13..3e68f21 100644
--- a/toys/example/hello.c
+++ b/toys/example/hello.c
@@ -5,6 +5,7 @@
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/
  * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cmdbehav.html
  * See https://www.ietf.org/rfc/rfc3.txt
+ * See http://man7.org/linux/man-pages/dir_section_1.html
 
 USE_HELLO(NEWTOY(hello, 0, TOYFLAG_USR|TOYFLAG_BIN))
 
diff --git a/toys/example/skeleton.c b/toys/example/skeleton.c
index 1796ba1..a22bc90 100644
--- a/toys/example/skeleton.c
+++ b/toys/example/skeleton.c
@@ -6,6 +6,7 @@
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/
  * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cmdbehav.html
  * See https://www.ietf.org/rfc/rfc3.txt
+ * See http://man7.org/linux/man-pages/dir_section_1.html
 
 // Accept many different kinds of command line argument (see top of lib/args.c)
 // Demonstrate two commands in the same file (see www/documentation.html)
diff --git a/toys/lsb/dmesg.c b/toys/lsb/dmesg.c
index a55dabf..829fcd5 100644
--- a/toys/lsb/dmesg.c
+++ b/toys/lsb/dmesg.c
@@ -69,15 +69,15 @@
   subsystem = p ? (p-text) : 0;
 
   // To get "raw" output for /dev/kmsg we need to add priority to each line
-  if (toys.optflags&FLAG_r) {
+  if (FLAG(r)) {
     color(0);
     printf("<%d>", facpri);
   }
 
   // Format the time.
-  if (!(toys.optflags&FLAG_t)) {
+  if (!FLAG(t)) {
     color(32);
-    if (toys.optflags&FLAG_T) {
+    if (FLAG(T)) {
       time_t t = TT.tea+time_s;
       char *ts = ctime(&t);
 
@@ -115,23 +115,23 @@
 
   if (TT.use_color) sigatexit(dmesg_cleanup);
   // If we're displaying output, is it klogctl or /dev/kmsg?
-  if (toys.optflags & (FLAG_C|FLAG_n)) goto no_output;
+  if (FLAG(C)||FLAG(n)) goto no_output;
 
-  if (toys.optflags&FLAG_T) {
+  if (FLAG(T)) {
     struct sysinfo info;
 
     sysinfo(&info);
     TT.tea = time(0)-info.uptime;
   }
 
-  if (!(toys.optflags&FLAG_S)) {
+  if (!FLAG(S)) {
     char msg[8193]; // CONSOLE_EXT_LOG_MAX+1
     ssize_t len;
     int fd;
 
     // Each read returns one message. By default, we block when there are no
     // more messages (--follow); O_NONBLOCK is needed for for usual behavior.
-    fd = open("/dev/kmsg", O_RDONLY|(O_NONBLOCK*!(toys.optflags&FLAG_w)));
+    fd = open("/dev/kmsg", O_RDONLY|(O_NONBLOCK*!FLAG(w)));
     if (fd == -1) goto klogctl_mode;
 
     // SYSLOG_ACTION_CLEAR(5) doesn't actually remove anything from /dev/kmsg,
@@ -158,7 +158,7 @@
     // Figure out how much data we need, and fetch it.
     if (!(size = TT.s)) size = xklogctl(10, 0, 0);
     data = from = xmalloc(size+1);
-    data[size = xklogctl(3+(toys.optflags&FLAG_c), data, size)] = 0;
+    data[size = xklogctl(3+FLAG(c), data, size)] = 0;
 
     // Send each line to format_message.
     to = data + size;
@@ -174,8 +174,8 @@
 
 no_output:
   // Set the log level?
-  if (toys.optflags & FLAG_n) xklogctl(8, 0, TT.n);
+  if (FLAG(n)) xklogctl(8, 0, TT.n);
 
   // Clear the buffer?
-  if (toys.optflags & (FLAG_C|FLAG_c)) xklogctl(5, 0, 0);
+  if (FLAG(C)||FLAG(c)) xklogctl(5, 0, 0);
 }
diff --git a/toys/lsb/md5sum.c b/toys/lsb/md5sum.c
index ff7e8b4..da9b14b 100644
--- a/toys/lsb/md5sum.c
+++ b/toys/lsb/md5sum.c
@@ -92,21 +92,19 @@
 GLOBALS(
   int sawline;
 
+  unsigned *md5table;
   // Crypto variables blanked after summing
-  unsigned state[5];
-  unsigned oldstate[5];
-  uint64_t count;
+  unsigned state[5], oldstate[5];
+  unsigned long long count;
   union {
     char c[64];
     unsigned i[16];
   } buffer;
 )
 
-// for(i=0; i<64; i++) md5table[i] = abs(sin(i+1))*(1<<32);  But calculating
-// that involves not just floating point but pulling in -lm (and arguing with
-// C about whether 1<<32 is a valid thing to do on 32 bit platforms) so:
-
-static uint32_t md5table[64] = {
+// Static table for when we haven't got floating point support
+#if ! CFG_TOYBOX_FLOAT
+static unsigned md5nofloat[64] = {
   0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
   0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
   0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
@@ -119,6 +117,9 @@
   0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
   0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
 };
+#else
+#define md5nofloat 0
+#endif
 
 // Mix next 64 bytes of data into md5 hash
 
@@ -156,7 +157,7 @@
       rot = (5*rot)+(((rot+2)&2)>>1);
       temp = x[(a+2)&3] ^ (x[(a+1)&3] | ~x[(a+3)&3]);
     }
-    temp += x[a] + b[in] + md5table[i];
+    temp += x[a] + b[in] + TT.md5table[i];
     x[a] = x[(a+1)&3] + ((temp<<rot) | (temp>>(32-rot)));
   }
   for (i=0; i<4; i++) TT.state[i] += x[i];
@@ -286,9 +287,9 @@
 
 static void do_builtin_hash(int fd, char *name)
 {
-  uint64_t count;
-  int i, sha1=toys.which->name[0]=='s';
-  char buf;
+  unsigned long long count;
+  int i, sha1 = toys.which->name[0]=='s';
+  char buf, *pp;
   void (*transform)(void);
 
   /* SHA1 initialization constants  (md5sum uses first 4) */
@@ -329,7 +330,10 @@
   else for (i=0; i<4; i++) sprintf(toybuf+8*i, "%08x", bswap_32(TT.state[i]));
 
   // Wipe variables. Cryptographer paranoia.
-  memset(TT.state, 0, sizeof(TT)-((long)TT.state-(long)&TT));
+  // if we do this with memset(), gcc throws a broken warning, and the (long)
+  // typecasts stop gcc from breaking "undefined behavior" that isn't.
+  for (pp = (void *)TT.state; (unsigned long)pp-(unsigned long)TT.state<sizeof(TT)-((unsigned long)TT.state-(unsigned long)&TT); pp++)
+    *pp = 0;
   i = strlen(toybuf)+1;
   memset(toybuf+i, 0, sizeof(toybuf)-i);
 }
@@ -341,7 +345,7 @@
   else do_builtin_hash(fd, name);
 
   if (name)
-    printf((toys.optflags & FLAG_b) ? "%s\n" : "%s  %s\n", toybuf, name);
+    printf(FLAG(b) ? "%s\n" : "%s  %s\n", toybuf, name);
 }
 
 static int do_c_line(char *line)
@@ -402,6 +406,16 @@
 void md5sum_main(void)
 {
   char **arg;
+  int i;
+
+  // Calculate table if we have floating point. Static version should drop
+  // out at compile time when we don't need it.
+  if (!CFG_TOYBOX_LIBCRYPTO && toys.which->name[0]=='m') {
+    if (CFG_TOYBOX_FLOAT) {
+      TT.md5table = xmalloc(64*4);
+      for (i = 0; i<64; i++) TT.md5table[i] = fabs(sin(i+1))*(1LL<<32);
+    } else TT.md5table = md5nofloat;
+  }
 
   if (FLAG(c)) for (arg = toys.optargs; *arg; arg++) do_c_file(*arg);
   else {
diff --git a/toys/lsb/mount.c b/toys/lsb/mount.c
index 6b1dfa4..8ff084c 100644
--- a/toys/lsb/mount.c
+++ b/toys/lsb/mount.c
@@ -38,7 +38,7 @@
 
 #config SMBMOUNT
 #  bool "smbmount"
-#  deault n
+#  default n
 #  helo
 #    usage: smbmount SHARE DIR
 #
diff --git a/toys/lsb/seq.c b/toys/lsb/seq.c
index 988466b..7a931c6 100644
--- a/toys/lsb/seq.c
+++ b/toys/lsb/seq.c
@@ -28,7 +28,7 @@
 GLOBALS(
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 )
 
 // Ensure there's one %f escape with correct attributes
@@ -37,10 +37,8 @@
   char *s = next_printf(f, 0);
 
   if (!s) error_exit("bad -f no %%f");
-  if (-1 == stridx("aAeEfFgG", *s) || (s = next_printf(s, 0))) {
-    // The @ is a byte offset, not utf8 chars. Waiting for somebody to complain.
+  if (-1 == stridx("aAeEfFgG", *s) || (s = next_printf(s, 0)))
     error_exit("bad -f '%s'@%d", f, (int)(s-f+1));
-  }
 }
 
 // Parse a numeric argument setting *prec to the precision of this argument.
@@ -54,11 +52,39 @@
   return xstrtod(s);
 }
 
+// fast integer conversion to decimal string
+// TODO move to lib?
+static char *itoa(char *s, int i)
+{
+  char buf[16], *ff = buf;
+  unsigned n = i;
+
+  if (i<0) {
+    *s++ = '-';
+    n = -i;
+  }
+  do *ff++ = '0'+n%10; while ((n /= 10));
+  do *s++ = *--ff; while (ff>buf);
+  *s++ = '\n';
+
+  return s;
+}
+
+static char *flush_toybuf(char *ss)
+{
+  if (ss-toybuf<TT.buflen) return ss;
+  xwrite(1, toybuf, ss-toybuf); 
+
+  return toybuf;
+}
+
 void seq_main(void)
 {
+  char fbuf[32], *ss;
   double first = 1, increment = 1, last, dd;
-  int i;
+  int ii, inc = 1, len, slen;
 
+  // parse arguments
   if (!TT.s) TT.s = "\n";
   switch (toys.optc) {
     case 3: increment = parsef(toys.optargs[1]);
@@ -66,19 +92,32 @@
     default: last = parsef(toys.optargs[toys.optc-1]);
   }
 
-  // Prepare format string with appropriate precision. Can't use %g because 1e6
-  if (toys.optflags & FLAG_f) insanitize(TT.f);
-  else sprintf(TT.f = toybuf, "%%.%df", TT.precision);
+  // measure arguments
+  if (FLAG(f)) insanitize(TT.f);
+  for (ii = len = 0; ii<3; ii++) {
+    dd = (double []){first, increment, last}[ii];
+    len = maxof(len, snprintf(0, 0, "%.*f", TT.precision, fabs(dd)));
+    if (ii == 2) dd += increment;
+    slen = dd;
+    if (dd != slen) inc = 0;
+  }
+  if (!FLAG(f)) sprintf(TT.f = fbuf, "%%0%d.%df", len, TT.precision);
+  TT.buflen = sizeof(toybuf) - 32 - len - TT.precision - strlen(TT.s);
+  if (TT.buflen<0) error_exit("bad -s");
 
-  // Pad to largest width
-  if (toys.optflags & FLAG_w) {
-    int len = 0;
+  // fast path: when everything fits in an int with no flags.
+  if (!toys.optflags && inc) {
+    ii = first;
+    len = last;
+    inc = increment;
+    ss = toybuf;
+    if (inc>0) for (; ii<=len; ii += inc)
+      ss = flush_toybuf(itoa(ss, ii));
+    else if (inc<0) for (; ii>=len; ii += inc)
+      ss = flush_toybuf(itoa(ss, ii));
+    if (ss != toybuf) xwrite(1, toybuf, ss-toybuf);
 
-    for (i=0; i<3; i++) {
-      dd = (double []){first, increment, last}[i];
-      len = maxof(len, snprintf(0, 0, TT.f, dd));
-    }
-    sprintf(TT.f = toybuf, "%%0%d.%df", len, TT.precision);
+    return;
   }
 
   // Other implementations output nothing if increment is 0 and first > last,
@@ -86,14 +125,14 @@
   // nothing for all three, if you want endless output use "yes".
   if (!increment) return;
 
-  i = 0;
-  for (;;) {
+  // Slow path, floating point and fancy sprintf() patterns
+  for (ii = 0, ss = toybuf;; ii++) {
     // Multiply to avoid accumulating rounding errors from increment.
-    dd = first+i*increment;
+    dd = first+ii*increment;
     if ((increment<0 && dd<last) || (increment>0 && dd>last)) break;
-    if (i++) printf("%s", TT.s);
-    printf(TT.f, dd);
+    if (ii) ss = flush_toybuf(stpcpy(ss, TT.s));
+    ss += sprintf(ss, TT.f, dd);
   }
-
-  if (i) printf("\n");
+  *ss++ = '\n';
+  xwrite(1, toybuf, ss-toybuf);
 }
diff --git a/toys/lsb/su.c b/toys/lsb/su.c
index 3d6fd57..8083630 100644
--- a/toys/lsb/su.c
+++ b/toys/lsb/su.c
@@ -20,7 +20,7 @@
 
     With one argument, switch to USER and run user's shell from /etc/passwd.
     With no arguments, USER is root. If COMMAND line provided after USER,
-    exec() it as new USER (bypasing shell). If -u or -g specified, first
+    exec() it as new USER (bypassing shell). If -u or -g specified, first
     argument (if any) isn't USER (it's COMMAND).
 
     first argument is USER name to switch to (which must exist).
diff --git a/toys/net/microcom.c b/toys/net/microcom.c
index 6f88ad4..963445c 100644
--- a/toys/net/microcom.c
+++ b/toys/net/microcom.c
@@ -2,7 +2,7 @@
  *
  * Copyright 2017 The Android Open Source Project.
 
-USE_MICROCOM(NEWTOY(microcom, "<1>1s:X", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MICROCOM(NEWTOY(microcom, "<1>1s#=115200X", TOYFLAG_USR|TOYFLAG_BIN))
 
 config MICROCOM
   bool "microcom"
@@ -12,7 +12,7 @@
 
     Simple serial console.
 
-    -s	Set baud rate to SPEED
+    -s	Set baud rate to SPEED (default 115200)
     -X	Ignore ^@ (send break) and ^] (exit)
 */
 
@@ -20,63 +20,62 @@
 #include "toys.h"
 
 GLOBALS(
-  char *s;
+  long s;
 
-  int fd;
-  struct termios original_stdin_state, original_fd_state;
+  int fd, stok;
+  struct termios old_stdin, old_fd;
 )
 
 // TODO: tty_sigreset outputs ansi escape sequences, how to disable?
 static void restore_states(int i)
 {
-  tcsetattr(0, TCSAFLUSH, &TT.original_stdin_state);
-  tcsetattr(TT.fd, TCSAFLUSH, &TT.original_fd_state);
+  if (TT.stok) tcsetattr(0, TCSAFLUSH, &TT.old_stdin);
+  tcsetattr(TT.fd, TCSAFLUSH, &TT.old_fd);
 }
 
 void microcom_main(void)
 {
+  struct termios tio;
   struct pollfd fds[2];
-  int i, speed;
-
-  if (!TT.s) speed = 115200;
-  else speed = atoi(TT.s);
+  int i;
 
   // Open with O_NDELAY, but switch back to blocking for reads.
   TT.fd = xopen(*toys.optargs, O_RDWR | O_NOCTTY | O_NDELAY);
-  if (-1==(i = fcntl(TT.fd, F_GETFL, 0)) || fcntl(TT.fd, F_SETFL, i&~O_NDELAY))
+  if (-1==(i = fcntl(TT.fd, F_GETFL, 0)) || fcntl(TT.fd, F_SETFL, i&~O_NDELAY)
+      || tcgetattr(TT.fd, &TT.old_fd))
     perror_exit_raw(*toys.optargs);
 
   // Set both input and output to raw mode.
-  xset_terminal(TT.fd, 1, speed, &TT.original_fd_state);
-  set_terminal(0, 1, 0, &TT.original_stdin_state);
+  memcpy(&tio, &TT.old_fd, sizeof(struct termios));
+  cfmakeraw(&tio);
+  xsetspeed(&tio, TT.s);
+  if (tcsetattr(TT.fd, TCSAFLUSH, &tio)) perror_exit("set speed");
+  if (!set_terminal(0, 1, 0, &TT.old_stdin)) TT.stok++;
   // ...and arrange to restore things, however we may exit.
   sigatexit(restore_states);
 
   fds[0].fd = TT.fd;
-  fds[0].events = POLLIN;
   fds[1].fd = 0;
-  fds[1].events = POLLIN;
+  fds[0].events = fds[1].events = POLLIN;
 
   while (poll(fds, 2, -1) > 0) {
-    char buf[BUFSIZ];
 
     // Read from connection, write to stdout.
     if (fds[0].revents) {
-      ssize_t n = read(TT.fd, buf, sizeof(buf));
-      if (n > 0) xwrite(0, buf, n);
+      if (0 < (i = read(TT.fd, toybuf, sizeof(toybuf)))) xwrite(0, toybuf, i);
       else break;
     }
 
     // Read from stdin, write to connection.
     if (fds[1].revents) {
-      if (read(0, buf, 1) != 1) break;
-      if (!(toys.optflags & FLAG_X)) {
-        if (!*buf) {
+      if (read(0, toybuf, 1) != 1) break;
+      if (!FLAG(X)) {
+        if (!*toybuf) {
           tcsendbreak(TT.fd, 0);
           continue;
-        } else if (*buf == (']'-'@')) break;
+        } else if (*toybuf == (']'-'@')) break;
       }
-      xwrite(TT.fd, buf, 1);
+      xwrite(TT.fd, toybuf, 1);
     }
   }
 }
diff --git a/toys/net/netcat.c b/toys/net/netcat.c
index 1729bd5..2d80a18 100644
--- a/toys/net/netcat.c
+++ b/toys/net/netcat.c
@@ -7,13 +7,13 @@
  * netcat -L zombies
 
 USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN))
-USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
+USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tElL")"w#<1W#<1p#<1>65535q#<1s:f:46uU"USE_NETCAT_LISTEN("[!tlL][!Lw]")"[!46U]", TOYFLAG_BIN))
 
 config NETCAT
   bool "netcat"
   default y
   help
-    usage: netcat [-46U] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME}
+    usage: netcat [-46U] [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME|COMMAND...}
 
     Forward stdin/stdout to a file or network connection.
 
@@ -36,19 +36,20 @@
   default y
   depends on NETCAT
   help
-    usage: netcat [-t] [-lL COMMAND...]
+    usage: netcat [-tElL]
 
-    -l	Listen for one incoming connection
-    -L	Listen for multiple incoming connections (server mode)
-    -t	Allocate tty (must come before -l or -L)
+    -l	Listen for one incoming connection, then exit
+    -L	Listen and background each incoming connection (server mode)
+    -t	Allocate tty
+    -E	Forward stderr
 
-    The command line after -l or -L is executed (as a child process) to handle
-    each incoming connection. If blank -l waits for a connection and forwards
-    it to stdin/stdout. If no -p specified, -l prints port it bound to and
+    When listening the COMMAND line is executed as a child process to handle
+    an incoming connection. With no COMMAND -l forwards the connection
+    to stdin/stdout. If no -p specified, -l prints the port it bound to and
     backgrounds itself (returning immediately).
 
     For a quick-and-dirty server, try something like:
-    netcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l
+    netcat -s 127.0.0.1 -p 1234 -tL sh -l
 */
 
 #define FOR_netcat
@@ -101,6 +102,7 @@
   TT.W = TT.W ? TT.W*1000 : -1;
   TT.q = TT.q ? TT.q*1000 : -1;
 
+  xsignal(SIGCHLD, SIG_IGN);
   set_alarm(TT.w);
 
   // The argument parsing logic can't make "<2" conditional on other
@@ -178,9 +180,10 @@
             close(in1);
             continue;
           }
+          close(sockfd);
           dup2(in1, 0);
           dup2(in1, 1);
-          if (FLAG(L)) dup2(in1, 2);
+          if (FLAG(E)) dup2(in1, 2);
           if (in1>2) close(in1);
           xexec(toys.optargs);
         }
diff --git a/toys/net/netstat.c b/toys/net/netstat.c
index 16907e2..24a2cee 100644
--- a/toys/net/netstat.c
+++ b/toys/net/netstat.c
@@ -34,40 +34,27 @@
 GLOBALS(
   struct num_cache *inodes;
   int wpad;
-);
+)
 
-// convert address into text format.
 static void addr2str(int af, void *addr, unsigned port, char *buf, int len,
   char *proto)
 {
+  char pres[INET6_ADDRSTRLEN];
+  struct servent *se = 0;
   int pos, count;
-  struct servent *ser = 0;
 
-  // Convert to numeric address
-  if (!inet_ntop(af, addr, buf, 256)) {
-    *buf = 0;
+  if (!inet_ntop(af, addr, pres, sizeof(pres))) perror_exit("inet_ntop");
 
-    return;
-  }
-  buf[len] = 0;
-  pos = strlen(buf);
-
-  // If there's no port number, it's a local :* binding, nothing to look up.
-  if (!port) {
-    if (len-pos<2) pos = len-2;
-    strcpy(buf+pos, ":*");
-
-    return;
-  }
-
-  if (!(toys.optflags & FLAG_n)) {
+  if (FLAG(n) || !port) {
+    strcpy(buf, pres);
+  } else {
     struct addrinfo hints, *result, *rp;
     char cut[4];
 
     memset(&hints, 0, sizeof(struct addrinfo));
     hints.ai_family = af;
 
-    if (!getaddrinfo(buf, NULL, &hints, &result)) {
+    if (!getaddrinfo(pres, NULL, &hints, &result)) {
       socklen_t sock_len = (af == AF_INET) ? sizeof(struct sockaddr_in)
         : sizeof(struct sockaddr_in6);
 
@@ -76,28 +63,31 @@
         if (!getnameinfo(rp->ai_addr, sock_len, buf, 256, 0, 0, 0)) break;
       freeaddrinfo(result);
       buf[len] = 0;
-      pos = strlen(buf);
     }
 
-    // Doesn't understand proto "tcp6", so truncate
+    // getservbyport() doesn't understand proto "tcp6", so truncate
     memcpy(cut, proto, 3);
     cut[3] = 0;
-    ser = getservbyport(htons(port), cut);
+    se = getservbyport(htons(port), cut);
   }
 
-  // Append :service
-  count = snprintf(0, 0, ":%u", port);
-  if (ser) {
-    count = snprintf(0, 0, ":%s", ser->s_name);
-    // sheer paranoia
+  if (!strcmp(buf, "::")) strcpy(buf, "[::]");
+
+  // Append :service or :* if port == 0.
+  if (se) {
+    count = snprintf(0, 0, ":%s", se->s_name);
+    // NI_MAXSERV == 32, which is greater than our minimum field width.
+    // (Although the longest service name on Debian in 2021 is only 16 bytes.)
     if (count>=len) {
       count = len-1;
-      ser->s_name[count] = 0;
+      se->s_name[count] = 0;
     }
-  }
+  } else count = port ? snprintf(0, 0, ":%u", port) : 2;
+  // We always show the port, even if that means clobbering the end of the host.
+  pos = strlen(buf);
   if (len-pos<count) pos = len-count;
-  if (ser) sprintf(buf+pos, ":%s", ser->s_name);
-  else sprintf(buf+pos, ":%u", port);
+  if (se) sprintf(buf+pos, ":%s", se->s_name);
+  else sprintf(buf+pos, port ? ":%u" : ":*", port);
 }
 
 // Display info for tcp/udp/raw
@@ -107,15 +97,10 @@
   char *state_label[] = {"", "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1",
                          "FIN_WAIT2", "TIME_WAIT", "CLOSE", "CLOSE_WAIT",
                          "LAST_ACK", "LISTEN", "CLOSING", "UNKNOWN"};
-  struct passwd *pw;
-  FILE *fp = fopen(fname, "r");
+  FILE *fp = xfopen(fname, "r");
 
-  if (!fp) {
-     perror_msg("'%s'", fname);
-     return;
-  }
-
-  if(!fgets(toybuf, sizeof(toybuf), fp)) return; //skip header.
+  // Skip header.
+  fgets(toybuf, sizeof(toybuf), fp);
 
   while (fgets(toybuf, sizeof(toybuf), fp)) {
     char lip[256], rip[256];
@@ -123,34 +108,30 @@
       struct {unsigned u; unsigned char b[4];} i4;
       struct {struct {unsigned a, b, c, d;} u; unsigned char b[16];} i6;
     } laddr, raddr;
-    unsigned lport, rport, state, txq, rxq, num, uid, nitems;
+    unsigned lport, rport, state, txq, rxq, num, uid, af = AF_INET6;
     unsigned long inode;
 
     // Try ipv6, then try ipv4
-    nitems = sscanf(toybuf,
+    if (16 != sscanf(toybuf,
       " %d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x %x:%x %*X:%*X %*X %d %*d %ld",
       &num, &laddr.i6.u.a, &laddr.i6.u.b, &laddr.i6.u.c,
       &laddr.i6.u.d, &lport, &raddr.i6.u.a, &raddr.i6.u.b,
       &raddr.i6.u.c, &raddr.i6.u.d, &rport, &state, &txq, &rxq,
-      &uid, &inode);
-
-    if (nitems!=16) {
-      nitems = sscanf(toybuf,
+      &uid, &inode))
+    {
+      af = AF_INET;
+      if (10 != sscanf(toybuf,
         " %d: %x:%x %x:%x %x %x:%x %*X:%*X %*X %d %*d %ld",
         &num, &laddr.i4.u, &lport, &raddr.i4.u, &rport, &state, &txq,
-        &rxq, &uid, &inode);
-
-      if (nitems!=10) continue;
-      nitems = AF_INET;
-    } else nitems = AF_INET6;
+        &rxq, &uid, &inode)) continue;
+    }
 
     // Should we display this? (listening or all or TCP/UDP/RAW)
-    if (!((toys.optflags & FLAG_l) && (!rport && (state & 0xA)))
-      && !(toys.optflags & FLAG_a) && !(rport & (0x10 | 0x20 | 0x40)))
-        continue;
+    if (!(FLAG(l) && (!rport && (state&0xA))) && !FLAG(a) && !(rport&0x70))
+      continue;
 
-    addr2str(nitems, &laddr, lport, lip, TT.wpad, label);
-    addr2str(nitems, &raddr, rport, rip, TT.wpad, label);
+    addr2str(af, &laddr, lport, lip, TT.wpad, label);
+    addr2str(af, &raddr, rport, rip, TT.wpad, label);
 
     // Display data
     s = label;
@@ -163,15 +144,14 @@
       else if (state == 7) ss_state = "";
     } else if (strstart(&s, "raw")) sprintf(ss_state = buf, "%u", state);
 
-    if (!(toys.optflags & FLAG_n) && (pw = bufgetpwuid(uid)))
-      snprintf(toybuf, sizeof(toybuf), "%s", pw->pw_name);
-    else snprintf(toybuf, sizeof(toybuf), "%d", uid);
-
-    printf("%-6s%6d%7d ", label, rxq, txq);
-    printf("%*.*s %*.*s ", -TT.wpad, TT.wpad, lip, -TT.wpad, TT.wpad, rip);
-    printf("%-11s", ss_state);
-    if ((toys.optflags & FLAG_e)) printf(" %-10s %-11ld", toybuf, inode);
-    if ((toys.optflags & FLAG_p)) {
+    printf("%-6s%6d%7d %*.*s %*.*s %-11s", label, rxq, txq, -TT.wpad, TT.wpad,
+      lip, -TT.wpad, TT.wpad, rip, ss_state);
+    if (FLAG(e)) {
+      if (FLAG(n)) sprintf(s = toybuf, "%d", uid);
+      else s = getusername(uid);
+      printf(" %-10s %-11ld", s, inode);
+    }
+    if (FLAG(p)) {
       struct num_cache *nc = get_num_cache(TT.inodes, inode);
 
       printf(" %s", nc ? nc->data : "-");
@@ -185,47 +165,43 @@
 {
   char *types[] = {"","STREAM","DGRAM","RAW","RDM","SEQPACKET","DCCP","PACKET"},
        *states[] = {"","LISTENING","CONNECTING","CONNECTED","DISCONNECTING"},
-       *s, *ss;
+       *filename = 0;
   unsigned long refcount, flags, type, state, inode;
   FILE *fp = xfopen("/proc/net/unix", "r");
 
-  if(!fgets(toybuf, sizeof(toybuf), fp)) return; //skip header.
+  // Skip header.
+  fgets(toybuf, sizeof(toybuf), fp);
 
-  while (fgets(toybuf, sizeof(toybuf), fp)) {
-    unsigned offset = 0;
-
-    // count = 6 or 7 (first field ignored, sockets don't always have filenames)
-    if (6<sscanf(toybuf, "%*p: %lX %*X %lX %lX %lX %lu %n",
-      &refcount, &flags, &type, &state, &inode, &offset))
-        continue;
-
+  while (fscanf(fp, "%*p: %lX %*X %lX %lX %lX %lu%m[^\n]", &refcount, &flags,
+                &type, &state, &inode, &filename) >= 5) {
     // Linux exports only SO_ACCEPTCON since 2.3.15pre3 in 1999, but let's
     // filter in case they add more someday.
     flags &= 1<<16;
 
-    // Only show unconnected listening sockets with -a
-    if (state==1 && flags && !(toys.optflags&FLAG_a)) continue;
+    // Only show unconnected listening sockets with -a or -l.
+    if (state==1 && flags && !(FLAG(a) || FLAG(l))) continue;
 
     if (type==10) type = 7; // move SOCK_PACKET into line
     if (type>ARRAY_LEN(types)) type = 0;
     if (state>ARRAY_LEN(states) || (state==1 && !flags)) state = 0;
-    sprintf(toybuf, "[ %s]", flags ? "ACC " : "");
 
-    printf("unix  %-6ld %-11s %-10s %-13s %8lu ",
+    if (state!=1 && FLAG(l)) continue;
+
+    sprintf(toybuf, "[ %s]", flags ? "ACC " : "");
+    printf("unix  %-6ld %-11s %-10s %-13s %-8lu ",
       refcount, toybuf, types[type], states[state], inode);
-    if (toys.optflags & FLAG_p) {
+    if (FLAG(p)) {
       struct num_cache *nc = get_num_cache(TT.inodes, inode);
 
-      printf("%-19.19s", nc ? nc->data : "-");
+      printf("%-19.19s ", nc ? nc->data : "-");
     }
 
-    if (offset) {
-      if ((ss = strrchr(s = toybuf+offset, '\n'))) *ss = 0;
-      printf("%s", s);
-    }
-    xputc('\n');
+    if (filename) {
+      printf("%s\n", filename+!FLAG(p));
+      free(filename);
+      filename = 0;
+    } else xputc('\n');
   }
-
   fclose(fp);
 }
 
@@ -244,13 +220,8 @@
 
   sprintf(s, "%d/fd", pid);
   if (-1==(dirfd = openat(dirtree_parentfd(node), s, O_RDONLY))) return 0;
-  if (!(dp = fdopendir(dirfd))) {
-    close(dirfd);
-
-    return 0;
-  }
-
-  while ((entry = readdir(dp))) {
+  if (!(dp = fdopendir(dirfd))) close(dirfd);
+  else while ((entry = readdir(dp))) {
     s = toybuf+256;
     if (!readlinkat0(dirfd, entry->d_name, s, sizeof(toybuf)-256)) continue;
     // Can the "[0000]:" happen in a modern kernel?
@@ -266,9 +237,7 @@
   return 0;
 }
 
-/*
- * extract inet4 route info from /proc/net/route file and display it.
- */
+// extract inet4 route info from /proc/net/route file and display it.
 static void display_routes(void)
 {
   static const char flagchars[] = "GHRDMDAC";
@@ -281,18 +250,16 @@
   char iface[64]={0};
   FILE *fp = xfopen("/proc/net/route", "r");
 
-  if(!fgets(toybuf, sizeof(toybuf), fp)) return; //skip header.
+  // Skip header.
+  fgets(toybuf, sizeof(toybuf), fp);
 
   printf("Kernel IP routing table\n"
           "Destination\tGateway \tGenmask \tFlags %s Iface\n",
-          !(toys.optflags&FLAG_e) ? "  MSS Window  irtt" : "Metric Ref    Use");
+          !FLAG(e) ? "  MSS Window  irtt" : "Metric Ref    Use");
 
-  while (fgets(toybuf, sizeof(toybuf), fp)) {
-     char *destip = 0, *gateip = 0, *maskip = 0;
-
-     if (11 != sscanf(toybuf, "%63s%x%x%X%d%d%d%x%d%d%d", iface, &dest,
-       &gate, &flags, &ref, &use, &metric, &mask, &mss, &win, &irtt))
-         break;
+  while (fscanf(fp, "%63s%x%x%X%d%d%d%x%d%d%d", iface, &dest, &gate, &flags,
+                &ref, &use, &metric, &mask, &mss, &win, &irtt) == 11) {
+    char *destip = 0, *gateip = 0, *maskip = 0;
 
     // skip down interfaces.
     if (!(flags & RTF_UP)) continue;
@@ -301,12 +268,12 @@
 
     if (dest) {
       if (inet_ntop(AF_INET, &dest, out, 16)) destip = out;
-    } else destip = (toys.optflags&FLAG_n) ? "0.0.0.0" : "default";
+    } else destip = FLAG(n) ? "0.0.0.0" : "default";
     out += 16;
 
     if (gate) {
       if (inet_ntop(AF_INET, &gate, out, 16)) gateip = out;
-    } else gateip = (toys.optflags&FLAG_n) ? "0.0.0.0" : "*";
+    } else gateip = FLAG(n) ? "0.0.0.0" : "*";
     out += 16;
 
 // TODO /24
@@ -324,60 +291,56 @@
     if (flags & RTF_REJECT) *flag_val = '!';
 
     printf("%-15.15s %-15.15s %-16s%-6s", destip, gateip, maskip, flag_val);
-    if (!(toys.optflags & FLAG_e))
-      printf("%5d %-5d %6d %s\n", mss, win, irtt, iface);
+    if (!FLAG(e)) printf("%5d %-5d %6d %s\n", mss, win, irtt, iface);
     else printf("%-6d %-2d %7d %s\n", metric, ref, use, iface);
   }
-
   fclose(fp);
 }
 
 void netstat_main(void)
 {
   int tuwx = FLAG_t|FLAG_u|FLAG_w|FLAG_x;
-  char *type = "w/o";
+  char *type = "w/o servers";
 
-  TT.wpad = (toys.optflags&FLAG_W) ? 51 : 23;
+  TT.wpad = FLAG(W) ? 51 : 23;
   if (!(toys.optflags&(FLAG_r|tuwx))) toys.optflags |= tuwx;
-  if (toys.optflags & FLAG_r) display_routes();
+  if (FLAG(r)) display_routes();
   if (!(toys.optflags&tuwx)) return;
 
-  if (toys.optflags & FLAG_a) type = "established and";
-  else if (toys.optflags & FLAG_l) type = "only";
+  if (FLAG(a)) type = "servers and established";
+  else if (FLAG(l)) type = "only servers";
 
-  if (toys.optflags & FLAG_p) dirtree_read("/proc", scan_pids);
+  if (FLAG(p)) dirtree_read("/proc", scan_pids);
 
   if (toys.optflags&(FLAG_t|FLAG_u|FLAG_w)) {
-    printf("Active %s (%s servers)\n", "Internet connections", type);
+    printf("Active Internet connections (%s)\n", type);
     printf("Proto Recv-Q Send-Q %*s %*s State      ", -TT.wpad, "Local Address",
       -TT.wpad, "Foreign Address");
-    if (toys.optflags & FLAG_e) printf(" User       Inode      ");
-    if (toys.optflags & FLAG_p) printf(" PID/Program Name");
+    if (FLAG(e)) printf(" User       Inode      ");
+    if (FLAG(p)) printf(" PID/Program Name");
     xputc('\n');
 
-    if (toys.optflags & FLAG_t) {
+    if (FLAG(t)) {
       show_ip("/proc/net/tcp");
       show_ip("/proc/net/tcp6");
     }
-    if (toys.optflags & FLAG_u) {
+    if (FLAG(u)) {
       show_ip("/proc/net/udp");
       show_ip("/proc/net/udp6");
     }
-    if (toys.optflags & FLAG_w) {
+    if (FLAG(w)) {
       show_ip("/proc/net/raw");
       show_ip("/proc/net/raw6");
     }
   }
 
-  if (toys.optflags & FLAG_x) {
-    printf("Active %s (%s servers)\n", "UNIX domain sockets", type);
-
-    printf("Proto RefCnt Flags\t Type\t    State\t    %s Path\n",
-      (toys.optflags&FLAG_p) ? "PID/Program Name" : "I-Node");
+  if (FLAG(x)) {
+    printf("Active UNIX domain sockets (%s)\n", type);
+    printf("Proto RefCnt Flags       Type       State         I-Node%sPath\n",
+           FLAG(p) ? "   PID/Program Name     " : "   ");
     show_unix_sockets();
   }
 
-  if ((toys.optflags & FLAG_p) && CFG_TOYBOX_FREE)
-    llist_traverse(TT.inodes, free);
+  if (FLAG(p) && CFG_TOYBOX_FREE) llist_traverse(TT.inodes, free);
   toys.exitval = 0;
 }
diff --git a/toys/net/ping.c b/toys/net/ping.c
index 63be72c..018e887 100644
--- a/toys/net/ping.c
+++ b/toys/net/ping.c
@@ -10,8 +10,8 @@
  *
  * Yes, I wimped out and capped -s at sizeof(toybuf), waiting for a complaint...
 
-// -s > 4088 = sizeof(toybuf)-sizeof(struct icmphdr), then kernel adds 20 bytes
-USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
+// -s > 4064 = sizeof(toybuf)-sizeof(struct icmphdr)-CMSG_SPACE(sizeof(uint8_t)), then kernel adds 20 bytes
+USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN))
  
 config PING
@@ -85,14 +85,39 @@
   return u;
 }
 
+static int xrecvmsgwait(int fd, struct msghdr *msg, int flag,
+  union socksaddr *sa, int timeout)
+{
+  socklen_t sl = sizeof(*sa);
+  int len;
+
+  if (timeout >= 0) {
+    struct pollfd pfd;
+
+    pfd.fd = fd;
+    pfd.events = POLLIN;
+    if (!xpoll(&pfd, 1, timeout)) return 0;
+  }
+
+  msg->msg_name = (void *)sa;
+  msg->msg_namelen = sl;
+  len = recvmsg(fd, msg, flag);
+  if (len<0) perror_exit("recvmsg");
+
+  return len;
+}
+
 void ping_main(void)
 {
   struct addrinfo *ai, *ai2;
   struct ifaddrs *ifa, *ifa2 = 0;
   struct icmphdr *ih = (void *)toybuf;
+  struct msghdr msg;
+  struct cmsghdr *cmsg;
+  struct iovec iov;
   union socksaddr srcaddr, srcaddr2;
   struct sockaddr *sa = (void *)&srcaddr;
-  int family = 0, len;
+  int family = 0, ttl = 0, len;
   long long tnext, tW, tnow, tw;
   unsigned short seq = 0, pkttime;
 
@@ -156,16 +181,19 @@
   }
   if (TT.I) xbind(TT.sock, sa, sizeof(srcaddr));
 
+  len = 1;
+  xsetsockopt(TT.sock, SOL_IP, IP_RECVTTL, &len, sizeof(len));
+
   if (FLAG(m)) {
     len = TT.m;
-    xsetsockopt(TT.sock, SOL_SOCKET, SO_MARK, &len, 4);
+    xsetsockopt(TT.sock, SOL_SOCKET, SO_MARK, &len, sizeof(len));
   }
 
   if (TT.t) {
     len = TT.t;
     if (ai->ai_family == AF_INET)
       xsetsockopt(TT.sock, IPPROTO_IP, IP_TTL, &len, 4);
-    else xsetsockopt(TT.sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &len, 4);
+    else xsetsockopt(TT.sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &len, sizeof(len));
   }
 
   if (!FLAG(q)) {
@@ -177,6 +205,7 @@
     // 20 byte TCP header, 8 byte ICMP header, plus data payload
     printf(": %ld(%ld) bytes.\n", TT.s, TT.s+28);
   }
+
   TT.min = ULONG_MAX;
   toys.exitval = 1;
 
@@ -184,6 +213,16 @@
   tnext = millitime();
   if (TT.w) tw = TT.w*1000+tnext;
 
+  memset(&msg, 0, sizeof(msg));
+  // left enought space to store ttl value
+  len = CMSG_SPACE(sizeof(uint8_t));
+  iov.iov_base = (void *)toybuf;
+  iov.iov_len = sizeof(toybuf) - len;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  msg.msg_control = &toybuf[iov.iov_len];
+  msg.msg_controllen = len;
+
   sigatexit(summary);
 
   // Send/receive packets
@@ -230,7 +269,7 @@
     // wait for next packet or timeout
 
     if (waitms<0) waitms = 0;
-    if (!(len = xrecvwait(TT.sock, toybuf, sizeof(toybuf), &srcaddr2, waitms)))
+    if (!(len = xrecvmsgwait(TT.sock, &msg, 0, &srcaddr2, waitms)))
       continue;
 
     TT.recv++;
@@ -240,11 +279,20 @@
 
     // reply id == 0 for ipv4, 129 for ipv6
 
+    cmsg = CMSG_FIRSTHDR(&msg);
+    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+      if (cmsg->cmsg_level == IPPROTO_IP
+        && cmsg->cmsg_type == IP_TTL) {
+          ttl = *(uint8_t *)CMSG_DATA(cmsg);
+          break;
+      }
+    };
+
     if (!FLAG(q)) {
       if (FLAG(f)) xputc('\b');
       else {
         printf("%d bytes from %s: icmp_seq=%d ttl=%d", len, ntop(&srcaddr2.s),
-               ih->un.echo.sequence, 0);
+               ih->un.echo.sequence, ttl);
         if (len >= sizeof(*ih)+4) printf(" time=%u ms", pkttime);
         xputc('\n');
       }
diff --git a/toys/net/sntp.c b/toys/net/sntp.c
index 0cb0b7b..161cab4 100644
--- a/toys/net/sntp.c
+++ b/toys/net/sntp.c
@@ -21,7 +21,7 @@
     -a	Adjust system clock gradually
     -S	Serve time instead of querying (bind to SERVER address if specified)
     -m	Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1)
-    -M	Multicast server on ADDRESS (deault 224.0.0.1)
+    -M	Multicast server on ADDRESS (default 224.0.0.1)
     -t	TTL (multicast only, default 1)
     -d	Daemonize (run in background re-querying )
     -D	Daemonize but stay in foreground: re-query time every 1000 seconds
@@ -78,6 +78,8 @@
   union socksaddr sa;
   int fd, tries = 0;
 
+  if (FLAG(d)) xvdaemon();
+
   if (FLAG(M)) toys.optflags |= FLAG_S;
   if (!(FLAG(S)||FLAG(m)) && !*toys.optargs)
     error_exit("Need -SMm or SERVER address");
@@ -87,8 +89,6 @@
   ai = xgetaddrinfo(*toys.optargs, TT.p, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
     AI_PASSIVE*!*toys.optargs);
 
-  if (FLAG(d) && daemon(0, 0)) perror_exit("daemonize");
-
   // Act as server if necessary
   if (FLAG(S)||FLAG(m)) {
     fd = xbindany(ai);
diff --git a/toys/other/base64.c b/toys/other/base64.c
index ef7854a..22652fd 100644
--- a/toys/other/base64.c
+++ b/toys/other/base64.c
@@ -2,9 +2,11 @@
  *
  * Copyright 2014 Rob Landley <rob@landley.net>
  *
- * No standard
+ * See https://tools.ietf.org/html/rfc4648
 
+// These optflags have to match. Todo: cleanup and collapse together?
 USE_BASE64(NEWTOY(base64, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_BASE32(NEWTOY(base32, "diw#<0=76[!dw]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config BASE64
   bool "base64"
@@ -17,15 +19,29 @@
     -d	Decode
     -i	Ignore non-alphabetic characters
     -w	Wrap output at COLUMNS (default 76 or 0 for no wrap)
+
+config BASE32
+  bool "base32"
+  default y
+  help
+    usage: base32 [-di] [-w COLUMNS] [FILE...]
+
+    Encode or decode in base32.
+
+    -d	Decode
+    -i	Ignore non-alphabetic characters
+    -w	Wrap output at COLUMNS (default 76 or 0 for no wrap)
 */
 
 #define FOR_base64
+#define FORCE_FLAGS
 #include "toys.h"
 
 GLOBALS(
   long w;
-
   unsigned total;
+  unsigned n;  // number of bits used in encoding. 5 for base32, 6 for base64
+  unsigned align;  // number of bits to align to
 )
 
 static void wraputchar(int c, int *x)
@@ -38,7 +54,7 @@
   };
 }
 
-static void do_base64(int fd, char *name)
+static void do_base(int fd, char *name)
 {
   int out = 0, bits = 0, x = 0, i, len;
   char *buf = toybuf+128;
@@ -49,8 +65,8 @@
     // If no more data, flush buffer
     if (!(len = xread(fd, buf, sizeof(toybuf)-128))) {
       if (!FLAG(d)) {
-        if (bits) wraputchar(toybuf[out<<(6-bits)], &x);
-        while (TT.total&3) wraputchar('=', &x);
+        if (bits) wraputchar(toybuf[out<<(TT.n-bits)], &x);
+        while (TT.total&TT.align) wraputchar('=', &x);
         if (x) xputc('\n');
       }
 
@@ -62,8 +78,8 @@
         if (buf[i] == '=') return;
 
         if ((x = stridx(toybuf, buf[i])) != -1) {
-          out = (out<<6) + x;
-          bits += 6;
+          out = (out<<TT.n) + x;
+          bits += TT.n;
           if (bits >= 8) {
             putchar(out >> (bits -= 8));
             out &= (1<<bits)-1;
@@ -78,8 +94,8 @@
       } else {
         out = (out<<8) + buf[i];
         bits += 8;
-        while (bits >= 6) {
-          wraputchar(toybuf[out >> (bits -= 6)], &x);
+        while (bits >= TT.n) {
+          wraputchar(toybuf[out >> (bits -= TT.n)], &x);
           out &= (1<<bits)-1;
         }
       }
@@ -89,6 +105,18 @@
 
 void base64_main(void)
 {
+  TT.n = 6;
+  TT.align = 3;
   base64_init(toybuf);
-  loopfiles(toys.optargs, do_base64);
+  loopfiles(toys.optargs, do_base);
+}
+
+void base32_main(void)
+{
+  int i;
+
+  TT.n = 5;
+  TT.align = 7;
+  for (i = 0; i<32; i++) toybuf[i] = i+(i<26 ? 'A' : 24);
+  loopfiles(toys.optargs, do_base);
 }
diff --git a/toys/other/blkdiscard.c b/toys/other/blkdiscard.c
new file mode 100644
index 0000000..862e4fe
--- /dev/null
+++ b/toys/other/blkdiscard.c
@@ -0,0 +1,55 @@
+/* blkdiscard - discard device sectors
+ *
+ * Copyright 2020 Patrick Oppenlander <patrick.oppenlander@gmail.com>
+ *
+ * See http://man7.org/linux/man-pages/man8/blkdiscard.8.html
+ *
+ * The -v and -p options are not supported.
+ * Size parsing does not match util-linux where MB, GB, TB are multiples of
+ * 1000 and MiB, TiB, GiB are multipes of 1024.
+
+USE_BLKDISCARD(NEWTOY(blkdiscard, "<1>1f(force)l(length)#<0o(offset)#<0s(secure)z(zeroout)[!sz]", TOYFLAG_BIN))
+
+config BLKDISCARD
+  bool "blkdiscard"
+  default y
+  help
+    usage: blkdiscard [-olszf] DEVICE
+
+    Discard device sectors.
+
+    -o, --offset OFF	Byte offset to start discarding at (default 0)
+    -l, --length LEN	Bytes to discard (default all)
+    -s, --secure		Perform secure discard
+    -z, --zeroout		Zero-fill rather than discard
+    -f, --force		Disable check for mounted filesystem
+
+    OFF and LEN must be aligned to the device sector size.
+    By default entire device is discarded.
+    WARNING: All discarded data is permanently lost!
+*/
+
+#define FOR_blkdiscard
+#include "toys.h"
+
+#include <linux/fs.h>
+
+GLOBALS(
+  long o, l;
+)
+
+void blkdiscard_main(void)
+{
+  int fd = xopen(*toys.optargs, O_WRONLY|O_EXCL*!FLAG(f));
+  unsigned long long ol[2];
+
+  // TODO: if numeric arg was long long array could live in TT.
+  ol[0] = TT.o;
+  if (FLAG(l)) ol[1] = TT.l;
+  else {
+    xioctl(fd, BLKGETSIZE64, ol+1);
+    ol[1] -= ol[0];
+  }
+  xioctl(fd, FLAG(s) ? BLKSECDISCARD : FLAG(z) ? BLKZEROOUT : BLKDISCARD, ol);
+  close(fd);
+}
diff --git a/toys/other/blkid.c b/toys/other/blkid.c
index 40391de..e3badca 100644
--- a/toys/other/blkid.c
+++ b/toys/other/blkid.c
@@ -73,7 +73,7 @@
     for (al = TT.s; al; al = al->next) if (!strcmp(key, al->arg)) show = 1;
   } else show = 1;
 
-  if (show) printf(" %s=\"%s\"", key, value);
+  if (show && *value) printf(" %s=\"%s\"", key, value);
 }
 
 static void flagshow(char *s, char *name)
diff --git a/toys/other/count.c b/toys/other/count.c
index f3b6f82..3d388e7 100644
--- a/toys/other/count.c
+++ b/toys/other/count.c
@@ -17,16 +17,22 @@
 
 void count_main(void)
 {
-  uint64_t size = 0;
+  struct pollfd pfd = {0, POLLIN, 0};
+  unsigned long long size = 0, last = 0, then = 0, now;
+  char *buf = xmalloc(65536);
   int len;
-  char buf[32];
 
+  // poll, print if data not ready, update 4x/second otherwise
   for (;;) {
-    len = xread(0, toybuf, sizeof(toybuf));
+    if (!(len = poll(&pfd, 1, (last != size) ? 250 : 0))) continue;
+    if (len<0 && errno != EINTR && errno != ENOMEM) perror_exit(0);
+    if ((len = xread(0, buf, 65536))) {
+      xwrite(1, buf, len);
+      size += len;
+      if ((now = millitime())-then<250) continue;
+    }
+    dprintf(2, "%llu bytes\r", size);
     if (!len) break;
-    size += len;
-    xwrite(1, toybuf, len);
-    xwrite(2, buf, sprintf(buf, "%"PRIu64" bytes\r", size));
   }
-  xwrite(2, "\n", 1);
+  dprintf(2, "\n");
 }
diff --git a/toys/other/devmem.c b/toys/other/devmem.c
index a6f9f60..920d856 100644
--- a/toys/other/devmem.c
+++ b/toys/other/devmem.c
@@ -10,9 +10,8 @@
   help
     usage: devmem ADDR [WIDTH [DATA]]
 
-    Read/write physical address via /dev/mem.
-
-    WIDTH is 1, 2, 4, or 8 bytes (default 4).
+    Read/write physical address. WIDTH is 1, 2, 4, or 8 bytes (default 4).
+    Prefix ADDR with 0x for hexadecimal, output is in same base as address.
 */
 
 #define FOR_devmem
@@ -20,15 +19,16 @@
 
 void devmem_main(void)
 {
-  int writing = toys.optc == 3, page_size = getpagesize(), bytes = 4, fd;
-  unsigned long long addr = atolx(toys.optargs[0]), data = 0, map_off, map_len;
+  int writing = toys.optc == 3, page_size = sysconf(_SC_PAGESIZE), bytes = 4,fd;
+  unsigned long long data = 0, map_off, map_len;
+  unsigned long addr = atolx(*toys.optargs);
   void *map, *p;
 
   // WIDTH?
   if (toys.optc>1) {
     int i;
 
-    if (strlen(toys.optargs[1])!=1 || (i=stridx("1248", *toys.optargs[1]))==-1)
+    if ((i=stridx("1248", *toys.optargs[1]))==-1 || toys.optargs[1][1])
       error_exit("bad width: %s", toys.optargs[1]);
     bytes = 1<<i;
   }
@@ -37,27 +37,31 @@
   if (writing) data = atolx_range(toys.optargs[2], 0, (1ULL<<(8*bytes))-1);
 
   // Map in just enough.
-  fd = xopen("/dev/mem", (writing ? O_RDWR : O_RDONLY) | O_SYNC);
-  map_off = addr & ~(page_size - 1);
-  map_len = (addr+bytes-map_off);
-  map = xmmap(NULL, map_len, writing ? PROT_WRITE : PROT_READ, MAP_SHARED, fd,
-      map_off);
-  p = map + (addr & (page_size - 1));
-  close(fd);
+  if (CFG_TOYBOX_FORK) {
+    fd = xopen("/dev/mem", (writing ? O_RDWR : O_RDONLY) | O_SYNC);
+
+    map_off = addr & ~(page_size - 1);
+    map_len = (addr+bytes-map_off);
+    map = xmmap(0, map_len, writing ? PROT_WRITE : PROT_READ, MAP_SHARED, fd,
+        map_off);
+    p = map + (addr & (page_size - 1));
+    close(fd);
+  } else p = (void *)addr;
 
   // Not using peek()/poke() because registers care about size of read/write
   if (writing) {
-    if (bytes == 1) *(char *)p = data;
-    else if (bytes == 2) *(short *)p = data;
-    else if (bytes == 4) *(int *)p = data;
-    else if (bytes == 8) *(long long *)p = data;
+    if (bytes == 1) *(unsigned char *)p = data;
+    else if (bytes == 2) *(unsigned short *)p = data;
+    else if (bytes == 4) *(unsigned int *)p = data;
+    else if (bytes == 8) *(unsigned long long *)p = data;
   } else {
-    if (bytes == 1) data = *(char *)p;
-    else if (bytes == 2) data = *(short *)p;
-    else if (bytes == 4) data = *(int *)p;
-    else if (bytes == 8) data = *(long long *)p;
-    printf("%#0*llx\n", bytes*2, data);
+    if (bytes == 1) data = *(unsigned char *)p;
+    else if (bytes == 2) data = *(unsigned short *)p;
+    else if (bytes == 4) data = *(unsigned int *)p;
+    else if (bytes == 8) data = *(unsigned long long *)p;
+    printf((!strchr(*toys.optargs, 'x')) ? "%0*lld\n" : "0x%0*llx\n",
+      bytes*2, data);
   }
 
-  munmap(map, map_len);
+  if (CFG_TOYBOX_FORK) munmap(map, map_len);
 }
diff --git a/toys/other/help.c b/toys/other/help.c
index 686f862..ab168c6 100644
--- a/toys/other/help.c
+++ b/toys/other/help.c
@@ -30,19 +30,19 @@
     xprintf("<a name=\"%s\"><h1>%s</h1><blockquote><pre>\n", t->name, t->name);
 
   toys.which = t;
-  show_help(stdout, !FLAG(u));
+  show_help(stdout, FLAG(h)+!FLAG(u));
 
   if (FLAG(h)) xprintf("</blockquote></pre>\n");
 }
 
-// The simple help is just toys.which = toy_find("name"); show_help(stdout);
-// But iterating through html output and all commands is a big more 
+// Simple help is just toys.which = toy_find("name"); show_help(stdout, 1);
+// but iterating through html output and all commands is a bit more
 
 void help_main(void)
 {
   int i;
 
-  // If called with no arguments as a builtin form the shell, show all builtins
+  // If called with no arguments as a builtin from the shell, show all builtins
   if (toys.rebound && !*toys.optargs && !toys.optflags) {
     for (i = 0; i < toys.toycount; i++) {
       if (!(toy_list[i].flags&(TOYFLAG_NOFORK|TOYFLAG_MAYFORK))) continue;
@@ -62,10 +62,11 @@
   }
 
   if (FLAG(h)) {
-    xprintf("<html>\n<title>Toybox command list</title>\n<body>\n<p>\n");
+    sprintf(toybuf, "Toybox %s command help", toybox_version);
+    xprintf("<html>\n<title>%s</title>\n<body>\n<h1>%s</h1><hr /><p>",
+            toybuf, toybuf);
     for (i=0; i < toys.toycount; i++)
-      xprintf("<a href=\"#%s\">%s\n</a>\n", toy_list[i].name,
-              toy_list[i].name);
+      xprintf("<a href=\"#%s\">%s</a> \n", toy_list[i].name, toy_list[i].name);
     xprintf("</p>\n");
   }
 
diff --git a/toys/other/hexedit.c b/toys/other/hexedit.c
index 4b62846..398ec15 100644
--- a/toys/other/hexedit.c
+++ b/toys/other/hexedit.c
@@ -10,18 +10,21 @@
   bool "hexedit"
   default y
   help
-    usage: hexedit FILENAME
+    usage: hexedit FILE
 
-    Hexadecimal file editor. All changes are written to disk immediately.
+    Hexadecimal file editor/viewer. All changes are written to disk immediately.
 
     -r	Read only (display but don't edit)
 
     Keys:
-    Arrows        Move left/right/up/down by one line/column
-    Pg Up/Pg Dn   Move up/down by one page
-    0-9, a-f      Change current half-byte to hexadecimal value
-    u             Undo
-    q/^c/^d/<esc> Quit
+    Arrows         Move left/right/up/down by one line/column
+    PgUp/PgDn      Move up/down by one page
+    Home/End       Start/end of line (start/end of file with ctrl)
+    0-9, a-f       Change current half-byte to hexadecimal value
+    ^J or :        Jump (+/- for relative offset, otherwise absolute address)
+    ^F or /        Find string (^G/n: next, ^D/p: previous match)
+    u              Undo
+    q/^C/^Q/Esc    Quit
 */
 
 #define FOR_hexedit
@@ -31,39 +34,83 @@
   char *data;
   long long len, base;
   int numlen, undo, undolen;
-  unsigned height;
+  unsigned rows, cols;
+  long long pos;
+  char keybuf[16];
+  char input[80];
+  char *search;
 )
 
 #define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
 
-// Render all characters printable, using color to distinguish.
-static int draw_char(FILE *fp, wchar_t broiled)
+static void show_error(char *what)
 {
-  if (fp) {
-    if (broiled<32 || broiled>=127) {
-      if (broiled>127) {
-        tty_esc("2m");
-        broiled &= 127;
-      }
-      if (broiled<32 || broiled==127) {
-        tty_esc("7m");
-        if (broiled==127) broiled = 32;
-        else broiled += 64;
-      }
-      printf("%c", (int)broiled);
-      tty_esc("0m");
-    } else printf("%c", (int)broiled);
-  }
-
-  return 1;
+  tty_jump(0, TT.rows);
+  printf("\e[41m\e[37m\e[K\e[1m%s\e[0m", what);
+  xflush(1);
+  msleep(500);
 }
 
-static void draw_tail(void)
+// TODO: support arrow keys, insertion, and scrolling (and reuse in vi)
+static int prompt(char *prompt, char *initial_value)
 {
-  tty_jump(0, TT.height);
+  int yes = 0, key, len = strlen(initial_value);
+
+  strcpy(TT.input, initial_value);
+  while (1) {
+    tty_jump(0, TT.rows);
+    tty_esc("K");
+    printf("\e[1m%s: \e[0m%s", prompt, TT.input);
+    tty_esc("?25h");
+    xflush(1);
+
+    key = scan_key(TT.keybuf, -1);
+    if (key < 0 || key == 27) break;
+    if (key == '\r') {
+      yes = len; // Hitting enter with no input counts as cancellation.
+      break;
+    }
+
+    if (key == 0x7f) {
+      if (len > 0) TT.input[--len] = 0;
+    } else if (key == 'U'-'@') {
+      while (len > 0) TT.input[--len] = 0;
+    } else if (key >= ' ' && key < 0x7f && len < sizeof(TT.input)) {
+      TT.input[len++] = key;
+    }
+  }
+
+  tty_esc("?25l");
+  return yes;
+}
+
+// Render all characters printable, using color to distinguish.
+static void draw_char(int ch)
+{
+  if (ch >= ' ' && ch < 0x7f) putchar(ch);
+  else {
+    if (ch < ' ') printf("\e[31m%c", ch + '@');
+    else printf("\e[35m?");
+  }
+  printf("\e[0m");
+}
+
+static void draw_status(void)
+{
+  char line[80];
+
+  tty_jump(0, TT.rows);
   tty_esc("K");
 
-  draw_trim(*toys.optargs, -1, 71);
+  snprintf(line, sizeof(line), "\"%s\"%s, %#llx/%#llx", *toys.optargs,
+    FLAG(r) ? " [readonly]" : "", TT.pos, TT.len);
+  draw_trim(line, -1, TT.cols);
+}
+
+static void draw_byte(int byte)
+{
+  if (byte) printf("%02x", byte);
+  else printf("\e[2m00\e[0m");
 }
 
 static void draw_line(long long yy)
@@ -74,10 +121,13 @@
   if (yy+xx>=TT.len) xx = TT.len-yy;
 
   if (yy<TT.len) {
-    printf("\r%0*llX ", TT.numlen, yy);
-    for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]);
+    printf("\r\e[33m%0*llx\e[0m ", TT.numlen, yy);
+    for (x=0; x<xx; x++) {
+      putchar(' ');
+      draw_byte(TT.data[yy+x]);
+    }
     printf("%*s", 2+3*(16-xx), "");
-    for (x=0; x<xx; x++) draw_char(stdout, TT.data[yy+x]);
+    for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
     printf("%*s", 16-xx, "");
   }
   tty_esc("K");
@@ -88,11 +138,11 @@
   int y;
 
   tty_jump(0, 0);
-  for (y = 0; y<TT.height; y++) {
+  for (y = 0; y<TT.rows; y++) {
     if (y) printf("\r\n");
     draw_line(y);
   }
-  draw_tail();
+  draw_status();
 }
 
 // side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
@@ -101,115 +151,177 @@
   char cc = TT.data[16*(TT.base+yy)+xx];
   int i;
 
-  // Display cursor
+  // Display cursor in hex area.
   tty_jump(2+TT.numlen+3*xx, yy);
   tty_esc("0m");
   if (side!=2) tty_esc("7m");
-  if (side>1) printf("%02X", cc);
+  if (side>1) draw_byte(cc);
   else for (i=0; i<2;) {
     if (side==i) tty_esc("32m");
-    printf("%X", (cc>>(4*(1&++i)))&15);
+    printf("%x", (cc>>(4*(1&++i)))&15);
   }
-  tty_esc("0m");
   tty_jump(TT.numlen+17*3+xx, yy);
-  draw_char(stdout, cc);
+
+  // Display cursor in text area.
+  if (side!=2) tty_esc("7m");
+  draw_char(cc);
+}
+
+static void find_next(int pos)
+{
+  char *p;
+
+  p = memmem(TT.data+pos, TT.len-pos, TT.search, strlen(TT.search));
+  if (p) TT.pos = p - TT.data;
+  else show_error("No match!");
+}
+
+static void find_prev(int pos)
+{
+  size_t len = strlen(TT.search);
+
+  for (; pos >= 0; pos--) {
+    if (!memcmp(TT.data+pos, TT.search, len)) {
+      TT.pos = pos;
+      return;
+    }
+  }
+  show_error("No match!");
 }
 
 void hexedit_main(void)
 {
-  long long pos = 0, y;
-  int x, i, side = 0, key, ro = toys.optflags&FLAG_r,
-      fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);
-  char keybuf[16];
-
-  *keybuf = 0;
+  long long y;
+  int x, i, side = 0, key, fd;
 
   // Terminal setup
-  TT.height = 25;
-  terminal_size(0, &TT.height);
-  if (TT.height) TT.height--;
+  TT.cols = 80;
+  TT.rows = 24;
+  terminal_size(&TT.cols, &TT.rows);
+  if (TT.rows) TT.rows--;
+  xsignal(SIGWINCH, generic_signal);
   sigatexit(tty_sigreset);
   tty_esc("0m");
   tty_esc("?25l");
-  fflush(0);
+  xflush(1);
   xset_terminal(1, 1, 0, 0);
 
+  if (access(*toys.optargs, W_OK)) toys.optflags |= FLAG_r;
+  fd = xopen(*toys.optargs, FLAG(r) ? O_RDONLY : O_RDWR);
   if ((TT.len = fdlength(fd))<1) error_exit("bad length");
   if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
   // count file length hex in digits, rounded up to multiple of 4
-  for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
+  for (TT.pos = TT.len, TT.numlen = 0; TT.pos; TT.pos >>= 4, TT.numlen++);
   TT.numlen += (4-TT.numlen)&3;
 
-  TT.data = xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);
+  TT.data=xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!FLAG(r)), MAP_SHARED, fd, 0);
+  close(fd);
   draw_page();
 
   for (;;) {
     // Scroll display if necessary
-    if (pos<0) pos = 0;
-    if (pos>=TT.len) pos = TT.len-1;
-    x = pos&15;
-    y = pos/16;
+    if (TT.pos<0) TT.pos = 0;
+    if (TT.pos>=TT.len) TT.pos = TT.len-1;
+    x = TT.pos&15;
+    y = TT.pos/16;
 
-    i = 0;
     while (y<TT.base) {
-      if (TT.base-y>(TT.height/2)) {
+      if (TT.base-y>(TT.rows/2)) {
         TT.base = y;
         draw_page();
       } else {
         TT.base--;
-        i++;
         tty_jump(0, 0);
         tty_esc("1L");
         draw_line(0);
       }
     }
-    while (y>=TT.base+TT.height) {
-      if (y-(TT.base+TT.height)>(TT.height/2)) {
-        TT.base = y-TT.height-1;
+    while (y>=TT.base+TT.rows) {
+      if (y-(TT.base+TT.rows)>(TT.rows/2)) {
+        TT.base = y-TT.rows-1;
         draw_page();
       } else {
         TT.base++;
-        i++;
         tty_jump(0, 0);
         tty_esc("1M");
-        tty_jump(0, TT.height-1);
-        draw_line(TT.height-1);
+        tty_jump(0, TT.rows-1);
+        draw_line(TT.rows-1);
       }
     }
-    if (i) draw_tail();
+    draw_status();
     y -= TT.base;
 
     // Display cursor and flush output
-    highlight(x, y, ro ? 3 : side);
+    highlight(x, y, FLAG(r) ? 3 : side);
     xflush(1);
 
     // Wait for next key
-    key = scan_key(keybuf, -1);
-    // Exit for q, ctrl-c, ctrl-d, escape, or EOF
-    if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
+    key = scan_key(TT.keybuf, -1);
+
+    // Window resized?
+    if (key == -3) {
+      toys.signal = 0;
+      terminal_size(&TT.cols, &TT.rows);
+      if (TT.rows) TT.rows--;
+      draw_page();
+      continue;
+    }
+
+    // Various popular ways to quit...
+    if (key==-1||key==('C'-'@')||key==('Q'-'@')||key==27||key=='q') break;
+    highlight(x, y, 2);
+
+    if (key == ('J'-'@') || key == ':' || key == '-' || key == '+') {
+      // Jump (relative or absolute)
+      char initial[2] = {}, *s = 0;
+      long long val;
+
+      if (key == '-' || key == '+') *initial = key;
+      if (!prompt("Jump to", initial)) continue;
+
+      val = estrtol(TT.input, &s, 0);
+      if (!errno && s && !*s) {
+        if (*TT.input == '-' || *TT.input == '+') TT.pos += val;
+        else TT.pos = val;
+      }
+      continue;
+    } else if (key == ('F'-'@') || key == '/') { // Find
+      if (!prompt("Find", TT.search ? TT.search : "")) continue;
+
+      // TODO: parse hex escapes in input, and record length to support \0
+      free(TT.search);
+      TT.search = xstrdup(TT.input);
+      find_next(TT.pos);
+    } else if (TT.search && (key == ('G'-'@') || key == 'n')) { // Find next
+      if (TT.pos < TT.len) find_next(TT.pos+1);
+    } else if (TT.search && (key == ('D'-'@') || key == 'p')) { // Find previous
+      if (TT.pos > 0) find_prev(TT.pos-1);
+    }
+
+    // Remove cursor
     highlight(x, y, 2);
 
     // Hex digit?
     if (key>='a' && key<='f') key-=32;
-    if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
+    if (!FLAG(r) && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
       if (!side) {
         long long *ll = (long long *)toybuf;
 
-        ll[TT.undo] = pos;
-        toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos];
+        ll[TT.undo] = TT.pos;
+        toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[TT.pos];
         if (TT.undolen < UNDO_LEN) TT.undolen++;
         TT.undo %= UNDO_LEN;
       }
 
       i = key - '0';
       if (i>9) i -= 7;
-      TT.data[pos] &= 15<<(4*side);
-      TT.data[pos] |= i<<(4*!side);
+      TT.data[TT.pos] &= 15<<(4*side);
+      TT.data[TT.pos] |= i<<(4*!side);
 
       if (++side==2) {
         highlight(x, y, side);
         side = 0;
-        ++pos;
+        ++TT.pos;
       }
     } else side = 0;
     if (key=='u') {
@@ -218,26 +330,35 @@
 
         TT.undolen--;
         if (!TT.undo) TT.undo = UNDO_LEN;
-        pos = ll[--TT.undo];
-        TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
+        TT.pos = ll[--TT.undo];
+        TT.data[TT.pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
       }
     }
     if (key>=256) {
       key -= 256;
 
-      if (key==KEY_UP) pos -= 16;
-      else if (key==KEY_DOWN) pos += 16;
+      if (key==KEY_UP) TT.pos -= 16;
+      else if (key==KEY_DOWN) TT.pos += 16;
       else if (key==KEY_RIGHT) {
-        if (x<15) pos++;
+        if (TT.pos<TT.len) TT.pos++;
       } else if (key==KEY_LEFT) {
-        if (x) pos--;
-      } else if (key==KEY_PGUP) pos -= 16*TT.height;
-      else if (key==KEY_PGDN) pos += 16*TT.height;
-      else if (key==KEY_HOME) pos = 0;
-      else if (key==KEY_END) pos = TT.len-1;
+        if (TT.pos>0) TT.pos--;
+      } else if (key==KEY_PGUP) {
+        TT.pos -= 16*TT.rows;
+        if (TT.pos < 0) TT.pos = 0;
+        TT.base = TT.pos/16;
+        draw_page();
+      } else if (key==KEY_PGDN) {
+        TT.pos += 16*TT.rows;
+        if (TT.pos > TT.len-1) TT.pos = TT.len-1;
+        TT.base = TT.pos/16;
+        draw_page();
+      } else if (key==KEY_HOME) TT.pos = TT.pos & ~0xf;
+      else if (key==KEY_END) TT.pos = TT.pos | 0xf;
+      else if (key==(KEY_CTRL|KEY_HOME)) TT.pos = 0;
+      else if (key==(KEY_CTRL|KEY_END)) TT.pos = TT.len-1;
     }
   }
   munmap(TT.data, TT.len);
-  close(fd);
   tty_reset();
 }
diff --git a/toys/other/hwclock.c b/toys/other/hwclock.c
index 52e7160..541c70a 100644
--- a/toys/other/hwclock.c
+++ b/toys/other/hwclock.c
@@ -14,7 +14,7 @@
 
     Get/set the hardware clock.
 
-    -f FILE	Use specified device file instead of /dev/rtc (--rtc)
+    -f FILE	Use specified device file instead of /dev/rtc0 (--rtc)
     -l	Hardware clock uses localtime (--localtime)
     -r	Show hardware clock time (--show)
     -s	Set system time from hardware clock (--hctosys)
@@ -29,110 +29,60 @@
 
 GLOBALS(
   char *f;
-
-  int utc;
 )
 
-static int rtc_find(struct dirtree* node)
-{
-  FILE *fp;
-
-  if (!node->parent) return DIRTREE_RECURSE;
-
-  sprintf(toybuf, "/sys/class/rtc/%s/hctosys", node->name);
-  fp = fopen(toybuf, "r");
-  if (fp) {
-    int hctosys = 0, items = fscanf(fp, "%d", &hctosys);
-
-    fclose(fp);
-    if (items == 1 && hctosys == 1) {
-      sprintf(toybuf, "/dev/%s", node->name);
-      TT.f = toybuf;
-
-      return DIRTREE_ABORT;
-    }
-  }
-
-  return 0;
-}
-
 void hwclock_main()
 {
   struct timezone tzone;
   struct timeval timeval;
   struct tm tm;
-  time_t time;
-  int fd = -1;
+  int fd = -1, utc;
 
-  // check for Grenich Mean Time
-  if (toys.optflags & FLAG_u) TT.utc = 1;
-  else {
-    FILE *fp;
-    char *s = 0;
+  if (FLAG(u)) utc = 1;
+  else if (FLAG(l)) utc = 0;
+  else utc = !readfile("/etc/adjtime", toybuf, sizeof(toybuf)) ||
+    !!strstr(toybuf, "UTC");
 
-    for (fp = fopen("/etc/adjtime", "r");
-         fp && getline(&s, (void *)toybuf, fp)>0;
-         free(s), s = 0) TT.utc += !strncmp(s, "UTC", 3);
-    if (fp) fclose(fp);
-  }
-
-  if (!(toys.optflags&FLAG_t)) {
-    int w = toys.optflags & FLAG_w, flag = O_WRONLY*w;
-
-    // Open /dev/rtc (if your system has no /dev/rtc symlink, search for it).
-    if (!TT.f && (fd = open("/dev/rtc", flag)) == -1) {
-      dirtree_read("/sys/class/rtc", rtc_find);
-      if (!TT.f) TT.f = "/dev/misc/rtc";
-    }
-    if (fd == -1) fd = xopen(TT.f, flag);
+  if (!FLAG(t)) {
+    if (!TT.f) TT.f = "/dev/rtc0";
+    fd = xopen(TT.f, O_WRONLY*FLAG(w));
 
     // Get current time in seconds from rtc device. todo: get subsecond time
-    if (!w) {
-      char *s = s;
-
+    if (!FLAG(w)) {
       xioctl(fd, RTC_RD_TIME, &tm);
-      if (TT.utc) s = xtzset("UTC0");
-      if ((time = mktime(&tm)) < 0) error_exit("mktime failed");
-      if (TT.utc) {
-        free(xtzset(s));
-        free(s);
-      }
+      timeval.tv_sec = xmktime(&tm, utc);
+      timeval.tv_usec = 0; // todo: fixit
     }
   }
 
-  if (toys.optflags & (FLAG_w|FLAG_t)) {
+  if (FLAG(w) || FLAG(t)) {
     if (gettimeofday(&timeval, 0)) perror_exit("gettimeofday failed");
-    if (!(TT.utc ? gmtime_r : localtime_r)(&timeval.tv_sec, &tm))
-      error_exit(TT.utc ? "gmtime_r failed" : "localtime_r failed");
+    if (!(utc ? gmtime_r : localtime_r)(&timeval.tv_sec, &tm))
+      error_exit(utc ? "gmtime_r failed" : "localtime_r failed");
   }
 
-  if (toys.optflags & FLAG_w) {
+  if (FLAG(w)) {
     /* The value of tm_isdst is positive if daylight saving time is in effect,
      * zero if it is not and negative if the information is not available. 
      * todo: so why isn't this negative...? */
     tm.tm_isdst = 0;
     xioctl(fd, RTC_SET_TIME, &tm);
-  } else if (toys.optflags & FLAG_s) {
+  } else if (FLAG(s)) {
     tzone.tz_minuteswest = timezone / 60 - 60 * daylight;
-    timeval.tv_sec = time;
-    timeval.tv_usec = 0; // todo: fixit
-  } else if (toys.optflags & FLAG_t) {
+  } else if (FLAG(t)) {
     // Adjust seconds for timezone and daylight saving time
     // extern long timezone is defined in header sys/time.h
     tzone.tz_minuteswest = timezone / 60;
     if (tm.tm_isdst) tzone.tz_minuteswest -= 60;
-    if (!TT.utc) timeval.tv_sec += tzone.tz_minuteswest * 60;
+    if (!utc) timeval.tv_sec += tzone.tz_minuteswest * 60;
   } else {
-    char *c = ctime(&time), *s = strrchr(c, '\n');
-
-    if (s) *s = '\0';
-    // TODO: implement this.
-    xprintf("%s  0.000000 seconds\n", c);
+    strftime(toybuf, sizeof(toybuf), "%F %T%z", &tm);
+    xputs(toybuf);
   }
-  if (toys.optflags & (FLAG_t|FLAG_s)) {
+  if (FLAG(t) || FLAG(s)) {
     tzone.tz_dsttime = 0;
     if (settimeofday(&timeval, &tzone)) perror_exit("settimeofday failed");
   }
 
-  if (fd != -1) close(fd);
+  xclose(fd);
 }
diff --git a/toys/other/i2ctools.c b/toys/other/i2ctools.c
index c9a1146..cd1b892 100644
--- a/toys/other/i2ctools.c
+++ b/toys/other/i2ctools.c
@@ -196,7 +196,7 @@
     bus = atolx_range(*toys.optargs, 0, INT_MAX);
     if (toys.optc == 3) {
       first = atolx_range(toys.optargs[1], 0, 0x7f);
-      last = atolx_range(toys.optargs[1], 0, 0x7f);
+      last = atolx_range(toys.optargs[2], 0, 0x7f);
       if (first > last) error_exit("first > last");
     }
 
diff --git a/toys/other/losetup.c b/toys/other/losetup.c
index 27d25a1..7b8aed4 100644
--- a/toys/other/losetup.c
+++ b/toys/other/losetup.c
@@ -52,7 +52,7 @@
 static int loopback_setup(char *device, char *file)
 {
   struct loop_info64 *loop = (void *)(toybuf+32);
-  int lfd = -1, ffd = ffd;
+  int lfd = -1, ffd = -1;
   int racy = !device;
 
   // Open file (ffd) and loop device (lfd)
@@ -129,8 +129,8 @@
   }
 
 done:
-  if (file) close(ffd);
-  if (lfd != -1) close(lfd);
+  xclose(ffd);
+  xclose(lfd);
   return 0;
 }
 
diff --git a/toys/other/lsattr.c b/toys/other/lsattr.c
index 3e32792..547012e 100644
--- a/toys/other/lsattr.c
+++ b/toys/other/lsattr.c
@@ -102,7 +102,8 @@
   {"No_Dump",                       FS_NODUMP_FL,       'd'},
   {"No_Atime",                      FS_NOATIME_FL,      'A'},
   {"Compression_Requested",         FS_COMPR_FL,        'c'},
-  {"Encrypted",                     FS_ENCRYPT_FL,      'E'},
+  // FS_ENCRYPT_FL added to linux 4.5 march 2016, +y7 = 2023
+  {"Encrypted",                     0x800,              'E'},
   {"Journaled_Data",                FS_JOURNAL_DATA_FL, 'j'},
   {"Indexed_directory",             FS_INDEX_FL,        'I'},
   {"No_Tailmerging",                FS_NOTAIL_FL,       't'},
diff --git a/toys/other/lspci.c b/toys/other/lspci.c
index c208484..4c115fd 100644
--- a/toys/other/lspci.c
+++ b/toys/other/lspci.c
@@ -13,7 +13,7 @@
 
     -e	Print all 6 digits in class
     -k	Print kernel driver
-    -m	Machine parseable format
+    -m	Machine readable format
 
 config LSPCI_TEXT
   bool "lspci readable output"
diff --git a/toys/other/modinfo.c b/toys/other/modinfo.c
index 286570f..6c1e939 100644
--- a/toys/other/modinfo.c
+++ b/toys/other/modinfo.c
@@ -40,16 +40,16 @@
 
 static void modinfo_file(char *full_name)
 {
-  int fd, len, i;
-  char *buf = 0, *pos, *modinfo_tags[] = {
-    "alias", "license", "description", "author", "firmware",
-    "vermagic", "srcversion", "intree", "depends", "parm",
-    "parmtype",
+  int fd, flen, i;
+  char *buf = 0, *end, *modinfo_tags[] = {
+    "license", "author", "description", "firmware", "alias", "srcversion",
+    "depends", "retpoline", "intree", "name", "vermagic", "parm", "parmtype",
   };
 
   if (-1 != (fd = open(full_name, O_RDONLY))) {
-    len = fdlength(fd);
-    buf = xmmap(0, len, PROT_READ, MAP_SHARED, fd, 0);
+    flen = fdlength(fd);
+    buf = xmmap(0, flen, PROT_READ, MAP_SHARED, fd, 0);
+    end = buf + flen;
     close(fd);
   }
 
@@ -61,47 +61,42 @@
   TT.count++;
   output_field("filename", full_name);
 
-  for (pos = buf; pos < buf+len; pos++) {
-    if (*pos) continue;
+  for (i=0; i<ARRAY_LEN(modinfo_tags); i++) {
+    char *field = modinfo_tags[i], *p = buf;
+    int slen = sprintf(toybuf, "%s=", field);
 
-    for (i=0; i<ARRAY_LEN(modinfo_tags); i++) {
-      char *str = modinfo_tags[i];
-      int len = strlen(str);
-
-      if (!strncmp(pos+1, str, len) && pos[len+1] == '=')
-        output_field(str, pos+len+2);
+    while (p && p < end) {
+      p = memmem(p, end-p, toybuf, slen);
+      if (p) output_field(field, p += slen);
     }
   }
 
-  munmap(buf, len);
+  munmap(buf, flen);
 }
 
 static int check_module(struct dirtree *new)
 {
+  char *s;
+  int len;
+
   if (!dirtree_notdotdot(new)) return 0;
 
-  if (S_ISREG(new->st.st_mode)) {
-    char *s;
+  if (!S_ISREG(new->st.st_mode)) return DIRTREE_RECURSE;
 
-    for (s = toys.optargs[TT.mod]; *s; s++) {
-      int len = 0;
+  s = toys.optargs[TT.mod];
 
-      // The kernel treats - and _ the same, so we should too.
-      for (len = 0; s[len]; len++) {
-        if (s[len] == '-' && new->name[len] == '_') continue;
-        if (s[len] == '_' && new->name[len] == '-') continue;
-        if (s[len] != new->name[len]) break;
-      }
-      if (s[len] || strcmp(new->name+len, ".ko")) break;
-
-      modinfo_file(s = dirtree_path(new, 0));
-      free(s);
-
-      return DIRTREE_ABORT;
-    }
+  // The kernel treats - and _ the same, so we should too.
+  for (len = 0; s[len]; len++) {
+    if (s[len] == '-' && new->name[len] == '_') continue;
+    if (s[len] == '_' && new->name[len] == '-') continue;
+    if (s[len] != new->name[len]) break;
   }
+  if (s[len] || strcmp(new->name+len, ".ko")) return DIRTREE_RECURSE;
 
-  return DIRTREE_RECURSE;
+  modinfo_file(s = dirtree_path(new, 0));
+  free(s);
+
+  return DIRTREE_ABORT;
 }
 
 void modinfo_main(void)
@@ -120,9 +115,7 @@
   }
 
   for (TT.mod = 0; TT.mod<toys.optc; TT.mod++) {
-    char *s = strstr(toys.optargs[TT.mod], ".ko");
-
-    if (s && !s[3]) modinfo_file(toys.optargs[TT.mod]);
+    if (strend(toys.optargs[TT.mod], ".ko")) modinfo_file(toys.optargs[TT.mod]);
     else {
       char *path = xmprintf("%s/lib/modules/%s", TT.b, TT.k);
 
diff --git a/toys/other/oneit.c b/toys/other/oneit.c
index c400f6d..4c8bb1f 100644
--- a/toys/other/oneit.c
+++ b/toys/other/oneit.c
@@ -8,7 +8,7 @@
   bool "oneit"
   default y
   help
-    usage: oneit [-p] [-c /dev/tty0] command [...]
+    usage: oneit [-prn3] [-c CONSOLE] [COMMAND...]
 
     Simple init program that runs a single supplied command line with a
     controlling tty (so CTRL-C can kill it).
@@ -16,6 +16,7 @@
     -c	Which console device to use (/dev/console doesn't do CTRL-C, etc)
     -p	Power off instead of rebooting when command exits
     -r	Restart child when it exits
+    -n	No reboot, just relaunch command line
     -3	Write 32 bit PID of each exiting reparented process to fd 3 of child
     	(Blocking writes, child must read to avoid eventual deadlock.)
 
@@ -39,7 +40,7 @@
 //
 // - Fork a child (PID 1 is special: can't exit, has various signals blocked).
 // - Do a setsid() (so we have our own session).
-// - In the child, attach stdio to /dev/tty0 (/dev/console is special)
+// - In the child, attach stdio to TT.c (/dev/console is special)
 // - Exec the rest of the command line.
 //
 // PID 1 then reaps zombies until the child process it spawned exits, at which
@@ -68,7 +69,7 @@
   // Setup signal handlers for signals of interest
   for (i = 0; i<ARRAY_LEN(pipes); i++) xsignal(pipes[i], oneit_signaled);
 
-  if (toys.optflags & FLAG_3) {
+  if (FLAG(3)) {
     // Ensure next available filehandles are #3 and #4
     while (xopen_stdio("/", 0) < 3);
     close(3);
@@ -87,17 +88,17 @@
       // We ignore the return value of write (what would we do with it?)
       // but save it in a variable we never read to make fortify shut up.
       // (Real problem is if pid2 never reads, write() fills pipe and blocks.)
-      while (pid != wait(&i)) if (toys.optflags & FLAG_3) i = write(4, &pid, 4);
-      if (toys.optflags & FLAG_n) continue;
+      while (pid != wait(&i)) if (FLAG(3)) i = write(4, &pid, 4);
+      if (FLAG(n)) continue;
 
-      oneit_signaled((toys.optflags & FLAG_p) ? SIGUSR2 : SIGTERM);
+      oneit_signaled(FLAG(p) ? SIGUSR2 : SIGTERM);
     } else {
-      // Redirect stdio to /dev/tty0, with new session ID, so ctrl-c works.
+      // Redirect stdio to TT.c, with new session ID, so ctrl-c works.
       setsid();
       for (i=0; i<3; i++) {
         close(i);
         // Remember, O_CLOEXEC is backwards for xopen()
-        xopen_stdio(TT.c ? TT.c : "/dev/tty0", O_RDWR|O_CLOEXEC);
+        xopen_stdio(TT.c ? : "/dev/tty0", O_RDWR|O_CLOEXEC);
       }
 
       // Can't xexec() here, we vforked so we don't want to error_exit().
diff --git a/toys/other/printenv.c b/toys/other/printenv.c
index 431bf1d..41cd0bd 100644
--- a/toys/other/printenv.c
+++ b/toys/other/printenv.c
@@ -2,7 +2,7 @@
  *
  * Copyright 2012 Georgi Chorbadzhiyski <georgi@unixsol.org>
 
-USE_PRINTENV(NEWTOY(printenv, "0(null)", TOYFLAG_BIN))
+USE_PRINTENV(NEWTOY(printenv, "(null)0", TOYFLAG_BIN))
 
 config PRINTENV
   bool "printenv"
diff --git a/toys/other/pwgen.c b/toys/other/pwgen.c
new file mode 100644
index 0000000..c6621cc
--- /dev/null
+++ b/toys/other/pwgen.c
@@ -0,0 +1,76 @@
+/* pwgen.c - A password generator.
+ *
+ * Copyright 2020 Moritz Röhrich <moritz@ildefons.de>
+
+USE_PWGEN(NEWTOY(pwgen, ">2r(remove):c(capitalize)n(numerals)y(symbols)s(secure)B(ambiguous)h(help)C1vA(no-capitalize)0(no-numerals)[-cA][-n0][-C1]", TOYFLAG_USR|TOYFLAG_BIN))
+
+config PWGEN
+  bool "pwgen"
+  default y
+  help
+    usage: pwgen [-cAn0yrsBhC1v] [LENGTH] [COUNT]
+
+    Generate human-readable random passwords. When output is to tty produces
+    a screenfull to defeat shoulder surfing (pick one and clear the screen).
+
+    -c  --capitalize                  Permit capital letters.
+    -A  --no-capitalize               Don't include capital letters.
+    -n  --numerals                    Permit numbers.
+    -0  --no-numerals                 Don't include numbers.
+    -y  --symbols                     Permit special characters ($#%...).
+    -r <chars>  --remove=<chars>      Don't include the given characters.
+    -s  --secure                      Generate more random passwords.
+    -B  --ambiguous                   Avoid ambiguous characters (e.g. 0, O).
+    -h  --help                        Print this help message.
+    -C                                Print the output in columns.
+    -1                                Print the output one line each.
+    -v                                Don't include vowels.
+*/
+
+#define FOR_pwgen
+#include "toys.h"
+
+GLOBALS(
+  char *r;
+)
+
+void pwgen_main(void)
+{
+  int length = 8, count, ii, jj, c, rand = 0, x = 0;
+  unsigned xx = 80, yy = 24;
+  char randbuf[16];
+
+  if (isatty(1)) terminal_size(&xx, &yy);
+  else toys.optflags |= FLAG_1;
+
+  if (toys.optc && (length = atolx(*toys.optargs))>sizeof(toybuf))
+    error_exit("bad length");
+  if (toys.optc>1) count = atolx(toys.optargs[1]);
+  else count = FLAG(1) ? 1 : (xx/(length+1))*(yy-1);
+
+  for (jj = 0; jj<count; jj++) {
+    for (ii = 0; ii<length;) {
+      // Don't fetch more random than necessary, give each byte 2 tries to fit
+      if (!rand) xgetrandom(randbuf, rand = sizeof(randbuf), 0);
+      c = 33+randbuf[--rand]%93; // remainder 69 makes >102 less likely
+      if (FLAG(s)) randbuf[rand] = 0;
+
+      if (c>='A' && c<='Z') {
+        if (FLAG(A)) continue;
+        // take out half the capital letters to be more human readable
+        else c |= (0x80&randbuf[rand])>>2;
+      }
+      if (FLAG(0) && c>='0' && c<='9') continue;
+      if (FLAG(B) && strchr("0O1lI'`.,", c)) continue;
+      if (FLAG(v) && strchr("aeiou", tolower(c))) continue;
+      if (!FLAG(y) || (0x80&randbuf[rand]))
+        if (c<'0' || (c>'9' && c<'A') || (c>'Z' && c<'a') || c>'z') continue;
+      if (TT.r && strchr(TT.r, c)) continue;
+
+      toybuf[ii++] = c;
+    }
+    if (FLAG(1) || (x += length+1)+length>=xx) x = 0;
+    xprintf("%.*s%c", length, toybuf, x ? ' ' : '\n');
+  }
+  if (x) xputc('\n');
+}
diff --git a/toys/other/readlink.c b/toys/other/readlink.c
index 9ebf68d..bae0953 100644
--- a/toys/other/readlink.c
+++ b/toys/other/readlink.c
@@ -3,6 +3,7 @@
  * Copyright 2007 Rob Landley <rob@landley.net>
 
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_REALPATH(NEWTOY(realpath, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 
 config READLINK
   bool "readlink"
@@ -19,9 +20,18 @@
     -m	Ignore missing entries, show where it would be
     -n	No trailing newline
     -q	Quiet (no output, just error code)
+
+config REALPATH
+  bool "realpath"
+  default y
+  help
+    usage: realpath FILE...
+
+    Display the canonical absolute pathname
 */
 
 #define FOR_readlink
+#define FORCE_FLAGS
 #include "toys.h"
 
 void readlink_main(void)
@@ -41,3 +51,9 @@
     } else toys.exitval = 1;
   }
 }
+
+void realpath_main(void)
+{
+  toys.optflags = FLAG_f;
+  readlink_main();
+}
diff --git a/toys/other/realpath.c b/toys/other/realpath.c
deleted file mode 100644
index 8f75d3f..0000000
--- a/toys/other/realpath.c
+++ /dev/null
@@ -1,26 +0,0 @@
-/* realpath.c - Return the canonical version of a pathname
- *
- * Copyright 2012 Andre Renaud <andre@bluewatersys.com>
-
-USE_REALPATH(NEWTOY(realpath, "<1", TOYFLAG_USR|TOYFLAG_BIN))
-
-config REALPATH
-  bool "realpath"
-  default y
-  help
-    usage: realpath FILE...
-
-    Display the canonical absolute pathname
-*/
-
-#include "toys.h"
-
-void realpath_main(void)
-{
-  char **s = toys.optargs;
-
-  for (s = toys.optargs; *s; s++) {
-    if (!realpath(*s, toybuf)) perror_msg_raw(*s);
-    else xputs(toybuf);
-  }
-}
diff --git a/toys/other/rtcwake.c b/toys/other/rtcwake.c
new file mode 100644
index 0000000..a382ee5
--- /dev/null
+++ b/toys/other/rtcwake.c
@@ -0,0 +1,118 @@
+/* rtcwake.c - enter sleep state until given time.
+ *
+ * Copyright 2020 The Android Open Source Project
+
+USE_RTCWAKE(NEWTOY(rtcwake, "(list-modes);(auto)a(device)d:(local)l(mode)m:(seconds)s#(time)t#(utc)u(verbose)v[!alu]", TOYFLAG_USR|TOYFLAG_BIN))
+
+config RTCWAKE
+  bool "rtcwake"
+  default y
+  help
+    usage: rtcwake [-aluv] [-d FILE] [-m MODE] [-s SECS] [-t UNIX]
+
+    Enter the given sleep state until the given time.
+
+    -a	RTC uses time specified in /etc/adjtime
+    -d FILE	Device to use (default /dev/rtc)
+    -l	RTC uses local time
+    -m	Mode (--list-modes to see those supported by your kernel):
+    	  standby  S1: default              mem     S3: suspend to RAM
+    	  disk     S4: suspend to disk      off     S5: power off
+    	  disable  Cancel current alarm     freeze  stop processes/processors
+    	  no       just set wakeup time     on      just poll RTC for alarm
+    	  show     just show current alarm
+    -s SECS	Wake SECS seconds from now
+    -t UNIX	Wake UNIX seconds from epoch
+    -u	RTC uses UTC
+    -v	Verbose
+*/
+
+#define FOR_rtcwake
+#include "toys.h"
+#include <linux/rtc.h>
+
+GLOBALS(
+  long t, s;
+  char *m, *d;
+)
+
+void rtcwake_main(void)
+{
+  struct rtc_wkalrm *alarm = (void *)(toybuf+2048);
+  struct tm rtc_tm;
+  time_t now, rtc_now, then;
+  int fd, utc;
+
+  if (FLAG(list_modes)) {
+    printf("off no on disable show %s",
+      xreadfile("/sys/power/state", toybuf, 2048));
+    return;
+  }
+
+  // util-linux defaults to "suspend", even though I don't have anything that
+  // supports that (testing everything from a ~2010 laptop to a 2019 desktop).
+  if (!TT.m) TT.m = "suspend";
+
+  if (FLAG(u)) utc = 1;
+  else if (FLAG(l)) utc = 0;
+  else utc = !readfile("/etc/adjtime", toybuf, 2048) || !!strstr(toybuf, "UTC");
+  if (FLAG(v)) xprintf("RTC time: %s\n", utc ? "UTC" : "local");
+
+  if (!TT.d) TT.d = "/dev/rtc0";
+  if (FLAG(v)) xprintf("Device: %s\n", TT.d);
+  fd = xopen(TT.d, O_RDWR);
+
+  now = time(0);
+  xioctl(fd, RTC_RD_TIME, &rtc_tm);
+  rtc_now = xmktime(&rtc_tm, utc);
+  if (FLAG(v)) {
+    xprintf("System time:\t%lld / %s", (long long)now, ctime(&now));
+    xprintf("RTC time:\t%lld / %s", (long long)rtc_now, ctime(&rtc_now));
+  }
+
+  if (!strcmp(TT.m, "show")) { // Don't suspend, just show current alarm.
+    xioctl(fd, RTC_WKALM_RD, alarm);
+    if (!alarm->enabled) xputs("alarm: off");
+    else {
+      if ((then = mktime((void *)&alarm->time)) < 0) perror_exit("mktime");
+      xprintf("alarm: on %s", ctime(&then));
+    }
+    return;
+  } else if (!strcmp(TT.m, "disable")) { // Cancel current alarm.
+    xioctl(fd, RTC_WKALM_RD, alarm);
+    alarm->enabled = 0;
+    xioctl(fd, RTC_WKALM_SET, alarm);
+    return;
+  }
+
+  if (FLAG(s)) then = rtc_now + TT.s + 1; // strace shows util-linux adds 1.
+  else if (FLAG(t)) {
+    then = TT.t + (rtc_now - now);
+    if (then<=rtc_now) error_exit("rtc %lld >= %ld", (long long)rtc_now, TT.t);
+  } else help_exit("-m %s needs -s or -t", TT.m);
+  if (FLAG(v)) xprintf("Wake time:\t%lld / %s", (long long)then, ctime(&then));
+
+  if (!(utc ? gmtime_r : localtime_r)(&then, (void *)&alarm->time))
+    error_exit("%s failed", utc ? "gmtime_r" : "localtime_r");
+
+  alarm->enabled = 1;
+  xioctl(fd, RTC_WKALM_SET, alarm);
+  sync();
+
+  xprintf("wakeup using \"%s\" from %s at %s", TT.m, TT.d, ctime(&then));
+  msleep(10);
+
+  if (!strcmp(TT.m, "no")); // Don't suspend, just set wakeup time.
+  else if (!strcmp(TT.m, "on")) { // Don't suspend, poll RTC for alarm.
+    unsigned long data = 0;
+
+    if (FLAG(v)) xputs("Reading RTC...");
+    while (!(data & RTC_AF)) {
+      if (read(fd, &data, sizeof(data)) != sizeof(data)) perror_exit("read");
+      if (FLAG(v)) xprintf("... %s: %lx\n", TT.d, data);
+    }
+  } else if (!strcmp(TT.m, "off")) xexec((char *[]){"poweroff", 0});
+  // Everything else lands here for one final step. The write will fail with
+  // EINVAL if the mode is not supported.
+  else xwrite(xopen("/sys/power/state", O_WRONLY), TT.m, strlen(TT.m));
+}
diff --git a/toys/other/setsid.c b/toys/other/setsid.c
index 7067242..654ce7a 100644
--- a/toys/other/setsid.c
+++ b/toys/other/setsid.c
@@ -24,14 +24,15 @@
 {
   int i;
 
-  // This must be before vfork() or tcsetpgrp() will hang waiting for parent.
-  setpgid(0, 0);
-
   // setsid() fails if we're already session leader, ala "exec setsid" from sh.
   // Second call can't fail, so loop won't continue endlessly.
   while (setsid()<0) {
-    pid_t pid = XVFORK();
+    pid_t pid;
 
+    // This must be before vfork() or tcsetpgrp() will hang waiting for parent.
+    setpgid(0, 0);
+
+    pid = XVFORK();
     if (pid) {
       i = 0;
       if (FLAG(w)) {
diff --git a/toys/other/sha3sum.c b/toys/other/sha3sum.c
new file mode 100644
index 0000000..ee951e0
--- /dev/null
+++ b/toys/other/sha3sum.c
@@ -0,0 +1,107 @@
+/* sha3sum.c - Keccak-f[1600] permutation, sponge construction
+ *
+ * Copyright 2014 David Leon Gil <coruus@gmail.com>
+ *
+ * https://keccak.team/files/Keccak-reference-3.0.pdf
+ * https://csrc.nist.gov/publications/detail/fips/202/final
+ * https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf
+
+// Depends on FLAG(b) being 4
+USE_SHA3SUM(NEWTOY(sha3sum, "bSa#<128>512=224", TOYFLAG_USR|TOYFLAG_BIN))
+
+config SHA3SUM
+  bool "sha3sum"
+  default y
+  help
+    usage: sha3sum [-S] [-a BITS] [FILE...]
+
+    Hash function du jour.
+
+    -a	Produce a hash BITS long (default 224)
+    -b	Brief (hash only, no filename)
+    -S	Use SHAKE termination byte instead of SHA3 (ask FIPS why)
+*/
+
+#define FOR_sha3sum
+#include "toys.h"
+
+GLOBALS(
+  long a;
+  unsigned long long rc[24];
+)
+
+static const char rho[] =
+  {1,3,6,10,15,21,28,36,45,55,2,14,27,41,56,8,25,43,62,18,39,61,20,44};
+static const char pi[] =
+  {10,7,11,17,18,3,5,16,8,21,24,4,15,23,19,13,12,2,20,14,22,9,6,1};
+static const char rcpack[] =
+  {0x33,0x07,0xdd,0x16,0x38,0x1b,0x7b,0x2b,0xad,0x6a,0xce,0x4c,0x29,0xfe,0x31,
+   0x68,0x9d,0xb0,0x8f,0x2f,0x0a};
+
+static void keccak(unsigned long long *a)
+{
+  unsigned long long b[5] = {0}, t;
+  int i, x, y;
+
+  for (i = 0; i < 24; i++) {
+    for (x = 0; x<5; x++) for (b[x] = 0, y = 0; y<25; y += 5) b[x] ^= a[x+y];
+    for (x = 0; x<5; x++) for (y = 0; y<25; y += 5) {
+      t = b[(x+1)%5];
+      a[y+x] ^= b[(x+4)%5]^(t<<1|t>>63);
+    }
+    for (t = a[1], x = 0; x<24; x++) {
+      *b = a[pi[x]];
+      a[pi[x]] = (t<<rho[x])|(t>>(64-rho[x]));
+      t = *b;
+    }
+    for (y = 0; y<25; y += 5) {
+      for (x = 0; x<5; x++) b[x] = a[y + x];
+      for (x = 0; x<5; x++) a[y + x] = b[x]^((~b[(x+1)%5])&b[(x+2)%5]);
+    }
+    *a ^= TT.rc[i];
+  }
+}
+
+static void do_sha3sum(int fd, char *name)
+{
+  int span, ii, len, rate = 200-TT.a/4;
+  char *ss = toybuf, buf[200];
+
+  memset(buf, 0, sizeof(buf));
+  for (len = 0;; ss += rate) {
+    if ((span = len-(ss-toybuf))<rate) {
+      memcpy(toybuf, ss, span);
+      len = span += readall(fd, (ss = toybuf)+span, sizeof(toybuf)-span);
+    }
+    if (span>rate) span = rate;
+    for (ii = 0; ii<span; ii++) buf[ii] ^= ss[ii];
+    if (rate!=span) {
+      buf[span] ^= FLAG(S) ? 0x1f : 0x06;
+      buf[rate-1] ^= 0x80;
+    }
+    keccak((void *)buf);
+    if (span<rate) break;
+  }
+
+  for (ii = 0; ii<TT.a/8; ) {
+    printf("%02x", buf[ii%rate]);
+    if (!(++ii%rate)) keccak((void *)buf);
+  }
+  memset(buf, 0, sizeof(buf));
+
+  // Depends on FLAG(b) being 4
+  xprintf("  %s\n"+FLAG(b), name);
+}
+
+// TODO test 224 256 384 512, and shake 128 256
+void sha3sum_main(void)
+{
+  int i, j, k;
+  char *s;
+
+  // Decompress RC table
+  for (s = (void *)rcpack, i = 127; i; s += 3) for (i>>=1,k = j = 0; k<24; k++)
+    if (1&(s[k>>3]>>(7-(k&7)))) TT.rc[k] |= 1ULL<<i;
+
+  loopfiles(toys.optargs, do_sha3sum);
+}
diff --git a/toys/other/shred.c b/toys/other/shred.c
index 1bac75c..3911e24 100644
--- a/toys/other/shred.c
+++ b/toys/other/shred.c
@@ -37,7 +37,7 @@
 {
   char **try;
 
-  if (!(toys.optflags & FLAG_n)) TT.n++;
+  if (!FLAG(n)) TT.n++;
 
   // We don't use loopfiles() here because "-" isn't stdin, and want to
   // respond to files we can't open via chmod.
@@ -47,7 +47,7 @@
     int fd = open(*try, O_RDWR), iter = 0, throw;
 
     // do -f chmod if necessary
-    if (fd == -1 && (toys.optflags & FLAG_f)) {
+    if (fd == -1 && FLAG(f)) {
       chmod(*try, 0600);
       fd = open(*try, O_RDWR);
     }
@@ -70,7 +70,7 @@
 
       if (pos >= len) {
         pos = -1;
-        if (++iter == TT.n && (toys.optargs && FLAG_z)) {
+        if (++iter == TT.n && FLAG(z)) {
           memset(toybuf, 0, sizeof(toybuf));
           continue;
         }
@@ -88,14 +88,12 @@
       // Determine length, read random data if not zeroing, write.
 
       throw = sizeof(toybuf);
-      if (toys.optflags & FLAG_x)
-        if (len-pos < throw) throw = len-pos;
+      if (FLAG(x) && len-pos < throw) throw = len-pos;
 
       if (iter != TT.n) xgetrandom(toybuf, throw, 0);
       if (throw != writeall(fd, toybuf, throw)) perror_msg_raw(*try);
       pos += throw;
     }
-    if (toys.optflags & FLAG_u)
-      if (unlink(*try)) perror_msg("unlink '%s'", *try);
+    if (FLAG(u) && unlink(*try)) perror_msg("unlink '%s'", *try);
   }
 }
diff --git a/toys/other/stat.c b/toys/other/stat.c
index 37dd180..98f27ed 100644
--- a/toys/other/stat.c
+++ b/toys/other/stat.c
@@ -33,8 +33,8 @@
     The valid format escape sequences for filesystems:
     %a  Available blocks    |%b  Total blocks       |%c  Total inodes
     %d  Free inodes         |%f  Free blocks        |%i  File system ID
-    %l  Max filename length |%n  File name          |%s  Fragment size
-    %S  Best transfer size  |%t  FS type (hex)      |%T  FS type (driver name)
+    %l  Max filename length |%n  File name          |%s  Best transfer size
+    %S  Actual block size   |%t  FS type (hex)      |%T  FS type (driver name)
 */
 
 #define FOR_stat
@@ -141,15 +141,8 @@
   else if (type == 'c') out('u', statfs->f_files);
   else if (type == 'd') out('u', statfs->f_ffree);
   else if (type == 'f') out('u', statfs->f_bfree);
-  else if (type == 'l') {
-#ifdef __APPLE__
-    // TODO: move this into portability.c somehow, or just use this everywhere?
-    // (glibc and bionic will just re-do the statfs and return f_namelen.)
-    out('d', pathconf(TT.file, _PC_NAME_MAX));
-#else
-    out('d', statfs->f_namelen);
-#endif
-  } else if (type == 't') out('x', statfs->f_type);
+  else if (type == 'l') out('d', pathconf(TT.file, _PC_NAME_MAX));
+  else if (type == 't') out('x', statfs->f_type);
   else if (type == 'T') strout(fs_type_name(statfs));
   else if (type == 'i') {
     int *val = (int *) &statfs->f_fsid;
@@ -157,8 +150,8 @@
 
     sprintf(buf, "%08x%08x", val[0], val[1]);
     strout(buf);
-  } else if (type == 's') out('d', statfs->f_frsize);
-  else if (type == 'S') out('d', statfs->f_bsize);
+  } else if (type == 's') out('d', statfs_bsize(statfs));
+  else if (type == 'S') out('d', statfs_frsize(statfs));
   else strout("?");
 }
 
diff --git a/toys/other/timeout.c b/toys/other/timeout.c
index 55b3dca..1e12530 100644
--- a/toys/other/timeout.c
+++ b/toys/other/timeout.c
@@ -55,7 +55,7 @@
 
 // timeval inexplicably makes up a new type for microseconds, despite timespec's
 // nanoseconds field (needing to store 1000* the range) using "long". Bravo.
-void xparsetimeval(char *s, struct timeval *tv)
+static void xparsetimeval(char *s, struct timeval *tv)
 {
   long ll;
 
diff --git a/toys/other/vmstat.c b/toys/other/vmstat.c
index 44f73d4..d5cf569 100644
--- a/toys/other/vmstat.c
+++ b/toys/other/vmstat.c
@@ -78,7 +78,7 @@
   int i, loop_delay = 0, loop_max = 0;
   unsigned loop, rows = 25, page_kb = sysconf(_SC_PAGESIZE)/1024;
   char *headers="r\0b\0swpd\0free\0buff\0cache\0si\0so\0bi\0bo\0in\0cs\0us\0"
-                "sy\0id\0wa", lengths[] = {2,2,6,6,6,6,4,4,5,5,4,4,2,2,2,2};
+                "sy\0id\0wa", lengths[] = {2,2,7,7,6,7,5,5,5,5,5,5,2,2,2,2};
 
   memset(top, 0, sizeof(top));
   if (toys.optc) loop_delay = atolx_range(toys.optargs[0], 0, INT_MAX);
@@ -98,7 +98,7 @@
       if (!(toys.optflags&FLAG_n) && isatty(1)) terminal_size(0, &rows);
       else rows = 0;
 
-      printf("procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----\n");
+      printf("procs ------------memory------------ ----swap--- -----io---- ---system-- ----cpu----\n");
       for (i=0; i<sizeof(lengths); i++) {
         printf(" %*s"+!i, lengths[i], header);
         header += strlen(header)+1;
diff --git a/toys/other/watch.c b/toys/other/watch.c
index fa5e71c..af65004 100644
--- a/toys/other/watch.c
+++ b/toys/other/watch.c
@@ -33,7 +33,7 @@
 )
 
 // When a child process exits, stop tracking them. Handle errors for -be
-void watch_child(int sig)
+static void watch_child(int sig)
 {
   int status;
   pid_t pid = wait(&status);
@@ -41,8 +41,8 @@
   status = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+127;
   if (status) {
     // TODO should this be beep()?
-    if (toys.optflags&FLAG_b) putchar('\b');
-    if (toys.optflags&FLAG_e) {
+    if (FLAG(b)) putchar('\b');
+    if (FLAG(e)) {
       printf("Exit status %d\r\n", status);
       tty_reset();
       _exit(status);
@@ -55,7 +55,7 @@
 
 // Return early for low-ascii characters with special behavior,
 // discard remaining low ascii, escape other unprintable chars normally
-int watch_escape(FILE *out, int cols, int wc)
+static int watch_escape(FILE *out, int cols, int wc)
 {
   if (wc==27 || (wc>=7 && wc<=13)) return -1;
   if (wc < 32) return 0;
@@ -99,7 +99,7 @@
       start_redraw(&width, &height);
 
       // redraw the header
-      if (!(toys.optflags&FLAG_t)) {
+      if (!FLAG(t)) {
         time_t t = time(0);
         int pad, ctimelen;
 
diff --git a/toys/other/watchdog.c b/toys/other/watchdog.c
new file mode 100644
index 0000000..0402d3e
--- /dev/null
+++ b/toys/other/watchdog.c
@@ -0,0 +1,51 @@
+/* watchdog - start a watchdog timer with configurable kick frequencies
+ *
+ * Copyright 2019 Chris Sarra <chrissarra@google.com>
+ *
+ * See kernel.org/doc/Documentation/watchdog/watchdog-api.txt
+
+USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
+
+config WATCHDOG
+  bool "watchdog"
+  default y
+  help
+    usage: watchdog [-F] [-t UPDATE] [-T DEADLINE] DEV
+
+    Start the watchdog timer at DEV with optional timeout parameters.
+
+    -F	run in the foreground (do not daemonize)
+    -t	poke watchdog every UPDATE seconds (default 4)
+    -T	reboot if not poked for DEADLINE seconds (default 60)
+*/
+
+#define FOR_watchdog
+#include "toys.h"
+#include "linux/watchdog.h"
+
+GLOBALS(
+  long T, t;
+
+  int fd;
+)
+
+static void safe_shutdown(int ignored)
+{
+  write(TT.fd, "V", 1);
+  close(TT.fd);
+  error_exit("safely exited watchdog.");
+}
+
+void watchdog_main(void)
+{
+  if (!FLAG(F)) xvdaemon();
+  xsignal(SIGTERM, safe_shutdown);
+  xsignal(SIGINT, safe_shutdown);
+  xioctl(TT.fd = xopen(*toys.optargs, O_WRONLY), WDIOC_SETTIMEOUT, &TT.T);
+
+  // Now that we've got the watchdog device open, kick it periodically.
+  for (;;) {
+    write(TT.fd, "", 1);
+    sleep(TT.t);
+  }
+}
diff --git a/toys/other/yes.c b/toys/other/yes.c
index 773a5a8..8edba0a 100644
--- a/toys/other/yes.c
+++ b/toys/other/yes.c
@@ -2,7 +2,7 @@
  *
  * Copyright 2007 Rob Landley <rob@landley.net>
 
-USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_YES(NEWTOY(yes, NULL, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
 
 config YES
   bool "yes"
diff --git a/toys/pending/bootchartd.c b/toys/pending/bootchartd.c
index 1fe6aff..3590c81 100644
--- a/toys/pending/bootchartd.c
+++ b/toys/pending/bootchartd.c
@@ -30,11 +30,10 @@
 
 GLOBALS(
   char buf[32];
-  long smpl_period_usec;
+  long msec;
   int proc_accounting;
-  int is_login;
 
-  pid_t cur_pid;
+  pid_t pid;
 )
 
 static void dump_data_in_file(char *fname, int wfd)
@@ -75,7 +74,7 @@
       toybuf[len] = '\0';
       close(fd);
       fputs(toybuf, fp);
-      if (!TT.is_login) continue;
+      if (TT.pid != 1) continue;
       if ((ptr = strchr(toybuf, '('))) {
         char *tmp = strchr(++ptr, ')');
 
@@ -94,31 +93,23 @@
 static int parse_config_file(char *fname)
 {
   size_t len = 0;
-  char  *line = NULL;
+  char  *line = 0;
   FILE *fp = fopen(fname, "r");
 
   if (!fp) return 0;
-  for (;getline(&line, &len, fp) != -1; line = NULL) {
+  for (;getline(&line, &len, fp) != -1; line = 0) {
     char *ptr = line;
 
     while (*ptr == ' ' || *ptr == '\t') ptr++;
     if (!*ptr || *ptr == '#' || *ptr == '\n') continue;
-    if (!strncmp(ptr, "SAMPLE_PERIOD", strlen("SAMPLE_PERIOD"))) {
-      double smpl_val;
+    if (strstart(&ptr, "SAMPLE_PERIOD=")) {
+      double dd;
 
-      if ((ptr = strchr(ptr, '='))) ptr += 1;
-      else continue;
-      sscanf(ptr, "%lf", &smpl_val);
-      TT.smpl_period_usec = smpl_val * 1000000;
-      if (TT.smpl_period_usec <= 0) TT.smpl_period_usec = 1;
-    }
-    if (!strncmp(ptr, "PROCESS_ACCOUNTING", strlen("PROCESS_ACCOUNTING"))) {
-      if ((ptr = strchr(ptr, '='))) ptr += 1;
-      else continue;
-      sscanf(ptr, "%s", toybuf);  // string will come with double quotes.
-      if (!(strncmp(toybuf+1, "on", strlen("on"))) ||
-          !(strncmp(toybuf+1, "yes", strlen("yes")))) TT.proc_accounting = 1;
-    }
+      sscanf(ptr, "%lf", &dd);
+      if ((TT.msec = dd*1000)<1) TT.msec = 1;
+    } else if (strstart(&ptr, "PROCESS_ACCOUNTING="))
+      if (strstart(&ptr, "\"on\"") || strstart(&ptr, "\"yes\""))
+        TT.proc_accounting = 1;
     free(line);
   }
   fclose(fp);
@@ -148,7 +139,7 @@
   int proc_diskstats_fd = xcreate("proc_diskstats.log",  
       O_WRONLY | O_CREAT | O_TRUNC, 0644);
   FILE *proc_ps_fp = xfopen("proc_ps.log", "w");
-  long tcnt = 60 * 1000 * 1000 / TT.smpl_period_usec;
+  long tcnt = 60 * 1000 / TT.msec;
 
   if (tcnt <= 0) tcnt = 1;
   if (TT.proc_accounting) {
@@ -160,10 +151,10 @@
   memset(TT.buf, 0, sizeof(TT.buf));
   while (--tcnt && !toys.signal) {
     int i = 0, j = 0, fd = open("/proc/uptime", O_RDONLY);
-    if (fd < 0) goto wait_usec;
+    if (fd < 0) goto wait;
     char *line = get_line(fd);
 
-    if (!line)  goto wait_usec;
+    if (!line)  goto wait;
     while (line[i] != ' ') {
       if (line[i] == '.') {
         i++;
@@ -172,18 +163,17 @@
       TT.buf[j++] = line[i++];
     }
     TT.buf[j++] = '\n';
-    TT.buf[j] = '\0';
+    TT.buf[j] = 0;
     free(line);
     close(fd);
     dump_data_in_file("/proc/stat", proc_stat_fd);
     dump_data_in_file("/proc/diskstats", proc_diskstats_fd);
     // stop proc dumping in 2 secs if getty or gdm, kdm, xdm found 
     if (dump_proc_data(proc_ps_fp))
-      if (tcnt > 2 * 1000 * 1000 / TT.smpl_period_usec)
-        tcnt = 2 * 1000 * 1000 / TT.smpl_period_usec;
-    fflush(NULL);
-wait_usec:
-    usleep(TT.smpl_period_usec);
+      if (tcnt > 2 * 1000 / TT.msec) tcnt = 2 * 1000 / TT.msec;
+    fflush(0);
+wait:
+    msleep(TT.msec);
   }
   xclose(proc_stat_fd);
   xclose(proc_diskstats_fd);
@@ -240,7 +230,7 @@
 
 static int signal_pid(pid_t pid, char *name)
 {
-  if (pid != TT.cur_pid) kill(pid, SIGUSR1);
+  if (pid != TT.pid) kill(pid, SIGUSR1);
   return 0;
 }
 
@@ -249,10 +239,9 @@
   pid_t lgr_pid;
   int bchartd_opt = 0; // 0=PID1, 1=start, 2=stop, 3=init
 
-  TT.cur_pid = getpid();
-  TT.smpl_period_usec = 200 * 1000;
+  TT.pid = getpid();
+  TT.msec = 200;
 
-  TT.is_login = (TT.cur_pid == 1);
   if (*toys.optargs) {
     if (!strcmp("start", *toys.optargs)) bchartd_opt = 1;
     else if (!strcmp("stop", *toys.optargs)) bchartd_opt = 2;
@@ -265,7 +254,7 @@
       names_to_pid(process_name, signal_pid, 0);
       return;
     }
-  } else if (!TT.is_login) error_exit("not PID 1");
+  } else if (TT.pid != 1) error_exit("not PID 1");
 
   // Execute the code below for start or init or PID1 
   if (!parse_config_file("bootchartd.conf"))
diff --git a/toys/pending/chsh.c b/toys/pending/chsh.c
new file mode 100644
index 0000000..bf40978
--- /dev/null
+++ b/toys/pending/chsh.c
@@ -0,0 +1,77 @@
+/* chsh.c - Change login shell.
+ *
+ * Copyright 2021 Michael Christensen
+ *
+ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/chsh.html
+
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
+
+config CHSH
+  bool "chsh"
+  default y
+  help
+    usage: chsh [-s SHELL] [USER]
+
+    Change user's login shell.
+
+    -s	Use SHELL instead of prompting
+
+    Non-root users can only change their own shell to one listed in /etc/shells.
+*/
+
+#define FOR_chsh
+#include "toys.h"
+
+GLOBALS(
+  char *s;
+)
+
+void chsh_main()
+{
+  FILE *file;
+  char *user, *line, *shell, *encrypted;
+  struct passwd *passwd_info;
+  struct spwd *shadow_info;
+
+  // Get uid user information, may be discarded later
+
+  if ((user = *toys.optargs)) {
+    passwd_info = xgetpwnam(user);
+    if (geteuid() && strcmp(passwd_info->pw_name, user))
+      error_exit("Permission denied\n");
+  } else {
+    passwd_info = xgetpwuid(getuid());
+    user = passwd_info->pw_name;
+  }
+
+  // Get a password, encrypt it, wipe it, and check it
+  if (mlock(toybuf, sizeof(toybuf))) perror_exit("mlock");
+  if (!(shadow_info = getspnam(passwd_info->pw_name))) perror_exit("getspnam");
+  if (read_password(toybuf, sizeof(toybuf), "Password: ")) perror_exit("woaj"); //xexit();
+  if (!(encrypted = crypt(toybuf, shadow_info->sp_pwdp))) perror_exit("crypt");
+  memset(toybuf, 0, sizeof(toybuf));
+  munlock(toybuf, sizeof(toybuf)); // prevents memset from "optimizing" away.
+  if (strcmp(encrypted, shadow_info->sp_pwdp)) perror_exit("Bad password");
+
+  // Get new shell (either -s or interactive)
+  file = xfopen("/etc/shells", "r");
+  if (toys.optflags) shell = TT.s;
+  else {
+    xprintf("Changing the login shell for %s\n"
+            "Enter the new value, or press ENTER for default\n"
+            "    Login shell [%s]: ", user, passwd_info->pw_shell);
+    if (!(shell = xgetline(stdin))) xexit();
+  }
+
+  // Verify supplied shell in /etc/shells, or get default shell
+  if (strlen(shell))
+    while ((line = xgetline(file)) && strcmp(shell, line)) free(line);
+  else do line = xgetline(file); while (line && *line != '/');
+  if (!line) error_exit("Shell not found in '/etc/shells'");
+
+  // Update /etc/passwd
+  passwd_info->pw_shell = line;
+  if (-1 == update_password("/etc/passwd", user, NULL)) perror_exit("Failed to remove passwd entry");
+  file = xfopen("/etc/passwd", "a");
+  if (putpwent(passwd_info, file)) perror_exit("putwent");
+}
diff --git a/toys/pending/dd.c b/toys/pending/dd.c
index 5b624fd..199e591 100644
--- a/toys/pending/dd.c
+++ b/toys/pending/dd.c
@@ -59,7 +59,7 @@
     unsigned long long offset;
   } in, out;
   unsigned conv, iflag, oflag;
-);
+)
 
 struct dd_flag {
   char *name;
@@ -98,8 +98,8 @@
   }
 }
 
-static void dd_sigint(int sig) {
-  status();
+static void dd_sigint(int sig)
+{
   toys.exitval = sig|128;
   xexit();
 }
@@ -174,8 +174,9 @@
   }
   if (bs) TT.in.sz = TT.out.sz = bs;
 
-  signal(SIGINT, dd_sigint);
-  signal(SIGUSR1, generic_signal);
+  sigatexit(status);
+  xsignal(SIGINT, dd_sigint);
+  xsignal(SIGUSR1, status);
   gettimeofday(&TT.start, NULL);
 
   // For bs=, in/out is done as it is. so only in.sz is enough.
@@ -223,8 +224,11 @@
   bs = TT.out.offset;
   if (!(TT.oflag & _DD_oflag_seek_bytes)) bs *= TT.out.sz;
   if (bs) {
+    struct stat st;
+
     xlseek(TT.out.fd, bs, SEEK_CUR);
-    if (trunc && ftruncate(TT.out.fd, bs)) perror_exit("ftruncate");
+    if (trunc && !fstat(TT.out.fd, &st) && S_ISREG(st.st_mode)
+      && ftruncate(TT.out.fd, bs)) perror_exit("unexpected ftruncate failure");
   }
 
   unsigned long long bytes_left = TT.c_count;
@@ -235,13 +239,6 @@
     int chunk = bytes_left < TT.in.sz ? bytes_left : TT.in.sz;
     ssize_t n;
 
-    // Show progress and exit on SIGINT or just continue on SIGUSR1.
-    if (toys.signal) {
-      status();
-      if (toys.signal==SIGINT) exit_signal(toys.signal);
-      toys.signal = 0;
-    }
-
     TT.in.bp = TT.in.buff + TT.in.count;
     if (TT.conv & _DD_conv_sync) memset(TT.in.bp, 0, TT.in.sz);
     if (!(n = read(TT.in.fd, TT.in.bp, chunk))) break;
@@ -285,6 +282,4 @@
   close(TT.in.fd);
   close(TT.out.fd);
   if (TT.in.buff) free(TT.in.buff);
-
-  status();
 }
diff --git a/toys/pending/dhcp.c b/toys/pending/dhcp.c
index 2dd392d..43e2942 100644
--- a/toys/pending/dhcp.c
+++ b/toys/pending/dhcp.c
@@ -428,7 +428,7 @@
     options_list[count].len = sizeof(uint32_t);
     options_list[count].val = xmalloc(sizeof(uint32_t));
     convtmp = strtou32(valstr);
-    if (convtmp < 0) error_exit("Invalid/wrong formated number %s", valstr);
+    if (convtmp < 0) error_exit("Invalid/wrong formatted number %s", valstr);
     convtmp = htonl(convtmp);
     memcpy(options_list[count].val, &convtmp, sizeof(uint32_t));
     break;
@@ -1136,12 +1136,14 @@
       msgopt_list[count].len = strlen(toybuf);
       break;
     case DHCP_IPLIST:
+      options = &optptr[2];
       optlen = optptr[1];
       dest = toybuf;
       while (optlen) {
-        memcpy(&convtmp, &optptr[2], sizeof(uint32_t));
+        memcpy(&convtmp, options, sizeof(uint32_t));
         addr.s_addr = convtmp;
         dest += sprintf(dest, "%s ", inet_ntoa(addr));
+        options += 4;
         optlen -= 4;
       }
       *(dest - 1) = '\0';
diff --git a/toys/pending/dhcp6.c b/toys/pending/dhcp6.c
index 728dc7d..72cda4f 100644
--- a/toys/pending/dhcp6.c
+++ b/toys/pending/dhcp6.c
@@ -377,7 +377,7 @@
             if(!getaddrinfo(TT.req_ip, NULL, NULL,&res)) {
               dbg("Requesting IP: %s\n", TT.req_ip);
               memcpy (&TT.input_socket6, res->ai_addr, res->ai_addrlen);
-              memcpy(t+4, TT.input_socket6.sin6_addr.__in6_u.__u6_addr8, 16);
+              memcpy(t+4, TT.input_socket6.sin6_addr.s6_addr, 16);
             } else xprintf("Invalid IP: %s\n",TT.req_ip);
             freeaddrinfo(res);
           }
@@ -619,7 +619,7 @@
         if (TT.state == DHCP6SOLICIT) {
           if (mymsg->dhcp6.msgtype == DHCP6ADVERTISE ) {
             if (!validate_ids()) {
-              dbg("Invalid id recieved, solicit.\n");
+              dbg("Invalid id received, solicit.\n");
               TT.state = DHCP6SOLICIT;
               continue;
             }
@@ -640,7 +640,7 @@
         } else if (TT.state == DHCP6REQUEST || TT.state == DHCP6RENEW ) {
           if (mymsg->dhcp6.msgtype == DHCP6REPLY) {
             if (!validate_ids()) {
-              dbg("Invalid id recieved, %d.\n", TT.state);
+              dbg("Invalid id received, %d.\n", TT.state);
               TT.state = DHCP6REQUEST;
               continue;
             }
diff --git a/toys/pending/dhcpd.c b/toys/pending/dhcpd.c
index 5d14316..c5125e7 100644
--- a/toys/pending/dhcpd.c
+++ b/toys/pending/dhcpd.c
@@ -125,7 +125,7 @@
 GLOBALS(
     char *iface;
     long port;
-);
+)
 
 struct config_keyword {
   char *keyword;
@@ -631,12 +631,12 @@
     while(grp){
       while(*grp == ' ' || *grp == '\t') grp++;
       tp = strchr(grp, '/');
-      if (!tp) error_exit("wrong formated static route option");
+      if (!tp) error_exit("wrong formatted static route option");
       *tp = '\0';
       mask = strtol(++tp, &tp, 10);
-      if (striptovar(grp, (uint8_t*)&nip)<0) error_exit("wrong formated static route option");
+      if (striptovar(grp, (uint8_t*)&nip)<0) error_exit("wrong formatted static route option");
       while(*tp == ' ' || *tp == '\t' || *tp == '-') tp++;
-      if (striptovar(tp, (uint8_t*)&router)<0) error_exit("wrong formated static route option");
+      if (striptovar(tp, (uint8_t*)&router)<0) error_exit("wrong formatted static route option");
       options_list[count].val = xrealloc(options_list[count].val, options_list[count].len + 1 + mask/8 + 4);
       memcpy(((uint8_t*)options_list[count].val)+options_list[count].len, &mask, 1);
       options_list[count].len += 1;
@@ -1287,7 +1287,7 @@
         req_exp > gconfig.valid_lifetime) {
       if ((gconfig.pref_lifetime > gconfig.valid_lifetime)) {
         error_msg("The valid lifetime must be greater than the preferred lifetime, \
-            setting to valid lifetime", gconfig.valid_lifetime);
+            setting to valid lifetime %u", gconfig.valid_lifetime);
         return gconfig.valid_lifetime;
       }
       return gconfig.pref_lifetime;
@@ -1748,8 +1748,7 @@
           dbg("no or bad message type option, ignoring packet.\n");
           continue;
         }
-        if (!gstate.rcvd.rcvd_pkt6.transaction_id || 
-            memcmp(gstate.rcvd.rcvd_pkt6.transaction_id, transactionid, 3)) {
+        if (memcmp(gstate.rcvd.rcvd_pkt6.transaction_id, transactionid, 3)) {
           dbg("no or bad transaction id, ignoring packet.\n");
           continue;
         }
diff --git a/toys/pending/diff.c b/toys/pending/diff.c
index d7bb43c..e93c622 100644
--- a/toys/pending/diff.c
+++ b/toys/pending/diff.c
@@ -253,7 +253,7 @@
  *    classes of lines in file[1], with e.last = true on the last element of each class.
  *    The elements are ordered by serial within classes.
  * 4. Form the p vector stored in  p_vector. p_vector[i], if non-zero, now points in e vector
- *    to the begining of the equiv class of lines in file[1] equivalent to line
+ *    to the beginning of the equiv class of lines in file[1] equivalent to line
  *    i in file[0].
  * 5. Form the k-candidates as discribed in do_merge.
  * 6. Create a vector J[i] = j, such that i'th line in file[0] is j'th line of
diff --git a/toys/pending/fdisk.c b/toys/pending/fdisk.c
index 08cfa71..c29a49e 100644
--- a/toys/pending/fdisk.c
+++ b/toys/pending/fdisk.c
@@ -46,7 +46,7 @@
 #define sector(s) ((s) & 0x3f)
 #define cylinder(s, c) ((c) | (((s) & 0xc0) << 2))
 
-typedef off_t sector_t;
+typedef unsigned long long sector_t;
 
 struct partition {
   unsigned char boot_ind, head, sector, cyl, sys_ind, end_head,
@@ -149,7 +149,7 @@
 {
   int arg;       
   if (ioctl(dev_fd, BLKSSZGET, &arg) == 0) g_sect_size = arg;
-  if (toys.optflags & FLAG_b) {
+  if (FLAG(b)) {
     if (TT.sect_sz !=  512 && TT.sect_sz != 1024 && TT.sect_sz != 2048 &&
         TT.sect_sz != 4096)
     {
@@ -375,12 +375,12 @@
   read_sec_sz();
   sector_fac = g_sect_size/SECTOR_SIZE; //512 is hardware sector size.
   physical_HS(&h, &s); //physical dimensions may be diferent from HDIO_GETGEO
-  g_sectors = (toys.optflags & FLAG_S && TT.sectors)? TT.sectors :  s? s : disk.sectors?disk.sectors : 63;
-  g_heads = (toys.optflags & FLAG_H && TT.heads)? TT.heads : h? h : disk.heads? disk.heads : 255;
+  g_sectors = (FLAG(S) && TT.sectors) ? TT.sectors : s ? s : disk.sectors ? disk.sectors : 63;
+  g_heads = (FLAG(H) && TT.heads) ? TT.heads : h ? h : disk.heads ? disk.heads : 255;
   g_cylinders = total_number_sectors/(g_heads * g_sectors * sector_fac);
 
-  if (!g_cylinders) g_cylinders = toys.optflags & FLAG_C? TT.cylinders : 0;
-  if ((g_cylinders > ONE_K) && !(toys.optflags & (FLAG_l | FLAG_S)))
+  if (!g_cylinders) g_cylinders = FLAG(C) ? TT.cylinders : 0;
+  if ((g_cylinders > ONE_K) && !(FLAG(l) || FLAG(S)))
     xprintf("\nThe number of cylinders for this disk is set to %lu.\n"
         "There is nothing wrong with that, but this is larger than 1024,\n"
         "and could in certain setups cause problems.\n", g_cylinders);
@@ -433,7 +433,7 @@
 
   //Logical and Physical diff 
   if (g_cylinders <= ONE_K && (physbc != lbc || physbh != lbh || physbs != lbs)) {
-    xprintf("Partition %u has different physical/logical beginings (Non-Linux?): \n", partition+1);
+    xprintf("Partition %u has different physical/logical beginnings (Non-Linux?): \n", partition+1);
     xprintf("phys = (%u %u %u) ",physbc, physbh, physbs);
     xprintf("logical = (%u %u %u)\n", lbc, lbh, lbs);
   }
@@ -1157,7 +1157,7 @@
     xprintf("Partition %u doesn't have data area\n", idx+1);
     return;
   }
-  sprintf(mesg, "New begining of data (0 - %lld, default %lld): ", 
+  sprintf(mesg, "New beginning of data (0 - %lld, default %lld): ",
       (long long int)(end), (long long int)(start));
   new_start = ask_value(mesg, 0, end, start);
   if (new_start != start) {
@@ -1332,7 +1332,7 @@
     char *msg = "Expert Command ('m' for help): ";
     choice = 0x20 | read_input(msg, NULL);
     switch (choice) {
-      case 'b': //move data begining in partition
+      case 'b': //move data beginning in partition
         idx = ask_partition(num_parts);
         move_begning(idx - 1);
         break;
@@ -1447,7 +1447,7 @@
   while (fgets(buffer, ONE_K, fp)) {
     reset_entries();
     num_parts = 4;
-    memset(name, 0, sizeof(name));
+    memset(name, 0, sizeof(*name));
     if (sscanf(buffer, " %u %u %u %[^\n ]", &ma, &mi, &sz, name) != 4)
       continue;
       
@@ -1469,8 +1469,8 @@
   move_fd();
   if (TT.heads >= 256) TT.heads = 0;
   if (TT.sectors >= 64) TT.sectors = 0;
-  if (toys.optflags & FLAG_u) disp_unit_cyl = 0;
-  if (toys.optflags & FLAG_l) {
+  if (FLAG(u)) disp_unit_cyl = 0;
+  if (FLAG(l)) {
     if (!toys.optc) read_and_print_parts();
     else {
       while(*toys.optargs){
diff --git a/toys/pending/getty.c b/toys/pending/getty.c
index 51b5896..8c93a25 100644
--- a/toys/pending/getty.c
+++ b/toys/pending/getty.c
@@ -5,7 +5,7 @@
  *
  * No Standard.
 
-USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN))
+USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh", TOYFLAG_SBIN))
 
 config GETTY
   bool "getty"
@@ -13,6 +13,8 @@
   help
     usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]
 
+    Wait for a modem to dial into serial port, adjust baud rate, call login.
+
     -h    Enable hardware RTS/CTS flow control
     -L    Set CLOCAL (ignore Carrier Detect state)
     -m    Get baud rate from modem's CONNECT status message
@@ -25,108 +27,44 @@
     -I INITSTR  Send INITSTR before anything else
     -H HOST    Log HOST into the utmp file as the hostname
 */
+
 #define FOR_getty
 #include "toys.h"
-#include <utmp.h>
 
 GLOBALS(
-  char *issue_str;
-  char *login_str;
-  char *init_str;
-  char *host_str; 
-  long timeout;
-  
-  char *tty_name;  
-  int  speeds[20];
-  int  sc;              
+  char *f, *l, *I, *H;
+  long t;
+
+  char *tty_name, buff[128];
+  int speeds[20], sc;
   struct termios termios;
-  char buff[128];
 )
 
-#define CTL(x)        ((x) ^ 0100) 
+#define CTL(x)        ((x) ^ 0100)
 #define HOSTNAME_SIZE 32
 
-typedef void (*sighandler_t)(int);
-struct speed_mapper {
-  long speed;
-  speed_t code;
-};
-
-struct speed_mapper speedtab[] = {
-  {50, B50}, {75, B75}, {110, B110}, {134, B134}, {150, B150}, {200, B200},
-  {300, B300}, {600, B600}, {1200, B1200}, {1800, B1800}, {2400, B2400},
-  {4800, B4800}, {9600, B9600},
-#ifdef  B19200
-  {19200, B19200},
-#endif
-#ifdef  B38400
-  {38400, B38400},
-#endif
-#ifdef  EXTA
-  {19200, EXTA},
-#endif
-#ifdef  EXTB
-  {38400, B38400},
-#endif
-#ifdef B57600
-  {57600, B57600},
-#endif
-#ifdef B115200
-  {115200, B115200},
-#endif
-#ifdef B230400
-  {230400, B230400},
-#endif
-  {0, 0},
-};
-
-// Find speed from mapper array 
-static speed_t encode(char *s)
-{
-  struct speed_mapper *sp;
-  long speed = atolx(s);
-
-  if (!speed) return 0;
-  for (sp = speedtab; sp->speed; sp++) if (sp->speed == speed) return sp->code;
-  return (speed_t) -1;
-}
-
-static void get_speed(char *sp)
+static void parse_speeds(char *sp)
 {
   char *ptr;
 
   TT.sc = 0;
   while ((ptr = strsep(&sp, ","))) {
-    TT.speeds[TT.sc] = encode(ptr);
-    if (TT.speeds[TT.sc] < 0) perror_exit("bad speed");
+    TT.speeds[TT.sc] = atolx_range(ptr, 0, INT_MAX);
+    if (TT.speeds[TT.sc] < 0) perror_exit("bad speed %s", ptr);
     if (++TT.sc > 10) perror_exit("too many speeds, max is 10");
   }
 }
 
-// Parse args and set TERM env. variable
-static void parse_arguments(void)
-{
-  if (isdigit(**toys.optargs)) {
-    get_speed(*toys.optargs);
-    if (*++toys.optargs) TT.tty_name = xmprintf("%s", *toys.optargs);
-  } else {
-    TT.tty_name = xmprintf("%s", *toys.optargs);
-    if (*++toys.optargs) get_speed(*toys.optargs);
-  } 
-  if (*++toys.optargs) setenv("TERM", *toys.optargs, 1);
-}
-
-// Get controlling terminal and redirect stdio 
+// Get controlling terminal and redirect stdio
 static void open_tty(void)
 {
   if (strcmp(TT.tty_name, "-")) {
     if (*(TT.tty_name) != '/') TT.tty_name = xmprintf("/dev/%s", TT.tty_name);
     // Sends SIGHUP to all foreground process if Session leader don't die,Ignore
-    sighandler_t sig = signal(SIGHUP, SIG_IGN); 
+    void* handler = signal(SIGHUP, SIG_IGN);
     ioctl(0, TIOCNOTTY, 0); // Giveup if there is any controlling terminal
-    signal(SIGHUP, sig);
-    if ((setsid() < 0) && (getpid() != getsid(0))) 
-      perror_exit("setsid");
+    signal(SIGHUP, handler);
+    if ((setsid() < 0) && (getpid() != getsid(0))) perror_exit("setsid");
     xclose(0);
     xopen_stdio(TT.tty_name, O_RDWR|O_NDELAY|O_CLOEXEC);
     fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK); // Block read
@@ -143,17 +81,16 @@
   }
 }
 
-// Intialise terminal settings
 static void termios_init(void)
 {
-  if (tcgetattr(STDIN_FILENO, &TT.termios) < 0) perror_exit("tcgetattr");
+  if (tcgetattr(0, &TT.termios) < 0) perror_exit("tcgetattr");
   // Flush input and output queues, important for modems!
-  tcflush(STDIN_FILENO, TCIOFLUSH); 
+  tcflush(0, TCIOFLUSH);
   TT.termios.c_cflag &= (0|CSTOPB|PARENB|PARODD);
 #ifdef CRTSCTS
-  if (toys.optflags & FLAG_h) TT.termios.c_cflag |= CRTSCTS;
+  if (FLAG(h)) TT.termios.c_cflag |= CRTSCTS;
 #endif
-  if (toys.optflags & FLAG_L) TT.termios.c_cflag |= CLOCAL;
+  if (FLAG(L)) TT.termios.c_cflag |= CLOCAL;
   TT.termios.c_cc[VTIME] = 0;
   TT.termios.c_cc[VMIN] = 1;
   TT.termios.c_oflag = OPOST|ONLCR;
@@ -167,61 +104,40 @@
   TT.termios.c_cc[VKILL] = CTL('U');
   TT.termios.c_cc[VERASE] = 127; // CERASE
   TT.termios.c_iflag = ICRNL|IXON|IXOFF;
-  // set non-zero baud rate. Zero baud rate left it unchanged.
-  if (TT.speeds[0] != B0) cfsetspeed(&TT.termios, TT.speeds[0]); 
-  if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) 
-    perror_exit("tcsetattr");
+  // Set non-zero baud rate. Zero baud rate left it unchanged.
+  if (TT.speeds[0] != 0) xsetspeed(&TT.termios, TT.speeds[0]);
+  if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
 }
 
 // Get the baud rate from modems CONNECT mesage, Its of form <junk><BAUD><Junk>
 static void sense_baud(void)
 {
-  int vmin;
+  int vmin, speed;
   ssize_t size;
   char *ptr;
-  speed_t speed;
 
   vmin = TT.termios.c_cc[VMIN]; // Store old
   TT.termios.c_cc[VMIN] = 0; // No block even queue is empty.
-  if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) 
-    perror_exit("tcsetattr");
-  size = readall(STDIN_FILENO, TT.buff, sizeof(TT.buff)-1);
+  if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
+  size = readall(0, TT.buff, sizeof(TT.buff)-1);
   if (size > 0) {
     for (ptr = TT.buff; ptr < TT.buff+size; ptr++) {
       if (isdigit(*ptr)) {
-        speed = encode(ptr);
-        if (speed > 0) cfsetspeed(&TT.termios,speed);
+        speed = atolx_range(ptr, 0, INT_MAX);
+        if (speed > 0) xsetspeed(&TT.termios, speed);
         break;
       }
-    } 
+    }
   }
   TT.termios.c_cc[VMIN] = vmin; //restore old value
-  if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0)
-    perror_exit("tcsetattr");
-}
-
-// Just prompt for login name 
-void print_prompt(void)
-{
-  char *hostname;
-  struct utsname uts;
-
-  uname(&uts);
-  hostname = xstrdup(uts.nodename);
-  fputs(hostname, stdout);
-  fputs(" login: ", stdout);
-  fflush(NULL);
-  free(hostname);
-  hostname = NULL;
+  if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
 }
 
 // Print /etc/isuue with taking care of each escape sequence
-void write_issue(char *file)
+void write_issue(char *file, struct utsname *uts)
 {
   char buff[20] = {0,};
-  struct utsname u;
-  uname(&u);
-  int size, fd = open(TT.issue_str, O_RDONLY);
+  int fd = open(TT.f, O_RDONLY), size;
 
   if (fd < 0) return;
   while ((size = readall(fd, buff, 1)) > 0) {
@@ -229,24 +145,30 @@
 
     if (*ch == '\\' || *ch == '%') {
       if (readall(fd, buff, 1) <= 0) perror_exit("readall");
-      if (*ch == 's') fputs(u.sysname, stdout);
-      if (*ch == 'n'|| *ch == 'h') fputs(u.nodename, stdout);
-      if (*ch == 'r') fputs(u.release, stdout);
-      if (*ch == 'm') fputs(u.machine, stdout);
+      if (*ch == 's') fputs(uts->sysname, stdout);
+      if (*ch == 'n'|| *ch == 'h') fputs(uts->nodename, stdout);
+      if (*ch == 'r') fputs(uts->release, stdout);
+      if (*ch == 'm') fputs(uts->machine, stdout);
       if (*ch == 'l') fputs(TT.tty_name, stdout);
     } else xputc(*ch);
   }
 }
 
-// Read login name and print prompt and Issue file. 
+// Read login name and print prompt and Issue file.
 static int read_login_name(void)
 {
-  tcflush(STDIN_FILENO, TCIFLUSH); // Flush pending speed switches
-  int i = 0;
+  tcflush(0, TCIFLUSH); // Flush pending speed switches
+  while (1) {
+    struct utsname uts;
+    int i = 0;
 
-  while (1) { // Option -i will overide -f
-    if (!(toys.optflags & FLAG_i)) write_issue(TT.issue_str); 
-    print_prompt();
+    uname(&uts);
+
+    if (!FLAG(i)) write_issue(TT.f, &uts);
+
+    printf("%s login: ", uts.nodename);
+    xflush(1);
+
     TT.buff[0] = getchar();
     if (!TT.buff[0] && TT.sc > 1) return 0; // Switch speed
     if (TT.buff[0] == '\n') continue;
@@ -259,76 +181,68 @@
   return 1;
 }
 
-// Put hostname entry in utmp file
 static void utmp_entry(void)
 {
-  struct utmp entry;
-  struct utmp *utp_ptr;
-  pid_t pid = getpid();
-  char *utmperr = "can't make utmp entry, host length greater than UT_HOSTSIZE(256)";
+  struct utmpx entry = {.ut_pid = getpid()}, *ep;
+  int fd;
 
-  utmpname(_PATH_UTMP);
-  setutent(); // Starts from start
-  while ((utp_ptr = getutent())) 
-    if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
-  if (!utp_ptr) { 
-    entry.ut_type = LOGIN_PROCESS;
-    entry.ut_pid = getpid();
-    xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + 
-        strlen("/dev/"), UT_LINESIZE);
-    time((time_t *)&entry.ut_time);
-    xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE);
-    if (strlen(TT.host_str) > UT_HOSTSIZE) perror_msg_raw(utmperr);
-    else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE);
-    setutent();
-    pututline(&entry);
-    return;
-  }
-  xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"), UT_LINESIZE);
-  xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE);
-  if (strlen(TT.host_str) > UT_HOSTSIZE) perror_msg_raw(utmperr);
-  else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE);
-  time((time_t *)&entry.ut_time);
-  setutent();
-  pututline(&entry);
+  // We're responsible for ensuring that the utmp file exists.
+  if (access(_PATH_UTMP, F_OK) && (fd = open(_PATH_UTMP, O_CREAT, 0664)) != -1)
+    close(fd);
+
+  // Find any existing entry.
+  setutxent();
+  while ((ep = getutxent()))
+    if (ep->ut_pid == entry.ut_pid && ep->ut_type >= INIT_PROCESS) break;
+  if (ep) entry = *ep;
+  else entry.ut_type = LOGIN_PROCESS;
+
+  // Modify.
+  entry.ut_tv.tv_sec = time(0);
+  xstrncpy(entry.ut_user, "LOGIN", sizeof(entry.ut_user));
+  xstrncpy(entry.ut_line, ttyname(0) + strlen("/dev/"), sizeof(entry.ut_line));
+  if (FLAG(H)) xstrncpy(entry.ut_host, TT.H, sizeof(entry.ut_host));
+
+  // Write.
+  pututxline(&entry);
+  endutxent();
 }
 
 void getty_main(void)
 {
-  pid_t pid = getpid();
-  char *ptr[3] = {"/bin/login", NULL, NULL}; //2 NULLs so we can add username
+  char ch, *cmd[3] = {TT.l ? : "/bin/login", 0, 0}; // space to add username
 
-  if (!(toys.optflags & FLAG_f)) TT.issue_str = "/etc/issue";
-  if (toys.optflags & FLAG_l) ptr[0] = TT.login_str;
-  parse_arguments();
+  if (!FLAG(f)) TT.f = "/etc/issue";
+
+  // parse arguments and set $TERM
+  if (isdigit(**toys.optargs)) {
+    parse_speeds(*toys.optargs);
+    if (*++toys.optargs) TT.tty_name = xmprintf("%s", *toys.optargs);
+  } else {
+    TT.tty_name = xmprintf("%s", *toys.optargs);
+    if (*++toys.optargs) parse_speeds(*toys.optargs);
+  }
+  if (*++toys.optargs) setenv("TERM", *toys.optargs, 1);
+
   open_tty();
   termios_init();
-  tcsetpgrp(STDIN_FILENO, pid);
-  if (toys.optflags & FLAG_H) utmp_entry();
-  if (toys.optflags & FLAG_I) 
-    writeall(STDOUT_FILENO,TT.init_str,strlen(TT.init_str));
-  if (toys.optflags & FLAG_m) sense_baud();
-  if (toys.optflags & FLAG_t) alarm(TT.timeout);
-  if (toys.optflags & FLAG_w) {
-    char ch;
-
-    while (readall(STDIN_FILENO, &ch, 1) != 1)  
-      if (ch == '\n' || ch == '\r') break;
-  }
-  if (!(toys.optflags & FLAG_n)) {
+  tcsetpgrp(0, getpid());
+  utmp_entry();
+  if (FLAG(I)) xputsn(TT.I);
+  if (FLAG(m)) sense_baud();
+  if (FLAG(t)) alarm(TT.t);
+  if (FLAG(w)) while (readall(0, &ch, 1) != 1)  if (ch=='\n' || ch=='\r') break;
+  if (!FLAG(n)) {
     int index = 1; // 0th we already set.
 
-    while (1) {
-      int l = read_login_name();
-
-      if (l) break;
-      index = index % TT.sc;
-      cfsetspeed(&TT.termios, TT.speeds[index]); // Select from multiple speeds
+    for (;;) {
+      if (read_login_name()) break;
+      index %= TT.sc;
+      xsetspeed(&TT.termios, TT.speeds[index]);
       //Necessary after cfsetspeed
-      if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) 
-        perror_exit("tcsetattr"); 
+      if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
     }
-    ptr[1]=TT.buff; //put the username in the login command line
+    cmd[1] = TT.buff; //put the username in the login command line
   }
-  xexec(ptr);
+  xexec(cmd);
 }
diff --git a/toys/pending/groupadd.c b/toys/pending/groupadd.c
index 30468c9..642b4a0 100644
--- a/toys/pending/groupadd.c
+++ b/toys/pending/groupadd.c
@@ -39,17 +39,17 @@
 {
   char *entry = NULL;
 
-  if (toys.optflags & FLAG_g) {
+  if (FLAG(g)) {
     if (TT.gid > INT_MAX) error_exit("gid should be less than  '%d' ", INT_MAX);
     if (getgrgid(TT.gid)) error_exit("group '%ld' is in use", TT.gid);
   } else {
-    if (toys.optflags & FLAG_S) TT.gid = CFG_TOYBOX_UID_SYS;
+    if (FLAG(S)) TT.gid = CFG_TOYBOX_UID_SYS;
     else TT.gid = CFG_TOYBOX_UID_USR;
     //find unused gid
     while (getgrgid(TT.gid)) TT.gid++;
   }
 
-  entry = xmprintf("%s:%s:%d:", *toys.optargs, "x", TT.gid);
+  entry = xmprintf("%s:%s:%ld:", *toys.optargs, "x", TT.gid);
   update_password(GROUP_PATH, *toys.optargs, entry);
   free(entry);
   entry = xmprintf("%s:%s::", *toys.optargs, "!");
diff --git a/toys/pending/init.c b/toys/pending/init.c
index 64b6148..afc9a3e 100644
--- a/toys/pending/init.c
+++ b/toys/pending/init.c
@@ -188,6 +188,25 @@
   }
 }
 
+static void reload_inittab(void)
+{
+  // Remove all inactive actions, then reload /etc/inittab
+  struct action_list_seed **y;
+  y = &action_list_pointer;
+  while (*y) {
+    if (!(*y)->pid) {
+      struct action_list_seed *x = *y;
+      free(x->terminal_name);
+      free(x->command);
+      *y = (*y)->next;
+      free(x);
+      continue;
+    }
+    y = &(*y)->next;
+  }
+  inittab_parsing();
+}
+
 static void run_command(char *command)
 {
   char *final_command[128];
@@ -289,11 +308,7 @@
 {
   if (pid <= 0) return;
 
-  for(;;) {
-    pid_t y = wait(NULL);
-    mark_as_terminated_process(y);
-    if (kill(y, 0)) break;
-  }
+  while (!kill(pid, 0)) mark_as_terminated_process(wait(NULL));
 }
 
 static void run_action_from_list(int action)
@@ -405,7 +420,7 @@
 static void catch_signal(int sig_no)
 {
   caught_signal = sig_no;
-  error_msg("signal seen");
+  error_msg("signal seen: %d", sig_no);
 }
 
 static void pause_handler(int sig_no)
@@ -439,6 +454,10 @@
     caught_signal = 0;
     signal_caught = 1;
     if (sig == SIGINT) run_action_from_list(CTRLALTDEL);
+    else if (sig == SIGHUP) {
+      error_msg("reloading inittab");
+      reload_inittab();
+    }
   }
 }
 
diff --git a/toys/pending/ip.c b/toys/pending/ip.c
index cc86c19..d8c891a 100644
--- a/toys/pending/ip.c
+++ b/toys/pending/ip.c
@@ -1285,7 +1285,7 @@
     if (!*argv)
       error_exit("Incomplete command for \"flush\"");
     if (TT.addressfamily == AF_PACKET)
-      error_exit("Can't flush link Addressess");
+      error_exit("Can't flush link Addresses");
   }
   addrinfo.scope = -1;
   while (*argv) {
@@ -1447,7 +1447,7 @@
         }
 
         for (; NLMSG_OK(addr_ptr, len); addr_ptr = NLMSG_NEXT(addr_ptr, len)) {
-          if ((addr_ptr->nlmsg_type == RTM_NEWADDR))
+          if (addr_ptr->nlmsg_type == RTM_NEWADDR)
             print_addrinfo(addr_ptr, flag_l);
           if ((addr_ptr->nlmsg_type == NLMSG_DONE) ||
               (addr_ptr->nlmsg_type == NLMSG_ERROR) ||
@@ -1499,15 +1499,13 @@
 
 static void show_iproute_help(void)
 {
-  char *errmsg = "\n\n" \
+  error_exit("\n\n" \
        "iproute { list | flush } SELECTOR\n" \
        "iproute get ADDRESS [from ADDRESS iif STRING]\n" \
        "	[oif STRING]\n" \
        "iproute { add | del | change | append | replace | test } ROUTE\n" \
        "	SELECTOR := [root PREFIX] [match PREFIX] [proto RTPROTO]\n" \
-       "	ROUTE := [TYPE] PREFIX [proto RTPROTO] [metric METRIC]";
-
-  error_exit(errmsg);
+       "	ROUTE := [TYPE] PREFIX [proto RTPROTO] [metric METRIC]");
 }
 
 static void print_rta_metrics(char* out, const struct rtattr *mxattr)
@@ -2174,12 +2172,10 @@
 // ===========================================================================
 static void show_iprule_help(void)
 {
-  char *errmsg = "usage: ip rule [ list | add | del ] SELECTOR ACTION\n"
+  error_exit("usage: ip rule [ list | add | del ] SELECTOR ACTION\n"
     "SELECTOR := [ from PREFIX ] [ to PREFIX ] [pref NUMBER] [ tos TOS ]\n"
     "            [ fwmark FWMARK] [ dev/iif STRING ] [type TYPE]\n"
-    "ACTION := [ table TABLE_ID ] [ realms [SRCREALM/]DSTREALM ]";
-
-  error_exit(errmsg);
+    "ACTION := [ table TABLE_ID ] [ realms [SRCREALM/]DSTREALM ]");
 }
 
 static int ruleupdate(char **argv)
@@ -2423,12 +2419,10 @@
 //============================================================================
 static void show_iptunnel_help(void)
 {
-  char *errmsg = "usage: iptunnel { add | change | del | show } [NAME]\n"
+  error_exit("usage: iptunnel { add | change | del | show } [NAME]\n"
     "           [mode { ipip | gre | sit }] [remote ADDR] [local ADDR]\n"
     "           [[i|o]seq] [[i|o]key KEY] [[i|o]csum] [ttl TTL]\n"
-    "           [tos TOS] [[no]pmtudisc] [dev PHYS_DEV]";
-
-  error_exit(errmsg);
+    "           [tos TOS] [[no]pmtudisc] [dev PHYS_DEV]");
 }
 
 static int tnl_ioctl(char *dev, int rtype, struct ip_tunnel_parm *ptnl)
diff --git a/toys/pending/mke2fs.c b/toys/pending/mke2fs.c
index 19ef5fb..ee0a5b1 100644
--- a/toys/pending/mke2fs.c
+++ b/toys/pending/mke2fs.c
@@ -546,7 +546,7 @@
 {
   int i, temp;
   off_t length;
-  uint32_t usedblocks, usedinodes, dtiblk, dtbblk;
+  uint32_t usedblocks, usedinodes, dtbblk;
   struct dirtree *dti, *dtb;
   struct ext2_superblock sb;
 
@@ -634,7 +634,7 @@
   put_zeroes(1024);
 
   // Loop through block groups, write out each one.
-  dtiblk = dtbblk = usedblocks = usedinodes = 0;
+  dtbblk = usedblocks = usedinodes = 0;
   for (i=0; i<TT.groups; i++) {
     struct ext2_inode *in = (struct ext2_inode *)toybuf;
     uint32_t start, itable, used, end;
diff --git a/toys/pending/modprobe.c b/toys/pending/modprobe.c
index 1d5479f..7424e7c 100644
--- a/toys/pending/modprobe.c
+++ b/toys/pending/modprobe.c
@@ -32,11 +32,9 @@
 GLOBALS(
   struct arg_list *dirs;
 
-  struct arg_list *probes;
-  struct arg_list *dbase[256];
+  struct arg_list *probes, *dbase[256];
   char *cmdopts;
-  int nudeps;
-  uint8_t symreq;
+  int nudeps, symreq;
 )
 
 /* Note: if "#define DBASE_SIZE" modified, 
@@ -170,7 +168,7 @@
   ssize_t len, nxtlen;
   size_t linelen, nxtlinelen;
 
-  while (1) {
+  for (;;) {
     line = NULL;
     linelen = nxtlinelen = 0;
     len = getline(&line, &linelen, fl);
@@ -273,7 +271,7 @@
       get_mod(tokens[1], 1)->flags |= MOD_BLACKLIST;
     else if (!strcmp(tokens[0], "install")) continue;
     else if (!strcmp(tokens[0], "remove")) continue;
-    else if (toys.optflags & FLAG_q)
+    else if (!FLAG(q))
       error_msg("Invalid option %s found in file %s", tokens[0], filename);
   }
   fclose(fc);
@@ -299,7 +297,7 @@
       if (tmp) *tmp = '\0';
       if (!cmdname || !fnmatch(cmdname, name, 0)) {
         if (tmp) *tmp = '.';
-        if (toys.optflags&FLAG_v) puts(line);
+        if (FLAG(v)) puts(line);
         ret = 0;
       }
     }
@@ -323,11 +321,10 @@
       *tmp = '\0';
       mod = get_mod(line, 0);
       if (!mod) continue;
-      if ((mod->flags & MOD_ALOADED) &&
-          !(toys.optflags & (FLAG_r | FLAG_D))) continue;
+      if ((mod->flags & MOD_ALOADED) && !(FLAG(r)|FLAG(D))) continue;
       
       mod->flags |= MOD_FNDDEPMOD;
-      if ((mod->flags & MOD_NDDEPS) && (!mod->dep)) {
+      if ((mod->flags & MOD_NDDEPS) && !mod->dep) {
         TT.nudeps--;
         llist_add(&mod->dep, xstrdup(line));
         tmp++;
@@ -346,16 +343,13 @@
 }
 
 // Remove a module from the Linux Kernel. if !modules does auto remove.
-static int rm_mod(char *modules, uint32_t flags)
+static int rm_mod(char *modules, unsigned flags)
 {
-  if (modules) {
-    int len = strlen(modules);
+  char *s;
 
-    if (len > 3 && !strcmp(modules+len-3, ".ko" )) modules[len-3] = 0;
-  }
-
+  if (modules && (s = strend(modules, ".ko"))) *s = 0;
   errno = 0;
-  syscall(__NR_delete_module, modules, flags ? flags : O_NONBLOCK|O_EXCL);
+  syscall(__NR_delete_module, modules, flags ? : O_NONBLOCK|O_EXCL);
 
   return errno;
 }
@@ -397,11 +391,11 @@
 {
   struct module_s *mod = get_mod(name, 1);
 
-  if (!(toys.optflags & (FLAG_r | FLAG_D)) && (mod->flags & MOD_ALOADED)) {
-    if (toys.optflags&FLAG_v) printf("skipping %s, already loaded\n", name);
+  if (!(FLAG(r)|FLAG(D)) && (mod->flags & MOD_ALOADED)) {
+    if (FLAG(v)) printf("%s already loaded\n", name);
     return;
   }
-  if (toys.optflags&FLAG_v) printf("queuing %s\n", name);
+  if (FLAG(v)) printf("queuing %s\n", name);
   mod->cmdname = name;
   mod->flags |= MOD_NDDEPS;
   llist_add_tail(&TT.probes, mod);
@@ -435,12 +429,11 @@
   int rc = 0, first = 1;
 
   if (!(m->flags & MOD_FNDDEPMOD)) {
-    if (!(toys.optflags & FLAG_q))
-      error_msg("module %s not found in modules.dep", m->name);
+    if (!FLAG(q)) error_msg("module %s not found in modules.dep", m->name);
     return -ENOENT;
   }
-  if (toys.optflags & FLAG_v) printf("go_prob'ing %s\n", m->name);
-  if (!(toys.optflags & FLAG_r)) m->dep = llist_rev(m->dep);
+  if (FLAG(v)) printf("go_prob'ing %s\n", m->name);
+  if (!FLAG(r)) m->dep = llist_rev(m->dep);
   
   while (m->dep) {
     struct module_s *m2;
@@ -450,7 +443,7 @@
     fn = llist_popme(&m->dep);
     m2 = get_mod(fn, 1);
     // are we removing ?
-    if (toys.optflags & FLAG_r) {
+    if (FLAG(r)) {
       if (m2->flags & MOD_ALOADED) {
         if ((rc = rm_mod(m2->name, O_EXCL))) {
           if (first) {
@@ -462,27 +455,27 @@
       first = 0;
       continue;
     }
+// TODO how does free work here without leaking?
     options = m2->opts;
     m2->opts = NULL;
     if (m == m2) options = add_opts(options, TT.cmdopts);
 
     // are we only checking dependencies ?
-    if (toys.optflags & FLAG_D) {
-      if (toys.optflags & FLAG_v)
+    if (FLAG(D)) {
+      if (FLAG(v))
         printf(options ? "insmod %s %s\n" : "insmod %s\n", fn, options);
       if (options) free(options);
       continue;
     }
     if (m2->flags & MOD_ALOADED) {
-      if (toys.optflags&FLAG_v)
-        printf("%s is already loaded, skipping\n", fn);
+      if (FLAG(v)) printf("%s already loaded\n", fn);
       if (options) free(options);
       continue;
     }
     // none of above is true insert the module.
     errno = 0;
     rc = ins_mod(fn, options);
-    if (toys.optflags&FLAG_v)
+    if (FLAG(v))
       printf("loaded %s '%s': %s\n", fn, options, strerror(errno));
     if (errno == EEXIST) rc = 0;
     free(options);
@@ -497,35 +490,31 @@
 
 void modprobe_main(void)
 {
-  struct utsname uts;
   char **argv = toys.optargs, *procline = NULL;
   FILE *fs;
   struct module_s *module;
-  unsigned flags = toys.optflags;
   struct arg_list *dirs;
 
-  if ((toys.optc < 1) && (((flags & FLAG_r) && (flags & FLAG_l))
-        ||(!((flags & FLAG_r)||(flags & FLAG_l)))))
-  {
-    help_exit("bad syntax");
-  }
+  if (toys.optc<1 && !FLAG(r) == !FLAG(l)) help_exit("bad syntax");
   // Check for -r flag without arg if yes then do auto remove.
-  if ((flags & FLAG_r) && !toys.optc) {
-    if (rm_mod(NULL, O_NONBLOCK | O_EXCL)) perror_exit("rmmod");
+  if (FLAG(r) && !toys.optc) {
+    if (rm_mod(0, O_NONBLOCK|O_EXCL)) perror_exit("rmmod");
     return;
   }
 
   if (!TT.dirs) {
+    struct utsname uts;
+
     uname(&uts);
     TT.dirs = xzalloc(sizeof(struct arg_list));
     TT.dirs->arg = xmprintf("/lib/modules/%s", uts.release);
   }
 
   // modules.dep processing for dependency check.
-  if (flags & FLAG_l) {
+  if (FLAG(l)) {
     for (dirs = TT.dirs; dirs; dirs = dirs->next) {
       xchdir(dirs->arg);
-      if (!depmode_read_entry(toys.optargs[0])) return;
+      if (!depmode_read_entry(*toys.optargs)) return;
     }
     error_exit("no module found.");
   }
@@ -534,22 +523,19 @@
   fs = xfopen("/proc/modules", "r");
   
   while (read_line(fs, &procline) > 0) {
-    *(strchr(procline, ' ')) = '\0';
+    *strchr(procline, ' ') = 0;
     get_mod(procline, 1)->flags = MOD_ALOADED;
     free(procline);
     procline = NULL;
   }
   fclose(fs);
-  if ((flags & FLAG_a) || (flags & FLAG_r)) {
-    do {
-      add_mod(*argv++);
-    } while (*argv);
-  } else {
-    add_mod(argv[0]);
+  if (FLAG(a) || FLAG(r)) while (argv) add_mod(*argv++);
+  else {
+    add_mod(*argv);
     TT.cmdopts = add_cmdopt(argv);
   }
   if (!TT.probes) {
-    if (toys.optflags&FLAG_v) puts("All modules loaded");
+    if (FLAG(v)) puts("All modules loaded");
     return;
   }
   dirtree_flagread("/etc/modprobe.conf", DIRTREE_SHUTUP, config_action);
@@ -568,23 +554,23 @@
 
   while ((module = llist_popme(&TT.probes))) {
     if (!module->rnames) {
-      if (toys.optflags&FLAG_v) puts("probing by module name");
+      if (FLAG(v)) puts("probing by module name");
       /* This is not an alias. Literal names are blacklisted
        * only if '-b' is given.
        */
-      if (!(flags & FLAG_b) || !(module->flags & MOD_BLACKLIST))
+      if (!FLAG(b) || !(module->flags & MOD_BLACKLIST))
         go_probe(module);
       continue;
     }
     do { // Probe all real names for the alias.
-      char *real = ((struct arg_list*)llist_pop(&module->rnames))->arg;
+      char *real = ((struct arg_list *)llist_pop(&module->rnames))->arg;
       struct module_s *m2 = get_mod(real, 0);
       
-      if (toys.optflags&FLAG_v)
+      if (FLAG(v))
         printf("probing alias %s by realname %s\n", module->name, real);
       if (!m2) continue;
       if (!(m2->flags & MOD_BLACKLIST) 
-          && (!(m2->flags & MOD_ALOADED) || (flags & (FLAG_r | FLAG_D))))
+          && (!(m2->flags & MOD_ALOADED) || FLAG(r) || FLAG(D)))
         go_probe(m2);
       free(real);
     } while (module->rnames);
diff --git a/toys/pending/openvt.c b/toys/pending/openvt.c
index 29f8a17..3cc97da 100644
--- a/toys/pending/openvt.c
+++ b/toys/pending/openvt.c
@@ -12,23 +12,23 @@
   default n
   depends on TOYBOX_FORK
   help
-    usage: openvt [-c N] [-sw] [command [command_options]]
+    usage: openvt [-c NUM] [-sw] [COMMAND...]
 
-    start a program on a new virtual terminal (VT)
+    Start a program on a new virtual terminal.
 
-    -c N  Use VT N
+    -c NUM  Use VT NUM
     -s    Switch to new VT
     -w    Wait for command to exit
 
-    if -sw used together, switch back to originating VT when command completes
+    Together -sw switch back to originating VT when command completes.
 
 config DEALLOCVT
   bool "deallocvt"
   default n
   help
-    usage: deallocvt [N]
+    usage: deallocvt [NUM]
 
-    Deallocate unused virtual terminal /dev/ttyN, or all unused consoles.
+    Deallocate unused virtual terminals, either a specific /dev/ttyNUM, or all.
 */
 
 #define FOR_openvt
@@ -37,109 +37,66 @@
 #include <linux/kd.h>
 
 GLOBALS(
-  unsigned long vt_num;
+  long c;
 )
 
 int open_console(void)
 {
-  char arg, *console_name[] = {"/dev/tty", "/dev/tty0", "/dev/console"};
+  char arg = 0, *console_name[] = {"/dev/tty", "/dev/tty0", "/dev/console"};
   int i, fd;
 
   for (i = 0; i < ARRAY_LEN(console_name); i++) {
-    fd = open(console_name[i], O_RDWR);
-    if (fd >= 0) {
-      arg = 0;
-      if (!ioctl(fd, KDGKBTYPE, &arg)) return fd;
-      close(fd);
-    }
+    if (0>(fd = open(console_name[i], O_RDWR))) continue;
+    if (!ioctl(fd, KDGKBTYPE, &arg)) return fd;
+    close(fd);
   }
-
-  /* check std fd 0, 1 and 2 */
-  for (fd = 0; fd < 3; fd++) {
-    arg = 0;
-    if (0 == ioctl(fd, KDGKBTYPE, &arg)) return fd;
-  }
-
-  return -1;
-}
-
-int xvtnum(int fd)
-{
-  int ret;
-
-  ret = ioctl(fd, VT_OPENQRY, (int *)&TT.vt_num);
-  if (ret != 0 || TT.vt_num <= 0) perror_exit("can't find open VT");
-
-  return TT.vt_num;
+  for (fd = 0; fd < 3; fd++) if (!ioctl(fd, KDGKBTYPE, &arg)) return fd;
+  error_exit("can't open console");
 }
 
 void openvt_main(void)
 {
-  int fd, vt_fd, ret = 0;
   struct vt_stat vstate;
+  int fd;
   pid_t pid;
 
-  if (!(toys.optflags & FLAG_c)) {
-    // check if fd 0,1 or 2 is already opened
-    for (fd = 0; fd < 3; fd++)
-      if (!ioctl(fd, VT_GETSTATE, &vstate)) {
-        ret = xvtnum(fd);
-        break;
-      }
+  // find current console
+  if (-1 == (ioctl(fd = open_console(), VT_GETSTATE, &vstate)) ||
+      (!TT.c && 0>=(TT.c = xioctl(fd, VT_OPENQRY, &fd))))
+    perror_exit("can't find open VT");
 
-    // find VT number using /dev/console
-    if (!ret) {
-      fd = xopen("/dev/console", O_RDONLY | O_NONBLOCK);
-      xioctl(fd, VT_GETSTATE, &vstate);
-      xvtnum(fd);
-    }
-  }
-
-  sprintf(toybuf, "/dev/tty%lu", TT.vt_num);
-  fd = open_console();
-  xioctl(fd, VT_GETSTATE, &vstate);
-
+  sprintf(toybuf, "/dev/tty%ld", TT.c);
   close(0);  //new vt becomes stdin
-  vt_fd = xopen_stdio(toybuf, O_RDWR);
-  if (toys.optflags & FLAG_s) {
-    ioctl(vt_fd, VT_ACTIVATE, TT.vt_num);
-    ioctl(vt_fd, VT_WAITACTIVE, TT.vt_num);
+  dup2(dup2(xopen_stdio(toybuf, O_RDWR), 1), 2);
+  if (FLAG(s)) {
+    ioctl(0, VT_ACTIVATE, (int)TT.c);
+    ioctl(0, VT_WAITACTIVE, (int)TT.c);
   }
 
-  close(1);
-  close(2);
-  dup2(vt_fd, 1);
-  dup2(vt_fd, 2);
-  while (vt_fd > 2)
-    close(vt_fd--);
-
-  pid = xfork();
-  if (!pid) {
+  if (!(pid = xfork())) {
     setsid();
-    ioctl(vt_fd, TIOCSCTTY, 0);
+    ioctl(0, TIOCSCTTY, 0);
+    if (fd>2) close(fd);
     xexec(toys.optargs);
   }
 
-  if (toys.optflags & FLAG_w) {
-    while (-1 == waitpid(pid, NULL, 0) && errno == EINTR)
-      ;
-    if (toys.optflags & FLAG_s) {
+  if (FLAG(w)) {
+    while (-1 == waitpid(pid, NULL, 0) && errno == EINTR);
+    if (FLAG(s)) {
       ioctl(fd, VT_ACTIVATE, vstate.v_active);
       ioctl(fd, VT_WAITACTIVE, vstate.v_active);
-      //check why deallocate isn't working here
-      xioctl(fd, VT_DISALLOCATE, (void *)(ptrdiff_t)TT.vt_num); 
+      ioctl(fd, VT_DISALLOCATE, (int)TT.c);
     }
   }
+  close(fd);
 }
 
 void deallocvt_main(void)
 {
-  long vt_num = 0; // 0 deallocates all unused consoles
-  int fd;
+  int fd, vt_num = 0; // 0 = all
 
   if (*toys.optargs) vt_num = atolx_range(*toys.optargs, 1, 63);
-
-  if ((fd = open_console()) < 0) error_exit("can't open console");
-  xioctl(fd, VT_DISALLOCATE, (void *)vt_num);
-  if (CFG_TOYBOX_FREE) close(fd);
+  if (-1 == ioctl(fd = open_console(), VT_DISALLOCATE, vt_num))
+    perror_exit("%d", vt_num);
+  close(fd);
 }
diff --git a/toys/pending/readelf.c b/toys/pending/readelf.c
index acb1ed0..e6e1623 100644
--- a/toys/pending/readelf.c
+++ b/toys/pending/readelf.c
@@ -4,25 +4,25 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/nm.html
 
-USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adhlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 
 config READELF
   bool "readelf"
-  default y
+  default n
   help
-    usage: readelf [-adhlnSsW] [-p SECTION] [-x SECTION] [file...]
+    usage: readelf [-adehlnSs] [-p SECTION] [-x SECTION] [file...]
 
     Displays information about ELF files.
 
     -a	Equivalent to -dhlnSs
     -d	Show dynamic section
+    -e	Headers (equivalent to -hlS)
     -h	Show ELF header
     -l	Show program headers
     -n	Show notes
     -p S	Dump strings found in named/numbered section
     -S	Show section headers
     -s	Show symbol tables (.dynsym and .symtab)
-    -W	Don't truncate fields (default in toybox)
     -x S	Hex dump of named/numbered section
 
     --dyn-syms	Show just .dynsym symbol table
@@ -35,49 +35,84 @@
   char *x, *p;
 
   char *elf, *shstrtab, *f;
-  long long shoff, phoff, size;
-  int bits, shnum, shentsize, phentsize;
-  int64_t (*elf_int)(void *ptr, unsigned size);
+  unsigned long long shoff, phoff, size, shstrtabsz;
+  int bits, endian, shnum, shentsize, phentsize;
 )
 
 // Section header.
 struct sh {
-  int type, link, info;
-  long long flags, addr, offset, size, addralign, entsize;
+  unsigned type, link, info;
+  unsigned long long flags, addr, offset, size, addralign, entsize;
   char *name;
 };
 
 // Program header.
 struct ph {
-  int type, flags;
-  long long offset, vaddr, paddr, filesz, memsz, align;
+  unsigned type, flags;
+  unsigned long long offset, vaddr, paddr, filesz, memsz, align;
 };
 
-static void get_sh(int i, struct sh *s)
+static long long elf_get(char **p, int len)
+{
+  long long result = ((TT.endian == 2) ? peek_be : peek_le)(*p, len);
+
+  *p += len;
+  return result;
+}
+
+static unsigned long long elf_long(char **p)
+{
+  return elf_get(p, 4*(TT.bits+1));
+}
+
+static unsigned elf_int(char **p)
+{
+  return elf_get(p, 4);
+}
+
+static unsigned short elf_short(char **p)
+{
+  return elf_get(p, 2);
+}
+
+static int get_sh(unsigned i, struct sh *s)
 {
   char *shdr = TT.elf+TT.shoff+i*TT.shentsize;
+  unsigned name_offset;
 
   if (i >= TT.shnum || shdr > TT.elf+TT.size-TT.shentsize) {
-    error_exit("%s: bad shdr %d",TT.f,i);
+    printf("No shdr %d\n", i);
+    return 0;
   }
 
-  s->type = TT.elf_int(shdr+4, 4);
-  s->flags = TT.elf_int(shdr+8, 4*(TT.bits+1));
-  s->addr = TT.elf_int(shdr+8+4*(TT.bits+1), 4*(TT.bits+1));
-  s->offset = TT.elf_int(shdr+8+8*(TT.bits+1), 4*(TT.bits+1));
-  s->size = TT.elf_int(shdr+8+12*(TT.bits+1), 4*(TT.bits+1));
-  s->link = TT.elf_int(shdr+8+16*(TT.bits+1), 4);
-  s->info = TT.elf_int(shdr+12+16*(TT.bits+1), 4);
-  s->addralign = TT.elf_int(shdr+16+16*(TT.bits+1), 4*(TT.bits+1));
-  s->entsize = TT.elf_int(shdr+16+20*(TT.bits+1), 4*(TT.bits+1));
+  name_offset = elf_int(&shdr);
+  s->type = elf_int(&shdr);
+  s->flags = elf_long(&shdr);
+  s->addr = elf_long(&shdr);
+  s->offset = elf_long(&shdr);
+  s->size = elf_long(&shdr);
+  s->link = elf_int(&shdr);
+  s->info = elf_int(&shdr);
+  s->addralign = elf_long(&shdr);
+  s->entsize = elf_long(&shdr);
+
+  if (s->type != 8) {
+    if (s->offset>TT.size || s->size>TT.size || s->offset>TT.size-s->size) {
+      printf("Bad offset/size %llu/%llu for sh %d\n", s->offset, s->size, i);
+      return 0;
+    }
+  }
 
   if (!TT.shstrtab) s->name = "?";
   else {
-    s->name = TT.shstrtab + TT.elf_int(shdr, 4);
-    if (s->name >= TT.elf+TT.size) error_exit("%s: bad shdr name %d",TT.f,i);
-    if (s->offset >= TT.size-s->size && s->type != 8 /*SHT_NOBITS*/)
-      error_exit("%s: bad section %d",TT.f,i);
+    s->name = TT.shstrtab + name_offset;
+    if (name_offset > TT.shstrtabsz || s->name >= TT.elf+TT.size) {
+      printf("Bad name for sh %d\n", i);
+      return 0;
+    }
   }
+
+  return 1;
 }
 
 static int find_section(char *spec, struct sh *s)
@@ -88,46 +123,52 @@
   // Valid section number?
   errno = 0;
   i = strtoul(spec, &end, 0);
-  if (!errno && !*end && i < TT.shnum) {
-    get_sh(i, s);
-    return 1;
-  }
+  if (!errno && !*end && i < TT.shnum) return get_sh(i, s);
 
   // Search the section names.
   for (i=0; i<TT.shnum; i++) {
-    get_sh(i, s);
-    if (!strcmp(s->name, spec)) return 1;
+    if (get_sh(i, s) && !strcmp(s->name, spec)) return 1;
   }
 
   error_msg("%s: no section '%s", TT.f, spec);
   return 0;
 }
 
-static void get_ph(int i, struct ph *ph)
+static int get_ph(int i, struct ph *ph)
 {
   char *phdr = TT.elf+TT.phoff+i*TT.phentsize;
 
-  if (phdr > TT.elf+TT.size-TT.phentsize) error_exit("%s: bad phdr %d",TT.f,i);
+  if (phdr > TT.elf+TT.size-TT.phentsize) {
+    printf("Bad phdr %d\n", i);
+    return 0;
+  }
 
   // Elf64_Phdr reordered fields.
-  ph->type = TT.elf_int(phdr, 4);
+  ph->type = elf_int(&phdr);
   if (TT.bits) {
-    ph->flags = TT.elf_int(phdr+=4, 4);
-    ph->offset = TT.elf_int(phdr+=4, 8);
-    ph->vaddr = TT.elf_int(phdr+=8, 8);
-    ph->paddr = TT.elf_int(phdr+=8, 8);
-    ph->filesz = TT.elf_int(phdr+=8, 8);
-    ph->memsz = TT.elf_int(phdr+=8, 8);
-    ph->align = TT.elf_int(phdr+=8, 8);
+    ph->flags = elf_int(&phdr);
+    ph->offset = elf_long(&phdr);
+    ph->vaddr = elf_long(&phdr);
+    ph->paddr = elf_long(&phdr);
+    ph->filesz = elf_long(&phdr);
+    ph->memsz = elf_long(&phdr);
+    ph->align = elf_long(&phdr);
   } else {
-    ph->offset = TT.elf_int(phdr+=4, 4);
-    ph->vaddr = TT.elf_int(phdr+=4, 4);
-    ph->paddr = TT.elf_int(phdr+=4, 4);
-    ph->filesz = TT.elf_int(phdr+=4, 4);
-    ph->memsz = TT.elf_int(phdr+=4, 4);
-    ph->flags = TT.elf_int(phdr+=4, 4);
-    ph->align = TT.elf_int(phdr+=4, 4);
+    ph->offset = elf_int(&phdr);
+    ph->vaddr = elf_int(&phdr);
+    ph->paddr = elf_int(&phdr);
+    ph->filesz = elf_int(&phdr);
+    ph->memsz = elf_int(&phdr);
+    ph->flags = elf_int(&phdr);
+    ph->align = elf_int(&phdr);
   }
+
+  if (ph->offset >= TT.size-ph->filesz) {
+    printf("phdr %d has bad offset/size %llu/%llu", i, ph->offset, ph->filesz);
+    return 0;
+  }
+
+  return 1;
 }
 
 #define MAP(...) __VA_ARGS__
@@ -200,7 +241,7 @@
 static void show_symbols(struct sh *table, struct sh *strtab)
 {
   char *symtab = TT.elf+table->offset, *ndx;
-  int sym_size = (TT.bits ? 24 : 16), numsym = table->size/sym_size, i;
+  int numsym = table->size/(TT.bits ? 24 : 16), i;
 
   if (numsym == 0) return;
 
@@ -209,28 +250,28 @@
          "   Num:    %*s  Size Type    Bind   Vis      Ndx Name\n",
          table->name, numsym, 5+8*TT.bits, "Value");
   for (i=0; i<numsym; i++) {
-    int st_name = TT.elf_int(symtab, 4), st_value, st_shndx;
+    unsigned st_name = elf_int(&symtab), st_value, st_shndx;
     unsigned char st_info, st_other;
-    long st_size;
+    unsigned long st_size;
     char *name;
 
     // The various fields were moved around for 64-bit.
     if (TT.bits) {
-      st_info = symtab[4];
-      st_other = symtab[5];
-      st_shndx = TT.elf_int(symtab+6, 2);
-      st_value = TT.elf_int(symtab+8, 8);
-      st_size = TT.elf_int(symtab+16, 8);
+      st_info = *symtab++;
+      st_other = *symtab++;
+      st_shndx = elf_short(&symtab);
+      st_value = elf_long(&symtab);
+      st_size = elf_long(&symtab);
     } else {
-      st_value = TT.elf_int(symtab+4, 4);
-      st_size = TT.elf_int(symtab+8, 4);
-      st_info = symtab[12];
-      st_other = symtab[13];
-      st_shndx = TT.elf_int(symtab+14, 2);
+      st_value = elf_int(&symtab);
+      st_size = elf_int(&symtab);
+      st_info = *symtab++;
+      st_other = *symtab++;
+      st_shndx = elf_short(&symtab);
     }
 
     name = TT.elf + strtab->offset + st_name;
-    if (name >= TT.elf+TT.size) error_exit("%s: bad symbol name", TT.f);
+    if (name >= TT.elf+TT.size) name = "???";
 
     if (!st_shndx) ndx = "UND";
     else if (st_shndx==0xfff1) ndx = "ABS";
@@ -238,54 +279,65 @@
 
     // TODO: look up and show any symbol versions with @ or @@.
 
-    printf("%6d: %0*x %5ld %-7s %-6s %-9s%3s %s\n", i, 8*(TT.bits+1),
+    printf("%6d: %0*x %5lu %-7s %-6s %-9s%3s %s\n", i, 8*(TT.bits+1),
       st_value, st_size, stt_type(st_info & 0xf), stb_type(st_info >> 4),
       stv_type(st_other & 3), ndx, name);
-    symtab += sym_size;
   }
 }
 
-static void show_notes(long offset, long size)
+static int notematch(int namesz, char **p, char *expected, int len)
+{
+  if (namesz != len || memcmp(*p, expected, namesz)) return 0;
+  *p += namesz;
+  return 1;
+}
+
+static void show_notes(unsigned long offset, unsigned long size)
 {
   char *note = TT.elf + offset;
 
+  if (size > TT.size || offset > TT.size-size) {
+    printf("Bad note bounds %lu/%lu\n", offset, size);
+    return;
+  }
+
   printf("  %-20s %10s\tDescription\n", "Owner", "Data size");
   while (note < TT.elf+offset+size) {
-    int namesz = TT.elf_int(note, 4), descsz = TT.elf_int(note+4, 4),
-        type = TT.elf_int(note+8, 4), j = 0;
-    char *name = note+12;
+    char *p = note, *desc;
+    unsigned namesz=elf_int(&p), descsz=elf_int(&p), type=elf_int(&p), j=0;
 
-    printf("  %-20.*s 0x%08x\t", namesz, name, descsz);
-    if (!memcmp(name, "GNU", 4)) {
+    if (namesz > size || descsz > size) {
+      error_msg("%s: bad note @%lu", TT.f, offset);
+      return;
+    }
+    printf("  %-20.*s 0x%08x\t", namesz, p, descsz);
+    if (notematch(namesz, &p, "GNU", 4)) {
       if (type == 1) {
-        printf("NT_GNU_ABI_TAG\tOS: %s, ABI: %d.%d.%d",
-               !TT.elf_int(note+16, 4)?"Linux":"?",
-               (int)TT.elf_int(note+20, 4), (int)TT.elf_int(note+24, 4),
-               (int)TT.elf_int(note+28, 4)), j=1;
+        printf("NT_GNU_ABI_TAG\tOS: %s, ABI: %u.%u.%u",
+          !elf_int(&p)?"Linux":"?", elf_int(&p), elf_int(&p), elf_int(&p)), j=1;
       } else if (type == 3) {
         printf("NT_GNU_BUILD_ID\t");
-        for (;j<descsz;j++) printf("%02x",note[16+j]);
+        for (;j<descsz;j++) printf("%02x", *p++);
       } else if (type == 4) {
-        printf("NT_GNU_GOLD_VERSION\t%.*s", descsz, note+16), j=1;
-      }
-    } else if (!memcmp(name, "Android", 8)) {
+        printf("NT_GNU_GOLD_VERSION\t%.*s", descsz, p), j=1;
+      } else p -= 4;
+    } else if (notematch(namesz, &p, "Android", 8)) {
       if (type == 1) {
-        printf("NT_VERSION\tAPI level %d", (int)TT.elf_int(note+20, 4)), j=1;
-        if (descsz>=132) printf(", NDK %.64s (%.64s)",note+24,note+24+64);
-      }
-    } else if (!memcmp(name, "CORE", 5) || !memcmp(name, "LINUX", 6)) {
-      char *desc = *name=='C' ? nt_type_core(type) : nt_type_linux(type);
-
-      if (*desc != '0') printf("%s", desc), j=1;
+        printf("NT_VERSION\tAPI level %u", elf_int(&p)), j=1;
+        if (descsz>=132) printf(", NDK %.64s (%.64s)", p, p+64);
+      } else p -= 8;
+    } else if (notematch(namesz, &p, "CORE", 5)) {
+      if (*(desc = nt_type_core(type)) != '0') printf("%s", desc), j=1;
+    } else if (notematch(namesz, &p, "LINUX", 6)) {
+      if (*(desc = nt_type_linux(type)) != '0') printf("%s", desc), j=1;
     }
 
     // If we didn't do custom output above, show a hex dump.
     if (!j) {
       printf("0x%x\t", type);
-      for (;j<descsz;j++) printf("%c%02x",!j?'\t':' ',note[16+j]);
+      for (;j<descsz;j++) printf("%c%02x",!j?'\t':' ', *p++/*note[16+j]*/);
     }
     xputc('\n');
-
     note += 3*4 + ((namesz+3)&~3) + ((descsz+3)&~3);
   }
 }
@@ -295,45 +347,35 @@
   struct sh dynamic = {}, dynstr = {}, dynsym = {}, shstr = {}, strtab = {},
     symtab = {}, s;
   struct ph ph;
-  int endian, version, elf_type, flags, entry, ehsize, phnum, shstrndx, i,j,w;
+  char *hdr = TT.elf;
+  int type, machine, version, flags, entry, ehsize, phnum, shstrndx, i, j, w;
 
-  if (TT.size < 45 || memcmp(TT.elf, "\177ELF", 4)) {
+  if (TT.size < 45 || memcmp(hdr, "\177ELF", 4)) {
     error_msg("%s: not ELF", TT.f);
     return;
   }
 
-  TT.bits = TT.elf[4] - 1;
-  endian = TT.elf[5];
-  version = TT.elf[6];
-  TT.elf_int = (endian==2) ? peek_be : peek_le;
-  if (TT.bits < 0 || TT.bits > 1 || endian < 1 || endian > 2 || version != 1) {
+  TT.bits = hdr[4] - 1;
+  TT.endian = hdr[5];
+  if (TT.bits<0 || TT.bits>1 || TT.endian<1 || TT.endian>2 || hdr[6]!=1) {
     error_msg("%s: bad ELF", TT.f);
     return;
   }
 
-  elf_type = TT.elf_int(TT.elf+16, 2);
-  entry = TT.elf_int(TT.elf+24, 4+4*TT.bits);
-  TT.phoff = TT.elf_int(TT.elf+28+4*TT.bits, 4+4*TT.bits);
-  TT.shoff = TT.elf_int(TT.elf+32+8*TT.bits, 4+4*TT.bits);
-  flags = TT.elf_int(TT.elf+36+12*TT.bits, 4);
-  ehsize = TT.elf_int(TT.elf+40+12*TT.bits, 2);
-  TT.phentsize = TT.elf_int(TT.elf+42+12*TT.bits, 2);
-  phnum = TT.elf_int(TT.elf+44+12*TT.bits, 2);
-  TT.shentsize = TT.elf_int(TT.elf+46+12*TT.bits, 2);
-  TT.shnum = TT.elf_int(TT.elf+48+12*TT.bits, 2);
-  shstrndx = TT.elf_int(TT.elf+50+12*TT.bits, 2);
-
-  // Set up the section header string table so we can use section header names.
-  // Core files have shstrndx == 0.
-  TT.shstrtab = 0;
-  if (shstrndx != 0) {
-    get_sh(shstrndx, &shstr);
-    if (shstr.type != 3 /*SHT_STRTAB*/) {
-      error_msg("%s: bad shstrndx", TT.f);
-      return;
-    }
-    TT.shstrtab = TT.elf+shstr.offset;
-  }
+  hdr += 16; // EI_NIDENT
+  type = elf_short(&hdr);
+  machine = elf_short(&hdr);
+  version = elf_int(&hdr);
+  entry = elf_long(&hdr);
+  TT.phoff = elf_long(&hdr);
+  TT.shoff = elf_long(&hdr);
+  flags = elf_int(&hdr);
+  ehsize = elf_short(&hdr);
+  TT.phentsize = elf_short(&hdr);
+  phnum = elf_short(&hdr);
+  TT.shentsize = elf_short(&hdr);
+  TT.shnum = elf_short(&hdr);
+  shstrndx = elf_short(&hdr);
 
   if (toys.optc > 1) printf("\nFile: %s\n", TT.f);
 
@@ -343,19 +385,17 @@
     for (i=0; i<16; i++) printf("%02x%c", TT.elf[i], i==15?'\n':' ');
     printf("  Class:                             ELF%d\n", TT.bits?64:32);
     printf("  Data:                              2's complement, %s endian\n",
-           (endian==2)?"big":"little");
+           (TT.endian==2)?"big":"little");
     printf("  Version:                           1 (current)\n");
     printf("  OS/ABI:                            %s\n", os_abi(TT.elf[7]));
     printf("  ABI Version:                       %d\n", TT.elf[8]);
-    printf("  Type:                              %s\n", et_type(elf_type));
-    printf("  Machine:                           %s\n",
-           elf_arch_name(TT.elf_int(TT.elf+18, 2)));
-    printf("  Version:                           0x%x\n",
-           (int) TT.elf_int(TT.elf+20, 4));
+    printf("  Type:                              %s\n", et_type(type));
+    printf("  Machine:                           %s\n", elf_arch_name(machine));
+    printf("  Version:                           0x%x\n", version);
     printf("  Entry point address:               0x%x\n", entry);
-    printf("  Start of program headers:          %lld (bytes into file)\n",
+    printf("  Start of program headers:          %llu (bytes into file)\n",
            TT.phoff);
-    printf("  Start of section headers:          %lld (bytes into file)\n",
+    printf("  Start of section headers:          %llu (bytes into file)\n",
            TT.shoff);
     printf("  Flags:                             0x%x\n", flags);
     printf("  Size of this header:               %d (bytes)\n", ehsize);
@@ -365,8 +405,29 @@
     printf("  Number of section headers:         %d\n", TT.shnum);
     printf("  Section header string table index: %d\n", shstrndx);
   }
+  if (TT.phoff > TT.size) {
+    error_msg("%s: bad phoff", TT.f);
+    return;
+  }
+  if (TT.shoff > TT.size) {
+    error_msg("%s: bad shoff", TT.f);
+    return;
+  }
 
-  w = 8*(TT.bits+1);
+  // Set up the section header string table so we can use section header names.
+  // Core files have shstrndx == 0.
+  TT.shstrtab = 0;
+  TT.shstrtabsz = 0;
+  if (shstrndx != 0) {
+    if (!get_sh(shstrndx, &shstr) || shstr.type != 3 /*SHT_STRTAB*/) {
+      error_msg("%s: bad shstrndx", TT.f);
+      return;
+    }
+    TT.shstrtab = TT.elf+shstr.offset;
+    TT.shstrtabsz = shstr.size;
+  }
+
+  w = 8<<TT.bits;
   if (FLAG(S)) {
     if (!TT.shnum) printf("\nThere are no sections in this file.\n");
     else {
@@ -376,14 +437,14 @@
       }
       printf("\n"
              "Section Headers:\n"
-             "  [Nr] %-20s %-14s %-*s %-6s %-6s ES Flg Lk Inf Al\n",
+             "  [Nr] %-17s %-15s %-*s %-6s %-6s ES Flg Lk Inf Al\n",
              "Name", "Type", w, "Address", "Off", "Size");
     }
   }
   // We need to iterate through the section headers even if we're not
   // dumping them, to find specific sections.
   for (i=0; i<TT.shnum; i++) {
-    get_sh(i, &s);
+    if (!get_sh(i, &s)) continue;
     if (s.type == 2 /*SHT_SYMTAB*/) symtab = s;
     else if (s.type == 6 /*SHT_DYNAMIC*/) dynamic = s;
     else if (s.type == 11 /*SHT_DYNSYM*/) dynsym = s;
@@ -396,7 +457,7 @@
       char sh_flags[12] = {}, *p = sh_flags;
 
       for (j=0; j<12; j++) if (s.flags&(1<<j)) *p++="WAXxMSILOTC"[j];
-      printf("  [%2d] %-20s %-14s %0*llx %06llx %06llx %02llx %3s %2d %2d %2lld\n",
+      printf("  [%2d] %-17s %-15s %0*llx %06llx %06llx %02llx %3s %2d %2d %2lld\n",
              i, s.name, sh_type(s.type), w, s.addr, s.offset, s.size,
              s.entsize, sh_flags, s.link, s.info, s.addralign);
     }
@@ -416,18 +477,19 @@
         "Entry point %#x\n"
         "There are %d program headers, starting at offset %lld\n"
         "\n",
-        et_type(elf_type), entry, phnum, TT.phoff);
+        et_type(type), entry, phnum, TT.phoff);
       }
       printf("Program Headers:\n"
              "  %-14s %-8s %-*s   %-*s   %-7s %-7s Flg Align\n", "Type",
              "Offset", w, "VirtAddr", w, "PhysAddr", "FileSiz", "MemSiz");
       for (i=0; i<phnum; i++) {
-        get_ph(i, &ph);
+        if (!get_ph(i, &ph)) continue;
         printf("  %-14s 0x%06llx 0x%0*llx 0x%0*llx 0x%05llx 0x%05llx %c%c%c %#llx\n",
                ph_type(ph.type), ph.offset, w, ph.vaddr, w, ph.paddr,
                ph.filesz, ph.memsz, ph.flags&4?'R':' ', ph.flags&2?'W':' ',
                ph.flags&1?'E':' ', ph.align);
-        if (ph.type == 3 /*PH_INTERP*/) {
+        if (ph.type == 3 /*PH_INTERP*/ && ph.filesz<TT.size &&
+            ph.offset<TT.size && ph.filesz - 1 < TT.size - ph.offset) {
           printf("      [Requesting program interpreter: %*s]\n",
                  (int) ph.filesz-1, TT.elf+ph.offset);
         }
@@ -437,12 +499,13 @@
              " Section to Segment mapping:\n"
              "  Segment Sections...\n");
       for (i=0; i<phnum; i++) {
-        get_ph(i, &ph);
-        printf("   %02d    ", i);
+        if (!get_ph(i, &ph)) continue;
+        printf("   %02d     ", i);
         for (j=0; j<TT.shnum; j++) {
-          get_sh(j, &s);
+          if (!get_sh(j, &s)) continue;
+          if (!*s.name) continue;
           if (s.offset >= ph.offset && s.offset+s.size <= ph.offset+ph.filesz)
-            printf(" %s", s.name);
+            printf("%s ", s.name);
         }
         xputc('\n');
       }
@@ -456,46 +519,51 @@
 
     xputc('\n');
     if (!dynamic.size) printf("There is no dynamic section in this file.\n");
-    else printf("Dynamic section at offset 0x%llx contains %lld entries:\n"
-                "  %-*s %-20s %s\n",
-                dynamic.offset, dynamic.size/dynamic.entsize,
-                w+2, "Tag", "Type", "Name/Value");
-    for (; dyn < end; dyn += dynamic.entsize) {
-      int es = 4*(TT.bits+1);
-      long tag = TT.elf_int(dyn, es), val = TT.elf_int(dyn+es, es);
-      char *type = dt_type(tag);
+    else if (!dynamic.entsize) printf("Bad dynamic entry size 0!\n");
+    else {
+      printf("Dynamic section at offset 0x%llx contains %lld entries:\n"
+             "  %-*s %-20s %s\n",
+             dynamic.offset, dynamic.size/dynamic.entsize,
+             w+2, "Tag", "Type", "Name/Value");
+      while (dyn < end) {
+        unsigned long long tag = elf_long(&dyn), val = elf_long(&dyn);
+        char *type = dt_type(tag);
 
-      printf(" 0x%0*lx %-20s ", w, tag, *type=='0' ? type : type+1);
-      if (*type == 'd') printf("%ld\n", val);
-      else if (*type == 'b') printf("%ld (bytes)\n", val);
-      else if (*type == 's') printf("%s\n", TT.elf+dynstr.offset+val);
-      else if (*type == 'f' || *type == 'F') {
-        struct bitname { int bit; char *s; }
-          df_names[] = {{0, "ORIGIN"},{1,"SYMBOLIC"},{2,"TEXTREL"},
-            {3,"BIND_NOW"},{4,"STATIC_TLS"},{}},
-          df_1_names[]={{0,"NOW"},{1,"GLOBAL"},{2,"GROUP"},{3,"NODELETE"},
-            {5,"INITFIRST"},{27,"PIE"},{}},
-          *names = *type == 'f' ? df_names : df_1_names;
-        int mask;
+        printf(" 0x%0*llx %-20s ", w, tag, *type=='0' ? type : type+1);
+        if (*type == 'd') printf("%lld\n", val);
+        else if (*type == 'b') printf("%lld (bytes)\n", val);
+        else if (*type == 's') printf("%s\n", TT.elf+dynstr.offset+val);
+        else if (*type == 'f' || *type == 'F') {
+          struct bitname { int bit; char *s; }
+            df_names[] = {{0, "ORIGIN"},{1,"SYMBOLIC"},{2,"TEXTREL"},
+              {3,"BIND_NOW"},{4,"STATIC_TLS"},{}},
+            df_1_names[]={{0,"NOW"},{1,"GLOBAL"},{2,"GROUP"},{3,"NODELETE"},
+              {5,"INITFIRST"},{27,"PIE"},{}},
+            *names = *type == 'f' ? df_names : df_1_names;
+          int mask;
 
-        if (*type == 'F') printf("Flags: ");
-        for (j=0; names[j].s; j++) {
-          if (val & (mask=(1<<names[j].bit))) {
-            printf("%s%s", names[j].s, (val &= ~mask) ? " " : "");
+          if (*type == 'F') printf("Flags: ");
+          for (j=0; names[j].s; j++) {
+            if (val & (mask=(1<<names[j].bit))) {
+              printf("%s%s", names[j].s, (val &= ~mask) ? " " : "");
+            }
           }
-        }
-        if (val) printf("0x%lx", val);
-        xputc('\n');
-      } else if (*type == 'N' || *type == 'R' || *type == 'S') {
-        printf("%s: [%s]\n", *type=='N' ? "Shared library" :
-          (*type=='R' ? "Library runpath" : "Library soname"),
-          TT.elf+dynstr.offset+val);
-      } else if (*type == 'P') {
-        type = dt_type(val);
-        j = strlen(type);
-        if (*type != '0') type += 2, j -= 3;
-        printf("%*.*s\n", j, j, type);
-      } else printf("0x%lx\n", val);
+          if (val) printf("0x%llx", val);
+          xputc('\n');
+        } else if (*type == 'N' || *type == 'R' || *type == 'S') {
+          char *s = TT.elf+dynstr.offset+val;
+
+          if (dynstr.offset>TT.size || val>TT.size || dynstr.offset>TT.size-val)
+            s = "???";
+          printf("%s: [%s]\n", *type=='N' ? "Shared library" :
+            (*type=='R' ? "Library runpath" : "Library soname"), s);
+        } else if (*type == 'P') {
+          type = dt_type(val);
+          j = strlen(type);
+          if (*type != '0') type += 2, j -= 3;
+          printf("%*.*s\n", j, j, type);
+        } else printf("0x%llx\n", val);
+      }
     }
   }
 
@@ -506,7 +574,7 @@
     int found = 0;
 
     for (i=0; i<TT.shnum; i++) {
-      get_sh(i, &s);
+      if (!get_sh(i, &s)) continue;
       if (s.type == 7 /*SHT_NOTE*/) {
         printf("\nDisplaying notes found in: %s\n", s.name);
         show_notes(s.offset, s.size);
@@ -514,7 +582,7 @@
       }
     }
     for (i=0; !found && i<phnum; i++) {
-      get_ph(i, &ph);
+      if (!get_ph(i, &ph)) continue;
       if (ph.type == 4 /*PT_NOTE*/) {
         printf("\n"
           "Displaying notes found at file offset 0x%llx with length 0x%llx:\n",
@@ -571,6 +639,7 @@
   int all = FLAG_d|FLAG_h|FLAG_l|FLAG_n|FLAG_S|FLAG_s|FLAG_dyn_syms;
 
   if (FLAG(a)) toys.optflags |= all;
+  if (FLAG(e)) toys.optflags |= FLAG_h|FLAG_l|FLAG_S;
   if (FLAG(s)) toys.optflags |= FLAG_dyn_syms;
   if (!(toys.optflags & (all|FLAG_p|FLAG_x))) help_exit("needs a flag");
 
diff --git a/toys/pending/route.c b/toys/pending/route.c
index 55f26c2..36089ae 100644
--- a/toys/pending/route.c
+++ b/toys/pending/route.c
@@ -2,33 +2,29 @@
  *
  * Copyright 2012 Ranjan Kumar <ranjankumar.bth@gmail.com>
  * Copyright 2013 Kyungwan Han <asura321@gmail.com>
+ * Copyright 2020 Eric Molitor <eric@molitor.org>
  *
  * No Standard
  *
- * TODO: autodetect -net -host target dev -A (but complain)
  * route add -net target 10.0.0.0 netmask 255.0.0.0 dev eth0
  * route del delete
  * delete net route, must match netmask, informative error message
  *
  * mod dyn reinstate metric netmask gw mss window irtt dev
 
-USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN))
+USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN))
 config ROUTE
   bool "route"
   default n
   help
-    usage: route [-ne] [-A [46]] [add|del TARGET [OPTIONS]]
+    usage: route [-ne] [-A [inet|inet6]] [add|del TARGET [OPTIONS]]
 
-    Display, add or delete network routes in the "Forwarding Information Base".
+    Display, add or delete network routes in the "Forwarding Information Base",
+    which send packets out a network interface to an address.
 
     -n	Show numerical addresses (no DNS lookups)
     -e	display netstat fields
 
-    Routing means sending packets out a network interface to an address.
-    The kernel can tell where to send packets one hop away by examining each
-    interface's address and netmask, so the most common use of this command
-    is to identify a "gateway" that forwards other traffic.
-
     Assigning an address to an interface automatically creates an appropriate
     network route ("ifconfig eth0 10.0.2.15/8" does "route add 10.0.0.0/8 eth0"
     for you), although some devices (such as loopback) won't show it in the
@@ -40,27 +36,22 @@
 
     Available OPTIONS include:
     reject   - blocking route (force match failure)
-    dev NAME - force packets out this interface (ala "eth0")
+    dev NAME - force matching packets out this interface (ala "eth0")
     netmask  - old way of saying things like ADDR/24
     gw ADDR  - forward packets to gateway ADDR
-
 */
 
 #define FOR_route
 #include "toys.h"
-#include <net/route.h>
+#define _LINUX_SYSINFO_H     // workaround for musl bug
+#include <linux/rtnetlink.h>
 
 GLOBALS(
-  char *family;
+  char *A;
 )
 
-#define DEFAULT_PREFIXLEN 128
-#define INVALID_ADDR 0xffffffffUL
-#define IPV6_ADDR_LEN 40 //32 + 7 (':') + 1 ('\0')
-
 struct _arglist {
   char *arg;
-
   int action;
 };
 
@@ -74,103 +65,176 @@
   { NULL, 0 }
 };
 
-// to get the host name from the given ip.
-static int get_hostname(char *ipstr, struct sockaddr_in *sockin)
+void xsend(int sockfd, void *buf, size_t len)
 {
-  struct hostent *host;
-
-  sockin->sin_family = AF_INET;
-  sockin->sin_port = 0;
-
-  if (!strcmp(ipstr, "default")) {
-    sockin->sin_addr.s_addr = INADDR_ANY;
-    return 1;
-  }
-
-  if (inet_aton(ipstr, &sockin->sin_addr)) return 0;
-  if (!(host = gethostbyname(ipstr))) perror_exit("resolving '%s'", ipstr);
-  memcpy(&sockin->sin_addr, host->h_addr_list[0], sizeof(struct in_addr));
-
-  return 0;
+  if (send(sockfd, buf, len, 0) != len) perror_exit("xsend");
 }
 
-// used to extract the address info from the given ip.
-static int get_addrinfo(char *ip, struct sockaddr_in6 *sock_in6)
+int xrecv(int sockfd, void *buf, size_t len)
 {
-  struct addrinfo hints, *result;
-  int status = 0;
+  int msg_len = recv(sockfd, buf, len, 0);
+  if (msg_len < 0) perror_exit("xrecv");
 
-  memset(&hints, 0, sizeof(struct addrinfo));
-  hints.ai_family = AF_INET6;
-  if ((status = getaddrinfo(ip, NULL, &hints, &result))) {
-    perror_msg("getaddrinfo: %s", gai_strerror(status));
-    return -1;
-  }
-  if (result) {
-    memcpy(sock_in6, result->ai_addr, sizeof(*sock_in6));
-    freeaddrinfo(result);
-  }
-  return 0;
+  return msg_len;
 }
 
-static void get_flag_value(char *str, int flags)
+void addAttr(struct nlmsghdr *nl, int maxlen, void *attr, int type, int len)
 {
-  // RTF_* bits in order:
-  // UP, GATEWAY, HOST, REINSTATE, DYNAMIC, MODIFIED, DEFAULT, ADDRCONF, CACHE
-  int i = 0, mask = 0x105003f;
-
-  for (; mask; mask>>=1) if (mask&1) {
-    if (flags&(1<<i)) *str++ = "UGHRDMDAC"[i];
-    i++;
-  }
-  *str = 0;
+  struct rtattr *rt;
+  int rtlen = RTA_LENGTH(len);
+  if (NLMSG_ALIGN(nl->nlmsg_len) + rtlen > maxlen) perror_exit("addAttr");
+  rt = (struct rtattr*)((char *)nl + NLMSG_ALIGN(nl->nlmsg_len));
+  rt->rta_type = type;
+  rt->rta_len = rtlen;
+  memcpy(RTA_DATA(rt), attr, len);
+  nl->nlmsg_len = NLMSG_ALIGN(nl->nlmsg_len) + rtlen;
 }
 
-// extract inet4 route info from /proc/net/route file and display it.
-static void display_routes(void)
-{
-  unsigned long dest, gate, mask;
-  int flags, ref, use, metric, mss, win, irtt, items;
-  char iface[64] = {0,}, flag_val[10]; //there are 9 flags "UGHRDMDAC" for route.
+static void get_hostname(sa_family_t f, void *a, char *dst, size_t len) {
+  size_t a_len = (AF_INET6 == f) ? sizeof(struct in6_addr) : sizeof(struct in_addr);
 
-  FILE *fp = xfopen("/proc/net/route", "r");
-
-  xprintf("Kernel IP routing table\n"
-      "Destination     Gateway         Genmask         Flags %s Iface\n",
-      (toys.optflags & FLAG_e)? "  MSS Window  irtt" : "Metric Ref    Use");
-
-  if (fscanf(fp, "%*[^\n]\n") < 0) perror_exit("fscanf"); //skip 1st line
-  while ((items = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n", iface, &dest, 
-          &gate, &flags, &ref, &use, &metric, &mask, &mss, &win, &irtt)) == 11)
-  {
-    char *destip = toybuf, *gateip = toybuf+32, *maskip = toybuf+64; //ip string 16
-
-    if (!(flags & RTF_UP)) continue; //skip down interfaces.
-
-    if (!dest && !(toys.optflags & FLAG_n)) strcpy( destip, "default");
-    else if (!inet_ntop(AF_INET, &dest, destip, 32)) perror_exit("inet");
-
-    if (!gate && !(toys.optflags & FLAG_n)) strcpy( gateip, "*");
-    else if (!inet_ntop(AF_INET, &gate, gateip, 32)) perror_exit("inet");
-
-    if (!inet_ntop(AF_INET, &mask, maskip, 32)) perror_exit("inet");
-
-    //Get flag Values
-    get_flag_value(flag_val, flags);
-    if (flags & RTF_REJECT) flag_val[0] = '!';
-    xprintf("%-15.15s %-15.15s %-16s%-6s", destip, gateip, maskip, flag_val);
-    if (toys.optflags & FLAG_e) xprintf("%5d %-5d %6d %s\n", mss, win, irtt, iface);
-    else xprintf("%-6d %-2d %7d %s\n", metric, ref, use, iface);
-  }
-
-  if (items > 0 && feof(fp)) perror_exit("fscanf %d", items);
-  fclose(fp);
+  struct hostent *host = gethostbyaddr(a, a_len, f);
+  if (host) xstrncpy(dst, host->h_name, len);
 }
 
-/*
- * find the given parameter in list like add/del/net/host.
- * and if match found return the appropriate action.
- */
+static void display_routes(sa_family_t f)
+{
+  int fd, msg_hdr_len, route_protocol;
+  struct {
+    struct nlmsghdr nl;
+    struct rtmsg rt;
+  } req;
+  struct nlmsghdr buf[8192 / sizeof(struct nlmsghdr)];
+  struct nlmsghdr *msg_hdr_ptr;
+  struct rtmsg *route_entry;
+  struct rtattr *rteattr;
+
+  fd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+
+  memset(&req, 0, sizeof(req));
+  req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+  req.nl.nlmsg_type = RTM_GETROUTE;
+  req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  req.nl.nlmsg_pid = getpid();
+  req.nl.nlmsg_seq = 1;
+  req.rt.rtm_family = f;
+  req.rt.rtm_table = RT_TABLE_MAIN;
+  xsend(fd, &req, sizeof(req));
+
+  if (f == AF_INET) {
+    xprintf("Kernel IP routing table\n"
+            "Destination     Gateway         Genmask         Flags %s Iface\n",
+            FLAG(e) ? "  MSS Window  irtt" : "Metric Ref    Use");
+  } else {
+    xprintf("Kernel IPv6 routing table\n"
+            "%-31s%-26s Flag Metric Ref Use If\n", "Destination", "Next Hop");
+  }
+
+  msg_hdr_len = xrecv(fd, buf, sizeof(buf));
+  msg_hdr_ptr = buf;
+  while (msg_hdr_ptr->nlmsg_type != NLMSG_DONE) {
+    while (NLMSG_OK(msg_hdr_ptr, msg_hdr_len)) {
+      route_entry = NLMSG_DATA(msg_hdr_ptr);
+      route_protocol = route_entry->rtm_protocol;
+
+      // Annoyingly NLM_F_MATCH is not yet implemented so even if we pass in
+      // RT_TABLE_MAIN with RTM_GETROUTE it still returns everything so we
+      // have to filter here.
+      if (route_entry->rtm_table == RT_TABLE_MAIN) {
+        int route_attribute_len;
+        char dest[INET6_ADDRSTRLEN], gate[INET6_ADDRSTRLEN], netmask[32],
+             flags[10] = "U", if_name[IF_NAMESIZE] = "-";
+        unsigned priority = 0, mss = 0, win = 0, irtt = 0, ref = 0, use = 0,
+                 route_netmask, metric_len;
+        struct in_addr netmask_addr;
+        struct rtattr *metric;
+        struct rta_cacheinfo *cache_info;
+
+        if (f == AF_INET) {
+          strcpy(dest, FLAG(n) ? "0.0.0.0" : "default");
+          strcpy(gate, FLAG(n) ? "*" : "0.0.0.0");
+          strcpy(netmask, "0.0.0.0");
+        } else {
+          strcpy(dest, "::");
+          strcpy(gate, "::");
+        }
+
+        route_netmask = route_entry->rtm_dst_len;
+        if (route_netmask == 0) netmask_addr.s_addr = ~((in_addr_t) -1);
+        else netmask_addr.s_addr = htonl(~((1 << (32 - route_netmask)) - 1));
+        inet_ntop(AF_INET, &netmask_addr, netmask, sizeof(netmask));
+
+        rteattr = RTM_RTA(route_entry);
+        route_attribute_len = RTM_PAYLOAD(msg_hdr_ptr);
+        while (RTA_OK(rteattr, route_attribute_len)) {
+          switch (rteattr->rta_type) {
+            case RTA_DST:
+              if (FLAG(n)) inet_ntop(f, RTA_DATA(rteattr), dest, sizeof(dest));
+              else get_hostname(f, RTA_DATA(rteattr), dest, sizeof(dest));
+              break;
+
+            case RTA_GATEWAY:
+              if (FLAG(n)) inet_ntop(f, RTA_DATA(rteattr), gate, sizeof(dest));
+              else get_hostname(f, RTA_DATA(rteattr), gate, sizeof(dest));
+              strcat(flags, "G");
+              break;
+
+            case RTA_PRIORITY:
+              priority = *(unsigned *)RTA_DATA(rteattr);
+              break;
+
+            case RTA_OIF:
+              if_indextoname(*(int *)RTA_DATA(rteattr), if_name);
+              break;
+
+            case RTA_METRICS:
+              metric_len = RTA_PAYLOAD(rteattr);
+              for (metric = RTA_DATA(rteattr); RTA_OK(metric, metric_len);
+                   metric = RTA_NEXT(metric, metric_len))
+                if (metric->rta_type == RTAX_ADVMSS) 
+                  mss = *(unsigned *)RTA_DATA(metric);
+                else if (metric->rta_type == RTAX_WINDOW)
+                  win = *(unsigned *)RTA_DATA(metric);
+                else if (metric->rta_type == RTAX_RTT)
+                  irtt = (*(unsigned *)RTA_DATA(metric))/8;
+              break;
+
+            case RTA_CACHEINFO:
+              cache_info = RTA_DATA(rteattr);
+              ref = cache_info->rta_clntref;
+              use = cache_info->rta_used;
+              break;
+          }
+
+          rteattr = RTA_NEXT(rteattr, route_attribute_len);
+        }
+
+        if (route_entry->rtm_type == RTN_UNREACHABLE) flags[0] = '!';
+        if (route_netmask == 32) strcat(flags, "H");
+        if (route_protocol == RTPROT_REDIRECT) strcat(flags, "D");
+
+        if (f == AF_INET) {
+          xprintf("%-15.15s %-15.15s %-16s%-6s", dest, gate, netmask, flags);
+          if (FLAG(e)) xprintf("%5d %-5d %6d %s\n", mss, win, irtt, if_name);
+          else xprintf("%-6d %-2d %7d %s\n", priority, ref, use, if_name);
+        } else {
+          char *dest_with_mask = xmprintf("%s/%u", dest, route_netmask);
+          xprintf("%-30s %-26s %-4s %-6d %-4d %2d %-8s\n",
+                  dest_with_mask, gate, flags, priority, ref, use, if_name);
+          free(dest_with_mask);
+        }
+      }
+      msg_hdr_ptr = NLMSG_NEXT(msg_hdr_ptr, msg_hdr_len);
+    }
+
+    msg_hdr_len = xrecv(fd, buf, sizeof(buf));
+    msg_hdr_ptr = buf;
+  }
+
+  xclose(fd);
+}
+
+// find parameter (add/del/net/host) in list, return appropriate action or 0.
 static int get_action(char ***argv, struct _arglist *list)
 {
   struct _arglist *alist;
@@ -185,271 +249,135 @@
   return 0;
 }
 
-/*
- * used to get the params like: metric, netmask, gw, mss, window, irtt, dev and their values.
- * additionally set the flag values for reject, mod, dyn and reinstate.
- */
-static void get_next_params(char **argv, struct rtentry *rt, char **netmask)
-{
-  for (;*argv;argv++) {
-    if (!strcmp(*argv, "reject")) rt->rt_flags |= RTF_REJECT;
-    else if (!strcmp(*argv, "mod")) rt->rt_flags |= RTF_MODIFIED;
-    else if (!strcmp(*argv, "dyn")) rt->rt_flags |= RTF_DYNAMIC;
-    else if (!strcmp(*argv, "reinstate")) rt->rt_flags |= RTF_REINSTATE;
-    else {
-      if (!argv[1]) help_exit(0);
-
-      //set the metric field in the routing table.
-      if (!strcmp(*argv, "metric"))
-        rt->rt_metric = atolx_range(argv[1], 0, ULONG_MAX) + 1;
-      else if (!strcmp(*argv, "netmask")) {
-        //when adding a network route, the netmask to be used.
-        struct sockaddr sock;
-        unsigned int addr_mask = (((struct sockaddr_in *)&((rt)->rt_genmask))->sin_addr.s_addr);
-
-        if (addr_mask) help_exit("dup netmask");
-        *netmask = argv[1];
-        get_hostname(*netmask, (struct sockaddr_in *) &sock);
-        rt->rt_genmask = sock;
-      } else if (!strcmp(*argv, "gw")) { 
-        //route packets via a gateway.
-        if (!(rt->rt_flags & RTF_GATEWAY)) {
-          if (!get_hostname(argv[1], (struct sockaddr_in *) &rt->rt_gateway))
-            rt->rt_flags |= RTF_GATEWAY;
-          else perror_exit("gateway '%s' is a NETWORK", argv[1]);
-        } else help_exit("dup gw");
-      } else if (!strcmp(*argv, "mss")) {
-        //set the TCP Maximum Segment Size for connections over this route.
-        rt->rt_mtu = atolx_range(argv[1], 64, 65536);
-        rt->rt_flags |= RTF_MSS;
-      } else if (!strcmp(*argv, "window")) {
-        //set the TCP window size for connections over this route to W bytes.
-        rt->rt_window = atolx_range(argv[1], 128, INT_MAX); //win low
-        rt->rt_flags |= RTF_WINDOW;
-      } else if (!strcmp(*argv, "irtt")) {
-        rt->rt_irtt = atolx_range(argv[1], 0, INT_MAX);
-        rt->rt_flags |= RTF_IRTT;
-      } else if (!strcmp(*argv, "dev") && !rt->rt_dev) rt->rt_dev = argv[1];
-      else help_exit("no '%s'", *argv);
-      argv++;
-    }
-  }
-
-  if (!rt->rt_dev && (rt->rt_flags & RTF_REJECT)) rt->rt_dev = (char *)"lo";
-}
-
-// verify the netmask and conflict in netmask and route address.
-static void verify_netmask(struct rtentry *rt, char *netmask)
-{
-  unsigned int addr_mask = (((struct sockaddr_in *)&((rt)->rt_genmask))->sin_addr.s_addr);
-  unsigned int router_addr = ~(unsigned int)(((struct sockaddr_in *)&((rt)->rt_dst))->sin_addr.s_addr);
-
-  if (addr_mask) {
-    addr_mask = ~ntohl(addr_mask);
-    if ((rt->rt_flags & RTF_HOST) && addr_mask != INVALID_ADDR)
-      perror_exit("conflicting netmask and host route");
-    if (addr_mask & (addr_mask + 1)) perror_exit("wrong netmask '%s'", netmask);
-    addr_mask = ((struct sockaddr_in *) &rt->rt_dst)->sin_addr.s_addr;
-    if (addr_mask & router_addr) perror_exit("conflicting netmask and route address");
-  }
-}
-
 // add/del a route.
-static void setroute(char **argv)
+static void setroute(sa_family_t f, char **argv)
 {
-  struct rtentry rt;
-  char *netmask, *targetip;
-  int is_net_or_host = 0, sokfd, arg2_action;
+  char *tgtip;
+  int sockfd, arg2_action;
   int action = get_action(&argv, arglist1); //verify the arg for add/del.
+  struct nlmsghdr buf[8192 / sizeof(struct nlmsghdr)];
+  struct nlmsghdr *nlMsg;
+  struct rtmsg *rtMsg;
 
   if (!action || !*argv) help_exit("setroute");
-
   arg2_action = get_action(&argv, arglist2); //verify the arg for -net or -host
   if (!*argv) help_exit("setroute");
+  tgtip = *argv++;
+  sockfd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  memset(buf, 0, sizeof(buf));
+  nlMsg = (struct nlmsghdr *) buf;
+  rtMsg = (struct rtmsg *) NLMSG_DATA(nlMsg);
 
-  memset(&rt, 0, sizeof(struct rtentry));
-  targetip = *argv++;
+  nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
 
-  netmask = strchr(targetip, '/');
-  if (netmask) {
-    *netmask++ = 0;
-    //used to verify the netmask and route conflict.
-    (((struct sockaddr_in *)&rt.rt_genmask)->sin_addr.s_addr)
-      = htonl((1<<(32-atolx_range(netmask, 0, 32)))-1);
-    rt.rt_genmask.sa_family = AF_INET;
-    netmask = 0;
-  } else netmask = "default";
+  //TODO(emolitor): Improve action and arg2_action handling
+  if (action == 1) { // Add
+    nlMsg->nlmsg_type = RTM_NEWROUTE;
+    nlMsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
+  } else { // Delete
+    nlMsg->nlmsg_type = RTM_DELROUTE;
+    nlMsg->nlmsg_flags = NLM_F_REQUEST;
+  }
 
-  is_net_or_host = get_hostname(targetip, (void *)&rt.rt_dst);
+  nlMsg->nlmsg_pid = getpid();
+  nlMsg->nlmsg_seq = 1;
+  rtMsg->rtm_family = f;
+  rtMsg->rtm_table = RT_TABLE_UNSPEC;
+  rtMsg->rtm_type = RTN_UNICAST;
+  rtMsg->rtm_protocol = RTPROT_UNSPEC;
+  rtMsg->rtm_flags = RTM_F_NOTIFY;
+  rtMsg->rtm_dst_len = rtMsg->rtm_src_len = (f == AF_INET) ? 32 : 128;
 
-  if (arg2_action) is_net_or_host = arg2_action & 1;
-  rt.rt_flags = ((is_net_or_host) ? RTF_UP : (RTF_UP | RTF_HOST));
+  if (arg2_action == 2) rtMsg->rtm_scope = RT_SCOPE_HOST;
 
-  get_next_params(argv, &rt, (char **)&netmask);
-  verify_netmask(&rt, (char *)netmask);
+  size_t addr_len = sizeof(struct in_addr);
+  if (f == AF_INET6) addr_len = sizeof(struct in6_addr);
+  unsigned char addr[sizeof(struct in6_addr)] = {0,};
 
-  if ((action == 1) && (rt.rt_flags & RTF_HOST))
-    (((struct sockaddr_in *)&((rt).rt_genmask))->sin_addr.s_addr) = INVALID_ADDR;
-
-  sokfd = xsocket(AF_INET, SOCK_DGRAM, 0);
-  if (action == 1) xioctl(sokfd, SIOCADDRT, &rt);
-  else xioctl(sokfd, SIOCDELRT, &rt);
-  xclose(sokfd);
-}
-
-/*
- * get prefix len (if any) and remove the prefix from target ip.
- * if no prefix then set default prefix len.
- */
-static void is_prefix_inet6(char **tip, struct in6_rtmsg *rt)
-{
-  unsigned long plen;
-  char *prefix = strchr(*tip, '/');
-
-  if (prefix) {
-    *prefix = '\0';
-    plen = atolx_range(prefix + 1, 0, 128); //DEFAULT_PREFIXLEN);
-  } else plen = DEFAULT_PREFIXLEN;
-
-  rt->rtmsg_flags = (plen == DEFAULT_PREFIXLEN) ? (RTF_UP | RTF_HOST) : RTF_UP;
-  rt->rtmsg_dst_len = plen;
-}
-
-/*
- * used to get the params like: metric, gw, dev and their values.
- * additionally set the flag values for mod and dyn.
- */
-static void get_next_params_inet6(char **argv, struct sockaddr_in6 *sock_in6, struct in6_rtmsg *rt, char **dev_name)
-{
-  for (;*argv;argv++) {
-    if (!strcmp(*argv, "mod")) rt->rtmsg_flags |= RTF_MODIFIED;
-    else if (!strcmp(*argv, "dyn")) rt->rtmsg_flags |= RTF_DYNAMIC;
+  for (; *argv; argv++) {
+    if (!strcmp(*argv, "mod")) continue;
+    else if (!strcmp(*argv, "dyn")) continue;
+    else if (!strcmp(*argv, "reinstate")) continue;
+    else if (!strcmp(*argv, "reject")) rtMsg->rtm_type = RTN_UNREACHABLE;
     else {
-      if (!argv[1]) help_exit(0);
+      if (!argv[1]) show_help(stdout, 1);
 
-      if (!strcmp(*argv, "metric")) 
-        rt->rtmsg_metric = atolx_range(argv[1], 0, ULONG_MAX);
-      else if (!strcmp(*argv, "gw")) {
-        //route packets via a gateway.
-        if (!(rt->rtmsg_flags & RTF_GATEWAY)) {
-          if (!get_addrinfo(argv[1], (struct sockaddr_in6 *) &sock_in6)) {
-            memcpy(&rt->rtmsg_gateway, sock_in6->sin6_addr.s6_addr, sizeof(struct in6_addr));
-            rt->rtmsg_flags |= RTF_GATEWAY;
-          } else perror_exit("resolving '%s'", argv[1]);
-        } else help_exit(0);
+      if (!strcmp(*argv, "metric")) {
+        unsigned int priority = atolx_range(argv[1], 0, UINT_MAX);
+        addAttr(nlMsg, sizeof(toybuf), &priority, RTA_PRIORITY, sizeof(unsigned int));
+      } else if (!strcmp(*argv, "netmask")) {
+        uint32_t netmask;
+        char *ptr;
+        uint32_t naddr[4] = {0,};
+        uint64_t plen;
+
+        netmask = (f == AF_INET6) ? 128 : 32; // set default netmask
+        plen = strtoul(argv[1], &ptr, 0);
+
+        if (!ptr || ptr == argv[1] || *ptr || !plen || plen > netmask) {
+          if (!inet_pton(f, argv[1], &naddr)) error_exit("invalid netmask");
+          if (f == AF_INET) {
+            uint32_t mask = htonl(*naddr), host = ~mask;
+            if (host & (host + 1)) error_exit("invalid netmask");
+            for (plen = 0; mask; mask <<= 1) ++plen;
+            if (plen > 32) error_exit("invalid netmask");
+          }
+        }
+        netmask = plen;
+        rtMsg->rtm_dst_len = netmask;
+      } else if (!strcmp(*argv, "gw")) {
+        if (!inet_pton(f, argv[1], &addr)) error_exit("invalid gw");
+        addAttr(nlMsg, sizeof(toybuf), &addr, RTA_GATEWAY, addr_len);
+      } else if (!strcmp(*argv, "mss")) {
+        // TODO(emolitor): Add RTA_METRICS support
+        //set the TCP Maximum Segment Size for connections over this route.
+        //rt->rt_mtu = atolx_range(argv[1], 64, 65536);
+        //rt->rt_flags |= RTF_MSS;
+      } else if (!strcmp(*argv, "window")) {
+        // TODO(emolitor): Add RTA_METRICS support
+        //set the TCP window size for connections over this route to W bytes.
+        //rt->rt_window = atolx_range(argv[1], 128, INT_MAX); //win low
+        //rt->rt_flags |= RTF_WINDOW;
+      } else if (!strcmp(*argv, "irtt")) {
+        // TODO(emolitor): Add RTA_METRICS support
+        //rt->rt_irtt = atolx_range(argv[1], 0, INT_MAX);
+        //rt->rt_flags |= RTF_IRTT;
       } else if (!strcmp(*argv, "dev")) {
-        if (!*dev_name) *dev_name = argv[1];
-      } else help_exit(0);
+        unsigned int if_idx = if_nametoindex(argv[1]);
+        if (!if_idx) perror_exit("dev");
+        addAttr(nlMsg, sizeof(toybuf), &if_idx, RTA_OIF, sizeof(unsigned int));
+      } else help_exit("no '%s'", *argv);
       argv++;
     }
   }
-}
 
-// add/del a route.
-static void setroute_inet6(char **argv)
-{
-  struct sockaddr_in6 sock_in6;
-  struct in6_rtmsg rt;
-  char *targetip, *dev_name = 0;
-  int sockfd, action = get_action(&argv, arglist1);
+  if (strcmp(tgtip, "default") != 0) {
+    char *prefix = strtok(0, "/");
 
-  if (!action || !*argv) help_exit(0);
-  memset(&sock_in6, 0, sizeof(struct sockaddr_in6));
-  memset(&rt, 0, sizeof(struct in6_rtmsg));
-  targetip = *argv++;
-  if (!*argv) help_exit(0);
+    if (prefix) rtMsg->rtm_dst_len = strtoul(prefix, &prefix, 0);
+    if (!inet_pton(f, strtok(tgtip, "/"), &addr)) error_exit("invalid target");
+    addAttr(nlMsg, sizeof(toybuf), &addr, RTA_DST, addr_len);
+  } else rtMsg->rtm_dst_len = 0;
 
-  if (!strcmp(targetip, "default")) {
-    rt.rtmsg_flags = RTF_UP;
-    rt.rtmsg_dst_len = 0;
-  } else {
-    is_prefix_inet6((char **)&targetip, &rt);
-    if (get_addrinfo(targetip, (struct sockaddr_in6 *) &sock_in6))
-      perror_exit("resolving '%s'", targetip);
-  }
-  rt.rtmsg_metric = 1; //default metric.
-  memcpy(&rt.rtmsg_dst, sock_in6.sin6_addr.s6_addr, sizeof(struct in6_addr));
-  get_next_params_inet6(argv, &sock_in6, &rt, (char **)&dev_name);
-
-  sockfd = xsocket(AF_INET6, SOCK_DGRAM, 0);
-  if (dev_name) {
-    char ifre_buf[sizeof(struct ifreq)] = {0,};
-    struct ifreq *ifre = (struct ifreq*)ifre_buf;
-    xstrncpy(ifre->ifr_name, dev_name, IFNAMSIZ);
-    xioctl(sockfd, SIOGIFINDEX, ifre);
-    rt.rtmsg_ifindex = ifre->ifr_ifindex;
-  }          
-  if (action == 1) xioctl(sockfd, SIOCADDRT, &rt);
-  else xioctl(sockfd, SIOCDELRT, &rt);
+  xsend(sockfd, nlMsg, nlMsg->nlmsg_len);
   xclose(sockfd);
 }
 
-/*
- * format the dest and src address in ipv6 format.
- * e.g. 2002:6b6d:26c8:d:ea03:9aff:fe65:9d62
- */
-static void ipv6_addr_formating(char *ptr, char *addr)
-{
-  int i = 0;
-  while (i <= IPV6_ADDR_LEN) {
-    if (!*ptr) {
-      if (i == IPV6_ADDR_LEN) {
-        addr[IPV6_ADDR_LEN - 1] = 0; //NULL terminating the ':' separated address.
-        break;
-      }
-      error_exit("IPv6 ip format error");
-    }
-    addr[i++] = *ptr++;
-    if (!((i+1) % 5)) addr[i++] = ':'; //put ':' after 4th bit
-  }
-}
-
-static void display_routes6(void)
-{
-  char iface[16] = {0,}, ipv6_dest_addr[41]; 
-  char ipv6_src_addr[41], flag_val[10], buf2[INET6_ADDRSTRLEN];
-  int prefixlen, metric, use, refcount, flag, items = 0;
-  unsigned char buf[sizeof(struct in6_addr)];
-
-  FILE *fp = xfopen("/proc/net/ipv6_route", "r");
-
-  xprintf("Kernel IPv6 routing table\n"
-      "%-43s%-40s Flags Metric Ref    Use Iface\n", "Destination", "Next Hop");
-
-  while ((items = fscanf(fp, "%32s%x%*s%*x%32s%x%x%x%x%15s\n", ipv6_dest_addr+8,
-          &prefixlen, ipv6_src_addr+8, &metric, &use, &refcount, &flag, 
-          iface)) == 8)
-  {
-    if (!(flag & RTF_UP)) continue; //skip down interfaces.
-
-    //ipv6_dest_addr+8: as the values are filled from the 8th location of the array.
-    ipv6_addr_formating(ipv6_dest_addr+8, ipv6_dest_addr);
-    ipv6_addr_formating(ipv6_src_addr+8, ipv6_src_addr);
-
-    get_flag_value(flag_val, flag);
-    if (inet_pton(AF_INET6, ipv6_dest_addr, buf) <= 0) perror_exit("inet");
-    if (inet_ntop(AF_INET6, buf, buf2, INET6_ADDRSTRLEN))
-      sprintf(toybuf, "%s/%d", buf2, prefixlen);
-
-    if (inet_pton(AF_INET6, ipv6_src_addr, buf) <= 0) perror_exit("inet");
-    if (inet_ntop(AF_INET6, buf, buf2, INET6_ADDRSTRLEN))
-      xprintf("%-43s %-39s %-5s %-6d %-4d %5d %-8s\n",
-          toybuf, buf2, flag_val, metric, refcount, use, iface);
-  }
-  if ((items > 0) && feof(fp)) perror_exit("fscanf");
-
-  fclose(fp);
-}
-
 void route_main(void)
 {
-  if (!TT.family) TT.family = "inet";
   if (!*toys.optargs) {
-    if (!strcmp(TT.family, "inet")) display_routes();
-    else if (!strcmp(TT.family, "inet6")) display_routes6();
-    else help_exit(0);
+    if (!TT.A || !strcmp(TT.A, "inet")) display_routes(AF_INET);
+    else if (!strcmp(TT.A, "inet6")) display_routes(AF_INET6);
+    else show_help(stdout, 1);
   } else {
-    if (!strcmp(TT.family, "inet6")) setroute_inet6(toys.optargs);
-    else setroute(toys.optargs);
+    if (!TT.A) {
+      if (toys.optc>1 && strchr(toys.optargs[1], ':')) {
+          xprintf("WARNING: Implicit IPV6 address using -Ainet6\n");
+          TT.A = "inet6";
+      } else TT.A = "inet";
+    }
+
+    if (!strcmp(TT.A, "inet")) setroute(AF_INET, toys.optargs);
+    else setroute(AF_INET6, toys.optargs);
   }
 }
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 9f68693..ab2408a 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -2,55 +2,58 @@
  *
  * Copyright 2006 Rob Landley <rob@landley.net>
  *
- * The POSIX-2008/SUSv4 spec for this is at:
+ * This shell aims for bash compatibility. The bash man page is at:
+ * http://man7.org/linux/man-pages/man1/bash.1.html
+ *
+ * The POSIX-2008/SUSv4 shell spec is at:
  * http://opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
  * and http://opengroup.org/onlinepubs/9699919799/utilities/sh.html
  *
- * The first link describes the following shell builtins:
- *
- *   break colon continue dot eval exec exit export readonly return set shift
- *   times trap unset
- *
- * The second link (the utilities directory) also contains specs for the
- * following shell builtins:
- *
- *   alias bg cd command fc fg getopts hash jobs kill read type ulimit
- *   umask unalias wait
- *
- * Things like the bash man page are good to read too.
- *
  * deviations from posix: don't care about $LANG or $LC_ALL
 
- * TODO: test that $PS1 color changes work without stupid \[ \] hack
- * TODO: Handle embedded NUL bytes in the command line? (When/how?)
- * TODO: replace getenv() with faster func: sort env and binary search
-
- * buitins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
- *          disown umask suspend source pushd popd dirs logout times trap
- *          unset local export readonly set : . let history declare
+ * builtins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
+ *          disown suspend source pushd popd dirs logout times trap cd hash exit
+ *           unset local export readonly set : . let history declare ulimit type
  * "special" builtins: break continue eval exec return shift
- * builtins with extra shell behavior: kill pwd time test
+ * external with extra shell behavior: kill pwd time test
 
- * | & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
- * * ? [ # ~ = %
- * ! { } case do done elif else esac fi for if in then until while
- * [[ ]] function select
+ * * ? [ # ~ = % [[ ]] function select exit label:
 
- * label:
+ * TODO: case, wildcard +(*|?), job control (find_plus_minus), ${x//}, $(())
+
+ * TODO: support case in $() because $(case a in a) ;; ; esac) stops at first )
  * TODO: test exit from "trap EXIT" doesn't recurse
  * TODO: ! history expansion
  * TODO: getuid() vs geteuid()
+ * TODO: test that $PS1 color changes work without stupid \[ \] hack
+ * TODO: Handle embedded NUL bytes in the command line? (When/how?)
+ * TODO: set -e -u -o pipefail, shopt -s nullglob
  *
  * bash man page:
  * control operators || & && ; ;; ;& ;;& ( ) | |& <newline>
  * reserved words
  *   ! case  coproc  do done elif else esac fi for  function  if  in  select
  *   then until while { } time [[ ]]
+ *
+ * Flow control statements:
+ *
+ * if/then/elif/else/fi, for select while until/do/done, case/esac,
+ * {/}, [[/]], (/), function assignment
 
 USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
+USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 
-USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
+USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
 USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
 USE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
 // Login lies in argv[0], so add some aliases to catch that
@@ -92,42 +95,207 @@
 
     Exit shell.  If no return value supplied on command line, use value
     of most recent command, or 0 if none.
+
+config SET
+  bool
+  default n
+  depends on SH
+  help
+    usage: set [+a] [+o OPTION] [VAR...]
+
+    Set variables and shell attributes. Use + to disable and - to enable.
+    NAME=VALUE arguments assign to the variable, any leftovers set $1, $2...
+    With no arguments, prints current variables.
+
+    -f	NAME is a function
+    -v	NAME is a variable
+    -n	don't follow name reference
+
+    OPTIONs:
+      history - enable command history
+
+config UNSET
+  bool
+  default n
+  depends on SH
+  help
+    usage: unset [-fvn] NAME...
+
+    -f	NAME is a function
+    -v	NAME is a variable
+    -n	dereference NAME and unset that
+
+config EVAL
+  bool
+  default n
+  depends on SH
+  help
+    usage: eval COMMAND...
+
+    Execute (combined) arguments as a shell command.
+
+config EXEC
+  bool
+  default n
+  depends on SH
+  help
+    usage: exec [-cl] [-a NAME] COMMAND...
+
+    -a	set argv[0] to NAME
+    -c	clear environment
+    -l	prepend - to argv[0]
+
+config EXPORT
+  bool
+  default n
+  depends on SH
+  help
+    usage: export [-n] [NAME[=VALUE]...]
+
+    Make variables available to child processes. NAME exports existing local
+    variable(s), NAME=VALUE sets and exports.
+
+    -n	Unexport. Turn listed variable(s) into local variables.
+
+    With no arguments list exported variables/attributes as "declare" statements.
+
+config JOBS
+  bool
+  default n
+  depends on SH
+  help
+    usage: jobs [-lnprs] [%JOB | -x COMMAND...]
+
+    List running/stopped background jobs.
+
+    -l Include process ID in list
+    -n Show only new/changed processes
+    -p Show process IDs only
+    -r Show running processes
+    -s Show stopped processes
+
+config LOCAL
+  bool
+  default n
+  depends on SH
+  help
+    usage: local [NAME[=VALUE]...]
+
+    Create a local variable that lasts until return from this function.
+    With no arguments lists local variables in current function context.
+    TODO: implement "declare" options.
+
+config SHIFT
+  bool
+  default n
+  depends on SH
+  help
+    usage: shift [N]
+
+    Skip N (default 1) positional parameters, moving $1 and friends along the list.
+    Does not affect $0.
+
+config SOURCE
+  bool
+  default n
+  depends on SH
+  help
+    usage: source FILE [ARGS...]
+
+    Read FILE and execute commands. Any ARGS become positional parameters.
+
+config WAIT
+  bool
+  default n
+  depends on SH
+  help
+    usage: wait [-n] [ID...]
+
+    Wait for background processes to exit, returning its exit code.
+    ID can be PID or job, with no IDs waits for all backgrounded processes.
+
+    -n	Wait for next process to exit
 */
 
 #define FOR_sh
 #include "toys.h"
 
 GLOBALS(
-  char *c;
+  union {
+    struct {
+      char *c;
+    } sh;
+    struct {
+      char *a;
+    } exec;
+  };
 
-  long lineno;
-  char **locals, *subshell_env;
-  struct double_list functions;
-  unsigned options, jobcnt, loc_ro, loc_magic;
-  int hfd;  // next high filehandle (>= 10)
+  // keep SECONDS here: used to work around compiler limitation in run_command()
+  long long SECONDS;
+  char *isexec, *wcpat;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
 
-  // Running jobs.
-  struct sh_job {
-    struct sh_job *next, *prev;
-    unsigned jobno;
+  // Callable function array
+  struct sh_function {
+    char *name;
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
+      struct sh_pipeline *next, *prev, *end;
+      int count, here, type, lineno;
+      struct sh_arg {
+        char **v;
+        int c;
+      } arg[1];
+    } *pipeline;
+    unsigned long refcount;
+  } **functions;
+  long funcslen;
 
-    // Every pipeline has at least one set of arguments or it's Not A Thing
-    struct sh_arg {
-      char **v;
-      int c;
-    } pipeline;
+  // runtime function call stack
+  struct sh_fcall {
+    struct sh_fcall *next, *prev;
 
-    // null terminated array of running processes in pipeline
-    struct sh_process {
-      struct sh_process *next, *prev;
-      struct arg_list *delete;   // expanded strings
-      int *urd, envlen, pid, exit;  // undo redirects, child PID, exit status
-      struct sh_arg arg;
-    } *procs, *proc;
-  } *jobs, *job;
+    // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+    struct sh_vars {
+      long flags;
+      char *str;
+    } *vars;
+    long varslen, shift;
+
+    struct sh_function *func; // TODO wire this up
+    struct sh_pipeline *pl;
+    char *ifs;
+    struct sh_arg arg;
+    struct arg_list *delete;
+
+    // Runtime stack of nested if/else/fi and for/do/done contexts.
+    struct sh_blockstack {
+      struct sh_blockstack *next;
+      struct sh_pipeline *start, *middle;
+      struct sh_process *pp;       // list of processes piping in to us
+      int run, loop, *urd, pout, pipe;
+      struct sh_arg farg;          // for/select arg stack, case wildcard deck
+      struct arg_list *fdelete;    // farg's cleanup list
+      char *fvar;                  // for/select's iteration variable name
+    } *blk;
+  } *ff;
+
+// TODO ctrl-Z suspend should stop script
+  struct sh_process {
+    struct sh_process *next, *prev; // | && ||
+    struct arg_list *delete;   // expanded strings
+    // undo redirects, a=b at start, child PID, exit status, has !, job #
+    int *urd, envlen, pid, exit, not, job, dash;
+    long long when; // when job backgrounded/suspended
+    struct sh_arg *raw, arg;
+  } *pp; // currently running process
+
+  // job list, command line for $*, scratch space for do_wildcard_files()
+  struct sh_arg jobs, *wcdeck;
 )
 
-#define BUGBUG 0
+// Prototype because $($($(blah))) nests, leading to run->parse->run loop
+int do_source(char *name, FILE *ff);
 
 // ordered for greedy matching, so >&; becomes >& ; not > &;
 // making these const means I need to typecast the const away later to
@@ -135,369 +303,300 @@
 static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
   ">&", ">|", ">", "&>>", "&>", 0};
 
-#define SH_NOCLOBBER 1   // set -C
+// The order of these has to match the string in set_main()
+#define OPT_B	0x100
+#define OPT_C	0x200
+#define OPT_x	0x400
 
-// like error_msg() but exit from shell scripts
-static void syntax_err(char *msg, ...)
+static void syntax_err(char *s)
 {
-  va_list va;
+  error_msg("syntax error: %s", s);
+  toys.exitval = 2;
+  if (!(TT.options&FLAG_i)) xexit();
+}
 
-// TODO rethink syntax errordom
-  va_start(va, msg);
-  verror_msg(msg, 0, va);
-  va_end(va);
+void debug_show_fds()
+{
+  int x = 0, fd = open("/proc/self/fd", O_RDONLY);
+  DIR *X = fdopendir(fd);
+  struct dirent *DE;
+  char *s, *ss = 0, buf[4096], *sss = buf;
 
-  if (*toys.optargs) xexit();
+  for (; (DE = readdir(X));) {
+    if (atoi(DE->d_name) == fd) continue;
+    s = xreadlink(ss = xmprintf("/proc/self/fd/%s", DE->d_name));
+    if (s && *s != '.') sss += sprintf(sss, ", %s=%s"+2*!x++, DE->d_name, s);
+    free(s); free(ss);
+  }
+  *sss = 0;
+  dprintf(2, "%d fd:%s\n", getpid(), buf);
+  closedir(X);
 }
 
 // append to array with null terminator and realloc as necessary
-void array_add(char ***list, unsigned count, char *data)
+static void arg_add(struct sh_arg *arg, char *data)
 {
-  if (!(count&31)) *list = xrealloc(*list, sizeof(char *)*(count+33));
-  (*list)[count] = data;
-  (*list)[count+1] = 0;
+  // expand with stride 32. Micro-optimization: don't realloc empty stack
+  if (!(arg->c&31) && (arg->c || !arg->v))
+    arg->v = xrealloc(arg->v, sizeof(char *)*(arg->c+33));
+  arg->v[arg->c++] = data;
+  arg->v[arg->c] = 0;
+}
+
+// add argument to an arg_list
+static void *push_arg(struct arg_list **list, void *arg)
+{
+  struct arg_list *al;
+
+  if (list) {
+    al = xmalloc(sizeof(struct arg_list));
+    al->next = *list;
+    al->arg = arg;
+    *list = al;
+  }
+
+  return arg;
+}
+
+static void arg_add_del(struct sh_arg *arg, char *data,struct arg_list **delete)
+{
+  arg_add(arg, push_arg(delete, data));
+}
+
+// Assign one variable from malloced key=val string, returns var struct
+// TODO implement remaining types
+#define VAR_NOFREE    (1<<10)
+#define VAR_WHITEOUT  (1<<9)
+#define VAR_DICT      (1<<8)
+#define VAR_ARRAY     (1<<7)
+#define VAR_INT       (1<<6)
+#define VAR_TOLOWER   (1<<5)
+#define VAR_TOUPPER   (1<<4)
+#define VAR_NAMEREF   (1<<3)
+#define VAR_GLOBAL    (1<<2)
+#define VAR_READONLY  (1<<1)
+#define VAR_MAGIC     (1<<0)
+
+// return length of valid variable name
+static char *varend(char *s)
+{
+  if (isdigit(*s)) return s;
+  while (*s>' ' && (*s=='_' || !ispunct(*s))) s++;
+
+  return s;
 }
 
 // Return index of variable within this list
-static unsigned findvar(char **list, char *name, int len)
+static struct sh_vars *findvar(char *name, struct sh_fcall **pff)
 {
-  unsigned i;
+  int len = varend(name)-name;
+  struct sh_fcall *ff = TT.ff;
 
-  for (i = 0; list[i]; i++)
-    if (!strncmp(list[i], name, len) && list[i][len] == '=') break;
+  // advance through locals to global context, ignoring whiteouts
+  if (len) do {
+    struct sh_vars *var = ff->vars+ff->varslen;
 
-  return i;
-}
-
-// Assign one variable
-// s: key=val
-// type: 0 = whatever it was before, local otherwise
-#define TAKE_MEM 0x80000000
-// declare -aAilnrux
-// ft
-static void setvar(char *s, unsigned type)
-{
-  unsigned uu;
-  int len = stridx(s, '=');
-
-  if (len == -1) return error_msg("no = in setvar %s\n", s);
-
-  if (type&TAKE_MEM) type ^= TAKE_MEM;
-  else s = xstrdup(s);
-
-  // local, export, readonly, integer...
-
-  // exported variable?
-  if (environ && environ[uu = findvar(environ, s, len)]) {
-    if (uu>=toys.envc) free(environ[uu]);
-    environ[uu] = s;
-  } else if (TT.locals[uu = findvar(TT.locals, s, len)]) {
-    if (uu<TT.loc_ro) return error_msg("%.*s: readonly variable", len, s);
-    free(TT.locals[uu]);
-    TT.locals[uu] = s;
-  } else array_add(&TT.locals, uu, s);
-}
-
-// get variable of length len starting at s.
-static char *getvarlen(char *s, int len)
-{
-  int i;
-
-  if (TT.locals && TT.locals[i = findvar(TT.locals, s, len)])
-    return TT.locals[i]+len+1;
-  if (environ && environ[i = findvar(environ, s, len)])
-    return environ[i]+len+1;
+    if (!var) continue;
+    if (pff) *pff = ff;
+    while (var-- != ff->vars)
+      if (pff || !(var->flags&VAR_WHITEOUT))
+        if (!strncmp(var->str, name, len) && var->str[len] == '=') return var;
+  } while ((ff = ff->next)!=TT.ff);
 
   return 0;
 }
 
+// Append variable to ff->vars, returning *struct. Does not check duplicates.
+static struct sh_vars *addvar(char *s, struct sh_fcall *ff)
+{
+  if (!(ff->varslen&31))
+    ff->vars = xrealloc(ff->vars, (ff->varslen+32)*sizeof(*ff->vars));
+  if (!s) return ff->vars;
+  ff->vars[ff->varslen].flags = 0;
+  ff->vars[ff->varslen].str = s;
+
+  return ff->vars+ff->varslen++;
+}
+
+// TODO function to resolve a string into a number for $((1+2)) etc
+long long do_math(char **s)
+{
+  long long ll;
+
+  while (isspace(**s)) ++*s;
+  ll = strtoll(*s, s, 0);
+  while (isspace(**s)) ++*s;
+
+  return ll;
+}
+
+// declare -aAilnrux
+// ft
+static struct sh_vars *setvar_found(char *s, struct sh_vars *var)
+{
+  long flags;
+
+  if ((flags = var->flags&~VAR_WHITEOUT)&VAR_READONLY) {
+    error_msg("%.*s: read only", (int)(strchr(s, '=')-s), s);
+    free(s);
+
+    return 0;
+  } else var->flags = flags;
+
+// TODO if (flags&(VAR_TOUPPER|VAR_TOLOWER)) 
+// unicode _is stupid enough for upper/lower case to be different utf8 byte
+// lengths. example: lowercase of U+0130 (C4 B0) is U+0069 (69)
+// TODO VAR_INT
+// TODO VAR_ARRAY VAR_DICT
+
+  if (flags&VAR_MAGIC) {
+    char *ss = strchr(s, '=')+1;
+
+    if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(&ss);
+    else if (*s == 'R') srandom(do_math(&ss));
+// TODO: trailing garbage after do_math()?
+  } else {
+    if (!(flags&VAR_NOFREE)) free(var->str);
+    else var->flags ^= VAR_NOFREE;
+    var->str = s;
+  }
+
+  return var;
+}
+
+// Update $IFS cache in function call stack after variable assignment
+static void cache_ifs(char *s, struct sh_fcall *ff)
+{
+  if (!strncmp(s, "IFS=", 4))
+    do ff->ifs = s+4; while ((ff = ff->next) != TT.ff->prev);
+}
+
+static struct sh_vars *setvar(char *s)
+{
+  struct sh_fcall *ff;
+  struct sh_vars *var;
+
+  if (s[varend(s)-s] != '=') {
+    error_msg("bad setvar %s\n", s);
+    free(s);
+
+    return 0;
+  }
+  if (!(var = findvar(s, &ff))) ff = TT.ff->prev;
+  cache_ifs(s, ff);
+  if (!var) return addvar(s, TT.ff->prev);
+
+  return setvar_found(s, var);
+}
+
+// returns whether variable found (whiteout doesn't count)
+static int unsetvar(char *name)
+{
+  struct sh_fcall *ff;
+  struct sh_vars *var = findvar(name, &ff);
+  int ii = var-ff->vars, len = varend(name)-name;
+
+  if (!var || (var->flags&VAR_WHITEOUT)) return 0;
+  if (var->flags&VAR_READONLY) error_msg("readonly %.*s", len, name);
+  else {
+    // turn local into whiteout
+    if (ff != TT.ff->prev) {
+      var->flags = VAR_WHITEOUT;
+      if (!(var->flags&VAR_NOFREE))
+        (var->str = xrealloc(var->str, len+2))[len+1] = 0;
+    // free from global context
+    } else {
+      if (!(var->flags&VAR_NOFREE)) free(var->str);
+      memmove(ff->vars+ii, ff->vars+ii+1, (ff->varslen--)-ii);
+    }
+    if (!strcmp(name, "IFS"))
+      do ff->ifs = " \t\n"; while ((ff = ff->next) != TT.ff->prev);
+  }
+
+  return 1;
+}
+
+static struct sh_vars *setvarval(char *name, char *val)
+{
+  return setvar(xmprintf("%s=%s", name, val));
+}
+
+// get value of variable starting at s.
 static char *getvar(char *s)
 {
-  return getvarlen(s, strlen(s));
+  struct sh_vars *var = findvar(s, 0);
+
+  if (!var) return 0;
+
+  if (var->flags & VAR_MAGIC) {
+    char c = *var->str;
+
+    if (c == 'S') sprintf(toybuf, "%lld", (millitime()-TT.SECONDS)/1000);
+    else if (c == 'R') sprintf(toybuf, "%ld", random()&((1<<16)-1));
+    else if (c == 'L') sprintf(toybuf, "%u", TT.ff->pl->lineno);
+    else if (c == 'G') sprintf(toybuf, "TODO: GROUPS");
+
+    return toybuf;
+  }
+
+  return varend(var->str)+1;
 }
 
-// returns offset of next unquoted (or double quoted if dquot) char.
-// handles \ '' "" `` $()
-int skip_quote(char *s, int dquot, int *depth)
-{
-  int i, q = dquot ? *depth : 0;
+// TODO: keep variable arrays sorted for binary search
 
-  // quotes were checked for balance and overflow by parse_word()
-  for (i = 0; s[i]; i++) {
-    char c = s[i], qq = q ? toybuf[q-1] : 0;
-
-    if (c == '\\') i++;
-    else if (dquot && q==1 && qq=='"' && c!='"') break;
-    else if (qq!='\'' && c=='$' && s[1]=='(') {
-      toybuf[q++] = ')';
-      i++;
-    } else if (q && qq==c) q--;
-    else if ((!q || qq==')') && (c=='"' || c=='\'' || c=='`')) toybuf[q++] = c;
-    else if (!q) break;
-  }
-
-  if (dquot) *depth = q;
-
-  return i;
-}
-
-// add argument to an arg_list
-void add_arg(struct arg_list **list, char *arg)
-{
-  struct arg_list *al;
-
-  if (!list) return;
-  al = xmalloc(sizeof(struct arg_list));
-  al->next = *list;
-  al->arg = arg;
-  *list = al;
-}
-
-#define NO_PATH  (1<<0)    // path expansion (wildcards)
-#define NO_SPLIT (1<<1)    // word splitting
-#define NO_BRACE (1<<2)    // {brace,expansion}
-#define NO_TILDE (1<<3)    // ~username/path
-#define NO_QUOTE (1<<4)    // quote removal
-#define FORCE_COPY (1<<31) // don't keep original, copy even if not modified
-#define FORCE_KEEP (1<<30) // this is a copy, free if not appended to delete
-// TODO: parameter/variable $(command) $((math)) split pathglob
-// TODO: ${name:?error} causes an error/abort here (syntax_err longjmp?)
-// TODO: $1 $@ $* need args marshalled down here: function+structure?
-// arg = append to this
-// new = string to expand
-// flags = type of expansions (not) to do
-// delete = append new allocations to this so they can be freed later
-// TODO: at_args: $1 $2 $3 $* $@
-static void expand_arg_nobrace(struct sh_arg *arg, char *old, unsigned flags,
-  struct arg_list **delete)
-{
-  char *new = old, *s, *ss, *sss;
-
-  if (flags&FORCE_KEEP) old = 0;
-
-// TODO ls -l /proc/$$/fd
-
-  // Tilde expansion
-  if (!(flags&NO_TILDE) && *new == '~') {
-    struct passwd *pw = 0;
-
-    // first expansion so don't need to free previous new
-    ss = 0;
-    for (s = new; *s && *s!=':' && *s!='/'; s++);
-    if (s-new==1) {
-      if (!(ss = getvar("HOME")) || !*ss) pw = bufgetpwuid(getuid());
-    } else {
-      // TODO bufgetpwnam
-      pw = getpwnam(sss = xstrndup(new+1, (s-new)-1));
-      free(sss);
-    }
-    if (pw && pw->pw_dir) ss = pw->pw_dir;
-    if (!ss || !*ss) ss = "/";
-    s = xmprintf("%s%s", ss, s);
-    if (old != new) free(new);
-    new = s;
-  }
-
-  // parameter/variable expansion
-
-// TODO this is wrong
-  if (*new == '$') {
-    char *s = getvar(new+1);
-
-    if (new != old) free(new);
-    if (!s) return;
-    new = xstrdup(s);
-  }
-
-/*
-  for (s = new; *(s += skip_quote(s, 1, &depth));) {
-    if (*s == '`') {
-
-// ${ $(( $( $[ $' ` " '
-
-  while (*s) {
-    if (quote != '*s == '$') {
-      // *@#?-$!_0 "Special Paremeters" ($0 not affected by shift)
-      // 0-9 positional parameters
-      if (s[1] == '$'
-    }
-  }
-
-  // replacement
-  while (*s) {
-    if (*s == '$') {
-      s++;
-    } else if (*strchr("*?[{", *s)) {
-      s++;
-    } else if (*s == '<' || *s == '>') {
-      s++;
-    } else s++;
-  }
-*/
-
-// TODO not else?
-  // quote removal
-  else if (!(flags&NO_QUOTE)) {
-    int to = 0, from = 0;
-
-    for (;;) {
-      char c = new[from++];
-
-      if (c == '"' || c=='\'') continue;
-      if (c == '\\' && new[from]) c = new[from++];
-      if (from != to && old == new) new = xstrdup(new);
-      if (!(new[to++] = c)) break;
-    }
-  }
-
-  // Record result.
-  if (old==new && (flags&FORCE_COPY)) new = xstrdup(new);
-  if (old!=new) add_arg(delete, new);
-  array_add(&arg->v, arg->c++, new);
-}
-
-// expand braces (ala {a,b,c}) and call expand_arg_nobrace() each permutation
-static void expand_arg(struct sh_arg *arg, char *old, unsigned flags,
-  struct arg_list **delete)
-{
-  struct brace {
-    struct brace *next, *prev, *stack;
-    int active, cnt, idx, commas[];
-  } *bb = 0, *blist = 0, *bstk, *bnext;
-  int i, j;
-  char *s, *ss;
-
-  // collect brace spans
-  if (!(flags&NO_BRACE)) for (i = 0; ; i++) {
-    i += skip_quote(old+i, 0, 0);
-    if (!bb && !old[i]) break;
-    if (bb && (!old[i] || old[i] == '}')) {
-      bb->active = bb->commas[bb->cnt+1] = i;
-      for (bnext = bb; bb && bb->active; bb = (bb==blist)?0:bb->prev);
-      if (!old[i] || !bnext->cnt) // discard commaless brace from start/middle
-        free(dlist_pop((blist == bnext) ? &blist : &bnext));
-    } else if (old[i] == '{') {
-      dlist_add_nomalloc((void *)&blist,
-        (void *)(bb = xzalloc(sizeof(struct brace)+34*4)));
-      bb->commas[0] = i;
-    } else if (!bb) continue;
-    else if  (bb && old[i] == ',') {
-      if (bb->cnt && !(bb->cnt&31)) {
-        dlist_lpop(&blist);
-        dlist_add_nomalloc((void *)&blist,
-          (void *)(bb = xrealloc(bb, sizeof(struct brace)+(bb->cnt+34)*4)));
-      }
-      bb->commas[++bb->cnt] = i;
-    }
-  }
-
-// TODO NOSPLIT with braces? (Collate with spaces?)
-  // If none, pass on verbatim
-  if (!blist) return expand_arg_nobrace(arg, old, flags, delete);
-
-  // enclose entire range in top level brace.
-  (bstk = xzalloc(sizeof(struct brace)+8))->commas[1] = strlen(old)+1;
-  bstk->commas[0] = -1;
-
-  // loop through each combination
-  for (;;) {
-
-    // Brace expansion can't be longer than original string. Keep start to {
-    s = ss = xmalloc(bstk->commas[1]);
-
-    // Append output from active braces (in "saved" list)
-    for (bb = blist; bb;) {
-
-      // keep prefix and push self onto stack
-      if (bstk == bb) bstk = bstk->stack;  // pop self
-      i = bstk->commas[bstk->idx]+1;
-      if (bstk->commas[bstk->cnt+1]>bb->commas[0])
-        s = stpncpy(s, old+i, bb->commas[0]-i);
-
-      // pop sibling
-      if (bstk->commas[bstk->cnt+1]<bb->commas[0]) bstk = bstk->stack;
- 
-      bb->stack = bstk; // push
-      bb->active = 1;
-      bstk = bnext = bb;
-
-      // skip inactive spans from earlier or later commas
-      while ((bnext = (bnext->next==blist) ? 0 : bnext->next)) {
-        i = bnext->commas[0];
-
-        // past end of this brace
-        if (i>bb->commas[bb->cnt+1]) break;
-
-        // in this brace but not this selection
-        if (i<bb->commas[bb->idx] || i>bb->commas[bb->idx+1]) {
-          bnext->active = 0;
-          bnext->stack = 0;
-
-        // in this selection
-        } else break;
-      }
-
-      // is next span past this range?
-      if (!bnext || bnext->commas[0]>bb->commas[bb->idx+1]) {
-
-        // output uninterrupted span
-        i = bb->commas[bstk->idx]+1;
-        s = stpncpy(s, old+i, bb->commas[bb->idx+1]-i);
-
-        // While not sibling, output tail and pop
-        while (!bnext || bnext->commas[0] > bstk->commas[bstk->cnt+1]) {
-          if (!(bb = bstk->stack)) break;
-          i = bstk->commas[bstk->cnt+1]+1; // start of span
-          j = bb->commas[bb->idx+1]; // enclosing comma span
-
-          while (bnext) {
-            if (bnext->commas[0]<j) {
-              j = bnext->commas[0];// sibling
-              break;
-            } else if (bb->commas[bb->cnt+1]>bnext->commas[0])
-              bnext = (bnext->next == blist) ? 0 : bnext->next;
-            else break;
-          }
-          s = stpncpy(s, old+i, j-i);
-
-          // if next is sibling but parent _not_ a sibling, don't pop
-          if (bnext && bnext->commas[0]<bstk->stack->commas[bstk->stack->cnt+1])
-            break;
-          bstk = bstk->stack;
-        }
-      }
-      bb = (bnext == blist) ? 0 : bnext;
-    }
-
-    // Save result
-    expand_arg_nobrace(arg, ss, flags|FORCE_KEEP, delete);
-
-    // increment
-    for (bb = blist->prev; bb; bb = (bb == blist) ? 0 : bb->prev) {
-      if (!bb->stack) continue;
-      else if (++bb->idx > bb->cnt) bb->idx = 0;
-      else break;
-    }
-
-    // if increment went off left edge, done expanding
-    if (!bb) return llist_traverse(blist, free);
-  }
-}
-
-// Expand exactly one arg, returning NULL if it split.
-static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del)
+// create array of variables visible in current function.
+static struct sh_vars **visible_vars(void)
 {
   struct sh_arg arg;
-  char *s = 0;
-  int i;
+  struct sh_fcall *ff;
+  struct sh_vars *vv;
+  unsigned ii, jj, len;
 
-  memset(&arg, 0, sizeof(arg));
-  expand_arg(&arg, new, flags, del);
-  if (arg.c == 1) s = *arg.v;
-  else if (!del) for (i = 0; i < arg.c; i++) free(arg.v[i]);
-  free(arg.v);
+  arg.c = 0;
+  arg.v = 0;
 
-  return s;
+  // Find non-duplicate entries: TODO, sort and binary search
+  for (ff = TT.ff; ; ff = ff->next) {
+    if (ff->vars) for (ii = ff->varslen; ii--;) {
+      vv = ff->vars+ii;
+      len = 1+(varend(vv->str)-vv->str);
+      for (jj = 0; ;jj++) {
+        if (jj == arg.c) arg_add(&arg, (void *)vv);
+        else if (strncmp(arg.v[jj], vv->str, len)) continue;
+
+        break;
+      }
+    }
+    if (ff->next == TT.ff) break;
+  }
+
+  return (void *)arg.v;
+}
+
+// malloc declare -x "escaped string"
+static char *declarep(struct sh_vars *var)
+{
+  char *types = "-rgnuliaA", *in = types, flags[16], *out = flags, *ss;
+  int len;
+
+  while (*++in) if (var->flags&(1<<(in-types))) *out++ = *in;
+  if (in == types) *out++ = *types;
+  *out = 0;
+  len = out-flags;
+
+  for (in = types = varend(var->str); *in; in++) len += !!strchr("$\"\\`", *in);
+  len += in-types;
+  ss = xmalloc(len+15);
+
+  out = ss + sprintf(ss, "declare -%s \"", out);
+  while (*types) {
+    if (strchr("$\"\\`", *types)) *out++ = '\\';
+    *out++ = *types++;
+  }
+  *out++ = '"';
+  *out = 0;
+ 
+  return ss; 
 }
 
 // return length of match found at this point (try is null terminated array)
@@ -524,42 +623,96 @@
   char *s = word;
 
   if (*s == '{') {
-    for (s++; isalnum(*s) || *s=='_'; s++);
-    if (*s == '}' && s != word+1) s++;
+    if (*(s = varend(s+1)) == '}' && s != word+1) s++;
     else s = word;
   } else while (isdigit(*s)) s++;
 
   return s-word;
 }
 
-// TODO |&
-
-// restore displaced filehandles, closing high filehandles they were copied to
-static void unredirect(int *urd)
+// parse next word from command line. Returns end, or 0 if need continuation
+// caller eats leading spaces. early = skip one quote block (or return start)
+// quote is depth of existing quote stack in toybuf (usually 0)
+static char *parse_word(char *start, int early, int quote)
 {
-  int *rr = urd+1, i;
+  int ii, qq, qc = 0;
+  char *end = start, *ss;
 
-  if (!urd) return;
+  // Handle redirections, <(), (( )) that only count at the start of word
+  ss = end + redir_prefix(end); // 123<<file- parses as 2 args: "123<<" "file-"
+  if (strstart(&ss, "<(") || strstart(&ss, ">(")) {
+    toybuf[quote++]=')';
+    end = ss;
+  } else if ((ii = anystart(ss, (void *)redirectors))) return ss+ii;
+  if (strstart(&end, "((")) toybuf[quote++] = 254;
 
-  for (i = 0; i<*urd; i++, rr += 2) {
-if (BUGBUG) dprintf(255, "urd %d %d\n", rr[0], rr[1]);
-    if (rr[1] != -1) {
-      // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
-      dup2(rr[0], rr[1]);
-      close(rr[0]);
+  // Loop to find end of this word
+  while (*end) {
+    // If we're stopping early and already handled a symbol...
+    if (early && end!=start && !quote) break;
+
+    // barf if we're near overloading quote stack (nesting ridiculously deep)
+    if (quote>4000) {
+      syntax_err("bad quote depth");
+      return (void *)1;
+    }
+
+    // Are we in a quote context?
+    if ((qq = quote ? toybuf[quote-1] : 0)) {
+      ii = *end++;
+      if ((qq==')' || qq>=254) && (ii=='(' || ii==')')) { // parentheses nest
+        if (ii=='(') qc++;
+        else if (qc) qc--;
+        else if (qq>=254) {
+          // (( can end with )) or retroactively become two (( if we hit one )
+          if (ii==')' && *end==')') quote--, end++;
+          else if (qq==254) return start+1;
+          else if (qq==255) toybuf[quote-1] = ')';
+        } else if (ii==')') quote--;
+      } else if (ii==qq) quote--;        // matching end quote
+      else if (qq!='\'') end--, ii = 0;  // single quote claims everything
+      if (ii) continue;                  // fall through for other quote types
+
+    // space and flow control chars only end word when not quoted in any way
+    } else {
+      if (isspace(*end)) break;
+      ss = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
+        "|&", "|", "&&", "&", "(", ")", 0});
+      if (ss!=end) return (end==start) ? ss : end;
+    }
+
+    // start new quote context? (' not special within ")
+    if (strchr("'\"`"+(qq=='"'), ii = *end++)) toybuf[quote++] = ii;
+
+    // \? $() ${} $[] ?() *() +() @() !()
+    else {
+      if (ii=='\\') { // TODO why end[1] here? sh -c $'abc\\\ndef' Add test.
+        if (!*end || (*end=='\n' && !end[1])) return early ? end : 0;
+      } else if (ii=='$' && -1!=(qq = stridx("({[", *end))) {
+        if (strstart(&end, "((")) {
+          toybuf[quote++] = 255;
+          end++;
+        } else toybuf[quote++] = ")}]"[qq];
+      } else if (*end=='(' && strchr("?*+@!", ii)) toybuf[quote++] = ')';
+      else {
+        end--;
+        if (early && !quote) return end;
+      }
+      end++;
     }
   }
-  free(urd);
+
+  return (quote && !early) ? 0 : end;
 }
 
 // Return next available high (>=10) file descriptor
-int next_hfd()
+static int next_hfd()
 {
   int hfd;
 
   for (; TT.hfd<=99999; TT.hfd++) if (-1 == fcntl(TT.hfd, F_GETFL)) break;
   hfd = TT.hfd;
-  if (TT.hfd > 99999) {
+  if (TT.hfd>99999) {
     hfd = -1;
     if (!errno) errno = EMFILE;
   }
@@ -569,23 +722,30 @@
 
 // Perform a redirect, saving displaced filehandle to a high (>10) fd
 // rd is an int array: [0] = count, followed by from/to pairs to restore later.
-// If from == -1 just save to, else dup from->to after saving to.
-int save_redirect(int **rd, int from, int to)
+// If from >= 0 dup from->to after saving to. If from == -1 just save to.
+// if from == -2 schedule "to" to be closed by unredirect.
+static int save_redirect(int **rd, int from, int to)
 {
   int cnt, hfd, *rr;
 
+//dprintf(2, "%d redir %d to %d\n", getpid(), from, to);
+  if (from == to) return 0;
   // save displaced to, copying to high (>=10) file descriptor to undo later
   // except if we're saving to environment variable instead (don't undo that)
-  if ((hfd = next_hfd())==-1) return 1;
-  if (hfd != dup2(to, hfd)) hfd = -1;
-  else fcntl(hfd, F_SETFD, FD_CLOEXEC);
+  if (from>-2) {
+    if ((hfd = next_hfd())==-1) return 1;
+    if (hfd != dup2(to, hfd)) hfd = -1;
+    else fcntl(hfd, F_SETFD, FD_CLOEXEC);
 
-if (BUGBUG) dprintf(255, "%d redir from=%d to=%d hfd=%d\n", getpid(), from, to, hfd);
-  // dup "to"
-  if (from != -1 && to != dup2(from, to)) {
-    if (hfd != -1) close(hfd);
+    // dup "to"
+    if (from >= 0 && to != dup2(from, to)) {
+      if (hfd >= 0) close(hfd);
 
-    return 1;
+      return 1;
+    }
+  } else {
+    hfd = to;
+    to = -1;
   }
 
   // Append undo information to redirect list so we can restore saved hfd later.
@@ -597,149 +757,1341 @@
   return 0;
 }
 
-// Pipeline segments
-struct sh_pipeline {
-  struct sh_pipeline *next, *prev;
-  int count, here, type;
-  struct sh_arg arg[1];
-};
-
-// scratch space (state held between calls). Don't want to make it global yet
-// because this could be reentrant.
-struct sh_function {
-  char *name;
-  struct sh_pipeline *pipeline;
-  struct double_list *expect;
-// TODO: lifetime rules for arg? remember "shift" command.
-  struct sh_arg *arg; // arguments to function call
-  char *end;
-};
-
 // TODO: waitpid(WNOHANG) to clean up zombies and catch background& ending
-
-static void subshell_callback(void)
+static void subshell_callback(char **argv)
 {
-  TT.subshell_env = xmprintf("@%d,%d=", getpid(), getppid());
-  xsetenv(TT.subshell_env, 0);
-  TT.subshell_env[strlen(TT.subshell_env)-1] = 0;
+  // This depends on environ having been replaced by caller
+  environ[1] = xmprintf("@%d,%d", getpid(), getppid());
+  environ[2] = xmprintf("$=%d", TT.pid);
+// TODO: test $$ in (nommu)
 }
 
-// TODO avoid prototype
-static int sh_run(char *new);
+// TODO what happens when you background a function?
+// turn a parsed pipeline back into a string.
+static char *pl2str(struct sh_pipeline *pl, int one)
+{
+  struct sh_pipeline *end = 0, *pp;
+  int len = len, i;
+  char *s, *ss;
+
+  // Find end of block (or one argument)
+  if (one) end = pl->next;
+  else for (end = pl, len = 0; end; end = end->next)
+    if (end->type == 1) len++;
+    else if (end->type == 3 && --len<0) break;
+
+  // measure, then allocate
+  for (ss = 0;; ss = xmalloc(len+1)) {
+    for (pp = pl; pp != end; pp = pp->next) {
+      if (pp->type == 'F') continue; // TODO fix this
+      for (i = len = 0; i<pp->arg->c; i++)
+        len += snprintf(ss+len, ss ? INT_MAX : 0, "%s ", pp->arg->v[i]);
+      if (!(s = pp->arg->v[pp->arg->c])) s = ";"+(pp->next==end);
+      len += snprintf(ss+len, ss ? INT_MAX : 0, s);
+    }
+
+    if (ss) return ss;
+  }
+
+// TODO test output with case and function
+// TODO add HERE documents back in
+// TODO handle functions
+}
+
+// restore displaced filehandles, closing high filehandles they were copied to
+static void unredirect(int *urd)
+{
+  int *rr = urd+1, i;
+
+  if (!urd) return;
+
+  for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) {
+    // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
+    dup2(rr[0], rr[1]);
+    close(rr[0]);
+  }
+  free(urd);
+}
+
+static struct sh_blockstack *clear_block(struct sh_blockstack *blk)
+{
+  memset(blk, 0, sizeof(*blk));
+  blk->start = TT.ff->pl;
+  blk->run = 1;
+  blk->pout = -1;
+
+  return blk;
+}
+
+// when ending a block, free, cleanup redirects and pop stack.
+static struct sh_pipeline *pop_block(void)
+{
+  struct sh_pipeline *pl = 0;
+  struct sh_blockstack *blk = TT.ff->blk;
+
+  // when ending a block, free, cleanup redirects and pop stack.
+  if (blk->pout != -1) close(blk->pout);
+  unredirect(blk->urd);
+  llist_traverse(blk->fdelete, llist_free_arg);
+  free(blk->farg.v);
+  if (TT.ff->blk->next) {
+    pl = blk->start->end;
+    free(llist_pop(&TT.ff->blk));
+  } else clear_block(blk);
+
+  return pl;
+}
+
+// Push a new empty block to the stack
+static void add_block(void)
+{
+  struct sh_blockstack *blk = clear_block(xmalloc(sizeof(*blk)));
+
+  blk->next = TT.ff->blk;
+  TT.ff->blk = blk;
+}
+
+// Add entry to runtime function call stack
+static void call_function(void)
+{
+  // dlist in reverse order: TT.ff = current function, TT.ff->prev = globals
+  dlist_add_nomalloc((void *)&TT.ff, xzalloc(sizeof(struct sh_fcall)));
+  TT.ff = TT.ff->prev;
+  add_block();
+
+// TODO caller needs to set pl, vars, func
+  // default $* is to copy previous
+  TT.ff->arg.v = TT.ff->next->arg.v;
+  TT.ff->arg.c = TT.ff->next->arg.c;
+  TT.ff->ifs = TT.ff->next->ifs;
+}
+
+// functions contain pipelines contain functions: prototype because loop
+static void free_pipeline(void *pipeline);
+
+static void free_function(struct sh_function *funky)
+{
+  if (--funky->refcount) return;
+
+  free(funky->name);
+  llist_traverse(funky->pipeline, free_pipeline);
+  free(funky);
+}
+
+// TODO: old function-vs-source definition is "has variables", but no ff->func?
+// returns 0 if source popped, nonzero if function popped
+static int end_function(int funconly)
+{
+  struct sh_fcall *ff = TT.ff;
+  int func = ff->next!=ff && ff->vars;
+
+  if (!func && funconly) return 0;
+  llist_traverse(ff->delete, llist_free_arg);
+  ff->delete = 0;
+  while (TT.ff->blk->next) pop_block();
+  pop_block();
+
+  // for a function, free variables and pop context
+  if (!func) return 0;
+  while (ff->varslen)
+    if (!(ff->vars[--ff->varslen].flags&VAR_NOFREE))
+      free(ff->vars[ff->varslen].str);
+  free(ff->vars);
+  free(TT.ff->blk);
+  if (ff->func) free_function(ff->func);
+  free(dlist_pop(&TT.ff));
+
+  return 1;
+}
+
+// TODO check every caller of run_subshell for error, or syntax_error() here
+// from pipe() failure
+
+// TODO need CLOFORK? CLOEXEC doesn't help if we don't exec...
 
 // Pass environment and command string to child shell, return PID of child
 static int run_subshell(char *str, int len)
 {
   pid_t pid;
-
+//dprintf(2, "%d run_subshell %.*s\n", getpid(), len, str); debug_show_fds();
   // The with-mmu path is significantly faster.
   if (CFG_TOYBOX_FORK) {
-    char *s;
-
     if ((pid = fork())<0) perror_msg("fork");
     else if (!pid) {
-      s = xstrndup(str, len);
-      sh_run(s);
-      free(s);
-
-      _exit(toys.exitval);
+      call_function();
+      if (str) {
+        do_source(0, fmemopen(str, len, "r"));
+        _exit(toys.exitval);
+      }
     }
 
   // On nommu vfork, exec /proc/self/exe, and pipe state data to ourselves.
   } else {
-    int pipes[2], i;
+    int pipes[2];
+    unsigned len, i;
+    char **oldenv = environ, *s, *ss = str ? : pl2str(TT.ff->pl->next, 0);
+    struct sh_vars **vv;
 
     // open pipe to child
     if (pipe(pipes) || 254 != dup2(pipes[0], 254)) return 1;
     close(pipes[0]);
     fcntl(pipes[1], F_SETFD, FD_CLOEXEC);
 
-    // vfork child
+    // vfork child with clean environment
+    environ = xzalloc(4*sizeof(char *));
+    *environ = getvar("PATH") ? : "PATH=";
     pid = xpopen_setup(0, 0, subshell_callback);
+// TODO what if pid -1? Handle process exhaustion.
+    // free entries added to end of environment by callback (shared heap)
+    free(environ[1]);
+    free(environ[2]);
+    free(environ);
+    environ = oldenv;
 
-    // marshall data to child
+    // marshall context to child
     close(254);
-    if (TT.locals)
-      for (i = 0; TT.locals[i]; i++) dprintf(pipes[1], "%s\n", TT.locals[i]);
-    dprintf(pipes[1], "%.*s\n", len, str);
+    for (i = 0, vv = visible_vars(); vv[i]; i++) {
+      if (vv[i]->flags&VAR_WHITEOUT) continue;
+      dprintf(pipes[1], "%s\n", s = declarep(vv[i]));
+      free(s);
+    }
+    free(vv);
+
+    // send command
+    dprintf(pipes[1], "%.*s\n", len, ss);
+    if (!str) free(ss);
     close(pipes[1]);
   }
 
   return pid;
 }
 
-// turn a parsed pipeline back into a string.
-static char *pl2str(struct sh_pipeline *pl)
+// Call subshell with either stdin/stdout redirected, return other end of pipe
+static int pipe_subshell(char *s, int len, int out)
 {
-  struct sh_pipeline *end = 0;
-  int level = 0, len = 0, i, j;
-  char *s, *ss, *sss;
+  int pipes[2], *uu = 0, in = !out;
 
-  // measure, then allocate
-  for (j = 0; ; j++) for (end = pl; end; end = end->next) {
-    if (end->type == 1) level++;
-    else if (end->type == 3 && --level<0) break;
+  // Grab subshell data
+  if (pipe(pipes)) {
+    perror_msg("%.*s", len, s);
 
-    for (i = 0; i<pl->arg->c; i++)
-      if (j) ss += sprintf(ss, "%s ", pl->arg->v[i]);
-      else len += strlen(pl->arg->v[i])+1;
+    return -1;
+  }
 
-    sss = pl->arg->v[pl->arg->c];
-    if (!sss) sss = ";";
-    if (j) ss = stpcpy(ss, sss);
-    else len += strlen(sss);
+  // Perform input or output redirect and launch process (ignoring errors)
+  save_redirect(&uu, pipes[in], in);
+  close(pipes[in]);
+  fcntl(pipes[!in], F_SETFD, FD_CLOEXEC);
+  run_subshell(s, len);
+  fcntl(pipes[!in], F_SETFD, 0);
+  unredirect(uu);
 
-// TODO add HERE documents back in
-    if (j) return s;
-    s = ss = xmalloc(len+1);
+  return pipes[out];
+}
+
+// utf8 strchr: return wide char matched at wc from chrs, or 0 if not matched
+// if len, save length of next wc (whether or not it's in list)
+static int utf8chr(char *wc, char *chrs, int *len)
+{
+  wchar_t wc1, wc2;
+  int ll;
+
+  if (len) *len = 1;
+  if (!*wc) return 0;
+  if (0<(ll = utf8towc(&wc1, wc, 99))) {
+    if (len) *len = ll;
+    while (*chrs) {
+      if(1>(ll = utf8towc(&wc2, chrs, 99))) chrs++;
+      else {
+        if (wc1 == wc2) return wc1;
+        chrs += ll;
+      }
+    }
+  }
+
+  return 0;
+}
+
+// grab variable or special param (ala $$) up to len bytes. Return value.
+// set *used to length consumed. Does not handle $* and $@
+char *getvar_special(char *str, int len, int *used, struct arg_list **delete)
+{
+  char *s = 0, *ss, cc = *str;
+  unsigned uu;
+
+  *used = 1;
+  if (cc == '-') {
+    s = ss = xmalloc(8);
+    if (TT.options&FLAG_i) *ss++ = 'i';
+    if (TT.options&OPT_B) *ss++ = 'B';
+    if (TT.options&FLAG_s) *ss++ = 's';
+    if (TT.options&FLAG_c) *ss++ = 'c';
+    *ss = 0;
+  } else if (cc == '?') s = xmprintf("%d", toys.exitval);
+  else if (cc == '$') s = xmprintf("%d", TT.pid);
+  else if (cc == '#') s = xmprintf("%d", TT.ff->arg.c ? TT.ff->arg.c-1 : 0);
+  else if (cc == '!') s = xmprintf("%d"+2*!TT.bangpid, TT.bangpid);
+  else {
+    delete = 0;
+    for (*used = uu = 0; *used<len && isdigit(str[*used]); ++*used)
+      uu = (10*uu)+str[*used]-'0';
+    if (*used) {
+      if (uu) uu += TT.ff->shift;
+      if (uu<TT.ff->arg.c) s = TT.ff->arg.v[uu];
+    } else if ((*used = varend(str)-str)) return getvar(str);
+  }
+  if (s) push_arg(delete, s);
+
+  return s;
+}
+
+// Return length of utf8 char @s fitting in len, writing value into *cc
+int getutf8(char *s, int len, int *cc)
+{
+  wchar_t wc;
+
+  if (len<0) wc = len = 0;
+  else if (1>(len = utf8towc(&wc, s, len))) wc = *s, len = 1;
+  if (cc) *cc = wc;
+
+  return len;
+}
+
+#define WILD_SHORT 1 // else longest match
+#define WILD_CASE  2 // case insensitive
+#define WILD_ANY   4 // advance through pattern instead of str
+// Returns length of str matched by pattern, or -1 if not all pattern consumed
+static int wildcard_matchlen(char *str, int len, char *pattern, int plen,
+  struct sh_arg *deck, int flags)
+{
+  struct sh_arg ant = {0};    // stack: of str offsets
+  long ss, pp, dd, best = -1;
+  int i, j, c, not;
+
+  // Loop through wildcards in pattern.
+  for (ss = pp = dd = 0; ;) {
+    if ((flags&WILD_ANY) && best!=-1) break;
+
+    // did we consume pattern?
+    if (pp==plen) {
+      if (ss>best) best = ss;
+      if (ss==len || (flags&WILD_SHORT)) break;
+    // attempt literal match?
+    } else if (dd>=deck->c || pp!=(long)deck->v[dd]) {
+      if (ss<len) {
+        if (flags&WILD_CASE) {
+          c = towupper(getutf8(str+ss, len-ss, &i));
+          ss += i;
+          i = towupper(getutf8(pattern+pp, pp-plen, &j));
+          pp += j;
+        } else c = str[ss++], i = pattern[pp++];
+        if (c==i) continue;
+      }
+
+    // Wildcard chars: |+@!*?()[]
+    } else {
+      c = pattern[pp++];
+      dd++;
+      if (c=='?' || ((flags&WILD_ANY) && c=='*')) {
+        ss += (i = getutf8(str+ss, len-ss, 0));
+        if (i) continue;
+      } else if (c=='*') {
+
+        // start with zero length match, don't record consecutive **
+        if (dd==1 || pp-2!=(long)deck->v[dd-1] || pattern[pp-2]!='*') {
+          arg_add(&ant, (void *)ss);
+          arg_add(&ant, 0);
+        }
+
+        continue;
+      } else if (c == '[') {
+        pp += (not = !!strchr("!^", pattern[pp]));
+        ss += getutf8(str+ss, len-ss, &c);
+        for (i = 0; pp<(long)deck->v[dd]; i = 0) {
+          pp += getutf8(pattern+pp, plen-pp, &i);
+          if (pattern[pp]=='-') {
+            ++pp;
+            pp += getutf8(pattern+pp, plen-pp, &j);
+            if (not^(i<=c && j>=c)) break;
+          } else if (not^(i==c)) break;
+        }
+        if (i) {
+          pp = 1+(long)deck->v[dd++];
+
+          continue;
+        }
+
+      // ( preceded by +@!*?
+
+      } else { // TODO ( ) |
+        dd++;
+        continue;
+      }
+    }
+
+    // match failure
+    if (flags&WILD_ANY) {
+      ss = 0;
+      if (plen==pp) break;
+      continue;
+    }
+
+    // pop retry stack or return failure (TODO: seek to next | in paren)
+    while (ant.c) {
+      if ((c = pattern[(long)deck->v[--dd]])=='*') {
+        if (len<(ss = (long)ant.v[ant.c-2]+(long)++ant.v[ant.c-1])) ant.c -= 2;
+        else {
+          pp = (long)deck->v[dd++]+1;
+          break;
+        }
+      } else if (c == '(') dprintf(2, "TODO: (");
+    }
+
+    if (!ant.c) break;
+  }
+  free (ant.v);
+
+  return best;
+}
+
+static int wildcard_match(char *s, char *p, struct sh_arg *deck, int flags)
+{
+  return wildcard_matchlen(s, strlen(s), p, strlen(p), deck, flags);
+}
+
+
+// TODO: test that * matches ""
+
+// skip to next slash in wildcard path, passing count active ranges.
+// start at pattern[off] and deck[*idx], return pattern pos and update *idx
+char *wildcard_path(char *pattern, int off, struct sh_arg *deck, int *idx,
+  int count)
+{
+  char *p, *old;
+  int i = 0, j = 0;
+
+  // Skip [] and nested () ranges within deck until / or NUL
+  for (p = old = pattern+off;; p++) {
+    if (!*p) return p;
+    while (*p=='/') {
+      old = p++;
+      if (j && !count) return old;
+      j = 0;
+    }
+
+    // Got wildcard? Return if start of name if out of count, else skip [] ()
+    if (*idx<deck->c && p-pattern == (long)deck->v[*idx]) {
+      if (!j++ && !count--) return old;
+      ++*idx;
+      if (*p=='[') p = pattern+(long)deck->v[(*idx)++];
+      else if (*p=='(') while (*++p) if (p-pattern == (long)deck->v[*idx]) {
+        ++*idx;
+        if (*p == ')') {
+          if (!i) break;
+          i--;
+        } else if (*p == '(') i++;
+      }
+    }
   }
 }
 
+// TODO ** means this directory as well as ones below it, shopt -s globstar
+
+// Filesystem traversal callback
+// pass on: filename, portion of deck, portion of pattern,
+// input: pattern+offset, deck+offset. Need to update offsets.
+int do_wildcard_files(struct dirtree *node)
+{
+  struct dirtree *nn;
+  char *pattern, *patend;
+  int lvl, ll = 0, ii = 0, rc;
+  struct sh_arg ant;
+
+  // Top level entry has no pattern in it
+  if (!node->parent) return DIRTREE_RECURSE;
+
+  // Find active pattern range
+  for (nn = node->parent; nn; nn = nn->parent) if (nn->parent) ii++;
+  pattern = wildcard_path(TT.wcpat, 0, TT.wcdeck, &ll, ii);
+  while (*pattern=='/') pattern++;
+  lvl = ll;
+  patend = wildcard_path(TT.wcpat, pattern-TT.wcpat, TT.wcdeck, &ll, 1);
+
+  // Don't include . entries unless explicitly asked for them 
+  if (*node->name=='.' && *pattern!='.') return 0;
+
+  // Don't descend into non-directory (was called with DIRTREE_SYMFOLLOW)
+  if (*patend && !S_ISDIR(node->st.st_mode) && *node->name) return 0;
+
+  // match this filename from pattern to p in deck from lvl to ll
+  ant.c = ll-lvl;
+  ant.v = TT.wcdeck->v+lvl;
+  for (ii = 0; ii<ant.c; ii++) TT.wcdeck->v[lvl+ii] -= pattern-TT.wcpat;
+  rc = wildcard_matchlen(node->name, strlen(node->name), pattern,
+    patend-pattern, &ant, 0);
+  for (ii = 0; ii<ant.c; ii++) TT.wcdeck->v[lvl+ii] += pattern-TT.wcpat;
+
+  // Return failure or save exact match.
+  if (rc<0 || node->name[rc]) return 0;
+  if (!*patend) return DIRTREE_SAVE;
+
+  // Are there more wildcards to test children against?
+  if (TT.wcdeck->c!=ll) return DIRTREE_RECURSE;
+
+  // No more wildcards: check for child and return failure if it isn't there.
+  pattern = xmprintf("%s%s", node->name, patend);
+  rc = faccessat(dirtree_parentfd(node), pattern, F_OK, AT_SYMLINK_NOFOLLOW);
+  free(pattern);
+  if (rc) return 0;
+
+  // Save child and self. (Child could be trailing / but only one saved.)
+  while (*patend=='/' && patend[1]) patend++;
+  node->child = xzalloc(sizeof(struct dirtree)+1+strlen(patend));
+  node->child->parent = node;
+  strcpy(node->child->name, patend);
+
+  return DIRTREE_SAVE;
+}
+
+// Record active wildcard chars in output string
+// *new start of string, oo offset into string, deck is found wildcards,
+static void collect_wildcards(char *new, long oo, struct sh_arg *deck)
+{
+  long bracket, *vv;
+  char cc = new[oo];
+
+  // Record unescaped/unquoted wildcard metadata for later processing
+
+  if (!deck->c) arg_add(deck, 0);
+  vv = (long *)deck->v;
+
+  // vv[0] used for paren level (bottom 16 bits) + bracket start offset<<16
+
+  // at end loop backwards through live wildcards to remove pending unmatched (
+  if (!cc) {
+    long ii = 0, jj = 65535&*vv, kk;
+
+    for (kk = deck->c; jj;) {
+      if (')' == (cc = new[vv[--kk]])) ii++;
+      else if ('(' == cc) {
+        if (ii) ii--;
+        else {
+          memmove(vv+kk, vv+kk+1, sizeof(long)*(deck->c-- -kk));
+          jj--;
+        }
+      }
+    }
+    if (deck->c) memmove(vv, vv+1, sizeof(long)*deck->c--);
+
+    return;
+  }
+
+  // Start +( range, or remove first char that isn't wildcard without (
+  if (deck->c>1 && vv[deck->c-1] == oo-1 && strchr("+@!*?", new[oo-1])) {
+    if (cc == '(') {
+      vv[deck->c-1] = oo;
+      return;
+    } else if (!strchr("*?", new[oo-1])) deck->c--;
+  }
+
+  // fall through to add wildcard, popping parentheses stack as necessary
+  if (strchr("|+@!*?", cc));
+  else if (cc == ')' && (65535&*vv)) --*vv;
+
+  // complete [range], discard wildcards within, add [, fall through to add ]
+  else if (cc == ']' && (bracket = *vv>>16)) {
+
+    // don't end range yet for [] or [^]
+    if (bracket+1 == oo || (bracket+2 == oo && strchr("!^", new[oo-1]))) return;
+    while (deck->c>1 && vv[deck->c-1]>=bracket) deck->c--;
+    *vv &= 65535;
+    arg_add(deck, (void *)bracket);
+
+  // Not a wildcard
+  } else {
+    // [ is speculative, don't add to deck yet, just record we saw it
+    if (cc == '[' && !(*vv>>16)) *vv = (oo<<16)+(65535&*vv);
+    return;
+  }
+
+  // add active wildcard location
+  arg_add(deck, (void *)oo);
+}
+
+// wildcard expand data against filesystem, and add results to arg list
+// Note: this wildcard deck has extra argument at start (leftover from parsing)
+static void wildcard_add_files(struct sh_arg *arg, char *pattern,
+  struct sh_arg *deck, struct arg_list **delete)
+{
+  struct dirtree *dt;
+  char *pp;
+  int ll = 0;
+
+  // fast path: when no wildcards, add pattern verbatim
+  collect_wildcards("", 0, deck);
+  if (!deck->c) return arg_add(arg, pattern);
+
+  // Traverse starting with leading patternless path.
+  pp = wildcard_path(TT.wcpat = pattern, 0, TT.wcdeck = deck, &ll, 0);
+  pp = (pp==pattern) ? 0 : xstrndup(pattern, pp-pattern);
+  dt = dirtree_flagread(pp, DIRTREE_STATLESS|DIRTREE_SYMFOLLOW,
+    do_wildcard_files);
+  free(pp);
+  deck->c = 0;
+
+  // If no match save pattern, else free tree saving each path found.
+  if (!dt) return arg_add(arg, pattern);
+  while (dt) {
+    while (dt->child) dt = dt->child;
+    arg_add(arg, dirtree_path(dt, 0));
+    do {
+      pp = (void *)dt;
+      if ((dt = dt->parent)) dt->child = dt->child->next;
+      free(pp);
+    } while (dt && !dt->child);
+  }
+// TODO: test .*/../
+}
+
+// Copy string until } including escaped }
+// if deck collect wildcards, and store terminator at deck->v[deck->c]
+char *slashcopy(char *s, char *c, struct sh_arg *deck)
+{
+  char *ss;
+  long ii, jj;
+
+  for (ii = 0; !strchr(c, s[ii]); ii++) if (s[ii] == '\\') ii++;
+  ss = xmalloc(ii+1);
+  for (ii = jj = 0; !strchr(c, s[jj]); ii++)
+    if ('\\'==(ss[ii] = s[jj++])) ss[ii] = s[jj++];
+    else if (deck) collect_wildcards(ss, ii, deck);
+  ss[ii] = 0;
+  if (deck) {
+    arg_add(deck, 0);
+    deck->v[--deck->c] = (void *)jj;
+    collect_wildcards("", 0, deck);
+  }
+
+  return ss;
+}
+
+#define NO_QUOTE (1<<0)    // quote removal
+#define NO_PATH  (1<<1)    // path expansion (wildcards)
+#define NO_SPLIT (1<<2)    // word splitting
+#define NO_BRACE (1<<3)    // {brace,expansion}
+#define NO_TILDE (1<<4)    // ~username/path
+#define NO_NULL  (1<<5)    // Expand to "" instead of NULL
+#define SEMI_IFS (1<<6)    // Use ' ' instead of IFS to combine $*
+// expand str appending to arg using above flag defines, add mallocs to delete
+// if ant not null, save wildcard deck there instead of expanding vs filesystem
+// returns 0 for success, 1 for error
+static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags,
+  struct arg_list **delete, struct sh_arg *ant)
+{
+  char cc, qq = flags&NO_QUOTE, sep[6], *new = str, *s, *ss = ss, *ifs, *slice;
+  int ii = 0, oo = 0, xx, yy, dd, jj, kk, ll, mm;
+  struct sh_arg deck = {0};
+
+  // Tilde expansion
+  if (!(flags&NO_TILDE) && *str == '~') {
+    struct passwd *pw = 0;
+
+    ss = 0;
+    while (str[ii] && str[ii]!=':' && str[ii]!='/') ii++;
+    if (ii==1) {
+      if (!(ss = getvar("HOME")) || !*ss) pw = bufgetpwuid(getuid());
+    } else {
+      // TODO bufgetpwnam
+      pw = getpwnam(s = xstrndup(str+1, ii-1));
+      free(s);
+    }
+    if (pw) {
+      ss = pw->pw_dir;
+      if (!ss || !*ss) ss = "/";
+    }
+    if (ss) {
+      oo = strlen(ss);
+      s = xmprintf("%s%s", ss, str+ii);
+      if (str != new) free(new);
+      new = s;
+    }
+  }
+
+  // parameter/variable expansion and dequoting
+  if (!ant) ant = &deck;
+  for (; (cc = str[ii++]); str!=new && (new[oo] = 0)) {
+    struct sh_arg aa = {0};
+    int nosplit = 0;
+
+    // skip literal chars
+    if (!strchr("'\"\\$`"+2*(flags&NO_QUOTE), cc)) {
+      if (str != new) new[oo] = cc;
+      if (!(flags&NO_PATH) && !(qq&1)) collect_wildcards(new, oo, ant);
+      oo++;
+      continue;
+    }
+
+    // allocate snapshot if we just started modifying
+    if (str == new) {
+      new = xstrdup(new);
+      new[oo] = 0;
+    }
+    ifs = slice = 0;
+
+    // handle escapes and quoting
+    if (cc == '\\') {
+      if (!(qq&1) || (str[ii] && strchr("\"\\$`", str[ii])))
+        new[oo++] = str[ii] ? str[ii++] : cc;
+    } else if (cc == '"') qq++;
+    else if (cc == '\'') {
+      if (qq&1) new[oo++] = cc;
+      else {
+        qq += 2;
+        while ((cc = str[ii++]) != '\'') new[oo++] = cc;
+      }
+
+    // both types of subshell work the same, so do $( here not in '$' below
+// TODO $((echo hello) | cat) ala $(( becomes $( ( retroactively
+    } else if (cc == '`' || (cc == '$' && str[ii] && strchr("([", str[ii]))) {
+      off_t pp = 0;
+
+      s = str+ii-1;
+      kk = parse_word(s, 1, 0)-s;
+      if (str[ii] == '[' || *toybuf == 255) {
+        s += 2+(str[ii]!='[');
+        kk -= 3+2*(str[ii]!='[');
+dprintf(2, "TODO: do math for %.*s\n", kk, s);
+      } else {
+        // Run subshell and trim trailing newlines
+        s += (jj = 1+(cc == '$'));
+        ii += --kk;
+        kk -= jj;
+
+        // Special case echo $(<input)
+        for (ss = s; isspace(*ss); ss++);
+        if (*ss != '<') ss = 0;
+        else {
+          while (isspace(*++ss));
+          if (!(ll = parse_word(ss, 0, 0)-ss)) ss = 0;
+          else {
+            jj = ll+(ss-s);
+            while (isspace(s[jj])) jj++;
+            if (jj != kk) ss = 0;
+            else {
+              jj = xcreate_stdio(ss = xstrndup(ss, ll), O_RDONLY|WARN_ONLY, 0);
+              free(ss);
+            }
+          }
+        }
+
+// TODO what does \ in `` mean? What is echo `printf %s \$x` supposed to do?
+        // This has to be async so pipe buffer doesn't fill up
+        if (!ss) jj = pipe_subshell(s, kk, 0);
+        if ((ifs = readfd(jj, 0, &pp)))
+          for (kk = strlen(ifs); kk && ifs[kk-1]=='\n'; ifs[--kk] = 0);
+        close(jj);
+      }
+
+    // $VARIABLE expansions
+
+    } else if (cc == '$') {
+      cc = *(ss = str+ii++);
+      if (cc=='\'') {
+        for (s = str+ii; *s != '\''; oo += wcrtomb(new+oo, unescape2(&s, 0),0));
+        ii = s-str+1;
+
+        continue;
+      } else if (cc=='"' && !(qq&1)) {
+        qq++;
+
+        continue;
+      } else if (cc == '{') {
+
+        // Skip escapes to find }, parse_word() guarantees ${} terminates
+        for (cc = *++ss; str[ii] != '}'; ii++) if (str[ii]=='\\') ii++;
+        ii++;
+
+        if (cc == '}') ifs = (void *)1;
+        else if (strchr("#!", cc)) ss++;
+        jj = varend(ss)-ss;
+        if (!jj) while (isdigit(ss[jj])) jj++;
+        if (!jj && strchr("#$!_*", *ss)) jj++;
+        // parameter or operator? Maybe not a prefix: ${#-} vs ${#-x}
+        if (!jj && strchr("-?@", *ss)) if (ss[++jj]!='}' && ss[-1]!='{') ss--;
+        slice = ss+jj;        // start of :operation
+
+        if (!jj) {
+          ifs = (void *)1;
+          // literal ${#} or ${!} wasn't a prefix
+          if (strchr("#!", cc)) ifs = getvar_special(--ss, 1, &kk, delete);
+        } else if (ss[-1]=='{'); // not prefix, fall through
+        else if (cc == '#') {  // TODO ${#x[@]}
+          dd = !!strchr("@*", *ss);  // For ${#@} or ${#*} do normal ${#}
+          ifs = getvar_special(ss-dd, jj, &kk, delete) ? : "";
+          if (!dd) push_arg(delete, ifs = xmprintf("%zu", strlen(ifs)));
+        // ${!@} ${!@Q} ${!x} ${!x@} ${!x@Q} ${!x#} ${!x[} ${!x[*]}
+        } else if (cc == '!') {  // TODO: ${var[@]} array
+
+          // special case: normal varname followed by @} or *} = prefix list
+          if (ss[jj] == '*' || (ss[jj] == '@' && !isalpha(ss[jj+1]))) {
+            struct sh_vars **vv = visible_vars();
+
+            for (slice++, kk = 0; vv[kk]; kk++) {
+              if (vv[kk]->flags&VAR_WHITEOUT) continue;
+              if (!strncmp(s = vv[kk]->str, ss, jj))
+                arg_add(&aa, push_arg(delete, s = xstrndup(s, stridx(s, '='))));
+            }
+            if (aa.c) push_arg(delete, aa.v);
+            free(vv);
+
+          // else dereference to get new varname, discarding if none, check err
+          } else {
+            // First expansion
+            if (strchr("@*", *ss)) { // special case ${!*}/${!@}
+              expand_arg_nobrace(&aa, "\"$*\"", NO_PATH|NO_SPLIT, delete, 0);
+              ifs = *aa.v;
+              free(aa.v);
+              memset(&aa, 0, sizeof(aa));
+              jj = 1;
+            } else ifs = getvar_special(ss, jj, &jj, delete);
+            slice = ss+jj;
+
+            // Second expansion
+            if (!jj) ifs = (void *)1;
+            else if (ifs && *(ss = ifs)) {
+              if (strchr("@*", cc)) {
+                aa.c = TT.ff->arg.c-1;
+                aa.v = TT.ff->arg.v+1;
+                jj = 1;
+              } else ifs = getvar_special(ifs, strlen(ifs), &jj, delete);
+              if (ss && ss[jj]) {
+                ifs = (void *)1;
+                slice = ss+strlen(ss);
+              }
+            }
+          }
+        }
+
+        // Substitution error?
+        if (ifs == (void *)1) {
+barf:
+          if (!(((unsigned long)ifs)>>1)) ifs = "bad substitution";
+          error_msg("%.*s: %s", (int)(slice-ss), ss, ifs);
+          goto fail;
+        }
+      } else jj = 1;
+
+      // Resolve unprefixed variables
+      if (strchr("{$", ss[-1])) {
+        if (strchr("@*", cc)) {
+          aa.c = TT.ff->arg.c-1;
+          aa.v = TT.ff->arg.v+1;
+        } else {
+          ifs = getvar_special(ss, jj, &jj, delete);
+          if (!jj) {
+            if (ss[-1] == '{') goto barf;
+            new[oo++] = '$';
+            ii--;
+            continue;
+          } else if (ss[-1] != '{') ii += jj-1;
+        }
+      }
+    }
+
+    // combine before/ifs/after sections & split words on $IFS in ifs
+    // keep oo bytes of str before (already parsed)
+    // insert ifs (active for wildcards+splitting)
+    // keep str+ii after (still to parse)
+
+    // Fetch separator to glue string back together with
+    *sep = 0;
+    if (((qq&1) && cc=='*') || (flags&NO_SPLIT)) {
+      wchar_t wc;
+
+      nosplit++;
+      if (flags&SEMI_IFS) strcpy(sep, " ");
+// TODO what if separator is bigger? Need to grab 1 column of combining chars
+      else if (0<(dd = utf8towc(&wc, TT.ff->ifs, 4)))
+        sprintf(sep, "%.*s", dd, TT.ff->ifs);
+    }
+
+    // when aa proceed through entries until NULL, else process ifs once
+    mm = yy = 0;
+    do {
+      // get next argument
+      if (aa.c) ifs = aa.v[mm++] ? : "";
+
+      // Are we performing surgery on this argument?
+      if (slice && *slice != '}') {
+        dd = slice[xx = (*slice == ':')];
+        if (!ifs || (xx && !*ifs)) {
+          if (strchr("-?=", dd)) { // - use default = assign default ? error
+            push_arg(delete, ifs = slashcopy(slice+xx+1, "}", 0));
+            if (dd == '?' || (dd == '=' &&
+              !(setvar(s = xmprintf("%.*s=%s", (int)(slice-ss), ss, ifs)))))
+                goto barf; // TODO ? exits past "source" boundary
+          }
+        } else if (dd == '-'); // NOP when ifs not empty
+        // use alternate value
+        else if (dd == '+')
+          push_arg(delete, ifs = slashcopy(slice+xx+1, "}", 0));
+        else if (xx) { // ${x::}
+          long long la, lb, lc;
+
+// TODO don't redo math in loop
+          ss = slice+1;
+          la = do_math(&s);
+          if (s && *s == ':') {
+            s++;
+            lb = do_math(&s);
+          } else lb = LLONG_MAX;
+          if (s && *s != '}') {
+            error_msg("%.*s: bad '%c'", (int)(slice-ss), ss, *s);
+            s = 0;
+          }
+          if (!s) goto fail;
+
+          // This isn't quite what bash does, but close enough.
+          if (!(lc = aa.c)) lc = strlen(ifs);
+          else if (!la && !yy && strchr("@*", slice[1])) {
+            aa.v--; // ${*:0} shows $0 even though default is 1-indexed
+            aa.c++;
+            yy++;
+          }
+          if (la<0 && (la += lc)<0) continue;
+          if (lb<0) lb = lc+lb-la;
+          if (aa.c) {
+            if (mm<la || mm>=la+lb) continue;
+          } else if (la>=lc || lb<0) ifs = "";
+          else if (la+lb>=lc) ifs += la;
+          else if (!*delete || ifs != (*delete)->arg)
+            push_arg(delete, ifs = xmprintf("%.*s", (int)lb, ifs+la));
+          else {
+            for (dd = 0; dd<lb ; dd++) if (!(ifs[dd] = ifs[dd+la])) break;
+            ifs[dd] = 0;
+          }
+        } else if (strchr("#%^,", *slice)) {
+          struct sh_arg wild = {0};
+          char buf[8];
+
+          s = slashcopy(slice+(xx = slice[1]==*slice)+1, "}", &wild);
+
+          // ${x^pat} ${x^^pat} uppercase ${x,} ${x,,} lowercase (no pat = ?)
+          if (strchr("^,", *slice)) {
+            for (ss = ifs; *ss; ss += dd) {
+              dd = getutf8(ss, 4, &jj);
+              if (!*s || 0<wildcard_match(ss, s, &wild, WILD_ANY)) {
+                ll = ((*slice=='^') ? towupper : towlower)(jj);
+
+                // Of COURSE unicode case switch can change utf8 encoding length
+                // Lower case U+0069 becomes u+0130 in turkish.
+                // Greek U+0390 becomes 3 characters TODO test this
+                if (ll != jj) {
+                  yy = ss-ifs;
+                  if (!*delete || (*delete)->arg!=ifs)
+                    push_arg(delete, ifs = xstrdup(ifs));
+                  if (dd != (ll = wctoutf8(buf, ll))) {
+                    if (dd<ll)
+                      ifs = (*delete)->arg = xrealloc(ifs, strlen(ifs)+1+dd-ll);
+                    memmove(ifs+yy+dd-ll, ifs+yy+ll, strlen(ifs+yy+ll)+1);
+                  }
+                  memcpy(ss = ifs+yy, buf, dd = ll);
+                }
+              }
+              if (!xx) break;
+            }
+          // ${x#y} remove shortest prefix ${x##y} remove longest prefix
+          } else if (*slice=='#') {
+            if (0<(dd = wildcard_match(ifs, s, &wild, WILD_SHORT*!xx)))
+              ifs += dd;
+          // ${x%y} ${x%%y} suffix
+          } else if (*slice=='%') {
+            for (ss = ifs+strlen(ifs), yy = -1; ss>=ifs; ss--) {
+              if (0<(dd = wildcard_match(ss, s, &wild, WILD_SHORT*xx))&&!ss[dd])
+              {
+                yy = ss-ifs;
+                if (!xx) break;
+              }
+            }
+
+            if (yy != -1) {
+              if (*delete && (*delete)->arg==ifs) ifs[yy] = 0;
+              else push_arg(delete, ifs = xstrndup(ifs, yy));
+            }
+          }
+          free(s);
+          free(wild.v);
+
+        // ${x/pat/sub} substitute ${x//pat/sub} global ${x/#pat/sub} begin
+        // ${x/%pat/sub} end ${x/pat} delete pat (x can be @ or *)
+        } else if (*slice=='/') {
+          struct sh_arg wild = {0};
+
+          s = slashcopy(ss = slice+(xx = !!strchr("/#%", slice[1]))+1, "/}",
+            &wild);
+          ss += (long)wild.v[wild.c];
+          ss = (*ss == '/') ? slashcopy(ss+1, "}", 0) : 0;
+          jj = ss ? strlen(ss) : 0;
+          ll = 0;
+          for (ll = 0; ifs[ll];) {
+            // TODO nocasematch option
+            if (0<(dd = wildcard_match(ifs+ll, s, &wild, 0))) {
+              char *bird = 0;
+
+              if (slice[1]=='%' && ifs[ll+dd]) {
+                ll++;
+                continue;
+              }
+              if (*delete && (*delete)->arg==ifs) {
+                if (jj==dd) memcpy(ifs+ll, ss, jj);
+                else if (jj<dd) sprintf(ifs+ll, "%s%s", ss, ifs+ll+dd);
+                else bird = ifs;
+              } else bird = (void *)1;
+              if (bird) {
+                ifs = xmprintf("%.*s%s%s", ll, ifs, ss ? : "", ifs+ll+dd);
+                if (bird != (void *)1) {
+                  free(bird);
+                  (*delete)->arg = ifs;
+                } else push_arg(delete, ifs);
+              }
+              if (slice[1]!='/') break;
+            } else ll++;
+            if (slice[1]=='#') break;
+          }
+
+// ${x@QEPAa} Q=$'blah' E=blah without the $'' wrap, P=expand as $PS1
+//   A=declare that recreates var a=attribute flags
+//   x can be @*
+//      } else if (*slice=='@') {
+
+// TODO test x can be @ or *
+        } else {
+// TODO test ${-abc} as error
+          ifs = slice;
+          goto barf;
+        }
+
+// TODO: $((a=42)) can change var, affect lifetime
+// must replace ifs AND any previous output arg[] within pointer strlen()
+// also x=;echo $x${x:=4}$x
+      }
+
+      // Nothing left to do?
+      if (!ifs) break;
+      if (!*ifs && !qq) continue;
+
+      // loop within current ifs checking region to split words
+      do {
+
+        // find end of (split) word
+        if ((qq&1) || nosplit) ss = ifs+strlen(ifs);
+        else for (ss = ifs; *ss; ss += kk)
+          if (utf8chr(ss, TT.ff->ifs, &kk)) break;
+
+        // when no prefix, not splitting, no suffix: use existing memory
+        if (!oo && !*ss && !((mm==aa.c) ? str[ii] : nosplit)) {
+          if (qq || ss!=ifs) {
+            if (!(flags&NO_PATH))
+              for (jj = 0; ifs[jj]; jj++) collect_wildcards(ifs, jj, ant);
+            wildcard_add_files(arg, ifs, &deck, delete);
+          }
+          continue;
+        }
+
+        // resize allocation and copy next chunk of IFS-free data
+        jj = (mm == aa.c) && !*ss;
+        new = xrealloc(new, oo + (ss-ifs) + ((nosplit&!jj) ? strlen(sep) : 0) +
+                       (jj ? strlen(str+ii) : 0) + 1);
+        dd = sprintf(new + oo, "%.*s%s", (int)(ss-ifs), ifs,
+          (nosplit&!jj) ? sep : "");
+        if (flags&NO_PATH) oo += dd;
+        else while (dd--) collect_wildcards(new, oo++, ant);
+        if (jj) break;
+
+        // If splitting, keep quoted, non-blank, or non-whitespace separator
+        if (!nosplit) {
+          if (qq || *new || *ss) {
+            push_arg(delete, new = xrealloc(new, strlen(new)+1));
+            wildcard_add_files(arg, new, &deck, delete);
+            new = xstrdup(str+ii);
+          }
+          qq &= 1;
+          oo = 0;
+        }
+
+        // Skip trailing seperator (combining whitespace)
+        kk = 0;
+        while ((jj = utf8chr(ss, TT.ff->ifs, &ll))) {
+          if (!iswspace(jj) && kk++) break;
+          ss += ll;
+        }
+      } while (*(ifs = ss));
+    } while (!(mm == aa.c));
+  }
+
+// TODO globbing * ? [] +() happens after variable resolution
+
+// TODO test word splitting completely eliminating argument when no non-$IFS data left
+// wordexp keeps pattern when no matches
+
+// TODO test NO_SPLIT cares about IFS, see also trailing \n
+
+  // Record result.
+  if (*new || qq) {
+    if (str != new) push_arg(delete, new);
+    wildcard_add_files(arg, new, &deck, delete);
+    new = 0;
+  }
+
+  // return success after freeing
+  arg = 0;
+
+fail:
+  if (str != new) free(new);
+  free(deck.v);
+  if (ant!=&deck && ant->v) collect_wildcards("", 0, ant);
+
+  return !!arg;
+}
+
+struct sh_brace {
+  struct sh_brace *next, *prev, *stack;
+  int active, cnt, idx, commas[];
+};
+
+static int brace_end(struct sh_brace *bb)
+{
+  return bb->commas[(bb->cnt<0 ? 0 : bb->cnt)+1];
+}
+
+// expand braces (ala {a,b,c}) and call expand_arg_nobrace() each permutation
+static int expand_arg(struct sh_arg *arg, char *old, unsigned flags,
+  struct arg_list **delete)
+{
+  struct sh_brace *bb = 0, *blist = 0, *bstk, *bnext;
+  int i, j, k, x;
+  char *s, *ss;
+
+  // collect brace spans
+  if ((TT.options&OPT_B) && !(flags&NO_BRACE)) for (i = 0; ; i++) {
+    // skip quoted/escaped text
+    while ((s = parse_word(old+i, 1, 0)) != old+i) i += s-(old+i);
+    // stop at end of string if we haven't got any more open braces
+    if (!bb && !old[i]) break;
+    // end a brace?
+    if (bb && (!old[i] || old[i] == '}')) {
+      bb->active = bb->commas[bb->cnt+1] = i;
+      // pop brace from bb into bnext
+      for (bnext = bb; bb && bb->active; bb = (bb==blist) ? 0 : bb->prev);
+      // Is this a .. span?
+      j = 1+*bnext->commas;
+      if (old[i] && !bnext->cnt && i-j>=4) {
+        // a..z span? Single digit numbers handled here too. TODO: utf8
+        if (old[j+1]=='.' && old[j+2]=='.') {
+          bnext->commas[2] = old[j];
+          bnext->commas[3] = old[j+3];
+          k = 0;
+          if (old[j+4]=='}' ||
+            (sscanf(old+j+4, "..%u}%n", bnext->commas+4, &k) && k))
+              bnext->cnt = -1;
+        }
+        // 3..11 numeric span?
+        if (!bnext->cnt) {
+          for (k=0, j = 1+*bnext->commas; k<3; k++, j += x)
+            if (!sscanf(old+j, "..%u%n"+2*!k, bnext->commas+2+k, &x)) break;
+          if (old[j] == '}') bnext->cnt = -2;
+        }
+        // Increment goes in the right direction by at least 1
+        if (bnext->cnt) {
+          if (!bnext->commas[4]) bnext->commas[4] = 1;
+          if ((bnext->commas[3]-bnext->commas[2]>0) != (bnext->commas[4]>0))
+            bnext->commas[4] *= -1;
+        }
+      }
+      // discard unterminated span, or commaless span that wasn't x..y
+      if (!old[i] || !bnext->cnt)
+        free(dlist_pop((blist == bnext) ? &blist : &bnext));
+    // starting brace
+    } else if (old[i] == '{') {
+      dlist_add_nomalloc((void *)&blist,
+        (void *)(bb = xzalloc(sizeof(struct sh_brace)+34*4)));
+      bb->commas[0] = i;
+    // no active span?
+    } else if (!bb) continue;
+    // add a comma to current span
+    else if (bb && old[i] == ',') {
+      if (bb->cnt && !(bb->cnt&31)) {
+        dlist_lpop(&blist);
+        dlist_add_nomalloc((void *)&blist,
+          (void *)(bb = xrealloc(bb, sizeof(struct sh_brace)+(bb->cnt+34)*4)));
+      }
+      bb->commas[++bb->cnt] = i;
+    }
+  }
+
+// TODO NOSPLIT with braces? (Collate with spaces?)
+  // If none, pass on verbatim
+  if (!blist) return expand_arg_nobrace(arg, old, flags, delete, 0);
+
+  // enclose entire range in top level brace.
+  (bstk = xzalloc(sizeof(struct sh_brace)+8))->commas[1] = strlen(old)+1;
+  bstk->commas[0] = -1;
+
+  // loop through each combination
+  for (;;) {
+
+    // Brace expansion can't be longer than original string. Keep start to {
+    s = ss = xmalloc(bstk->commas[1]);
+
+    // Append output from active braces to string
+    for (bb = blist; bb; bb = (bnext == blist) ? 0 : bnext) {
+
+      // If this brace already tip of stack, pop it. (We'll re-add in a moment.)
+      if (bstk == bb) bstk = bstk->stack;
+      // if bb is within bstk, save prefix text from bstk's "," to bb's "{"
+      if (brace_end(bstk)>bb->commas[0]) {
+        i = bstk->commas[bstk->idx]+1;
+        s = stpncpy(s, old+i, bb->commas[0]-i);
+      }
+      else bstk = bstk->stack; // bb past bstk so done with old bstk, pop it
+      // push self onto stack as active
+      bb->stack = bstk;
+      bb->active = 1;
+      bstk = bnext = bb;
+
+      // Find next active range: skip inactive spans from earlier/later commas
+      while ((bnext = (bnext->next==blist) ? 0 : bnext->next)) {
+
+        // past end of this brace (always true for a..b ranges)
+        if ((i = bnext->commas[0])>brace_end(bb)) break;
+
+        // in this brace but not this section
+        if (i<bb->commas[bb->idx] || i>bb->commas[bb->idx+1]) {
+          bnext->active = 0;
+          bnext->stack = 0;
+
+        // in this section
+        } else break;
+      }
+
+      // is next span past this range?
+      if (!bnext || bb->cnt<0 || bnext->commas[0]>bb->commas[bb->idx+1]) {
+
+        // output uninterrupted span
+        if (bb->cnt<0) {
+          k = bb->commas[2]+bb->commas[4]*bb->idx;
+          s += sprintf(s, (bb->cnt==-1) ? "\\%c"+!ispunct(k) : "%d", k);
+        } else {
+          i = bb->commas[bstk->idx]+1;
+          s = stpncpy(s, old+i, bb->commas[bb->idx+1]-i);
+        }
+
+        // While not sibling, output tail and pop
+        while (!bnext || bnext->commas[0]>brace_end(bstk)) {
+          if (!(bb = bstk->stack)) break;
+          i = brace_end(bstk)+1; // start of span
+          j = bb->commas[bb->idx+1]; // enclosing comma span (can't be a..b)
+
+          while (bnext) {
+            if (bnext->commas[0]<j) {
+              j = bnext->commas[0];// sibling
+              break;
+            } else if (brace_end(bb)>bnext->commas[0])
+              bnext = (bnext->next == blist) ? 0 : bnext->next;
+            else break;
+          }
+          s = stpncpy(s, old+i, j-i);
+
+          // if next is sibling but parent _not_ a sibling, don't pop
+          if (bnext && bnext->commas[0]<brace_end(bb)) break;
+          bstk = bb;
+        }
+      }
+    }
+
+    // Save result, aborting on expand error
+    if (expand_arg_nobrace(arg, push_arg(delete, ss), flags, delete, 0)) {
+      llist_traverse(blist, free);
+
+      return 1;
+    }
+
+    // increment
+    for (bb = blist->prev; bb; bb = (bb == blist) ? 0 : bb->prev) {
+      if (!bb->stack) continue;
+      else if (bb->cnt<0) {
+        if (abs(bb->commas[2]-bb->commas[3]) < abs(++bb->idx*bb->commas[4]))
+          bb->idx = 0;
+        else break;
+      } else if (++bb->idx > bb->cnt) bb->idx = 0;
+      else break;
+    }
+
+    // if increment went off left edge, done expanding
+    if (!bb) break;
+  }
+  llist_traverse(blist, free);
+
+  return 0;
+}
+
+// Expand exactly one arg, returning NULL on error.
+static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del)
+{
+  struct sh_arg arg = {0};
+  char *s = 0;
+
+  if (!expand_arg(&arg, new, flags|NO_PATH|NO_SPLIT, del))
+    if (!(s = *arg.v) && (flags&(SEMI_IFS|NO_NULL))) s = "";
+  free(arg.v);
+
+  return s;
+}
+
+// TODO |&
+
 // Expand arguments and perform redirections. Return new process object with
 // expanded args. This can be called from command or block context.
-static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd)
+static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd)
 {
   struct sh_process *pp;
   char *s = s, *ss, *sss, *cv = 0;
   int j, to, from, here = 0;
 
   TT.hfd = 10;
-
   pp = xzalloc(sizeof(struct sh_process));
   pp->urd = urd;
+  pp->raw = arg;
 
   // When we redirect, we copy each displaced filehandle to restore it later.
 
   // Expand arguments and perform redirections
-  for (j = envlen; j<arg->c; j++) {
+  for (j = skip; j<arg->c; j++) {
     int saveclose = 0, bad = 0;
 
     s = arg->v[j];
 
+    if (!strcmp(s, "!")) {
+      pp->not ^= 1;
+
+      continue;
+    }
+
     // Handle <() >() redirectionss
     if ((*s == '<' || *s == '>') && s[1] == '(') {
-      int pipes[2], *uu = 0, dd;
+      int new = pipe_subshell(s+2, strlen(s+2)-1, *s == '>');
 
       // Grab subshell data
-      if (pipe(pipes)) {
-        perror_msg_raw(s);
+      if (new == -1) {
         pp->exit = 1;
 
         return pp;
       }
-
-      // Perform input or output redirect and launch process
-      dd = *s == '<';
-      save_redirect(&uu, pipes[dd], dd);
-      close(pipes[dd]);
-      run_subshell(s+2, strlen(s+2)-1); // ignore errors, don't track
-      unredirect(uu);
-      save_redirect(&urd, -1, pipes[!dd]);
+      save_redirect(&pp->urd, -2, new);
 
       // bash uses /dev/fd/%d which requires /dev/fd to be a symlink to
       // /proc/self/fd so we just produce that directly.
-      add_arg(&pp->delete, ss = xmprintf("/proc/self/fd/%d", pipes[!dd]));
-      array_add(&pp->arg.v, pp->arg.c++, ss);
+      arg_add_del(&pp->arg, ss = xmprintf("/proc/self/fd/%d", new),&pp->delete);
 
       continue;
     }
@@ -749,8 +2101,11 @@
     sss = ss + anystart(ss, (void *)redirectors);
     if (ss == sss) {
       // Nope: save/expand argument and loop
-      expand_arg(&pp->arg, s, 0, &pp->delete);
+      if (expand_arg(&pp->arg, s, 0, &pp->delete)) {
+        pp->exit = 1;
 
+        return pp;
+      }
       continue;
     } else if (j+1 >= arg->c) {
       // redirect needs one argument
@@ -763,20 +2118,26 @@
     if (isdigit(*s) && ss-s>5) break;
 
     // expand arguments for everything but << and <<-
-    if (strncmp(ss, "<<", 2) && ss[2] != '<' &&
-      !(sss = expand_one_arg(sss, NO_PATH, &pp->delete)))
-    {
-      s = sss;
-      break; // arg splitting here is an error
+    if (strncmp(ss, "<<", 2) && ss[2] != '<') {
+      struct sh_arg tmp = {0};
+
+      if (!expand_arg(&tmp, sss, 0, &pp->delete) && tmp.c == 1) sss = *tmp.v;
+      else {
+        if (tmp.c > 1) error_msg("%s: ambiguous redirect", sss);
+        s = 0;
+      }
+      free(tmp.v);
+      if (!s) break;
     }
 
     // Parse the [fd] part of [fd]<name
     to = *ss != '<';
     if (isdigit(*s)) to = atoi(s);
     else if (*s == '{') {
+      if (*varend(s+1) != '}') break;
       // when we close a filehandle, we _read_ from {var}, not write to it
       if ((!strcmp(ss, "<&") || !strcmp(ss, ">&")) && !strcmp(sss, "-")) {
-        if (!(ss = getvarlen(s+1, ss-s-2))) break;
+        if (!(ss = getvar(s+1))) break;
         to = atoi(ss); // TODO trailing garbage?
         if (save_redirect(&pp->urd, -1, to)) break;
         close(to);
@@ -786,7 +2147,7 @@
       } else {
         // we don't save this, it goes in the env var and user can close it.
         if (-1 == (to = next_hfd())) break;
-        cv = xmprintf("%.*s=%d", (int)(ss-s-1), s+1, to);
+        cv = xmprintf("%.*s=%d", (int)(ss-s-2), s+1, to);
       }
     }
 
@@ -802,19 +2163,25 @@
 
         // write contents to file (if <<< else <<) then lseek back to start
         else if (ss[2] == '<') {
-          if (x) sss = expand_one_arg(sss, NO_PATH|NO_SPLIT, 0);
-          len = strlen(sss);
-          if (len != writeall(from, sss, len)) bad++;
-          if (x) free(sss);
+          if (!(ss = expand_one_arg(sss, 0, 0))) {
+            s = 0;
+            break;
+          }
+          len = strlen(ss);
+          if (len != writeall(from, ss, len)) bad++;
+          if (ss != sss) free(ss);
         } else {
           struct sh_arg *hh = arg+here++;
 
           for (i = 0; i<hh->c; i++) {
             ss = hh->v[i];
             sss = 0;
+// TODO audit this ala man page
             // expand_parameter, commands, and arithmetic
-            if (x) ss = sss = expand_one_arg(ss,
-              NO_PATH|NO_SPLIT|NO_BRACE|NO_TILDE|NO_QUOTE, 0);
+            if (x && !(ss = sss = expand_one_arg(ss, ~SEMI_IFS, 0))) {
+              s = 0;
+              break;
+            }
 
             while (zap && *ss == '\t') ss++;
             x = writeall(from, ss, len = strlen(ss));
@@ -835,26 +2202,26 @@
 
     // Handle file descriptor duplication/close (&> &>> <& >& with number or -)
     // These redirect existing fd so nothing to open()
-    } else if (strchr(ss, '&')) {
+    } else if (*ss == '&' || ss[1] == '&') {
 
       // is there an explicit fd?
       for (ss = sss; isdigit(*ss); ss++);
       if (ss-sss>5 || (*ss && (*ss != '-' || ss[1]))) {
-        // bad fd
-        s = sss;
-        break;
+        if (*ss=='&') ss++;
+        saveclose = 4;
+        goto notfd;
       }
 
       from = (ss==sss) ? to : atoi(sss);
       saveclose = 2-(*ss == '-');
     } else {
-
+notfd:
       // Permissions to open external file with: < > >> <& >& <> >| &>> &>
       if (!strcmp(ss, "<>")) from = O_CREAT|O_RDWR;
-      else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND;
+      else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND|O_WRONLY;
       else {
-        from = (*ss != '<') ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
-        if (!strcmp(ss, ">") && (TT.options&SH_NOCLOBBER)) {
+        from = (*ss == '<') ? O_RDONLY : O_CREAT|O_WRONLY|O_TRUNC;
+        if (!strcmp(ss, ">") && (TT.options&OPT_C)) {
           struct stat st;
 
           // Not _just_ O_EXCL: > /dev/null allowed
@@ -867,7 +2234,11 @@
 // TODO: /dev/{tcp,udp}/host/port
 
       // Open the file
-      if (-1 == (from = xcreate(sss, from|WARN_ONLY, 0666))) break;
+      if (-1 == (from = xcreate_stdio(sss, from|WARN_ONLY, 0666))) {
+        s = 0;
+
+        break;
+      }
     }
 
     // perform redirect, saving displaced "to".
@@ -875,17 +2246,18 @@
     // Do we save displaced "to" in env variable instead of undo list?
     if (cv) {
       --*pp->urd;
-      setvar(cv, TAKE_MEM);
+      if (!setvar(cv)) bad++;
       cv = 0;
     }
     if ((saveclose&1) && save_redirect(&pp->urd, -1, from)) bad++;
+    if ((saveclose&4) && save_redirect(&pp->urd, from, 2)) bad++;
     if (!(saveclose&2)) close(from);
     if (bad) break;
   }
 
   // didn't parse everything?
   if (j != arg->c) {
-    syntax_err("bad %s", s);
+    if (s) syntax_err(s);
     if (!pp->exit) pp->exit = 1;
     free(cv);
   }
@@ -893,243 +2265,231 @@
   return pp;
 }
 
-// Execute a single command
-static struct sh_process *run_command(struct sh_arg *arg)
+// Call binary, or run script via xexec("sh --")
+static void sh_exec(char **argv)
 {
-  struct sh_process *pp;
-  struct toy_list *tl;
-  int envlen, j;
-  char *s;
+  char *pp = getvar("PATH" ? : _PATH_DEFPATH), *cc = TT.isexec ? : *argv, *ss,
+    **sss = 0, **oldenv = environ, **argv2;
+  struct string_list *sl;
 
-  // Grab leading variable assignments
-  for (envlen = 0; envlen<arg->c; envlen++) {
-    s = arg->v[envlen];
-    for (j=0; s[j] && (s[j]=='_' || !ispunct(s[j])); j++);
-    if (!j || s[j] != '=') break;
+  if (getpid() != TT.pid) signal(SIGINT, SIG_DFL); // TODO: restore all?
+  errno = ENOENT;
+  if (strchr(ss = cc, '/')) {
+    if (access(ss, X_OK)) ss = 0;
+  } else for (sl = find_in_path(pp, cc); sl || (ss = 0); free(llist_pop(&sl)))
+    if (!access(ss = sl->str, X_OK)) break;
+
+  if (ss) {
+    struct sh_vars **vv = visible_vars();
+    struct sh_arg aa;
+    unsigned uu, argc;
+
+    // convert vars in-place and use original sh_arg alloc to add one more
+    aa.v = environ = (void *)vv;
+    for (aa.c = uu = 0; vv[uu]; uu++) {
+      if ((vv[uu]->flags&(VAR_WHITEOUT|VAR_GLOBAL))==VAR_GLOBAL) {
+        if (*(pp = vv[uu]->str)=='_' && pp[1] == '=') sss = aa.v+aa.c;
+        aa.v[aa.c++] = pp;
+      }
+    }
+    aa.v[aa.c] = 0;
+    if (!sss) {
+      arg_add(&aa, 0);
+      sss = aa.v+aa.c-1;
+    }
+    *sss = xmprintf("_=%s", ss);
+
+    // exec or source
+    execve(ss, argv, environ);
+    if (errno == ENOEXEC) {
+      for (argc = 0; argv[argc]; argc++);
+      argv2 = xmalloc((argc+3)*sizeof(char *));
+      memcpy(argv2+3, argv+1, argc*sizeof(char *));
+      argv2[0] = "sh";
+      argv2[1] = "--";
+      argv2[2] = ss;
+      xexec(argv2);
+      free(argv2);
+    }
+    environ = oldenv;
+    free(*sss);
+    free(aa.v);
   }
 
-  // expand arguments and perform redirects
+  perror_msg("%s", *argv);
+  if (!TT.isexec) _exit(127);
+  llist_traverse(sl, free);
+}
+
+// Execute a single command at TT.ff->pl
+static struct sh_process *run_command(void)
+{
+  char *s, *sss;
+  struct sh_arg *arg = TT.ff->pl->arg;
+  int envlen, funk = TT.funcslen, jj = 0, locals = 0;
+  struct sh_process *pp;
+
+  // Count leading variable assignments
+  for (envlen = 0; envlen<arg->c; envlen++)
+    if ((s = varend(arg->v[envlen])) == arg->v[envlen] || *s != '=') break;
   pp = expand_redir(arg, envlen, 0);
 
-if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); for (i=0; i<pp->arg.c; i++) dprintf(255, "'%s' ", pp->arg.v[i]); dprintf(255, "\n"); }
-  // perform assignments locally if there's no command
-  if (envlen == arg->c) {
-    for (j = 0; j<envlen; j++) {
-      s = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT, 0);
-      setvar(s, TAKE_MEM*(s!=arg->v[j]));
+  // Are we calling a shell function?  TODO binary search
+  if (pp->arg.c && !strchr(*pp->arg.v, '/'))
+    for (funk = 0; funk<TT.funcslen; funk++)
+       if (!strcmp(*pp->arg.v, TT.functions[funk]->name)) break;
+
+  // Create new function context to hold local vars?
+  if (funk != TT.funcslen || (envlen && pp->arg.c) || TT.ff->blk->pipe) {
+    call_function();
+// TODO function needs to run asynchronously in pipeline
+    if (funk != TT.funcslen) {
+      TT.ff->delete = pp->delete;
+      pp->delete = 0;
     }
+    addvar(0, TT.ff); // function context (not source) so end_function deletes
+    locals = 1;
+  }
 
-  // Do nothing if nothing to do
-  } else if (pp->exit || !pp->arg.v);
-//  else if (!strcmp(*pp->arg.v, "(("))
-// TODO: handle ((math)) currently totally broken
-// TODO: call functions()
-  // Is this command a builtin that should run in this process?
-  else if ((tl = toy_find(*pp->arg.v))
-    && (tl->flags & (TOYFLAG_NOFORK|TOYFLAG_MAYFORK)))
-  {
-    struct toy_context temp;
-    sigjmp_buf rebound;
+  // perform any assignments
+  if (envlen) {
+    struct sh_fcall *ff;
+    struct sh_vars *vv;
 
-    // This fakes lots of what toybox_main() does.
-    memcpy(&temp, &toys, sizeof(struct toy_context));
-    memset(&toys, 0, sizeof(struct toy_context));
+    for (; jj<envlen && !pp->exit; jj++) {
+      if (!(vv = findvar(s = arg->v[jj], &ff))) ff = locals?TT.ff:TT.ff->prev;
+      else if (vv->flags&VAR_READONLY) ff = 0;
+      else if (locals && ff!=TT.ff) vv = 0, ff = TT.ff;
 
-    if (!sigsetjmp(rebound, 1)) {
-      toys.rebound = &rebound;
-      toy_init(tl, pp->arg.v);  // arg.v must be null terminated
-      tl->toy_main();
-      xflush(0);
-    }
-    pp->exit = toys.exitval;
-    if (toys.optargs != toys.argv+1) free(toys.optargs);
-    if (toys.old_umask) umask(toys.old_umask);
-    memcpy(&toys, &temp, sizeof(struct toy_context));
-  } else {
-    char **env = 0, **old = environ, *ss, *sss;
-    int kk = 0, ll;
-
-    // We don't allocate/free any array members, just the array
-    if (environ) while (environ[kk]) kk++;
-    if (kk) {
-      env = xmalloc(sizeof(char *)*(kk+33));
-      memcpy(env, environ, sizeof(char *)*(kk+1));
-      environ = env;
-    }
-    // assign leading environment variables
-    for (j = 0; j<envlen; j++) {
-      sss = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT, &pp->delete);
-      for (ll = 0; ll<kk; ll++) {
-        for (s = sss, ss = env[ll]; *s == *ss && *s != '='; s++, ss++);
-        if (*s != '=') continue;
-        env[ll] = sss;
-        break;
+      if (!vv&&ff) (vv = addvar(s, ff))->flags = VAR_NOFREE|(VAR_GLOBAL*locals);
+      if (!(sss = expand_one_arg(s, SEMI_IFS, 0))) pp->exit = 1;
+      else {
+        if (!setvar_found(sss, vv)) continue;
+        if (sss==s) {
+          if (!locals) vv->str = xstrdup(sss);
+          else vv->flags |= VAR_NOFREE;
+        }
+        cache_ifs(vv->str, ff ? : TT.ff);
       }
-      if (ll == kk) array_add(&environ, kk++, sss);
     }
-    ss = getvar("SHLVL");
-    sprintf(toybuf, "%d", atoi(ss ? ss : "")+1);
-    xsetenv("SHLVL", toybuf);
+  }
 
-    if (-1 == (pp->pid = xpopen_both(pp->arg.v, 0)))
-      perror_msg("%s: vfork", *pp->arg.v);
+  // Do the thing
+  if (pp->exit || envlen==arg->c) s = 0; // leave $_ alone
+  else if (!pp->arg.c) s = "";           // nothing to do but blank $_
 
-    // Restore environment variables
-    environ = old;
-    free(env);
+// TODO: call functions() FUNCTION
+// TODO what about "echo | x=1 | export fruit", must subshell? Test this.
+//   Several NOFORK can just NOP in a pipeline? Except ${a?b} still errors
+
+  // call shell function
+  else if (funk != TT.funcslen) {
+    (TT.ff->func = TT.functions[funk])->refcount++;
+    TT.ff->pl = TT.ff->func->pipeline;
+    TT.ff->arg = pp->arg;
+  } else {
+    struct toy_list *tl = toy_find(*pp->arg.v);
+
+    jj = tl ? tl->flags : 0;
+    TT.pp = pp;
+    s = pp->arg.v[pp->arg.c-1];
+    sss = pp->arg.v[pp->arg.c];
+//dprintf(2, "%d run command %p %s\n", getpid(), TT.ff, *pp->arg.v); debug_show_fds();
+// TODO handle ((math)): else if (!strcmp(*pp->arg.v, "(("))
+// TODO: figure out when can exec instead of forking, ala sh -c blah
+
+    // Is this command a builtin that should run in this process?
+    if ((jj&TOYFLAG_NOFORK) || ((jj&TOYFLAG_MAYFORK) && !locals)) {
+      sigjmp_buf rebound;
+      char temp[jj = offsetof(struct toy_context, rebound)];
+
+      // This fakes lots of what toybox_main() does.
+      memcpy(&temp, &toys, jj);
+      memset(&toys, 0, jj);
+
+      // The compiler complains "declaration does not declare anything" if we
+      // name the union in TT, only works WITHOUT name. So we can't
+      // sizeof(union) instead offsetof() first thing after union to get size.
+      memset(&TT, 0, offsetof(struct sh_data, SECONDS));
+      if (!sigsetjmp(rebound, 1)) {
+        toys.rebound = &rebound;
+        toy_singleinit(tl, pp->arg.v);
+        tl->toy_main();
+        xflush(0);
+      }
+      toys.rebound = 0;
+      pp->exit = toys.exitval;
+      if (toys.optargs != toys.argv+1) free(toys.optargs);
+      if (toys.old_umask) umask(toys.old_umask);
+      memcpy(&toys, &temp, jj);
+    } else if (-1==(pp->pid = xpopen_setup(pp->arg.v, 0, sh_exec)))
+        perror_msg("%s: vfork", *pp->arg.v);
   }
 
   // cleanup process
   unredirect(pp->urd);
+  pp->urd = 0;
+  if (locals && funk == TT.funcslen) end_function(0);
+  if (s) setvarval("_", s);
 
   return pp;
 }
 
-static void free_process(void *ppp)
+static int free_process(struct sh_process *pp)
 {
-  struct sh_process *pp = ppp;
+  int rc;
+
+  if (!pp) return 127;
+  rc = pp->exit;
   llist_traverse(pp->delete, llist_free_arg);
   free(pp);
-}
 
-
-// parse next word from command line. Returns end, or 0 if need continuation
-// caller eats leading spaces
-static char *parse_word(char *start)
-{
-  int i, quote = 0, q, qc = 0;
-  char *end = start, *s;
-
-  // Things we should only return at the _start_ of a word
-
-  if (strstart(&end, "<(") || strstart(&end, ">(")) toybuf[quote++]=')';
-
-  // Redirections. 123<<file- parses as 2 args: "123<<" "file-".
-  s = end + redir_prefix(end);
-  if ((i = anystart(s, (void *)redirectors))) s += i;
-  if (s != end) return (end == start) ? s : end;
-
-  // (( is a special quote at the start of a word
-  if (strstart(&end, "((")) toybuf[quote++] = 255;
-
-  // find end of this word
-  while (*end) {
-    i = 0;
-
-    // barf if we're near overloading quote stack (nesting ridiculously deep)
-    if (quote>4000) {
-      syntax_err("tilt");
-      return (void *)1;
-    }
-
-    // Handle quote contexts
-    if ((q = quote ? toybuf[quote-1] : 0)) {
-
-      // when waiting for parentheses, they nest
-      if ((q == ')' || q == '\xff') && (*end == '(' || *end == ')')) {
-        if (*end == '(') qc++;
-        else if (qc) qc--;
-        else if (q == '\xff') {
-          // (( can end with )) or retroactively become two (( if we hit one )
-          if (strstart(&end, "))")) quote--;
-          else return start+1;
-        } else if (*end == ')') quote--;
-        end++;
-
-      // end quote?
-      } else if (*end == q) quote--, end++;
-
-      // single quote claims everything
-      else if (q == '\'') end++;
-      else i++;
-
-      // loop if we already handled a symbol
-      if (!i) continue;
-    } else {
-      // Things that only matter when unquoted
-
-      if (isspace(*end)) break;
-      if (*end == ')') return end+(start==end);
-
-      // Flow control characters that end pipeline segments
-      s = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
-        "|&", "|", "&&", "&", "(", ")", 0});
-      if (s != end) return (end == start) ? s : end;
-    }
-
-    // Things the same unquoted or in most non-single-quote contexts
-
-    // start new quote context?
-    if (strchr("\"'`", *end)) toybuf[quote++] = *end++;
-
-    // backslash escapes
-    else if (*end == '\\') {
-      if (!end[1] || (end[1]=='\n' && !end[2])) return 0;
-      end += 2;
-    } else if (*end++ == '$') {
-      if (-1 != (i = stridx("({[", *end))) {
-        toybuf[quote++] = ")}]"[i];
-        end++;
-      }
-    }
-  }
-
-  return quote ? 0 : end;
+  return rc;
 }
 
 // if then fi for while until select done done case esac break continue return
 
 // Free one pipeline segment.
-void free_pipeline(void *pipeline)
+static void free_pipeline(void *pipeline)
 {
   struct sh_pipeline *pl = pipeline;
   int i, j;
 
-  if (pl) for (j=0; j<=pl->count; j++) {
-    for (i = 0; i<=pl->arg->c; i++)  free(pl->arg[j].v[i]);
+  if (!pl) return;
+
+  // free either function or arguments and HERE doc contents
+  if (pl->type == 'F') {
+    free_function((void *)*pl->arg->v);
+    *pl->arg->v = 0;
+  }
+  for (j=0; j<=pl->count; j++) {
+    if (!pl->arg[j].v) continue;
+    for (i = 0; i<=pl->arg[j].c; i++) free(pl->arg[j].v[i]);
     free(pl->arg[j].v);
   }
   free(pl);
 }
 
-// Return end of current block, or NULL if we weren't in block and fell off end.
-struct sh_pipeline *block_end(struct sh_pipeline *pl)
+// Append a new pipeline to function, returning pipeline and pipeline's arg
+static struct sh_pipeline *add_pl(struct sh_pipeline **ppl, struct sh_arg **arg)
 {
-  int i = 0;
+  struct sh_pipeline *pl = xzalloc(sizeof(struct sh_pipeline));
 
-// TODO: should this be inlined into type 1 processing to set blk->end and
-// then everything else use that?
+  if (arg) *arg = pl->arg;
+  pl->lineno = TT.LINENO;
+  dlist_add_nomalloc((void *)ppl, (void *)pl);
 
-  while (pl) {
-    if (pl->type == 1 || pl->type == 'f') i++;
-    else if (pl->type == 3) if (--i<1) break;
-    pl = pl->next;
-  }
-
-  return pl;
-}
-
-void free_function(struct sh_function *sp)
-{
-  llist_traverse(sp->pipeline, free_pipeline);
-  llist_traverse(sp->expect, free);
-  memset(sp, 0, sizeof(struct sh_function));
-}
-
-// TODO this has to add to a namespace context. Functions within functions...
-struct sh_pipeline *add_function(char *name, struct sh_pipeline *pl)
-{
-dprintf(2, "stub add_function");
-
-  return block_end(pl->next);
+  return pl->end = pl;
 }
 
 // Add a line of shell script to a shell function. Returns 0 if finished,
 // 1 to request another line of input (> prompt), -1 for syntax err
-static int parse_line(char *line, struct sh_function *sp)
+static int parse_line(char *line, struct sh_pipeline **ppl,
+   struct double_list **expect)
 {
-  char *start = line, *delete = 0, *end, *last = 0, *s, *ex, done = 0;
-  struct sh_pipeline *pl = sp->pipeline ? sp->pipeline->prev : 0;
+  char *start = line, *delete = 0, *end, *s, *ex, done = 0,
+    *tails[] = {"fi", "done", "esac", "}", "]]", ")", 0};
+  struct sh_pipeline *pl = *ppl ? (*ppl)->prev : 0, *pl2, *pl3;
   struct sh_arg *arg = 0;
   long i;
 
@@ -1148,29 +2508,28 @@
       arg += 1+pl->here;
 
       // Match unquoted EOF.
-      for (s = line, end = arg->v[arg->c]; *s && *end; s++, i++) {
+      for (s = line, end = arg->v[arg->c]; *s && *end; s++) {
         s += strspn(s, "\\\"'");
         if (*s != *end) break;
       }
+      // Add this line, else EOF hit so end HERE document
       if (!*s && !*end) {
-        // Add this line
-        array_add(&arg->v, arg->c++, xstrdup(line));
-        array_add(&arg->v, arg->c, arg->v[arg->c]);
-        arg->c++;
-      // EOF hit, end HERE document
+        end = arg->v[arg->c];
+        arg_add(arg, xstrdup(line));
+        arg->v[arg->c] = end;
       } else {
         arg->v[arg->c] = 0;
         pl->here++;
       }
       start = 0;
 
-    // Nope, new segment
-    } else pl = 0;
+    // Nope, new segment if not self-managing type
+    } else if (pl->type < 128) pl = 0;
   }
 
   // Parse words, assemble argv[] pipelines, check flow control and HERE docs
   if (start) for (;;) {
-    ex = sp->expect ? sp->expect->prev->data : 0;
+    ex = *expect ? (*expect)->prev->data : 0;
 
     // Look for << HERE redirections in completed pipeline segment
     if (pl && pl->count == -1) {
@@ -1187,9 +2546,9 @@
 
         // Add another arg[] to the pipeline segment (removing/readding to list
         // because realloc can move pointer)
-        dlist_lpop(&sp->pipeline);
+        dlist_lpop(ppl);
         pl = xrealloc(pl, sizeof(*pl) + ++pl->count*sizeof(struct sh_arg));
-        dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
+        dlist_add_nomalloc((void *)ppl, (void *)pl);
 
         // queue up HERE EOF so input loop asks for more lines.
         arg[pl->count].v = xzalloc(2*sizeof(void *));
@@ -1198,6 +2557,39 @@
         arg[pl->count].c = 0;
         if (s[2] == '<') pl->here++; // <<< doesn't load more data
       }
+
+      // Did we just end a function?
+      if (ex == (void *)1) {
+        struct sh_function *funky;
+
+        // function must be followed by a compound statement for some reason
+        if ((*ppl)->prev->type != 3) {
+          s = *(*ppl)->prev->arg->v;
+          goto flush;
+        }
+
+        // Back up to saved function() statement and create sh_function
+        free(dlist_lpop(expect));
+        pl = (void *)(*expect)->data;
+        funky = xmalloc(sizeof(struct sh_function));
+        funky->refcount = 1;
+        funky->name = *pl->arg->v;
+        *pl->arg->v = (void *)funky;
+
+        // Chop out pipeline segments added since saved function
+        funky->pipeline = pl->next;
+        pl->next->prev = (*ppl)->prev;
+        (*ppl)->prev->next = pl->next;
+        pl->next = *ppl;
+        (*ppl)->prev = pl;
+        dlist_terminate(funky->pipeline = add_pl(&funky->pipeline, 0));
+        funky->pipeline->type = 'f';
+
+        // Immature function has matured (meaning cleanup is different)
+        pl->type = 'F';
+        free(dlist_lpop(expect));
+        ex = *expect ? (*expect)->prev->data : 0;
+      }
       pl = 0;
     }
     if (done) break;
@@ -1208,20 +2600,27 @@
     if (*start=='#') while (*start && *start != '\n') ++start;
 
     // Parse next word and detect overflow (too many nested quotes).
-    if ((end = parse_word(start)) == (void *)1)
-      goto flush;
+    if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush;
+//dprintf(2, "%d %p %s word=%.*s\n", getpid(), pl, (ex != (void *)1) ? ex : "function", (int)(end-start), end ? start : "");
+
+    if (pl && pl->type == 'f' && arg->c == 1 && (end-start!=1 || *start!='(')) {
+funky:
+      // end function segment, expect function body
+      dlist_add(expect, (void *)pl);
+      pl = 0;
+      dlist_add(expect, (void *)1);
+      dlist_add(expect, 0);
+
+      continue;
+    }
 
     // Is this a new pipeline segment?
-    if (!pl) {
-      pl = xzalloc(sizeof(struct sh_pipeline));
-      arg = pl->arg;
-      dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
-    }
+    if (!pl) pl = add_pl(ppl, &arg);
 
     // Do we need to request another line to finish word (find ending quote)?
     if (!end) {
       // Save unparsed bit of this line, we'll need to re-parse it.
-      array_add(&arg->v, arg->c++, xstrndup(start, strlen(start)));
+      arg_add(arg, xstrndup(start, strlen(start)));
       arg->c = -arg->c;
       free(delete);
 
@@ -1230,78 +2629,156 @@
 
     // Ok, we have a word. What does it _mean_?
 
-    // Did we hit end of line or ) outside a function declaration?
-    // ) is only saved at start of a statement, ends current statement
-    if (end == start || (arg->c && *start == ')' && pl->type!='f')) {
-      if (!arg->v) array_add(&arg->v, arg->c, 0);
+    // case/esac parsing is weird (unbalanced parentheses!), handle first
+    i = (unsigned long)ex>1 && !strcmp(ex, "esac") &&
+        ((pl->type && pl->type != 3) || (*start==';' && end-start>1));
+    if (i) {
 
-      if (pl->type == 'f' && arg->c<3) {
-        s = "function()";
-        goto flush;
-      }
-
-      // "for" on its own line is an error.
-      if (arg->c == 1 && ex && !memcmp(ex, "do\0A", 4)) {
+      // Premature EOL in type 1 (case x\nin) or 2 (at start or after ;;) is ok
+      if (end == start) {
+        if (pl->type==128 && arg->c==2) break;  // case x\nin
+        if (pl->type==129 && (!arg->c || (arg->c==1 && **arg->v==';'))) break;
         s = "newline";
         goto flush;
       }
 
-      // don't save blank pipeline segments
-      if (!arg->c) free_pipeline(dlist_lpop(&sp->pipeline));
+      // type 0 means just got ;; so start new type 2
+      if (!pl->type) {
+        // catch "echo | ;;" errors
+        if (arg->v && arg->v[arg->c] && strcmp(arg->v[arg->c], "&")) goto flush;
+        if (!arg->c) {
+          if (pl->prev->type == 2) {
+            // Add a call to "true" between empty ) ;;
+            arg_add(arg, xstrdup(":"));
+            pl = add_pl(ppl, &arg);
+          }
+          pl->type = 129;
+        } else {
+          // check for here documents
+          pl->count = -1;
+          continue;
+        }
+      }
 
-      // stop at EOL, else continue with new pipeline segment for )
+    // Did we hit end of line or ) outside a function declaration?
+    // ) is only saved at start of a statement, ends current statement
+    } else if (end == start || (arg->c && *start == ')' && pl->type!='f')) {
+      // function () needs both parentheses or neither
+      if (pl->type == 'f' && arg->c != 1 && arg->c != 3) {
+        s = "function(";
+        goto flush;
+      }
+
+      // "for" on its own line is an error.
+      if (arg->c == 1 && (unsigned long)ex>1 && !memcmp(ex, "do\0A", 4)) {
+        s = "newline";
+        goto flush;
+      }
+
+      // Stop at EOL. Discard blank pipeline segment, else end segment
       if (end == start) done++;
-      pl->count = -1;
-      last = 0;
+      if (!pl->type && !arg->c) free_pipeline(dlist_lpop(ppl));
+      else pl->count = -1;
 
       continue;
     }
 
-    // Save argument (strdup) and check for flow control
-    array_add(&arg->v, arg->c, s = xstrndup(start, end-start));
+    // Save word and check for flow control
+    arg_add(arg, s = xstrndup(start, end-start));
     start = end;
-    if (strchr(";|&", *s)) {
-      // flow control without a statement is an error
-      if (!arg->c) goto flush;
+
+    // Second half of case/esac parsing
+    if (i) {
+      // type 1 (128): case x [\n] in
+      if (pl->type==128) {
+        if (arg->c==2 && strchr("()|;&", *s)) goto flush;
+        if (arg->c==3) {
+          if (strcmp(s, "in")) goto flush;
+          pl->type = 1;
+          (pl = add_pl(ppl, &arg))->type = 129;
+        }
+
+        continue;
+
+      // type 2 (129): [;;] [(] pattern [|pattern...] )
+      } else {
+
+        // can't start with line break or ";;" or "case ? in ;;" without ")"
+        if (*s==';') {
+          if (arg->c>1 || (arg->c==1 && pl->prev->type==1)) goto flush;
+        } else pl->type = 2;
+        i = arg->c - (**arg->v==';' && arg->v[0][1]);
+        if (i==1 && !strcmp(s, "esac")) {
+          // esac right after "in" or ";;" ends block, fall through
+          if (arg->c>1) {
+            arg->v[1] = 0;
+            pl = add_pl(ppl, &arg);
+            arg_add(arg, s);
+          } else pl->type = 0;
+        } else {
+          if (arg->c>1) i -= *arg->v[1]=='(';
+          if (i>0 && ((i&1)==!!strchr("|)", *s) || strchr(";(", *s)))
+            goto flush;
+          if (*s=='&' || !strcmp(s, "||")) goto flush;
+          if (*s==')') pl = add_pl(ppl, &arg);
+
+          continue;
+        }
+      }
+    }
+
+    // Are we starting a new [function] name [()] definition
+    if (!pl->type || pl->type=='f') {
+      if (!pl->type && arg->c==1 && !strcmp(s, "function")) {
+        free(arg->v[--arg->c]);
+        arg->v[arg->c] = 0;
+        pl->type = 'f';
+        continue;
+      } else if (arg->c==2 && !strcmp(s, "(")) pl->type = 'f';
+    }
+
+    // one or both of [function] name[()]
+    if (pl->type=='f') {
+      if (arg->v[0][strcspn(*arg->v, "\"'`><;|&$")]) {
+        s = *arg->v;
+        goto flush;
+      }
+      if (arg->c == 2 && strcmp(s, "(")) goto flush;
+      if (arg->c == 3) {
+        if (strcmp(s, ")")) goto flush;
+        goto funky;
+      }
+
+      continue;
+
+    // is it a line break token?
+    } else if (strchr(";|&", *s) && strncmp(s, "&>", 2)) {
+      arg->c--;
 
       // treat ; as newline so we don't have to check both elsewhere.
       if (!strcmp(s, ";")) {
         arg->v[arg->c] = 0;
         free(s);
         s = 0;
+// TODO can't have ; between "for i" and in or do. (Newline yes, ; no. Why?)
+        if (!arg->c && (unsigned long)ex>1 && !memcmp(ex, "do\0C", 4)) continue;
 
       // ;; and friends only allowed in case statements
-      } else if (*s == ';' && (!ex || strcmp(ex, "esac"))) goto flush;
-      last = s;
+      } else if (*s == ';') goto flush;
+
+      // flow control without a statement is an error
+      if (!arg->c) goto flush;
       pl->count = -1;
 
       continue;
-    } else arg->v[++arg->c] = 0;
-
-    // is a function() in progress?
-    if (arg->c>1 && !strcmp(s, "(")) pl->type = 'f';
-    if (pl->type=='f') {
-      if (arg->c == 2 && strcmp(s, "(")) goto flush;
-      if (arg->c == 3) {
-        if (strcmp(s, ")")) goto flush;
-
-        // end function segment, expect function body
-        pl->count = -1;
-        last = 0;
-        dlist_add(&sp->expect, "}");
-        dlist_add(&sp->expect, 0);
-        dlist_add(&sp->expect, "{");
-
-        continue;
-      }
 
     // a for/select must have at least one additional argument on same line
-    } else if (ex && !memcmp(ex, "do\0A", 4)) {
+    } else if ((unsigned long)ex>1 && !memcmp(ex, "do\0A", 4)) {
 
       // Sanity check and break the segment
-      if (strncmp(s, "((", 2) && strchr(s, '=')) goto flush;
+      if (strncmp(s, "((", 2) && *varend(s)) goto flush;
       pl->count = -1;
-      sp->expect->prev->data = "do\0C";
+      (*expect)->prev->data = "do\0C";
 
       continue;
 
@@ -1309,23 +2786,16 @@
     } else if (arg->c>1) continue;
 
     // Do we expect something that _must_ come next? (no multiple statements)
-    if (ex) {
-      // When waiting for { it must be next symbol, but can be on a new line.
-      if (!strcmp(ex, "{")) {
-        if (strcmp(s, "{")) goto flush;
-        free(arg->v[--arg->c]);  // don't save the {, function starts the block
-        free(dlist_lpop(&sp->expect));
-
-        continue;
-
+    if ((unsigned long)ex>1) {
       // The "test" part of for/select loops can have (at most) one "in" line,
       // for {((;;))|name [in...]} do
-      } else if (!memcmp(ex, "do\0C", 4)) {
+      if (!memcmp(ex, "do\0C", 4)) {
         if (strcmp(s, "do")) {
           // can only have one "in" line between for/do, but not with for(())
-          if (!pl->prev->type) goto flush;
+          if (pl->prev->type == 's') goto flush;
           if (!strncmp(pl->prev->arg->v[1], "((", 2)) goto flush;
           else if (strcmp(s, "in")) goto flush;
+          pl->type = 's';
 
           continue;
         }
@@ -1334,10 +2804,11 @@
 
     // start of a new block?
 
-    // for/select requires variable name on same line, can't break segment yet
-    if (!strcmp(s, "for") || !strcmp(s, "select")) {
-      if (!pl->type) pl->type = 1;
-      dlist_add(&sp->expect, "do\0A");
+    // for/select/case require var name on same line, can't break segment yet
+    if (!strcmp(s, "for") || !strcmp(s, "select") || !strcmp(s, "case")) {
+// TODO why !pl->type here
+      if (!pl->type) pl->type = (*s == 'c') ? 128 : 1;
+      dlist_add(expect, (*s == 'c') ? "esac" : "do\0A");
 
       continue;
     }
@@ -1345,35 +2816,44 @@
     end = 0;
     if (!strcmp(s, "if")) end = "then";
     else if (!strcmp(s, "while") || !strcmp(s, "until")) end = "do\0B";
-    else if (!strcmp(s, "case")) end = "esac";
     else if (!strcmp(s, "{")) end = "}";
     else if (!strcmp(s, "[[")) end = "]]";
     else if (!strcmp(s, "(")) end = ")";
 
     // Expecting NULL means a statement: I.E. any otherwise unrecognized word
-    else if (sp->expect && !ex) {
-      free(dlist_lpop(&sp->expect));
-      continue;
-    } else if (!ex) goto check;
+    if (!ex && *expect) free(dlist_lpop(expect));
 
-    // Did we start a new statement?
+    // Did we start a new statement
     if (end) {
       pl->type = 1;
 
       // Only innermost statement needed in { { { echo ;} ;} ;} and such
-      if (sp->expect && !sp->expect->prev->data) free(dlist_lpop(&sp->expect));
+      if (*expect && !(*expect)->prev->data) free(dlist_lpop(expect));
+
+    // if can't end a statement here skip next few tests
+    } else if ((unsigned long)ex<2);
 
     // If we got here we expect a specific word to end this block: is this it?
-    } else if (!strcmp(s, ex)) {
+    else if (!strcmp(s, ex)) {
       // can't "if | then" or "while && do", only ; & or newline works
-      if (last && (strcmp(ex, "then") || strcmp(last, "&"))) {
-        s = end;
-        goto flush;
-      }
+      if (strcmp(pl->prev->arg->v[pl->prev->arg->c] ? : "&", "&")) goto flush;
 
-      free(dlist_lpop(&sp->expect));
-      pl->type = anystr(s, (char *[]){"fi", "done", "esac", "}", "]]", ")", 0})
-        ? 3 : 2;
+      // consume word, record block end location in earlier !0 type blocks
+      free(dlist_lpop(expect));
+      if (3 == (pl->type = anystr(s, tails) ? 3 : 2)) {
+        for (i = 0, pl2 = pl3 = pl; (pl2 = pl2->prev);) {
+          if (pl2->type == 3) i++;
+          else if (pl2->type) {
+            if (!i) {
+              if (pl2->type == 2) {
+                pl2->end = pl3;
+                pl3 = pl2;
+              } else pl2->end = pl;
+            }
+            if (pl2->type == 1 && --i<0) break;
+          }
+        }
+      }
 
       // if it's a multipart block, what comes next?
       if (!strcmp(s, "do")) end = "done";
@@ -1382,7 +2862,7 @@
     // fi could have elif, which queues a then.
     } else if (!strcmp(ex, "fi")) {
       if (!strcmp(s, "elif")) {
-        free(dlist_lpop(&sp->expect));
+        free(dlist_lpop(expect));
         end = "then";
       // catch duplicate else while we're here
       } else if (!strcmp(s, "else")) {
@@ -1390,20 +2870,20 @@
           s = "2 else";
           goto flush;
         }
-        free(dlist_lpop(&sp->expect));
+        free(dlist_lpop(expect));
         end = "fi\0B";
       }
     }
 
-    // Do we need to queue up the next thing to expect?
+    // Queue up the next thing to expect, all preceded by a statement
     if (end) {
       if (!pl->type) pl->type = 2;
-      dlist_add(&sp->expect, end);
-      dlist_add(&sp->expect, 0);    // they're all preceded by a statement
+
+      dlist_add(expect, end);
+      if (!anystr(end, tails)) dlist_add(expect, 0);
       pl->count = -1;
     }
 
-check:
     // syntax error check: these can't be the first word in an unexpected place
     if (!pl->type && anystr(s, (char *[]){"then", "do", "esac", "}", "]]", ")",
         "done", "fi", "elif", "else", 0})) goto flush;
@@ -1411,79 +2891,112 @@
   free(delete);
 
   // ignore blank and comment lines
-  if (!sp->pipeline) return 0;
+  if (!*ppl) return 0;
 
 // TODO <<< has no parsing impact, why play with it here at all?
   // advance past <<< arguments (stored as here documents, but no new input)
-  pl = sp->pipeline->prev;
+  pl = (*ppl)->prev;
   while (pl->count<pl->here && pl->arg[pl->count].c<0)
     pl->arg[pl->count++].c = 0;
 
   // return if HERE document pending or more flow control needed to complete
-  if (sp->expect) return 1;
-  if (sp->pipeline && pl->count != pl->here) return 1;
-  if (pl->arg->v[pl->arg->c]) return 1;
+  if (*expect) return 1;
+  if (*ppl && pl->count != pl->here) return 1;
+  if (pl->arg->v[pl->arg->c] && strcmp(pl->arg->v[pl->arg->c], "&")) return 1;
 
   // Don't need more input, can start executing.
 
-  dlist_terminate(sp->pipeline);
+  dlist_terminate(*ppl);
   return 0;
 
 flush:
-  if (s) syntax_err("bad %s", s);
-  free_function(sp);
+  if (s) syntax_err(s);
+  llist_traverse(*ppl, free_pipeline);
+  *ppl = 0;
+  while (*expect) {
+    struct double_list *del = dlist_pop(expect);
+
+    if (del->data != (void *)1) free(del->data);
+    free(del);
+  }
+  *expect = 0;
 
   return 0-!!s;
 }
 
-static void dump_state(struct sh_function *sp)
+// Find + and - jobs. Returns index of plus, writes minus to *minus
+int find_plus_minus(int *minus)
 {
-  struct sh_pipeline *pl;
-  int q = 0;
-  long i;
+  long long when, then;
+  int i, plus;
 
-  if (sp->expect) {
-    struct double_list *dl;
-
-    for (dl = sp->expect; dl; dl = (dl->next == sp->expect) ? 0 : dl->next)
-      dprintf(255, "expecting %s\n", dl->data);
-    if (sp->pipeline)
-      dprintf(255, "pipeline count=%d here=%d\n", sp->pipeline->prev->count,
-        sp->pipeline->prev->here);
-  }
-
-  for (pl = sp->pipeline; pl ; pl = (pl->next == sp->pipeline) ? 0 : pl->next) {
-    for (i = 0; i<pl->arg->c; i++)
-      dprintf(255, "arg[%d][%ld]=%s\n", q, i, pl->arg->v[i]);
-    if (pl->arg->c<0) dprintf(255, "argc=%d\n", pl->arg->c);
-    else dprintf(255, "type=%d term[%d]=%s\n", pl->type, q++, pl->arg->v[pl->arg->c]);
-  }
-}
-
-void dump_filehandles(char *when)
-{
-  int fd = open("/proc/self/fd", O_RDONLY);
-  DIR *dir = fdopendir(fd);
-  char buf[256];
-
-  if (dir) {
-    struct dirent *dd;
-
-    while ((dd = readdir(dir))) {
-      if (atoi(dd->d_name)!=fd && 0<readlinkat(fd, dd->d_name, buf,sizeof(buf)))
-        dprintf(2, "OPEN %s %d: %s = %s\n", when, getpid(), dd->d_name, buf);
+  if (minus) *minus = 0;
+  for (then = i = plus = 0; i<TT.jobs.c; i++) {
+    if ((when = ((struct sh_process *)TT.jobs.v[i])->when) > then) {
+      then = when;
+      if (minus) *minus = plus;
+      plus = i;
     }
-    closedir(dir);
   }
-  close(fd);
+
+  return plus;
 }
 
-/* Flow control statements:
+char is_plus_minus(int i, int plus, int minus)
+{
+  return (i == plus) ? '+' : (i == minus) ? '-' : ' ';
+}
 
-  if/then/elif/else/fi, for select while until/do/done, case/esac,
-  {/}, [[/]], (/), function assignment
-*/
 
+// We pass in dash to avoid looping over every job each time
+char *show_job(struct sh_process *pp, char dash)
+{
+  char *s = "Run", *buf = 0;
+  int i, j, len, len2;
+
+// TODO Terminated (Exited)
+  if (pp->exit<0) s = "Stop";
+  else if (pp->exit>126) s = "Kill";
+  else if (pp->exit>0) s = "Done";
+  for (i = len = len2 = 0;; i++) {
+    len += snprintf(buf, len2, "[%d]%c  %-6s", pp->job, dash, s);
+    for (j = 0; j<pp->raw->c; j++)
+      len += snprintf(buf, len2, " %s"+!j, pp->raw->v[j]);
+    if (!i) buf = xmalloc(len2 = len+1);
+    else break;
+  }
+
+  return buf;
+}
+
+// Wait for pid to exit and remove from jobs table, returning process or 0.
+struct sh_process *wait_job(int pid, int nohang)
+{
+  struct sh_process *pp;
+  int ii, status, minus, plus;
+
+  if (TT.jobs.c<1) return 0;
+  for (;;) {
+    errno = 0;
+    if (1>(pid = waitpid(pid, &status, nohang ? WNOHANG : 0))) {
+      if (!nohang && errno==EINTR && !toys.signal) continue;
+      return 0;
+    }
+    for (ii = 0; ii<TT.jobs.c; ii++) {
+      pp = (void *)TT.jobs.v[ii];
+      if (pp->pid == pid) break;
+    }
+    if (ii == TT.jobs.c) continue;
+    if (pid<1) return 0;
+    if (!WIFSTOPPED(status) && !WIFCONTINUED(status)) break;
+  }
+  plus = find_plus_minus(&minus);
+  memmove(TT.jobs.v+ii, TT.jobs.v+ii+1, (TT.jobs.c--)-ii);
+  pp->exit = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128;
+  pp->dash = is_plus_minus(ii, plus, minus);
+
+  return pp;
+}
 
 // wait for every process in a pipeline to end
 static int wait_pipeline(struct sh_process *pp)
@@ -1497,321 +3010,16 @@
       pp->pid = 0;
     }
     // TODO handle set -o pipefail here
-    rc = pp->exit;
+    rc = pp->not ? !pp->exit : pp->exit;
   }
 
-  return rc;
-}
+  while ((pp = wait_job(-1, 1)) && (TT.options&FLAG_i)) {
+    char *s = show_job(pp, pp->dash);
 
-// pipe data into and out of this segment, I.E. handle leading and trailing |
-static int pipe_segments(char *ctl, int *pipes, int **urd)
-{
-  unredirect(*urd);
-  *urd = 0;
-
-  // Did the previous pipe segment pipe input into us?
-  if (*pipes != -1) {
-    save_redirect(urd, *pipes, 0);
-    close(*pipes);
-    *pipes = -1;
+    dprintf(2, "%s\n", s);
+    free(s);
   }
 
-  // are we piping output to the next segment?
-  if (ctl && *ctl == '|' && ctl[1] != '|') {
-    if (pipe(pipes)) {
-      perror_msg("pipe");
-// TODO record pipeline rc
-// TODO check did not reach end of pipeline after loop
-      return 1;
-    }
-    if (pipes[1] != 1) {
-      save_redirect(urd, pipes[1], 1);
-      close(pipes[1]);
-    }
-    fcntl(*pipes, F_SETFD, FD_CLOEXEC);
-  }
-
-  return 0;
-}
-
-// Handle && and || traversal in pipeline segments
-static struct sh_pipeline *skip_andor(int rc, struct sh_pipeline *pl)
-{
-  char *ctl = pl->arg->v[pl->arg->c];
-
-  // For && and || skip pipeline segment(s) based on return code
-  while (ctl && ((!strcmp(ctl, "&&") && rc) || (!strcmp(ctl, "||") && !rc))) {
-    if (!pl->next || pl->next->type == 2 || pl->next->type == 3) break;
-    pl = pl->type ? block_end(pl) : pl->next;
-    ctl = pl ? pl->arg->v[pl->arg->c] : 0;
-  }
-
-  return pl;
-}
-
-// run a parsed shell function. Handle flow control blocks and characters,
-// setup pipes and block redirection, break/continue, call builtins,
-// vfork/exec external commands.
-static void run_function(struct sh_pipeline *pl)
-{
-  struct sh_pipeline *end;
-  struct blockstack {
-    struct blockstack *next;
-    struct sh_pipeline *start, *end;
-    struct sh_process *pin;      // processes piping into this block
-    int run, loop, *urd, pout;
-    struct sh_arg farg;          // for/select arg stack
-    struct arg_list *fdelete; // farg's cleanup list
-    char *fvar;                  // for/select's iteration variable name
-  } *blk = 0, *new;
-  struct sh_process *pplist = 0; // processes piping into current level
-  int *urd = 0, pipes[2] = {-1, -1};
-  long i;
-
-// TODO can't free sh_process delete until ready to dispose else no debug output
-
-  // iterate through pipeline segments
-  while (pl) {
-    struct sh_arg *arg = pl->arg;
-    char *s = *arg->v, *ss = arg->v[1], *ctl = arg->v[arg->c];
-if (BUGBUG) dprintf(255, "%d runtype=%d %s %s\n", getpid(), pl->type, s, ctl);
-    // Is this an executable segment?
-    if (!pl->type) {
-
-      // Skip disabled block
-      if (blk && !blk->run) {
-        while (pl->next && !pl->next->type) pl = pl->next;
-        continue;
-      }
-      if (pipe_segments(ctl, pipes, &urd)) break;
-
-      // If we just started a new pipeline, implicit parentheses (subshell)
-
-// TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
-//       probably have to inline run_command here to do that? Implicit ()
-//       also "X=42 | true; echo $X" doesn't get X.
-
-      // TODO: bash supports "break &" and "break > file". No idea why.
-
-      // Is it a flow control jump? These aren't handled as normal builtins
-      // because they move *pl to other pipeline segments which is local here.
-      if (!strcmp(s, "break") || !strcmp(s, "continue")) {
-
-        // How many layers to peel off?
-        i = ss ? atol(ss) : 0;
-        if (i<1) i = 1;
-        if (!blk || arg->c>2 || ss[strspn(ss, "0123456789")]) {
-          syntax_err("bad %s", s);
-          break;
-        }
-        i = atol(ss);
-        if (!i) i++;
-        while (i && blk) {
-          if (--i && *s == 'c') {
-            pl = blk->start;
-            break;
-          }
-          pl = blk->end;
-// TODO collate end_block logic
-
-          // if ending a block, free, cleanup redirects and pop stack.
-          llist_traverse(blk->fdelete, free);
-          unredirect(blk->urd);
-          if (*pipes) close(*pipes);
-          *pipes = blk->pout;
-          free(llist_pop(&blk));
-        }
-        if (i) {
-          syntax_err("break outside loop");
-          break;
-        }
-        pl = pl->next;
-        continue;
-
-      // Parse and run next command
-      } else {
-
-// TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
-//       probably have to inline run_command here to do that? Implicit ()
-//       also "X=42 | true; echo $X" doesn't get X.
-//       I.E. run_subshell() here sometimes? (But when?)
-
-        dlist_add_nomalloc((void *)&pplist, (void *)run_command(arg));
-      }
-
-      if (*pipes == -1) {
-        toys.exitval = wait_pipeline(pplist);
-        llist_traverse(pplist, free_process);
-        pplist = 0;
-        pl = skip_andor(toys.exitval, pl);
-      }
-
-    // Start of flow control block?
-    } else if (pl->type == 1) {
-      struct sh_process *pp = 0;
-
-      // are we entering this block (rather than looping back to it)?
-      if (!blk || blk->start != pl) {
-
-        // If it's a nested block we're not running, skip ahead.
-        end = block_end(pl->next);
-        if (blk && !blk->run) {
-          pl = end;
-          if (pl) pl = pl->next;
-          continue;
-        }
-
-        // If previous piped into this block, save context until block end
-        if (pipe_segments(end->arg->v[end->arg->c], pipes, &urd)) break;
-
-        // It's a new block we're running, save context and add it to the stack.
-        new = xzalloc(sizeof(*blk));
-        new->next = blk;
-        blk = new;
-        blk->start = pl;
-        blk->end = end;
-        blk->run = 1;
-
-        // save context until block end
-        blk->pout = *pipes;
-        blk->urd = urd;
-        urd = 0;
-        *pipes = -1;
-
-        // Perform redirects listed at end of block
-        pp = expand_redir(end->arg, 1, blk->urd);
-        blk->urd = pp->urd;
-        if (pp->arg.c) {
-// TODO this is a syntax_error
-          perror_msg("unexpected %s", *pp->arg.v);
-          llist_traverse(pp->delete, free);
-          free(pp);
-          break;
-        }
-      }
-
-      // What flow control statement is this?
-
-      // {/} if/then/elif/else/fi, while until/do/done - no special handling
-
-      // for select/do/done
-      if (!strcmp(s, "for") || !strcmp(s, "select")) {
-        if (blk->loop);
-        else if (!strncmp(blk->fvar = ss, "((", 2)) {
-          blk->loop = 1;
-dprintf(2, "TODO skipped init for((;;)), need math parser\n");
-        } else {
-
-          // populate blk->farg with expanded arguments
-          if (!pl->next->type) {
-            for (i = 1; i<pl->next->arg->c; i++)
-              expand_arg(&blk->farg, pl->next->arg->v[i], 0, &blk->fdelete);
-          } else expand_arg(&blk->farg, "\"$@\"", 0, &blk->fdelete);
-        }
-
-// TODO case/esac [[/]] (/) ((/)) function/}
-
-/*
-TODO: a | b | c needs subshell for builtins?
-        - anything that can produce output
-        - echo declare dirs
-      (a; b; c) like { } but subshell
-      when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)'
-*/
-
-      // subshell
-      } else if (!strcmp(s, "(")) {
-        if (!CFG_TOYBOX_FORK) {
-          ss = pl2str(pl->next);
-          pp->pid = run_subshell(ss, strlen(ss));
-          free(ss);
-        } else {
-          if (!(pp->pid = fork())) {
-            run_function(pl->next);
-            _exit(toys.exitval);
-          }
-        }
-
-        dlist_add_nomalloc((void *)&pplist, (void *)pp);
-        pl = blk->end->prev;
-      }
-
-    // gearshift from block start to block body (end of flow control test)
-    } else if (pl->type == 2) {
-
-      // Handle if statement
-      if (!strcmp(s, "then")) blk->run = blk->run && !toys.exitval;
-      else if (!strcmp(s, "else") || !strcmp(s, "elif")) blk->run = !blk->run;
-      else if (!strcmp(s, "do")) {
-        ss = *blk->start->arg->v;
-        if (!strcmp(ss, "while")) blk->run = blk->run && !toys.exitval;
-        else if (!strcmp(ss, "until")) blk->run = blk->run && toys.exitval;
-        else if (blk->loop >= blk->farg.c) {
-          blk->run = 0;
-          pl = block_end(pl);
-          continue;
-        } else if (!strncmp(blk->fvar, "((", 2)) {
-dprintf(2, "TODO skipped running for((;;)), need math parser\n");
-        } else setvar(xmprintf("%s=%s", blk->fvar, blk->farg.v[blk->loop++]),
-          TAKE_MEM);
-      }
-
-    // end of block, may have trailing redirections and/or pipe
-    } else if (pl->type == 3) {
-
-      // if we end a block we're not in, we started in a block.
-      if (!blk) break;
-
-      // repeating block?
-      if (blk->run && !strcmp(s, "done")) {
-        pl = blk->start;
-        continue;
-      }
-
-// TODO goto "break" above instead of copying it here?
-      // if ending a block, free, cleanup redirects, and pop stack.
-      // needing to unredirect(urd) or close(pipes[0]) here would be syntax err
-      llist_traverse(blk->fdelete, free);
-      unredirect(blk->urd);
-      *pipes = blk->pout;
-      free(llist_pop(&blk));
-    } else if (pl->type == 'f') pl = add_function(s, pl);
-
-    pl = pl->next;
-  }
-
-  // did we exit with unfinished stuff?
-  if (*pipes != -1) close(*pipes);
-  if (pplist) {
-    toys.exitval = wait_pipeline(pplist);
-    llist_traverse(pplist, free_process);
-  }
-  unredirect(urd);
-
-  // Cleanup from syntax_err();
-  while (blk) {
-    llist_traverse(blk->fdelete, free);
-    unredirect(blk->urd);
-    free(llist_pop(&blk));
-  }
-
-  return;
-}
-
-// Parse and run a self-contained command line with no prompt/continuation
-static int sh_run(char *new)
-{
-  struct sh_function scratch;
-  int rc;
-
-// TODO switch the fmemopen for -c to use this? Error checking? $(blah)
-
-  memset(&scratch, 0, sizeof(struct sh_function));
-  if (!parse_line(new, &scratch)) run_function(scratch.pipeline);
-  free_function(&scratch);
-  rc = toys.exitval;
-  toys.exitval = 0;
-
   return rc;
 }
 
@@ -1822,14 +3030,14 @@
   char *s, *ss, c, cc, *pp = toybuf;
   int len, ll;
 
-  if (!prompt) prompt = "\\$ ";
+  if (!prompt) return;
   while ((len = sizeof(toybuf)-(pp-toybuf))>0 && *prompt) {
     c = *(prompt++);
 
     if (c=='!') {
       if (*prompt=='!') prompt++;
       else {
-        pp += snprintf(pp, len, "%ld", TT.lineno);
+        pp += snprintf(pp, len, "%u", TT.LINENO);
         continue;
       }
     } else if (c=='\\') {
@@ -1875,116 +3083,644 @@
   writeall(2, toybuf, len);
 }
 
-// only set local variable when global not present
-static void setonlylocal(char ***to, char *name, char *val)
+// returns NULL for EOF, 1 for invalid, else null terminated string.
+static char *get_next_line(FILE *ff, int prompt)
 {
-  if (getenv(name)) return;
-  *(*to)++ = xmprintf("%s=%s", name, val ? val : "");
+  char *new;
+  int len, cc;
+
+  if (!ff) {
+    char ps[16];
+
+    sprintf(ps, "PS%d", prompt);
+    do_prompt(getvar(ps));
+  }
+
+// TODO what should ctrl-C do? (also in "select")
+// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
+//  TODO: after first EINTR returns closed?
+// TODO: ctrl-z during script read having already read partial line,
+// SIGSTOP and SIGTSTP need SA_RESTART, but child proc should stop
+// TODO if (!isspace(*new)) add_to_history(line);
+
+  for (new = 0, len = 0;;) {
+    errno = 0;
+    if (!(cc = getc(ff ? : stdin))) {
+      if (TT.LINENO) continue;
+      free(new);
+      return (char *)1;
+    }
+    if (cc<0) {
+      if (errno == EINTR) continue;
+      break;
+    }
+    if (!(len&63)) new = xrealloc(new, len+65);
+    if (cc == '\n') break;
+    new[len++] = cc;
+  }
+  if (new) new[len] = 0;
+
+  return new;
+}
+
+/*
+ TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
+       probably have to inline run_command here to do that? Implicit ()
+       also "X=42 | true; echo $X" doesn't get X.
+       I.E. run_subshell() here sometimes? (But when?)
+ TODO: bash supports "break &" and "break > file". No idea why.
+ TODO If we just started a new pipeline, implicit parentheses (subshell)
+ TODO can't free sh_process delete until ready to dispose else no debug output
+ TODO: a | b | c needs subshell for builtins?
+        - anything that can produce output
+        - echo declare dirs
+      (a; b; c) like { } but subshell
+      when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)'
+*/
+
+// run a parsed shell function. Handle flow control blocks and characters,
+// setup pipes and block redirection, break/continue, call builtins, functions,
+// vfork/exec external commands. Return when out of input.
+static void run_lines(void)
+{
+  char *ctl, *s, *ss, **vv;
+  struct sh_process *pp, *pplist = 0; // processes piping into current level
+  long i, j, k;
+
+  // iterate through pipeline segments
+  for (;;) {
+    if (!TT.ff->pl) {
+      if (!end_function(1)) break;
+      goto advance;
+    }
+
+    ctl = TT.ff->pl->end->arg->v[TT.ff->pl->end->arg->c];
+    s = *TT.ff->pl->arg->v;
+    ss = TT.ff->pl->arg->v[1];
+//dprintf(2, "%d s=%s ss=%s ctl=%s type=%d pl=%p ff=%p\n", getpid(), (TT.ff->pl->type == 'F') ? ((struct sh_function *)s)->name : s, ss, ctl, TT.ff->pl->type, TT.ff->pl, TT.ff);
+    if (!pplist) TT.hfd = 10;
+
+    // Skip disabled blocks, handle pipes and backgrounding
+    if (TT.ff->pl->type<2) {
+      if (!TT.ff->blk->run) {
+        TT.ff->pl = TT.ff->pl->end->next;
+
+        continue;
+      }
+
+      if (TT.options&OPT_x) {
+        unsigned lineno;
+        char *ss, *ps4 = getvar("PS4");
+
+        // duplicate first char of ps4 call depth times
+        if (ps4 && *ps4) {
+          j = getutf8(ps4, k = strlen(ps4), 0);
+          ss = xmalloc(TT.srclvl*j+k+1);
+          for (k = 0; k<TT.srclvl; k++) memcpy(ss+k*j, ps4, j);
+          strcpy(ss+k*j, ps4+j);
+          // show saved line number from function, not next to read
+          lineno = TT.LINENO;
+          TT.LINENO = TT.ff->pl->lineno;
+          do_prompt(ss);
+          TT.LINENO = lineno;
+          free(ss);
+
+          // TODO resolve variables
+          ss = pl2str(TT.ff->pl, 1);
+          dprintf(2, "%s\n", ss);
+          free(ss);
+        }
+      }
+
+      // pipe data into and out of this segment, I.E. leading/trailing |
+      unredirect(TT.ff->blk->urd);
+      TT.ff->blk->urd = 0;
+      TT.ff->blk->pipe = 0;
+
+      // Consume pipe from previous segment as stdin.
+      if (TT.ff->blk->pout != -1) {
+        TT.ff->blk->pipe++;
+        if (save_redirect(&TT.ff->blk->urd, TT.ff->blk->pout, 0)) break;
+        close(TT.ff->blk->pout);
+        TT.ff->blk->pout = -1;
+      }
+
+      // Create output pipe and save next process's stdin in pout
+      if (ctl && *ctl == '|' && ctl[1] != '|') {
+        int pipes[2] = {-1, -1};
+
+        TT.ff->blk->pipe++;
+        if (pipe(pipes)) {
+          perror_msg("pipe");
+
+          break;
+        }
+        if (save_redirect(&TT.ff->blk->urd, pipes[1], 1)) {
+          close(pipes[0]);
+          close(pipes[1]);
+
+          break;
+        }
+        if (pipes[1] != 1) close(pipes[1]);
+        fcntl(TT.ff->blk->pout = *pipes, F_SETFD, FD_CLOEXEC);
+        if (ctl[1] == '&') save_redirect(&TT.ff->blk->urd, 1, 2);
+      }
+    }
+
+    // Is this an executable segment?
+    if (!TT.ff->pl->type) {
+      // Is it a flow control jump? These aren't handled as normal builtins
+      // because they move *pl to other pipeline segments which is local here.
+      if (!strcmp(s, "break") || !strcmp(s, "continue")) {
+
+        // How many layers to peel off?
+        i = ss ? atol(ss) : 0;
+        if (i<1) i = 1;
+        if (TT.ff->blk->next && TT.ff->pl->arg->c<3
+            && (!ss || !ss[strspn(ss,"0123456789")]))
+        {
+          while (i && TT.ff->blk->next)
+            if (TT.ff->blk->middle && !strcmp(*TT.ff->blk->middle->arg->v, "do")
+              && !--i && *s=='c') TT.ff->pl = TT.ff->blk->start;
+            else TT.ff->pl = pop_block();
+        }
+        if (i) {
+          syntax_err(s);
+          break;
+        }
+      // Parse and run next command, saving resulting process
+      } else if ((pp = run_command()))
+        dlist_add_nomalloc((void *)&pplist, (void *)pp);
+
+    // Start of flow control block?
+    } else if (TT.ff->pl->type == 1) {
+
+// TODO test cat | {thingy} is new PID: { is ( for |
+
+      // perform/save trailing redirects
+      pp = expand_redir(TT.ff->pl->end->arg, 1, TT.ff->blk->urd);
+      TT.ff->blk->urd = pp->urd;
+      pp->urd = 0;
+      if (pp->arg.c) syntax_err(*pp->arg.v);
+      llist_traverse(pp->delete, llist_free_arg);
+      pp->delete = 0;
+      if (pp->exit || pp->arg.c) {
+        free(pp);
+        toys.exitval = 1;
+
+        break;
+      }
+      add_block();
+
+// TODO test background a block: { abc; } &
+
+      // If we spawn a subshell, pass data off to child process
+      if (TT.ff->blk->pipe || !strcmp(s, "(") || (ctl && !strcmp(ctl, "&"))) {
+        if (!(pp->pid = run_subshell(0, -1))) {
+
+          // zap forked child's cleanup context and advance to next statement
+          pplist = 0;
+          while (TT.ff->blk->next) TT.ff->blk = TT.ff->blk->next;
+          TT.ff->blk->pout = -1;
+          TT.ff->blk->urd = 0;
+          TT.ff->pl = TT.ff->next->pl->next;
+
+          continue;
+        }
+        TT.ff->pl = TT.ff->pl->end;
+        pop_block();
+        dlist_add_nomalloc((void *)&pplist, (void *)pp);
+
+      // handle start of block in this process
+      } else {
+        free(pp);
+
+        // What flow control statement is this?
+
+        // {/} if/then/elif/else/fi, while until/do/done - no special handling
+
+        // for/select/do/done: populate blk->farg with expanded args (if any)
+        if (!strcmp(s, "for") || !strcmp(s, "select")) {
+          if (TT.ff->blk->loop);
+          else if (!strncmp(TT.ff->blk->fvar = ss, "((", 2)) {
+            TT.ff->blk->loop = 1;
+dprintf(2, "TODO skipped init for((;;)), need math parser\n");
+
+          // in LIST
+          } else if (TT.ff->pl->next->type == 's') {
+            for (i = 1; i<TT.ff->pl->next->arg->c; i++)
+              if (expand_arg(&TT.ff->blk->farg, TT.ff->pl->next->arg->v[i],
+                             0, &TT.ff->blk->fdelete)) break;
+            if (i != TT.ff->pl->next->arg->c) TT.ff->pl = pop_block();
+
+          // in without LIST. (This expansion can't return error.)
+          } else expand_arg(&TT.ff->blk->farg, "\"$@\"", 0,
+                            &TT.ff->blk->fdelete);
+
+          // TODO: ls -C style output
+          if (*s == 's') for (i = 0; i<TT.ff->blk->farg.c; i++)
+            dprintf(2, "%ld) %s\n", i+1, TT.ff->blk->farg.v[i]);
+
+        // TODO: bash man page says it performs <(process substituion) here?!?
+        } else if (!strcmp(s, "case")) {
+          TT.ff->blk->fvar = expand_one_arg(ss, NO_NULL, &TT.ff->blk->fdelete);
+          if (!TT.ff->blk->fvar) break;
+        }
+
+// TODO [[/]] ((/)) function/}
+      }
+
+    // gearshift from block start to block body (end of flow control test)
+    } else if (TT.ff->pl->type == 2) {
+      int match, err;
+
+      TT.ff->blk->middle = TT.ff->pl;
+
+      // ;; end, ;& continue through next block, ;;& test next block
+      if (!strcmp(*TT.ff->blk->start->arg->v, "case")) {
+        if (!strcmp(s, ";;")) {
+          while (TT.ff->pl->type!=3) TT.ff->pl = TT.ff->pl->end;
+          continue;
+        } else if (strcmp(s, ";&")) {
+          struct sh_arg arg = {0}, arg2 = {0};
+
+          for (err = 0, vv = 0;;) {
+            if (!vv) {
+              vv = TT.ff->pl->arg->v + (**TT.ff->pl->arg->v == ';');
+              if (!*vv) {
+                // TODO syntax err if not type==3, catch above
+                TT.ff->pl = TT.ff->pl->next;
+                break;
+              } else vv += **vv == '(';
+            }
+            arg.c = arg2.c = 0;
+            if ((err = expand_arg_nobrace(&arg, *vv++, NO_SPLIT,
+              &TT.ff->blk->fdelete, &arg2))) break;
+            s = arg.c ? *arg.v : "";
+            match = wildcard_match(TT.ff->blk->fvar, s, &arg2, 0);
+            if (match>=0 && !s[match]) break;
+            else if (**vv++ == ')') {
+              vv = 0;
+              if ((TT.ff->pl = TT.ff->pl->end)->type!=2) break;
+            }
+          }
+          free(arg.v);
+          free(arg2.v);
+          if (err) break;
+          if (TT.ff->pl->type==3) continue;
+        }
+
+      // Handle if/else/elif statement
+      } else if (!strcmp(s, "then"))
+        TT.ff->blk->run = TT.ff->blk->run && !toys.exitval;
+      else if (!strcmp(s, "else") || !strcmp(s, "elif"))
+        TT.ff->blk->run = !TT.ff->blk->run;
+
+      // Loop
+      else if (!strcmp(s, "do")) {
+        struct sh_blockstack *blk = TT.ff->blk;
+
+        ss = *blk->start->arg->v;
+        if (!strcmp(ss, "while")) blk->run = blk->run && !toys.exitval;
+        else if (!strcmp(ss, "until")) blk->run = blk->run && toys.exitval;
+        else if (!strcmp(ss, "select")) {
+          if (!(ss = get_next_line(0, 3)) || ss==(void *)1) {
+            TT.ff->pl = pop_block();
+            printf("\n");
+          } else {
+            match = atoi(ss);
+            free(ss);
+            if (!*ss) {
+              TT.ff->pl = blk->start;
+              continue;
+            } else setvarval(blk->fvar, (match<1 || match>blk->farg.c)
+                                        ? "" : blk->farg.v[match-1]);
+          }
+        } else if (blk->loop >= blk->farg.c) TT.ff->pl = pop_block();
+        else if (!strncmp(blk->fvar, "((", 2)) {
+dprintf(2, "TODO skipped running for((;;)), need math parser\n");
+        } else setvarval(blk->fvar, blk->farg.v[blk->loop++]);
+      }
+
+    // end of block
+    } else if (TT.ff->pl->type == 3) {
+      // If we end a block we're not in, exit subshell
+      if (!TT.ff->blk->next) xexit();
+
+      // repeating block?
+      if (TT.ff->blk->run && !strcmp(s, "done")) {
+        TT.ff->pl = TT.ff->blk->middle;
+        continue;
+      }
+
+      // cleans up after trailing redirections/pipe
+      pop_block();
+
+    // declare a shell function
+    } else if (TT.ff->pl->type == 'F') {
+      struct sh_function *funky = (void *)*TT.ff->pl->arg->v;
+
+// TODO binary search
+      for (i = 0; i<TT.funcslen; i++)
+        if (!strcmp(TT.functions[i]->name, funky->name)) break;
+      if (i == TT.funcslen) {
+        struct sh_arg arg = {(void *)TT.functions, TT.funcslen};
+
+        arg_add(&arg, (void *)funky); // TODO possibly an expand@31 function?
+        TT.functions = (void *)arg.v;
+        TT.funcslen++;
+      } else {
+        free_function(TT.functions[i]);
+        TT.functions[i] = funky;
+      }
+      TT.functions[i]->refcount++;
+    }
+
+    // Three cases: 1) background & 2) pipeline | 3) last process in pipeline ;
+    // If we ran a process and didn't pipe output, background or wait for exit
+    if (pplist && TT.ff->blk->pout == -1) {
+      if (ctl && !strcmp(ctl, "&")) {
+        if (!TT.jobs.c) TT.jobcnt = 0;
+        pplist->job = ++TT.jobcnt;
+        arg_add(&TT.jobs, (void *)pplist);
+        if (TT.options&FLAG_i) dprintf(2, "[%u] %u\n", pplist->job,pplist->pid);
+      } else {
+        toys.exitval = wait_pipeline(pplist);
+        llist_traverse(pplist, (void *)free_process);
+      }
+      pplist = 0;
+    }
+advance:
+    // for && and || skip pipeline segment(s) based on return code
+    if (!TT.ff->pl->type || TT.ff->pl->type == 3) {
+      for (;;) {
+        ctl = TT.ff->pl->arg->v[TT.ff->pl->arg->c];
+        if (!ctl || strcmp(ctl, toys.exitval ? "&&" : "||")) break;
+        if ((TT.ff->pl = TT.ff->pl->next)->type) TT.ff->pl = TT.ff->pl->end;
+      }
+    }
+    TT.ff->pl = TT.ff->pl->next;
+  }
+
+  // clean up any unfinished stuff
+  if (pplist) {
+    toys.exitval = wait_pipeline(pplist);
+    llist_traverse(pplist, (void *)free_process);
+  }
+
+  // exit source context (and function calls on syntax err)
+  while (end_function(0));
+}
+
+// set variable
+static struct sh_vars *initvar(char *name, char *val)
+{
+  return addvar(xmprintf("%s=%s", name, val ? val : ""), TT.ff);
+}
+
+static struct sh_vars *initvardef(char *name, char *val, char *def)
+{
+  return initvar(name, (!val || !*val) ? def : val);
+}
+
+// export existing "name" or assign/export name=value string (making new copy)
+static void export(char *str)
+{
+  struct sh_vars *shv = 0;
+  char *s;
+
+  // Make sure variable exists and is updated
+  if (strchr(str, '=')) shv = setvar(xstrdup(str));
+  else if (!(shv = findvar(str, 0))) {
+    shv = addvar(str = xmprintf("%s=", str), TT.ff->prev);
+    shv->flags = VAR_WHITEOUT;
+  } else if (shv->flags&VAR_WHITEOUT) shv->flags |= VAR_GLOBAL;
+  if (!shv || (shv->flags&VAR_GLOBAL)) return;
+
+  // Resolve magic for export (bash bug compatibility, really should be dynamic)
+  if (shv->flags&VAR_MAGIC) {
+    s = shv->str;
+    shv->str = xmprintf("%.*s=%s", (int)(varend(str)-str), str, getvar(str));
+    free(s);
+  }
+  shv->flags |= VAR_GLOBAL;
+}
+
+static void unexport(char *str)
+{
+  struct sh_vars *shv = findvar(str, 0);
+
+  if (shv) shv->flags &=~VAR_GLOBAL;
+  if (strchr(str, '=')) setvar(str);
+}
+
+FILE *fpathopen(char *name)
+{
+  struct string_list *sl = 0;
+  FILE *f = fopen(name, "r");
+  char *pp = getvar("PATH") ? : _PATH_DEFPATH;
+
+  if (!f) {
+    for (sl = find_in_path(pp, name); sl; free(llist_pop(&sl)))
+      if ((f = fopen(sl->str, "r"))) break;
+    if (sl) llist_traverse(sl, free);
+  }
+
+  return f;
+}
+
+// Read script input and execute lines, with or without prompts
+int do_source(char *name, FILE *ff)
+{
+  struct sh_pipeline *pl = 0;
+  struct double_list *expect = 0;
+  unsigned lineno = TT.LINENO, more = 0;
+  int cc, ii;
+  char *new;
+
+  if (++TT.recursion>(50+200*CFG_TOYBOX_FORK)) {
+    error_msg("recursive occlusion");
+
+    goto end;
+  }
+
+// TODO fix/catch NONBLOCK on input?
+// TODO when DO we reset lineno? (!LINENO means \0 returns 1)
+// when do we NOT reset lineno? Inherit but preserve perhaps? newline in $()?
+  if (!name) TT.LINENO = 0;
+
+  do {
+    if ((void *)1 == (new = get_next_line(ff, more+1))) goto is_binary;
+//dprintf(2, "%d getline from %p %s\n", getpid(), ff, new); debug_show_fds();
+    // did we exec an ELF file or something?
+    if (!TT.LINENO++ && name && new) {
+      wchar_t wc;
+
+      // A shell script's first line has no high bytes that aren't valid utf-8.
+      for (ii = 0; new[ii] && 0<(cc = utf8towc(&wc, new+ii, 4)); ii += cc);
+      if (new[ii]) {
+is_binary:
+        if (name) error_msg("'%s' is binary", name); // TODO syntax_err() exit?
+        free(new);
+        new = 0;
+      }
+    }
+
+    // TODO: source <(echo 'echo hello\') vs source <(echo -n 'echo hello\')
+    // prints "hello" vs "hello\"
+
+    // returns 0 if line consumed, command if it needs more data
+    more = parse_line(new ? : " ", &pl, &expect);
+    free(new);
+    if (more==1) {
+      if (!new) {
+        if (!ff) syntax_err("unexpected end of file");
+      } else continue;
+    } else if (!more && pl) {
+      TT.ff->pl = pl;
+      run_lines();
+    } else more = 0;
+
+    llist_traverse(pl, free_pipeline);
+    pl = 0;
+    llist_traverse(expect, free);
+    expect = 0;
+  } while (new);
+
+  if (ff) fclose(ff);
+
+  if (!name) TT.LINENO = lineno;
+
+end:
+  TT.recursion--;
+
+  return more;
 }
 
 // init locals, sanitize environment, handle nommu subshell handoff
-void subshell_setup(void)
+static void subshell_setup(void)
 {
-  struct passwd *pw = getpwuid(getuid());
-  int to, from, pid = 0, ppid = 0, mypid, myppid, len;
-  char *s, *ss, **ll, *locals[] = {"GROUPS=", "SECONDS=", "RANDOM=", "LINENO=",
-    xmprintf("PPID=%d", myppid = getppid()), xmprintf("EUID=%d", geteuid()),
-    xmprintf("$=%d", mypid = getpid()), xmprintf("UID=%d", getuid())};
+  int ii, from, pid, ppid, zpid, myppid = getppid(), len, uid = getuid();
+  struct passwd *pw = getpwuid(uid);
+  char *s, *ss, *magic[] = {"SECONDS", "RANDOM", "LINENO", "GROUPS"},
+    *readonly[] = {xmprintf("EUID=%d", geteuid()), xmprintf("UID=%d", uid),
+                   xmprintf("PPID=%d", myppid)};
   struct stat st;
+  struct sh_vars *shv;
   struct utsname uu;
-  FILE *fp;
 
-  // Initialize read only local variables
-  TT.locals = xmalloc(32*sizeof(char *));
-  memcpy(TT.locals, locals, sizeof(locals));
-  ll = TT.locals+(TT.loc_ro = ARRAY_LEN(locals));
-  TT.loc_magic = 4;
+  // Create initial function context
+  call_function();
+  TT.ff->arg.v = toys.optargs;
+  TT.ff->arg.c = toys.optc;
+
+  // Initialize magic and read only local variables
+  srandom(TT.SECONDS = millitime());
+  for (ii = 0; ii<ARRAY_LEN(magic); ii++)
+    initvar(magic[ii], "")->flags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii]));
+  for (ii = 0; ii<ARRAY_LEN(readonly); ii++)
+    addvar(readonly[ii], TT.ff)->flags = VAR_READONLY|VAR_INT;
 
   // Add local variables that can be overwritten
-  setonlylocal(&ll, "PATH", _PATH_DEFPATH);
+  initvar("PATH", _PATH_DEFPATH);
   if (!pw) pw = (void *)toybuf; // first use, so still zeroed
-  setonlylocal(&ll, "HOME", *pw->pw_dir ? pw->pw_dir : "/");
-  setonlylocal(&ll, "SHELL", pw->pw_shell);
-  setonlylocal(&ll, "USER", pw->pw_name);
-  setonlylocal(&ll, "LOGNAME", pw->pw_name);
+  sprintf(toybuf+1024, "%u", uid);
+  initvardef("HOME", pw->pw_dir, "/");
+  initvardef("SHELL", pw->pw_shell, "/bin/sh");
+  initvardef("USER", pw->pw_name, toybuf+1024);
+  initvardef("LOGNAME", pw->pw_name, toybuf+1024);
   gethostname(toybuf, sizeof(toybuf)-1);
-  *ll++ = xmprintf("HOSTNAME=%s", toybuf);
+  initvar("HOSTNAME", toybuf);
   uname(&uu);
-  setonlylocal(&ll, "HOSTTYPE", uu.machine);
+  initvar("HOSTTYPE", uu.machine);
   sprintf(toybuf, "%s-unknown-linux", uu.machine);
-  setonlylocal(&ll, "MACHTYPE", toybuf);
-  setonlylocal(&ll, "OSTYPE", uu.sysname);
+  initvar("MACHTYPE", toybuf);
+  initvar("OSTYPE", uu.sysname);
   // sprintf(toybuf, "%s-toybox", TOYBOX_VERSION);
-  // setonlylocal(&ll, "BASH_VERSION", toybuf);
-  *ll++ = xstrdup("OPTERR=1");
-  *toybuf = 0;
-  if (readlink0("/proc/self/exe", toybuf, sizeof(toybuf)))
-    setonlylocal(&ll, "BASH", toybuf);
-  *ll = 0;
+  // initvar("BASH_VERSION", toybuf); TODO
+  initvar("OPTERR", "1"); // TODO: test if already exported?
+  if (readlink0("/proc/self/exe", s = toybuf, sizeof(toybuf))||(s=getenv("_")))
+    initvar("BASH", s);
+  initvar("PS2", "> ");
+  initvar("PS3", "#? ");
+  initvar("PS4", "+ ");
 
   // Ensure environ copied and toys.envc set, and clean out illegal entries
-  xunsetenv("");
-  for (to = from = 0; (s = environ[from]); from++) {
+  TT.ff->ifs = " \t\n";
+
+  for (from = pid = ppid = zpid = 0; (s = environ[from]); from++) {
 
     // If nommu subshell gets handoff
     if (!CFG_TOYBOX_FORK && !toys.stacktop) {
       len = 0;
       sscanf(s, "@%d,%d%n", &pid, &ppid, &len);
-      if (len && s[len]) pid = ppid = 0;
+      if (s[len]) pid = ppid = 0;
+      if (*s == '$' && s[1] == '=') zpid = atoi(s+2);
+// TODO marshall $- to subshell like $$
     }
 
-    // Filter out non-shell variable names
-    for (len = 0; s[len] && ((s[len] == '_') || !ispunct(s[len])); len++);
-    if (s[len] == '=') environ[to++] = environ[from];
+    // Filter out non-shell variable names from inherited environ.
+    if (*varend(s) != '=') continue;
+
+    if (!(shv = findvar(s, 0))) addvar(s, TT.ff)->flags = VAR_GLOBAL|VAR_NOFREE;
+    else if (shv->flags&VAR_READONLY) continue;
+    else {
+      if (!(shv->flags&VAR_NOFREE)) {
+        free(shv->str);
+        shv->flags ^= VAR_NOFREE;
+      }
+      shv->flags |= VAR_GLOBAL;
+      shv->str = s;
+    }
+    cache_ifs(s, TT.ff);
   }
-  environ[toys.optc = to] = 0;
 
   // set/update PWD
-  sh_run("cd .");
+  do_source(0, fmemopen("cd .", 4, "r"));
 
   // set _ to path to this shell
   s = toys.argv[0];
   ss = 0;
   if (!strchr(s, '/')) {
-    if (!(ss = getcwd(0, 0))) {
-      if (*toybuf) s = toybuf;
-    } else {
+    if ((ss = getcwd(0, 0))) {
       s = xmprintf("%s/%s", ss, s);
       free(ss);
       ss = s;
-    }
+    } else if (*toybuf) s = toybuf; // from /proc/self/exe
   }
-  xsetenv("_", s);
+  setvarval("_", s)->flags |= VAR_GLOBAL;
   free(ss);
-  if (!getvar("SHLVL")) xsetenv("SHLVL", "1");
+  if (!(ss = getvar("SHLVL"))) export("SHLVL=1");
+  else {
+    char buf[16];
+
+    sprintf(buf, "%u", atoi(ss+6)+1);
+    setvarval("SHLVL", buf)->flags |= VAR_GLOBAL;
+  }
 
 //TODO indexed array,associative array,integer,local,nameref,readonly,uppercase
 //          if (s+1<ss && strchr("aAilnru", *s)) {
 
-  // sanity check: magic env variable, pipe status
-  if (CFG_TOYBOX_FORK || toys.stacktop || pid!=mypid || ppid!=myppid) return;
-  if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
-  fcntl(254, F_SETFD, FD_CLOEXEC);
-  fp = fdopen(254, "r");
+  // Are we a nofork subshell? (check magic env variable and pipe status)
+  if (!CFG_TOYBOX_FORK && !toys.stacktop && pid==getpid() && ppid==myppid) {
+    if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
+    TT.pid = zpid;
+    fcntl(254, F_SETFD, FD_CLOEXEC);
+    do_source(0, fdopen(254, "r"));
 
-  // This is not efficient, could array_add the local vars.
-// TODO implicit exec when possible
-  while ((s = xgetline(fp, 0))) to = sh_run(s);
-  fclose(fp);
-
-  toys.exitval = to;
-  xexit();
+    xexit();
+  }
 }
 
 void sh_main(void)
 {
-  FILE *f;
-  char *new;
-  struct sh_function scratch;
-  int prompt = 0;
+  char *cc = 0;
+  FILE *ff;
 
-  TT.hfd = 10;
   signal(SIGPIPE, SIG_IGN);
+  TT.options = OPT_B;
+  TT.pid = getpid();
+  TT.SECONDS = time(0);
 
   // TODO euid stuff?
   // TODO login shell?
@@ -1992,55 +3728,44 @@
 
   // if (!FLAG(noprofile)) { }
 
-if (BUGBUG) { int fd = open("/dev/tty", O_RDWR); dup2(fd, 255); close(fd); }
-  // Is this an interactive shell?
-//  if (FLAG(i) || (!FLAG(c)&&(FLAG(S)||!toys.optc) && isatty(0) && isatty(1))) 
+  // If not reentering, figure out if this is an interactive shell.
+  if (toys.stacktop) {
+    cc = TT.sh.c;
+    if (!FLAG(c)) {
+      if (toys.optc==1) toys.optflags |= FLAG_s;
+      if (FLAG(s) && isatty(0)) toys.optflags |= FLAG_i;
+    }
+    if (toys.optc>1) {
+      toys.optargs++;
+      toys.optc--;
+    }
+    TT.options |= toys.optflags&0xff;
+  }
 
-  // Set up signal handlers and grab control of this tty.
-
-  // Read environment for exports from parent shell
+  // Read environment for exports from parent shell. Note, calls run_sh()
+  // which blanks argument sections of TT and this, so parse everything
+  // we need from shell command line before that.
   subshell_setup();
 
-  memset(&scratch, 0, sizeof(scratch));
-
-// TODO unify fmemopen() here with sh_run
-  if (TT.c) f = fmemopen(TT.c, strlen(TT.c), "r");
-  else if (*toys.optargs) f = xfopen(*toys.optargs, "r");
-  else {
-    f = stdin;
-    if (isatty(0)) toys.optflags |= FLAG_i;
+  if (TT.options&FLAG_i) {
+    if (!getvar("PS1")) setvarval("PS1", getpid() ? "\\$ " : "# ");
+    // TODO Set up signal handlers and grab control of this tty.
+    // ^C SIGINT ^\ SIGQUIT ^Z SIGTSTP SIGTTIN SIGTTOU SIGCHLD
+    // setsid(), setpgid(), tcsetpgrp()...
+    xsignal(SIGINT, SIG_IGN);
   }
 
-  for (;;) {
+  if (cc) ff = fmemopen(cc, strlen(cc), "r");
+  else if (TT.options&FLAG_s) ff = (TT.options&FLAG_i) ? 0 : stdin;
+  else if (!(ff = fpathopen(*toys.optargs))) perror_exit_raw(*toys.optargs);
 
-    // Prompt and read line
-    if (f == stdin) {
-      char *s = getenv(prompt ? "PS2" : "PS1");
-
-      if (!s) s = prompt ? "> " : (getpid() ? "\\$ " : "# ");
-      do_prompt(s);
-    } else TT.lineno++;
-// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
-    if (!(new = xgetline(f ? f : stdin, 0))) break;
-// TODO if (!isspace(*new)) add_to_history(line);
-
-    // returns 0 if line consumed, command if it needs more data
-    prompt = parse_line(new, &scratch);
-if (BUGBUG) dump_state(&scratch);
-    if (prompt != 1) {
-// TODO: ./blah.sh one two three: put one two three in scratch.arg
-      if (!prompt) run_function(scratch.pipeline);
-      free_function(&scratch);
-      prompt = 0;
-    }
-    free(new);
-  }
-
-  if (prompt) error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
-  toys.exitval = f && ferror(f);
-  clearerr(stdout);
+  // Read and execute lines from file
+  if (do_source(cc ? : *toys.optargs, ff))
+    error_exit("%u:unfinished line"+3*!TT.LINENO, TT.LINENO);
 }
 
+// TODO: ./blah.sh one two three: put one two three in scratch.arg
+
 /********************* shell builtin functions *************************/
 
 #define CLEANUP_sh
@@ -2048,26 +3773,16 @@
 #include "generated/flags.h"
 void cd_main(void)
 {
-  char *home = getvar("HOME"), *pwd = getvar("PWD"), *dd = 0, *from, *to,
-    *dest = (*toys.optargs && **toys.optargs) ? *toys.optargs : "~";
+  char *home = getvar("HOME") ? : "/", *pwd = getvar("PWD"), *from, *to = 0,
+    *dd = xstrdup(*toys.optargs ? *toys.optargs : home);
   int bad = 0;
 
   // TODO: CDPATH? Really?
 
-  if (!home) home = "/";
-
-  // expand variables
-  if (dest) dd = expand_one_arg(dest, FORCE_COPY|NO_SPLIT, 0);
-  if (!dd || !*dd) {
-    free(dd);
-    dd = xstrdup("/");
-  }
-
   // prepend cwd or $PWD to relative path
   if (*dd != '/') {
-    to = 0;
-    from = pwd ? pwd : (to = getcwd(0, 0));
-    if (!from) xsetenv("PWD", "(nowhere)");
+    from = pwd ? : (to = getcwd(0, 0));
+    if (!from) setvarval("PWD", "(nowhere)");
     else {
       from = xmprintf("%s/%s", from, dd);
       free(dd);
@@ -2106,8 +3821,18 @@
 
   if (bad || chdir(dd)) perror_msg("chdir '%s'", dd);
   else {
-    if (pwd) xsetenv("OLDPWD", pwd);
-    xsetenv("PWD", dd);
+    if (pwd) {
+      setvarval("OLDPWD", pwd);
+      if (TT.cdcount == 1) {
+        export("OLDPWD");
+        TT.cdcount++;
+      }
+    }
+    setvarval("PWD", dd);
+    if (!TT.cdcount) {
+      export("PWD");
+      TT.cdcount++;
+    }
   }
   free(dd);
 }
@@ -2116,3 +3841,342 @@
 {
   exit(*toys.optargs ? atoi(*toys.optargs) : 0);
 }
+
+// lib/args.c can't +prefix & "+o history" needs space so parse cmdline here
+void set_main(void)
+{
+  char *cc, *ostr[] = {"braceexpand", "noclobber", "xtrace"};
+  int ii, jj, kk, oo = 0, dd = 0;
+
+  // display visible variables
+  if (!*toys.optargs) {
+    struct sh_vars **vv = visible_vars();
+
+// TODO escape properly
+    for (ii = 0; vv[ii]; ii++)
+      if (!(vv[ii]->flags&VAR_WHITEOUT)) printf("%s\n", vv[ii]->str);
+    free(vv);
+
+    return;
+  }
+
+  // Handle options
+  for (ii = 0;; ii++) {
+    if ((cc = toys.optargs[ii]) && !(dd = stridx("-+", *cc)+1) && oo--) {
+      for (jj = 0; jj<ARRAY_LEN(ostr); jj++) if (!strcmp(cc, ostr[jj])) break;
+      if (jj != ARRAY_LEN(ostr)) {
+        if (dd==1) TT.options |= OPT_B<<kk;
+        else TT.options &= ~(OPT_B<<kk);
+
+        continue;
+      }
+      error_exit("bad -o %s", cc);
+    }
+    if (oo>0) for (jj = 0; jj<ARRAY_LEN(ostr); jj++)
+      printf("%s\t%s\n", ostr[jj], TT.options&(OPT_B<<jj) ? "on" : "off");
+    oo = 0;
+    if (!cc || !dd) break;
+    for (jj = 1; cc[jj]; jj++) {
+      if (cc[jj] == 'o') oo++;
+      else if (-1 != (kk = stridx("BCx", cc[jj]))) {
+        if (*cc == '-') TT.options |= OPT_B<<kk;
+        else TT.options &= ~(OPT_B<<kk);
+      } else error_exit("bad -%c", toys.optargs[ii][1]);
+    }
+  }
+
+  // handle positional parameters
+  if (cc) {
+    struct arg_list *al, **head;
+    struct sh_arg *arg = &TT.ff->arg;
+
+    // don't free memory that's already scheduled for deletion
+    for (al = *(head = &TT.ff->delete); al; al = *(head = &al->next))
+      if (al->arg == (void *)arg->v) break;
+
+    // free last set's memory (if any) so it doesn't accumulate in loop
+    if (al) for (jj = arg->c+1; jj; jj--) {
+      *head = al->next;
+      free(al->arg);
+      free(al);
+    }
+
+    while (toys.optargs[ii])
+      arg_add(arg, push_arg(&TT.ff->delete, strdup(toys.optargs[ii++])));
+    push_arg(&TT.ff->delete, arg->v);
+  }
+}
+
+// TODO need test: unset clears var first and stops, function only if no var.
+#define CLEANUP_cd
+#define FOR_unset
+#include "generated/flags.h"
+
+void unset_main(void)
+{
+  char **arg, *s;
+  int ii;
+
+  for (arg = toys.optargs; *arg; arg++) {
+    s = varend(*arg);
+    if (s == *arg || *s) {
+      error_msg("bad '%s'", *arg);
+      continue;
+    }
+
+    // TODO -n and name reference support
+    // unset variable
+    if (!FLAG(f) && unsetvar(*arg)) continue;
+    // unset function TODO binary search
+    for (ii = 0; ii<TT.funcslen; ii++)
+      if (!strcmp(*arg, TT.functions[ii]->name)) break;
+    if (ii != TT.funcslen) {
+      free_function(TT.functions[ii]);
+      memmove(TT.functions+ii, TT.functions+ii+1, TT.funcslen+1-ii);
+    }
+  }
+}
+
+#define CLEANUP_unset
+#define FOR_export
+#include "generated/flags.h"
+
+void export_main(void)
+{
+  char **arg, *eq;
+
+  // list existing variables?
+  if (!toys.optc) {
+    struct sh_vars **vv = visible_vars();
+    unsigned uu;
+
+    for (uu = 0; vv[uu]; uu++) {
+      if ((vv[uu]->flags&(VAR_WHITEOUT|VAR_GLOBAL))==VAR_GLOBAL) {
+        xputs(eq = declarep(vv[uu]));
+        free(eq);
+      }
+    }
+
+    return;
+  }
+
+  // set/move variables
+  for (arg = toys.optargs; *arg; arg++) {
+    eq = varend(*arg);
+    if (eq == *arg || (*eq && *eq != '=')) {
+      error_msg("bad %s", *arg);
+      continue;
+    }
+
+    if (FLAG(n)) unexport(*arg);
+    else export(*arg);
+  }
+}
+
+void eval_main(void)
+{
+  char *s;
+
+  // borrow the $* expand infrastructure
+  call_function();
+  TT.ff->arg.v = toys.argv;
+  TT.ff->arg.c = toys.optc+1;
+  s = expand_one_arg("\"$*\"", SEMI_IFS, 0);
+  TT.ff->arg.v = TT.ff->next->arg.v;
+  TT.ff->arg.c = TT.ff->next->arg.c;
+  do_source(0, fmemopen(s, strlen(s), "r"));
+  free(dlist_pop(&TT.ff));
+  free(s);
+}
+
+#define CLEANUP_export
+#define FOR_exec
+#include "generated/flags.h"
+
+void exec_main(void)
+{
+  char *ee[1] = {0}, **old = environ;
+
+  // discard redirects and return if nothing to exec
+  free(TT.pp->urd);
+  TT.pp->urd = 0;
+  if (!toys.optc) return;
+
+  // exec, handling -acl
+  TT.isexec = *toys.optargs;
+  if (FLAG(c)) environ = ee;
+  if (TT.exec.a || FLAG(l))
+    *toys.optargs = xmprintf("%s%s", FLAG(l) ? "-" : "", TT.exec.a?:TT.isexec);
+  sh_exec(toys.optargs);
+
+  // report error (usually ENOENT) and return
+  perror_msg("%s", TT.isexec);
+  if (*toys.optargs != TT.isexec) free(*toys.optargs);
+  TT.isexec = 0;
+  toys.exitval = 127;
+  environ = old;
+}
+
+// Return T.jobs index or -1 from identifier
+// Note, we don't return "ambiguous job spec", we return the first hit or -1.
+// TODO %% %+ %- %?ab
+int find_job(char *s)
+{
+  char *ss;
+  long ll = strtol(s, &ss, 10);
+  int i, j;
+
+  if (!TT.jobs.c) return -1;
+  if (!*s || (!s[1] && strchr("%+-", *s))) {
+    int minus, plus = find_plus_minus(&minus);
+
+    return (*s == '-') ? minus : plus;
+  }
+
+  // Is this a %1 numeric jobspec?
+  if (s != ss && !*ss)
+    for (i = 0; i<TT.jobs.c; i++)
+      if (((struct sh_process *)TT.jobs.v[i])->job == ll) return i;
+
+  // Match start of command or %?abc
+  for (i = 0; i<TT.jobs.c; i++) {
+    struct sh_process *pp = (void *)TT.jobs.v[i];
+
+    if (strstart(&s, *pp->arg.v)) return i;
+    if (*s != '?' || !s[1]) continue;
+    for (j = 0; j<pp->arg.c; j++) if (strstr(pp->arg.v[j], s+1)) return i;
+  }
+
+  return -1;
+}
+
+void jobs_main(void)
+{
+  int i, j, minus, plus = find_plus_minus(&minus);
+  char *s;
+
+// TODO -lnprs
+
+  for (i = 0;;i++) {
+    if (toys.optc) {
+      if (!(s = toys.optargs[i])) break;
+      if ((j = find_job(s+('%' == *s))) == -1) {
+        perror_msg("%s: no such job", s);
+
+        continue;
+      }
+    } else if ((j = i) >= TT.jobs.c) break;
+
+    s = show_job((void *)TT.jobs.v[i], is_plus_minus(i, plus, minus));
+    printf("%s\n", s);
+    free(s);
+  }
+}
+
+#define CLEANUP_exec
+#define FOR_local
+#include "generated/flags.h"
+
+void local_main(void)
+{
+  struct sh_fcall *ff, *ff2;
+  struct sh_vars *var;
+  char **arg, *eq;
+
+  // find local variable context
+  for (ff = TT.ff;; ff = ff->next) {
+    if (ff == TT.ff->prev) return error_msg("not in function");
+    if (ff->vars) break;
+  }
+
+  // list existing vars (todo: 
+  if (!toys.optc) {
+    for (var = ff->vars; var; var++) xputs(var->str); // TODO escape
+    return;
+  }
+
+  // set/move variables
+  for (arg = toys.optargs; *arg; arg++) {
+    if ((eq = varend(*arg)) == *arg || (*eq && *eq != '=')) {
+      error_msg("bad %s", *arg);
+      continue;
+    }
+
+    if ((var = findvar(*arg, &ff2)) && ff == ff2 && !*eq) continue;
+    if (var && (var->flags&VAR_READONLY)) {
+      error_msg("%.*s: readonly variable", (int)(varend(*arg)-*arg), *arg);
+      continue;
+    }
+
+    // Add local inheriting global status and setting whiteout if blank.
+    if (!var || ff!=ff2) {
+      int flags = var ? var->flags&VAR_GLOBAL : 0;
+
+      var = addvar(xmprintf("%s%s", *arg, *eq ? "" : "="), ff);
+      var->flags = flags|(VAR_WHITEOUT*!*eq);
+    }
+
+    // TODO accept declare options to set more flags
+    // TODO, integer, uppercase take effect. Setvar?
+  }
+}
+
+void shift_main(void)
+{
+  long long by = 1;
+
+  if (toys.optc) by = atolx(*toys.optargs);
+  by += TT.ff->shift;
+  if (by<0 || by>=TT.ff->arg.c) toys.exitval++;
+  else TT.ff->shift = by;
+}
+
+void source_main(void)
+{
+  char *name = *toys.optargs;
+  FILE *ff = fpathopen(name);
+
+  if (!ff) return perror_msg_raw(name);
+  // $0 is shell name, not source file name while running this
+// TODO add tests: sh -c "source input four five" one two three
+  *toys.optargs = *toys.argv;
+  ++TT.srclvl;
+  call_function();
+  TT.ff->arg.v = toys.optargs;
+  TT.ff->arg.c = toys.optc;
+  do_source(name, ff);
+  free(dlist_pop(&TT.ff));
+  --TT.srclvl;
+}
+
+#define CLEANUP_local
+#define FOR_wait
+#include "generated/flags.h"
+
+void wait_main(void)
+{
+  struct sh_process *pp;
+  int ii, jj;
+  long long ll;
+  char *s;
+
+  // TODO does -o pipefail affect return code here
+  if (FLAG(n)) toys.exitval = free_process(wait_job(-1, 0));
+  else if (!toys.optc) while (TT.jobs.c) {
+    if (!(pp = wait_job(-1, 0))) break;
+  } else for (ii = 0; ii<toys.optc; ii++) {
+    ll = estrtol(toys.optargs[ii], &s, 10);
+    if (errno || *s) {
+      if (-1 == (jj = find_job(toys.optargs[ii]))) {
+        error_msg("%s: bad pid/job", toys.optargs[ii]);
+        continue;
+      }
+      ll = ((struct sh_process *)TT.jobs.v[jj])->pid;
+    }
+    if (!(pp = wait_job(ll, 0))) {
+      if (toys.signal) toys.exitval = 128+toys.signal;
+      break;
+    }
+    toys.exitval = free_process(pp);
+  }
+}
diff --git a/toys/pending/stty.c b/toys/pending/stty.c
index df12526..a997f49 100644
--- a/toys/pending/stty.c
+++ b/toys/pending/stty.c
@@ -41,7 +41,7 @@
 #include <linux/tty.h>
 
 GLOBALS(
-  char *device;
+  char *F;
 
   int fd, col;
   unsigned output_cols;
@@ -120,12 +120,15 @@
   char *from;
   char *to;
 } synonyms[] = {
-  { "cbreak", "-icanon" }, { "-cbreak", "icanon" }, { "-cooked", "raw" },
-  { "crterase", "echoe" }, { "-crterase", "-echoe" }, { "crtkill", "echoke" },
-  { "-crtkill", "-echoke" }, { "ctlecho", "echoctl" }, { "-tandem", "-ixoff" },
-  { "-ctlecho", "-echoctl" }, { "hup", "hupcl" }, { "-hup", "-hupcl" },
-  { "prterase", "echoprt" }, { "-prterase", "-echoprt" }, { "-raw", "cooked" },
-  { "tabs", "tab0" }, { "-tabs", "tab3" }, { "tandem", "ixoff" },
+  { "cbreak", "-icanon" }, { "-cbreak", "icanon" },
+  { "-cooked", "raw" }, { "-raw", "cooked" },
+  { "crterase", "echoe" }, { "-crterase", "-echoe" },
+  { "crtkill", "echoke" }, { "-crtkill", "-echoke" },
+  { "ctlecho", "echoctl" }, { "-ctlecho", "-echoctl" },
+  { "-tandem", "-ixoff" }, { "tandem", "ixoff" },
+  { "hup", "hupcl" }, { "-hup", "-hupcl" },
+  { "prterase", "echoprt" }, { "-prterase", "-echoprt" },
+  { "tabs", "tab0" }, { "-tabs", "tab3" },
 };
 
 static void out(const char *fmt, ...)
@@ -160,9 +163,9 @@
   int i, j, value, mask;
 
   // Implement -a by ensuring that sane != actual so we'll show everything.
-  if (toys.optflags&FLAG_a) sane = ~actual;
+  if (FLAG(a)) sane = ~actual;
 
-  for (i=j=0;i<len;i++) {
+  for (i = j = 0; i<len; i++) {
     value = flags[i].value;
     if ((mask = flags[i].mask)) {
       if ((actual&mask)==value && (sane&mask)!=value) {
@@ -183,7 +186,7 @@
 {
   struct winsize ws;
 
-  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device);
+  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.F);
   out(verbose ? "rows %d; columns %d;" : "%d %d\n", ws.ws_row, ws.ws_col);
 }
 
@@ -196,11 +199,11 @@
   out(fmt, ispeed, ospeed);
 }
 
-static int get_arg(int *i, long long low, long long high)
+static int get_arg(int *i, long long high)
 {
   (*i)++;
   if (!toys.optargs[*i]) error_exit("missing arg");
-  return atolx_range(toys.optargs[*i], low, high);
+  return atolx_range(toys.optargs[*i], 0, high);
 }
 
 static int set_flag(tcflag_t *f, const struct flag *flags, int len,
@@ -249,10 +252,10 @@
 {
   struct winsize ws;
 
-  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device);
+  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.F);
   if (is_rows) ws.ws_row = value;
   else ws.ws_col = value;
-  if (ioctl(TT.fd, TIOCSWINSZ, &ws)) perror_exit("TIOCSWINSZ %s", TT.device);
+  if (ioctl(TT.fd, TIOCSWINSZ, &ws)) perror_exit("TIOCSWINSZ %s", TT.F);
 }
 
 static int set_special_character(struct termios *new, int *i, char *char_name)
@@ -271,7 +274,6 @@
       else if (arg[0] == '^' && arg[2] == 0) ch = (toupper(arg[1])-'@');
       else if (!arg[1]) ch = arg[0];
       else error_exit("invalid arg: %s", arg);
-      xprintf("setting %s to %s (%02x)\n", char_name, arg, ch);
       new->c_cc[chars[j].value] = ch;
       return 1;
     }
@@ -311,51 +313,61 @@
 
 static void xtcgetattr(struct termios *t)
 {
-  if (tcgetattr(TT.fd, t)) perror_exit("tcgetattr %s", TT.device);
+  if (tcgetattr(TT.fd, t)) perror_exit("tcgetattr %s", TT.F);
 }
 
-static void do_stty()
+void stty_main(void)
 {
   struct termios old, sane;
   int i, j, n;
 
+  if (toys.optflags&(FLAG_a|FLAG_g) && *toys.optargs)
+    error_exit("no settings with -a/-g");
+
+  if (!TT.F) TT.F = "standard input";
+  else TT.fd = xopen(TT.F, (O_RDWR*!!*toys.optargs)|O_NOCTTY|O_NONBLOCK);
+
   xtcgetattr(&old);
 
   if (*toys.optargs) {
-    struct termios new = old;
+    struct termios new = old, tmp;
 
     for (i=0; toys.optargs[i]; i++) {
       char *arg = toys.optargs[i];
 
       if (!strcmp(arg, "size")) show_size(0);
       else if (!strcmp(arg, "speed")) show_speed(&old, 0);
-      else if (!strcmp(arg, "line")) new.c_line = get_arg(&i, N_TTY, NR_LDISCS);
-      else if (!strcmp(arg, "min")) new.c_cc[VMIN] = get_arg(&i, 0, 255);
-      else if (!strcmp(arg, "time")) new.c_cc[VTIME] = get_arg(&i, 0, 255);
-      else if (atoi(arg) > 0) {
-        int new_speed = speed(atolx_range(arg, 0, 4000000));
-
-        cfsetispeed(&new, new_speed);
-        cfsetospeed(&new, new_speed);
-      } else if (!strcmp(arg, "ispeed"))
-        cfsetispeed(&new, speed(get_arg(&i, 0, 4000000)));
-      else if (!strcmp(arg, "ospeed"))
-        cfsetospeed(&new, speed(get_arg(&i, 0, 4000000)));
-      else if (!strcmp(arg, "rows")) set_size(1, get_arg(&i, 0, USHRT_MAX));
-      else if (!strcmp(arg, "cols") || !strcmp(arg, "columns"))
-        set_size(0, get_arg(&i, 0, USHRT_MAX));
-      else if (sscanf(arg, "%x:%x:%x:%x:%n", &new.c_iflag, &new.c_oflag,
-                        &new.c_cflag, &new.c_lflag, &n) == 4)
+      else if (!strcmp(arg, "line")) new.c_line = get_arg(&i, NR_LDISCS);
+      else if (!strcmp(arg, "min")) new.c_cc[VMIN] = get_arg(&i, 255);
+      else if (!strcmp(arg, "time")) new.c_cc[VTIME] = get_arg(&i, 255);
+      else if (sscanf(arg, "%x:%x:%x:%x:%n", &tmp.c_iflag, &tmp.c_oflag,
+                        &tmp.c_cflag, &tmp.c_lflag, &n) == 4)
       {
         int value;
 
+        new.c_iflag = tmp.c_iflag;
+        new.c_oflag = tmp.c_oflag;
+        new.c_cflag = tmp.c_cflag;
+        new.c_lflag = tmp.c_lflag;
         arg += n;
         for (j=0;j<NCCS;j++) {
           if (sscanf(arg, "%x%n", &value, &n) != 1) error_exit("bad -g string");
           new.c_cc[j] = value;
           arg += n+1;
         }
-      } else if (set_special_character(&new, &i, arg));
+      } else if (atoi(arg) > 0) {
+        int new_speed = speed(atolx_range(arg, 0, 4000000));
+
+        cfsetispeed(&new, new_speed);
+        cfsetospeed(&new, new_speed);
+      } else if (!strcmp(arg, "ispeed"))
+        cfsetispeed(&new, speed(get_arg(&i, 4000000)));
+      else if (!strcmp(arg, "ospeed"))
+        cfsetospeed(&new, speed(get_arg(&i, 4000000)));
+      else if (!strcmp(arg, "rows")) set_size(1, get_arg(&i, USHRT_MAX));
+      else if (!strcmp(arg, "cols") || !strcmp(arg, "columns"))
+        set_size(0, get_arg(&i, USHRT_MAX));
+      else if (set_special_character(&new, &i, arg));
         // Already done as a side effect.
       else if (!strcmp(arg, "cooked"))
         set_options(&new, "brkint", "ignpar", "istrip", "icrnl", "ixon",
@@ -384,7 +396,7 @@
       } else if (!strcmp(arg, "sane")) make_sane(&new);
       else {
         // Translate historical cruft into canonical forms.
-        for (j=0;j<ARRAY_LEN(synonyms);j++) {
+        for (j=0; j<ARRAY_LEN(synonyms); j++) {
           if (!strcmp(synonyms[j].from, arg)) {
             arg = synonyms[j].to;
             break;
@@ -393,15 +405,16 @@
         set_option(&new, arg);
       }
     }
+
     tcsetattr(TT.fd, TCSAFLUSH, &new);
     xtcgetattr(&old);
     if (memcmp(&old, &new, sizeof(old)))
-      error_exit("unable to perform all requested operations on %s", TT.device);
+      error_exit("unable to perform all requested operations on %s", TT.F);
 
     return;
   }
 
-  if (toys.optflags&FLAG_g) {
+  if (FLAG(g)) {
     xprintf("%x:%x:%x:%x:", old.c_iflag, old.c_oflag, old.c_cflag, old.c_lflag);
     for (i=0;i<NCCS;i++) xprintf("%x%c", old.c_cc[i], i==NCCS-1?'\n':':');
     return;
@@ -411,21 +424,20 @@
   // special characters and any flags that differ from the "sane" settings.
   make_sane(&sane);
   show_speed(&old, 1);
-  if (toys.optflags&FLAG_a) show_size(1);
+  if (FLAG(a)) show_size(1);
   out("line = %d;\n", old.c_line);
 
-  for (i=j=0;i<ARRAY_LEN(chars);i++) {
+  for (i=j=0; i<ARRAY_LEN(chars); i++) {
     char vis[16] = {};
     cc_t ch = old.c_cc[chars[i].value];
 
-    if (ch == sane.c_cc[chars[i].value] && (toys.optflags&FLAG_a)==0)
+    if (ch == sane.c_cc[chars[i].value] && !FLAG(a))
       continue;
 
-    if (chars[i].value == VMIN || chars[i].value == VTIME) {
+    if (chars[i].value == VMIN || chars[i].value == VTIME)
       snprintf(vis, sizeof(vis), "%u", ch);
-    } else if (ch == _POSIX_VDISABLE) {
-      strcat(vis, "<undef>");
-    } else {
+    else if (ch == _POSIX_VDISABLE) strcat(vis, "<undef>");
+    else {
       if (ch > 0x7f) {
         strcat(vis, "M-");
         ch -= 128;
@@ -443,17 +455,6 @@
   show_flags(old.c_iflag, sane.c_iflag, iflags, ARRAY_LEN(iflags));
   show_flags(old.c_oflag, sane.c_oflag, oflags, ARRAY_LEN(oflags));
   show_flags(old.c_lflag, sane.c_lflag, lflags, ARRAY_LEN(lflags));
-}
 
-void stty_main(void)
-{
-  if (toys.optflags&(FLAG_a|FLAG_g) && *toys.optargs)
-    error_exit("can't make settings with -a/-g");
-
-  if (!TT.device) TT.device = "standard input";
-  else TT.fd=xopen(TT.device, (O_RDWR*!!*toys.optargs)|O_NOCTTY|O_NONBLOCK);
-
-  do_stty();
-
-  if (CFG_TOYBOX_FREE && TT.device) close(TT.fd);
+  if (TT.fd) close(TT.fd);
 }
diff --git a/toys/pending/telnet.c b/toys/pending/telnet.c
index b8c9c14..6b2c273 100644
--- a/toys/pending/telnet.c
+++ b/toys/pending/telnet.c
@@ -14,330 +14,258 @@
   help
     usage: telnet HOST [PORT]
 
-    Connect to telnet server
+    Connect to telnet server.
 */
 
 #define FOR_telnet
 #include "toys.h"
 #include <arpa/telnet.h>
-#include <netinet/in.h>
-#include  <sys/poll.h>
 
 GLOBALS(
-  int port;
-  int sfd;
-  char buff[128];
-  int pbuff;
-  char iac[256];
-  int piac;
-  char *ttype;
-  struct termios def_term;
+  int sock;
+  char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
+  struct termios old_term;
   struct termios raw_term;
-  uint8_t term_ok;
-  uint8_t term_mode;
-  uint8_t flags;
-  unsigned win_width;
-  unsigned win_height;
+  uint8_t mode;
+  int echo, sga;
+  int state, request;
 )
 
-#define DATABUFSIZE 128
-#define IACBUFSIZE  256
+#define NORMAL 0
+#define SAW_IAC 1
+#define SAW_WWDD 2
+#define SAW_SB 3
+#define SAW_SB_TTYPE 4
+#define WANT_IAC 5
+#define WANT_SE 6
+#define SAW_CR 10
+
 #define CM_TRY      0
 #define CM_ON       1
 #define CM_OFF      2
-#define UF_ECHO     0x01
-#define UF_SGA      0x02
 
-// sets terminal mode: LINE or CHARACTER based om internal stat.
-static char const es[] = "\r\nEscape character is ";
+static void raw(int raw)
+{
+  tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term);
+}
+
+static void slc(int line)
+{
+  TT.mode = line ? CM_OFF : CM_ON;
+  xprintf("Entering %s mode\r\nEscape character is '^%c'.\r\n",
+      line ? "line" : "character", line ? 'C' : ']');
+  raw(!line);
+}
+
 static void set_mode(void)
 {
-  if (TT.flags & UF_ECHO) {
-    if (TT.term_mode == CM_TRY) {
-      TT.term_mode = CM_ON;
-      printf("\r\nEntering character mode%s'^]'.\r\n", es);
-      if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
-    }
-  } else {
-    if (TT.term_mode != CM_OFF) {
-      TT.term_mode = CM_OFF;
-      printf("\r\nEntering line mode%s'^C'.\r\n", es);
-      if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
-    }
-  }
-}
-
-// flushes all data in IAC buff to server.
-static void flush_iac(void)
-{
-  int wlen = write(TT.sfd, TT.iac, TT.piac);
-
-  if(wlen <= 0) error_msg("IAC : send failed.");
-  TT.piac = 0;
-}
-
-// puts DATA in iac buff of length LEN and updates iac buff pointer.
-static void put_iac(int len, ...)
-{
-  va_list va; 
-
-  if(TT.piac + len >= IACBUFSIZE) flush_iac();
-  va_start(va, len);
-  for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--);
-  va_end(va);
-}
-
-// puts string STR in iac buff and updates iac buff pointer.
-static void str_iac(char *str)
-{
-  int len = strlen(str);
-
-  if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac();
-  strcpy(&TT.iac[TT.piac], str);
-  TT.piac += len+1;
+  if (TT.echo) {
+    if (TT.mode == CM_TRY) slc(0);
+  } else if (TT.mode != CM_OFF) slc(1);
 }
 
 static void handle_esc(void)
 {
   char input;
 
-  if(toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
-  xwrite(1,"\r\nConsole escape. Commands are:\r\n\n"
+  if (toys.signal) raw(1);
+
+  // This matches busybox telnet, not BSD telnet.
+  xputsn("\r\n"
+      "Console escape. Commands are:\r\n"
+      "\r\n"
       " l  go to line mode\r\n"
       " c  go to character mode\r\n"
       " z  suspend telnet\r\n"
-      " e  exit telnet\r\n", 114);
-
-  if (read(STDIN_FILENO, &input, 1) <= 0) {
-    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
-    exit(0);
+      " e  exit telnet\r\n"
+      "\r\n"
+      "telnet> ");
+  // In particular, the boxes only read a single character, not a line.
+  if (read(0, &input, 1) <= 0 || input == 4 || input == 'e') {
+    xprintf("Connection closed.\r\n");
+    xexit();
   }
 
-  switch (input) {
-  case 'l':
+  if (input == 'l') {
     if (!toys.signal) {
-      TT.term_mode = CM_TRY;
-      TT.flags &= ~(UF_ECHO | UF_SGA);
+      TT.mode = CM_TRY;
+      TT.echo = TT.sga = 0;
       set_mode();
-      put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA);
-      flush_iac();
+      dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DONT,TELOPT_ECHO,IAC,DONT,TELOPT_SGA);
       goto ret;
     }
-    break;
-  case 'c':
+  } else if (input == 'c') {
     if (toys.signal) {
-      TT.term_mode = CM_TRY;
-      TT.flags |= (UF_ECHO | UF_SGA);
+      TT.mode = CM_TRY;
+      TT.echo = TT.sga = 1;
       set_mode();
-      put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
-      flush_iac();
+      dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
       goto ret;
     }
-    break;
-  case 'z':
-    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+  } else if (input == 'z') {
+    raw(0);
     kill(0, SIGTSTP);
-    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
-    break;
-  case 'e':
-    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
-    exit(0);
-  default: break;
+    raw(1);
   }
 
-  xwrite(1, "continuing...\r\n", 15);
-  if (toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+  xprintf("telnet %s %s\r\n", toys.optargs[0], toys.optargs[1] ?: "23");
+  if (toys.signal) raw(0);
 
 ret:
   toys.signal = 0;
 }
 
-/*
- * handles telnet SUB NEGOTIATIONS
- * only terminal type is supported.
- */
-static void handle_negotiations(void)
+// Handle WILL WONT DO DONT requests from the server.
+static void handle_wwdd(char opt)
 {
-  char opt = TT.buff[TT.pbuff++];
-
-  switch(opt) {
-  case TELOPT_TTYPE:
-    opt =  TT.buff[TT.pbuff++];
-    if(opt == TELQUAL_SEND) {
-      put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS);
-      str_iac(TT.ttype);
-      put_iac(2, IAC,SE);
-    }
-    break;
-  default: break;
-  }
-}
-
-/*
- * handles server's DO DONT WILL WONT requests.
- * supports ECHO, SGA, TTYPE, NAWS
- */
-static void handle_ddww(char ddww)
-{
-  char opt = TT.buff[TT.pbuff++];
-
-  switch (opt) {
-  case TELOPT_ECHO: /* ECHO */
-    if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO);
-    if(ddww == DONT) break;
-    if (TT.flags & UF_ECHO) {
-        if (ddww == WILL) return;
-      } else if (ddww == WONT) return;
-    if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO;
-    (TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) : 
-      put_iac(3, IAC,DONT,TELOPT_ECHO);
+  if (opt == TELOPT_ECHO) {
+    if (TT.request == DO) dprintf(TT.sock, "%c%c%c", IAC, WONT, TELOPT_ECHO);
+    if (TT.request == DONT) return;
+    if (TT.echo) {
+        if (TT.request == WILL) return;
+    } else if (TT.request == WONT) return;
+    if (TT.mode != CM_OFF) TT.echo ^= 1;
+    dprintf(TT.sock, "%c%c%c", IAC, TT.echo ? DO : DONT, TELOPT_ECHO);
     set_mode();
-    printf("\r\n");
-    break;
+  } else if (opt == TELOPT_SGA) { // Suppress Go Ahead
+    if (TT.sga) {
+      if (TT.request == WILL) return;
+    } else if (TT.request == WONT) return;
+    TT.sga ^= 1;
+    dprintf(TT.sock, "%c%c%c", IAC, TT.sga ? DO : DONT, TELOPT_SGA);
+  } else if (opt == TELOPT_TTYPE) { // Terminal TYPE
+    dprintf(TT.sock, "%c%c%c", IAC, WILL, TELOPT_TTYPE);
+  } else if (opt == TELOPT_NAWS) { // Negotiate About Window Size
+    unsigned cols = 80, rows = 24;
 
-  case TELOPT_SGA: /* Supress GO Ahead */
-    if (TT.flags & UF_SGA){ if (ddww == WILL) return;
-    } else if (ddww == WONT) return;
-
-    TT.flags ^= UF_SGA;
-    (TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) :
-      put_iac(3, IAC,DONT,TELOPT_SGA);
-    break;
-
-  case TELOPT_TTYPE: /* Terminal Type */
-    (TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE):
-      put_iac(3, IAC,WONT,TELOPT_TTYPE);
-    break;
-
-  case TELOPT_NAWS: /* Window Size */
-    put_iac(3, IAC,WILL,TELOPT_NAWS);
-    put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff,
-        TT.win_width & 0xff,(TT.win_height >> 8) & 0xff,
-        TT.win_height & 0xff,IAC,SE);
-    break;
-
-  default: /* Default behaviour is to say NO */
-    if(ddww == WILL) put_iac(3, IAC,DONT,opt);
-    if(ddww == DO) put_iac(3, IAC,WONT,opt);
-    break;
+    terminal_size(&cols, &rows);
+    dprintf(TT.sock, "%c%c%c%c%c%c%c%c%c%c%c%c", IAC, WILL, TELOPT_NAWS,
+            IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows,
+            IAC, SE);
+  } else {
+    // Say "no" to anything we don't understand.
+    dprintf(TT.sock, "%c%c%c", IAC, (TT.request == WILL) ? DONT : WONT, opt);
   }
 }
 
-/*
- * parses data which is read from server of length LEN.
- * and passes it to console.
- */
-static int read_server(int len)
+static void handle_server_output(int n)
 {
+  char *p = TT.buf, *end = TT.buf + n, ch;
   int i = 0;
-  char curr;
-  TT.pbuff = 0;
 
-  do {
-    curr = TT.buff[TT.pbuff++];
-    if (curr == IAC) {
-      curr = TT.buff[TT.pbuff++];
-      switch (curr) {
-      case DO:    /* FALLTHROUGH */
-      case DONT:    /* FALLTHROUGH */
-      case WILL:    /* FALLTHROUGH */
-      case WONT:
-        handle_ddww(curr);
-        break;
-      case SB:
-        handle_negotiations();
-        break;
-      case SE:
-        break;
-      default: break;
+  // Possibilities:
+  //
+  // 1. Regular character
+  // 2. IAC [WILL|WONT|DO|DONT] option
+  // 3. IAC SB option arg... IAC SE
+  //
+  // The only subnegotiation we support is IAC SB TTYPE SEND IAC SE, so we just
+  // hard-code that into our state machine rather than having a more general
+  // "collect the subnegotation into a buffer and handle it after we've seen
+  // the IAC SE at the end". It's 2021, so we're unlikely to need more.
+
+  while (p < end) {
+    ch = *p++;
+    if (TT.state == SAW_IAC) {
+      if (ch >= WILL && ch <= DONT) {
+        TT.state = SAW_WWDD;
+        TT.request = ch;
+      } else if (ch == SB) {
+        TT.state = SAW_SB;
+      } else {
+        TT.state = NORMAL;
       }
+    } else if (TT.state == SAW_WWDD) {
+      handle_wwdd(ch);
+      TT.state = NORMAL;
+    } else if (TT.state == SAW_SB) {
+      if (ch == TELOPT_TTYPE) TT.state = SAW_SB_TTYPE;
+      else TT.state = WANT_IAC;
+    } else if (TT.state == SAW_SB_TTYPE) {
+      if (ch == TELQUAL_SEND) {
+        dprintf(TT.sock, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS,
+                getenv("TERM") ?: "NVT", IAC, SE);
+      }
+      TT.state = WANT_IAC;
+    } else if (TT.state == WANT_IAC) {
+      if (ch == IAC) TT.state = WANT_SE;
+    } else if (TT.state == WANT_SE) {
+      if (ch == SE) TT.state = NORMAL;
+    } else if (ch == IAC) {
+      TT.state = SAW_IAC;
     } else {
-      toybuf[i++] = curr;
-      if (curr == '\r') { curr = TT.buff[TT.pbuff++];
-        if (curr != '\0') TT.pbuff--;
-      }
+      if (TT.state == SAW_CR && ch == '\0') {
+        // CR NUL -> CR
+      } else toybuf[i++] = ch;
+      if (ch == '\r') TT.state = SAW_CR;
+      TT.state = NORMAL;
     }
-  } while (TT.pbuff < len);
-
-  if (i) xwrite(STDIN_FILENO, toybuf, i);
-  return 0;
+  }
+  if (i) xwrite(0, toybuf, i);
 }
 
-/*
- * parses data which is read from console of length LEN
- * and passes it to server.
- */
-static void write_server(int len)
+static void handle_user_input(int n)
 {
-  char *c = (char*)TT.buff;
+  char *p = TT.buf, ch;
   int i = 0;
 
-  for (; len > 0; len--, c++) {
-    if (*c == 0x1d) {
+  while (n--) {
+    ch = *p++;
+    if (ch == 0x1d) {
       handle_esc();
       return;
     }
-    toybuf[i++] = *c;
-    if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */
-    else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */
+    toybuf[i++] = ch;
+    if (ch == IAC) toybuf[i++] = IAC; // IAC -> IAC IAC
+    else if (ch == '\r') toybuf[i++] = '\n'; // CR -> CR LF
+    else if (ch == '\n') { // LF -> CR LF
+      toybuf[i-1] = '\r';
+      toybuf[i++] = '\n';
+    }
   }
-  if(i) xwrite(TT.sfd, toybuf, i);
+  if (i) xwrite(TT.sock, toybuf, i);
+}
+
+static void reset_terminal(void)
+{
+  raw(0);
 }
 
 void telnet_main(void)
 {
-  char *port = "23";
-  int set = 1, len;
   struct pollfd pfds[2];
+  int n = 1;
 
-  TT.win_width = 80; //columns
-  TT.win_height = 24; //rows
+  tcgetattr(0, &TT.old_term);
+  TT.raw_term = TT.old_term;
+  cfmakeraw(&TT.raw_term);
 
-  if (toys.optc == 2) port = toys.optargs[1];
+  TT.sock = xconnectany(xgetaddrinfo(*toys.optargs, toys.optargs[1] ?: "23", 0,
+      SOCK_STREAM, IPPROTO_TCP, 0));
+  xsetsockopt(TT.sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n));
 
-  TT.ttype = getenv("TERM");
-  if(!TT.ttype) TT.ttype = "";
-  if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0';
+  xprintf("Connected to %s.\r\n", *toys.optargs);
 
-  if (!tcgetattr(0, &TT.def_term)) {
-    TT.term_ok = 1;
-    TT.raw_term = TT.def_term;
-    cfmakeraw(&TT.raw_term);
-  }
-  terminal_size(&TT.win_width, &TT.win_height);
-
-  TT.sfd = xconnectany(xgetaddrinfo(*toys.optargs, port, 0, SOCK_STREAM,
-    IPPROTO_TCP, 0));
-  setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
-  setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
-
-  pfds[0].fd = STDIN_FILENO;
-  pfds[0].events = POLLIN;
-  pfds[1].fd = TT.sfd;
-  pfds[1].events = POLLIN;
-
+  sigatexit(reset_terminal);
   signal(SIGINT, generic_signal);
-  while(1) {
-    if(TT.piac) flush_iac();
-    if(poll(pfds, 2, -1) < 0) {
-      if (toys.signal) handle_esc();
-      else sleep(1);
 
-      continue;
+  pfds[0].fd = 0;
+  pfds[0].events = POLLIN;
+  pfds[1].fd = TT.sock;
+  pfds[1].events = POLLIN;
+  for (;;) {
+    if (poll(pfds, 2, -1) < 0) {
+      if (toys.signal) handle_esc();
+      else perror_exit("poll");
     }
-    if(pfds[0].revents) {
-      len = read(STDIN_FILENO, TT.buff, DATABUFSIZE);
-      if(len > 0) write_server(len);
-      else return;
+    if (pfds[0].revents) {
+      if ((n = read(0, TT.buf, sizeof(TT.buf))) <= 0) xexit();
+      handle_user_input(n);
     }
-    if(pfds[1].revents) {
-      len = read(TT.sfd, TT.buff, DATABUFSIZE);
-      if(len > 0) read_server(len);
-      else {
-        printf("Connection closed by foreign host\r\n");
-        if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
-        exit(1);
-      }
+    if (pfds[1].revents) {
+      if ((n = read(TT.sock, TT.buf, sizeof(TT.buf))) <= 0)
+        error_exit("Connection closed by foreign host\r");
+      handle_server_output(n);
     }
   }
 }
diff --git a/toys/pending/telnetd.c b/toys/pending/telnetd.c
index ad39d8c..235bd17 100644
--- a/toys/pending/telnetd.c
+++ b/toys/pending/telnetd.c
@@ -24,7 +24,8 @@
 
 #define FOR_telnetd
 #include "toys.h"
-#include <utmp.h>
+#include <arpa/telnet.h>
+
 GLOBALS(
     char *login_path;
     char *issue_path;
@@ -36,20 +37,6 @@
     pid_t fork_pid;
 )
 
-
-# define IAC         255  /* interpret as command: */
-# define DONT        254  /* you are not to use option */
-# define DO          253  /* please, you use option */
-# define WONT        252  /* I won't use option */
-# define WILL        251  /* I will use option */
-# define SB          250  /* interpret as subnegotiation */
-# define SE          240  /* end sub negotiation */
-# define NOP         241  /* No Operation */
-# define TELOPT_ECHO   1  /* echo */
-# define TELOPT_SGA    3  /* suppress go ahead */
-# define TELOPT_TTYPE 24  /* terminal type */
-# define TELOPT_NAWS  31  /* window size */
-
 #define BUFSIZE 4*1024
 struct term_session {
   int new_fd, pty_fd;
@@ -109,30 +96,13 @@
   else ((struct sockaddr_in6*)buf)->sin6_port = port_num;
 }
 
-static void utmp_entry(void)
-{               
-  struct utmp entry;
-  struct utmp *utp_ptr;
-  pid_t pid = getpid();
-
-  utmpname(_PATH_UTMP);
-  setutent(); //start from start
-  while ((utp_ptr = getutent()) != NULL) {
-    if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
-  }             
-  if (!utp_ptr) entry.ut_type = DEAD_PROCESS;
-  time(&entry.ut_time);  
-  setutent();   
-  pututline(&entry);     
-}
-
 static int listen_socket(void)
 {
   int s, af = AF_INET, yes = 1;
   char buf[sizeof(struct sockaddr_storage)];
 
   memset(buf, 0, sizeof(buf));
-  if (toys.optflags & FLAG_b) {
+  if (FLAG(b)) {
     get_sockaddr(TT.host_addr, buf);
     af = ((struct sockaddr *)buf)->sa_family;
   } else {
@@ -140,8 +110,7 @@
     ((struct sockaddr_in*)buf)->sin_family = af;
   }
   s = xsocket(af, SOCK_STREAM, 0);
-  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1) 
-    perror_exit("setsockopt");
+  xsetsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
 
   xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
           (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
@@ -178,27 +147,27 @@
 
 static int new_session(int sockfd)
 {
-  char *argv_login[2]; //arguments for execvp cmd, NULL
+  char *argv_login[] = {NULL, "-h", NULL, NULL};
   char tty_name[30]; //tty name length.
-  int fd, flags, i = 1;
+  int fd, i = 1;
   char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
     IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
+  struct sockaddr_storage sa;
+  socklen_t sl = sizeof(sa);
 
   setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
-  flags = fcntl(sockfd, F_GETFL);
-  fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
-  if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
 
-  writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
-  if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) {
-    flags = fcntl(fd, F_GETFL);
-    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-    return fd;
-  }
+  writeall(FLAG(i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
+  if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) return fd;
   if (TT.fork_pid < 0) perror_exit("fork");
+
+  if (getpeername(sockfd, (void *)&sa, &sl)) perror_exit("getpeername");
+  if (getnameinfo((void *)&sa, sl, toybuf, sizeof(toybuf), NULL, 0, NI_NUMERICHOST))
+    perror_exit("getnameinfo");
+
   write_issue(tty_name);
-  argv_login[0] = strdup(TT.login_path);
-  argv_login[1] = NULL;
+  argv_login[0] = TT.login_path;
+  argv_login[2] = toybuf;
   execvp(argv_login[0], argv_login);
   exit(EXIT_FAILURE);
 }
@@ -300,21 +269,19 @@
 
 void telnetd_main(void)
 {
-  errno = 0;
   fd_set rd, wr;
   struct term_session *tm = NULL;
   struct timeval tv, *tv_ptr = NULL;
   int pty_fd, new_fd, c = 0, w, master_fd = 0;
-  int inetd_m = toys.optflags & FLAG_i;
 
-  if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login";
-  if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net";
-  if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F;
-  if (!inetd_m) {
+  if (!FLAG(l)) TT.login_path = "/bin/login";
+  if (!FLAG(f)) TT.issue_path = "/etc/issue.net";
+  if (FLAG(w)) toys.optflags |= FLAG_F;
+  if (!FLAG(i)) {
     master_fd = listen_socket();
     fcntl(master_fd, F_SETFD, FD_CLOEXEC);
     if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
-    if (!(toys.optflags & FLAG_F)) daemon(0, 0); 
+    if (!FLAG(F)) daemon(0, 0);
   } else {
     pty_fd = new_session(master_fd); //master_fd = 0
     if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
@@ -328,7 +295,7 @@
     } else session_list = tm;
   }
 
-  if ((toys.optflags & FLAG_w) && !session_list) {
+  if (FLAG(w) && !session_list) {
     tv.tv_sec = TT.w_sec;
     tv.tv_usec = 0;
     tv_ptr = &tv;
@@ -338,7 +305,7 @@
   for (;;) {
     FD_ZERO(&rd);
     FD_ZERO(&wr);
-    if (!inetd_m) FD_SET(master_fd, &rd);
+    if (!FLAG(i)) FD_SET(master_fd, &rd);
 
     tm = session_list;
     while (tm) {
@@ -354,10 +321,10 @@
 
 
     int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
-    if (!r) return; //timeout
+    if (!r) error_exit("select timed out");
     if (r < -1) continue;
 
-    if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection
+    if (!FLAG(i) && FD_ISSET(master_fd, &rd)) { //accept new connection
       new_fd = accept(master_fd, NULL, NULL);
       if (new_fd < 0) continue;
       tv_ptr = NULL;
@@ -382,13 +349,21 @@
         if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
                 BUFSIZE-tm->buff1_avail)) <= 0) break;
         tm->buff1_avail += c;
-        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
+        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
                 tm->buff1_avail - tm->buff1_written)) < 0) break;
         tm->buff1_written += w;
       }
       if (FD_ISSET(tm->new_fd, &rd)) {
         if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
-                BUFSIZE-tm->buff2_avail)) <= 0) break;
+                BUFSIZE-tm->buff2_avail)) <= 0) {
+          // The other side went away without a proper shutdown. Happens if
+          // you exit telnet via ^]^D, leaving the socket in TIME_WAIT.
+          xclose(tm->new_fd);
+          tm->new_fd = -1;
+          xclose(tm->pty_fd);
+          tm->pty_fd = -1;
+          break;
+        }
         c = handle_iacs(tm, c, tm->pty_fd);
         tm->buff2_avail += c;
         if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, 
@@ -401,7 +376,7 @@
         tm->buff2_written += w;
       }
       if (FD_ISSET(tm->new_fd, &wr)) {
-        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
+        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
                 tm->buff1_avail - tm->buff1_written)) < 0) break;
         tm->buff1_written += w;
       }
@@ -421,20 +396,19 @@
       // funny little dance to avoid race conditions.
       toys.signal = 0;
       pid = waitpid(-1, &status, WNOHANG);
-      if (pid < 0) break;
+      if (pid <= 0) break;
       toys.signal++;
 
-
       for (tm = session_list; tm; tm = tm->next) {
         if (tm->child_pid == pid) break;
         prev = tm;
       }
-      if (!tm) return; // reparented child we don't care about
+      if (!tm) error_exit("unexpected reparenting of %d", pid);
 
-      if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS);
+      if (FLAG(i)) exit(EXIT_SUCCESS);
+
       if (!prev) session_list = session_list->next;
       else prev->next = tm->next;
-      utmp_entry();
       xclose(tm->pty_fd);
       xclose(tm->new_fd);
       free(tm);
diff --git a/toys/pending/tftp.c b/toys/pending/tftp.c
index 1e32fdf..80f1439 100644
--- a/toys/pending/tftp.c
+++ b/toys/pending/tftp.c
@@ -132,7 +132,7 @@
  * Recieves data from server in BUFF with socket SD and updates FROM
  * and returns read length.
  */
-static ssize_t read_server(int sd, void *buf, size_t len,
+static int read_server(int sd, void *buf, int len,
   struct sockaddr_storage *from)
 {
   socklen_t alen;
@@ -214,7 +214,7 @@
   uint16_t *port, uint16_t *blockno)
 {
   struct sockaddr_storage from;
-  ssize_t nbytes;
+  int nbytes;
   uint16_t opcode, rblockno;
   int packetlen, retry;
 
@@ -434,12 +434,12 @@
   struct addrinfo *info, rp, *res=0;
   int ret;
 
-  if (toys.optflags & FLAG_r) {
-    if (!(toys.optflags & FLAG_l)) {
+  if (FLAG(r)) {
+    if (!FLAG(l)) {
       char *slash = strrchr(TT.remote_file, '/');
       TT.local_file = (slash) ? slash + 1 : TT.remote_file;
     }
-  } else if (toys.optflags & FLAG_l) TT.remote_file = TT.local_file;
+  } else if (FLAG(l)) TT.remote_file = TT.local_file;
   else error_exit("Please provide some files.");
 
   memset(&rp, 0, sizeof(rp));
@@ -457,6 +457,6 @@
   memcpy((void *)&TT.inaddr, info->ai_addr, info->ai_addrlen);
   freeaddrinfo(info);
 
-  if (toys.optflags & FLAG_g) file_get();
-  if (toys.optflags & FLAG_p) file_put();
+  if (FLAG(g)) file_get();
+  if (FLAG(p)) file_put();
 }
diff --git a/toys/pending/tftpd.c b/toys/pending/tftpd.c
index b5d0558..f945aba 100644
--- a/toys/pending/tftpd.c
+++ b/toys/pending/tftpd.c
@@ -102,8 +102,8 @@
   if (TT.pw) xsetuser(TT.pw);
 
   if (opcode == TFTPD_OP_RRQ) fd = open(file, O_RDONLY, 0666);
-  else fd = open(file, ((toys.optflags & FLAG_c) ?
-        (O_WRONLY|O_TRUNC|O_CREAT) : (O_WRONLY|O_TRUNC)) , 0666);
+  else fd = open(file,
+    FLAG(c) ? (O_WRONLY|O_TRUNC|O_CREAT) : (O_WRONLY|O_TRUNC), 0666);
   if (fd < 0) {
     g_errpkt[3] = TFTPD_ER_NOSUCHFILE;
     send_errpkt(dstaddr, socklen, "can't open file");
@@ -263,9 +263,9 @@
   // request is either upload or Download.
   opcode = buf[1];
   if (((opcode != TFTPD_OP_RRQ) && (opcode != TFTPD_OP_WRQ))
-      || ((opcode == TFTPD_OP_WRQ) && (toys.optflags & FLAG_r))) {
+      || ((opcode == TFTPD_OP_WRQ) && FLAG(r))) {
     send_errpkt((struct sockaddr*)&dstaddr, socklen,
-    	(opcode == TFTPD_OP_WRQ) ? "write error" : "packet format error");
+      (opcode == TFTPD_OP_WRQ) ? "write error" : "packet format error");
     return;
   }
 
@@ -304,5 +304,5 @@
   //do send / receive file.
   do_action((struct sockaddr*)&srcaddr, (struct sockaddr*)&dstaddr,
       socklen, toybuf + 2, opcode, tsize, blksize);
-  if (CFG_TOYBOX_FREE) close(STDIN_FILENO);
+  if (CFG_TOYBOX_FREE) close(0);
 }
diff --git a/toys/pending/tr.c b/toys/pending/tr.c
index 9a823f6..e68ae46 100644
--- a/toys/pending/tr.c
+++ b/toys/pending/tr.c
@@ -210,26 +210,21 @@
 
 static void print_map(char *set1, char *set2)
 {
-  int r = 0, i, prev_char = -1;
+  int n, src, dst, prev = -1;
 
-  while (1)
-  {
-    i = 0;
-    r = read(STDIN_FILENO, (toybuf), sizeof(toybuf));
-    if (!r) break;
-    for (;r > i;i++) {
+  while ((n = read(0, toybuf, sizeof(toybuf)))) {
+    if (!FLAG(d) && !FLAG(s)) {
+      for (dst = 0; dst < n; dst++) toybuf[dst] = TT.map[toybuf[dst]];
+    } else {
+      for (src = dst = 0; src < n; src++) {
+        int ch = TT.map[toybuf[src]];
 
-      if ((toys.optflags & FLAG_d) && (TT.map[(int)toybuf[i]] & 0x100)) continue;
-      if (toys.optflags & FLAG_s) {
-        if ((TT.map[(int)toybuf[i]] & 0x200) &&
-            (prev_char == TT.map[(int)toybuf[i]])) {
-          continue;
-        }
+        if (FLAG(d) && (ch & 0x100)) continue;
+        if (FLAG(s) && ((ch & 0x200) && prev == ch)) continue;
+        toybuf[dst++] = prev = ch;
       }
-      xputc(TT.map[(int)toybuf[i]] & 0xFF);
-      prev_char = TT.map[(int)toybuf[i]];
-      fflush(stdout);
     }
+    xwrite(1, toybuf, dst);
   }
 }
 
diff --git a/toys/pending/traceroute.c b/toys/pending/traceroute.c
index 1cfdc48..c6bcfc3 100644
--- a/toys/pending/traceroute.c
+++ b/toys/pending/traceroute.c
@@ -218,7 +218,7 @@
 
       fflush(NULL);
       if (!TT.istraceroute6)
-        if (probe && (toys.optflags & FLAG_z)) usleep(TT.pause_time * 1000);
+        if (probe && (toys.optflags & FLAG_z)) msleep(TT.pause_time);
 
       if (!TT.istraceroute6) send_probe4(++seq, ttl);
       else send_probe6(++seq, ttl);
@@ -477,7 +477,7 @@
 
 void traceroute_main(void)
 {
-  unsigned opt_len = 0, pack_size = 0, tyser = 0;
+  unsigned pack_size = 0, tyser = 0;
   int lsrr = 0, set = 1;
   
   if(!(toys.optflags & FLAG_4) && 
@@ -499,7 +499,6 @@
         resolve_addr(node->arg, AF_INET, SOCK_STREAM, 0, &sin);
         TT.gw_list[lsrr] = sin.sin_addr.s_addr;
       }
-      opt_len = (lsrr + 1) * sizeof(TT.gw_list[0]);
   } else TT.first_ttl = 1;
 
   TT.msg_len = pack_size = ICMP_HD_SIZE4; //udp payload is also 8bytes
diff --git a/toys/pending/unicode.c b/toys/pending/unicode.c
new file mode 100644
index 0000000..0a9eb24
--- /dev/null
+++ b/toys/pending/unicode.c
@@ -0,0 +1,65 @@
+/* unicode.c - convert between Unicode and UTF-8
+ *
+ * Copyright 2020 The Android Open Source Project.
+ *
+ * Loosely based on the Plan9/Inferno unicode(1).
+
+USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+
+config UNICODE
+  bool "unicode"
+  default n
+  help
+    usage: unicode [[min]-max]
+
+    Convert between Unicode code points and UTF-8, in both directions.
+*/
+
+#define FOR_unicode
+#include "toys.h"
+
+static void codepoint(unsigned wc) {
+  char *low="NULSOHSTXETXEOTENQACKBELBS HT LF VT FF CR SO SI DLEDC1DC2DC3DC4"
+            "NAKSYNETBCANEM SUBESCFS GS RS US ";
+  unsigned n, i;
+
+  printf("U+%04X : ", wc);
+  if (wc < ' ') printf("%.3s", low+(wc*3));
+  else if (wc == 0x7f) printf("DEL");
+  else {
+    toybuf[n = wctoutf8(toybuf, wc)] = 0;
+    printf("%s%s", toybuf, n>1 ? " :":"");
+    if (n>1) for (i = 0; i < n; i++) printf(" %#02x", toybuf[i]);
+  }
+  xputc('\n');
+}
+
+void unicode_main(void)
+{
+  unsigned from, to;
+  char next, **args;
+
+  for (args = toys.optargs; *args; args++) {
+    // unicode 660-666 => table of `U+0600 : ٠ : 0xd9 0xa0` etc.
+    if (sscanf(*args, "%x-%x%c", &from, &to, &next) == 2) {
+      while (from <= to) codepoint(from++);
+
+    // unicode 666 => just `U+0666 : ٦ : 0xd9 0xa6`.
+    } else if (sscanf(*args, "%x%c", &from, &next) == 1) {
+      codepoint(from);
+
+    // unicode hello => table showing every character in the string.
+    } else {
+      char *s = *args;
+      size_t l = strlen(s);
+      wchar_t wc;
+      int n;
+
+      while ((n = utf8towc(&wc, s, l)) > 0) {
+        codepoint(wc);
+        s += n;
+        l -= n;
+      }
+    }
+  }
+}
diff --git a/toys/pending/vi.c b/toys/pending/vi.c
index c6f44ef..da43d5d 100644
--- a/toys/pending/vi.c
+++ b/toys/pending/vi.c
@@ -21,37 +21,28 @@
 #include "toys.h"
 
 GLOBALS(
-    char *s;
-    int cur_col;
-    int cur_row;
-    int scr_row;
-    int drawn_row;
-    int drawn_col;
-    unsigned screen_height;
-    unsigned screen_width;
-    int vi_mode;
-    int count0;
-    int count1;
-    int vi_mov_flag;
-    int modified;
-    char vi_reg;
-    char *last_search;
-    int tabstop;
-    int list;
-    struct str_line {
-      int alloc;
-      int len;
-      char *data;
-    } *il;
-    size_t screen; //offset in slices must be higher than cursor
-    size_t cursor; //offset in slices
-    //yank buffer
-    struct yank_buf {
-      char reg;
-      int alloc;
-      char* data;
-    } yank;
+  char *s;
+  int vi_mode, tabstop, list;
+  int cur_col, cur_row, scr_row;
+  int drawn_row, drawn_col;
+  int count0, count1, vi_mov_flag;
+  unsigned screen_height, screen_width;
+  char vi_reg, *last_search;
+  struct str_line {
+    int alloc;
+    int len;
+    char *data;
+  } *il;
+  size_t screen, cursor; //offsets
+  //yank buffer
+  struct yank_buf {
+    char reg;
+    int alloc;
+    char* data;
+  } yank;
 
+  int modified;
+  size_t filesize;
 // mem_block contains RO data that is either original file as mmap
 // or heap allocated inserted data
 //
@@ -85,47 +76,58 @@
       const char *data;
     } *node;
   } *slices;
-
-  size_t filesize;
-  int fd; //file_handle
-
 )
 
 static const char *blank = " \n\r\t";
 static const char *specials = ",.:;=-+*/(){}<>[]!@#$%^&|\\?\"\'";
 
-// TT.vi_mov_flag is used for special cases when certain move
-// acts differently depending is there DELETE/YANK or NOP
-// Also commands such as G does not default to count0=1
-// 0x1 = Command needs argument (f,F,r...)
-// 0x2 = Move 1 right on yank/delete/insert (e, $...)
-// 0x4 = yank/delete last line fully
-// 0x10000000 = redraw after cursor needed
-// 0x20000000 = full redraw needed
-// 0x40000000 = count0 not given
-// 0x80000000 = move was reverse
+//get utf8 length and width at same time
+static int utf8_lnw(int *width, char *s, int bytes)
+{
+  wchar_t wc;
+  int length = 1;
 
+  if (*s == '\t') *width = TT.tabstop;
+  else {
+    length = utf8towc(&wc, s, bytes);
+    if (length < 1) length = 0, *width = 0;
+    else *width = wcwidth(wc);
+  }
+  return length;
+}
 
-static void draw_page();
+static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
+{
+  int len = 0;
+  char *c = utf8_scratch;
+  c[*sta_p] = key;
+  if (!(*sta_p))  *c = key;
+  if (*c < 0x7F) { *sta_p = 1; return 1; }
+  if ((*c & 0xE0) == 0xc0) len = 2;
+  else if ((*c & 0xF0) == 0xE0 ) len = 3;
+  else if ((*c & 0xF8) == 0xF0 ) len = 4;
+  else {*sta_p = 0; return 0; }
 
-//utf8 support
-static int utf8_lnw(int* width, char* str, int bytes);
-static int utf8_dec(char key, char *utf8_scratch, int *sta_p);
-static char* utf8_last(char* str, int size);
+  (*sta_p)++;
 
+  if (*sta_p == 1) return 0;
+  if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
 
-static int cur_left(int count0, int count1, char* unused);
-static int cur_right(int count0, int count1, char* unused);
-static int cur_up(int count0, int count1, char* unused);
-static int cur_down(int count0, int count1, char* unused);
-static void check_cursor_bounds();
-static void adjust_screen_buffer();
-static int search_str(char *s);
+  if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
 
-//from TT.cursor to
-static int vi_yank(char reg, size_t from, int flags);
-static int vi_delete(char reg, size_t from, int flags);
+  return 0;
+}
 
+static char* utf8_last(char* str, int size)
+{
+  char* end = str+size;
+  int pos = size, len, width = 0;
+  for (;pos >= 0; end--, pos--) {
+    len = utf8_lnw(&width, end, size-pos);
+    if (len && width) return end;
+  }
+  return 0;
+}
 
 struct double_list *dlist_add_before(struct double_list **head,
   struct double_list **list, char *data)
@@ -254,27 +256,21 @@
       spos += s->node->len;
       offset += s->node->len;
       s = dlist_pop(&s);
-
       if (s == TT.slices) TT.slices = s->next;
-    }
 
-    else if (spos < offset && ( end >= spos+s->node->len)) {
+    } else if (spos < offset && ( end >= spos+s->node->len)) {
       //cut end
       size_t clip = s->node->len - (offset - spos);
       offset = spos+s->node->len;
       spos += s->node->len;
       s->node->len -= clip;
-    }
-
-    else if (spos == offset && s == e) {
+    } else if (spos == offset && s == e) {
       //cut begin
       size_t clip = end - offset;
       s->node->len -= clip;
       s->node->data += clip;
       break;
-    }
-
-    else {
+    } else {
       //cut middle
       struct slice *tail = xmalloc(sizeof(struct slice));
       size_t clip = end-offset;
@@ -410,7 +406,7 @@
     if (!state) return -1;
 
   if (!finished && !state) return -1;
-  if (dest) memcpy(dest,scratch,8);
+  if (dest) memcpy(dest, scratch, 8);
 
   return strlen(scratch);
 }
@@ -512,15 +508,8 @@
 static void linelist_unload()
 {
   llist_traverse((void *)TT.slices, llist_free_double);
-  TT.slices = 0;
-
   llist_traverse((void *)TT.text, block_list_free);
-  TT.text = 0;
-
-  if (TT.fd) {
-    xclose(TT.fd);
-    TT.fd = 0;
-  }
+  TT.slices = 0, TT.text = 0;
 }
 
 static int linelist_load(char *filename)
@@ -528,20 +517,15 @@
   if (!filename) filename = (char*)*toys.optargs;
 
   if (filename) {
-    int fd;
-    size_t len, size;
+    int fd = open(filename, O_RDONLY);
+    size_t size;
     char *data;
-    if ( (fd = open(filename, O_RDONLY)) <0) return 0;
 
-    size = fdlength(fd);
-    if (!(len = lseek(fd, 0, SEEK_END))) len = size;
-    lseek(fd, 0, SEEK_SET);
-
-    data = xmmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
-    if (data == MAP_FAILED) return 0;
-    insert_str(data, 0, size, len, MMAP);
+    if (fd == -1) return 0;
+    data = xmmap(0, size = fdlength(fd), PROT_READ, MAP_SHARED, fd, 0);
+    xclose(fd);
+    insert_str(data, 0, size, size, MMAP);
     TT.filesize = text_filesize();
-    TT.fd = fd;
   }
 
   return 1;
@@ -575,6 +559,236 @@
 
 }
 
+//jump into valid offset index
+//and valid utf8 codepoint
+static void check_cursor_bounds()
+{
+  char buf[8] = {0};
+  int len, width = 0;
+  if (!TT.filesize) TT.cursor = 0;
+
+  for (;;) {
+    if (TT.cursor < 1) {
+      TT.cursor = 0;
+      return;
+    } else if (TT.cursor >= TT.filesize-1) {
+      TT.cursor = TT.filesize-1;
+      return;
+    }
+    if ((len = text_codepoint(buf, TT.cursor)) < 1) {
+      TT.cursor--; //we are not in valid data try jump over
+      continue;
+    }
+    if (utf8_lnw(&width, buf, len) && width) break;
+    else TT.cursor--; //combine char jump over
+  }
+}
+
+// TT.vi_mov_flag is used for special cases when certain move
+// acts differently depending is there DELETE/YANK or NOP
+// Also commands such as G does not default to count0=1
+// 0x1 = Command needs argument (f,F,r...)
+// 0x2 = Move 1 right on yank/delete/insert (e, $...)
+// 0x4 = yank/delete last line fully
+// 0x10000000 = redraw after cursor needed
+// 0x20000000 = full redraw needed
+// 0x40000000 = count0 not given
+// 0x80000000 = move was reverse
+
+//TODO rewrite the logic, difficulties counting lines
+//and with big files scroll should not rely in knowing
+//absoluteline numbers
+static void adjust_screen_buffer()
+{
+  size_t c, s;
+  TT.cur_row = 0, TT.scr_row = 0;
+  if (!TT.cursor) {
+    TT.screen = 0;
+    TT.vi_mov_flag = 0x20000000;
+    return;
+  } else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) {
+     //give up, file is big, do full redraw
+
+    TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
+    TT.vi_mov_flag = 0x20000000;
+    return;
+  }
+
+  s = text_count(0, TT.screen, '\n');
+  c = text_count(0, TT.cursor, '\n');
+  if (s >= c) {
+    TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
+    s = c;
+    TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
+  } else {
+    int distance = c-s+1;
+    if (distance > (int)TT.screen_height) {
+      int n, adj = distance-TT.screen_height;
+      TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
+      for (;adj; adj--, s++)
+        if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen)
+          TT.screen = n+1;
+    }
+  }
+
+  TT.scr_row = s;
+  TT.cur_row = c;
+
+}
+
+//TODO search yank buffer by register
+//TODO yanks could be separate slices so no need to copy data
+//now only supports default register
+static int vi_yank(char reg, size_t from, int flags)
+{
+  size_t start = from, end = TT.cursor;
+  char *str;
+
+  memset(TT.yank.data, 0, TT.yank.alloc);
+  if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
+  else TT.cursor = start; //yank moves cursor to left pos always?
+
+  if (TT.yank.alloc < end-from) {
+    size_t new_bounds = (1+end-from)/1024;
+    new_bounds += ((1+end-from)%1024) ? 1 : 0;
+    new_bounds *= 1024;
+    TT.yank.data = xrealloc(TT.yank.data, new_bounds);
+    TT.yank.alloc = new_bounds;
+  }
+
+  //this is naive copy
+  for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start);
+
+  *str = 0;
+
+  return 1;
+}
+
+static int vi_delete(char reg, size_t from, int flags)
+{
+  size_t start = from, end = TT.cursor;
+
+  vi_yank(reg, from, flags);
+
+  if (TT.vi_mov_flag&0x80000000)
+    start = TT.cursor, end = from;
+
+  //pre adjust cursor move one right until at next valid rune
+  if (TT.vi_mov_flag&2) {
+    //TODO
+  }
+  //do slice cut
+  cut_str(start, end-start);
+
+  //cursor is at start at after delete
+  TT.cursor = start;
+  TT.filesize = text_filesize();
+  //find line start by strrchr(/n) ++
+  //set cur_col with crunch_n_str maybe?
+  TT.vi_mov_flag |= 0x30000000;
+
+  return 1;
+}
+
+static int vi_change(char reg, size_t to, int flags)
+{
+  vi_delete(reg, to, flags);
+  TT.vi_mode = 2;
+  return 1;
+}
+
+static int cur_left(int count0, int count1, char *unused)
+{
+  int count = count0*count1;
+  TT.vi_mov_flag |= 0x80000000;
+  for (;count && TT.cursor; count--) {
+    TT.cursor--;
+    if (text_byte(TT.cursor) == '\n') TT.cursor++;
+    check_cursor_bounds();
+  }
+  return 1;
+}
+
+static int cur_right(int count0, int count1, char *unused)
+{
+  int count = count0*count1, len, width = 0;
+  char buf[8] = {0};
+
+  for (;count; count--) {
+    len = text_codepoint(buf, TT.cursor);
+
+    if (*buf == '\n') break;
+    else if (len > 0) TT.cursor += len;
+    else TT.cursor++;
+
+    for (;TT.cursor < TT.filesize;) {
+      if ((len = text_codepoint(buf, TT.cursor)) < 1) {
+        TT.cursor++; //we are not in valid data try jump over
+        continue;
+      }
+
+      if (utf8_lnw(&width, buf, len) && width) break;
+      else TT.cursor += len;
+    }
+  }
+  check_cursor_bounds();
+  return 1;
+}
+
+//TODO column shift
+static int cur_up(int count0, int count1, char *unused)
+{
+  int count = count0*count1;
+  for (;count--;) TT.cursor = text_psol(TT.cursor);
+
+  TT.vi_mov_flag |= 0x80000000;
+  check_cursor_bounds();
+  return 1;
+}
+
+//TODO column shift
+static int cur_down(int count0, int count1, char *unused)
+{
+  int count = count0*count1;
+  for (;count--;) TT.cursor = text_nsol(TT.cursor);
+  check_cursor_bounds();
+  return 1;
+}
+
+static int vi_H(int count0, int count1, char *unused)
+{
+  TT.cursor = text_sol(TT.screen);
+  return 1;
+}
+
+static int vi_L(int count0, int count1, char *unused)
+{
+  TT.cursor = text_sol(TT.screen);
+  cur_down(TT.screen_height-1, 1, 0);
+  return 1;
+}
+
+static int vi_M(int count0, int count1, char *unused)
+{
+  TT.cursor = text_sol(TT.screen);
+  cur_down(TT.screen_height/2, 1, 0);
+  return 1;
+}
+
+static int search_str(char *s)
+{
+  size_t pos = text_strstr(TT.cursor+1, s);
+
+  if (TT.last_search != s) {
+    free(TT.last_search);
+    TT.last_search = xstrdup(s);
+  }
+
+  if (pos != SIZE_MAX) TT.cursor = pos;
+  check_cursor_bounds();
+  return 0;
+}
+
 static int vi_yy(char reg, int count0, int count1)
 {
   size_t history = TT.cursor;
@@ -592,7 +806,7 @@
 static int vi_dd(char reg, int count0, int count1)
 {
   size_t pos = text_sol(TT.cursor); //go left to first char on line
-  TT.vi_mov_flag |= 0x4;
+  TT.vi_mov_flag |= 0x30000000;
 
   for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
 
@@ -620,7 +834,7 @@
   return 1;
 }
 
-static int vi_movw(int count0, int count1, char* unused)
+static int vi_movw(int count0, int count1, char *unused)
 {
   int count = count0*count1;
   while (count--) {
@@ -653,7 +867,7 @@
   return 1;
 }
 
-static int vi_movb(int count0, int count1, char* unused)
+static int vi_movb(int count0, int count1, char *unused)
 {
   int count = count0*count1;
   int type = 0;
@@ -717,7 +931,7 @@
 }
 
 
-static void i_insert(char* str, int len)
+static void i_insert(char *str, int len)
 {
   if (!str || !len) return;
 
@@ -735,15 +949,59 @@
   return 1;
 }
 
-static int vi_eol(int count0, int count1, char *unused)
+static int vi_dollar(int count0, int count1, char *unused)
 {
-  //forward find /n
-  TT.cursor = text_strchr(TT.cursor, '\n');
-  TT.vi_mov_flag |= 2;
-  check_cursor_bounds();
+  size_t new = text_strchr(TT.cursor, '\n');
+
+  if (new != TT.cursor) {
+    TT.cursor = new - 1;
+    TT.vi_mov_flag |= 2;
+    check_cursor_bounds();
+  }
   return 1;
 }
 
+static void vi_eol()
+{
+  TT.cursor = text_strchr(TT.cursor, '\n');
+  check_cursor_bounds();
+}
+
+static void ctrl_b()
+{
+  int i;
+
+  for (i=0; i<TT.screen_height-2; ++i) {
+    TT.screen = text_psol(TT.screen);
+    // TODO: retain x offset.
+    TT.cursor = text_psol(TT.screen);
+  }
+}
+
+static void ctrl_f()
+{
+  int i;
+
+  for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
+  // TODO: real vi keeps the x position.
+  if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+}
+
+static void ctrl_e()
+{
+  TT.screen = text_nsol(TT.screen);
+  // TODO: real vi keeps the x position.
+  if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+}
+
+static void ctrl_y()
+{
+  TT.screen = text_psol(TT.screen);
+  // TODO: only if we're on the bottom line
+  TT.cursor = text_psol(TT.cursor);
+  // TODO: real vi keeps the x position.
+}
+
 //TODO check register where to push from
 static int vi_push(char reg, int count0, int count1)
 {
@@ -807,56 +1065,40 @@
   return 1;
 }
 
-static int vi_delete(char reg, size_t from, int flags)
+static int vi_o(char reg, int count0, int count1)
 {
-  size_t start = from, end = TT.cursor;
-
-  vi_yank(reg, from, flags);
-
-  if (TT.vi_mov_flag&0x80000000)
-    start = TT.cursor, end = from;
-
-  //pre adjust cursor move one right until at next valid rune
-  if (TT.vi_mov_flag&2) {
-    //int len, width;
-    //char *s = end->line->data;
-    //len = utf8_lnw(&width, s+col_e, strlen(s+col_e));
-    //for (;;) {
-      //col_e += len;
-      //len = utf8_lnw(&width, s+col_e, strlen(s+col_e));
-      //if (len<1 || width || !(*(s+col_e))) break;
-    //}
-  }
-  //find if range contains atleast single /n
-  //if so set TT.vi_mov_flag |= 0x10000000;
-
-  //do slice cut
-  cut_str(start, end-start);
-
-  //cursor is at start at after delete
-  TT.cursor = start;
-  TT.filesize = text_filesize();
-  //find line start by strrchr(/n) ++
-  //set cur_col with crunch_n_str maybe?
-
+  TT.cursor = text_eol(TT.cursor);
+  insert_str(xstrdup("\n"), TT.cursor++, 1, 1, HEAP);
+  TT.vi_mov_flag |= 0x30000000;
+  TT.vi_mode = 2;
   return 1;
 }
 
+static int vi_O(char reg, int count0, int count1)
+{
+  TT.cursor = text_psol(TT.cursor);
+  return vi_o(reg, count0, count1);
+}
 
 static int vi_D(char reg, int count0, int count1)
 {
   size_t pos = TT.cursor;
   if (!count0) return 1;
-  vi_eol(1, 1, 0);
+  vi_eol();
   vi_delete(reg, pos, 0);
-  count0--;
-  if (count0) {
-    vi_dd(reg, count0, 1);
-  }
+  if (--count0) vi_dd(reg, count0, 1);
+
   check_cursor_bounds();
   return 1;
 }
 
+static int vi_I(char reg, int count0, int count1)
+{
+  TT.cursor = text_sol(TT.cursor);
+  TT.vi_mode = 2;
+  return 1;
+}
+
 static int vi_join(char reg, int count0, int count1)
 {
   size_t next;
@@ -875,41 +1117,6 @@
   return 1;
 }
 
-static int vi_change(char reg, size_t to, int flags)
-{
-  vi_delete(reg, to, flags);
-  TT.vi_mode = 2;
-  return 1;
-}
-
-//TODO search yank buffer by register
-//TODO yanks could be separate slices so no need to copy data
-//now only supports default register
-static int vi_yank(char reg, size_t from, int flags)
-{
-  size_t start = from, end = TT.cursor;
-  char *str;
-
-  memset(TT.yank.data, 0, TT.yank.alloc);
-  if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
-  else TT.cursor = start; //yank moves cursor to left pos always?
-
-  if (TT.yank.alloc < end-from) {
-    size_t new_bounds = (1+end-from)/1024;
-    new_bounds += ((1+end-from)%1024) ? 1 : 0;
-    new_bounds *= 1024;
-    TT.yank.data = xrealloc(TT.yank.data, new_bounds);
-    TT.yank.alloc = new_bounds;
-  }
-
-  //this is naive copy
-  for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start);
-
-  *str = 0;
-
-  return 1;
-}
-
 //NOTES
 //vi-mode cmd syntax is
 //("[REG])[COUNT0]CMD[COUNT1](MOV)
@@ -942,13 +1149,16 @@
 };
 struct vi_special_param vi_special[] =
 {
+  {"D", &vi_D},
+  {"I", &vi_I},
+  {"J", &vi_join},
+  {"O", &vi_O},
+  {"n", &vi_find_next},
+  {"o", &vi_o},
+  {"p", &vi_push},
+  {"x", &vi_x},
   {"dd", &vi_dd},
   {"yy", &vi_yy},
-  {"D", &vi_D},
-  {"J", &vi_join},
-  {"n", &vi_find_next},
-  {"x", &vi_x},
-  {"p", &vi_push}
 };
 //there is around ~47 vi moves
 //some of them need extra params
@@ -959,12 +1169,15 @@
   {"b", 0, &vi_movb},
   {"e", 0, &vi_move},
   {"G", 0, &vi_go},
+  {"H", 0, &vi_H},
   {"h", 0, &cur_left},
   {"j", 0, &cur_down},
   {"k", 0, &cur_up},
+  {"L", 0, &vi_L},
   {"l", 0, &cur_right},
+  {"M", 0, &vi_M},
   {"w", 0, &vi_movw},
-  {"$", 0, &vi_eol},
+  {"$", 0, &vi_dollar},
   {"f", 1, &vi_find_c},
   {"F", 1, &vi_find_cb},
 };
@@ -1039,19 +1252,6 @@
   return 0;
 }
 
-static int search_str(char *s)
-{
-  size_t pos = text_strstr(TT.cursor+1, s);
-
-  if (TT.last_search != s) {
-    free(TT.last_search);
-    TT.last_search = xstrdup(s);
-  }
-
-  if (pos != SIZE_MAX) TT.cursor = pos;
-  check_cursor_bounds();
-  return 0;
-}
 
 static int run_ex_cmd(char *cmd)
 {
@@ -1088,14 +1288,219 @@
 
 }
 
+static int vi_crunch(FILE *out, int cols, int wc)
+{
+  int ret = 0;
+  if (wc < 32 && TT.list) {
+    tty_esc("1m");
+    ret = crunch_escape(out,cols,wc);
+    tty_esc("m");
+  } else if (wc == 0x09) {
+    if (out) {
+      int i = TT.tabstop;
+      for (;i--;) fputs(" ", out);
+    }
+    ret = TT.tabstop;
+  } else if (wc == '\n') return 0;
+  return ret;
+}
+
+//crunch_str with n bytes restriction for printing substrings or
+//non null terminated strings
+static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
+  int (*escout)(FILE *out, int cols, int wc))
+{
+  int columns = 0, col, bytes;
+  char *start, *end;
+
+  for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
+    wchar_t wc;
+
+    if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
+      if (!escmore || wc>255 || !strchr(escmore, wc)) {
+        if (width-columns<col) break;
+        if (out) fwrite(end, bytes, 1, out);
+
+        continue;
+      }
+    }
+
+    if (bytes<1) {
+      bytes = 1;
+      wc = *end;
+    }
+    col = width-columns;
+    if (col<1) break;
+    if (escout) {
+      if ((col = escout(out, col, wc))<0) break;
+    } else if (out) fwrite(end, 1, bytes, out);
+  }
+  *str = end;
+
+  return columns;
+}
+
+static void draw_page()
+{
+  unsigned y = 0;
+  int x = 0;
+
+  char *line = 0, *end = 0;
+  int bytes = 0;
+
+  //screen coordinates for cursor
+  int cy_scr = 0, cx_scr = 0;
+
+  //variables used only for cursor handling
+  int aw = 0, iw = 0, clip = 0, margin = 8;
+
+  int scroll = 0, redraw = 0;
+
+  int SSOL, SOL;
+
+
+  adjust_screen_buffer();
+  //redraw = 3; //force full redraw
+  redraw = (TT.vi_mov_flag & 0x30000000)>>28;
+
+  scroll = TT.drawn_row-TT.scr_row;
+  if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
+  else if (abs(scroll)>TT.screen_height/2) redraw = 3;
+
+  tty_jump(0, 0);
+  if (redraw&2) tty_esc("2J"), tty_esc("H");   //clear screen
+  else if (scroll>0) printf("\033[%dL", scroll);  //scroll up
+  else if (scroll<0) printf("\033[%dM", -scroll); //scroll down
+
+  SOL = text_sol(TT.cursor);
+  bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
+  line = toybuf;
+
+  for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL);
+
+  cy_scr = y;
+
+  //draw cursor row
+  /////////////////////////////////////////////////////////////
+  //for long lines line starts to scroll when cursor hits margin
+  bytes = TT.cursor-SOL; // TT.cur_col;
+  end = line;
+
+
+  tty_jump(0, y);
+  tty_esc("2K");
+  //find cursor position
+  aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
+
+  //if we need to render text that is not inserted to buffer yet
+  if (TT.vi_mode == 2 && TT.il->len) {
+    char* iend = TT.il->data; //input end
+    x = 0;
+    //find insert end position
+    iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
+    clip = (aw+iw) - TT.screen_width+margin;
+
+    //if clipped area is bigger than text before insert
+    if (clip > aw) {
+      clip -= aw;
+      iend = TT.il->data;
+
+      iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
+      x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
+    } else {
+      iend = TT.il->data;
+      end = line;
+
+      //if clipped area is substring from cursor row start
+      aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
+      x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
+      x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
+    }
+  }
+  //when not inserting but still need to keep cursor inside screen
+  //margin area
+  else if ( aw+margin > TT.screen_width) {
+    clip = aw-TT.screen_width+margin;
+    end = line;
+    aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
+    x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
+  }
+  else {
+    end = line;
+    x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
+  }
+  cx_scr = x;
+  cy_scr = y;
+  x += crunch_str(&end, TT.screen_width-x,  stdout, "\t\n", vi_crunch);
+
+  //start drawing all other rows that needs update
+  ///////////////////////////////////////////////////////////////////
+  y = 0, SSOL = TT.screen, line = toybuf;
+  bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
+
+  //if we moved around in long line might need to redraw everything
+  if (clip != TT.drawn_col) redraw = 3;
+
+  for (; y < TT.screen_height; y++ ) {
+    int draw_line = 0;
+    if (SSOL == SOL) {
+      line = toybuf;
+      SSOL += bytes+1;
+      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
+      continue;
+    } else if (redraw) draw_line++;
+    else if (scroll<0 && TT.screen_height-y-1<-scroll)
+      scroll++, draw_line++;
+    else if (scroll>0) scroll--, draw_line++;
+
+    tty_jump(0, y);
+    if (draw_line) {
+      tty_esc("2K");
+      if (line && strlen(line)) {
+        aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
+        crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
+        if ( *line ) printf("@");
+      } else printf("\033[2m~\033[m");
+    }
+    if (SSOL+bytes < TT.filesize)  {
+      line = toybuf;
+      SSOL += bytes+1;
+      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
+   } else line = 0;
+  }
+
+  TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
+
+  // Finished updating visual area, show status line.
+  tty_jump(0, TT.screen_height);
+  tty_esc("2K");
+  if (TT.vi_mode == 2) printf("\033[1m-- INSERT --\033[m");
+  if (!TT.vi_mode) {
+    cx_scr = printf("%s", TT.il->data);
+    cy_scr = TT.screen_height;
+    *toybuf = 0;
+  } else {
+    // TODO: the row,col display doesn't show the cursor column
+    // TODO: real vi shows the percentage by lines, not bytes
+    sprintf(toybuf, "%zu/%zuC  %zu%%  %d,%d", TT.cursor, TT.filesize,
+      (100*TT.cursor)/TT.filesize, TT.cur_row+1, TT.cur_col+1);
+    if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
+  }
+  tty_jump(TT.screen_width-strlen(toybuf), TT.screen_height);
+  printf("%s", toybuf);
+
+  tty_jump(cx_scr, cy_scr);
+  xflush(1);
+}
+
 void vi_main(void)
 {
+  char stdout_buf[BUFSIZ];
   char keybuf[16] = {0};
   char vi_buf[16] = {0};
   char utf8_code[8] = {0};
   int utf8_dec_p = 0, vi_buf_pos = 0;
-  FILE *script = 0;
-  if (FLAG(s)) script = fopen(TT.s, "r");
+  FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
 
   TT.il = xzalloc(sizeof(struct str_line));
   TT.il->data = xzalloc(80);
@@ -1113,17 +1518,19 @@
   terminal_size(&TT.screen_width, &TT.screen_height);
   TT.screen_height -= 1;
 
+  // Avoid flicker.
+  setbuf(stdout, stdout_buf);
+
+  xsignal(SIGWINCH, generic_signal);
   set_terminal(0, 1, 0, 0);
   //writes stdout into different xterm buffer so when we exit
   //we dont get scroll log full of junk
   tty_esc("?1049h");
-  tty_esc("H");
-  xflush(1);
 
-
-  draw_page();
   for (;;) {
     int key = 0;
+
+    draw_page();
     if (script) {
       key = fgetc(script);
       if (key == EOF) {
@@ -1134,9 +1541,12 @@
     } else key = scan_key(keybuf, -1);
 
     if (key == -1) goto cleanup_vi;
-
-    terminal_size(&TT.screen_width, &TT.screen_height);
-    TT.screen_height -= 1; //TODO this is hack fix visual alignment
+    else if (key == -3) {
+      toys.signal = 0;
+      terminal_size(&TT.screen_width, &TT.screen_height);
+      TT.screen_height -= 1; //TODO this is hack fix visual alignment
+      continue;
+    }
 
     // TODO: support cursor keys in ex mode too.
     if (TT.vi_mode && key>=256) {
@@ -1145,7 +1555,10 @@
       else if (key==KEY_DOWN) cur_down(1, 1, 0);
       else if (key==KEY_LEFT) cur_left(1, 1, 0);
       else if (key==KEY_RIGHT) cur_right(1, 1, 0);
-      draw_page();
+      else if (key==KEY_HOME) vi_zero(1, 1, 0);
+      else if (key==KEY_END) vi_dollar(1, 1, 0);
+      else if (key==KEY_PGDN) ctrl_f();
+      else if (key==KEY_PGUP) ctrl_b();
       continue;
     }
 
@@ -1159,7 +1572,7 @@
           TT.il->len++;
           break;
         case 'A':
-          vi_eol(1, 1, 0);
+          vi_eol();
           TT.vi_mode = 2;
           break;
         case 'a':
@@ -1168,6 +1581,18 @@
         case 'i':
           TT.vi_mode = 2;
           break;
+        case 'B'-'@':
+          ctrl_b();
+          break;
+        case 'E'-'@':
+          ctrl_e();
+          break;
+        case 'F'-'@':
+          ctrl_f();
+          break;
+        case 'Y'-'@':
+          ctrl_y();
+          break;
         case 27:
           vi_buf[0] = 0;
           vi_buf_pos = 0;
@@ -1266,9 +1691,6 @@
           break;
       }
     }
-
-    draw_page();
-
   }
 cleanup_vi:
   linelist_unload();
@@ -1276,388 +1698,3 @@
   tty_reset();
   tty_esc("?1049l");
 }
-
-static int vi_crunch(FILE* out, int cols, int wc)
-{
-  int ret = 0;
-  if (wc < 32 && TT.list) {
-    tty_esc("1m");
-    ret = crunch_escape(out,cols,wc);
-    tty_esc("m");
-  } else if (wc == 0x09) {
-    if (out) {
-      int i = TT.tabstop;
-      for (;i--;) fputs(" ", out);
-    }
-    ret = TT.tabstop;
-  } else if (wc == '\n') return 0;
-  return ret;
-}
-
-//crunch_str with n bytes restriction for printing substrings or
-//non null terminated strings
-static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
-  int (*escout)(FILE *out, int cols, int wc))
-{
-  int columns = 0, col, bytes;
-  char *start, *end;
-
-  for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
-    wchar_t wc;
-
-    if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
-      if (!escmore || wc>255 || !strchr(escmore, wc)) {
-        if (width-columns<col) break;
-        if (out) fwrite(end, bytes, 1, out);
-
-        continue;
-      }
-    }
-
-    if (bytes<1) {
-      bytes = 1;
-      wc = *end;
-    }
-    col = width-columns;
-    if (col<1) break;
-    if (escout) {
-      if ((col = escout(out, col, wc))<0) break;
-    } else if (out) fwrite(end, 1, bytes, out);
-  }
-  *str = end;
-
-  return columns;
-}
-
-//BUGBUG cursor at eol
-static void draw_page()
-{
-  unsigned y = 0;
-  int x = 0;
-
-  char *line = 0, *end = 0;
-  int bytes = 0;
-
-  //screen coordinates for cursor
-  int cy_scr = 0, cx_scr = 0;
-
-  //variables used only for cursor handling
-  int aw = 0, iw = 0, clip = 0, margin = 8;
-
-  int scroll = 0, redraw = 0;
-
-  int SSOL, SOL;
-
-
-  adjust_screen_buffer();
-  //redraw = 3; //force full redraw
-  redraw = (TT.vi_mov_flag & 0x30000000)>>28;
-
-  scroll = TT.drawn_row-TT.scr_row;
-  if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
-  else if (abs(scroll)>TT.screen_height/2) redraw = 3;
-
-  tty_jump(0, 0);
-  if (redraw&2) tty_esc("2J"), tty_esc("H");   //clear screen
-  else if (scroll>0) printf("\033[%dL", scroll);  //scroll up
-  else if (scroll<0) printf("\033[%dM", -scroll); //scroll down
-
-  SOL = text_sol(TT.cursor);
-  bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
-  line = toybuf;
-
-  for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL);
-
-  cy_scr = y;
-
-  //draw cursor row
-  /////////////////////////////////////////////////////////////
-  //for long lines line starts to scroll when cursor hits margin
-  bytes = TT.cursor-SOL; // TT.cur_col;
-  end = line;
-
-
-  tty_jump(0, y);
-  tty_esc("2K");
-  //find cursor position
-  aw = crunch_nstr(&end, 1024, bytes, 0, "\t\n", vi_crunch);
-
-  //if we need to render text that is not inserted to buffer yet
-  if (TT.vi_mode == 2 && TT.il->len) {
-    char* iend = TT.il->data; //input end
-    x = 0;
-    //find insert end position
-    iw = crunch_str(&iend, 1024, 0, "\t\n", vi_crunch);
-    clip = (aw+iw) - TT.screen_width+margin;
-
-    //if clipped area is bigger than text before insert
-    if (clip > aw) {
-      clip -= aw;
-      iend = TT.il->data;
-
-      iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
-      x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
-    } else {
-      iend = TT.il->data;
-      end = line;
-
-      //if clipped area is substring from cursor row start
-      aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
-      x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
-      x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
-    }
-  }
-  //when not inserting but still need to keep cursor inside screen
-  //margin area
-  else if ( aw+margin > TT.screen_width) {
-    clip = aw-TT.screen_width+margin;
-    end = line;
-    aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
-    x = crunch_str(&end, aw,  stdout, "\t\n", vi_crunch);
-  }
-  else {
-    end = line;
-    x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
-  }
-  cx_scr = x;
-  cy_scr = y;
-  x += crunch_str(&end, TT.screen_width-x,  stdout, "\t\n", vi_crunch);
-
-  //start drawing all other rows that needs update
-  ///////////////////////////////////////////////////////////////////
-  y = 0, SSOL = TT.screen, line = toybuf;
-  bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
-
-  //if we moved around in long line might need to redraw everything
-  if (clip != TT.drawn_col) redraw = 3;
-
-  for (; y < TT.screen_height; y++ ) {
-    int draw_line = 0;
-    if (SSOL == SOL) {
-      line = toybuf;
-      SSOL += bytes+1;
-      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
-      continue;
-    } else if (redraw) draw_line++;
-    else if (scroll<0 && TT.screen_height-y-1<-scroll)
-      scroll++, draw_line++;
-    else if (scroll>0) scroll--, draw_line++;
-
-    tty_jump(0, y);
-    if (draw_line) {
-
-      tty_esc("2K");
-      if (line) {
-        if (draw_line && line && strlen(line)) {
-
-          aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
-          crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
-          if ( *line ) printf("@");
-
-        }
-      } else if (draw_line) printf("~");
-    }
-    if (SSOL+bytes < TT.filesize)  {
-      line = toybuf;
-      SSOL += bytes+1;
-      bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
-   } else line = 0;
-  }
-
-  TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
-
-  //finished updating visual area
-  tty_jump(0, TT.screen_height);
-  tty_esc("2K");
-  if (TT.vi_mode == 2) printf("\x1b[1m-- INSERT --\x1b[m");
-  if (!TT.vi_mode) printf("\x1b[1m%s \x1b[m",TT.il->data);
-
-  sprintf(toybuf, "%zu / %zu,%d,%d", TT.cursor, TT.filesize,
-    TT.cur_row+1, TT.cur_col+1);
-
-  if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
-
-  tty_jump(TT.screen_width-strlen(toybuf), TT.screen_height);
-  printf("%s", toybuf);
-
-  if (TT.vi_mode) tty_jump(cx_scr, cy_scr);
-
-  xflush(1);
-
-}
-//jump into valid offset index
-//and valid utf8 codepoint
-static void check_cursor_bounds()
-{
-  char buf[8] = {0};
-  int len, width = 0;
-  if (!TT.filesize) TT.cursor = 0;
-
-  for (;;) {
-    if (TT.cursor < 1) {
-      TT.cursor = 0;
-      return;
-    } else if (TT.cursor >= TT.filesize-1) {
-      TT.cursor = TT.filesize-1;
-      return;
-    }
-    if ((len = text_codepoint(buf, TT.cursor)) < 1) {
-      TT.cursor--; //we are not in valid data try jump over
-      continue;
-    }
-    if (utf8_lnw(&width, buf, len) && width) break;
-    else TT.cursor--; //combine char jump over
-  }
-}
-
-//TODO rewrite the logic, difficulties counting lines
-//and with big files scroll should not rely in knowing
-//absoluteline numbers
-static void adjust_screen_buffer()
-{
-  size_t c, s;
-  TT.cur_row = 0, TT.scr_row = 0;
-  if (!TT.cursor) {
-    TT.screen = 0;
-    TT.vi_mov_flag = 0x20000000;
-    return;
-  } else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) {
-     //give up, file is big, do full redraw
-
-    TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
-    TT.vi_mov_flag = 0x20000000;
-    return;
-  }
-
-  s = text_count(0, TT.screen, '\n');
-  c = text_count(0, TT.cursor, '\n');
-  if (s >= c) {
-    TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
-    s = c;
-    TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
-  } else {
-    int distance = c-s+1;
-    if (distance > (int)TT.screen_height) {
-      int n, adj = distance-TT.screen_height;
-      TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
-      for (;adj; adj--, s++)
-        if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen)
-          TT.screen = n+1;
-    }
-  }
-
-  TT.scr_row = s;
-  TT.cur_row = c;
-
-}
-
-//get utf8 length and width at same time
-static int utf8_lnw(int* width, char* s, int bytes)
-{
-  wchar_t wc;
-  int length;
-
-  *width = 0;
-  if (*s == '\t') {
-    *width = TT.tabstop;
-    return 1;
-  }
-  length = utf8towc(&wc, s, bytes);
-  if (length < 1) return 0;
-  *width = wcwidth(wc);
-  return length;
-}
-
-static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
-{
-  int len = 0;
-  char *c = utf8_scratch;
-  c[*sta_p] = key;
-  if (!(*sta_p))  *c = key;
-  if (*c < 0x7F) { *sta_p = 1; return 1; }
-  if ((*c & 0xE0) == 0xc0) len = 2;
-  else if ((*c & 0xF0) == 0xE0 ) len = 3;
-  else if ((*c & 0xF8) == 0xF0 ) len = 4;
-  else {*sta_p = 0; return 0; }
-
-  (*sta_p)++;
-
-  if (*sta_p == 1) return 0;
-  if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
-
-  if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
-
-  return 0;
-}
-
-static char* utf8_last(char* str, int size)
-{
-  char* end = str+size;
-  int pos = size;
-  int len = 0;
-  int width = 0;
-  while (pos >= 0) {
-    len = utf8_lnw(&width, end, size-pos);
-    if (len && width) return end;
-    end--; pos--;
-  }
-  return 0;
-}
-
-static int cur_left(int count0, int count1, char* unused)
-{
-  int count = count0*count1;
-  TT.vi_mov_flag |= 0x80000000;
-  for (;count--;) {
-    if (!TT.cursor) return 1;
-
-    TT.cursor--;
-    check_cursor_bounds();
-  }
-  return 1;
-}
-
-static int cur_right(int count0, int count1, char* unused)
-{
-  int count = count0*count1;
-  char buf[8] = {0};
-  int len, width = 0;
-  for (;count; count--) {
-    if ((len = text_codepoint(buf, TT.cursor)) > 0) TT.cursor += len;
-    else TT.cursor++;
-
-    for (;TT.cursor < TT.filesize;) {
-      if ((len = text_codepoint(buf, TT.cursor)) < 1) {
-        TT.cursor++; //we are not in valid data try jump over
-        continue;
-      }
-
-      if (utf8_lnw(&width, buf, len) && width) break;
-      else TT.cursor += len;
-    }
-    if (*buf == '\n') break;
-  }
-  check_cursor_bounds();
-  return 1;
-}
-
-//TODO column shift
-static int cur_up(int count0, int count1, char* unused)
-{
-  int count = count0*count1;
-  for (;count--;) TT.cursor = text_psol(TT.cursor);
-
-  TT.vi_mov_flag |= 0x80000000;
-  check_cursor_bounds();
-  return 1;
-}
-
-//TODO column shift
-static int cur_down(int count0, int count1, char* unused)
-{
-  int count = count0*count1;
-  for (;count--;) TT.cursor = text_nsol(TT.cursor);
-
-  check_cursor_bounds();
-  return 1;
-}
diff --git a/toys/pending/wget.c b/toys/pending/wget.c
index 21d4446..5c85889 100644
--- a/toys/pending/wget.c
+++ b/toys/pending/wget.c
@@ -25,11 +25,11 @@
   char *filename;
 )
 
-// extract hostname from url
+// extract hostname and port from url
 static unsigned get_hn(const char *url, char *hostname) {
   unsigned i;
 
-  for (i = 0; url[i] != '\0' && url[i] != ':' && url[i] != '/'; i++) {
+  for (i = 0; url[i] != '\0' && url[i] != '/'; i++) {
     if(i >= 1024) error_exit("too long hostname in URL");
     hostname[i] = url[i];
   }
@@ -41,7 +41,6 @@
 // extract port number
 static unsigned get_port(const char *url, char *port, unsigned url_i) {
   unsigned i;
-
   for (i = 0; url[i] != '\0' && url[i] != '/'; i++, url_i++) {
     if('0' <= url[i] && url[i] <= '9') port[i] = url[i];
     else error_exit("wrong decimal port number");
@@ -52,6 +51,20 @@
   return url_i;
 }
 
+static void strip_v6_brackets(char* hostname) {
+  size_t len = strlen(hostname);
+  if (len > 1023) {
+    error_exit("hostname too long, %d bytes\n", len);
+  }
+  char * closing_bracket = strchr(hostname, ']');
+  if (closing_bracket && closing_bracket == hostname + len - 1) {
+    if (strchr(hostname, '[') == hostname) {
+      hostname[len-1] = 0;
+      memmove(hostname, hostname + 1, len - 1);
+    }
+  }
+}
+
 // get http infos in URL
 static void get_info(const char *url, char* hostname, char *port, char *path) {
   unsigned i = 7, len;
@@ -62,11 +75,30 @@
   len = get_hn(url+i, hostname);
   i += len;
 
-  // get port if exists
-  if (url[i] == ':') {
-    i++;
-    i = get_port(url+i, port, i);
-  } else strcpy(port, "80");
+  // `hostname` now contains `host:port`, where host can be any of: a raw IPv4
+  // address; a bracketed, raw IPv6 address, or a hostname. Extract port, if it exists,
+  // by searching for the last ':' in the hostname string.
+  char *port_delim = strrchr(hostname, ':');
+  char use_default_port = 1;
+  if (port_delim) {
+    // Found a colon; is there a closing bracket after it? If so,
+    // then this colon was in the middle of a bracketed IPv6 address
+    if (!strchr(port_delim, ']')) {
+      // No closing bracket; this is a real port
+      use_default_port = 0;
+      get_port(port_delim + 1, port, 0);
+
+      // Mark the new end of the hostname string
+      *port_delim = 0;
+    }
+  }
+
+  if (use_default_port) {
+    strcpy(port, "80");
+  }
+
+  // This is a NOP if hostname is not a bracketed IPv6 address
+  strip_v6_brackets(hostname);
 
   // get uri in URL
   if (url[i] == '\0') strcpy(path, "/");
@@ -135,7 +167,7 @@
   FILE *fp;
   ssize_t len, body_len;
   char *body, *result, *rc, *r_str, *redir_loc = 0;
-  char ua[18] = "toybox wget",  hostname[1024], port[6], path[1024];
+  char ua[] = "toybox wget/" TOYBOX_VERSION, hostname[1024], port[6], path[1024];
 
   // TODO extract filename to be saved from URL
   if (!(toys.optflags & FLAG_O)) help_exit("no filename");
@@ -144,7 +176,6 @@
   if(!toys.optargs[0]) help_exit("no URL");
   get_info(toys.optargs[0], hostname, port, path);
 
-  sprintf(ua+11, "/%s", TOYBOX_VERSION);
   for (;; redirects--) {
     sock = conn_svr(hostname, port);
     // compose HTTP request
diff --git a/toys/pending/xzcat.c b/toys/pending/xzcat.c
index 55e0a05..6fdf342 100644
--- a/toys/pending/xzcat.c
+++ b/toys/pending/xzcat.c
@@ -2570,7 +2570,7 @@
   if (s->check_type == XZ_CHECK_CRC32)
     s->crc = xz_crc32(b->out + s->out_start,
         b->out_pos - s->out_start, s->crc);
-  else if (s->check_type == XZ_CHECK_CRC64)
+  else if (s->check_type == XZ_CHECK_CRC64) {
     s->crc = ~(s->crc);
     size_t size = b->out_pos - s->out_start;
     uint8_t *buf = b->out + s->out_start;
@@ -2579,6 +2579,7 @@
       --size;
     }
     s->crc=~(s->crc);
+  }
 
   if (ret == XZ_STREAM_END) {
     if (s->block_header.compressed != VLI_UNKNOWN
diff --git a/toys/posix/chgrp.c b/toys/posix/chgrp.c
index 20a2e0e..a63272a 100644
--- a/toys/posix/chgrp.c
+++ b/toys/posix/chgrp.c
@@ -5,7 +5,7 @@
  * See http://opengroup.org/onlinepubs/9699919799/utilities/chown.html
  * See http://opengroup.org/onlinepubs/9699919799/utilities/chgrp.html
 
-USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv[-HLP]", TOYFLAG_BIN))
+USE_CHGRP(NEWTOY(chgrp, "<2h(no-dereference)PLHRfv[-HLP]", TOYFLAG_BIN))
 USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
 
 config CHGRP
@@ -99,8 +99,3 @@
 
   if (CFG_TOYBOX_FREE && ischown) free(own);
 }
-
-void chown_main()
-{
-  chgrp_main();
-}
diff --git a/toys/posix/chmod.c b/toys/posix/chmod.c
index 4292439..ef74c4f 100644
--- a/toys/posix/chmod.c
+++ b/toys/posix/chmod.c
@@ -4,7 +4,7 @@
  *
  * See http://opengroup.org/onlinepubs/9699919799/utilities/chmod.html
 
-USE_CHMOD(NEWTOY(chmod, "<2?vRf[-vf]", TOYFLAG_BIN))
+USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
 
 config CHMOD
   bool "chmod"
@@ -19,7 +19,7 @@
     Stanzas are applied in order: For each category (u = user,
     g = group, o = other, a = all three, if none specified default is a),
     set (+), clear (-), or copy (=), r = read, w = write, x = execute.
-    s = u+s = suid, g+s = sgid, o+s = sticky. (+t is an alias for o+s).
+    s = u+s = suid, g+s = sgid, +t = sticky. (o+s ignored so a+s doesn't set +t)
     suid/sgid: execute as the user/group who owns the file.
     sticky: can't delete files you don't own out of this directory
     X = x for directories or if any category already has x set.
@@ -45,15 +45,22 @@
 
   if (!dirtree_notdotdot(try)) return 0;
 
-  mode = string_to_mode(TT.mode, try->st.st_mode);
-  if (toys.optflags & FLAG_v) {
-    char *s = dirtree_path(try, 0);
-    printf("chmod '%s' to %04o\n", s, mode);
-    free(s);
-  }
-  wfchmodat(dirtree_parentfd(try), try->name, mode);
+  if (FLAG(R) && try->parent && S_ISLNK(try->st.st_mode)) {
+    // Ignore symlinks found during recursion. We'll only try to modify
+    // symlinks mentioned directly as arguments. We'll fail, of course,
+    // but that's what you asked for in that case.
+  } else {
+    mode = string_to_mode(TT.mode, try->st.st_mode) & ~S_IFMT;
+    if (FLAG(v)) {
+      char *s = dirtree_path(try, 0);
 
-  return (toys.optflags & FLAG_R) ? DIRTREE_RECURSE : 0;
+      printf("chmod '%s' to %s\n", s, TT.mode);
+      free(s);
+    }
+    wfchmodat(dirtree_parentfd(try), try->name, mode);
+  }
+
+  return FLAG(R)*DIRTREE_RECURSE;
 }
 
 void chmod_main(void)
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index 13bfd7e..8eaecc9 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -15,15 +15,15 @@
 // options shared between mv/cp must be in same order (right to left)
 // for FLAG macros to work out right in shared infrastructure.
 
-USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]", TOYFLAG_BIN))
-USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fiT[-ni]", TOYFLAG_BIN))
-USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CP(NEWTOY(cp, "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu]", TOYFLAG_BIN))
+USE_MV(NEWTOY(mv, "<1vnF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
+USE_INSTALL(NEWTOY(install, "<1cdDpsvt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
 
 config CP
   bool "cp"
   default y
   help
-    usage: cp [-adfHiLlnPpRrsTv] SOURCE... DEST
+    usage: cp [-adfHiLlnPpRrsTv] [--preserve=motcxa] [-t TARGET] SOURCE... [DEST]
 
     Copy files from SOURCE to DEST.  If more than one SOURCE, DEST must
     be a directory.
@@ -38,23 +38,17 @@
     -L	Follow all symlinks
     -l	Hard link instead of copy
     -n	No clobber (don't overwrite DEST)
-    -P	Do not follow symlinks [default]
+    -u	Update (keep newest mtime)
+    -P	Do not follow symlinks
     -p	Preserve timestamps, ownership, and mode
     -R	Recurse into subdirectories (DEST must be a directory)
     -r	Synonym for -R
     -s	Symlink instead of copy
+    -t	Copy to TARGET dir (no DEST)
     -T	DEST always treated as file, max 2 arguments
     -v	Verbose
 
-config CP_PRESERVE
-  bool "cp --preserve support"
-  default y
-  depends on CP
-  help
-    usage: cp [--preserve=motcxa]
-
-    --preserve takes either a comma separated list of attributes, or the first
-    letter(s) of:
+    Arguments to --preserve are the first letter(s) of:
 
             mode - permissions (ignore umask for rwx, copy suid and sticky bit)
        ownership - user and group
@@ -67,11 +61,12 @@
   bool "mv"
   default y
   help
-    usage: mv [-finTv] SOURCE... DEST
+    usage: mv [-finTv] [-t TARGET] SOURCE... [DEST]
 
     -f	Force copy by deleting destination file
     -i	Interactive, prompt before overwriting existing DEST
     -n	No clobber (don't overwrite DEST)
+    -t	Move to TARGET dir (no DEST)
     -T	DEST always treated as file, max 2 arguments
     -v	Verbose
 
@@ -79,7 +74,7 @@
   bool "install"
   default y
   help
-    usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST
+    usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [-t TARGET] [SOURCE...] [DEST]
 
     Copy files and set attributes.
 
@@ -90,6 +85,7 @@
     -o	Make copy belong to USER
     -p	Preserve timestamps
     -s	Call "strip -p"
+    -t	Copy files to TARGET dir (no DEST)
     -v	Verbose
 */
 
@@ -101,11 +97,11 @@
   union {
     // install's options
     struct {
-      char *g, *o, *m;
+      char *g, *o, *m, *t;
     } i;
     // cp's options
     struct {
-      char *preserve;
+      char *t, *preserve;
     } c;
   };
 
@@ -157,7 +153,7 @@
       return 0;
     }
 
-    // Handle -invF
+    // Handle -inuvF
 
     if (!faccessat(cfd, catch, F_OK, 0) && !S_ISDIR(cst.st_mode)) {
       char *s;
@@ -170,11 +166,13 @@
         error_msg("unlink '%s'", catch);
         return 0;
       } else if (flags & FLAG_n) return 0;
+      else if ((flags & FLAG_u) && nanodiff(&try->st.st_mtim, &cst.st_mtim)>0)
+        return 0;
       else if (flags & FLAG_i) {
         fprintf(stderr, "%s: overwrite '%s'", toys.which->name,
           s = dirtree_path(try, 0));
         free(s);
-        if (!yesno(1)) return 0;
+        if (!yesno(0)) return 0;
       }
     }
 
@@ -246,7 +244,7 @@
 
       // Do something _other_ than copy contents of a file?
       } else if (!S_ISREG(try->st.st_mode)
-                 && (try->parent || (flags & (FLAG_a|FLAG_r))))
+                 && (try->parent || (flags & (FLAG_a|FLAG_P|FLAG_r))))
       {
         int i;
 
@@ -367,21 +365,30 @@
 
 void cp_main(void)
 {
-  char *destname = toys.optargs[--toys.optc];
-  int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode);
+  char *tt = *toys.which->name == 'i' ? TT.i.t : TT.c.t,
+    *destname = tt ? : toys.optargs[--toys.optc];
+  int i, destdir = !stat(destname, &TT.top);
+
+  if (!toys.optc) error_exit("Needs 2 arguments");
+  if (!destdir && errno==ENOENT && FLAG(D)) {
+    if (tt && mkpathat(AT_FDCWD, tt, 0777, MKPATHAT_MAKE|MKPATHAT_MKLAST))
+      perror_exit("-t '%s'", tt);
+    destdir = 1;
+  } else {
+    destdir = destdir && S_ISDIR(TT.top.st_mode);
+    if (!destdir && (toys.optc>1 || FLAG(D) || tt))
+      error_exit("'%s' not directory", destname);
+  }
 
   if (FLAG(T)) {
     if (toys.optc>1) help_exit("Max 2 arguments");
     if (destdir) error_exit("'%s' is a directory", destname);
   }
 
-  if ((toys.optc>1 || FLAG(D)) && !destdir)
-    error_exit("'%s' not directory", destname);
-
   if (FLAG(a)||FLAG(p)) TT.pflags = _CP_mode|_CP_ownership|_CP_timestamps;
 
   // Not using comma_args() (yet?) because interpeting as letters.
-  if (CFG_CP_PRESERVE && FLAG(preserve)) {
+  if (FLAG(preserve)) {
     char *pre = xstrdup(TT.c.preserve ? TT.c.preserve : "mot"), *s;
 
     if (comma_remove(pre, "all")) TT.pflags = ~0;
@@ -408,14 +415,14 @@
 
   // Loop through sources
   for (i=0; i<toys.optc; i++) {
-    char *src = toys.optargs[i], *trail = src;
-    int rc = 1;
+    char *src = toys.optargs[i], *trail;
+    int send = 1;
 
-    while (*++trail);
-    if (*--trail == '/') *trail = 0;
+    if (!(trail = strrchr(src, '/')) || trail[1]) trail = 0;
+    else while (trail>src && *trail=='/') *trail-- = 0;
 
     if (destdir) {
-      char *s = FLAG(D) ? dirname(src) : getbasename(src);
+      char *s = FLAG(D) ? src : getbasename(src);
 
       TT.destname = xmprintf("%s/%s", destname, s);
       if (FLAG(D)) {
@@ -429,6 +436,7 @@
       }
     } else TT.destname = destname;
 
+    // "mv across devices" triggers cp fallback path, so set that as default
     errno = EXDEV;
     if (CFG_MV && toys.which->name[0] == 'm') {
       int force = FLAG(f), no_clobber = FLAG(n);
@@ -440,20 +448,20 @@
         // Prompt if -i or file isn't writable.  Technically "is writable" is
         // more complicated (022 is not writeable by the owner, just everybody
         // _else_) but I don't care.
-        if (exists && (FLAG(i) || !(st.st_mode & 0222))) {
+        if (exists && (FLAG(i) || (!(st.st_mode & 0222) && isatty(0)))) {
           fprintf(stderr, "%s: overwrite '%s'", toys.which->name, TT.destname);
-          if (!yesno(1)) rc = 0;
+          if (!yesno(0)) send = 0;
           else unlink(TT.destname);
         }
         // if -n and dest exists, don't try to rename() or copy
-        if (exists && no_clobber) rc = 0;
+        if (exists && no_clobber) send = 0;
       }
-      if (rc) rc = rename(src, TT.destname);
-      if (errno && !*trail) *trail = '/';
+      if (send) send = rename(src, TT.destname);
+      if (trail) trail[1] = '/';
     }
 
-    // Copy if we didn't mv, skipping nonexistent sources
-    if (rc) {
+    // Copy if we didn't mv or hit an error, skipping nonexistent sources
+    if (send) {
       if (errno!=EXDEV || dirtree_flagread(src, DIRTREE_SHUTUP+
         DIRTREE_SYMFOLLOW*!!(FLAG(H)||FLAG(L)), TT.callback))
           perror_msg("bad '%s'", src);
@@ -516,12 +524,11 @@
   }
 
   if (FLAG(D)) {
-    TT.destname = toys.optargs[toys.optc-1];
-    if (mkpathat(AT_FDCWD, TT.destname, 0, MKPATHAT_MAKE))
-      perror_exit("-D '%s'", TT.destname);
-    if (toys.optc == 1) return;
+    char *destname = FLAG(t) ? TT.i.t : (TT.destname = toys.optargs[toys.optc-1]);
+    if (mkpathat(AT_FDCWD, destname, 0777, MKPATHAT_MAKE | (FLAG(t) ? MKPATHAT_MKLAST : 0)))
+      perror_exit("-D '%s'", destname);
+    if (toys.optc == !FLAG(t)) return;
   }
-  if (toys.optc < 2) error_exit("needs 2 args");
 
   // Translate flags from install to cp
   toys.optflags = cp_flag_F() + cp_flag_v()*!!FLAG(v)
diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c
index 97b4035..35b74b3 100644
--- a/toys/posix/cpio.c
+++ b/toys/posix/cpio.c
@@ -1,29 +1,29 @@
 /* cpio.c - a basic cpio
  *
- * Written 2013 AD by Isaac Dunham; this code is placed under the
- * same license as toybox or as CC0, at your option.
+ * Copyright 2013 Isaac Dunham <ibid.ag@gmail.com>
+ * Copyright 2015 Frontier Silicon Ltd.
  *
- * Portions Copyright 2015 by Frontier Silicon Ltd.
- *
- * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cpio.html
+ * see https://www.kernel.org/doc/Documentation/early-userspace/buffer-format.txt
+ * and 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
  *
- * Yes, that's SUSv2, the newer standards removed it around the time RPM
- * and initramfs started heavily using this archive format.
- *
- * Modern cpio expanded header to 110 bytes (first field 6 bytes, rest are 8).
+ * Yes, that's SUSv2, newer versions removed it, but RPM and initramfs use
+ * this archive format. We implement (only) the modern "-H newc" variant which
+ * expanded headers 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.
+ * This is the equivalent of mode -H newc in other implementations.
+ *
+ * todo: export/import linux file list text format ala gen_initramfs_list.sh
 
-USE_CPIO(NEWTOY(cpio, "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
+USE_CPIO(NEWTOY(cpio, "(quiet)(no-preserve-owner)md(make-directories)uH:p|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
 
 config CPIO
   bool "cpio"
   default y
   help
     usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]
-           [ignored: -mdu -H newc]
+           [ignored: -m -H newc]
 
     Copy files into and out of a "newc" format cpio archive.
 
@@ -32,16 +32,17 @@
     -i	Extract from archive into file system (stdin=archive)
     -o	Create archive (stdin=list of files, stdout=archive)
     -t	Test files (list only, stdin=archive, stdout=list of files)
+    -d	Create directories if needed
+    -u	unlink existing files when extracting
     -v	Verbose
     --no-preserve-owner (don't set ownership during extract)
-    --trailer Add legacy trailer (prevents concatenation)
 */
 
 #define FOR_cpio
 #include "toys.h"
 
 GLOBALS(
-  char *F, *p, *H;
+  char *F, *H;
 )
 
 // Read strings, tail padded to 4 byte alignment. Argument "align" is amount
@@ -72,7 +73,7 @@
   // Because scanf gratuitously treats %*X differently than printf does.
   sprintf(pattern, "%%%dX%%n", inpos);
   sscanf(hex, pattern, &val, &outpos);
-  if (inpos != outpos) error_exit("bad header");
+  if (inpos != outpos) error_exit("bad hex");
 
   return val;
 }
@@ -80,12 +81,17 @@
 void cpio_main(void)
 {
   // Subtle bit: FLAG_o is 1 so we can just use it to select stdin/stdout.
-  int pipe, afd = toys.optflags & FLAG_o;
+  int pipe, afd = FLAG(o), empty = 1;
   pid_t pid = 0;
 
   // In passthrough mode, parent stays in original dir and generates archive
   // to pipe, child does chdir to new dir and reads archive from stdin (pipe).
-  if (TT.p) {
+  if (FLAG(p)) {
+    if (FLAG(d)) {
+      if (!*toys.optargs) error_exit("need directory for -p");
+      if (mkdir(*toys.optargs, 0700) == -1 && errno != EEXIST)
+        perror_exit("mkdir %s", *toys.optargs);
+    }
     if (toys.stacktop) {
       // xpopen() doesn't return from child due to vfork(), instead restarts
       // with !toys.stacktop
@@ -94,30 +100,43 @@
     } else {
       // child
       toys.optflags |= FLAG_i;
-      xchdir(TT.p);
+      xchdir(*toys.optargs);
     }
   }
 
   if (TT.F) {
-    int perm = (toys.optflags & FLAG_o) ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
+    int perm = FLAG(o) ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
 
     afd = xcreate(TT.F, perm, 0644);
   }
 
   // read cpio archive
 
-  if (toys.optflags & (FLAG_i|FLAG_t)) for (;;) {
+  if (FLAG(i) || FLAG(t)) for (;; empty = 0) {
     char *name, *tofree, *data;
-    unsigned size, mode, uid, gid, timestamp;
-    int test = toys.optflags & FLAG_t, err = 0;
+    unsigned mode, uid, gid, timestamp;
+    int test = FLAG(t), err = 0, size = 0, len;
 
-    // Read header and name.
-    if (!(size =readall(afd, toybuf, 110))) break;
+    // read header, skipping arbitrary leading NUL bytes (concatenated archives)
+    for (;;) {
+      if (1>(len = readall(afd, toybuf+size, 110-size))) break;
+      if (size || *toybuf) {
+        size += len;
+        break;
+      }
+      for (size = 0; size<len; size++) if (toybuf[size]) break;
+      memmove(toybuf, toybuf+size, len-size);
+      size = len-size;
+    }
+    if (!size) {
+      if (empty) error_exit("empty archive");
+      else break;
+    }
     if (size != 110 || memcmp(toybuf, "070701", 6)) error_exit("bad header");
     tofree = name = strpad(afd, x8u(toybuf+94), 110);
     if (!strcmp("TRAILER!!!", name)) {
-      if (CFG_TOYBOX_FREE) free(tofree);
-      break;
+      free(tofree);
+      continue;
     }
 
     // If you want to extract absolute paths, "cd /" and run cpio.
@@ -130,9 +149,12 @@
     gid = x8u(toybuf+30);
     timestamp = x8u(toybuf+46); // unsigned 32 bit, so year 2100 problem
 
-    if (toys.optflags & (FLAG_t|FLAG_v)) puts(name);
+    // (This output is unaffected by --quiet.)
+    if (FLAG(t) || FLAG(v)) puts(name);
 
-    if (!test && strrchr(name, '/') && mkpath(name)) {
+    if (FLAG(u) && !test) if (unlink(name) && errno == EISDIR) rmdir(name);
+
+    if (!test && FLAG(d) && strrchr(name, '/') && mkpath(name)) {
       perror_msg("mkpath '%s'", name);
       test++;
     }
@@ -141,16 +163,18 @@
     // properly aligned with next file.
 
     if (S_ISDIR(mode)) {
-      if (!test) err = mkdir(name, mode);
+      if (!test) err = mkdir(name, mode) && !FLAG(u);
     } else if (S_ISLNK(mode)) {
       data = strpad(afd, size, 0);
-      if (!test) err = symlink(data, name);
+      if (!test) {
+        err = symlink(data, name);
+        // Can't get a filehandle to a symlink, so do special chown
+        if (!err && !geteuid() && !FLAG(no_preserve_owner))
+          err = lchown(name, uid, gid);
+      }
       free(data);
-      // Can't get a filehandle to a symlink, so do special chown
-      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);
+      int fd = test ? 0 : open(name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, mode);
 
       // If write fails, we still need to read/discard data to continue with
       // archive. Since doing so overwrites errno, report error now
@@ -173,7 +197,7 @@
 
       if (!test) {
         // set owner, restore dropped suid bit
-        if (!geteuid() && !(toys.optflags & FLAG_no_preserve_owner)) {
+        if (!geteuid() && !FLAG(no_preserve_owner)) {
           err = fchown(fd, uid, gid);
           if (!err) err = fchmod(fd, mode);
         }
@@ -189,7 +213,7 @@
       // Check that we at least have the right type of entity open, and do
       // NOT restore dropped suid bit in this case.
       if (!S_ISREG(mode) && !S_ISLNK(mode) && !geteuid()
-          && !(toys.optflags & FLAG_no_preserve_owner))
+          && !FLAG(no_preserve_owner))
       {
         int fd = open(name, O_RDONLY|O_NOFOLLOW);
         struct stat st;
@@ -224,6 +248,7 @@
       struct stat st;
       unsigned nlen, error = 0, zero = 0;
       int len, fd = -1;
+      char *link = 0;
       ssize_t llen;
 
       len = getline(&name, &size, stdin);
@@ -231,12 +256,16 @@
       if (name[len-1] == '\n') name[--len] = 0;
       nlen = len+1;
       if (lstat(name, &st) || (S_ISREG(st.st_mode)
-          && st.st_size && (fd = open(name, O_RDONLY))<0))
+          && st.st_size && (fd = open(name, O_RDONLY))<0)
+          || (S_ISLNK(st.st_mode) && !(link = xreadlink(name))))
       {
         perror_msg_raw(name);
         continue;
       }
+      // encrypted filesystems can stat the wrong link size
+      if (link) st.st_size = strlen(link);
 
+      if (FLAG(no_preserve_owner)) st.st_uid = st.st_gid = 0;
       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) st.st_size = 0;
       if (st.st_size >> 32) perror_msg("skipping >2G file '%s'", name);
       else {
@@ -254,14 +283,9 @@
         if (llen) xwrite(afd, &zero, 4-llen);
 
         // Write out body for symlink or regular file
-        llen = st.st_size;
-        if (S_ISLNK(st.st_mode)) {
-          if (readlink(name, toybuf, sizeof(toybuf)-1) == llen)
-            xwrite(afd, toybuf, llen);
-          else perror_msg("readlink '%s'", name);
-        } else while (llen) {
+        if (link) xwrite(afd, link, st.st_size);
+        else for (llen = st.st_size; llen; llen -= nlen) {
           nlen = llen > sizeof(toybuf) ? sizeof(toybuf) : llen;
-          llen -= nlen;
           // If read fails, write anyway (already wrote size in header)
           if (nlen != readall(fd, toybuf, nlen))
             if (!error++) perror_msg("bad read from file '%s'", name);
@@ -270,17 +294,15 @@
         llen = st.st_size & 3;
         if (llen) xwrite(afd, &zero, 4-llen);
       }
-      close(fd);
+      free(link);
+      xclose(fd);
     }
-    free(name);
+    if (CFG_TOYBOX_FREE) free(name);
 
-    if (FLAG_trailer) {
-      memset(toybuf, 0, sizeof(toybuf));
-      xwrite(afd, toybuf,
-        sprintf(toybuf, "070701%040X%056X%08XTRAILER!!!", 1, 0x0b, 0)+4);
-    }
+    // nlink=1, namesize=11, with padding
+    dprintf(afd, "070701%040X%056X%08XTRAILER!!!%c%c%c%c", 1, 11, 0, 0, 0, 0,0);
   }
   if (TT.F) xclose(afd);
 
-  if (TT.p) toys.exitval |= xpclose(pid, pipe);
+  if (FLAG(p) && pid) toys.exitval |= xpclose(pid, pipe);
 }
diff --git a/toys/posix/date.c b/toys/posix/date.c
index 75408ca..86c2d34 100644
--- a/toys/posix/date.c
+++ b/toys/posix/date.c
@@ -7,18 +7,19 @@
  * Note: setting a 2 year date is 50 years back/forward from today,
  * not posix's hardwired magic dates.
 
-USE_DATE(NEWTOY(date, "d:D:r:u[!dr]", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:u(utc)[!dr]", TOYFLAG_BIN))
 
 config DATE
   bool "date"
   default y
   help
-    usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]
+    usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]
 
     Set/get the current date/time. With no SET shows the current date.
 
     -d	Show DATE instead of current time (convert date format)
     -D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])
+    -I RES	ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns
     -r	Use modification time of FILE instead of current date
     -u	Use UTC instead of current timezone
 
@@ -29,6 +30,9 @@
     YYYY-MM-DD [hh:mm[:ss]]   ISO 8601
     hh:mm[:ss]                24-hour time today
 
+    All input formats can be followed by fractional seconds, and/or a UTC
+    offset such as -0800.
+
     All input formats can be preceded by TZ="id" to set the input time zone
     separately from the output time zone. Otherwise $TZ sets both.
 
@@ -43,19 +47,19 @@
     %j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)
     %N nanosec (output only)
 
-    %U Week of year (0-53 start sunday)   %W Week of year (0-53 start monday)
-    %V Week of year (1-53 start monday, week < 4 days not part of this year)
+    %U Week of year (0-53 start Sunday)   %W Week of year (0-53 start Monday)
+    %V Week of year (1-53 start Monday, week < 4 days not part of this year)
 
-    %F "%Y-%m-%d"     %R "%H:%M"        %T "%H:%M:%S"    %z numeric timezone
-    %D "%m/%d/%y"     %r "%I:%M:%S %p"  %h "%b"          %s unix epoch time
-    %x locale date    %X locale time    %c locale date/time
+    %F "%Y-%m-%d"   %R "%H:%M"        %T "%H:%M:%S"        %z  timezone (-0800)
+    %D "%m/%d/%y"   %r "%I:%M:%S %p"  %h "%b"              %:z timezone (-08:00)
+    %x locale date  %X locale time    %c locale date/time  %s  unix epoch time
 */
 
 #define FOR_date
 #include "toys.h"
 
 GLOBALS(
-  char *r, *D, *d;
+  char *r, *I, *D, *d;
 
   unsigned nano;
 )
@@ -85,30 +89,42 @@
   }
 }
 
-// Print strftime plus %N escape(s). note: modifies fmt for %N
+// Print strftime plus %N and %:z escape(s). Note: modifies fmt in those cases.
 static void puts_time(char *fmt, struct tm *tm)
 {
-  char *s, *snap;
-  long width = width;
+  char *s, *snap, *out;
 
   for (s = fmt;;s++) {
+    long n = 0;
 
-    // Find next %N or end
-    if (*(snap = s) == '%') {
-      width = isdigit(*++s) ? *(s++)-'0' : 9;
-      if (*s && *s != 'N') continue;
-    } else if (*s) continue;
-
-    // Don't modify input string if no %N (default format is constant string).
-    if (*s) *snap = 0;
-    if (!strftime(toybuf, sizeof(toybuf)-10, fmt, tm))
-      perror_exit("bad format '%s'", fmt);
-    if (*s) {
-      snap = toybuf+strlen(toybuf);
-      sprintf(snap, "%09u", TT.nano);
-      snap[width] = 0;
+    // Find next %N/%:z or end of format string.
+    if (*(snap = s)) {
+      if (*s != '%') continue;
+      if (*++s == 'N') n = 9;
+      else if (isdigit(*s) && s[1] == 'N') n = *s++-'0';
+      else if (*s == ':' && s[1] == 'z') s++, n++;
+      else continue;
     }
-    fputs(toybuf, stdout);
+
+    // Only modify input string if needed (default format is constant string).
+    if (*s) *snap = 0;
+    // Do we have any regular work for strftime to do?
+    out = toybuf;
+    if (*fmt) {
+      if (!strftime(out, sizeof(toybuf)-12, fmt, tm))
+        perror_exit("bad format '%s'", fmt);
+      out += strlen(out);
+    }
+    // Do we have any custom formatting to append to that?
+    if (*s == 'N') {
+      sprintf(out, "%09u", TT.nano);
+      out[n] = 0;
+    } else if (*s == 'z') {
+      strftime(out, 10, "%z", tm);
+      memmove(out+4, out+3, strlen(out+3)+1);
+      out[3] = ':';
+    }
+    xputsn(toybuf);
     if (!*s || !*(fmt = s+1)) break;
   }
   xputc('\n');
@@ -120,6 +136,14 @@
     *tz = NULL;
   time_t t;
 
+  if (FLAG(I)) {
+    char *iso_formats[] = {"%F","%FT%H%:z","%FT%R%:z","%FT%T%:z","%FT%T,%N%:z"};
+    int i = stridx("dhmsn", (TT.I && *TT.I) ? *TT.I : 'd');
+
+    if (i<0) help_exit("bad -I: %s", TT.I);
+    format_string = xstrdup(iso_formats[i]);
+  }
+
   if (FLAG(u)) {
     tz = getenv("TZ");
     setenv("TZ", "UTC", 1);
@@ -170,6 +194,5 @@
     else unsetenv("TZ");
     tzset();
   }
-
-  return;
+  if (CFG_TOYBOX_FREE && FLAG(I)) free(format_string);
 }
diff --git a/toys/posix/df.c b/toys/posix/df.c
index a93a762..b8f298f 100644
--- a/toys/posix/df.c
+++ b/toys/posix/df.c
@@ -4,7 +4,7 @@
  *
  * See http://opengroup.org/onlinepubs/9699919799/utilities/df.html
 
-USE_DF(NEWTOY(df, "HPkhit*a[-HPkh]", TOYFLAG_SBIN))
+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN))
 
 config DF
   bool "df"
@@ -24,7 +24,7 @@
     -i	Show inodes instead of blocks
     -t type	Display only filesystems of this type
 
-    Pedantic provides a slightly less useful output format dictated by Posix,
+    Pedantic provides a slightly less useful output format dictated by POSIX,
     and sets the units to 512 bytes instead of the default 1024 bytes.
 */
 
@@ -34,151 +34,116 @@
 GLOBALS(
   struct arg_list *t;
 
-  long units;
-  int column_widths[5];
-  int header_shown;
+  int units, width[6];
 )
 
-static void measure_column(int col, const char *s)
+static void measure_columns(char *s[])
 {
-  size_t len = strlen(s);
+  int i;
 
-  if (TT.column_widths[col] < len) TT.column_widths[col] = len;
+  for (i = 0; i<5; i++) TT.width[i] = maxof(TT.width[i], strlen(s[i]));
 }
 
-static void measure_numeric_column(int col, long long n)
+static void print_columns(char **dsuapm)
 {
-  snprintf(toybuf, sizeof(toybuf), "%llu", n);
-  return measure_column(col, toybuf);
+  int i;
+
+  for (i = 0; i<6; i++) printf(!i ? "%-*s" : " %*s", TT.width[i], dsuapm[i]);
+  xputc('\n');
 }
 
-static void show_header()
+static void print_header()
 {
-  TT.header_shown = 1;
+  char *dsuapm[] = {"Filesystem", "Size", "Used", "Avail", "Use%","Mounted on"};
 
   // The filesystem column is always at least this wide.
-  if (TT.column_widths[0] < 14) TT.column_widths[0] = 14;
+  TT.width[0] = maxof(TT.width[0], 14+(FLAG(H)||FLAG(h)));
 
-  if ((toys.optflags & (FLAG_H|FLAG_h))) {
-    xprintf((toys.optflags&FLAG_i) ?
-            "%-*sInodes  IUsed  IFree IUse%% Mounted on\n" :
-            "%-*s Size  Used Avail Use%% Mounted on\n",
-            TT.column_widths[0], "Filesystem");
-  } else {
-    const char *item_label, *used_label, *free_label, *use_label;
-
-    if (toys.optflags & FLAG_i) {
-      item_label = "Inodes";
-      used_label = "IUsed";
-      free_label = "IFree";
-      use_label = "IUse%";
-    } else {
-      item_label = TT.units == 512 ? "512-blocks" : "1K-blocks";
-      used_label = "Used";
-      free_label = "Available";
-      use_label = toys.optflags & FLAG_P ? "Capacity" : "Use%";
+  if (FLAG(i)) memcpy(dsuapm+1, (char *[]){"Inodes", "IUsed", "IFree", "IUse%"},
+                      sizeof(char *)*4);
+  else {
+    if (!(FLAG(H)||FLAG(h))) {
+      dsuapm[1] = TT.units == 512 ? "512-blocks" :
+        FLAG(P) ? "1024-blocks" : "1K-blocks";
+      dsuapm[3] = "Available";
+      if (FLAG(P)) dsuapm[4] = "Capacity";
     }
-
-    measure_column(1, item_label);
-    measure_column(2, used_label);
-    measure_column(3, free_label);
-    measure_column(4, use_label);
-    xprintf("%-*s %*s %*s %*s %*s Mounted on\n",
-            TT.column_widths[0], "Filesystem",
-            TT.column_widths[1], item_label,
-            TT.column_widths[2], used_label,
-            TT.column_widths[3], free_label,
-            TT.column_widths[4], use_label);
-
-    // For the "Use%" column, the trailing % should be inside the column.
-    TT.column_widths[4]--;
   }
+
+  measure_columns(dsuapm);
+  TT.width[5] = -1;
+  print_columns(dsuapm);
 }
 
 static void show_mt(struct mtab_list *mt, int measuring)
 {
-  unsigned long long size, used, avail, percent, block;
-  char *device;
+  unsigned long long suap[4], block = 1, ll;
+  char *dsuapm[6]; // device, size, used, avail, percent, mount
+  int i;
 
-  // Return if it wasn't found (should never happen, but with /etc/mtab...)
-  if (!mt) return;
+  // If we don't have -a, skip overmounted and synthetic filesystems.
+  if (!mt || (!FLAG(a) && (!mt->stat.st_dev || !mt->statvfs.f_blocks))) return;
 
   // If we have -t, skip other filesystem types
   if (TT.t) {
     struct arg_list *al;
 
-    for (al = TT.t; al; al = al->next) 
-      if (!strcmp(mt->type, al->arg)) break;
+    for (al = TT.t; al; al = al->next) if (!strcmp(mt->type, al->arg)) break;
 
     if (!al) return;
   }
 
-  // If we don't have -a, skip synthetic filesystems
-  if (!(toys.optflags & FLAG_a) && !mt->statvfs.f_blocks) return;
-
-  // Figure out how much total/used/free space this filesystem has,
-  // forcing 64-bit math because filesystems are big now.
-  if (toys.optflags & FLAG_i) {
-    size = mt->statvfs.f_files;
-    used = mt->statvfs.f_files - mt->statvfs.f_ffree;
-    avail = getuid() ? mt->statvfs.f_favail : mt->statvfs.f_ffree;
-  } else {
-    block = mt->statvfs.f_bsize ? mt->statvfs.f_bsize : 1;
-    size = (block * mt->statvfs.f_blocks) / TT.units;
-    used = (block * (mt->statvfs.f_blocks-mt->statvfs.f_bfree)) / TT.units;
-    avail= (block*(getuid()?mt->statvfs.f_bavail:mt->statvfs.f_bfree))/TT.units;
-  }
-  if (!(used+avail)) percent = 0;
+  // Prepare filesystem display fields
+  *dsuapm = *mt->device == '/' ? xabspath(mt->device, 0) : 0;
+  if (!*dsuapm) *dsuapm = mt->device;
+  if (!mt->stat.st_dev) for (i = 1; i<5; i++) dsuapm[i] = "-";
   else {
-    percent = (used*100)/(used+avail);
-    if (used*100 != percent*(used+avail)) percent++;
+    if (FLAG(i)) {
+      suap[0] = mt->statvfs.f_files;
+      suap[1] = mt->statvfs.f_files - mt->statvfs.f_ffree;
+      suap[2] = getuid() ? mt->statvfs.f_favail : mt->statvfs.f_ffree;
+    } else {
+      block = maxof(mt->statvfs.f_frsize, 1);
+      suap[0] = mt->statvfs.f_blocks;
+      suap[1] = mt->statvfs.f_blocks - mt->statvfs.f_bfree;
+      suap[2] = getuid() ? mt->statvfs.f_bavail : mt->statvfs.f_bfree;
+    }
+
+    // Scale and convert to strings
+    dsuapm[1] = toybuf;
+    for (i = 0; i<3; i++) {
+      suap[i] = (block*suap[i])/TT.units;
+
+      if (FLAG(H)||FLAG(h))
+        human_readable(dsuapm[i+1], suap[i], FLAG(H) ? HR_1000 : 0);
+      else sprintf(dsuapm[i+1], "%llu", suap[i]);
+      dsuapm[i+2] = strchr(dsuapm[i+1], 0)+1;
+    }
+
+    // percent
+    if ((suap[3] = ll = suap[1]+suap[2])) {
+      suap[3] = (block = suap[1]*100)/ll;
+      if (block != suap[3]*ll) suap[3]++;
+    }
+    sprintf(dsuapm[4], "%llu%%", suap[3]);
   }
+  dsuapm[5] = mt->dir;
 
-  device = *mt->device == '/' ? realpath(mt->device, NULL) : NULL;
-  if (!device) device = mt->device;
+  if (measuring) measure_columns(dsuapm);
+  else print_columns(dsuapm);
 
-  if (measuring) {
-    measure_column(0, device);
-    measure_numeric_column(1, size);
-    measure_numeric_column(2, used);
-    measure_numeric_column(3, avail);
-  } else {
-    if (!TT.header_shown) show_header();
-
-    if (toys.optflags & (FLAG_H|FLAG_h)) {
-      char *size_str = toybuf, *used_str = toybuf+64, *avail_str = toybuf+128;
-      int hr_flags = (toys.optflags & FLAG_H) ? HR_1000 : 0;
-      int w = 4 + !!(toys.optflags & FLAG_i);
-
-      human_readable(size_str, size, hr_flags);
-      human_readable(used_str, used, hr_flags);
-      human_readable(avail_str, avail, hr_flags);
-      xprintf("%-*s %*s  %*s  %*s %*llu%% %s\n",
-        TT.column_widths[0], device,
-        w, size_str, w, used_str, w, avail_str, w-1, percent, mt->dir);
-    } else xprintf("%-*s %*llu %*llu %*llu %*llu%% %s\n",
-        TT.column_widths[0], device,
-        TT.column_widths[1], size,
-        TT.column_widths[2], used,
-        TT.column_widths[3], avail,
-        TT.column_widths[4], percent,
-        mt->dir);
-  }
-
-  if (device != mt->device) free(device);
+  if (*dsuapm != mt->device) free(*dsuapm);
 }
 
 void df_main(void)
 {
-  struct mtab_list *mt, *mtstart, *mtend;
+  struct mtab_list *mt, *mtstart, *mtend, *mt2, *mt3;
   int measuring;
+  char **next;
 
-  if (toys.optflags & (FLAG_H|FLAG_h)) {
-    TT.units = 1;
-  } else {
-    // Units are 512 bytes if you select "pedantic" without "kilobytes".
-    TT.units = toys.optflags & FLAG_P ? 512 : 1024;
-  }
+  // Units are 512 bytes if you select "pedantic" without "kilobytes".
+  if (FLAG(H)||FLAG(h)||FLAG(i)) TT.units = 1;
+  else TT.units = FLAG(P) && !FLAG(k) ? 512 : 1024;
 
   if (!(mtstart = xgetmountlist(0))) return;
   mtend = dlist_terminate(mtstart);
@@ -186,57 +151,45 @@
   // If we have a list of filesystems on the command line, loop through them.
   if (*toys.optargs) {
     // Measure the names then output the table.
-    for (measuring = 1; measuring >= 0; --measuring) {
-      char **next;
-
+    for (measuring = 1;;) {
       for (next = toys.optargs; *next; next++) {
         struct stat st;
 
         // Stat it (complain if we can't).
         if (stat(*next, &st)) {
-          perror_msg("'%s'", *next);
-          continue;
-        }
-
-        // Find and display this filesystem.  Use _last_ hit in case of
-        // overmounts (which is first hit in the reversed list).
-        for (mt = mtend; mt; mt = mt->prev) {
-          if (st.st_dev == mt->stat.st_dev
-              || (st.st_rdev && (st.st_rdev == mt->stat.st_dev)))
-          {
-            show_mt(mt, measuring);
-            break;
+          if (!measuring) perror_msg("'%s'", *next);
+        } else {
+          // Find and display this filesystem.  Use _last_ hit in case of
+          // overmounts (which is first hit in the reversed list).
+          for (mt = mtend, mt2 = 0; mt; mt = mt->prev) {
+            if (!mt2 && st.st_dev == mt->stat.st_dev) mt2 = mt;
+            if (st.st_rdev && (st.st_rdev == mt->stat.st_dev)) break;
           }
+          show_mt(mt ? : mt2, measuring);
         }
       }
+      if (!measuring--) break;
+      print_header();
     }
   } else {
     // Loop through mount list to filter out overmounts.
     for (mt = mtend; mt; mt = mt->prev) {
-      struct mtab_list *mt2, *mt3;
-
-      // 0:0 is LANANA null device
-      if (!mt->stat.st_dev) continue;
-
-      // Filter out overmounts.
-      mt3 = mt;
-      for (mt2 = mt->prev; mt2; mt2 = mt2->prev) {
+      for (mt3 = mt, mt2 = mt->prev; mt2; mt2 = mt2->prev) {
         if (mt->stat.st_dev == mt2->stat.st_dev) {
           // For --bind mounts, show earliest mount
           if (!strcmp(mt->device, mt2->device)) {
-            if (!(toys.optflags & FLAG_a)) mt3->stat.st_dev = 0;
+            mt3->stat.st_dev = 0;
             mt3 = mt2;
           } else mt2->stat.st_dev = 0;
         }
       }
     }
 
-    // Measure the names then output the table.
-    for (measuring = 1; measuring >= 0; --measuring) {
-      // Cosmetic: show filesystems in creation order.
-      for (mt = mtstart; mt; mt = mt->next) {
-        if (mt->stat.st_dev) show_mt(mt, measuring);
-      }
+    // Measure the names then output the table (in filesystem creation order).
+    for (measuring = 1;;) {
+      for (mt = mtstart; mt; mt = mt->next) show_mt(mt, measuring);
+      if (!measuring--) break;
+      print_header();
     }
   }
 
diff --git a/toys/posix/du.c b/toys/posix/du.c
index e7334d1..8611867 100644
--- a/toys/posix/du.c
+++ b/toys/posix/du.c
@@ -4,9 +4,11 @@
  *
  * See http://opengroup.org/onlinepubs/9699919799/utilities/du.html
  *
- * TODO: cleanup
+ * TODO: cleanup (should seen_inode be lib?)
+ * 32 bit du -b maxes out at 4 gigs (instead of 2 terabytes via *512 trick)
+ * because dirtree->extra is a long.
 
-USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsx[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_DU(NEWTOY(du, "d#<0=-1hmlcaHkKLsxb[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config DU
   bool "du"
@@ -17,6 +19,7 @@
     Show disk usage, space consumed by files and directories.
 
     Size in:
+    -b	Apparent bytes (directory listing size, not space used)
     -k	1024 byte blocks (default)
     -K	512 byte blocks (posix)
     -m	Megabytes
@@ -65,7 +68,8 @@
     if (FLAG(K)) bits = 9;
     else if (FLAG(m)) bits = 20;
 
-    printf("%llu", (size>>bits)+!!(size&((1<<bits)-1)));
+    if (FLAG(b) && bits == 10 && !FLAG(k)) printf("%llu", size);
+    else printf("%llu", (size>>bits)+!!(size&((1<<bits)-1)));
   }
   if (node) name = dirtree_path(node, NULL);
   xprintf("\t%s\n", name);
@@ -138,7 +142,8 @@
 
   // Modern compilers' optimizers are insane and think signed overflow
   // behaves differently than unsigned overflow. Sigh. Big hammer.
-  blocks = node->st.st_blocks + (unsigned long)node->extra;
+  blocks = FLAG(b) ? node->st.st_size : node->st.st_blocks;
+  blocks += (unsigned long)node->extra;
   node->extra = blocks;
   if (node->parent)
     node->parent->extra = (unsigned long)node->parent->extra+blocks;
@@ -146,7 +151,7 @@
 
   if (FLAG(a) || !node->parent || (S_ISDIR(node->st.st_mode) && !FLAG(s))) {
     blocks = node->extra;
-    print(blocks*512LL, node);
+    print(FLAG(b) ? blocks : blocks*512LL, node);
   }
 
   return 0;
@@ -160,7 +165,7 @@
   for (args = toys.optc ? toys.optargs : noargs; *args; args++)
     dirtree_flagread(*args, DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)),
       do_du);
-  if (FLAG(c)) print(TT.total*512, 0);
+  if (FLAG(c)) print(FLAG(b) ? TT.total : TT.total*512, 0);
 
   if (CFG_TOYBOX_FREE) seen_inode(TT.inodes, 0);
 }
diff --git a/toys/posix/echo.c b/toys/posix/echo.c
index c78c2c0..9b7b922 100644
--- a/toys/posix/echo.c
+++ b/toys/posix/echo.c
@@ -9,7 +9,7 @@
  * We also honor -- to _stop_ option parsing (bash doesn't, we go with
  * consistency over compatibility here).
 
-USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK))
+USE_ECHO(NEWTOY(echo, "^?Een[-eE]", TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_LINEBUF))
 
 config ECHO
   bool "echo"
@@ -41,12 +41,10 @@
 
 void echo_main(void)
 {
-  int i = 0, out;
-  char *arg, *c;
+  int i = 0;
+  char *arg, *c, out[8];
 
-  for (;;) {
-    arg = toys.optargs[i];
-    if (!arg) break;
+  while ((arg = toys.optargs[i])) {
     if (i++) putchar(' ');
 
     // Should we output arg verbatim?
@@ -58,40 +56,12 @@
 
     // Handle -e
 
-    for (c = arg;;) {
-      if (!(out = *(c++))) break;
+    for (c = arg; *c; ) {
+      unsigned u;
 
-      // handle \escapes
-      if (out == '\\' && *c) {
-        int slash = *(c++), n = unescape(slash);
-
-        if (n) out = n;
-        else if (slash=='c') return;
-        else if (slash=='0') {
-          out = 0;
-          while (*c>='0' && *c<='7' && n++<3) out = (out*8)+*(c++)-'0';
-        } else if (slash=='x') {
-          out = 0;
-          while (n++<2) {
-            if (*c>='0' && *c<='9') out = (out*16)+*(c++)-'0';
-            else {
-              int temp = tolower(*c);
-              if (temp>='a' && temp<='f') {
-                out = (out*16)+temp-'a'+10;
-                c++;
-              } else {
-                if (n==1) {
-                  --c;
-                  out = '\\';
-                }
-                break;
-              }
-            }
-          }
-        // Slash in front of unknown character, print literal.
-        } else c--;
-      }
-      putchar(out);
+      if (*c == '\\' && c[1] == 'c') return;
+      if ((u = unescape2(&c, 1))<128) putchar(u);
+      else printf("%.*s", (int)wcrtomb(out, u, 0), out);
     }
   }
 
diff --git a/toys/posix/env.c b/toys/posix/env.c
index 73c8951..750ba49 100644
--- a/toys/posix/env.c
+++ b/toys/posix/env.c
@@ -6,7 +6,7 @@
  *
  * Deviations from posix: "-" argument and -0
 
-USE_ENV(NEWTOY(env, "^0iu*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
+USE_ENV(NEWTOY(env, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
 
 config ENV
   bool "env"
@@ -26,7 +26,7 @@
 
 GLOBALS(
   struct arg_list *u;
-);
+)
 
 void env_main(void)
 {
diff --git a/toys/posix/expand.c b/toys/posix/expand.c
index f1fd8d3..f3cd44d 100644
--- a/toys/posix/expand.c
+++ b/toys/posix/expand.c
@@ -43,22 +43,18 @@
     }
     if (!len) break;
     for (i=0; i<len; i++) {
-      int width = 1;
+      wchar_t blah;
+      int width = utf8towc(&blah, toybuf+i, len-i);
       char c;
 
-      if (CFG_TOYBOX_I18N) {
-        wchar_t blah;
-
-        width = utf8towc(&blah, toybuf+i, len-i);
-        if (width > 1) {
-          if (width != fwrite(toybuf+i, width, 1, stdout))
-            perror_exit("stdout");
-          i += width-1;
-          x++;
-          continue;
-        } else if (width == -2) break;
-        else if (width == -1) continue;
-      }
+      if (width > 1) {
+        if (width != fwrite(toybuf+i, width, 1, stdout))
+          perror_exit("stdout");
+        i += width-1;
+        x++;
+        continue;
+      } else if (width == -2) break;
+      else if (width == -1) continue;
       c = toybuf[i];
 
       if (c != '\t') {
diff --git a/toys/posix/file.c b/toys/posix/file.c
index 4e84e1d..ecb3fc4 100644
--- a/toys/posix/file.c
+++ b/toys/posix/file.c
@@ -37,7 +37,7 @@
       phentsize, phnum, shsize, shnum;
   int64_t (*elf_int)(void *ptr, unsigned size);
   char *map = 0;
-  off_t phoff, shoff;
+  long phoff, shoff;
 
   printf("ELF ");
   elf_int = (endian==2) ? peek_be : peek_le;
@@ -107,11 +107,11 @@
 
   // We need to read the phdrs for dynamic vs static.
   // (Note: fields got reordered for 64 bit)
-  if (phoff+phnum*phentsize>TT.len) goto bad;
+  if (phoff<0 || phoff>TT.len || phnum*phentsize>TT.len-phoff) goto bad;
   for (i = 0; i<phnum; i++) {
     char *phdr = map+phoff+i*phentsize;
     int p_type = elf_int(phdr, 4);
-    long long p_offset, p_filesz;
+    unsigned long long p_offset, p_filesz;
 
     if (p_type==2 /*PT_DYNAMIC*/) dynamic = 1;
     if (p_type!=3 /*PT_INTERP*/ && p_type!=4 /*PT_NOTE*/) continue;
@@ -121,7 +121,7 @@
     p_filesz = elf_int(phdr+16*j, 4*j);
 
     if (p_type==3 /*PT_INTERP*/) {
-      if (p_offset+p_filesz>TT.len) goto bad;
+      if (p_filesz>TT.len || p_offset>TT.len-p_filesz) goto bad;
       printf(", dynamic (%.*s)", (int)p_filesz, map+p_offset);
     }
   }
@@ -131,12 +131,17 @@
   // Notes are in program headers *and* section headers, but some files don't
   // contain program headers, so we prefer to check here.
   // (Note: fields got reordered for 64 bit)
-  if (shoff+i*shnum>TT.len) goto bad;
+  if (shoff<0 || shoff>TT.len || shnum*shsize>TT.len-shoff) goto bad;
   for (i = 0; i<shnum; i++) {
     char *shdr = map+shoff+i*shsize;
-    int sh_type = elf_int(shdr+4, 4);
-    long sh_offset = elf_int(shdr+8+8*(bits+1), 4*(bits+1));
-    int sh_size = elf_int(shdr+8+12*(bits+1), 4);
+    unsigned long sh_offset;
+    int sh_type, sh_size;
+
+    if (shdr>map+TT.len-(8+4*(bits+1))) goto bad;
+    sh_type = elf_int(shdr+4, 4);
+    sh_offset = elf_int(shdr+8+8*(bits+1), 4*(bits+1));
+    sh_size = elf_int(shdr+8+12*(bits+1), 4);
+    if (sh_offset>TT.len || sh_size>TT.len-sh_offset) goto bad;
 
     if (sh_type == 2 /*SHT_SYMTAB*/) {
       stripped = 0;
@@ -151,12 +156,13 @@
       while (sh_size >= 3*4) { // Don't try to read a truncated entry.
         unsigned n_namesz, n_descsz, n_type, notesz;
 
-        if (sh_offset+sh_size>TT.len) goto bad;
+        if (note>map+TT.len-3*4) goto bad;
 
         n_namesz = elf_int(note, 4);
         n_descsz = elf_int(note+4, 4);
         n_type = elf_int(note+8, 4);
         notesz = 3*4 + ((n_namesz+3)&~3) + ((n_descsz+3)&~3);
+        if (notesz<n_namesz || notesz<n_descsz) goto bad;
 
         // Does the claimed size of this note actually fit in the section?
         if (notesz > sh_size) goto bad;
@@ -191,7 +197,7 @@
 static void do_regular_file(int fd, char *name)
 {
   char *s;
-  int len, magic;
+  unsigned len, magic;
 
   // zero through elf shnum, just in case
   memset(toybuf, 0, 80);
@@ -225,8 +231,8 @@
 
   // https://www.w3.org/Graphics/GIF/spec-gif89a.txt
   } else if (len>16 && (strstart(&s, "GIF87a") || strstart(&s, "GIF89a")))
-    xprintf("GIF image data, %d x %d\n",
-      (int)peek_le(s, 2), (int)peek_le(s+8, 2));
+    xprintf("GIF image data, version %3.3s, %d x %d\n",
+      s-3, (int)peek_le(s, 2), (int)peek_le(s+2, 2));
 
   // TODO: parsing JPEG for width/height is harder than GIF or PNG.
   else if (len>32 && !memcmp(toybuf, "\xff\xd8", 2)) xputs("JPEG image data");
@@ -264,6 +270,12 @@
     xprintf("Zip archive data");
     if (ver) xprintf(", requires at least v%d.%d to extract", ver/10, ver%10);
     xputc('\n');
+  } else if (len>9 && strstart(&s, "7z\xbc\xaf\x27\x1c")) {
+    int ver = toybuf[6]*10+toybuf[7];
+
+    xprintf("7-zip archive data");
+    if (ver) xprintf(", version %d.%d", ver/10, ver%10);
+    xputc('\n');
   } else if (len>4 && strstart(&s, "BZh") && isdigit(*s))
     xprintf("bzip2 compressed data, block size = %c00k\n", *s);
   else if (len > 31 && peek_be(s, 7) == 0xfd377a585a0000UL)
@@ -388,6 +400,11 @@
     xprintf("Android DTB/DTBO v%d, %d entries\n", (int) peek_be(s+28, 4),
         (int) peek_be(s+16, 4));
 
+    // frameworks/base/core/java/com/android/internal/util/BinaryXmlSerializer.java
+  } else if (len>4 && !memcmp(s, "ABX", 3)) {
+    xprintf("Android Binary XML v%d\n", s[3]);
+
+    // Text files, including shell scripts.
   } else {
     char *what = 0;
     int i, bytes;
diff --git a/toys/posix/find.c b/toys/posix/find.c
index 3fb97b1..3824f0c 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -35,7 +35,7 @@
     -inum N          inode number N            -empty      empty files and dirs
     -type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)
     -true            always true               -false      always false
-    -context PATTERN security context
+    -context PATTERN security context          -executable access(X_OK) perm+ACL
     -newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)
 
     Numbers N may be prefixed by a - (less than) or + (greater than). Units for
@@ -345,11 +345,13 @@
         } else test = 0;
       }
     } else if (!strcmp(s, "nouser")) {
-      if (check) if (bufgetpwuid(new->st.st_uid)) test = 0;
+      if (check && bufgetpwuid(new->st.st_uid)) test = 0;
     } else if (!strcmp(s, "nogroup")) {
-      if (check) if (bufgetgrgid(new->st.st_gid)) test = 0;
+      if (check && bufgetgrgid(new->st.st_gid)) test = 0;
     } else if (!strcmp(s, "prune")) {
       if (check && S_ISDIR(new->st.st_mode) && !TT.depth) recurse = 0;
+    } else if (!strcmp(s, "executable")) {
+      if (check && faccessat(dirtree_parentfd(new), new->name,X_OK,0)) test = 0;
 
     // Remaining filters take an argument
     } else {
@@ -398,10 +400,16 @@
       } else if (!strcmp(s, "type")) {
         if (check) {
           int types[] = {S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFIFO,
-                         S_IFREG, S_IFSOCK}, i = stridx("bcdlpfs", *ss[1]);
+                         S_IFREG, S_IFSOCK}, i;
+          char *t = ss[1];
 
-          if (i<0) error_exit("bad -type '%c'", *ss[1]);
-          if ((new->st.st_mode & S_IFMT) != types[i]) test = 0;
+          for (; *t; t++) {
+            if (*t == ',') continue;
+            i = stridx("bcdlpfs", *t);
+            if (i<0) error_exit("bad -type '%c'", *t);
+            if ((new->st.st_mode & S_IFMT) == types[i]) break;
+          }
+          test = *t;
         }
 
       } else if (strchr("acm", *s)
@@ -585,16 +593,12 @@
         if (check) for (fmt = ss[1]; *fmt; fmt++) {
           // Print the parts that aren't escapes
           if (*fmt == '\\') {
-            int slash = *++fmt, n = unescape(slash);
+            unsigned u;
 
-            if (n) ch = n;
-            else if (slash=='c') break;
-            else if (slash=='0') {
-              ch = 0;
-              while (*fmt>='0' && *fmt<='7' && n++<3) ch=(ch*8)+*(fmt++)-'0';
-              --fmt;
-            } else error_exit("bad \\%c", *fmt);
-            putchar(ch);
+            if (fmt[1] == 'c') break;
+            if ((u = unescape2(&fmt, 0))<128) putchar(u);
+            else printf("%.*s", (int)wcrtomb(buf, u, 0), buf);
+            fmt--;
           } else if (*fmt != '%') putchar(*fmt);
           else if (*++fmt == '%') putchar('%');
           else {
@@ -688,7 +692,7 @@
 
   // Distinguish paths from filters
   for (len = 0; toys.optargs[len]; len++)
-    if (strchr("-!(", *toys.optargs[len])) break;
+    if (*toys.optargs[len] && strchr("-!(", *toys.optargs[len])) break;
   TT.filter = toys.optargs+len;
 
   // use "." if no paths
diff --git a/toys/posix/grep.c b/toys/posix/grep.c
index 1fa1a7c..52d1013 100644
--- a/toys/posix/grep.c
+++ b/toys/posix/grep.c
@@ -10,9 +10,9 @@
 * echo hello | grep -f </dev/null
 *
 
-USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
-USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
-USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_GREP(NEWTOY(grep, "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rRsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]", TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
+USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
+USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_LINEBUF))
 
 config GREP
   bool "grep"
@@ -65,7 +65,6 @@
 
 #define FOR_grep
 #include "toys.h"
-#include <regex.h>
 
 GLOBALS(
   long m, A, B, C;
@@ -155,7 +154,7 @@
     lcount++;
     errno = 0;
     ulen = len = getdelim(&line, &ulen, TT.indelim, file);
-    if (errno) perror_msg("%s", name);
+    if (len == -1 && errno) perror_msg("%s", name);
     if (len<1) break;
     if (line[ulen-1] == TT.indelim) line[--ulen] = 0;
 
@@ -219,8 +218,12 @@
         }
       }
 
-      if (!rc && FLAG(x))
-        if (mm->rm_so || line[mm->rm_eo]) rc = 1;
+      if (!rc && FLAG(o) && !mm->rm_eo && ulen>start-line) {
+        start++;
+        continue;
+      }
+
+      if (!rc && FLAG(x) && (mm->rm_so || ulen-(start-line)!=mm->rm_eo)) rc = 1;
 
       if (!rc && FLAG(w)) {
         char c = 0;
@@ -241,10 +244,8 @@
 
       if (FLAG(v)) {
         if (FLAG(o)) {
-          if (rc) {
-            mm->rm_so = 0;
-            mm->rm_eo = ulen-(start-line);
-          } else if (!mm->rm_so) {
+          if (rc) mm->rm_eo = ulen-(start-line);
+          else if (!mm->rm_so) {
             start += mm->rm_eo;
             continue;
           } else mm->rm_eo = mm->rm_so;
@@ -373,20 +374,12 @@
   // exit to free. Not supporting nofork for this command any time soon.)
   al = TT.f ? TT.f : TT.e;
   while (al) {
-    if (TT.f) s = ss = xreadfile(al->arg, 0, 0);
-    else s = ss = al->arg;
-
-    // Split lines at \n, add individual lines to new list.
-    do {
-// TODO: NUL terminated input shouldn't split -e at \n
-      ss = strchr(s, '\n');
-      if (ss) *(ss++) = 0;
-      new = xmalloc(sizeof(struct arg_list));
-      new->next = list;
-      new->arg = s;
-      list = new;
-      s = ss;
-    } while (ss && *s);
+    if (TT.f) {
+      if (!*(s = ss = xreadfile(al->arg, 0, 0))) {
+        free(ss);
+        s = 0;
+      }
+    } else s = ss = al->arg;
 
     // Advance, when we run out of -f switch to -e.
     al = al->next;
@@ -394,6 +387,18 @@
       TT.f = 0;
       al = TT.e;
     }
+    if (!s) continue;
+
+    // Split lines at \n, add individual lines to new list.
+    do {
+      ss = FLAG(z) ? 0 : strchr(s, '\n');
+      if (ss) *(ss++) = 0;
+      new = xmalloc(sizeof(struct arg_list));
+      new->next = list;
+      new->arg = s;
+      list = new;
+      s = ss;
+    } while (ss && *s);
   }
   TT.e = list;
 
diff --git a/toys/posix/logger.c b/toys/posix/logger.c
index 3bcfb17..906d64f 100644
--- a/toys/posix/logger.c
+++ b/toys/posix/logger.c
@@ -6,7 +6,7 @@
  *
  * Deviations from posix: specified manner and format, defined implementation.
 
-USE_LOGGER(NEWTOY(logger, "st:p:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_LOGGER(NEWTOY(logger, "t:p:s", TOYFLAG_USR|TOYFLAG_BIN))
 
 config LOGGER
   bool "logger"
@@ -30,46 +30,44 @@
 
 // find str in names[], accepting unambiguous short matches
 // returns offset into array of match, or -1 if no match
-int arrayfind(char *str, char *names[], int len)
+// TODO: move to lib?
+static int arrayfind(char *str, char *names[], int len)
 {
-  int try, i, matchlen = 0, found = -1, ambiguous = 1;
+  int j, i, ll = 0, maybe = -1;
 
-  for (try = 0; try<len; try++) {
-    for (i=0; ; i++) {
-      if (!str[i]) {
-        if (matchlen<i) found = try, ambiguous = 0;
-        if (matchlen==i) ambiguous++;
-        if (!names[try][i]) return try;
-        break;
-      }
-      if (!names[try][i]) break;
-      if (toupper(str[i]) != toupper(names[try][i])) break;
+  for (j = 0; j<len; j++) for (i=0; ; i++) {
+    if (!str[i]) {
+      if (!names[j][i]) return j;
+      if (i>ll) maybe = j;
+      else if (i==ll) maybe = -1;
+      break;
     }
+    if (!names[j][i] || toupper(str[i])!=toupper(names[j][i])) break;
   }
-  return ambiguous ? -1 : found;
+
+  return maybe;
 }
 
 void logger_main(void)
 {
-  int facility = LOG_USER, priority = LOG_NOTICE, len;
+  int facility = LOG_USER, priority = LOG_NOTICE, len = 0;
   char *s1, *s2, **arg,
     *priorities[] = {"emerg", "alert", "crit", "error", "warning", "notice",
                      "info", "debug"},
     *facilities[] = {"kern", "user", "mail", "daemon", "auth", "syslog",
                      "lpr", "news", "uucp", "cron", "authpriv", "ftp"};
 
-  if (!TT.t) TT.t = xstrdup(xgetpwuid(geteuid())->pw_name);
-  if (toys.optflags & FLAG_p) {
+  if (!TT.t) TT.t = xgetpwuid(geteuid())->pw_name;
+  if (TT.p) {
     if (!(s1 = strchr(TT.p, '.'))) s1 = TT.p;
     else {
-      *s1++ = len = 0;
+      *s1++ = 0;
       facility = arrayfind(TT.p, facilities, ARRAY_LEN(facilities));
-      if (facility == -1 && strncasecmp(TT.p, "local", 5)) {
-        facility = s1[5]-'0';
-        if (facility>7 || s1[6]) facility = -1;
-        if (facility>=0) facility += 16;
+      if (facility<0) {
+        if (sscanf(TT.p, "local%d", &facility)>0 && !(facility&~7))
+          facility += 16;
+        else error_exit("bad facility: %s", TT.p);
       }
-      if (facility<0) error_exit("bad facility: %s", TT.p);
       facility *= 8;
     }
 
@@ -78,18 +76,15 @@
   }
 
   if (toys.optc) {
-    for (len = 0, arg = toys.optargs; *arg; arg++) len += strlen(*arg)+1;
+    for (arg = toys.optargs; *arg; arg++) len += strlen(*arg)+1;
     s1 = s2 = xmalloc(len);
     for (arg = toys.optargs; *arg; arg++) {
       if (arg != toys.optargs) *s2++ = ' ';
       s2 = stpcpy(s2, *arg);
     }
-  } else {
-    toybuf[readall(0, toybuf, sizeof(toybuf)-1)] = 0;
-    s1 = toybuf;
-  }
+  } else toybuf[readall(0, s1 = toybuf, sizeof(toybuf)-1)] = 0;
 
-  openlog(TT.t, LOG_PERROR*!!(toys.optflags&FLAG_s), facility);
+  openlog(TT.t, LOG_PERROR*FLAG(s), facility);
   syslog(priority, "%s", s1);
   closelog();
 }
diff --git a/toys/posix/ls.c b/toys/posix/ls.c
index fb4f7d5..d9cd90b 100644
--- a/toys/posix/ls.c
+++ b/toys/posix/ls.c
@@ -151,7 +151,7 @@
     } else len[5] = print_with_h(tmp, st->st_size, 1);
   }
 
-  len[6] = FLAG(s) ? print_with_h(tmp, st->st_blocks, 512) : 0;
+  len[6] = FLAG(s) ? print_with_h(tmp, st->st_blocks, 1024) : 0;
   len[7] = FLAG(Z) ? strwidth((char *)dt->extra) : 0;
 }
 
@@ -200,7 +200,7 @@
 
   if (FLAG(u)) new->st.st_mtime = new->st.st_atime;
   if (FLAG(c)) new->st.st_mtime = new->st.st_ctime;
-  new->st.st_blocks >>= 1;
+  new->st.st_blocks >>= 1; // Use 1KiB blocks rather than 512B blocks.
 
   if (FLAG(a)||FLAG(f)) return DIRTREE_SAVE;
   if (!FLAG(A) && new->name[0]=='.') return 0;
@@ -272,7 +272,7 @@
   }
 
   memset(totals, 0, sizeof(totals));
-  if (CFG_TOYBOX_ON_ANDROID || CFG_TOYBOX_DEBUG) memset(len, 0, sizeof(len));
+  memset(len, 0, sizeof(len));
 
   // Top level directory was already populated by main()
   if (!indir->parent) {
@@ -325,7 +325,7 @@
     totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7];
     if ((FLAG(h)||FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)||FLAG(s)) && indir->parent)
     {
-      print_with_h(tmp, blocks, 512);
+      print_with_h(tmp, blocks, 1024);
       xprintf("total %s\n", tmp);
     }
   }
@@ -401,7 +401,7 @@
     if (FLAG(i)) zprint(zap, "lu ", totals[1], st->st_ino);
 
     if (FLAG(s)) {
-      print_with_h(tmp, st->st_blocks, 512);
+      print_with_h(tmp, st->st_blocks, 1024);
       zprint(zap, "s ", totals[6], (unsigned long)tmp);
     }
 
diff --git a/toys/posix/patch.c b/toys/posix/patch.c
index d3d7779..f0aad4e 100644
--- a/toys/posix/patch.c
+++ b/toys/posix/patch.c
@@ -125,12 +125,12 @@
 static int apply_one_hunk(void)
 {
   struct double_list *plist, *buf = 0, *check;
-  int matcheof, trail = 0, reverse = FLAG(R), backwarn = 0, allfuzz = 0, fuzz,i;
+  int matcheof, trail = 0, reverse = FLAG(R), backwarn = 0, allfuzz, fuzz, i;
   int (*lcmp)(char *aa, char *bb) = FLAG(l) ? (void *)loosecmp : (void *)strcmp;
 
   // Match EOF if there aren't as many ending context lines as beginning
   dlist_terminate(TT.current_hunk);
-  for (plist = TT.current_hunk; plist; plist = plist->next) {
+  for (fuzz = 0, plist = TT.current_hunk; plist; plist = plist->next) {
     char c = *plist->data, *s;
 
     if (c==' ') trail++;
@@ -142,22 +142,19 @@
     if (c==' ' || c=="-+"[reverse]) {
       s = plist->data+1;
       while (isspace(*s)) s++;
-      if (*s && s[1] && !isspace(s[1])) allfuzz++;
+      if (*s && s[1] && !isspace(s[1])) fuzz++;
     }
 
     if (FLAG(x)) fprintf(stderr, "HUNK:%s\n", plist->data);
   }
   matcheof = !trail || trail < TT.context;
-  if (allfuzz<2) allfuzz = 0;
-  else allfuzz = FLAG(F) ? TT.F : TT.context ? TT.context-1 : 0;
-  if (allfuzz>=sizeof(toybuf)/sizeof(long))
-    allfuzz = (sizeof(toybuf)/sizeof(long))-1;
+  if (fuzz<2) allfuzz = 0;
+  else allfuzz = FLAG(F) ? TT.F : (TT.context ? TT.context-1 : 0);
 
   if (FLAG(x)) fprintf(stderr,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
 
   // Loop through input data searching for this hunk. Match all context
-  // lines and all lines to be removed until we've found the end of a
-  // complete hunk.
+  // lines and lines to be removed until we've found end of complete hunk.
   plist = TT.current_hunk;
   fuzz = 0;
   for (;;) {
@@ -190,27 +187,22 @@
       if (FLAG(x)) fprintf(stderr, "IN: %s\n", data);
     }
     check = dlist_add(&buf, data);
-    // Compare this line with next expected line of hunk.
 
-    // A match can fail because the next line doesn't match, or because
-    // we hit the end of a hunk that needed EOF and this isn't EOF.
-
-    // If match failed, flush first line of buffered data and
-    // recheck buffered data for a new match until we find one or run
-    // out of buffer.
+    // Compare this line with next expected line of hunk. Match can fail
+    // because next line doesn't match, or because we hit end of a hunk that
+    // needed EOF and this isn't EOF.
     for (i = 0;; i++) {
       if (!plist || lcmp(check->data, plist->data+1)) {
+
+        // Match failed: can we fuzz it?
         if (plist && *plist->data == ' ' && fuzz<allfuzz) {
           if (FLAG(x))
             fprintf(stderr, "FUZZED: %ld %s\n", TT.linenum, plist->data);
-          ((long *)toybuf)[fuzz++] = TT.outnum+i;
+          fuzz++;
 
           goto fuzzed;
         }
 
-        // Match failed.  Write out first line of buffered data and
-        // recheck remaining buffered data for a new match.
-
         if (FLAG(x)) {
           int bug = 0;
 
@@ -228,14 +220,13 @@
           goto done;
         }
 
+        // Write out first line of buffer and recheck rest for new match.
         TT.state = 3;
         do_line(check = dlist_pop(&buf));
         plist = TT.current_hunk;
-        memset(toybuf, 0, (fuzz+7)/8);
         fuzz = 0;
 
-        // If we've reached the end of the buffer without confirming a
-        // match, read more lines.
+        // If end of the buffer without finishing a match, read more lines.
         if (!buf) break;
         check = buf;
       } else {
@@ -252,13 +243,9 @@
 out:
   // We have a match.  Emit changed data.
   TT.state = "-+"[reverse];
-  allfuzz = 0;
   while ((plist = dlist_pop(&TT.current_hunk))) {
     if (TT.state == *plist->data || *plist->data == ' ') {
-      if (((long *)toybuf)[allfuzz] == ++TT.outnum) {
-        dprintf(TT.fileout, "%s\n", buf->data);
-        allfuzz++;
-      } else if (*plist->data == ' ') dprintf(TT.fileout, "%s\n",plist->data+1);
+      if (*plist->data == ' ') dprintf(TT.fileout, "%s\n", buf->data);
       llist_free_double(dlist_pop(&buf));
     } else dprintf(TT.fileout, "%s\n", plist->data+1);
     llist_free_double(plist);
@@ -272,7 +259,7 @@
 }
 
 // read a filename that has been quoted or escaped
-char *unquote_file(char *filename)
+static char *unquote_file(char *filename)
 {
   char *s = filename, *t;
 
@@ -327,7 +314,7 @@
     // Other versions of patch accept damaged patches, so we need to also.
     if (strip || !patchlinenum++) {
       int len = strlen(patchline);
-      if (patchline[len-1] == '\r') {
+      if (len && patchline[len-1] == '\r') {
         if (!strip && !FLAG(s)) fprintf(stderr, "Removing DOS newlines\n");
         strip = 1;
         patchline[len-1]=0;
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index cd8c73e..304fe00 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -213,7 +213,7 @@
   dev_t tty;
   void *fields, *kfields;
   long long ticks, bits, time;
-  int kcount, forcek, sortpos;
+  int kcount, forcek, sortpos, pidlen;
   int (*match_process)(long long *slot);
   void (*show_process)(void *tb);
 )
@@ -311,7 +311,6 @@
 #define XX 64 // force string representation for sorting, etc
 
 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
-// TODO: ideally, PID and PPID would auto-size too.
 struct typography {
   char *name, *help;
   signed char width, slot;
@@ -600,7 +599,7 @@
     }
     if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE);
     if (TT.forcek) sprintf(out, "%lldk", ll/1024);
-    else human_readable_long(s, ll, i-1, 0);
+    else human_readable_long(s, ll, i-1, 0, 0);
 
   // Posix doesn't specify what flags should say. Man page says
   // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
@@ -1112,8 +1111,9 @@
   }
   if (i==ARRAY_LEN(typos)) return type;
   if (!field->title) field->title = typos[field->which].name;
-  if (!field->len) field->len = typos[field->which].width;
-  else if (typos[field->which].width<0) field->len *= -1;
+  k = i<2 ? TT.pidlen : typos[field->which].width;
+  if (!field->len) field->len = k;
+  else if (k<0) field->len *= -1;
   dlist_add_nomalloc(data, (void *)field);
 
   return 0;
@@ -1280,11 +1280,9 @@
   if (x) help_help();
 }
 
-void ps_main(void)
+static void common_setup(void)
 {
-  char **arg;
-  struct dirtree *dt;
-  char *not_o;
+  char buf[128];
   int i;
 
   TT.ticks = sysconf(_SC_CLK_TCK); // units for starttime/uptime
@@ -1295,6 +1293,20 @@
     if (!fstat(i, &st)) TT.tty = st.st_rdev;
   }
 
+  if (readfile("/proc/sys/kernel/pid_max", buf, 128))
+    while (isdigit(buf[TT.pidlen])) TT.pidlen++;
+  else TT.pidlen = 6;
+}
+
+void ps_main(void)
+{
+  char **arg;
+  struct dirtree *dt;
+  char *not_o;
+  int i;
+
+  common_setup();
+
   // If we can't query terminal size pad to 80 but do -w
   TT.width = 80;
   if (!isatty(1) || !terminal_size(&TT.width, 0)) toys.optflags |= FLAG_w;
@@ -1568,7 +1580,7 @@
           char hr[4][32];
           long long ll, up = 0;
           long run[6];
-          int j;
+          int j, k;
 
           // Count running, sleeping, stopped, zombie processes.
           // The kernel has more states (and different sets in different
@@ -1590,19 +1602,19 @@
               j = i%3;
               pos = strafter(toybuf+256, (char *[]){"MemTotal:","\nMemFree:",
                     "\nBuffers:","\nSwapTotal:","\nSwapFree:","\nCached:"}[i]);
-              human_readable_long(hr[j+!!j], 1024*(run[i] = pos?atol(pos):0),
-                8, 0);
-              if (j==1) human_readable_long(hr[1], 1024*(run[i-1]-run[i]), 8,0);
+              run[i] = pos ? atol(pos) : 0;
+              k = (*run>=10000000);
+              human_readable_long(hr[j+!!j], run[i]>>(10*k), 9, k+1, HR_NODOT);
+              if (j==1) human_readable_long(hr[1], (run[i-1]-run[i])>>(10*k),
+                8, k+1, HR_NODOT);
               else if (j==2) {
-                sprintf(toybuf, (i<3)
-                  ? "  Mem: %9s total, %9s used, %9s free, %9s buffers"
-                  : " Swap: %9s total, %9s used, %9s free, %9s cached",
-                  hr[0], hr[1], hr[2], hr[3]);
+                sprintf(toybuf, " %s:%10s total,%10s used,%10s free,%10s %s",
+                  (i<3) ? " Mem" : "Swap", hr[0], hr[1], hr[2], hr[3],
+                  (i<3) ? "buffers" : "cached");
                 lines = header_line(lines, 0);
               }
             }
           }
-
           pos = toybuf;
           i = sysconf(_SC_NPROCESSORS_CONF);
           pos += sprintf(pos, "%d%%cpu", i*100);
@@ -1733,8 +1745,7 @@
 
 static void top_setup(char *defo, char *defk)
 {
-  TT.ticks = sysconf(_SC_CLK_TCK); // units for starttime/uptime
-  TT.tty = tty_fd() != -1;
+  common_setup();
 
   // Are we doing "batch" output or interactive?
   if (FLAG(b)) TT.width = TT.height = 99999;
diff --git a/toys/posix/rm.c b/toys/posix/rm.c
index eec586c..be611e6 100644
--- a/toys/posix/rm.c
+++ b/toys/posix/rm.c
@@ -105,14 +105,13 @@
       continue;
     }
 
-    // Files that already don't exist aren't errors for -f, so try a quick
-    // unlink now to see if it succeeds or reports that it didn't exist.
-    if (FLAG(f) && (!unlink(*s) || errno == ENOENT)) continue;
+    // Files that already don't exist aren't errors for -f. Use lstat() instead
+    // of faccessat() because bionic doesn't support AT_SYMLINK_NOFOLLOW
+    if (FLAG(f) && lstat(*s, (void *)toybuf) && errno == ENOENT) continue;
 
     // There's a race here where a file removed between the above check and
     // dirtree's stat would report the nonexistence as an error, but that's
     // not a normal "it didn't exist" so I'm ok with it.
-
     dirtree_read(*s, do_rm);
   }
 }
diff --git a/toys/posix/rmdir.c b/toys/posix/rmdir.c
index 3bece15..3714f33 100644
--- a/toys/posix/rmdir.c
+++ b/toys/posix/rmdir.c
@@ -4,7 +4,7 @@
  *
  * See http://opengroup.org/onlinepubs/9699919799/utilities/rmdir.html
 
-USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p", TOYFLAG_BIN))
+USE_RMDIR(NEWTOY(rmdir, "<1(ignore-fail-on-non-empty)p(parents)", TOYFLAG_BIN))
 
 config RMDIR
   bool "rmdir"
@@ -25,7 +25,7 @@
 {
   char *temp;
 
-  for (;;) {
+  do {
     if (rmdir(name)) {
       if (!FLAG(ignore_fail_on_non_empty) || errno != ENOTEMPTY)
         perror_msg_raw(name);
@@ -39,7 +39,7 @@
       if (!(temp = strrchr(name, '/'))) return;
       *temp = 0;
     } while (!temp[1]);
-  }
+  } while (*name);
 }
 
 void rmdir_main(void)
diff --git a/toys/posix/sed.c b/toys/posix/sed.c
index fa40dbf..d5a4a83 100644
--- a/toys/posix/sed.c
+++ b/toys/posix/sed.c
@@ -10,17 +10,22 @@
  * TODO: handle error return from emit(), error_msg/exit consistently
  *       What's the right thing to do for -i when write fails? Skip to next?
  * test '//q' with no previous regex, also repeat previous regex?
+ *
+ * Deviations from POSIX: allow extended regular expressions with -r,
+ * editing in place with -i, separate with -s, NUL-separated input with -z,
+ * printf escapes in text, line continuations, semicolons after all commands,
+ * 2-address anywhere an address is allowed, "T" command, multiline
+ * continuations for [abc], \; to end [abc] argument before end of line.
 
-USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
+USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
 
 config SED
   bool "sed"
   default y
   help
-    usage: sed [-inrzE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]
+    usage: sed [-inrszE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...]
 
-    Stream editor. Apply one or more editing SCRIPTs to each line of input
-    (from FILE or stdin) producing output (by default to stdout).
+    Stream editor. Apply editing SCRIPTs to lines of input.
 
     -e	Add SCRIPT to list
     -f	Add contents of SCRIPT_FILE to list
@@ -29,144 +34,86 @@
     -r	Use extended regular expression syntax
     -E	POSIX alias for -r
     -s	Treat input files separately (implied by -i)
-    -z	Use \0 rather than \n as the input line separator
+    -z	Use \0 rather than \n as input line separator
 
-    A SCRIPT is a series of one or more COMMANDs separated by newlines or
-    semicolons. All -e SCRIPTs are concatenated together as if separated
-    by newlines, followed by all lines from -f SCRIPT_FILEs, in order.
-    If no -e or -f SCRIPTs are specified, the first argument is the SCRIPT.
+    A SCRIPT is one or more COMMANDs separated by newlines or semicolons.
+    All -e SCRIPTs are combined as if separated by newlines, followed by all -f
+    SCRIPT_FILEs. If no -e or -f then first argument is the SCRIPT.
 
-    Each COMMAND may be preceded by an address which limits the command to
-    apply only to the specified line(s). Commands without an address apply to
-    every line. Addresses are of the form:
+    COMMANDs apply to every line unless prefixed with an ADDRESS of the form:
 
       [ADDRESS[,ADDRESS]][!]COMMAND
 
-    The ADDRESS may be a decimal line number (starting at 1), a /regular
-    expression/ within a pair of forward slashes, or the character "$" which
-    matches the last line of input. (In -s or -i mode this matches the last
-    line of each file, otherwise just the last line of the last file.) A single
-    address matches one line, a pair of comma separated addresses match
-    everything from the first address to the second address (inclusive). If
-    both addresses are regular expressions, more than one range of lines in
-    each file can match. The second address can be +N to end N lines later.
+    ADDRESS is a line number (starting at 1), a /REGULAR EXPRESSION/, or $ for
+    last line (-s or -i makes it last line of each file). One address matches one
+    line, ADDRESS,ADDRESS matches from first to second inclusive. Two regexes can
+    match multiple ranges. ADDRESS,+N ends N lines later. ! inverts the match.
 
-    REGULAR EXPRESSIONS in sed are started and ended by the same character
-    (traditionally / but anything except a backslash or a newline works).
-    Backslashes may be used to escape the delimiter if it occurs in the
-    regex, and for the usual printf escapes (\abcefnrtv and octal, hex,
-    and unicode). An empty regex repeats the previous one. ADDRESS regexes
-    (above) require the first delimiter to be escaped with a backslash when
-    it isn't a forward slash (to distinguish it from the COMMANDs below).
+    REGULAR EXPRESSIONS start and end with the same character (anything but
+    backslash or newline). To use the delimiter in the regex escape it with a
+    backslash, and printf escapes (\abcefnrtv and octal, hex, and unicode) work.
+    An empty regex repeats the previous one. ADDRESS regexes require any
+    first delimiter except / to be \escaped to distinguish it from COMMANDs.
 
-    Sed mostly operates on individual lines one at a time. It reads each line,
-    processes it, and either writes it to the output or discards it before
-    reading the next line. Sed can remember one additional line in a separate
-    buffer (using the h, H, g, G, and x commands), and can read the next line
-    of input early (using the n and N command), but other than that command
-    scripts operate on individual lines of text.
+    Sed reads each line of input, processes it, and writes it out or discards it
+    before reading the next. Sed can remember one additional line in a separate
+    buffer (the h, H, g, G, and x commands), and can read the next line of input
+    early (the n and N commands), but otherwise operates on individual lines.
 
-    Each COMMAND starts with a single character. The following commands take
-    no arguments:
+    Each COMMAND starts with a single character. Commands with no arguments are:
 
-      !  Run this command when the test _didn't_ match.
-
-      {  Start a new command block, continuing until a corresponding "}".
-         Command blocks may nest. If the block has an address, commands within
-         the block are only run for lines within the block's address range.
-
-      }  End command block (this command cannot have an address)
-
+      !  Run this command when the ADDRESS _didn't_ match.
+      {  Start new command block, continuing until a corresponding "}".
+         Command blocks nest and can have ADDRESSes applying to the whole block.
+      }  End command block (this COMMAND cannot have an address)
       d  Delete this line and move on to the next one
          (ignores remaining COMMANDs)
-
       D  Delete one line of input and restart command SCRIPT (same as "d"
          unless you've glued lines together with "N" or similar)
-
       g  Get remembered line (overwriting current line)
-
       G  Get remembered line (appending to current line)
-
       h  Remember this line (overwriting remembered line)
-
       H  Remember this line (appending to remembered line, if any)
-
-      l  Print line, escaping \abfrtv (but not newline), octal escaping other
-         nonprintable characters, wrapping lines to terminal width with a
-         backslash, and appending $ to actual end of line.
-
-      n  Print default output and read next line, replacing current line
-         (If no next line available, quit processing script)
-
-      N  Append next line of input to this line, separated by a newline
-         (This advances the line counter for address matching and "=", if no
-         next line available quit processing script without default output)
-
+      l  Print line escaping \abfrtv (but not \n), octal escape other nonprintng
+         chars, wrap lines to terminal width with \, append $ to end of line.
+      n  Print default output and read next line over current line (quit at EOF)
+      N  Append \n and next line of input to this line. Quit at EOF without
+         default output. Advances line counter for ADDRESS and "=".
       p  Print this line
-
       P  Print this line up to first newline (from "N")
-
       q  Quit (print default output, no more commands processed or lines read)
-
       x  Exchange this line with remembered line (overwrite in both directions)
+      =  Print the current line number (plus newline)
+      #  Comment, ignores rest of this line of SCRIPT (until newline)
 
-      =  Print the current line number (followed by a newline)
+    Commands that take an argument: 
 
-    The following commands (may) take an argument. The "text" arguments (to
-    the "a", "b", and "c" commands) may end with an unescaped "\" to append
-    the next line (for which leading whitespace is not skipped), and also
-    treat ";" as a literal character (use "\;" instead).
-
-      a [text]   Append text to output before attempting to read next line
-
-      b [label]  Branch, jumps to :label (or with no label, to end of SCRIPT)
-
-      c [text]   Delete line, output text at end of matching address range
-                 (ignores remaining COMMANDs)
-
-      i [text]   Print text
-
-      r [file]   Append contents of file to output before attempting to read
-                 next line.
-
-      s/S/R/F    Search for regex S, replace matched text with R using flags F.
-                 The first character after the "s" (anything but newline or
-                 backslash) is the delimiter, escape with \ to use normally.
-
-                 The replacement text may contain "&" to substitute the matched
-                 text (escape it with backslash for a literal &), or \1 through
-                 \9 to substitute a parenthetical subexpression in the regex.
-                 You can also use the normal backslash escapes such as \n and
-                 a backslash at the end of the line appends the next line.
-
-                 The flags are:
-
-                 [0-9]    A number, substitute only that occurrence of pattern
-                 g        Global, substitute all occurrences of pattern
-                 i        Ignore case when matching
-                 p        Print the line if match was found and replaced
-                 w [file] Write (append) line to file if match replaced
-
-      t [label]  Test, jump to :label only if an "s" command found a match in
-                 this line since last test (replacing with same text counts)
-
-      T [label]  Test false, jump only if "s" hasn't found a match.
-
-      w [file]   Write (append) line to file
-
+      : LABEL    Target for jump commands
+      a TEXT     Append text to output before reading next line
+      b LABEL    Branch, jumps to :LABEL (with no LABEL to end of SCRIPT)
+      c TEXT     Delete matching ADDRESS range and output TEXT instead
+      i TEXT     Insert text (output immediately)
+      r FILE     Append contents of FILE to output before reading next line.
+      s/S/R/F    Search for regex S replace match with R using flags F. Delimiter
+                 is anything but \n or \, escape with \ to use in S or R. Printf
+                 escapes work. Unescaped & in R becomes full matched text, \1
+                 through \9 = parenthetical subexpression from S. \ at end of
+                 line appends next line of SCRIPT. The flags in F are:
+                 [0-9]    A number N, substitute only Nth match
+                 g        Global, substitute all matches
+                 i/I      Ignore case when matching
+                 p        Print resulting line when match found and replaced
+                 w [file] Write (append) line to file when match replaced
+      t LABEL    Test, jump if s/// command matched this line since last test 
+      T LABEL    Test false, jump to :LABEL only if no s/// found a match
+      w FILE     Write (append) line to file
       y/old/new/ Change each character in 'old' to corresponding character
                  in 'new' (with standard backslash escapes, delimiter can be
                  any repeated character except \ or \n)
 
-      : [label]  Labeled target for jump commands
-
-      #  Comment, ignore rest of this line of SCRIPT
-
-    Deviations from POSIX: allow extended regular expressions with -r,
-    editing in place with -i, separate with -s, NUL-separated input with -z,
-    printf escapes in text, line continuations, semicolons after all commands,
-    2-address anywhere an address is allowed, "T" command, multiline
-    continuations for [abc], \; to end [abc] argument before end of line.
+    The TEXT arguments (to a c i) may end with an unescaped "\" to append
+    the next line (leading whitespace is not skipped), and treat ";" as a
+    literal character (use "\;" instead).
 */
 
 #define FOR_sed
@@ -200,7 +147,7 @@
   int rmatch[2];  // offset of regex struct for prefix matches (/abc/,/def/p)
   int arg1, arg2, w; // offset of two arguments per command, plus s//w filename
   unsigned not, hit;
-  unsigned sflags; // s///flag bits: i=1, g=2, p=4
+  unsigned sflags; // s///flag bits: i=1, g=2, p=4, x=8
   char c; // action
 };
 
@@ -265,7 +212,7 @@
   int eol = 0, tea = 0;
 
   // Ignore EOF for all files before last unless -i
-  if (!pline && !FLAG(i)) return;
+  if (!pline && !FLAG(i) && !FLAG(s)) return;
 
   // Grab next line for deferred processing (EOF detection: we get a NULL
   // pline at EOF to flush last line). Note that only end of _last_ input
@@ -494,9 +441,9 @@
         } else zmatch = 0;
 
         // If we're replacing only a specific match, skip if this isn't it
-        off = command->sflags>>3;
+        off = command->sflags>>4;
         if (off && off != ++count) {
-          memcpy(l2+l2used, rline, match[0].rm_eo);
+          if (l2) memcpy(l2+l2used, rline, match[0].rm_eo);
           l2used += match[0].rm_eo;
           rline += match[0].rm_eo;
 
@@ -652,13 +599,15 @@
 // Callback called on each input file
 static void do_sed_file(int fd, char *name)
 {
-  char *tmp;
+  char *tmp, *s;
 
   if (FLAG(i)) {
-    struct sedcmd *command;
-
     if (!fd) return error_msg("-i on stdin");
     TT.fdout = copy_tempfile(fd, name, &tmp);
+  }
+  if (FLAG(i) || FLAG(s)) {
+    struct sedcmd *command;
+
     TT.count = 0;
     for (command = (void *)TT.pattern; command; command = command->next)
       command->hit = 0;
@@ -666,13 +615,13 @@
   do_lines(fd, TT.delim, sed_line);
   if (FLAG(i)) {
     if (TT.i && *TT.i) {
-      char *s = xmprintf("%s%s", name, TT.i);
-
-      xrename(name, s);
+      xrename(name, s = xmprintf("%s%s", name, TT.i));
       free(s);
     }
     replace_tempfile(-1, TT.fdout, &tmp);
     TT.fdout = 1;
+  }
+  if (FLAG(i) || FLAG(s)) {
     TT.nextline = 0;
     TT.nextlen = TT.noeol = 0;
   }
@@ -844,6 +793,7 @@
       if (!TT.nextlen--) break;
     } else if (c == 's') {
       char *end, delim = 0;
+      int flags;
 
       // s/pattern/replacement/flags
 
@@ -896,18 +846,20 @@
 
         if (isspace(*line) && *line != '\n') continue;
 
-        if (0 <= (l = stridx("igp", *line))) command->sflags |= 1<<l;
-        else if (!(command->sflags>>3) && 0<(l = strtol(line, &line, 10))) {
-          command->sflags |= l << 3;
+        if (0 <= (l = stridx("igpx", *line))) command->sflags |= 1<<l;
+        else if (*line == 'I') command->sflags |= 1<<0;
+        else if (!(command->sflags>>4) && 0<(l = strtol(line, &line, 10))) {
+          command->sflags |= l << 4;
           line--;
         } else break;
       }
+      flags = (FLAG(r) || (command->sflags&8)) ? REG_EXTENDED : 0;
+      if (command->sflags&1) flags |= REG_ICASE;
 
       // We deferred actually parsing the regex until we had the s///i flag
       // allocating the space was done by extend_string() above
       if (!*TT.remember) command->arg1 = 0;
-      else xregcomp((void *)(command->arg1 + (char *)command), TT.remember,
-        (REG_EXTENDED*!!FLAG(r))|((command->sflags&1)*REG_ICASE));
+      else xregcomp((void *)(command->arg1+(char *)command),TT.remember,flags);
       free(TT.remember);
       TT.remember = 0;
       if (*line == 'w') {
@@ -1071,8 +1023,8 @@
   loopfiles_rw(args, O_RDONLY|WARN_ONLY, 0, do_sed_file);
 
   // Provide EOF flush at end of cumulative input for non-i mode.
-  if (!FLAG(i)) {
-    toys.optflags |= FLAG_i;
+  if (!FLAG(i) && !FLAG(s)) {
+    toys.optflags |= FLAG_s;
     sed_line(0, 0);
   }
 
diff --git a/toys/posix/split.c b/toys/posix/split.c
index 0da8da6..daf6422 100644
--- a/toys/posix/split.c
+++ b/toys/posix/split.c
@@ -65,7 +65,7 @@
       if (j) error_exit("bad suffix");
       bytesleft = TT.b;
       linesleft = TT.l;
-      if (outfd != -1) close(outfd);
+      xclose(outfd);
       outfd = xcreate(TT.outfile, O_RDWR|O_CREAT|O_TRUNC, st.st_mode & 0777);
     }
 
@@ -86,7 +86,7 @@
   }
 
   if (CFG_TOYBOX_FREE) {
-    if (outfd != -1) close(outfd);
+    xclose(outfd);
     if (infd) close(infd);
     free(TT.outfile);
   }
diff --git a/toys/posix/tar.c b/toys/posix/tar.c
index 1ac9400..c4fb4fa 100644
--- a/toys/posix/tar.c
+++ b/toys/posix/tar.c
@@ -17,7 +17,7 @@
  * Why --exclude pattern but no --include? tar cvzf a.tgz dir --include '*.txt'
  *
 
-USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mode):(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)I(use-compress-program):J(xz)j(bzip2)z(gzip)S(sparse)O(to-stdout)P(absolute-names)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config TAR
   bool "tar"
@@ -38,9 +38,10 @@
     --mode MODE      Adjust modes           --mtime TIME  Override timestamps
     --owner NAME     Set file owner to NAME --group NAME  Set file group to NAME
     --sparse         Record sparse files
-    --restrict       All archive contents must extract under one subdirctory
+    --restrict       All archive contents must extract under one subdirectory
     --numeric-owner  Save/use/display uid and gid, not user/group name
     --no-recursion   Don't store directory contents
+    -I PROG          Filter through PROG to compress or PROG -d to decompress
 */
 
 #define FOR_tar
@@ -49,7 +50,7 @@
 GLOBALS(
   char *f, *C;
   struct arg_list *T, *X;
-  char *to_command, *owner, *group, *mtime, *mode;
+  char *I, *to_command, *owner, *group, *mtime, *mode;
   struct arg_list *exclude;
 
   struct double_list *incl, *excl, *seen;
@@ -84,14 +85,19 @@
        prefix[155], padd[12];
 };
 
+// Tar uses ASCII octal when it fits, base-256 otherwise.
+static int ascii_fits(unsigned long long val, int len)
+{
+  return !(val>>(3*(len-1)));
+}
+
 // convert from int to octal (or base-256)
 static void itoo(char *str, int len, unsigned long long val)
 {
-  // Do we need binary encoding?
-  if (!(val>>(3*(len-1)))) sprintf(str, "%0*llo", len-1, val);
+  if (ascii_fits(val, len)) sprintf(str, "%0*llo", len-1, val);
   else {
+    for (str += len; len--; val >>= 8) *--str = val;
     *str = 128;
-    while (--len) *++str = val>>(3*len);
   }
 }
 #define ITOO(x, y) itoo(x, sizeof(x), y)
@@ -179,7 +185,7 @@
   struct tar_hdr hdr;
   struct passwd *pw = pw;
   struct group *gr = gr;
-  int i, fd =-1;
+  int i, fd = -1, norecurse = FLAG(no_recursion);
   char *name, *lnk, *hname;
 
   if (!dirtree_notdotdot(node)) return 0;
@@ -189,11 +195,15 @@
   }
 
   i = 1;
-  name = dirtree_path(node, &i);
+  name = hname = dirtree_path(node, &i);
 
   // exclusion defaults to --no-anchored and --wildcards-match-slash
   for (lnk = name; *lnk;) {
-    if (filter(TT.excl, lnk)) goto done;
+    if (filter(TT.excl, lnk)) {
+      norecurse++;
+
+      goto done;
+    }
     while (*lnk && *lnk!='/') lnk++;
     while (*lnk=='/') lnk++;
   }
@@ -202,7 +212,7 @@
   if (S_ISDIR(st->st_mode) && name[i-1] != '/') strcat(name, "/");
 
   // remove leading / and any .. entries from saved name
-  for (hname = name; *hname == '/'; hname++);
+  if (!FLAG(P)) while (*hname == '/') hname++;
   for (lnk = hname;;) {
     if (!(lnk = strstr(lnk, ".."))) break;
     if (lnk == hname || lnk[-1] == '/') {
@@ -246,7 +256,8 @@
       i = 1;
     } else {
       // first time we've seen it. Store as normal file, but remember it.
-      if (!(TT.hlc&255)) TT.hlx = xrealloc(TT.hlx, TT.hlc+256);
+      if (!(TT.hlc&255))
+        TT.hlx = xrealloc(TT.hlx, sizeof(*TT.hlx)*(TT.hlc+256));
       TT.hlx[TT.hlc].arg = xstrdup(hname);
       TT.hlx[TT.hlc].ino = st->st_ino;
       TT.hlx[TT.hlc].dev = st->st_dev;
@@ -255,22 +266,16 @@
     }
   } else i = 0;
 
-  // !i because hardlink to a symlink is a thing.
-  if (!i && S_ISLNK(st->st_mode)) {
-    i = 2;
-    lnk = xreadlink(name);
-  }
-
   // Handle file types
-  if (i) {
-    hdr.type = '0'+i;
-    if (i==2 && !(lnk = xreadlink(name))) {
+  if (i || S_ISLNK(st->st_mode)) {
+    hdr.type = '1'+!i;
+    if (!i && !(lnk = xreadlink(name))) {
       perror_msg("readlink");
       goto done;
     }
     if (strlen(lnk) > sizeof(hdr.link)) write_longname(lnk, 'K');
     strncpy(hdr.link, lnk, sizeof(hdr.link));
-    if (i) free(lnk);
+    if (!i) free(lnk);
   } else if (S_ISREG(st->st_mode)) {
     hdr.type = '0';
     ITOO(hdr.size, st->st_size);
@@ -288,9 +293,11 @@
   if (strlen(hname) > sizeof(hdr.name)) write_longname(hname, 'L');
 
   if (!FLAG(numeric_owner)) {
-    if (TT.owner || (pw = bufgetpwuid(st->st_uid)))
+    if ((TT.owner || (pw = bufgetpwuid(st->st_uid))) &&
+        ascii_fits(st->st_uid, sizeof(hdr.uid)))
       strncpy(hdr.uname, TT.owner ? TT.owner : pw->pw_name, sizeof(hdr.uname));
-    if (TT.group || (gr = bufgetgrgid(st->st_gid)))
+    if ((TT.group || (gr = bufgetgrgid(st->st_gid))) &&
+        ascii_fits(st->st_gid, sizeof(hdr.gid)))
       strncpy(hdr.gname, TT.group ? TT.group : gr->gr_name, sizeof(hdr.gname));
   }
 
@@ -338,7 +345,7 @@
   itoo(hdr.chksum, sizeof(hdr.chksum)-1, tar_cksum(&hdr));
   hdr.chksum[7] = ' ';
 
-  if (FLAG(v)) dprintf(TT.fd ? 2 : 1, "%s\n", hname);
+  if (FLAG(v)) dprintf((TT.fd==1) ? 2 : 1, "%s\n", hname);
 
   // Write header and data to archive
   xwrite(TT.fd, &hdr, 512);
@@ -374,7 +381,7 @@
 done:
   free(name);
 
-  return (DIRTREE_RECURSE|(FLAG(h)?DIRTREE_SYMFOLLOW:0))*!FLAG(no_recursion);
+  return (DIRTREE_RECURSE|(FLAG(h)?DIRTREE_SYMFOLLOW:0))*!norecurse;
 }
 
 static void wsettime(char *s, long long sec)
@@ -485,8 +492,10 @@
       return perror_msg(":%s: can't mkdir", name);
 
   // remove old file, if exists
-  if (!FLAG(k) && !S_ISDIR(ala) && unlink(name) && errno!=ENOENT)
-    return perror_msg("can't remove: %s", name);
+  if (!FLAG(k) && !S_ISDIR(ala) && unlink(name)) {
+    if (errno==EISDIR && !rmdir(name));
+    else if (errno!=ENOENT) return perror_msg("can't remove: %s", name);
+  }
 
   if (S_ISREG(ala)) {
     // hardlink?
@@ -495,8 +504,9 @@
         return perror_msg("can't link '%s' -> '%s'", name, TT.hdr.link_target);
     // write contents
     } else {
-      int fd = xcreate(name, O_WRONLY|O_CREAT|(FLAG(overwrite)?O_TRUNC:O_EXCL),
-        WARN_ONLY|(ala & 07777));
+      int fd = xcreate(name,
+        WARN_ONLY|O_WRONLY|O_CREAT|(FLAG(overwrite)?O_TRUNC:O_EXCL),
+        ala & 07777);
       if (fd != -1) sendfile_sparse(fd);
       else skippy(TT.hdr.size);
     }
@@ -589,7 +599,7 @@
       else if (tar.type == 'L') alloread(&TT.hdr.name, TT.hdr.size);
       else if (tar.type == 'x') {
         char *p, *buf = 0;
-        int i, len, n;
+        int i, len, n = 0;
 
         // Posix extended record "LEN NAME=VALUE\n" format
         alloread(&buf, TT.hdr.size);
@@ -600,7 +610,7 @@
             break;
           }
           p[len-1] = 0;
-          if (i == 2) {
+          if (n) {
             TT.hdr.name = xstrdup(p+n);
             break;
           }
@@ -790,7 +800,7 @@
 void tar_main(void)
 {
   char *s, **args = toys.optargs,
-    *archiver = FLAG(z) ? "gzip" : (FLAG(J) ? "xz" : "bzip2");
+    *archiver = FLAG(I) ? TT.I : (FLAG(z) ? "gzip" : (FLAG(J) ? "xz":"bzip2"));
   int len = 0;
 
   // Needed when extracting to command
@@ -842,7 +852,7 @@
     char *hdr = 0;
 
     // autodetect compression type when not specified
-    if (!(FLAG(j)||FLAG(z)||FLAG(J))) {
+    if (!(FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J))) {
       len = xread(TT.fd, hdr = toybuf+sizeof(toybuf)-512, 512);
       if (len!=512 || !is_tar_header(hdr)) {
         // detect gzip and bzip signatures
@@ -856,14 +866,14 @@
       }
     }
 
-    if (FLAG(j)||FLAG(z)||FLAG(J)) {
+    if (FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J)) {
       int pipefd[2] = {hdr ? -1 : TT.fd, -1}, i, pid;
-      struct string_list *zcat = find_in_path(getenv("PATH"),
+      struct string_list *zcat = FLAG(I) ? 0 : find_in_path(getenv("PATH"),
         FLAG(j) ? "bzcat" : FLAG(J) ? "xzcat" : "zcat");
 
       // Toybox provides more decompressors than compressors, so try them first
       xpopen_both(zcat ? (char *[]){zcat->str, 0} :
-        (char *[]){archiver, "-dc", 0}, pipefd);
+        (char *[]){archiver, "-d", 0}, pipefd);
       if (CFG_TOYBOX_FREE) llist_traverse(zcat, free);
 
       if (!hdr) {
@@ -920,7 +930,7 @@
     struct double_list *dl = TT.incl;
 
     // autodetect compression type based on -f name. (Use > to avoid.)
-    if (TT.f && !FLAG(j) && !FLAG(z)) {
+    if (TT.f && !FLAG(j) && !FLAG(z) && !FLAG(I) && !FLAG(J)) {
       char *tbz[] = {".tbz", ".tbz2", ".tar.bz", ".tar.bz2"};
       if (strend(TT.f, ".tgz") || strend(TT.f, ".tar.gz"))
         toys.optflags |= FLAG_z;
@@ -930,10 +940,10 @@
         if (strend(TT.f, tbz[len])) toys.optflags |= FLAG_j;
     }
 
-    if (FLAG(j)||FLAG(z)||FLAG(J)) {
+    if (FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J)) {
       int pipefd[2] = {-1, TT.fd};
 
-      xpopen_both((char *[]){archiver, "-f", 0}, pipefd);
+      xpopen_both((char *[]){archiver, 0}, pipefd);
       close(TT.fd);
       TT.fd = pipefd[0];
     }
diff --git a/toys/posix/tee.c b/toys/posix/tee.c
index 4352942..88f7361 100644
--- a/toys/posix/tee.c
+++ b/toys/posix/tee.c
@@ -24,6 +24,7 @@
 
 GLOBALS(
   void *outputs;
+  int out;
 )
 
 struct fd_list {
@@ -39,33 +40,27 @@
 
   temp = xmalloc(sizeof(struct fd_list));
   temp->next = TT.outputs;
-  temp->fd = fd;
+  if (1 == (temp->fd = fd)) TT.out++;
   TT.outputs = temp;
 }
 
 void tee_main(void)
 {
+  struct fd_list *fdl;
+  int len;
+
   if (FLAG(i)) xsignal(SIGINT, SIG_IGN);
 
-  // Open output files
+  // Open output files (plus stdout if not already in output list)
   loopfiles_rw(toys.optargs,
     O_RDWR|O_CREAT|WARN_ONLY|(FLAG(a)?O_APPEND:O_TRUNC),
     0666, do_tee_open);
+  if (!TT.out) do_tee_open(1, 0);
 
+  // Read data from stdin, write to each output file.
   for (;;) {
-    struct fd_list *fdl;
-    int len, out = 0;
-
-    // Read data from stdin
-    len = xread(0, toybuf, sizeof(toybuf));
-    if (len<1) break;
-
-    // Write data to each output file, plus stdout.
-    for (fdl = TT.outputs; ;fdl = fdl->next) {
-      if (!fdl && out) break;
-      if (len != writeall(fdl ? fdl->fd : 1, toybuf, len)) toys.exitval=1;
-      if (!fdl) break;
-      if (fdl->fd == 1) out++;
-    }
+    if (1>(len = xread(0, toybuf, sizeof(toybuf)))) break;
+    for (fdl = TT.outputs; fdl;fdl = fdl->next)
+      if (len != writeall(fdl->fd, toybuf, len)) toys.exitval = 1;
   }
 }
diff --git a/toys/posix/test.c b/toys/posix/test.c
index 87a866a..3fadf3a 100644
--- a/toys/posix/test.c
+++ b/toys/posix/test.c
@@ -3,9 +3,11 @@
  * Copyright 2018 Rob Landley <rob@landley.net>
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
+ *
+ * TODO sh [[ ]] options: <   aaa<bbb  >   bbb>aaa   ~= regex
 
 USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
 
 config TEST
   bool "test"
@@ -20,7 +22,7 @@
       -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
+      -e  exists         -L  symlink        -s  nonzero size   -k  sticky bit
     STRING is:
       -n  nonzero size   -z  zero size      (STRING by itself implies -n)
     FD (integer file descriptor) is:
@@ -29,6 +31,7 @@
     --- 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
@@ -36,12 +39,17 @@
     --- 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)
+
+config TEST_GLUE
+  bool
+  default y
+  depends on TEST || SH
 */
 
 #include "toys.h"
 
 // Consume 3, 2, or 1 argument test, returning result and *count used.
-int do_test(char **args, int *count)
+static int do_test(char **args, int *count)
 {
   char c, *s;
   int i;
@@ -66,16 +74,16 @@
   if (*count>=2 && *s == '-' && s[1] && !s[2]) {
     *count = 2;
     c = s[1];
-    if (-1 != (i = stridx("hLbcdefgpSusxwr", c))) {
+    if (-1 != (i = stridx("hLbcdefgkpSusxwr", 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 (i>=13) return !!(st.st_mode&(0111<<(i-13)));
       if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
 
       // handle file type checking and SUID/SGID
-      if ((i = (unsigned short []){80,80,48,16,32,0,64,2,8,96,4}[i]<<9)>=4096)
+      if ((i = ((char []){80,80,48,16,32,0,64,2,1,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];
@@ -94,7 +102,7 @@
   int pos, paren, pstack, result = 0;
 
   toys.exitval = 2;
-  if (!strcmp("[", toys.which->name))
+  if (CFG_TOYBOX && !strcmp("[", toys.which->name))
     if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
       error_exit("Missing ']'");
 
@@ -102,7 +110,7 @@
   if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
     int len = toys.optc-pos;
 
-    if (!toys.optargs[pos]) perror_exit("need arg @%d", pos);
+    if (!len) perror_exit("need arg @%d", pos);
 
     // Evaluate next test
     result = do_test(toys.optargs+pos, &len);
@@ -132,14 +140,14 @@
       else if (pstack&AND) result = 0;
 
       // Do it again for every )
-      if (!paren || !s || strcmp(")", s)) break;
+      if (!paren || pos==toys.optc || strcmp(")", s)) break;
       paren--;
       pstack >>= 3;
       s = toys.optargs[++pos];
     }
 
     // Out of arguments?
-    if (!s) {
+    if (pos==toys.optc) {
       if (paren) perror_exit("need )");
       break;
     }
diff --git a/toys/posix/true.c b/toys/posix/true.c
index b92d9c3..8e56269 100644
--- a/toys/posix/true.c
+++ b/toys/posix/true.c
@@ -5,7 +5,7 @@
  * See http://opengroup.org/onlinepubs/9699919799/utilities/true.html
 
 USE_TRUE(NEWTOY(true, NULL, TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
-USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
+USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
 
 config TRUE
   bool "true"
diff --git a/toys/posix/ulimit.c b/toys/posix/ulimit.c
index da7f2ea..3797098 100644
--- a/toys/posix/ulimit.c
+++ b/toys/posix/ulimit.c
@@ -30,16 +30,17 @@
     (or read-only -ap selected) display current value (sizes in bytes).
     Default is ulimit -P $PPID -Sf" (show soft filesize of your shell).
     
-    -S  Set/show soft limit          -H  Set/show hard (maximum) limit
-    -a  Show all limits              -c  Core file size
-    -d  Process data segment         -e  Max scheduling priority
-    -f  Output file size             -i  Pending signal count
-    -l  Locked memory                -m  Resident Set Size
-    -n  Number of open files         -p  Pipe buffer
-    -q  Posix message queue          -r  Max Real-time priority
-    -R  Realtime latency (usec)      -s  Stack size
-    -t  Total CPU time (in seconds)  -u  Maximum processes (under this UID)
-    -v  Virtual memory size          -P  PID to affect (default $PPID)
+    -P  PID to affect (default $PPID)  -a  Show all limits
+    -S  Set/show soft limit            -H  Set/show hard (maximum) limit
+
+    -c  Core file size (blocks)        -d  Process data segment (KiB)
+    -e  Max scheduling priority        -f  File size (KiB)
+    -i  Pending signal count           -l  Locked memory (KiB)
+    -m  Resident Set Size (KiB)        -n  Number of open files
+    -p  Pipe buffer (512 bytes)        -q  POSIX message queues
+    -r  Max realtime priority          -R  Realtime latency (us)
+    -s  Stack size (KiB)               -t  Total CPU time (s)
+    -u  Maximum processes (this UID)   -v  Virtual memory size (KiB)
 */
 
 #define FOR_ulimit
@@ -55,17 +56,25 @@
   struct rlimit *old_limit);
 
 // I'd like to sort the RLIMIT values 0-15, but mips, alpha and sparc
-// override the asm-generic values for 5-9. Also, the kernel implementation
-// of RLIMIT_LOCKS (-x) was removed from Linux in 2003.
+// override the asm-generic values for 5-9, and alphabetical order is nice for
+// humans anyway.
 void ulimit_main(void)
 {
   struct rlimit rr;
   int i;
-  // Order is cdefilmnqRrstuv
-  char map[] = {RLIMIT_CORE, RLIMIT_DATA, RLIMIT_NICE, RLIMIT_FSIZE,
-                RLIMIT_SIGPENDING, RLIMIT_MEMLOCK, RLIMIT_RSS, RLIMIT_NOFILE, 0,
-                RLIMIT_MSGQUEUE, RLIMIT_RTTIME, RLIMIT_RTPRIO, RLIMIT_STACK,
-                RLIMIT_CPU, RLIMIT_NPROC, RLIMIT_AS};
+  // map and desc are in the same order as flags.
+  // The Linux kernel implementation of RLIMIT_LOCKS (-x) was removed in 2003.
+  char *flags="cdefilmnpqRrstuv";
+  char map[] = {RLIMIT_CORE, RLIMIT_DATA, RLIMIT_NICE,
+    RLIMIT_FSIZE, RLIMIT_SIGPENDING, RLIMIT_MEMLOCK,
+    RLIMIT_RSS, RLIMIT_NOFILE, 0, RLIMIT_MSGQUEUE,
+    RLIMIT_RTTIME, RLIMIT_RTPRIO, RLIMIT_STACK, RLIMIT_CPU, RLIMIT_NPROC,
+    RLIMIT_AS};
+  char *desc[]={"core dump size (blocks)", "data size (KiB)", "max nice",
+    "file size (blocks)", "pending signals", "locked memory (KiB)",
+    "RSS (KiB)", "open files", "pipe size (512 bytes)", "message queues",
+    "RT time (us)", "RT priority", "stack (KiB)", "cpu time (s)", "processes",
+    "address space (KiB)"};
 
   if (!(toys.optflags&(FLAG_H-1))) toys.optflags |= FLAG_f;
   if ((FLAG(a)||FLAG(p)) && toys.optc) error_exit("can't set -ap");
@@ -74,13 +83,11 @@
   if (!FLAG(P)) TT.P = getppid();
 
   for (i=0; i<sizeof(map); i++) {
-    char *flags="cdefilmnpqRrstuv";
-
     int get = toys.optflags&(FLAG_a|(1<<i));
 
     if (get && prlimit(TT.P, map[i], 0, &rr)) perror_exit("-%c", flags[i]);
     if (!toys.optc) {
-      if (FLAG(a)) printf("-%c: ", flags[i]);
+      if (FLAG(a)) printf("-%c: %-25s ", flags[i], desc[i]);
       if (get) {
         if ((1<<i)&FLAG_p) {
           if (FLAG(H))
diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c
index 79e7dea..cc3ee3d 100644
--- a/toys/posix/xargs.c
+++ b/toys/posix/xargs.c
@@ -8,15 +8,14 @@
  * TODO: -I	Insert mode
  * TODO: -L	Max number of lines of input per command
  * TODO: -x	Exit if can't fit everything in one command
- * TODO: -P NUM	Run up to NUM processes at once
 
-USE_XARGS(NEWTOY(xargs, "^E:P#optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config XARGS
   bool "xargs"
   default y
   help
-    usage: xargs [-0prt] [-s NUM] [-n NUM] [-E STR] COMMAND...
+    usage: xargs [-0prt] [-snE STR] COMMAND...
 
     Run command line one or more times, appending arguments from stdin.
 
@@ -27,7 +26,8 @@
     -n	Max number of arguments per command
     -o	Open tty for COMMAND's stdin (default /dev/null)
     -p	Prompt for y/n from tty before running each command
-    -r	Don't run command with empty input (otherwise always run command once)
+    -P	Parallel processes (default 1)
+    -r	Don't run with empty input (otherwise always run command once)
     -s	Size in bytes per command line
     -t	Trace, print command line to stderr
 */
@@ -39,7 +39,7 @@
   long s, n, P;
   char *E;
 
-  long entries, bytes;
+  long entries, bytes, np;
   char delim;
   FILE *tty;
 )
@@ -91,13 +91,23 @@
   return 0;
 }
 
+// Handle SIGUSR1 and SIGUSR2 for -P
+static void signal_P(int sig)
+{
+  if (sig == SIGUSR2 && TT.P>1) TT.P--;
+  else TT.P++;
+}
+
 void xargs_main(void)
 {
   struct double_list *dlist = 0, *dtemp;
   int entries, bytes, done = 0, status;
-  char *data = 0, **out;
+  char *data = 0, **out = 0;
   pid_t pid = 0;
 
+  xsignal_flags(SIGUSR1, signal_P, SA_RESTART);
+  xsignal_flags(SIGUSR2, signal_P, SA_RESTART);
+
   // POSIX requires that we never hit the ARG_MAX limit, even if we try to
   // with -s. POSIX also says we have to reserve 2048 bytes "to guarantee
   // that the invoked utility has room to modify its environment variables
@@ -138,7 +148,6 @@
         }
       }
       dlist_add(&dlist, data);
-
       // Count data used
       if (!(data = handle_entries(data, 0))) continue;
       if (data == (char *)2) done++;
@@ -150,8 +159,7 @@
 
     if (!TT.entries) {
       if (data) error_exit("argument too long");
-      else if (pid) return;
-      else if (FLAG(r)) continue;
+      if (pid || FLAG(r)) goto reap_children;
     }
 
     // Fill out command line to exec
@@ -170,7 +178,7 @@
       if (FLAG(p)) {
         fprintf(stderr, "?");
         if (!TT.tty) TT.tty = xfopen("/dev/tty", "re");
-        if (!fyesno(TT.tty, 0)) goto skip;
+        if (!fyesno(TT.tty, 0)) goto reap_children;
       } else fprintf(stderr, "\n");
     }
 
@@ -179,28 +187,30 @@
       xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY);
       xexec(out);
     }
-    waitpid(pid, &status, 0);
+    TT.np++;
 
-    // xargs is yet another weird collection of exit value special cases,
-    // different from all the others.
-    if (WIFEXITED(status)) {
-      if (WEXITSTATUS(status) == 126 || WEXITSTATUS(status) == 127) {
-        toys.exitval = WEXITSTATUS(status);
-        return;
-      } else if (WEXITSTATUS(status) >= 1 && WEXITSTATUS(status) <= 125) {
-        toys.exitval = 123;
-      } else if (WEXITSTATUS(status) == 255) {
-        error_msg("%s: exited with status 255; aborting", out[0]);
+reap_children:
+    while (TT.np) {
+      int xv = (TT.np == TT.P) || (!data && done);
+
+      if (1>(xv = waitpid(-1, &status, WNOHANG*!xv))) break;
+      TT.np--;
+      xv = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128;
+      if (xv == 255) {
+        error_msg("%s: exited with status 255; aborting", *out);
         toys.exitval = 124;
-        return;
-      }
-    } else toys.exitval = 127;
+        break;
+      } else if ((xv|1)==127) toys.exitval = xv;
+      else if (xv>127) xv = 125;
+      else if (xv) toys.exitval = 123;
+    }
 
     // Abritrary number of execs, can't just leak memory each time...
-skip:
     llist_traverse(dlist, llist_free_double);
     dlist = 0;
     free(out);
+    out = 0;
   }
+  while (TT.np && -1 != wait(&status)) TT.np--;
   if (TT.tty) fclose(TT.tty);
 }
diff --git a/www/about.html b/www/about.html
index 088faf2..0c8637e 100755
--- a/www/about.html
+++ b/www/about.html
@@ -59,18 +59,21 @@
   </ul>
 </ul>
 
-<p>The <a href=http://landley.net/talks/celf-2015.txt>2015 toybox talk</a>
-starts with links to three previous talks on the history and motivation of
-the project: "Why Toybox", "Why Public Domain", and "Why did I do
-Aboriginal Linux (which led me here)?". If you're really bored,
-there's even a half-finished
-<a href=http://landley.net/aboriginal/history.html>a history page</a>.</p>
+<p>A more recent talk from 2019 compares
+<a href=https://www.youtube.com/watch?v=MkJkyMuBm3g#t=1m18s>BusyBox vs toybox</a>
+and explains the design decisions behind both.
+(A 2015 toybox talk was part of the channel
+<a href=https://marc.info/?l=linux-embedded&m=158159902514847&w=2>accidentally deleted</a> off youtube by the Linux Foundation,
+but the <a href=https://landley.net/talks/celf-2015.txt>outline</a> is
+still available.)</p>
 
-<p>The toybox maintainer's earlier minimal self-hosting system project,
+<b><h2><a name="context" />What context was toybox created in?</h2></b>
+
+<p>The toybox maintainer's previous minimal self-hosting system project,
 <a href=http://landley.net/aboriginal/about.html>Aboriginal Linux</a>,
-got its minimal native development environment down to seven packages in
+got a native development environment down to only seven packages in
 its 1.0 release (busybox, uClibc, gcc, binutils, make, bash, and linux)
-and built Linux From Scratch under the result. That project
+and then built Linux From Scratch under the result. That project
 <a href=http://landley.net/aboriginal/history.html>was the reason</a>
 toybox's maintainer became busybox maintainer, having done so
 much work to extend busybox to replace all the gnu tools in a Linux From
@@ -84,13 +87,21 @@
 shipped with Android due to the license. As long as we're starting over anyway,
 we can do a better job.</p>
 
-<p>These days, toybox is replacing busybox
-in Aboriginal Linux one command at a time, and each toybox release is
-regression tested by building Aboriginal Linux with it, then building
-Linux From Scratch under the result with the new toybox commands.
-The list of commands remaining is tracked <a href=roadmap.html#dev_env>in
-the roadmap</a>, and the replacing busybox in Aboriginal Linux is
-one of the main goals for toybox' 1.0 release.</p>
+<p>Toybox's current minimal native development environment builder is a new
+<a href=https://github.com/landley/toybox/blob/master/scripts/mkroot.sh>tiny
+implementation</a> integrated into the toybox source.
+The "make root" target will create a simple toybox chroot
+(by default in the root/host directory), and adding a LINUX= argument to
+the make command line pointing to Linux kernel source code creates a tiny
+bootable system with a wrapper script to run it under the emulator
+<a href=https://qemu.org>qemu</a>.</p>
+
+<p>The list of commands remaining before we can build Linux From Scratch under
+the result (with an appropriate
+<a href=https://github.com/landley/toybox/blob/master/scripts/mcm-buildall.sh>compiler</a>)
+is tracked <a href=roadmap.html#dev_env>in
+the roadmap</a>, and doing so is one of the main goals for toybox's 1.0
+release.</p>
 
 <p>Building LFS requres fewer commands than building AOSP, which has a lot more
 <a href=http://source.android.com/source/initializing.html>build
diff --git a/www/code.html b/www/code.html
index 953c53b..25a1819 100644
--- a/www/code.html
+++ b/www/code.html
@@ -20,8 +20,9 @@
 
 <p><h1><a name="building" /><a href="#building">Building Toybox</a></h1></p>
 
-<p>Toybox is configured using the Kconfig language pioneered by the Linux
-kernel, and adopted by many other projects (uClibc, OpenEmbedded, etc).
+<p>Toybox is configured using the
+<a href=https://github.com/torvalds/linux/blob/v2.6.16/Documentation/kbuild/kconfig-language.txt>Kconfig language</a> pioneered by the Linux
+kernel, and adopted by many other projects (buildroot, OpenEmbedded, etc).
 This generates a ".config" file containing the selected options, which
 controls which features are included when compiling toybox.</p>
 
@@ -189,8 +190,8 @@
 year.</p></li>
 
 <li><p>Give a URL to the relevant standards document, where applicable.
-(Sample links to SUSv4 and LSB are provided, feel free to link to other
-documentation or standards as appropriate.)</p></li>
+(Sample links to SUSv4, LSB, IETF RFC, and man7.org are provided, feel free to
+link to other documentation or standards as appropriate.)</p></li>
 
 <li><p>Update the USE_YOURCOMMAND(NEWTOY(yourcommand,"blah",0)) line.
 The NEWTOY macro fills out this command's <a href="#toy_list">toy_list</a>
@@ -263,11 +264,13 @@
 
 <a name="headers" /><h2><a href="#headers">Headers.</a></h2>
 
-<p>Commands generally don't have their own headers. If it's common code
-it can live in lib/, if it isn't put it in the command's .c file. (The line
-between implementing multiple commands in a C file via OLDTOY() to share
-infrastructure and moving that shared infrastructure to lib/ is a judgement
-call. Try to figure out which is simplest.)</p>
+<p>Commands are implemented as self-contained .c files, and generally don't
+have their own .h files. If it's common code put it in lib/, and if it's
+something like a local structure definition just put it in the command's .c
+file. If it would only ever be #included from one place, inline it.
+(The line between implementing multiple commands in a C file via OLDTOY()
+to share infrastructure and moving that shared infrastructure to lib/ is a
+judgement call. Try to figure out which is simplest.)</p>
 
 <p>The top level toys.h should #include all the standard (posix) headers
 that any command uses. (Partly this is friendly to ccache and partly this
@@ -281,7 +284,7 @@
 lib/portability.c. (There's even some minimal compile-time environment probing
 that writes data to generated/portability.h, see scripts/genconfig.sh.)</p>
 
-<p>Only include linux/*.h headers from individual commands (not from other
+<p>Only include &lt;linux/*.h&gt; headers from individual commands (not from other
 headers), and only if you really need to. Data that varies per architecture
 is a good reason to include a header. If you just need a couple constants
 that haven't changed since the 1990's, it's ok to #define them yourself or
@@ -1370,7 +1373,8 @@
 
 <h2>Directory kconfig/</h2>
 
-<p>Menuconfig infrastructure copied from the Linux kernel.  See the
+<p>Menuconfig infrastructure copied from the Linux kernel a long time ago
+(version 2.6.16).  See the
 Linux kernel's Documentation/kbuild/kconfig-language.txt</p>
 
 <!-- todo
diff --git a/www/design.html b/www/design.html
index b2595fd..e651913 100644
--- a/www/design.html
+++ b/www/design.html
@@ -351,13 +351,25 @@
 the same size on 64 bit systems, but pointer and long are.
 This is guaranteed by the LP64 memory model, a Unix standard (which Linux
 and MacOS X both implement, and which modern 64 bit processors such as
-x86-64 were <a href=http://www.pagetable.com/?p=6>designed for</a>).  See
-<a href=http://www.unix.org/whitepapers/64bit.html>the LP64 standard</a> and
-<a href=http://www.unix.org/version2/whatsnew/lp64_wp.html>the LP64
-rationale</a> for details.</p>
+x86-64 were <a href=http://www.pagetable.com/?p=6>designed for</a>).</p>
+
+<p>Back
+before unix.org went down, they hosted the
+<a href=https://web.archive.org/web/20020905181545/http://www.unix.org/whitepapers/64bit.html>LP64 standard</a> and
+<a href=https://web.archive.org/web/20020921185209/http://www.unix.org/version2/whatsnew/lp64_wp.html>the LP64 rationale</a>, but the important part is
+LP64 gives all the basic C integer types defined sizes:</p>
+
+<table border=1 cellpadding=10 cellspacing=2>
+<tr><td>C type</td><td>32 bit<br />sizeof</td><td>64 bit<br />sizeof</td></tr>
+<tr><td>char</td><td>1 byte</td><td>1 byte</td></tr>
+<tr><td>short</td><td>2 bytes</td><td>2 bytes</td></tr>
+<tr><td>int</td><td>4 bytes</td><td>4 bytes</td></tr>
+<tr><td>long</td><td>4 bytes</td><td>8 bytes</td></tr>
+<tr><td>long long</td><td>8 bytes</td><td>8 bytes</td></tr>
+</table>
 
 <p>Note that Windows doesn't work like this, and I don't care.
-<a href=http://blogs.msdn.com/oldnewthing/archive/2005/01/31/363790.aspx>The
+<a href=https://devblogs.microsoft.com/oldnewthing/20050131-00/?p=36563>The
 insane legacy reasons why this is broken on Windows are explained here.</a></p>
 
 <b><h3>Signedness of char</h3></b>
diff --git a/www/faq.html b/www/faq.html
index 37ab915..803d7b5 100755
--- a/www/faq.html
+++ b/www/faq.html
@@ -1,79 +1,96 @@
-<html><head><title>toybox news</title>
+<html><head><title>toybox FAQ</title>
 <!--#include file="header.html" -->
 
+<h1>Frequently Asked Questions</h1>
+
+<h2>General Questions</h2>
+
 <ul>
-<li><h2><a href="#capitalize">Do you capitalize toybox?</a></h2></li>
 <li><h2><a href="#why_toybox">Why toybox? (What was wrong with busybox?)</a></h2></li>
+<li><h2><a href="#capitalize">Do you capitalize toybox?</a></h2></li>
 <li><h2><a href="#support_horizon">Why a 7 year support horizon?</a></h2></li>
 <li><h2><a href="#releases">Why time based releases?</a></h2></li>
 <li><h2><a href="#code">Where do I start understanding the toybox source code?</a></h2></li>
+<li><h2><a href="#when">When were historical toybox versions released?</a></h2></li>
+<li><h2><a href="#bugs">Where do I report bugs?</a></h2></li>
+<li><h2><a href="#b_links">What are those /b/number links in the git log?</a></h2></li>
+<li><h2><a href="#opensource">What is the relationship between toybox and android?</a></h2></li>
+<li><h2><a href="#backporting">Will you backport fixes to old versions?</a></h2></li>
+<li><h2><a href="#dotslash">What's this ./ on the front of commands in your examples?</a></h2></li>
+
 </ul>
 
-<a name="capitalize" />
-<h2>Q: Do you capitalize toybox?</h2>
+<h2>Using toybox</h2>
 
-<p>A: Only at the start of a sentence. The command name is all lower case so
-it seems silly to capitalize the project name, but not capitalizing the
-start of sentences is awkward, so... compromise. (It is _not_ "ToyBox".)</p>
+<ul>
+<!-- get binaries -->
+<li><h2><a href="#install">How do I install toybox?</h2></li>
+<li><h2><a href="#cross">How do I cross compile toybox?</h2></li>
+<li><h2><a href="#system">What part of Linux/Android does toybox provide?</h2></li>
+<li><h2><a href="#mkroot">How do I build a working Linux system with toybox?</a></h2></li>
+</ul>
 
-<a name="why_toybox" />
-<h2>Q: "Why is there toybox? What was wrong with busybox?"</h2>
+<hr /><h2><a name="why_toybox" />Q: "Why is there toybox? What was wrong with busybox?"</h2>
 
-<p>A: Toybox started back in 2006 when I
+<p>A: Toybox started back in 2006 when I (Rob Landley)
 <a href=https://lwn.net/Articles/202106/>handed off BusyBox maintainership</a>
 and <a href=http://landley.net/notes-2006.html#28-09-2006>started over from
 scratch</a> on a new codebase after a
 <a href=http://lists.busybox.net/pipermail/busybox/2006-September/058617.html>protracted licensing argument</a> took all the fun out of working on BusyBox.</p>
 
 <p>Toybox was just a personal project until it got
-<a href=http://landley.net/notes-2011.html#13-11-2011>relaunched
-in November 2011</a> with a new goal to
-<a href=http://landley.net/aboriginal/about.html#selfhost>make Android
-self-hosting</a>. This involved me relicensing my own
-code, which <a href=https://lwn.net/Articles/478308/>made people who had
-never used or participated in the project loudly angry</a>. The switch came
+<a href=http://landley.net/notes-2011.html#13-11-2011>relaunched</a>
+in November 2011 with a new goal to make Android
+<a href=http://landley.net/aboriginal/about.html#selfhost>self-hosting</a>.
+This involved me relicensing my own
+code, which made people who had never used or participated in the project
+<a href=https://lwn.net/Articles/478308/>loudly angry</a>. The switch came
 after a lot of thinking <a href=http://landley.net/talks/ohio-2013.txt>about
 licenses</a> and <a href=http://landley.net/notes-2011.html#21-03-2011>the
 transition to smartphones</a>, which led to a
-<a href=http://landley.net/talks/celf-2013.txt>2013</a>
-<a href=https://www.youtube.com/watch?v=SGmtP5Lg_t0>talk</a> laying
-out a strategy to make Android self-hosting using toybox. This helped
+<a href=https://www.youtube.com/watch?v=SGmtP5Lg_t0>2013 talk</a> laying
+out a
+<a href=http://landley.net/talks/celf-2013.txt>strategy</a>
+to make Android self-hosting using toybox. This helped
 <a href=https://code.google.com/p/android/issues/detail?id=76861>bring
 it to Android's attention</a>, and they
 <a href=https://lwn.net/Articles/629362/>merged it</a> into Android M.</p>
 
-<p>The answer to the second question is "licensing". BusyBox predates Android
-by almost a decade but Android still doesn't ship with it because GPLv3 came
+<p>The unfixable problem with busybox was licensing: BusyBox predates Android
+by almost a decade, but Android still doesn't ship with it because GPLv3 came
 out around the same time Android did and caused many people to throw
 out the GPLv2 baby with the GPLv3 bathwater.
 Android <a href=https://source.android.com/source/licenses.html>explicitly
 discourages</a> use of GPL and LGPL licenses in its products, and has gradually
-reimplemented historical GPL components such as its bluetooth stack under the
-Apache license. Similarly, Apple froze xcode at the last GPLv2 releases
-(GCC 4.2.1 with binutils 2.17) for over 5 years while it sponsored the
+reimplemented historical GPL components (such as its bluetooth stack) under the
+Apache license. Apple's
+<a href=http://meta.ath0.com/2012/02/05/apples-great-gpl-purge/>less subtle</a> response was to freeze xcode at the last GPLv2 releases
+(GCC 4.2.1 with binutils 2.17) for over 5 years while sponsoring the
 development of new projects (clang/llvm/lld) to replace them,
-implemented its SMB server from scratch to replace samba,
-<a href=http://meta.ath0.com/2012/02/05/apples-great-gpl-purge/>and so
-on</a>. Toybox itself exists because somebody with in a legacy position
+implementing a
+<a href=https://www.osnews.com/story/24572/apple-ditches-samba-in-favour-of-homegrown-replacement/>new SMB server</a> from scratch to
+<a href=https://archive.org/details/copyleftconf2020-allison>replace samba</a>,
+switching <a href=https://www.theverge.com/2019/6/4/18651872/apple-macos-catalina-zsh-bash-shell-replacement-features>bash with zsh</a>, and so on.
+Toybox itself exists because somebody with in a legacy position
 just wouldn't shut up about GPLv3, otherwise I would probably
 still happily be maintaining BusyBox. (For more on how I wound
 up working on busybox in the first place,
 <a href=http://landley.net/aboriginal/history.html>see here</a>.)</p>
 
-<h2><a name="support_horizon">Q: Why a 7 year support horizon?</a></h2>
+<hr /><h2><a name="capitalize" />Q: Do you capitalize toybox?</h2>
+
+<p>A: Only at the start of a sentence. The command name is all lower case so
+it seems silly to capitalize the project name, but not capitalizing the
+start of sentences is awkward, so... compromise. (It is _not_ "ToyBox".)</p>
+
+<hr /><h2><a name="support_horizon">Q: Why a 7 year support horizon?</a></h2>
 
 <p>A: Our <a href=http://lists.busybox.net/pipermail/busybox/2006-September/058440.html>longstanding rule of thumb</a> is to try to run and build on
 hardware and distributions released up to 7 years ago, and feel ok dropping
 support for stuff older than that. (This is a little longer than Ubuntu's
 Long Term Support, but not by much.)</p>
 
-<p>If a kernel or libc feature is less than 7 years old, I try to have a
-build-time configure test for it and let the functionality cleanly drop out.
-I also keep old Ubuntu images around in VMs and perform the occasional
-defconfig build there to see what breaks. (I'm not perfect about this,
-but I accept bug reports.)</p>
-
-<p>My original theory was "4 to 5 18-month cycles of moore's law should cover
+<p>My original theory was "4 to 5 of the 18-month cycles of moore's law should cover
 the vast majority of the installed base of PC hardware", loosely based on some
 research I did <a href=http://www.catb.org/esr/halloween/halloween9.html#id2867629>back in 2003</a>
 and <a href=http://catb.org/esr/writings/world-domination/world-domination-201.html#id248066>updated in 2006</a>
@@ -82,59 +99,62 @@
 the useful lifetime of most systems no longer being sold but still in use and
 potentially being upgraded to new software releases.</p>
 
-<p>It turns out <a href=http://landley.net/notes-2011.html#26-06-2011>I missed
-industry changes</a> in the 1990's that stretched the gap
-from low end to high end from 2 cycles to 4 cycles, and _that_ analysis
-ignored the switch from PC to smartphone cutting off the R&D air supply of the
-laptop market.  Meanwhile the Moore's Law s-curve started bending
-down in 2000 and these days is pretty flat because the drive for faster clock
+<p>That analysis missed <a href=http://landley.net/notes-2011.html#26-06-2011>industry
+changes</a> in the 1990's that stretched the gap
+from low end to high end from 2 cycles to 4 cycles, and ignored
+<a href=https://landley.net/notes-2010.html#09-10-2010>the switch</a> from PC to smartphone cutting off the R&D air supply of the
+laptop market. Meanwhile the Moore's Law <a href=https://en.wikipedia.org/wiki/Logistic_function>s-curve</a> started bending back down (as they
+<a href=https://en.wikipedia.org/wiki/Diffusion_of_innovations>always do</a>)
+back in 2000, and these days is pretty flat: the drive for faster clock
 speeds <a href=http://www.anandtech.com/show/613>stumbled</a>
-then <a href=http://www.pcworld.com/article/118603/article.html>died</a>, and
-the subsequent drive to go wide maxed out around 4x SMP with ~2 megabyte
-caches for most applications. These days the switch from exponential to
+and <a href=http://www.pcworld.com/article/118603/article.html>died</a>, with
+the subsequent drive to go "wide" maxing out for most applications
+around 4x SMP with maybe 2 megabyte caches. These days the switch from exponential to
 linear growth in hardware capabilities is
-<a href=https://www.cnet.com/news/end-of-moores-law-its-not-just-about-physics/>common</a>
-<a href=http://www.acm.org/articles/people-of-acm/2016/david-patterson>knowledge</a>.</p>
+<a href=https://www.cnet.com/news/end-of-moores-law-its-not-just-about-physics/>common knowledge</a> and
+<a href=http://www.acm.org/articles/people-of-acm/2016/david-patterson>widely
+accepted</a>.</p>
 
 <p>But the 7 year rule of thumb stuck around anyway: if a kernel or libc
 feature is less than 7 years old, I try to have a build-time configure test
-for it and let the functionality cleanly drop out. I also keep old Ubuntu
-images around in VMs and perform the occasional defconfig build there to
-see what breaks.</p>
+for it to let the functionality cleanly drop out. I also keep old Ubuntu
+images around in VMs to perform the occasional defconfig build there to
+see what breaks. (I'm not perfect about this, but I accept bug reports.)</p>
 
-<h2><a name="releases" />Q: Why time based releases?</h2>
+<hr /><h2><a name="releases" />Q: Why time based releases?</h2>
 <p>A: Toybox targets quarterly releases (a similar schedule to the Linux
-kernel) because Martin Michlmayr's
-<a href=http://www.youtube.com/watch?v=IKsQsxubuAA>talk</a> on the
-subject was convincing.</p>
+kernel) because Martin Michlmayr's excellent
+<a href=http://www.youtube.com/watch?v=IKsQsxubuAA>talk on the
+subject</a> was convincing. This is actually two questions, "why have
+releases" and "why schedule them".</p>
 
 <p>Releases provide synchronization points where the developers certify
 "it worked for me". Each release is a known version with predictable behavior,
 and right or wrong at least everyone should be seeing
-similar results where you might be able to google an unexpected outcome.
+similar results so might be able to google an unexpected outcome.
 Releases focus end-user testing on specific versions
 where issues can be reproduced, diagnosed, and fixed.
 Releases also force the developers to do periodic tidying, packaging,
 documentation review, finish up partially implemented features languishing
 in their private trees, and give regular checkpoints to measure progress.</p>
 
-<p>Over time feature sets change, data formats change, control knobs change...
-For example toybox's switch from "ls -q" to "ls -b" as the default output
-format wasn't exactly a bug, it was a design improvement... but the
+<p>Changes accumulate over time: different feature sets, data formats,
+control knobs... Toybox's switch from "ls -q" to "ls -b" as the default output
+format was not-a-bug-it's-a "design improvement", but the
 difference is academic if the change breaks somebody's script.
-Releases give you the option to schedule upgrades later, and not to rock
-the boat just now: just use a known working release version.</p>
+Releases give you the option to schedule upgrades as maintenance, not to rock
+the boat just now, and use a known working release version until later.</p>
 
 <p>The counter-argument is that "continuous integration"
 can be made robust with sufficient automated testing. But like the
-<a href=http://www.shirky.com/weblog/2013/11/healthcare-gov-and-the-gulf-between-planning-and-reality/>waterfall method</a>, this places insufficent
+<a href=https://web.archive.org/web/20131123071427/http://www.shirky.com/weblog/2013/11/healthcare-gov-and-the-gulf-between-planning-and-reality/>waterfall method</a>, this places insufficent
 emphasis on end-user feedback and learning from real world experience.
 Developer testing is either testing that the code does what the developers
-expect given expected inputs running in an expected environment, or it's
+expect given known inputs running in an established environment, or it's
 regression testing against bugs previously found in the field. No plan
 survives contact with the enemy, and technology always breaks once it
-leaves the lab and encounters real world data and use cases, not just
-at runtime but in different build environments.</p>
+leaves the lab and encounters real world data and use cases in new
+runtime and build environments.</p>
 
 <p>The best way to give new users a reasonable first experience is to point
 them at specific stable versions where development quiesced and
@@ -142,10 +162,16 @@
 people experiencing the _same_ teething troubles can potentially
 help each other out.</p>
 
-<p>As for why releases on a schedule are better than releases "when it's
-ready", watch the video.</p>
+<p>Releases on a schedule are better than releases "when it's ready" for
+the same reason a regularly scheduled bus beats one that leaves when it's
+"full enough": the schedule lets its users make plans. Even if the bus leaves
+empty you know when the next one arrives so missing this one isn't a disaster.
+and starting the engine to leave doesn't provoke a last-minute rush of nearby
+not-quite-ready passengers racing to catch it causing further delay and
+repeated start/stop cycles as it ALMOST leaves.
+(The video in the first paragraph goes into much greater detail.)</p>
 
-<h2><a name="code" />Q: Where do I start understanding the source code?</h2>
+<hr /><h2><a name="code" />Q: Where do I start understanding the source code?</h2>
 
 <p>A: Toybox is written in C. There are longer writeups of the
 <a href=design.html>design ideas</a> and a <a href=code.html>code walkthrough</a>,
@@ -157,43 +183,51 @@
 make; make install</b>". Type "<b>make help</b>" to
 see available make targets.</p>
 
-<p><b>The configure stage is copied from the Linux kernel</b> (in the "kconfig"
+<p><u>The configure stage</u> is copied from the Linux kernel (in the "kconfig"
 directory), and saves your selections in the file ".config" at the top
-level. The "defconfig" target selects the
+level. The "<b>make defconfig</b>" target selects the
 maximum sane configuration (enabling all the commands and features that
-aren't unfinished, only intended as examples, debug code, etc) and is
-probably what you want. You can use "make menuconfig" to manually select
+aren't unfinished, or only intended as examples, or debug code...) and is
+probably what you want. You can use "<b>make menuconfig</b>" to manually select
 specific commands to include, through an interactive menu (cursor up and
 down, enter to descend into a sub-menu, space to select an entry, ? to see
 an entry's help text, esc to exit). The menuconfig help text is the
-same as the command's --help output.</p>
+same as the command's "<b>--help</b>" output.</p>
 
-<p><b>The "make" stage creates a toybox binary</b> (which is stripped, look in
-generated/unstripped for the debug versions), and "install" adds a bunch of
+<p><u>The "make" stage</u> creates a toybox binary (which is stripped, look in
+generated/unstripped for the debug versions), and "<b>make install</b>" adds a bunch of
 symlinks to toybox under the various command names. Toybox determines which
 command to run based on the filename, or you can use the "toybox" name in which case the first
-argument is the command to run (ala "toybox ls -l"). <b>You can also build
-individual commands as standalone executables</b>, ala "make sed cat ls".</p>
+argument is the command to run (ala "toybox ls -l").</p>
 
-<p><b>The main() function is in main.c at the top level</b>,
+<p><u>You can also build
+individual commands as standalone executables</u>, ala "make sed cat ls".
+The "make change" target builds all of them, as in "change for a $20".</p>
+
+<p><u>The main() function is in main.c</u> at the top level,
 along with setup plumbing and selecting which command to run this time.
-The function toybox_main() implements the "toybox" multiplexer command.</p>
+The function toybox_main() in the same file implements the "toybox"
+multiplexer command that lists and selects the other commands.</p>
 
-<p><b>The individual command implementations are under "toys"</b>, and are grouped
+<p><u>The individual command implementations are under "toys"</u>, and are grouped
 into categories (mostly based on which standard they come from, posix, lsb,
 android...) The "pending" directory contains unfinished commands, and the
-"examples" directory contains examples. Commands in those two directories
-are _not_ selected by defconfig. (These days pending directory is mostly
-third party submissions that have not yet undergone proper code review.)</p>
+"examples" directory contains example code that aren't really useful commands.
+Commands in those two directories
+are _not_ selected by defconfig. (Most of the files in the pending directory
+are third party submissions that have not yet undergone
+<a href=cleanup.html>proper code review</a>.)</p>
 
-<p><b>Common infrastructure shared between commands is under "lib"</b>. Most
+<p><u>Common infrastructure shared between commands is under "lib"</u>. Most
 commands call lib/args.c to parse their command line arguments before calling
 the command's own main() function, which uses the option string in
 the command's NEWTOY() macro. This is similar to the libc function getopt(),
-but more powerful, and is documented at the top of lib/args.c.</p>
+but more powerful, and is documented at the top of lib/args.c. A NULL option
+string prevents this code from being called for that command.</p>
 
-<p>Most of the actual <b>build/install infrastructure is shell scripts under
-"scripts"</b>. <b>These populate the "generated" directory</b> with headers
+<p><u>The build/install infrastructure is shell scripts under
+"scripts"</u> (starting with scripts/make.sh and scripts/install.sh).
+<u>These populate the "generated" directory</u> with headers
 created from other files, which are <a href=code.html#generated>described</a>
 in the code walkthrough. All the
 build's temporary files live under generated, including the .o files built
@@ -201,20 +235,558 @@
 directory. ("make distclean" also deletes your .config and deletes the
 kconfig binaries that process .config.)</p>
 
-<p>Each command's file contains all the information for that command, so
-<b>adding a command to toybox means adding a single file under "toys"</b>.
+<p><u>Each command's .c file contains all the information for that command</u>, so
+adding a command to toybox means adding a single file under "toys".
 Usually you <a href=code.html#adding>start a new command</a> by copying an
 existing command file to a new filename
 (toys/examples/hello.c, toys/examples/skeleton.c, toys/posix/cat.c,
 and toys/posix/true.c have all been used for this purpose) and then replacing
 all instances of its old name with the new name (which should match the
 new filename), and modifying the help text, argument string, and what the
-code does. You might have to "make distclean" before you new command
+code does. You might have to "make distclean" before your new command
 shows up in defconfig or menuconfig.</p>
 
-<p><b>The toybox test suite lives in the "tests" directory</b>. From the top
-level you can "make tests" to test everything, or "make test_sed" test a
-single command's standalone version (which should behave identically)
-but that's why we test.</p>
+<p><u>The toybox test suite lives in the "tests" directory</u>, and is
+driven by scripts/test.sh and scripts/runtest.sh. From the top
+level you can "make tests" to test everything, or "make test_sed" to test a
+single command's standalone version (which should behave identically,
+but that's why we test). You can set TEST_HOST=1 to test the host version
+instead of the toybox version (in theory they should work the same),
+and VERBOSE=1 to see diffs of the expected and actual output when a
+test fails. Set VERBOSE=fail to stop at the first such failure.</p>
+
+<hr /><h2><a name="when" />Q: When were historical toybox versions released?</h2>
+
+<p>A: For vanilla releases, check the
+<a href=https://github.com/landley/toybox/tags>date on the commit tag</a>
+or <a href=https://landley.net/toybox/downloads/binaries/>the
+example binaries</a> against the output of "toybox --version".
+Between releases the --version
+information is in "git describe --tags" format with "tag-count-hash" showing the
+most recent commit tag, the number of commits since that tag, and
+the hash of the current commit.</p>
+
+<p>Android makes its own releases on its own
+<a href=https://en.wikipedia.org/wiki/Android_version_history>schedule</a>
+using its own version tags, but lists corresponding upstream toybox release
+versions <a href=https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/README.md>here</a>. For more detail you can look up
+<a href=https://android.googlesource.com/platform/external/toybox/+refs>AOSP's
+git tags</a>. (The <a href=https://source.android.com/setup/start>Android Open Source Project</a> is the "upstream" android vendors
+start form when making their own releases. Google's phones run AOSP versions
+verbatim, other vendors tend to take those releases as starting points to
+modify.)</p>
+
+<p>If you want to find the vanilla toybox commit corresponding to an AOSP
+toybox version, find the most recent commit in the android log that isn't from a
+@google or @android address and search for it in the vanilla commit log.
+(The timestamp should match but the hash will differ,
+because each git hash includes the previous
+git hash in the data used to generate it so all later commits have a different
+hash if any of the tree's history differs; yes Linus Torvalds published 3 years
+before Satoshi Nakamoto.) Once you've identified the vanilla commit's hash,
+"git describe --tags $HASH" in the vanilla tree should give you the --version
+info for that one.</p>
+
+<hr /><h2><a name="bugs" />Q: Where do I report bugs?</h2>
+
+<p>A: Ideally on the <a href=http://lists.landley.net/listinfo.cgi/toybox-landley.net>mailing list</a>, although <a href=mailto:rob@landley.net>emailing the
+maintainer</a> is a popular if slightly less reliable alternative.
+Issues submitted to <a href=https://github.com/landley/toybox>github</a>
+are generally dealt with less promptly, but mostly get done eventually.
+AOSP has its <a href=https://source.android.com/setup/contribute/report-bugs>own bug reporting mechanism</a> (although for toybox they usually forward them
+to the mailing list) and Android vendors usually forward them to AOSP which
+forwards them to the list.</p>
+
+<p>Note that if we can't reproduce a bug, we probably can't fix it.
+Not only does this mean providing enough information for us to see the
+behavior ourselves, but ideally doing so in a reasonably current version.
+The older it is the greater the chance somebody else found and fixed it
+already, so the more out of date the version you're reporting a bug against
+the less effort we're going to put into reproducing the problem.</p>
+
+<hr /><h2><a name="b_links" />Q: What are those /b/number bug report
+links in the git log?</h2>
+
+<p>A: It's a Google thing. Replace /b/$NUMBER with
+https://issuetracker.google.com/$NUMBER to read it outside the googleplex.</p>
+
+<hr /><a name="opensource" /><h2>Q: What is the relationship between toybox and android?</h2>
+
+<p>A: The <a href=about.html>about page</a> tries to explain that,
+and Linux Weekly News has covered toybox's history a
+<a href=https://lwn.net/Articles/202106/>little</a>
+<a href=https://lwn.net/Articles/478308/>over</a>
+<a href=https://lwn.net/Articles/616272/>the</a>
+<a href=https://lwn.net/Articles/629362/>years</a>.</p>
+
+<p>Toybox is a traditional open source project created and maintained
+by hobbyist (volunteer) developers, originally for Linux but these days
+also running on Android, BSD, and MacOS. The project started in 2006
+and its original author (Rob Landley)
+continues to maintain the open source project.</p>
+
+<p>Android's base OS maintainer (Elliott Hughes, I.E. enh)
+<a href=https://github.com/landley/toybox/commit/69a9f257234a>ported</a>
+<a href=https://github.com/landley/toybox/commit/6a29bb1ebe62>toybox</a>
+to Android in 2014, merged it into Android M (Marshmallow), and remains
+Android's toybox maintainer. (He explained it in his own words in
+<a href=http://androidbackstage.blogspot.com/2016/07/episode-53-adb-on-adb.html>this podcast</a>, starting either 18 or 20 minutes in depending how
+much backstory you want.)</p>
+
+<p>Android's policy for toybox development is to push patches to the
+open source project (submitting them via the mailing list) then
+"git pull" the public tree into Android's tree. To avoid merge conflicts, Android's
+tree doesn't change any of the existing toybox files but instead adds <a href=https://android.googlesource.com/platform/external/toybox/+/refs/heads/master/Android.bp>parallel
+build infrastructure</a> off to one side. (Toybox uses a make wrapper around bash
+scripts, AOSP builds with soong/ninja instead and checks in a snapshot of the
+generated/ directory to avoid running kconfig each build).
+Android's changes to toybox going into the open source tree first
+and being pulled from there into Android keeps the two trees in
+sync, and makes sure each change undergoes full open source design review
+and discussion.</p>
+
+<p>Rob acknowledges Android is by far the largest userbase for the project,
+but develops on a standard 64-bit Linux+glibc distro while building embedded
+32-bit big-endian nommu musl systems requiring proper data alignment for work,
+and is not a Google employee so does not have access
+to the Google build cluster of powerful machines capable of running the full
+AOSP build in a reasonable amount of time. Rob is working to get android
+building under android (the list of
+toybox tools Android's build uses is
+<a href=https://android.googlesource.com/platform/prebuilts/build-tools/+/refs/heads/master/path/linux-x86/>here</a>,
+and what else it needs from its build environment is
+<a href=https://android.googlesource.com/platform/build/soong/+/refs/heads/master/ui/build/paths/config.go>here</a>), and he hopes someday to not only make a usable development
+environment out of it but also nudge the base OS towards a more granular
+package management system allowing you to upgrade things like toybox without
+a complete reinstall and reboot, plus the introduction of a "posix container"
+within which you can not only run builds, but selinux lets you run binaries
+you've just built). In the meantime, Rob tests static bionic
+builds via the Android NDK when he remembers, but has limited time to work
+on toybox because it's not his day job. (The products his company makes ship
+toybox and they do sponsor the project's development, but it's one of many
+responsibilities at work.)</p>
+
+<p>Elliott is the Android base OS maintainer, in which role he manages
+a team of engineers. He also has limited time for toybox, both because it's one
+of many packages he's responsible for (he maintains bionic, used to maintain
+dalvik...) and because he allowed himself to be promoted into management
+and thus spends less time coding than he does sitting in meetings where testers
+talk to security people about vendor issues.</p>
+
+<p>Android has many other coders and security people who submit the occasional
+toybox patch, but of the last 1000 commits at the <a href=https://github.com/landley/toybox/commit/88b34c4bd3f8>time
+of writing</a> this FAQ entry, Elliott submitted 276 and all other google.com
+or android.com addresses combined totaled 17. (Rob submitted 591, leaving
+116 from other sources, but for both Rob and Elliott there's a lot of "somebody
+else pointed out an issue, and then we wrote a patch". A lot of patches
+from both "Author:" lines thank someone else for the suggestion in the
+commit comment.)</p>
+
+<hr /><a name="backporting" /><h2>Q: Will you backport fixes to old versions?</h2>
+
+<p>A: Probably not. The easiest thing to do is get your issue fixed upstream
+in the current release, then get the newest version of the
+project built and running in the old environment.</p>
+
+<p>Backporting fixes generally isn't something open source projects run by
+volunteer developers do because the goal of the project's development community
+is to extend and improve the project. We're happy to respond to our users'
+needs, but if you're coming to the us for free tech support we're going
+to ask you to upgrade to a current version before we try to diagnose your
+problem.</p>
+
+<p>The volunteers are happy to fix any bugs you point out in the current
+versions because doing so helps everybody and makes the project better. We
+want to make the current version work for you. But diagnosing, debugging, and
+backporting fixes to old versions doesn't help anybody but you, so isn't
+something we do for free. The cost of volunteer tech support is using a
+reasonably current version of the project.</p>
+
+<p>If you're using an old version built with an old
+compiler on an old OS (kernel and libc), there's a fairly large chance
+whatever problem you're
+seeing already got fixed, and to get that fix all you have to do is upgrade
+to a newer version. Diagnosing a problem that wasn't our bug means we spent
+time that only helps you, without improving the project.
+If you don't at least _try_ a current version, you're asking us for free
+personalized tech support.</p>
+
+<p>Reproducing bugs in current versions also makes our job easier.
+The further back in time
+you are, the more work it is for us digging back in the history to figure
+out what we hadn't done yet in your version. If spot a problem in a git
+build pulled 3 days ago, it's obvious what changed and easy to fix or back out.
+If you ask about the current release version 3 months after it came out,
+we may have to think a while to remember what we did and there are a number of
+possible culprits, but it's still tractable. If you ask about 3 year old
+code, we have to reconstruct the history and the problem could be anything,
+there's a lot more ground to cover and we haven't seen it in a while.</p>
+
+<p>As a rule of thumb, volunteers will generally answer polite questions about
+a given version for about three years after its release before it's so old
+we don't remember the answer off the top of our head. And if you want us to
+put any _effort_ into tracking it down, we want you to put in a little effort
+of your own by confirming it's still a problem with the current version
+(I.E. we didn't fix it already). It's
+also hard for us to fix a problem of yours if we can't reproduce it because
+we don't have any systems running an environment that old.</p>
+
+<p>If you don't want to upgrade, you have the complete source code and thus
+the ability to fix it yourself, or can hire a consultant to do it for you. If
+you got your version from a vendor who still supports the older version, they
+can help you. But there are limits as to what volunteers will feel obliged to
+do for you.</p>
+
+<p>Commercial companies have different incentives. Your OS vendor, or
+hardware vendor for preinstalled systems, may have their own bug reporting
+mechanism and update channel providing backported fixes. And a paid consultant
+will happily set up a special environment just to reproduce your problem.</p>
+
+<hr /><h2><a name="install" />Q: How do I install toybox?</h2>
+
+<p>A:
+Multicall binaries like toybox behave differently based on the filename
+used to call them, so if you "mv toybox ls; ./ls -l" it acts like ls. Creating
+symlinks or hardlinks and adding them to the $PATH lets you run the
+commands normally by name, so that's probably what you want to do.</p>
+
+<p>If you already have a <a href=https://landley.net/toybox/downloads/binaries/>toybox binary</a>
+you can install a tree of command symlinks to
+<a href=http://git.musl-libc.org/cgit/musl/tree/include/paths.h>the
+standard path</a>
+locations (<b>export PATH=/bin:/usr/bin:/sbin:/usr/sbin</b>) by doing:</p>
+
+<blockquote><p><b>for i in $(/bin/toybox --long); do ln -s /bin/toybox $i; done</b></p></blockquote>
+
+<p>Or you can install all the symlinks in the same directory as the toybox binary
+(<b>export PATH="$PWD:$PATH"</b>) via:</p>
+
+<blockquote><p><b>for i in $(./toybox); do ln -s toybox $i; done</b></p></blockquote></p>
+
+<p>When building from source, use the "<b>make install</b>" and
+"<b>make install_flat</b>"
+targets with an appropriate <b>PREFIX=/target/path</b> either
+exported or on the make command line. When cross compiling,
+"<b>make list</b>" outputs the command names enabled by defconfig.
+For more information, see "<b>make help</b>".</p>
+
+<p>The command name "toybox" takes the second argument as the name of the
+command to run, so "./toybox ls -l" also behaves like ls. The "toybox"
+name is special in that it can have a suffix (toybox-i686 or toybox-1.2.3)
+and still be recognized, so you can have multiple versions of toybox in the
+same directory.</p>
+
+<p>When toybox doesn't recognize its
+filename as a command, it dereferences one
+level of symlink. So if your script needs "gsed" you can "ln -s sed gsed",
+then when you run "gsed" toybox knows how to be "sed".</p>
+
+<hr /><h2><a name="dotslash" />Q: What's this ./ on the front of commands in your examples?</h2>
+
+<p>A: When you don't give a path to a command's executable file,
+linux command shells search the directories listed in the $PATH envionment
+variable (in order), which usually doesn't include the current directory
+for security reasons. The
+magic name "." indicates the current directory (the same way ".." means
+the parent directory and starting with "/" means the root directory)
+so "./file" gives a path to the executable file, and thus runs a command
+out of the current directory where just typing "file" won't find it.
+For historical reasons PATH is colon-separated, and treats an
+empty entry (including leading/trailing colon) as "check the current
+directory", so if you WANT to add the current directory to PATH you
+can PATH="$PATH:" but doing so is a TERRIBLE idea.</p>
+
+<p>Toybox's shell (toysh) checks for built-in commands before looking at the
+$PATH (using the standard "bash builtin" logic just with lots more builtins),
+so "ls" doesn't have to exist in your filesystem for toybox to find it. When
+you give a path to a command the shell won't run the built-in version
+but will run the file at that location. (But the multiplexer command
+won't: "toybox /bin/ls" runs the built-in ls, you can't point it at an
+arbitrary file out of the filesystem and have it run that. You could
+"toybox nice /bin/ls" though.)</p>
+
+<hr /><h2><a name="standalone" />Q: How do I make individual/standalone toybox command binaries?</h2>
+
+<p>After running the configure step (generally "make defconfig")
+you can "make list" to see available command names you can use as build
+targets to build just that command
+(ala "make sed"). Commands built this way do not contain a multiplexer and
+don't care what the command filename is.</p>
+
+<p>The "make change" target (as in change for a $20) builds every command
+standalone (in the "change" subdirectory). Note that this is collectively
+about 10 times as large as the multiplexer version, both in disk space and
+runtime memory. (Even more when statically linked.)</p>
+
+<hr /><h2><a name="cross" />Q: How do I cross compile toybox?</h2>
+
+<p>A: You need a compiler "toolchain" capable of producing binaries that
+run on your target. A toolchain is an
+integrated suite of compiler, assembler, and linker, plus the standard
+headers and
+libraries necessary to build C programs. (And a few miscellaneous binaries like
+nm and objdump.)</p>
+
+<p>Toybox is tested against two compilers (llvm, gcc) and three C libraries
+(bionic, musl, glibc) in the following combinations:</p>
+
+<a name="cross1" />
+<p><a href="#cross1">1) gcc+glibc = host toolchain</a></p>
+
+<p>Most Linux distros come with that as a host compiler, which is used by
+default when you build normally
+(<b>make distclean defconfig toybox</b>, or <b>make menuconfig</b> followed
+by <b>make</b>).</p>
+
+<p>You can use LDFLAGS=--static if you want static binaries, but static
+glibc is hugely inefficient ("hello world" is 810k on x86-64) and throws a
+zillion linker warnings because one of its previous maintainers
+<a href=https://www.akkadia.org/drepper/no_static_linking.html>was insane</a>
+(which meant at the time he refused to fix
+<a href=https://elinux.org/images/2/2d/ELC2010-gc-sections_Denys_Vlasenko.pdf>obvious bugs</a>), plus it uses dlopen() at runtime to implement basic things like
+<a href=https://stackoverflow.com/questions/15165306/compile-a-static-binary-which-code-there-a-function-gethostbyname>DNS lookup</a> (which is almost impossible
+to support properly from a static binary because you wind up with two
+instances of malloc() managing two heaps which corrupt as soon as a malloc()
+from one is free()d into the other, although glibc added
+<a href=https://stackoverflow.com/questions/14289488/use-dlsym-on-a-static-binary>improper support</a> which still requires the shared libraries to be
+installed on the system alongside the static binary:
+<a href=https://www.youtube.com/watch?v=Ih-3vK2qLls>in brief, avoid</a>).
+These days glibc is <a href=https://blog.aurel32.net/175>maintained
+by a committee</a> instead of a single
+maintainer, if that's an improvement. (As with Windows and
+Cobol, most people deal with it and get on with their lives.)</p>
+
+<a name="cross2" />
+<p><a href="#cross2">2) gcc+musl = musl-cross-make</a></p>
+
+<p>The cross compilers I test this with are built from the
+<a href=http://musl.libc.org/>musl-libc</a> maintainer's
+<a href=https://github.com/richfelker/musl-cross-make>musl-cross-make</a>
+project, built by running toybox's scripts/mcm-buildall.sh in that directory,
+and then symlink the resulting "ccc" subdirectory into toybox where
+"make root CROSS=" can find them, ala:</p>
+
+<blockquote><b><pre>
+cd ~
+git clone https://github.com/landley/toybox
+git clone https://github.com/richfelker/musl-cross-make
+cd musl-cross-make
+../toybox/scripts/mcm-buildall.sh # this takes a while
+ln -s $(realpath ccc) ../toybox/ccc
+</pre></b></blockquote>
+
+<p>Instead of symlinking ccc, you can specify a CROSS_COMPILE= prefix
+in the same format the Linux kernel build uses. You can either provide a
+full path in the CROSS_COMPILE string, or add the appropriate bin directory
+to your $PATH. I.E:</p>
+
+<blockquote>
+<b><p>make LDFLAGS=--static CROSS_COMPILE=~/musl-cross-make/ccc/m68k-linux-musl-cross/bin/m68k-linux-musl- distclean defconfig toybox</p></b>
+</blockquote>
+
+<p>Is equivalent to:</p>
+
+<blockquote><b><p>
+export "PATH=~/musl-cross-make/ccc/m68k-linux-musl-cross/bin:$PATH"<br />
+LDFLAGS=--static make distclean defconfig toybox CROSS=m68k-linux-musl-
+</p></b></blockquote>
+
+<p>Note: these examples use static linking becausae a dynamic musl binary
+won't run on your host unless you install musl's libc.so into the system
+libraries (which is an accident waiting to happen adding a second C library
+to most glibc linux distribution) or play with $LD_LIBRARY_PATH.
+In theory you could "make root" a dynamic root filesystem with musl by copying
+the shared libraries out of the toolchain, but I haven't bothered implementing
+that in mkroot yet because a static linked musl hello world is 10k on x86
+(5k if stripped).</p>
+
+<a name="cross3" />
+<p><a href="#cross3">3) llvm+bionic = Android NDK</a></p>
+
+<p>The <a href=https://developer.android.com/ndk/downloads>Android
+Native Development Kit</a> provides an llvm toolchain with the bionic
+libc used by Android. To turn it into something toybox can use, you
+just have to add an appropriately prefixed "cc" symlink to the other
+prefixed tools, ala:</p>
+
+<blockquote><b><pre>
+unzip android-ndk-r21b-linux-x86_64.zip
+cd android-ndk-21b/toolchains/llvm/prebuilt/linux-x86_64/bin
+ln -s x86_64-linux-android29-clang x86_64-linux-android-cc
+PATH="$PWD:$PATH"
+cd ~/toybox
+make distclean
+make LDFLAGS=--static CROSS_COMPILE=x86_64-linux-android- defconfig toybox
+</pre></b></blockquote>
+
+<p>Again, you need to static link unless you want to install bionic on your
+host. Binaries statically linked against bionic are almost as big as with
+glibc, but at least it doesn't have the dlopen() issues. (You still can't
+sanely use dlopen() from a static binary, but bionic doesn't use dlopen()
+internally to implement basic features.)</p>
+
+<p>Note: although the resulting toybox will run in a standard
+Linux system, even "hello world"
+statically linked against bionic segfaults before calling main()
+when /dev/null isn't present. This presents mkroot with a chicken and
+egg problem for both chroot and qemu cases, because mkroot's init script
+has to mount devtmpfs on /dev to provide /dev/null before the shell binary
+can run mkroot's init script.
+Since mkroot runs as a normal user, we can't "mknod dev/null" at build
+time to create a "null" device in the filesystem we're packaging up so
+initramfs doesn't start with an empty /dev, and the
+<a href=https://lkml.org/lkml/2016/6/22/686>kernel</a>
+<a href=https://lkml.org/lkml/2017/5/14/180>developers</a>
+<a href=https://lkml.org/lkml/2017/9/13/651>repeatedly</a>
+<a href=https://lkml.org/lkml/2020/5/14/1584>rejected</a> a patch to
+make the Linux kernel honor DEVTMPFS_MOUNT in initramfs. Teaching toybox
+cpio to accept synthetic filesystem metadata,
+presumably in <a href=https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt>get_init_cpio</a> format, remains a todo item.</p>
+
+<hr /><h2><a name="system" />Q: What part of Linux/Android does toybox provide?</h2>
+
+<p>A:
+Toybox is one of three packages (linux, libc, command line) which together provide a bootable unix-style command line operating system.
+Toybox provides the "command line" part, with a
+<a href=https://en.wikipedia.org/wiki/Bash_(Unix_shell)>bash</a> compatible
+<a href=https://en.wikipedia.org/wiki/Unix_shell>command line interpreter</a>
+and over two hundred <a href=https://landley.net/toybox/help.html>commands</a>
+to call from it, as documented in
+<a href=https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/>posix</a>,
+the <a href=https://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cmdbehav.html>Linux Standard Base</a>, and the
+<a href=https://man7.org/linux/man-pages/dir_section_1.html>Linux Manual
+Pages</a>.</p>
+
+<p>Toybox is not by itself a complete operating system, it's a set of standard command line utilities that run in an operating system.
+Booting a simple system to a shell prompt requires a kernel to drive the hardware (such as Linux, or BSD with a Linux emulation layer), programs for the system to run (such as toybox's commands), and a C library ("libc") to connect them together.</p>
+
+<p>Toybox has a policy of requiring no external dependencies other than the
+kernel and C library (at least for defconfig builds). You can optionally enable support for
+additional libraries in menuconfig (such as openssl, zlib, or selinux),
+but toybox either provides its own built-in versions of such functionality
+(which the libraries provide larger, more complex, often assembly optimized
+alternatives to), or allows things like selinux support to cleanly drop
+out.</p>
+
+<p>Static linking (with the --static option) copies library contents
+into the resulting binary, creating larger but more portable programs which
+can run even if they're the only file in the filesystem. Otherwise,
+the "dynamically" linked programs require each shared library file to be
+present on the target system, either copied out of the toolchain or built
+again from source (with potential version skew if they don't match the toolchain
+versions exactly), plus a dynamic linker executable installed at a specific
+absolute path. See the
+<a href=https://www.man7.org/linux/man-pages/man1/ldd.1.html>ldd</a>,
+<a href=https://www.man7.org/linux/man-pages/man8/ld.so.8.html>ld.so</a>,
+and <a href=https://www.man7.org/linux/man-pages/man7/libc.7.html>libc</a>
+man pages for details.</p>
+
+<p>Most embedded systems will add another package to the kernel/libc/cmdline
+above containing the dedicated "application" that the embedded system exists to
+run, plus any other packages that application depends on.
+Build systems add a native version of the toolchain packages so
+they can compile additional software on the resulting system. Desktop systems
+add a GUI and additional application packages like web browsers
+and video players. A linux distro like Debian adds hundreds of packages.
+Android adds around a thousand.</p>
+
+<p>But all of these systems conceptually sit on a common three-package
+"kernel/libc/cmdline" base (often inefficiently implemented and broken up
+into more packages), and toybox aims to provide a simple, reproducible,
+auditable version of the cmdline portion of that base.</p>
+
+<hr /><h2><a name="mkroot" />Q: How do you build a working Linux system with toybox?</h2>
+
+<p>A: Toybox has a built-in <a href=https://github.com/landley/toybox/blob/master/scripts/mkroot.sh>system builder</a>, with the Makefile target "<b>make
+root</b>". To enter the resulting root filesystem, "<b>sudo chroot
+root/host/fs /init</b>". Type "exit" to get back out.</p>
+
+<p>You can cross compile simple three package (toybox+libc+linux)
+systems configured to boot to a shell prompt under the emulator
+<a href=https://qemu.org>qemu</a>
+by specifying a target type with CROSS=
+(or by setting CROSS_COMPILE= to a <a href=#cross>cross compiler</a> prefix with optional absolute
+path), and pointing the build at a Linux kernel source directory, ala:</p>
+
+<blockquote><p><b>make root CROSS=sh4 LINUX=~/linux</b></p></blockquote>
+
+<p>Then you can <b>cd root/sh4; ./qemu-sh4.sh</b> to launch the emulator.
+(You'll need the appropriate qemu-system-* emulator binary installed.)
+Type "exit" when done and it should shut down the emulator on the way out,
+similar to exiting the chroot version. (Except this is more like you ssh'd
+to a remote machine: the emulator created its own CPU with its own memory
+and I/O devices, and booted a kernel in it.)</p>
+
+<p>The build finds the <a href=#system>three packages</a> needed to produce
+this system because 1) you're in a toybox source directory, 2) your cross
+compiler has a libc built into it, 3) you tell it where to find a Linux kernel
+source directory with LINUX= on the command line. If you don't say LINUX=,
+it skips that part of the build and just produces a root filesystem directory
+ala the first example in this FAQ answer.</p>
+
+<p>The CROSS= shortcut expects a "ccc" symlink in the toybox source directory
+pointing at a directory full of cross compilers. The ones I test this with are built from the musl-libc
+maintainer's
+<a href=https://github.com/richfelker/musl-cross-make>musl-cross-make</a>
+project, built by running toybox's scripts/mcm-buildall.sh in that directory,
+and then symlink the resulting "ccc" subdirectory into toybox where CROSS=
+can find them:</p>
+
+<blockquote><b><pre>
+cd ~
+git clone https://github.com/landley/toybox
+git clone https://github.com/richfelker/musl-cross-make
+cd musl-cross-make
+../toybox/scripts/mcm-buildall.sh # this takes a while
+ln -s $(realpath ccc) ../toybox/ccc
+</pre></b></blockquote>
+
+<p>If you don't want to do that, you can download <a href=http://mkroot.musl.cc/latest/>prebuilt binary versions</a> from Zach van Rijn's site and
+just extract them into a "ccc" subdirectory under the toybox source.</p>
+
+<p>Once you've installed the cross compilers, "<b>make root CROSS=help</b>"
+should list all the available cross compilers it recognizes under ccc,
+something like:</p>
+
+<blockquote><b><p>
+aarch64 armv4l armv5l armv7l armv7m armv7r i486 i686 m68k microblaze mips mips64 mipsel powerpc powerpc64 powerpc64le s390x sh2eb sh4 x32 x86_64
+</p></b></blockquote>
+
+<p>(A long time ago I
+<a href=http://landley.net/aboriginal/architectures.html>tried to explain</a>
+what some of these architectures were.)</p>
+
+<p>You can build all the targets at once, and can add additonal packages
+to the build, by calling the script directly and listing packages on
+the command line:</p>
+
+<blockquote>
+<p><b>scripts/mkroot.sh CROSS=all LINUX=~/linux dropbear</b></p>
+</blockquote>
+
+<p>An example package build script (building the dropbear ssh server, adding a
+port forward from 127.0.0.1:2222 to the qemu command line, and providing a
+ssh2dropbear.sh convenience script to the output directory) is provided
+in the scripts/root directory. If you add your own scripts elsewhere, just
+give a path to them on the command line. (No, I'm not merging more package build
+scripts, I <a href=https://speakerdeck.com/landley/developing-for-non-x86-targets-using-qemu?slide=78>learned that lesson</a> long ago. But if you
+want to write your own, feel free.)</p>
+
+<p>(Note: currently mkroot.sh cheats. If you don't have a .config it'll
+make defconfig and add CONFIG_SH and CONFIG_ROUTE to it, because the new
+root filesystem kinda needs those commands to function properly. If you already
+have a .config that
+_doesn't_ have CONFIG_SH in it, you won't get a shell prompt or be able to run
+the init script without a shell. This is currently a problem because sh
+and route are still in pending and thus not in defconfig, so "make root"
+cheats and adds them. I'm working on it. tl;dr if make root doesn't work
+"rm .config" and run it again, and all this should be fixed up in future when
+those two commands are promoted out of pending so "make defconfig" would have
+what you need anyway. It's designed to let yout tweak your config, which is
+why it uses the .config that's there when there is one, but the default is
+currently wrong because it's not quite finished yet. All this should be
+cleaned up in a future release, before 1.0.)</p>
+
+
 
 <!--#include file="footer.html" -->
diff --git a/www/header.html b/www/header.html
index e43574c..9857e2c 100644
--- a/www/header.html
+++ b/www/header.html
@@ -39,7 +39,7 @@
     <li><a href=https://github.com/landley/toybox/commits/master.atom>Commit RSS feed</a></li>
     <li><a href="/notes.html">Maintainer's Blog</a></li>
     <li><a href=cleanup.html>Cleanup</a></li>
-    <li><a href=http://www.ohloh.net/p/toybox-landley>Statistics</a></li>
+    <li><a href=https://www.openhub.net/p/toybox-landley>Statistics</a></li>
     <li><a href=conduct.html>Code of Conduct</a></li>
   </ul>
 </td>
diff --git a/www/news.html b/www/news.html
old mode 100755
new mode 100644
index 5497fdf..7a36a12
--- a/www/news.html
+++ b/www/news.html
@@ -8,6 +8,423 @@
 
 <h2>News</h2>
 
+<a name="24-10-2020" /><a href="#24-10-2020"><hr><h2><b>October 24, 2020</b></h2></a>
+<blockquote><p>
+"We are now cruising at a level of two to the power of twenty-five thousand to
+one against and falling, and we will be restoring normality just as soon as we
+are sure what is normal anyway."</p>
+<p>- The Hitchhiker's Guide to the Galaxy</p>
+</blockquote>
+
+<p>After a longer and slightly more
+<a href=https://github.com/landley/toybox/commit/07a896862ddf>turbulent</a>
+development cycle than some,
+<a href=downloads/toybox-0.8.4.tar.gz>Toybox 0.8.4</a>
+(<a href=https://github.com/landley/toybox/releases/tag/0.8.4>git commit</a>)
+is out with new commands <b>sha3sum</b> and <b><a href=https://github.com/landley/toybox/commit/6b4c32ae3986>watchdog</a></b>.</p>
+
+<p>There are <a href=downloads/binaries/mkroot/0.8.4>prebuilt mkroot binaries</a>
+now, tiny toybox linux systems for a dozen architectures, all bootable under
+qemu. (The vmlinux in each tarball is a vanilla linux-5.9 kernel built
+from the included config file.)</p>
+
+<p>The FAQ got <a href=faq.html>noticeably larger</a>, and the README has
+more links. New command features include the <b>sed -s</b> flag,
+<b>cpio --no-preserve-owner</b> now affects archive creation,
+Elliott added <b>tar -I</b> and multi-type
+<b>find -type a,b,c</b> support, Mark Salyzyn added <b>xargs -P</b> to
+run parallel jobs, the <b>ps</b> and <b>top</b> commands
+now autodetect pid length, and <b>top</b> adjusts units for memory display based on
+system size.</p>
+
+<p><u>Toysh and toyroot</u>:
+lots of new work on <b>toysh</b>: 29 commits to sh.c since last release, adding 1500
+lines and deleting 700, plus a bunch of sh.tests entries). Since last release
+we implemented wildcards, case/esac and select, brace expansion sequences
+(ala {1..10..2} and {a..z}),
+the remaining variable slice types ${a#y} ${a%y} ${a^y} ${a,y}
+${a/search/replace}, the "<b>source</b>" shell builtin,
+the start of job control, and several bugfixes.
+Plus the standalone "<b>make sh</b>" build understands MAYFORK now. It's
+still missing features like functions() and $((math)) but it's getting
+close to usable now.</p>
+
+<p><u>Documentation</u>:
+Some of the README contents moved to the FAQ, and the README's "presentations"
+section got some new links. Several new <a href=faq.html>FAQ</a> entries explaining things like
+mkroot ("how do I build a working Linux system
+with toybox"), cross compiling (how to get/setup the 2 compilers and
+3 libc we regression test against), and "where does toybox fit into the
+linux/android ecosystem".
+The "toybox --help" output now lists the project's web page (<a href=https://github.com/landley/toybox/issues/50>by request</a>).
+Elliott removed
+getevent (an android board bringup/hardware debugging tool built by running
+a python script against kernel headers, not really in scope for toybox),
+fixed xargs help formatting, and taught the toybox multiplexer's
+command list output (and "kill -l") to measure the current the terminal width
+when wordwrapping (previously hardwired to 80 columns).
+If you're curious, I checked in my <a href=www/release.txt>release procedure
+checklist</a>, and
+vixed a stale link in the nav bar on the left ("statistics" changed domains).
+Firas Khaliki Khana fixed some issues in the roadmap, and Rob
+tweaked the roadmap so status.html is slightly more current.</p>
+
+<p><u>Tests</u>:
+The "make tests" target now fails if any of the tests it ran failed.
+Lots of TEST_HOST tests got fixed (checking for specific error messages exposes
+TEST_HOST to version skew, and scripts/test.sh will now skip TEST_HOST
+commands that aren't installed) and added toyonly annotations as necessary
+(replacing most uses of SKIP_HOST).
+Eric Molitor added automated github tests on MacOS and Ubuntu using their
+"workflows" thing.
+The clang asan plumbing slows some code down more than 10 times, so some testing timeouts were expanded.
+
+<p><u>Pending</u>:
+Eric Molitor did a bunch of work on route: moved it to sbin, added xsend and
+xrecv, taught display_routes() and setroute to use the rtnetlink API and
+do hostname lookups, added support for mss, win, and irtt, merged ipv6 and ipv4
+codepaths with autodetection of address type, implemented RTA_CACHEINFO
+support, switched exit paths to perror_exit(), and removed unused code.
+Ethan Sommer fixed warnings in dhcpd, removed a bunch of unnecessary
+; after GLOBALS() blocks and an unnecessary return in df.
+Erik Moqvist fixed dns setting in the dhcp client.
+Rob did some cleanup on bootchartd, traceroute, getty...
+Elliott fixed getty to reliably update utmp.
+Chris Sarra added ipv6 support to wget.
+Ariadne Conill submitted several small fixes from testing Alpine Linux with
+toybox, and Antoni Villalonga i Noceras fixed typos in error messages.</p>
+
+<p><u>Bugfixes</u>:
+Elliott avoided sign extension in devmem, and Ethan Sommer switched it from
+getpagesize() to posix sysconf(_SC_PAGESIZE).
+Elliott fixed chmod -R
+ignoring dangling symlinks, fixed stty <a href=https://github.com/landley/toybox/issues/251>misparsing c_iflags</a>, fixed the chattr f2fs test with
+compression enabled, taught blkid not to show empty tags, taught xparsedate()
+to read date's default output format, fixed a recent echo -e \0 regression,
+changed cpio -p parsing to match a <a href=http://lists.landley.net/pipermail/toybox-landley.net/2020-August/011955.html>bug</a> in the gnu version
+which an existing script <a href=https://github.com/landley/toybox/commit/fa1af3b085cc>depended on</a>,
+and switched hwclock back to looking
+only at /dev/rtc0 for <a href=https://github.com/landley/toybox/commit/70e2232ce61c>reasons</a> involving kernel version skew and vendor bug reports.</p>
+
+<p>David Legault pointed out that unescape2() (and thus echo -e) wasn't handling
+\0 right.
+Khem Raj reported that mips glibc doesn't have SIGSTKFLT, leading to
+a build break if we assume glibc behaves consistently, which led to a bunch of
+macos signal portability cleanups from Elliott.
+William Djupström fixed tar extracting long file paths, adding hardlinks
+to an archive, and reported that --exclude wasn't working.
+Peter McConalogue pointed out that cp/mv -i prompt should
+default N, and mv should only prompt when stdin is a tty.
+Patrick Oppenlander made rtcwake and hwclock default to UTC if /dev/adjtime
+isn't available, suggested watchdog catch SIGINT, and caught an
+uninitialized offset in blkdiscard which gcc apparently didn't.
+Ryan Pritchard reported that <b>file</b> wasn't getting gif heights right,
+and Elliott added gif version output so TEST_HOST provided similar output.
+Martin Stjernholm fixed <b>cp -P</b> to not follow symlinks and updated
+the help text to say it's not the default.
+Antoni Villalonga added more --long asiases to <b>chgrp/chown/rmdir</b>.
+
+<p>The symlink indirection code subtly broke xexec() so it would still try to call
+a builtin when given a path to a command (fixed now).
+The code to trip \n off xgetline() was using the length of the allocation
+instead of the length of the read.
+Netcat no longer leaks sockfd into child processes.
+Patch "fuzz" support was outputting context lines from the hunk, not the
+file. Chrisrfq fixed i2cdetect parameter reading.
+Chris Sarra taught dd not to throw an error trying to truncate device files,
+added a misshing fflush() to lib/password.c, fixed a parse error in logger
+preventing local facility logging from working right, taught init
+to reload inittab when it receives SIGHUP, and fixed a memory leak in tar.</p>
+
+<p><u>Library</u>:
+New xvdaemon() function daemonizing on nommu systems, to wean commands
+off daemon() which requires TOYBOX_FORK.
+Teach sendfile_len() to use copy_file_range() when available, with compile
+time probe. Fix xsignal_all_killers() to install the correct handler.
+Rob switched dirtree_path() to a non-recursive implementation, and
+taught dirtree that a top level entry named "" is equivalent to
+"." but should not show up in dirtree_path(),
+read_password() now reads from tty device rather than stdin, and handles ctrl-c and ctrl-d, and
+human_readable() now has HR_NODOT so it can print single digit numbers without going "0.0".
+Petri Gynther increased the vmstat column sizes so it didn't fit
+in 80 columns anymore.
+Elliott added UTC offset support to xparsedate().</p>
+
+<p><u>Infrastructure</u>:
+mkroot now only passes --no-preserve-owner to cpio (so initramfs files
+belong to root rather than whichever user ran the build) when using toybox's
+version, because the other one is brain-damaged and errors out not
+understanding it. (It understands it for extract, but not create. Yes really.)
+New PENDING variable you can set to enable more than just sh and route out
+of pending.
+The "make distclean" no longer deletes root_download (where mkroot's "extra plumbing"
+file saves source code downloads it tarballs, such as dropbear).</p>
+
+<p><u>Cleanup</u>:
+Cleanup openvt (35 lines added, 78 lines removed)
+Cleanup blkdiscard so lib/args.c parses the -o and -l numbers, which means
+32 bit systems are limited to 2 gigabyte sizes which is a TODO item systemwide.
+oneit now uses flag macros and mentions -rn in the help. demo-utf8towc
+now has main.c call setlocale for it.
+Some cleanup on stty.</p>
+
+<a name="11-05-2020" /><a href="#11-05-2020"><hr><h2><b>May 11, 2020</b></h2></a>
+<blockquote>
+<p>Ford: Ah. A
+<a href=https://www.youtube.com/watch?v=WSsR419HBpQ>towel</a>.
+Keep this and guard it with your life.</p>
+<p>Arthur: Huh?</p>
+<p>Ford: Listen, it's a tough universe. There's all sorts of people and things
+trying to do you, kill you, rip you off, everything. If you're going to
+survive out there, you've really got to know where your
+<a href=towel.jpg>towel</a> is.</p>
+<p>- The Hitchhiker's Guide to the Galaxy.</p>
+</blockquote>
+
+<p>Despite everything <a href=downloads/toybox-0.8.3.tar.gz>Toybox 0.8.3</a>
+(<a href=https://github.com/landley/toybox/releases/tag/0.8.3>git commit</a>)
+is finally out, with new commands <b>rtcwake</b> from Elliott Hughes and
+<b>blkdiscard</b> from Patrick Oppenlander.
+The big news is "<b>make root</b>" now boots to a shell prompt,
+with toysh making it all the way through toyroot's init script.
+(Some people were looking forward to <b>patch --fuzz</b> support too.)</p>
+
+<p><u>Toyroot</u>: <b>make root</b> now does what it says on the tin, it
+builds a bootable toybox-based Linux system using two source
+packages (toybox and linux). The trivial version is "make root && sudo chroot
+root/host/fs /init". Here's
+a <a href=http://lists.landley.net/pipermail/toybox-landley.net/2020-April/011667.html>post with instructions</a> if you want to know how to build the
+cross compilers for testing the various architectures. The self-contained
+<a href=https://github.com/landley/toybox/blob/0.8.3/scripts/mkroot.sh>script
+that builds a simple bootable Linux system</a> is 250 lines long, and
+should be easy to read if you want to know how it works.</p>
+
+<p>It also has basic module support, meaning arguments that aren't X=Y variable
+assignments are the names of scripts to run to build more
+packages at the end of the build. I checked in an <a href=https://github.com/landley/toybox/blob/master/scripts/root/dropbear>example package</a>,
+and there's generic "download and extract source tarballs"
+<a href=https://github.com/landley/toybox/blob/master/scripts/root/plumbing>plumbing</a> available to such modules.
+(The Makefile doesn't know how to pass module names through to the script,
+so instead of "make root" you have to
+call the script directly, ala "scripts/mkroot.sh CROSS=sh4 LINUX=~/linux dropbear".)</p>
+
+<p>The resulting root filesystem now uses /root as the home
+dir for UID 0, and creates /dev/fd and /dev/shm in devtmpfs. The build works
+around a kernel
+build bug where "make distclean" doesn't work in a "cp -sfR" symlink
+directory. (The bug meant if you pointed LINUX= at unclean source, it was
+unhappy, so now it distcleans the source directory before copying it. This
+modifies said source directory, which is not ideal, but as usual the kernel guys
+<a href=http://lkml.iu.edu/hypermail/linux/kernel/2002.2/00083.html>ignored
+the bug report</a>, so a workaround was required.)
+The CROSS=all build announces each target in the title bar, puts
+its logs into root/log, and has better trap handling to stop with one
+ctrl-c.
+Since last release it uses a more concise config format for the various kernel
+arch targets (which shrank the script by about 50%), and merged
+the old (now removed) scripts/cross.sh into the main script so
+"make CROSS=armv7l LINUX=~/linux" is
+literally just a call to "scripts/mkroot.sh CROSS=armv7l LINUX=~/linux" now.</p>
+
+<p>As for <b>scripts/mcm-buildall.sh</b> building cross compilers,
+of COURSE gcc 8.3 requires a different configuration to build the same
+toolchain as previous versions, it's gcc. It now builds a proper
+nommu libc for sh2eb without a broken fork() call in it that can never
+be used but prevents compile-time probes from detecting nommu,
+and checks that the cross compiler completed before trying to build
+the native compiler.</p>
+
+<p>This was all tested with a recent
+<a href=https://github.com/richfelker/musl-cross-make/commit/5086175f2902>version</a>
+of musl-cross-make with the top level Makefile
+edited to use BINUTILS_VER = 2.32 because the newer one
+<a href=https://www.spinics.net/lists/linux-sh/msg56844.html>breaks the kernel
+build</a>
+and LINUX_VER = 4.19.90 because the default musl-cross-make config uses an
+out-of-tree headers package for some reason (those who forget history are
+<a href=https://lkml.org/lkml/2006/4/28/194>doomed to repeat it</a>) which
+breaks m68k and s390x. I won't post binaries of the resulting toolchains
+because they're GPLv3, but <a href=https://musl.cc/>Thalheim might</a>.
+(I've also <a href=http://lists.landley.net/pipermail/toybox-landley.net/2020-May/011673.html>test built</a>
+with the Android NDK, but bionic's startup code currently segfaults before
+calling main() if it can't open /dev/null, and the kernel guys
+<a href=https://lkml.org/lkml/2017/9/13/651>ignored my patches</a> to
+make CONFIG_DEVTMPFS_MOUNT work in initramfs.)</p>
+
+<p>At the moment toyroot cheats and uses two commands out of pending: toysh
+is about 80% of the way to being useful but still missing some
+obvious features like function support, job and terminal control, command
+history, $((math)), wildcard expansion... plus a lot of bash features like array
+variables, so it isn't out of pending yet. And route needs to be redone to use the
+netlink interface that can handle multiple routing tables. For the moment
+scripts/mkroot.sh adds both of them to defconfig if you haven't already
+got a .config when you run it. (If you build and can't boot, your .config
+probably hasn't got CONFIG_SH switched on. Fix it in menuconfig, or
+rm .config and try gain.)</p>
+
+<p><u>New toybox features</u>:
+Elliott taught <b>patch</b> to understand [FILE [PATCH]] arguments, made
+<b>cal</b> highlight the current day, added -T to <b>cp</b>/<b>mv</b>,
+did a lot of work on <b>lsattr</b>/<b>chattr</b> (including adding -p
+and "chattr ="), added tar --absolute-names, taught <b>id</b> to
+support numeric lookup and handle unknown groups, made -G show all
+groups, and removed context= from -Z.
+Rob added <b>patch -F</b> (fuzz factor) support and <b>help -u</b> (usage only),
+taught <b>echo -e</b> about bash extensions like \uXXXX unicode escapes,
+<b>netcat -L</b> no longer automatically includes stderr (new -E
+option does that),
+<b>setsid</b> now uses -c (like the man page says) instead of -t, and added
+-w (wait) and -d (detach from tty). 
+Andrew Ilijic added <b>ls -w</b>, removed trailing whitespace on output,
+and added tests for -C and -x.</p>
+
+<p><u>Library</u>:
+another fix to 32 bit option parsing of long long optflag values,
+remove getdirname() and just use the libc function (which modifies
+its argument, but we don't have to free a malloc),
+dlist_terminate() can now be called repeatedly on the same list,
+new relative_path() function finds path from one directory to another,
+and readfd() works like readfile() but on an already open fd.
+Use MPATH macros in mkpathat(). Elliott added macOS versions of
+dev_minor()/dev_major()/makedev() to portability.c,
+implemented posix_fallocate() for macOS, removed a
+leftover uClibc workaround in fallocate that was breaking macOS,
+and moved the table of ELF architectures from file to lib/lib.c (so
+readelf can share).
+Park Ju Hyung pointed out the fast path of fdlength() was commented out,
+and we switched it to the 64 bit API while we were there (and then Elliott
+added macOS support).
+Joeky taught file to recognize 7z archives.</p>
+
+<p><u>Pending</u>:
+The shell got a bunch of work: standalone "make sh" now includes the
+multiplexer for builtin commands like "exit" and "cd". Added MAYFORK annotation
+for commands that exist in the $PATH but can also be run within the shell
+process (and sometimes have different behavior within the shell):
+currently applied to help, echo, false, kill, printf, pwd, test, time, and
+true.</p>
+
+<p>Elliott added new commands <b>getopt</b> and <b>readelf</b>.
+Jarno Mäkipää taught wget how to follow http 301 and 302 redirects,
+and did lots of work on vi. (Elliott also did work on vi.)
+Gavin Howard fixed a comparison bug in bc.
+Ethan Sommer fixed numerous small issues (including several build
+warnings and FLAG() macro conversions).</p>
+
+<p><u>Bugfixes</u>:
+<b>tar</b> extract now deletes files and symlinks where it's making a directory,
+<b>find -L -type -l</b> now finds dangling symlinks, extra #ifdefs in
+portability.h prevent old gcc versions from barfing on __has_include(),
+xgetline() now returns NULL at EOF, tee with no arguments was
+writing to stdout twice, setsid works on nommu (I.E. vfork) now,
+<b>netcat -L</b> no longer accumulates zombie processes,
+<b>sntp</b> now uses adjtimex instead of adjtime (to build on bionic).
+xcreate_stdio() was checking WARN_ONLY in the wrong field (and tar was
+passing it in the wrong field, so it worked there).
+Several people wrestled with the <b>xargs</b> "argument too long" problem.
+Alessio Balsini fixed memory leaks in loopback_setup() and "wayling"
+added a missing continue to losetup.
+Elliott fixed xargs -E, various things in modinfo,
+added an error check to gzip when using zlib (which copies non-gzip data to
+the output verbatim for some reason),
+found an case where dirtree could use
+uninitialized data and silenced "Invalid argument" warnings (triggered by
+Android's selinux policies making stat() and readlink() fail),
+fixed locale support in macOS (both in iconv
+and in main.c), taught stat to show filesystem time on macOS,
+fixed a 32 bit truncation in sntp, fixed a memory access one byte outside
+of its array in patch.c, removed the cpio --trailer option,
+and widened the pid display fields in ps to
+6 digits. Rich Felker fixed find.c assuming
+time_t is a long (y2038 issue on 32 bit).
+Greg Kaiser fixed a thinko in get_block_device_size().
+Jarno Mäkipää fixed utf8 support in cut -C, and cp --parents.
+David Legault complained that dir/.* tells rm to delete dir/.. and we'd do it.
+JakeSFR pointed out a bug in file's identification of broken symlinks.
+William Haddon fixed cp to treat a directory with a trailing slash
+teh same as one without.
+Denys Nykula fixed rm -i not to prompt for an empty "" argument.
+SebiderSushi reported that chmod g+s wasn't working.
+The linux kernel doesn't let an O_PATH fd work with fgetxattr(), so
+Elliott switched <b>ls</b> to use the path-based functions now (which is racy,
+it means you can stat() one inode and get the xattrs for a different one,
+but nobody in kernel land seems to use xattrs much so they're not fully
+supported by the API, and who's crazy enough to use xattrs for security
+anyway). Derick Pallas pointed out an xclearenv() bug.
+Atatsulo (or possibly luolongjuna) did a lot of work on the <b>ping</b>
+command: pointed out min/range/max were out of order,
+that we shouldn't print a summary unless we received at least one
+reply packet, and implemented ttl support.</p>
+
+<p><u>Build plumbing</u>:
+The "make root CROSS=all" build announces each target in the title bar, puts
+its logs into root/log, and has better trap handling to stop with one
+ctrl-c.</p>
+
+<p>Fixed scripts/single.sh to work when PREFIX has no trailing slash,
+make silentoldconfig no longer feeds "y" to kconfig (which puts menus in a
+loop) and instead just feeds in newlines to accept whatever the default is.
+The non-git version number I keep forgetting to update each release moved
+from main.c to toys.h.
+Fixed a couple different errors in mkflags.c (one of which caused ls --color
+to set all the other flags).</p>
+
+<p>Elliott added more macos support and tests, and added
+fallocate, cp, mktemp, and mv to the macOS defconfig.</p>
+
+<p><u>Cleanup</u>:
+Rob did some cleanup on xargs, ls, md5sum, and sort, tidied up main.c a bit,
+made cp, base64, dmesg, and free use FLAG macros,
+and switched fallocate to new style global names.
+Elliott Hughes switched rfkill from the old byte at a time get_line()
+to libc getline(), made du use FLAG() macros, and mad chattr use
+standard toybox argument parsing for -v and -p.
+Several commands (help, cp) had sub-options removed from menuconfig.
+Merged realpath into readlink.c, and use xabspath() instead of libc realpath().</p>
+
+<p><u>Documentation</u>:
+New roadmap section about <a href=roadmap.html#packages>other packages</a>
+that toybox can (eventually) replace.</p>
+
+<p>Update roadmap to note that posix-2008 moved to a different URL (content
+at the old URL undergoes random changes), and link to the IETF RFCs.
+design.html explains more of the history of environment sizes on links
+and has a #bits anchor tag.</p>
+
+<p>Update the <a href=design.html#bits>LP64 section</a> of design.html to
+fish the documents out of archive.org now that unix.org is gone,
+and show the actual size table locally.</p>
+
+<p>Elliott improved the help of <b>date</b> and <b>chattr</b>, and
+made the usage: lines in the posix directory more consistent.</p>
+
+<p>Shrank the sed help from 150 lines to 90-ish.</p>
+
+<p><u>Tests</u>:
+New "txpect" performs interactive tests, running through a sequence of
+writes to a command's stdin and reads from stdout and stderr, when failing it
+reports the first non-matching step. (This for example lets sh.test check
+the shell line continuation logic prompts with $PS1 and $PS2 appropriately
+with various unfinished input lines, and that "echo hello; if" doesn't
+output hello before prompting for the next line of input.) Added
+VERBOSE=xpect to print out each read and write successfully performed by txpect.</p>
+
+<p>runtest.sh only creates an "input" file when the input argument isn't empty,
+EVAL doesn't supply -- (you have to provide your own if you want that).</p>
+
+<p>Rob added basic <b>stat</b>, <b>patch</b>, and <b>tee</b> tests.
+Jarno added a bunch of <b>vi</b> tests.
+Elliott fixed tests for <b>ifconfig</b>, <b>lsattr</b>, <b>chattr</b>,
+and <b>date</b>, added tests for find, id, xargs, and made
+the id, iconv, env, file, printf and cat tests work on macOS (and skipped
+the du tests there). Rob cleaned up chmod tests.</p>
+
+<p><u>Sheer pedantry</u>:
+true and false now have usage: lines (which you have to "help false" to see
+because they (intentionally!) don't support --help.
+Renamed get_chunk()/dump_chunk() to read_chunk()/write_chunk() in tail.c
+Elliott fixed some typos.</p>
+
 <a name="18-10-2019" /><a href="#18-10-2019"><hr><h2><b>October 18, 2019</b></h2></a>
 <blockquote>
 <p>"In those days spirits were brave, the stakes were high, men were real
diff --git a/www/release.txt b/www/release.txt
new file mode 100644
index 0000000..7dede94
--- /dev/null
+++ b/www/release.txt
@@ -0,0 +1,34 @@
+VERBOSE=fail make distclean defconfig toybox tests
+news.html - release notes
+Update version in main.c
+  - git commit toys.h www/news.html
+tag repo
+  - git tag $VER
+  - git push
+  - git push --tags
+source tarball
+  git archive --prefix=toybox-$VER/ $VER | gzip -9 > toybox-$VER.tar.gz
+  scp toybox-$VER.tar.gz landley.net:landley.net/toybox/downloads
+  scp www/news.html landley.net:landley.net/toybox/
+binaries
+  cd ../clean
+  git pull ../toybox
+  make distclean; make defconfig root CROSS=all
+  mkdir send; for i in root/*/fs/bin/toybox; do X=${i#*/}; X=${X%%/*}; cp $i send/toybox-$X; done
+  ssh landley.net "mkdir landley.net/toybox/downloads/binaries/$VER"
+  scp send/toybox-* landley.net:landley.net/toybox/downloads/binaries/$VER/
+  update symlink
+update help.html
+  make clean; make defconfig; make
+  (./toybox --version && ./toybox help -ah) > www/help.html
+  scp www/help.html landley.net:landley.net/toybox/
+scripts/mkstatus.py -> status.html
+  - scp www/status.gen landley.net:landley.net/toybox/
+roadmap.html
+code.html
+Inform mailing list (with link to https://landley.net/toybox)
+Update #toybox topic
+  Toybox 0.7.3 released February 21, 2017. http://landley.net/toybox
+
+todo:
+  x86-64, sparc, cortex-m toolchains
diff --git a/www/roadmap.html b/www/roadmap.html
old mode 100755
new mode 100644
index 81dc6ff..41b3866
--- a/www/roadmap.html
+++ b/www/roadmap.html
@@ -27,8 +27,9 @@
 set of Bash 4.x functionality, but does involve {various,features} &lt(beyond)
 posix.</p>
 
-<p>See the <a href=status.html>status page</a> for the combined list
-and progress towards implementing it.</p>
+<p>See the <a href=status.html>status page</a> for the categorized command list
+and progress towards implementing it. There's also a
+<a href=todo.html>historical todo list</a> from the project's 2011 relaunch.</p>
 
 <ul>
 <li><a href=#susv4>POSIX-2008/SUSv4</a></li>
@@ -41,6 +42,7 @@
 <li>Miscelaneous: <a href=#klibc>klibc</a>, <a href=#glibc>glibc</a>,
 <a href=#sash>sash</a>, <a href=#sbase>sbase</a>,
 <a href=#uclinux>uclinux</a>...</li>
+<li><a href=#packages>Other Packages</a></li>
 </ul>
 
 <hr />
@@ -51,7 +53,7 @@
 <p>The best standards describe reality rather than attempting to impose a
 new one. A good standard should document, not legislate.
 Standards which document existing reality tend to be approved by
-more than one standards body, such ANSI and ISO both approving C. That's why
+more than one standards body, such ANSI and ISO both approving <a href=https://landley.net/c99-draft.html>C99</a>. That's why
 the IEEE POSIX committee's 2008 standard, the Single Unix Specification version
 4, and the Open Group Base Specification edition 7 are all the same standard
 from three sources, but most people just call it "posix" (portable operating
@@ -92,7 +94,7 @@
 <p>Starting with the
 <a href="http://pubs.opengroup.org/onlinepubs/9699919799.2008edition/idx/utilities.html">full "utilities" list</a>,
 we first remove generally obsolete
-commands (compess ed ex pr uncompress uccp uustat uux), commands for the
+commands (compress ed ex pr uncompress uccp uustat uux), commands for the
 pre-CVS "SCCS" source control system (admin delta get prs rmdel sact sccs unget
 val what), fortran support (asa fort77), and batch processing support (batch
 qalter qdel qhold qmove qmsg qrerun qrls qselect qsig qstat qsub).</p>
@@ -117,7 +119,10 @@
 days (talk mesg write).  The "pax" utility <a href=https://slashdot.org/story/06/09/04/1335226/debian-kicks-jrg-schilling>failed</a> to replace tar,
 "mailx" is
 a command line email client, and "lp" submits files for printing to... what
-exactly?  (cups?)  The standard defines crontab but not crond.</p>
+exactly?  (cups?)  The standard defines crontab but not crond. What is
+pathchk supposed to be portable _to_? (Linux accepts 255 byte path components
+with any char except NUL or / and no max length on the total path, and
+EXPLICITLY doesn't care if it's an invalid utf8 sequence.)</p>
 
 <p>Removing all of that leaves the following commands, which toybox should
 implement:</p>
@@ -127,7 +132,7 @@
 at awk basename bc cal cat chgrp chmod chown cksum cmp comm cp
 csplit cut date dd df diff dirname du echo env expand expr false file find
 fold fuser getconf grep head id join kill link ln logger logname ls man
-mkdir mkfifo more mv newgrp nice nl nohup od paste patch pathchk printf ps
+mkdir mkfifo more mv newgrp nice nl nohup od paste patch printf ps
 pwd renice rm rmdir sed sh sleep sort split stty tabs tail tee test time
 touch tput tr true tty uname unexpand uniq unlink uudecode uuencode vi wc
 who xargs zcat
@@ -193,7 +198,9 @@
 
 <p>Of these, gettext and msgfmt are internationalization, install_initd and
 remove_initd weren't present in Ubuntu 10.04, lpr is out of scope,
-and lsb_release just reports information in /etc/os-release.</p>
+lsb_release just reports information in /etc/os-release, and sendmail's
+turned into a pile of cryptographic verification and DNS shenanigans due
+to spammers.</p>
 
 <p>This leaves:</p>
 
@@ -201,7 +208,7 @@
 <span id=lsb>
 chfn chsh dmesg egrep fgrep groupadd groupdel groupmod groups
 gunzip gzip hostname install killall md5sum
-mknod mktemp mount passwd pidof sendmail seq shutdown
+mknod mktemp mount passwd pidof seq shutdown
 su sync tar umount useradd userdel usermod zcat
 </span>
 </b></blockquote>
@@ -342,9 +349,8 @@
 
 <p>getprop/setprop/start were in toybox and moved back because they're so
 tied to non-public system interfaces. modprobe shares the implementation
-used in init. getevent probably does make sense as a toybox command, but at the
-moment it's built with a python script that pulls all the constants from the
-latest kernel headers, which is very convenient.</p>
+used in init. getevent is a board bringup tool built with a python script
+that pulls all the constants from the latest kernel headers.</p>
 
 <h3>Other Android /system/bin commands</h3>
 
@@ -382,7 +388,7 @@
 for toybox, we get:</p>
 
 <blockquote><b>
-arping blkid e2fsck dd fsck.f2fs fsck_msdos getevent gzip ip iptables
+arping blkid e2fsck dd fsck.f2fs fsck_msdos gzip ip iptables
 ip6tables iw logwrapper make_ext4fs make_f2fs modpobe newfs_msdos ping ping6
 reboot resize2fs sh ss tc tracepath tracepath6 traceroute traceroute6
 </b></blockquote>
@@ -475,7 +481,7 @@
 
 <blockquote><b>
 <span id=tizen>
-arch base64 users dir vdir unexpand shred join csplit
+arch base64 users unexpand shred join csplit
 hostid nproc runcon sha224sum sha256sum sha384sum sha512sum sha3sum mkfs.vfat fsck.vfat 
 dosfslabel uname stdbuf pinky diff3 sdiff zcmp zdiff zegrep zfgrep zless zmore
 </span>
@@ -688,7 +694,7 @@
 <blockquote><b>
 <span id=sash_cmd>
 ar chattr dd ed file find grep gunzip gzip lsattr more mount mv pivot_root
-sh sum tar umount
+sh tar umount
 </span>
 </b></blockquote>
 
@@ -707,7 +713,7 @@
 
 <blockquote><p>
 <span id=sbase_cmd>
-basename cal cat chgrp chmod chown chroot cksum cmp cols comm cp crond cut date
+basename cal cat chgrp chmod chown chroot cksum cmp comm cp crond cut date
 dirname du echo env expand expr false find flock fold getconf grep head
 hostname join kill link ln logger logname ls md5sum mkdir mkfifo mktemp mv
 nice nl nohup od paste printenv printf pwd readlink renice rm rmdir sed seq
@@ -1077,7 +1083,7 @@
 dig freeramdisk getty halt hexdump hwclock klogd modprobe ping ping6 pivot_root
 poweroff readahead rev sfdisk sudo syslogd taskset telnet telnetd tracepath
 traceroute unzip usleep vconfig zip free login modinfo unshare netcat help w
-ntpd iwconfig iwlist rdate
+iwconfig iwlist rdate
 dos2unix unix2dos catv clear
 pmap realpath setsid timeout truncate
 mkswap swapon swapoff
@@ -1098,8 +1104,203 @@
 microcom tunctl chrt getfattr setfattr
 kexec
 ascii crc32 devmem fmt i2cdetect i2cdump i2cget i2cset mcookie prlimit sntp ulimit uuidgen dhcp6 ipaddr iplink iproute iprule iptunnel cd exit toysh bash traceroute6
+blkdiscard rtcwake
+watchdog
 </span>
 </b></blockquote>
 
+<hr />
+<a name=packages />
+<h2>Other packages</h2>
+
+<p>System administrators have <a href=https://github.com/landley/toybox/issues/168#issuecomment-583725500>asked</a> what other Linux packages toybox commands
+replace, so they can annotate alternatives in their package management system.</p>
+
+<p>This section uses the package definitions from Chapter 6 of
+<a href=http://www.linuxfromscratch.org/lfs/downloads/9.0/LFS-BOOK-9.0-NOCHUNKS.html>Linux From Scratch 9.0</a>). Each package lists what we currently
+replace, pending commands [in square brackets], and what we DON'T plan to
+implement.</p>
+
+<p>Each "see also" note means the listed package also installs the listed shared
+libraries. (While toybox contains equivalent functionality to a lot of these
+shared libraries in its lib/ directory, it does not currently provide a shared
+library interface.)</p>
+
+<h3>Packages toybox plans to provide complete-ish replacents for:</h3>
+<ul>
+<li><b>file</b>: file (see also: libmagic)</li>
+<li><b>m4</b>: [m4]</li>
+<li><b>bc</b>: [bc] [dc]</li>
+<li><b>bison</b>: [yacc] (not: bison, see also: liby)</li>
+<li><b>flex</b>: [lex] (not: flex flex++, see also: libfl)</li>
+<li><b>make</b>: [make]</li>
+<li><b>sed</b>: sed</li>
+<li><b>grep</b>: grep egrep fgrep</li>
+<li><b>bash</b>: bash sh (not: bashbug)</li>
+<li><b>diffutils</b>: cmp [diff] [diff3] [sdiff]</li>
+<li><b>gawk</b>: [awk] (not: gawk gawk-5.0.1)</li>
+<li><b>findutils</b>: find xargs (not: locate updatedb)</li>
+<li><b>less</b>: less (not: lessecho lesskey)</li>
+<li><b>gzip</b>: zcat [gzip] [gunzip] [zcmp] [zdiff] [zegrep] [zfgrep] [zgrep] [zless] [zmore]
+(not: gzexe uncompress zforce znew)</li>
+<li><b>make</b>: [make]</li>
+<li><b>patch</b>: patch</li>
+<li><b>tar</b>: tar</li>
+<li><b>procps-ng</b>: free pgrep pidof pkill ps sysctl top uptime vmstat w watch
+[pmap] [pwdx] [slabtop]
+(not: tload, see also libprocps)</li>
+<li><b>sysklogd</b>: [klogd] [syslogd]</li>
+<li><b>sysvinit</b>: [init] halt poweroff reboot killall5 [shutdown]
+(not telinit runlevel fstab-decode bootlogd)</li>
+<li><b>man</b>: man (but not accessdb apropos catman lexgrog mandb manpath whatis,
+see also libman libmandb)</li>
+<li><b>vim</b>: vi xxd (but not ex, rview, rvim, view, vim, vimdiff, vimtutor)</li>
+<li><b>sysvinit</b>: [init] halt poweroff reboot killall5 [shutdown]
+(not telinit runlevel fstab-decode bootlogd)</li>
+<li><b>kmod</b>: insmod lsmod rmmod modinfo [modprobe]
+(not: depmod kmod)</li>
+<li><b>attr</b>: [getfattr] setfattr (not: attr, see also: libattr)</li>
+<li><b>shadow</b>: [chfn] [chpasswd] [chsh] [groupadd] [groupdel] [groupmod]
+[newusers] passwd [su] [useradd] [userdel] [usermod]
+[lastlog] [login] [newgidmap] [newuidmap]
+(not: chage expiry faillog groupmems grpck logoutd newgrp nologin pwck sg
+vigr vipw, grpconv grpunconv pwconv pwunconv, chgpasswd gpasswd)</li>
+<li><b>psmisc</b>: killall [fuser] [pstree] [peekfd] [prtstat]
+(not: pslog pstree.x11)</li>
+<li><b>inetutils</b>: dnsdomainname [ftp] hostname ifconfig ping ping6 [telnet] [tftp] [traceroute] (not: talk)</li>
+<li><b>coreutils</b>: [ base32 base64 basename cat chgrp chmod chown chroot cksum comm cp cut date
+dd df dirname du echo env expand factor false fmt fold groups head hostid id install
+link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup nproc od
+paste printenv printf pwd readlink realpath rm rmdir seq sha1sum shred
+sleep sort split stat sync tac tail tee test timeout touch true truncate
+tty uname uniq unlink wc who whoami yes
+[expr] [fold] [join] [numfmt] [runcon] [sha224sum] [sha256sum] [sha384sum]
+[sha512sum] [stty] [b2sum] [tr] [unexpand]
+(not: basenc chcon csplit dir dircolors pathchk
+pinky pr ptx shuf stdbuf sum tsort users vdir, see also libstdbuf)</li>
+<li><b>util-linux</b>: blkid blockdev cal chrt dmesg eject fallocate flock hwclock
+ionice kill logger losetup mcookie mkswap more mount mountpoint nsenter
+pivot_root prlimit rename renice rev setsid swapoff swapon switch_root taskset
+umount unshare uuidgen
+[addpart] [fdisk] [findfs] [findmnt] [fsck] [fsfreeze] [fstrim] [getopt]
+[hexdump] [linux32] [linux64] [lsblk] [lscpu] [lsns] [setarch]
+(not: agetty blkdiscard blkzone cfdisk chcpu chmem choom col
+colcrt colrm column ctrlaltdel delpart fdformat fincore fsck.cramfs
+fsck.minix ipcmk ipcrm ipcs isosize last lastb ldattach look lsipc
+lslocks lslogins lsmem mesg mkfs mkfs.bfs mkfs.cramfs mkfs.minix namei partx
+raw readprofile resizepart rfkill rtcwake script scriptreplay
+setterm sfdisk sulogin swaplabel ul
+uname26 utmpdump uuidd uuidparse wall wdctl whereis wipefs
+i386 x86_64 zramctl)</li>
+</ul>
+
+<p>Commentary: toybox init doesn't do runlevels, man and vim are just the
+relevant commands without the piles of strange overgrowth, and if you want
+to call a toybox binary by another name you can create a symlink to a
+symlink. If somebody really wants to argue for "gzexe" or similar, be
+my guest, but there's a lot of obsolete crap in shadow, coreutils,
+util-linux...</p>
+
+<p>No idea why LFS is installing inetutils instead of net-tools
+(which contains arp route ifconfig mii-tool nameif netstat and rarp that
+toybox does or might implement, and plipconfig slattach that it probably won't.)</p>
+
+<h3>Packages toybox plans to provide partial replacents for:</h3>
+
+<p>Toybox provides replacements for some binaries from these packages,
+but there are other useful binaries which this package provides that toybox
+currently considers out of scope for the project:</p>
+
+<ul>
+<li><b>binutils</b>: strings [ar] [nm] [readelf] [size] [objcopy] [strip]
+(not c++filt, dwp, elfedit, gprof. The following commands belong
+in <a href=/code/qcc>qcc</a>: addr2line as ld objdump ranlib)</li>
+<li><b>bzip2</b>: bunzip2 bzcat [bzcmp] [bzdiff] [bzegrep] [bzfgrep] [bzgrep] [bzless]
+[bzmore] (not: bzip2, bzip2recover, see also libbz2)</li>
+<li><b>xz</b>: [xzcat] [lzcat] [lzcmp] [lzdiff] [lzegrep] [lzfgrep] [lzgrep]
+[lzless] [lzmadec, lzmainfo] [lzmore] [unlzma] [unxz] [xzcat]
+[xzcmp] [xzdec] [xzdiff] [xzegrep] [xzfgrep] [xzgrep] [xzless] [xzmore]
+(not: compression side, see also: liblzma)</li>
+<li><b>ncurses</b>: clear reset (not: everything else, see also: libcurses)</li>
+<li><b>e2fsprogs</b>: chattr lsattr [e2fsck] [mkfs.ext2] [mkfs.ext3]
+[fsck.ext2] [fsck.ext3] [e2label] [resize2fs] [tune2fs]
+(not badblocks compile_et debugfs dumpe2fse2freefrag e2image
+e2mmpstatus e2scrub e2scrub_all e2undo e4crypt e4defrag filefrag
+fsck.ext4 logsave mk_cmds mkfs.ext4 mklost+found)</li>
+</ul>
+
+<p>Toybox provides several decompressors but compresses to a single format
+(deflate, ala gzip/zlib). Our e2fsprogs doesn't currently plan to support
+ext4 or defrag. The "qcc" reference is because someday an external project to glue
+QEMU's <a href=https://git.qemu.org/?p=qemu.git;a=blob;f=tcg/README;h=bfa2e4ed246c;hb=HEAD>Tiny Code Generator</a>
+to Fabrice Bellard's old <a href=http://landley.net/hg/tinycc>Tiny C Compiler</a>
+making a multicall binary that does cc/ld/as for all the targets QEMU
+supports (then use the
+<a href=https://github.com/JuliaComputing/llvm-cbe>LLVM C Backend</a>
+to compile LLVM itself to C for use as a modern replacement for
+<a href=https://en.wikipedia.org/wiki/Cfront>cfront</a> to bootstrap
+C++ code) is under consideration
+as a successor project to toybox. Until then things like objdump -d
+(requiring target-specific disassembly for an unbounded number of architectures)
+are out of scope for toybox. (This means drawing the line somewhere between
+architecture-specific support in file and strace, and including a full
+assembler for each architecture.)</p>
+</span>
+
+<h3>Packages from LFS ch6 toybox does NOT plan to replace:</h3>
+
+<ul>
+<li><b>linux-api-headers</b></li>
+<li><b>man-pages glibc</b></li>
+<li><b>zlib</b></li>
+<li><b>readline</b></li>
+<li><b>gmp</b></li>
+<li><b>mpfr</b></li>
+<li><b>mpc</b></li>
+<li><b>gcc</b></li>
+<li><b>pkg-config</b></li>
+<li><b>ncurses</b></li>
+<li><b>acl</b></li>
+<li><b>libcap</b></li>
+<li><b>psmisc</b></li>
+<li><b>iana-etc</b></li>
+<li><b>libtool</b></li>
+<li><b>gdbm</b></li>
+<li><b>gperf</b></li>
+<li><b>expat</b></li>
+<li><b>perl</b></li>
+<li><b>XML::Parser</b></li>
+<li><b>intltool</b></li>
+<li><b>autoconf</b></li>
+<li><b>automake</b></li>
+<li><b>gettext</b></li>
+<li><b>libelf</b></li>
+<li><b>libffi</b></li>
+<li><b>openssl</b></li>
+<li><b>python</b></li>
+<li><b>ninja</b></li>
+<li><b>meson</b></li>
+<li><b>check</b></li>
+<li><b>groff</b></li>
+<li><b>grub</b></li>
+<li><b>libpipeline</b></li>
+<li><b>texinfo</b></li>
+</ul>
+
+<p>That said, we do implement our own zlib and readline replacements, and
+presumably _could_ export them as library bindings. Plus we provide
+our own version of a bunch of the section 1 man pages (as command help).
+Possibly libcap and acl are interesting?</p>
+
+<h3>Misc</h3>
+
+<p>The kbd package has over a dozen commands, we only implement chvt. The
+iproute2 package implements over a dozen commands, there's an "ip" in
+pending but I'm not a fan (ifconfig and route and such should be extended
+to work properly). We don't implement eudev, but toybox's maintainer
+created busybox mdev way back when (which replaces it) and plans to do a
+new one for toybox as soon as we work out what subset is still needed now that
+devtmpfs is available.</p>
+
 <!-- #include "footer.html" -->
 
diff --git a/www/toycans.png b/www/toycans.png
index 1a133da..fcd2a0e 100644
--- a/www/toycans.png
+++ b/www/toycans.png
Binary files differ