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

Change-Id: Id8974b50d3d8640a544b81f7cc99e569371fd214
diff --git a/.config-device b/.config-device
index f52c7ac..57fe20c 100644
--- a/.config-device
+++ b/.config-device
@@ -44,6 +44,7 @@
 # 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
@@ -247,6 +248,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
@@ -324,6 +326,7 @@
 CONFIG_ULIMIT=y
 CONFIG_UMOUNT=y
 CONFIG_UNAME=y
+# CONFIG_UNICODE is not set
 CONFIG_UNIQ=y
 CONFIG_UNIX2DOS=y
 CONFIG_UNLINK=y
diff --git a/.config-linux b/.config-linux
index 82b80a8..f8ec411 100644
--- a/.config-linux
+++ b/.config-linux
@@ -45,6 +45,7 @@
 # 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
@@ -241,6 +242,7 @@
 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
@@ -313,6 +315,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
diff --git a/.config-mac b/.config-mac
index ee4d3ef..a419353 100644
--- a/.config-mac
+++ b/.config-mac
@@ -45,6 +45,7 @@
 # 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
@@ -241,6 +242,7 @@
 # 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
@@ -313,6 +315,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
diff --git a/.github/workflows/toybox.yml b/.github/workflows/toybox.yml
index b3b1667..1b463b2 100644
--- a/.github/workflows/toybox.yml
+++ b/.github/workflows/toybox.yml
@@ -7,6 +7,20 @@
     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
 
diff --git a/Config.in b/Config.in
index f7407d8..27948ad 100644
--- a/Config.in
+++ b/Config.in
@@ -106,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
diff --git a/Makefile b/Makefile
index 21d3359..52602d2 100644
--- a/Makefile
+++ b/Makefile
@@ -63,6 +63,7 @@
 	@echo root cleaned
 
 clean::
+	@chmod -fR 700 generated || true
 	@rm -rf toybox generated change .singleconfig*
 	@echo cleaned
 
diff --git a/android/device/generated/config.h b/android/device/generated/config.h
index e4be83f..8aacc15 100644
--- a/android/device/generated/config.h
+++ b/android/device/generated/config.h
@@ -62,6 +62,8 @@
 #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
@@ -468,6 +470,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
@@ -622,6 +626,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
diff --git a/android/device/generated/flags.h b/android/device/generated/flags.h
index 0ac63e7..ed8aa2e 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]"
@@ -2344,6 +2355,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
@@ -2560,6 +2591,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"
@@ -3246,6 +3285,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"
@@ -3598,6 +3645,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
@@ -5513,6 +5569,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
@@ -5691,6 +5765,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
@@ -6275,6 +6355,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
diff --git a/android/device/generated/globals.h b/android/device/generated/globals.h
index 9005cea..876e468 100644
--- a/android/device/generated/globals.h
+++ b/android/device/generated/globals.h
@@ -124,7 +124,7 @@
 struct seq_data {
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 };
 
 // toys/lsb/su.c
@@ -161,8 +161,8 @@
 struct microcom_data {
   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
@@ -214,8 +214,9 @@
 
 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
@@ -379,6 +380,12 @@
   char *c;
 };
 
+// toys/other/pwgen.c
+
+struct pwgen_data {
+  char *r;
+};
+
 // toys/other/rtcwake.c
 
 struct rtcwake_data {
@@ -841,11 +848,10 @@
     } exec;
   };
 
-  // keep lineno here: used to work around compiler limitation in run_command()
-  long lineno;
+  // keep ifs here: used to work around compiler limitation in run_command()
   char *ifs, *isexec, *wcpat;
-  unsigned options, jobcnt;
-  int hfd, pid, bangpid, varslen, shift, cdcount;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount;
   long long SECONDS;
 
   // global and local variables
@@ -857,8 +863,9 @@
   // Parsed functions
   struct sh_function {
     char *name;
-    struct sh_pipeline {  // pipeline segments
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
       struct sh_pipeline *next, *prev, *end;
+      unsigned lineno;
       int count, here, type; // TODO abuse type to replace count during parsing
       struct sh_arg {
         char **v;
@@ -878,8 +885,17 @@
     struct sh_arg *raw, arg;
   } *pp; // currently running process
 
+  struct sh_callstack {
+    struct sh_callstack *next;
+    struct sh_function scratch;
+    struct sh_arg arg;
+    struct arg_list *delete;
+    unsigned lineno;
+    long shift;
+  } *cc;
+
   // job list, command line for $*, scratch space for do_wildcard_files()
-  struct sh_arg jobs, *arg, *wcdeck;
+  struct sh_arg jobs, *wcdeck;
 };
 
 // toys/pending/stty.c
@@ -1492,6 +1508,7 @@
 
 struct tee_data {
   void *outputs;
+  int out;
 };
 
 // toys/posix/touch.c
@@ -1587,6 +1604,7 @@
 	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;
diff --git a/android/device/generated/help.h b/android/device/generated/help.h
index d18e68a..754e9a1 100644
--- a/android/device/generated/help.h
+++ b/android/device/generated/help.h
@@ -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."
@@ -200,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"
@@ -318,6 +318,8 @@
 
 #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."
@@ -334,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"
@@ -368,6 +372,8 @@
 
 #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	dereference NAME and unset that\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)"
@@ -490,7 +496,7 @@
 
 #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\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"
 
@@ -612,7 +618,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 1b8324f..3e86e65 100644
--- a/android/device/generated/newtoys.h
+++ b/android/device/generated/newtoys.h
@@ -4,7 +4,7 @@
 USE_SH(OLDTOY(-toysh, sh, 0))
 USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
 USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY([, test, TOYFLAG_NOFORK|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))
@@ -12,6 +12,7 @@
 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))
@@ -68,7 +69,7 @@
 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_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, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
 USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
@@ -81,7 +82,7 @@
 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))
@@ -100,7 +101,7 @@
 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_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))
@@ -208,6 +209,7 @@
 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)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
@@ -227,6 +229,7 @@
 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))
@@ -284,6 +287,7 @@
 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))
diff --git a/android/linux/generated/config.h b/android/linux/generated/config.h
index ec88bca..342dda3 100644
--- a/android/linux/generated/config.h
+++ b/android/linux/generated/config.h
@@ -64,6 +64,8 @@
 #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
@@ -456,6 +458,8 @@
 #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
@@ -600,6 +604,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
diff --git a/android/linux/generated/flags.h b/android/linux/generated/flags.h
index 5aefddd..e6bbb33 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]"
@@ -2344,6 +2355,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
@@ -2560,6 +2591,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"
@@ -3246,6 +3285,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"
@@ -3598,6 +3645,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
@@ -5513,6 +5569,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
@@ -5691,6 +5765,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
@@ -6275,6 +6355,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
diff --git a/android/linux/generated/globals.h b/android/linux/generated/globals.h
index 9005cea..876e468 100644
--- a/android/linux/generated/globals.h
+++ b/android/linux/generated/globals.h
@@ -124,7 +124,7 @@
 struct seq_data {
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 };
 
 // toys/lsb/su.c
@@ -161,8 +161,8 @@
 struct microcom_data {
   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
@@ -214,8 +214,9 @@
 
 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
@@ -379,6 +380,12 @@
   char *c;
 };
 
+// toys/other/pwgen.c
+
+struct pwgen_data {
+  char *r;
+};
+
 // toys/other/rtcwake.c
 
 struct rtcwake_data {
@@ -841,11 +848,10 @@
     } exec;
   };
 
-  // keep lineno here: used to work around compiler limitation in run_command()
-  long lineno;
+  // keep ifs here: used to work around compiler limitation in run_command()
   char *ifs, *isexec, *wcpat;
-  unsigned options, jobcnt;
-  int hfd, pid, bangpid, varslen, shift, cdcount;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount;
   long long SECONDS;
 
   // global and local variables
@@ -857,8 +863,9 @@
   // Parsed functions
   struct sh_function {
     char *name;
-    struct sh_pipeline {  // pipeline segments
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
       struct sh_pipeline *next, *prev, *end;
+      unsigned lineno;
       int count, here, type; // TODO abuse type to replace count during parsing
       struct sh_arg {
         char **v;
@@ -878,8 +885,17 @@
     struct sh_arg *raw, arg;
   } *pp; // currently running process
 
+  struct sh_callstack {
+    struct sh_callstack *next;
+    struct sh_function scratch;
+    struct sh_arg arg;
+    struct arg_list *delete;
+    unsigned lineno;
+    long shift;
+  } *cc;
+
   // job list, command line for $*, scratch space for do_wildcard_files()
-  struct sh_arg jobs, *arg, *wcdeck;
+  struct sh_arg jobs, *wcdeck;
 };
 
 // toys/pending/stty.c
@@ -1492,6 +1508,7 @@
 
 struct tee_data {
   void *outputs;
+  int out;
 };
 
 // toys/posix/touch.c
@@ -1587,6 +1604,7 @@
 	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;
diff --git a/android/linux/generated/help.h b/android/linux/generated/help.h
index bcc1f77..dfe4a40 100644
--- a/android/linux/generated/help.h
+++ b/android/linux/generated/help.h
@@ -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."
@@ -202,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"
@@ -320,6 +320,8 @@
 
 #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."
@@ -336,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"
@@ -370,6 +374,8 @@
 
 #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	dereference NAME and unset that\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)"
@@ -492,7 +498,7 @@
 
 #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\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"
 
@@ -618,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/linux/generated/newtoys.h b/android/linux/generated/newtoys.h
index 1b8324f..3e86e65 100644
--- a/android/linux/generated/newtoys.h
+++ b/android/linux/generated/newtoys.h
@@ -4,7 +4,7 @@
 USE_SH(OLDTOY(-toysh, sh, 0))
 USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
 USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY([, test, TOYFLAG_NOFORK|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))
@@ -12,6 +12,7 @@
 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))
@@ -68,7 +69,7 @@
 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_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, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
 USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
@@ -81,7 +82,7 @@
 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))
@@ -100,7 +101,7 @@
 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_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))
@@ -208,6 +209,7 @@
 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)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
@@ -227,6 +229,7 @@
 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))
@@ -284,6 +287,7 @@
 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))
diff --git a/android/mac/generated/config.h b/android/mac/generated/config.h
index b39ec91..d03b74a 100644
--- a/android/mac/generated/config.h
+++ b/android/mac/generated/config.h
@@ -64,6 +64,8 @@
 #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
@@ -456,6 +458,8 @@
 #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
@@ -600,6 +604,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
diff --git a/android/mac/generated/flags.h b/android/mac/generated/flags.h
index c2a9b82..ae5d58a 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]"
@@ -2344,6 +2355,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
@@ -2560,6 +2591,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"
@@ -3246,6 +3285,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"
@@ -3598,6 +3645,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
@@ -5513,6 +5569,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
@@ -5691,6 +5765,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
@@ -6275,6 +6355,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
diff --git a/android/mac/generated/globals.h b/android/mac/generated/globals.h
index 9005cea..876e468 100644
--- a/android/mac/generated/globals.h
+++ b/android/mac/generated/globals.h
@@ -124,7 +124,7 @@
 struct seq_data {
   char *s, *f;
 
-  int precision;
+  int precision, buflen;
 };
 
 // toys/lsb/su.c
@@ -161,8 +161,8 @@
 struct microcom_data {
   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
@@ -214,8 +214,9 @@
 
 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
@@ -379,6 +380,12 @@
   char *c;
 };
 
+// toys/other/pwgen.c
+
+struct pwgen_data {
+  char *r;
+};
+
 // toys/other/rtcwake.c
 
 struct rtcwake_data {
@@ -841,11 +848,10 @@
     } exec;
   };
 
-  // keep lineno here: used to work around compiler limitation in run_command()
-  long lineno;
+  // keep ifs here: used to work around compiler limitation in run_command()
   char *ifs, *isexec, *wcpat;
-  unsigned options, jobcnt;
-  int hfd, pid, bangpid, varslen, shift, cdcount;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount;
   long long SECONDS;
 
   // global and local variables
@@ -857,8 +863,9 @@
   // Parsed functions
   struct sh_function {
     char *name;
-    struct sh_pipeline {  // pipeline segments
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
       struct sh_pipeline *next, *prev, *end;
+      unsigned lineno;
       int count, here, type; // TODO abuse type to replace count during parsing
       struct sh_arg {
         char **v;
@@ -878,8 +885,17 @@
     struct sh_arg *raw, arg;
   } *pp; // currently running process
 
+  struct sh_callstack {
+    struct sh_callstack *next;
+    struct sh_function scratch;
+    struct sh_arg arg;
+    struct arg_list *delete;
+    unsigned lineno;
+    long shift;
+  } *cc;
+
   // job list, command line for $*, scratch space for do_wildcard_files()
-  struct sh_arg jobs, *arg, *wcdeck;
+  struct sh_arg jobs, *wcdeck;
 };
 
 // toys/pending/stty.c
@@ -1492,6 +1508,7 @@
 
 struct tee_data {
   void *outputs;
+  int out;
 };
 
 // toys/posix/touch.c
@@ -1587,6 +1604,7 @@
 	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;
diff --git a/android/mac/generated/help.h b/android/mac/generated/help.h
index bcc1f77..dfe4a40 100644
--- a/android/mac/generated/help.h
+++ b/android/mac/generated/help.h
@@ -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."
@@ -202,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"
@@ -320,6 +320,8 @@
 
 #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."
@@ -336,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"
@@ -370,6 +374,8 @@
 
 #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	dereference NAME and unset that\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)"
@@ -492,7 +498,7 @@
 
 #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\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"
 
@@ -618,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/mac/generated/newtoys.h b/android/mac/generated/newtoys.h
index 1b8324f..3e86e65 100644
--- a/android/mac/generated/newtoys.h
+++ b/android/mac/generated/newtoys.h
@@ -4,7 +4,7 @@
 USE_SH(OLDTOY(-toysh, sh, 0))
 USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
 USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY([, test, TOYFLAG_NOFORK|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))
@@ -12,6 +12,7 @@
 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))
@@ -68,7 +69,7 @@
 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_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, "^i0u*", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
 USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
@@ -81,7 +82,7 @@
 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))
@@ -100,7 +101,7 @@
 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_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))
@@ -208,6 +209,7 @@
 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)adehlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
@@ -227,6 +229,7 @@
 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))
@@ -284,6 +287,7 @@
 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))
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 7166313..775d8d6 100644
--- a/kconfig/macos_miniconfig
+++ b/kconfig/macos_miniconfig
@@ -114,4 +114,3 @@
 CONFIG_TOYBOX_FLOAT=y
 CONFIG_TOYBOX_HELP=y
 CONFIG_TOYBOX_HELP_DASHDASH=y
-CONFIG_TOYBOX_I18N=y
diff --git a/lib/deflate.c b/lib/deflate.c
index eebcd3d..8c72442 100644
--- a/lib/deflate.c
+++ b/lib/deflate.c
@@ -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);
diff --git a/lib/lib.c b/lib/lib.c
index c4e70df..1aa9b80 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -349,17 +349,18 @@
 // Convert wc to utf8, returning bytes written. Does not null terminate.
 int wctoutf8(char *s, unsigned wc)
 {
-  int len = (wc>0x7ff)+(wc>0xffff), mask = 12+len+!!len;
+  int len = (wc>0x7ff)+(wc>0xffff), i;
 
   if (wc<128) {
     *s = wc;
     return 1;
   } else {
+    i = len;
     do {
-      s[1+len] = 0x80+(wc&0x3f);
-      wc >>= 7;
-    } while (len--);
-    *s = wc|mask;
+      s[1+i] = 0x80+(wc&0x3f);
+      wc >>= 6;
+    } while (i--);
+    *s = (((signed char) 0x80) >> (len+1)) | wc;
   }
 
   return 2+len;
@@ -395,37 +396,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;
 }
@@ -865,6 +870,18 @@
   *(p++) = '/';
 }
 
+// Init base32 table
+
+void base32_init(char *p)
+{
+  int i;
+
+  for (i = 'A'; i != '8'; i++) {
+    if (i == 'Z'+1) i = '2';
+    *(p++) = i;
+  }
+}
+
 int yesno(int def)
 {
   return fyesno(stdin, def);
@@ -970,55 +987,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);
 }
diff --git a/lib/lib.h b/lib/lib.h
index 150133b..1ba59ee 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -255,6 +255,7 @@
 void replace_tempfile(int fdin, int fdout, char **tempname);
 void crc_init(unsigned int *crc_table, int little_endian);
 void base64_init(char *p);
+void base32_init(char *p);
 int yesno(int def);
 int fyesno(FILE *fp, int def);
 int qstrcmp(const void *a, const void *b);
@@ -337,6 +338,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);
diff --git a/lib/portability.c b/lib/portability.c
index 91c0190..0c364c2 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -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;
       }
diff --git a/lib/portability.h b/lib/portability.h
index 5886f63..bbba12d 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -208,12 +208,16 @@
 ssize_t xattr_fset(int, const char*, const void*, size_t, int);
 #endif
 
-// macOS doesn't have these functions, but we can fake them.
 #ifdef __APPLE__
+// macOS doesn't have these functions, but we can fake them.
 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
 
+
 // Android is missing some headers and functions
 // "generated/config.h" is included first
 #if CFG_TOYBOX_SHADOW
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..b0d0c5d 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)
 {
diff --git a/main.c b/main.c
index 7c60bdf..a7a8a69 100644
--- a/main.c
+++ b/main.c
@@ -98,12 +98,15 @@
   if (!(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);
   }
 }
 
diff --git a/scripts/runtest.sh b/scripts/runtest.sh
index ddbf054..6aad9ff 100644
--- a/scripts/runtest.sh
+++ b/scripts/runtest.sh
@@ -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
 
   "$@"
 }
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/chmod.test b/tests/chmod.test
index cbc3280..cd4f810 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,20 @@
 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"
 
 mkdir foo
 ln -s bar foo/baz
diff --git a/tests/find.test b/tests/find.test
index 71a5501..f427737 100755
--- a/tests/find.test
+++ b/tests/find.test
@@ -142,5 +142,6 @@
 
 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/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/sed.test b/tests/sed.test
index beb11d5..c3928e5 100755
--- a/tests/sed.test
+++ b/tests/sed.test
@@ -185,6 +185,8 @@
 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 (20 sec timeout)' \
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
index 67921c1..1d021ac 100644
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -107,6 +107,9 @@
 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' ""
+
 #########################################################################
 # Change EVAL to call sh -c for us, using "bash" explicitly for the host.
 export EVAL="$SH -c"
@@ -191,6 +194,8 @@
 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" "" ""
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/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/toys.h b/toys.h
index b2e4721..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>
diff --git a/toys/example/demo_utf8towc.c b/toys/example/demo_utf8towc.c
index 2573785..c052254 100644
--- a/toys/example/demo_utf8towc.c
+++ b/toys/example/demo_utf8towc.c
@@ -6,7 +6,6 @@
 
 config DEMO_UTF8TOWC
   bool "demo_utf8towc"
-  depends on TOYBOX_I18N
   default n
   help
     usage: demo_utf8towc
diff --git a/toys/lsb/seq.c b/toys/lsb/seq.c
index 988466b..beeaed3 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
+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;
+}
+
+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/net/microcom.c b/toys/net/microcom.c
index 62b020f..963445c 100644
--- a/toys/net/microcom.c
+++ b/toys/net/microcom.c
@@ -22,30 +22,35 @@
 GLOBALS(
   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;
 
   // 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, TT.s, &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);
 
diff --git a/toys/other/base64.c b/toys/other/base64.c
index ef7854a..25e37d4 100644
--- a/toys/other/base64.c
+++ b/toys/other/base64.c
@@ -5,6 +5,7 @@
  * No standard
 
 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,6 +18,18 @@
     -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
@@ -24,8 +37,9 @@
 
 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 +52,7 @@
   };
 }
 
-static void do_base64(int fd, char *name)
+static void do_base_n(int fd, char *name)
 {
   int out = 0, bits = 0, x = 0, i, len;
   char *buf = toybuf+128;
@@ -49,8 +63,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 +76,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 +92,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 +103,20 @@
 
 void base64_main(void)
 {
+  TT.n = 6;
+  TT.align = 3;
   base64_init(toybuf);
-  loopfiles(toys.optargs, do_base64);
+  loopfiles(toys.optargs, do_base_n);
+}
+
+#define CLEANUP_base64
+#define FOR_base32
+#include "generated/flags.h"
+
+void base32_main(void)
+{
+  TT.n = 5;
+  TT.align = 7;
+  base32_init(toybuf);
+  loopfiles(toys.optargs, do_base_n);
 }
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/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/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/pending/sh.c b/toys/pending/sh.c
index 21143199..14fb55c 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -45,6 +45,7 @@
 USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
 USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
 USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
 USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
@@ -93,6 +94,24 @@
     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	dereference NAME and unset that
+
+    OPTIONs:
+      history - enable command history
+
 config UNSET
   bool
   default n
@@ -186,11 +205,10 @@
     } exec;
   };
 
-  // keep lineno here: used to work around compiler limitation in run_command()
-  long lineno;
+  // keep ifs here: used to work around compiler limitation in run_command()
   char *ifs, *isexec, *wcpat;
-  unsigned options, jobcnt;
-  int hfd, pid, bangpid, varslen, shift, cdcount;
+  unsigned options, jobcnt, LINENO;
+  int hfd, pid, bangpid, varslen, cdcount;
   long long SECONDS;
 
   // global and local variables
@@ -202,8 +220,9 @@
   // Parsed functions
   struct sh_function {
     char *name;
-    struct sh_pipeline {  // pipeline segments
+    struct sh_pipeline {  // pipeline segments: linked list of arg w/metadata
       struct sh_pipeline *next, *prev, *end;
+      unsigned lineno;
       int count, here, type; // TODO abuse type to replace count during parsing
       struct sh_arg {
         char **v;
@@ -223,8 +242,17 @@
     struct sh_arg *raw, arg;
   } *pp; // currently running process
 
+  struct sh_callstack {
+    struct sh_callstack *next;
+    struct sh_function scratch;
+    struct sh_arg arg;
+    struct arg_list *delete;
+    unsigned lineno;
+    long shift;
+  } *cc;
+
   // job list, command line for $*, scratch space for do_wildcard_files()
-  struct sh_arg jobs, *arg, *wcdeck;
+  struct sh_arg jobs, *wcdeck;
 )
 
 // Can't yet avoid this prototype. Fundamental problem is $($($(blah))) nests,
@@ -237,8 +265,10 @@
 static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
   ">&", ">|", ">", "&>>", "&>", 0};
 
-#define OPT_BRACE       0x100   // set -B
-#define OPT_NOCLOBBER   0x200   // 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
 
 static void syntax_err(char *s)
 {
@@ -258,7 +288,7 @@
 }
 
 // add argument to an arg_list
-static char *push_arg(struct arg_list **list, char *arg)
+static void *push_arg(struct arg_list **list, void *arg)
 {
   struct arg_list *al;
 
@@ -409,7 +439,7 @@
 
     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, "%ld", TT.lineno);
+    else if (c == 'L') sprintf(toybuf, "%u", TT.LINENO);
     else if (c == 'G') sprintf(toybuf, "TODO: GROUPS");
 
     return toybuf;
@@ -485,11 +515,18 @@
 
   // 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))) return s+i;
+
+  if (strstart(&s, "<(") || strstart(&s, ">(")) {
+    toybuf[quote++]=')';
+    end = s;
+  } else if ((i = anystart(s, (void *)redirectors))) return s+i;
+
+  if (strstart(&s, "<(") || strstart(&s, ">(")) {
+    toybuf[quote++]=')';
+    end = s;
+  }
 
   // (( is a special quote at the start of a word
   if (strstart(&end, "((")) toybuf[quote++] = 254;
@@ -506,14 +543,13 @@
 
     // Handle quote contexts
     if ((q = quote ? toybuf[quote-1] : 0)) {
-
       // when waiting for parentheses, they nest
       if ((q == ')' || q >= 254) && (*end == '(' || *end == ')')) {
         if (*end == '(') qc++;
         else if (qc) qc--;
         else if (q >= 254) {
           // (( can end with )) or retroactively become two (( if we hit one )
-          if (strstart(&end, "))")) quote--;
+          if (*end == ')' && end[1] == ')') quote--, end++;
           else if (q == 254) return start+1;
           else if (q == 255) toybuf[quote-1] = ')';
         } else if (*end == ')') quote--;
@@ -533,7 +569,6 @@
       // 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 *[]){";;&", ";;", ";&", ";", "||",
@@ -544,7 +579,7 @@
     // Things the same unquoted or in most non-single-quote contexts
 
     // start new quote context?
-    if (strchr("\"'`", *end)) toybuf[quote++] = *end;
+    if (strchr("'\"`"+(q == '"'), *end)) toybuf[quote++] = *end;
 
     // backslash escapes
     else if (*end == '\\') {
@@ -757,21 +792,21 @@
   if (cc == '-') {
     s = ss = xmalloc(8);
     if (TT.options&FLAG_i) *ss++ = 'i';
-    if (TT.options&OPT_BRACE) *ss++ = 'B';
+    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.arg->c?TT.arg->c-1:0);
+  else if (cc == '#') s = xmprintf("%d", TT.cc->arg.c?TT.cc->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.shift;
-      if (uu<TT.arg->c) s = TT.arg->v[uu];
+      if (uu) uu += TT.cc->shift;
+      if (uu<TT.cc->arg.c) s = TT.cc->arg.v[uu];
     } else if ((*used = varend(str)-str)) return getvar(str);
   }
   if (s) push_arg(delete, s);
@@ -1270,7 +1305,7 @@
             for (slice++, kk = 0; kk<TT.varslen; kk++)
               if (!strncmp(s = TT.vars[kk].str, ss, jj))
                 arg_add(&aa, push_arg(delete, s = xstrndup(s, stridx(s, '='))));
-            if (aa.c) push_arg(delete, (void *)aa.v);
+            if (aa.c) push_arg(delete, aa.v);
 
           // else dereference to get new varname, discarding if none, check err
           } else {
@@ -1288,8 +1323,8 @@
             if (!jj) ifs = (void *)1;
             else if (ifs && *(ss = ifs)) {
               if (strchr("@*", cc)) {
-                aa.c = TT.arg->c-1;
-                aa.v = TT.arg->v+1;
+                aa.c = TT.cc->arg.c-1;
+                aa.v = TT.cc->arg.v+1;
                 jj = 1;
               } else ifs = getvar_special(ifs, strlen(ifs), &jj, delete);
               if (ss && ss[jj]) {
@@ -1312,8 +1347,8 @@
       // Resolve unprefixed variables
       if (strchr("{$", ss[-1])) {
         if (strchr("@*", cc)) {
-          aa.c = TT.arg->c-1;
-          aa.v = TT.arg->v+1;
+          aa.c = TT.cc->arg.c-1;
+          aa.v = TT.cc->arg.v+1;
         } else {
           ifs = getvar_special(ss, jj, &jj, delete);
           if (!jj) {
@@ -1600,7 +1635,7 @@
   char *s, *ss;
 
   // collect brace spans
-  if ((TT.options&OPT_BRACE) && !(flags&NO_BRACE)) for (i = 0; ; i++) {
+  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
@@ -1778,31 +1813,32 @@
 // TODO |&
 
 // turn a parsed pipeline back into a string.
-static char *pl2str(struct sh_pipeline *pl)
+static char *pl2str(struct sh_pipeline *pl, int one)
 {
-  struct sh_pipeline *end = 0;
-  int level = 0, len = 0, i, j;
-  char *s, *ss, *sss;
+  struct sh_pipeline *end = 0, *pp;
+  int 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 (j = 0; ; j++) for (end = pl; end; end = end->next) {
-    if (end->type == 1) level++;
-    else if (end->type == 3 && --level<0) break;
+  for (ss = 0;; ss = xmalloc(len+1)) {
+    for (pp = pl; pp != end; pp = pp->next) {
+      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);
+    }
 
-    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;
-
-    sss = pl->arg->v[pl->arg->c];
-    if (!sss) sss = ";";
-    if (j) ss = stpcpy(ss, sss);
-    else len += strlen(sss);
+    if (ss) return ss;
+  }
 
 // TODO test output with case and function
 // TODO add HERE documents back in
-    if (j) return s;
-    s = ss = xmalloc(len+1);
-  }
 }
 
 // Expand arguments and perform redirections. Return new process object with
@@ -1977,7 +2013,7 @@
       else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND|O_WRONLY;
       else {
         from = (*ss == '<') ? O_RDONLY : O_CREAT|O_WRONLY|O_TRUNC;
-        if (!strcmp(ss, ">") && (TT.options&OPT_NOCLOBBER)) {
+        if (!strcmp(ss, ">") && (TT.options&OPT_C)) {
           struct stat st;
 
           // Not _just_ O_EXCL: > /dev/null allowed
@@ -2117,7 +2153,7 @@
     // "declaration does not declare anything", but if we DON'T give it a name
     // it accepts it. So we can't use the union's type name here, and have
     // to offsetof() the first thing _after_ the union to get the size.
-    memset(&TT, 0, offsetof(struct sh_data, lineno));
+    memset(&TT, 0, offsetof(struct sh_data, ifs));
 
     TT.pp = pp;
     if (!sigsetjmp(rebound, 1)) {
@@ -2187,11 +2223,13 @@
   return pl->end;
 }
 
+// Append a new pipeline to function, returning pipeline and pipeline's arg
 static struct sh_pipeline *add_pl(struct sh_function *sp, struct sh_arg **arg)
 {
   struct sh_pipeline *pl = xzalloc(sizeof(struct sh_pipeline));
 
   *arg = pl->arg;
+  if (TT.cc) pl->lineno = TT.cc->lineno;
   dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
 
   return pl->end = pl;
@@ -2283,8 +2321,6 @@
     // Parse next word and detect overflow (too many nested quotes).
     if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush;
 
-// dprintf(2, "word[%ld]=%.*s (%s)\n", end ? end-start : 0, (int)(end ? end-start : 0), start, ex);
-
     // Is this a new pipeline segment?
     if (!pl) pl = add_pl(sp, &arg);
 
@@ -2301,8 +2337,10 @@
     // Ok, we have a word. What does it _mean_?
 
     // case/esac parsing is weird (unbalanced parentheses!), handle first
-    i = ex && !strcmp(ex, "esac") && (pl->type || (*start==';' && end-start>1));
+    i = ex && !strcmp(ex, "esac") &&
+        ((pl->type && pl->type != 3) || (*start==';' && end-start>1));
     if (i) {
+
       // 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
@@ -2642,6 +2680,7 @@
   return 0;
 }
 
+// Stack of nested if/else/fi and for/do/done contexts.
 struct blockstack {
   struct blockstack *next;
   struct sh_pipeline *start, *middle;
@@ -2683,7 +2722,7 @@
     if (c=='!') {
       if (*prompt=='!') prompt++;
       else {
-        pp += snprintf(pp, len, "%ld", TT.lineno);
+        pp += snprintf(pp, len, "%u", TT.cc->lineno);
         continue;
       }
     } else if (c=='\\') {
@@ -2737,7 +2776,9 @@
   struct blockstack *blk = 0, *new;
   struct sh_process *pplist = 0; // processes piping into current level
   int *urd = 0, pipes[2] = {-1, -1};
-  long i;
+  long i, j, k;
+
+  if (!pl) return;
 
 // TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
 //       probably have to inline run_command here to do that? Implicit ()
@@ -2762,11 +2803,33 @@
       *s = *pl->arg->v, *ss = pl->arg->v[1];
 
     // Skip disabled blocks, handle pipes
+    TT.LINENO = pl->lineno;
     if (pl->type<2) {
       if (blk && !blk->run) {
         pl = pl->end->next;
         continue;
       }
+
+      if (TT.options&OPT_x) {
+        struct sh_callstack *sc;
+        char *ss, *ps4 = getvar("PS4");
+
+        // duplicate first char of ps4 call depth times
+        if (ps4 && *ps4) {
+          for (i = 0, sc = TT.cc; sc; sc = sc->next) i++;
+          j = getutf8(ps4, k = strlen(ps4), 0);
+          ss = xmalloc(i*j+k);
+          for (k = 0; k<i; k++) memcpy(ss+k*j, ps4, j);
+          strcpy(ss+k*j, ps4+j);
+          do_prompt(ss);
+          free(ss);
+
+          ss = pl2str(pl, 1);
+          dprintf(2, "%s\n", ss);
+          free(ss);
+        }
+      }
+
       if (pipe_segments(ctl, pipes, &urd)) break;
     }
 
@@ -2780,13 +2843,14 @@
         // How many layers to peel off?
         i = ss ? atol(ss) : 0;
         if (i<1) i = 1;
-        if (!blk || pl->arg->c>2 || ss[strspn(ss, "0123456789")]) {
+        if (!blk || pl->arg->c>2 || (ss && ss[strspn(ss, "0123456789")])) {
           syntax_err(s);
           break;
         }
 
         while (i && blk)
-          if (!--i && *s == 'c') pl = blk->start;
+          if (blk->middle && !strcmp(*blk->middle->arg->v, "do")
+            && !--i && *s=='c') pl = blk->start;
           else pl = pop_block(&blk, pipes);
         if (i) {
           syntax_err("break");
@@ -2838,7 +2902,7 @@
       } else {
         // Create new process
         if (!CFG_TOYBOX_FORK) {
-          ss = pl2str(pl->next);
+          ss = pl2str(pl->next, 0);
           pp->pid = run_subshell(ss, strlen(ss));
           free(ss);
         } else if (!(pp->pid = fork())) {
@@ -2904,7 +2968,7 @@
                 break;
               } else vv += **vv == '(';
             }
-            arg.c = 0;
+            arg.c = arg2.c = 0;
             if ((err = expand_arg_nobrace(&arg, *vv++, NO_SPLIT, &blk->fdelete,
               &arg2))) break;
             s = arg.c ? *arg.v : "";
@@ -3001,6 +3065,7 @@
   struct sh_function scratch;
 
 // TODO switch the fmemopen for -c to use this? Error checking? $(blah)
+// TODO Merge this with do_source()
 
   memset(&scratch, 0, sizeof(struct sh_function));
   if (!parse_line(new, &scratch)) run_function(scratch.pipeline);
@@ -3010,7 +3075,7 @@
   return toys.exitval;
 }
 
-// only set local variable when global not present, does not extend array
+// set variable
 static struct sh_vars *initlocal(char *name, char *val)
 {
   return addvar(xmprintf("%s=%s", name, val ? val : ""));
@@ -3093,22 +3158,18 @@
 // Read script input and execute lines, with or without prompts
 int do_source(char *name, FILE *ff)
 {
-  struct sh_function scratch;
-  long lineno = TT.lineno, shift = TT.shift;
-  struct sh_arg arg, *old = TT.arg;
+  struct sh_callstack *cc = xzalloc(sizeof(struct sh_callstack));
   int more = 0;
   char *new;
 
-  arg.c = toys.optc;
-  arg.v = toys.optargs;
-  TT.arg = &arg;
-  TT.lineno = TT.shift = 0;
-  memset(&scratch, 0, sizeof(scratch));
+  cc->next = TT.cc;
+  cc->arg.v = toys.optargs;
+  cc->arg.c = toys.optc;
+  TT.cc = cc;
 
-  // TODO: factor out and combine with sh_main() plumbing?
   do {
     new = prompt_getline(ff, more+1);
-    if (!TT.lineno++ && new && !memcmp(new, "\177ELF", 4)) {
+    if (!(TT.LINENO = TT.cc->lineno++) && new && !memcmp(new, "\177ELF", 4)) {
       error_msg("'%s' is ELF", name);
       free(new);
 
@@ -3119,21 +3180,22 @@
     // prints "hello" vs "hello\"
 
     // returns 0 if line consumed, command if it needs more data
-    more = parse_line(new ? : " ", &scratch);
+    more = parse_line(new ? : " ", &cc->scratch);
     if (more==1) {
       if (!new && !ff) syntax_err("unexpected end of file");
     } else {
-      if (!more) run_function(scratch.pipeline);
-      free_function(&scratch);
+      if (!more) run_function(cc->scratch.pipeline);
+      free_function(&cc->scratch);
       more = 0;
     }
     free(new);
   } while(new);
 
   if (ff) fclose(ff);
-  TT.lineno = lineno;
-  TT.shift = shift;
-  TT.arg = old;
+  TT.cc = TT.cc->next;
+  free_function(&cc->scratch);
+  llist_traverse(cc->delete, llist_free_arg);
+  free(cc);
 
   return more;
 }
@@ -3178,6 +3240,7 @@
     initlocal("BASH", s);
   initlocal("PS2", "> ");
   initlocal("PS3", "#? ");
+  initlocal("PS4", "+ ");
 
   // Ensure environ copied and toys.envc set, and clean out illegal entries
   TT.ifs = " \t\n";
@@ -3257,7 +3320,7 @@
   FILE *ff;
 
   signal(SIGPIPE, SIG_IGN);
-  TT.options = OPT_BRACE;
+  TT.options = OPT_B;
 
   TT.pid = getpid();
   TT.SECONDS = time(0);
@@ -3274,7 +3337,8 @@
     if (!FLAG(c)) {
       if (toys.optc==1) toys.optflags |= FLAG_s;
       if (FLAG(s) && isatty(0)) toys.optflags |= FLAG_i;
-    } else if (toys.optc>1) {
+    }
+    if (toys.optc>1) {
       toys.optargs++;
       toys.optc--;
     }
@@ -3301,7 +3365,7 @@
 
   // Read and execute lines from file
   if (do_source(cc ? : *toys.optargs, ff))
-    error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
+    error_exit("%u:unfinished line"+3*!TT.cc->lineno, TT.cc->lineno);
 }
 
 // TODO: ./blah.sh one two three: put one two three in scratch.arg
@@ -3382,6 +3446,65 @@
   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;
+
+  if (!*toys.optargs) {
+// TODO escape properly
+    for (ii = 0; ii<TT.varslen; ii++) printf("%s\n", TT.vars[ii].str);
+
+    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.cc->arg;
+
+    for (al = *(head = &TT.cc->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.cc->delete, strdup(toys.optargs[ii++])));
+    push_arg(&TT.cc->delete, arg->v);
+  }
+}
+
 void unset_main(void)
 {
   char **arg, *s;
@@ -3428,12 +3551,18 @@
 
 void eval_main(void)
 {
-  struct sh_arg *old = TT.arg, new = {toys.argv, toys.optc+1};
+  struct sh_arg old = TT.cc->arg, *volatile arg = &TT.cc->arg;
   char *s;
 
-  TT.arg = &new;
+  // borrow the $* expand infrastructure (avoiding $* from trap handler race).
+  arg->c = 0;
+  arg->v = toys.argv;
+  arg->c = toys.optc+1;
   s = expand_one_arg("\"$*\"", SEMI_IFS, 0);
-  TT.arg = old;
+  arg->c = 0;
+  arg->v = old.v;
+  arg->c = old.c;
+
   sh_run(s);
   free(s);
 }
@@ -3559,14 +3688,14 @@
   long long by = 1;
 
   if (toys.optc) by = atolx(*toys.optargs);
-  by += TT.shift;
-  if (by<0 || by>=TT.arg->c) toys.exitval++;
-  else TT.shift = by;
+  by += TT.cc->shift;
+  if (by<0 || by>=TT.cc->arg.c) toys.exitval++;
+  else TT.cc->shift = by;
 }
 
 void source_main(void)
 {
-  char *name = *toys.optargs;
+  char *name = toys.optargs[1];
   FILE *ff = fpathopen(name);
 
   if (!ff) return perror_msg_raw(name);
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/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/posix/chmod.c b/toys/posix/chmod.c
index 3645ebc..2cdda95 100644
--- a/toys/posix/chmod.c
+++ b/toys/posix/chmod.c
@@ -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.
@@ -50,7 +50,7 @@
     // 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);
+    mode = string_to_mode(TT.mode, try->st.st_mode & ~S_IFMT);
     if (FLAG(v)) {
       char *s = dirtree_path(try, 0);
 
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/find.c b/toys/posix/find.c
index ff127b9..2d8ca0b 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -690,7 +690,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 9f445fc..fce8d56 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"
diff --git a/toys/posix/sed.c b/toys/posix/sed.c
index 8fbef0c..9bd0503 100644
--- a/toys/posix/sed.c
+++ b/toys/posix/sed.c
@@ -147,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
 };
 
@@ -441,7 +441,7 @@
         } 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);
           l2used += match[0].rm_eo;
@@ -793,6 +793,7 @@
       if (!TT.nextlen--) break;
     } else if (c == 's') {
       char *end, delim = 0;
+      int flags;
 
       // s/pattern/replacement/flags
 
@@ -845,19 +846,20 @@
 
         if (isspace(*line) && *line != '\n') continue;
 
-        if (0 <= (l = stridx("igp", *line))) command->sflags |= 1<<l;
+        if (0 <= (l = stridx("igpx", *line))) command->sflags |= 1<<l;
         else if (*line == 'I') command->sflags |= 1<<0;
-        else if (!(command->sflags>>3) && 0<(l = strtol(line, &line, 10))) {
-          command->sflags |= l << 3;
+        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') {
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 cf6e1f5..d4bc184 100644
--- a/toys/posix/test.c
+++ b/toys/posix/test.c
@@ -5,7 +5,7 @@
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
 
 USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
-USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
+USE_SH(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
 
 config TEST
   bool "test"
@@ -20,7 +20,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:
@@ -66,16 +66,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];
diff --git a/www/faq.html b/www/faq.html
index 6704cc5..803d7b5 100755
--- a/www/faq.html
+++ b/www/faq.html
@@ -56,15 +56,15 @@
 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. Apple's even
-<a href=http://meta.ath0.com/2012/02/05/apples-great-gpl-purge/>more pronounced</a> response was to freeze xcode at the last GPLv2 releases
+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,
 implementing a
@@ -530,7 +530,8 @@
 <p>Toybox is tested against two compilers (llvm, gcc) and three C libraries
 (bionic, musl, glibc) in the following combinations:</p>
 
-<p><u>1) gcc+glibc = host toolchain</u></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
@@ -555,7 +556,8 @@
 maintainer, if that's an improvement. (As with Windows and
 Cobol, most people deal with it and get on with their lives.)</p>
 
-<p><u>2) gcc+musl = musl-cross-make</u></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
@@ -598,7 +600,8 @@
 that in mkroot yet because a static linked musl hello world is 10k on x86
 (5k if stripped).</p>
 
-<p><u>3) llvm+bionic = Android NDK</u></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
diff --git a/www/roadmap.html b/www/roadmap.html
index ea10f11..5b7a8dc 100644
--- a/www/roadmap.html
+++ b/www/roadmap.html
@@ -1167,7 +1167,7 @@
 <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>: [ base64 basename true cat chgrp chmod chown chroot cksum comm cp cut date
+<li><b>coreutils</b>: [ 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
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