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

Change-Id: Iefe42ee7a2b54ddfae8812448b49301e5b5de92c
diff --git a/generated/flags.h b/generated/flags.h
index fe80a90..b0d0a15 100644
--- a/generated/flags.h
+++ b/generated/flags.h
@@ -639,9 +639,9 @@
 #undef FLAG_P
 #endif
 
-// diff <2>2(color)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3 <2>2(color)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3
+// diff <2>2(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3 <2>2(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3
 #undef OPTSTR_diff
-#define OPTSTR_diff "<2>2(color)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3"
+#define OPTSTR_diff "<2>2(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3"
 #ifdef CLEANUP_diff
 #undef CLEANUP_diff
 #undef FOR_diff
@@ -661,6 +661,7 @@
 #undef FLAG_b
 #undef FLAG_d
 #undef FLAG_B
+#undef FLAG_strip_trailing_cr
 #undef FLAG_color
 #endif
 
@@ -1015,9 +1016,9 @@
 #undef FLAG_t
 #endif
 
-// grep (line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw] (line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]
+// 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] (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]
 #undef OPTSTR_grep
-#define OPTSTR_grep "(line-buffered)(color):;(exclude-dir)*S(exclude)*M(include)*ZzEFHIab(byte-offset)h(no-filename)ino(only-matching)rsvwcl(files-with-matches)q(quiet)(silent)e*f*C#B#A#m#x[!wx][!EFw]"
+#define OPTSTR_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]"
 #ifdef CLEANUP_grep
 #undef CLEANUP_grep
 #undef FOR_grep
@@ -1034,6 +1035,7 @@
 #undef FLAG_w
 #undef FLAG_v
 #undef FLAG_s
+#undef FLAG_R
 #undef FLAG_r
 #undef FLAG_o
 #undef FLAG_n
@@ -2103,12 +2105,13 @@
 #undef FLAG_c
 #endif
 
-// pidof <1so: <1so:
+// pidof <1so:x <1so:x
 #undef OPTSTR_pidof
-#define OPTSTR_pidof "<1so:"
+#define OPTSTR_pidof "<1so:x"
 #ifdef CLEANUP_pidof
 #undef CLEANUP_pidof
 #undef FOR_pidof
+#undef FLAG_x
 #undef FLAG_o
 #undef FLAG_s
 #endif
@@ -3894,7 +3897,8 @@
 #define FLAG_b (1<<13)
 #define FLAG_d (1<<14)
 #define FLAG_B (1<<15)
-#define FLAG_color (1<<16)
+#define FLAG_strip_trailing_cr (1<<16)
+#define FLAG_color (1<<17)
 #endif
 
 #ifdef FOR_dirname
@@ -4203,24 +4207,25 @@
 #define FLAG_w (1<<10)
 #define FLAG_v (1<<11)
 #define FLAG_s (1<<12)
-#define FLAG_r (1<<13)
-#define FLAG_o (1<<14)
-#define FLAG_n (1<<15)
-#define FLAG_i (1<<16)
-#define FLAG_h (1<<17)
-#define FLAG_b (1<<18)
-#define FLAG_a (1<<19)
-#define FLAG_I (1<<20)
-#define FLAG_H (1<<21)
-#define FLAG_F (1<<22)
-#define FLAG_E (1<<23)
-#define FLAG_z (1<<24)
-#define FLAG_Z (1<<25)
-#define FLAG_M (1<<26)
-#define FLAG_S (1<<27)
-#define FLAG_exclude_dir (1<<28)
-#define FLAG_color (1<<29)
-#define FLAG_line_buffered (1<<30)
+#define FLAG_R (1<<13)
+#define FLAG_r (1<<14)
+#define FLAG_o (1<<15)
+#define FLAG_n (1<<16)
+#define FLAG_i (1<<17)
+#define FLAG_h (1<<18)
+#define FLAG_b (1<<19)
+#define FLAG_a (1<<20)
+#define FLAG_I (1<<21)
+#define FLAG_H (1<<22)
+#define FLAG_F (1<<23)
+#define FLAG_E (1<<24)
+#define FLAG_z (1<<25)
+#define FLAG_Z (1<<26)
+#define FLAG_M (1<<27)
+#define FLAG_S (1<<28)
+#define FLAG_exclude_dir (1<<29)
+#define FLAG_color (1<<30)
+#define FLAG_line_buffered (1<<31)
 #endif
 
 #ifdef FOR_groupadd
@@ -5104,8 +5109,9 @@
 #ifndef TT
 #define TT this.pidof
 #endif
-#define FLAG_o (1<<0)
-#define FLAG_s (1<<1)
+#define FLAG_x (1<<0)
+#define FLAG_o (1<<1)
+#define FLAG_s (1<<2)
 #endif
 
 #ifdef FOR_ping
diff --git a/generated/globals.h b/generated/globals.h
index 9f54f13..9570248 100644
--- a/generated/globals.h
+++ b/generated/globals.h
@@ -479,7 +479,7 @@
   int proc_accounting;
   int is_login;
 
-  void *head;
+  pid_t cur_pid;
 };
 
 // toys/pending/brctl.c
@@ -520,6 +520,7 @@
     long sz, count;
     unsigned long long offset;
   } in, out;
+  unsigned conv, iflag, oflag;
 };;
 
 // toys/pending/dhcp.c
diff --git a/generated/help.h b/generated/help.h
index 21c4eef..28eb07d 100644
--- a/generated/help.h
+++ b/generated/help.h
@@ -74,7 +74,7 @@
 
 #define HELP_seq "usage: seq [-w|-f fmt_str] [-s sep_str] [first] [increment] last\n\nCount from first to last, by increment. Omitted arguments default\nto 1. Two arguments are used as first and last. Arguments can be\nnegative or floating point.\n\n-f	Use fmt_str as a printf-style floating point format string\n-s	Use sep_str as separator, default is a newline character\n-w	Pad to equal width with leading zeroes"
 
-#define HELP_pidof "usage: pidof [-s] [-o omitpid[,omitpid...]] [NAME]...\n\nPrint the PIDs of all processes with the given names.\n\n-s	Single shot, only return one pid\n-o	Omit PID(s)"
+#define HELP_pidof "usage: pidof [-s] [-o omitpid[,omitpid...]] [NAME]...\n\nPrint the PIDs of all processes with the given names.\n\n-s	Single shot, only return one pid\n-o	Omit PID(s)\n-x	Match shell scripts too"
 
 #define HELP_passwd_sad "Password changes are checked to make sure they're at least 6 chars long,\ndon't include the entire username (but not a subset of it), or the entire\nprevious password (but changing password1, password2, password3 is fine).\nThis heuristic accepts \"aaaaaa\" and \"123456\"."
 
@@ -410,7 +410,7 @@
 
 #define HELP_dumpleases "usage: dumpleases [-r|-a] [-f LEASEFILE]\n\nDisplay DHCP leases granted by udhcpd\n-f FILE,  Lease file\n-r        Show remaining time\n-a        Show expiration time"
 
-#define HELP_diff "usage: diff [-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2\n\n-a  Treat all files as text\n-b  Ignore changes in the amount of whitespace\n-B  Ignore changes whose lines are all blank\n-d  Try hard to find a smaller set of changes\n-i  Ignore case differences\n-L  Use LABEL instead of the filename in the unified header\n-N  Treat absent files as empty\n-q  Output only whether files differ\n-r  Recurse\n-S  Start with FILE when comparing directories\n-T  Make tabs line up by prefixing a tab when necessary\n-s  Report when two files are the same\n-t  Expand tabs to spaces in output\n-U  Output LINES lines of context\n-w  Ignore all whitespace\n\n--color  Colored output"
+#define HELP_diff "usage: diff [-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2\n\n-a	Treat all files as text\n-b	Ignore changes in the amount of whitespace\n-B	Ignore changes whose lines are all blank\n-d	Try hard to find a smaller set of changes\n-i	Ignore case differences\n-L	Use LABEL instead of the filename in the unified header\n-N	Treat absent files as empty\n-q	Output only whether files differ\n-r	Recurse\n-S	Start with FILE when comparing directories\n-T	Make tabs line up by prefixing a tab when necessary\n-s	Report when two files are the same\n-t	Expand tabs to spaces in output\n-u	Unified diff\n-U	Output LINES lines of context\n-w	Ignore all whitespace\n\n--color              Colored output\n--strip-trailing-cr  Strip trailing '\\r's from input lines"
 
 #define HELP_dhcpd "usage: dhcpd [-46fS] [-i IFACE] [-P N] [CONFFILE]\n\n -f    Run in foreground\n -i Interface to use\n -S    Log to syslog too\n -P N  Use port N (default ipv4 67, ipv6 547)\n -4, -6    Run as a DHCPv4 or DHCPv6 server"
 
@@ -418,7 +418,7 @@
 
 #define HELP_dhcp "usage: dhcp [-fbnqvoCRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n            [-H HOSTNAME] [-V VENDOR] [-x OPT:VAL] [-O OPT]\n\n     Configure network dynamically using DHCP.\n\n   -i Interface to use (default eth0)\n   -p Create pidfile\n   -s Run PROG at DHCP events (default /usr/share/dhcp/default.script)\n   -B Request broadcast replies\n   -t Send up to N discover packets\n   -T Pause between packets (default 3 seconds)\n   -A Wait N seconds after failure (default 20)\n   -f Run in foreground\n   -b Background if lease is not obtained\n   -n Exit if lease is not obtained\n   -q Exit after obtaining lease\n   -R Release IP on exit\n   -S Log to syslog too\n   -a Use arping to validate offered address\n   -O Request option OPT from server (cumulative)\n   -o Don't request any options (unless -O is given)\n   -r Request this IP address\n   -x OPT:VAL  Include option OPT in sent packets (cumulative)\n   -F Ask server to update DNS mapping for NAME\n   -H Send NAME as client hostname (default none)\n   -V VENDOR Vendor identifier (default 'toybox VERSION')\n   -C Don't send MAC as client identifier\n   -v Verbose\n\n   Signals:\n   USR1  Renew current lease\n   USR2  Release current lease"
 
-#define HELP_dd "usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [bs=N] [count=N] [skip=N]\n        [seek=N] [conv=notrunc|noerror|sync|fsync] [status=noxfer|none]\n\nCopy/convert files.\n\nif=FILE		Read from FILE instead of stdin\nof=FILE		Write to FILE instead of stdout\nbs=N		Read and write N bytes at a time\nibs=N		Read N bytes at a time\nobs=N		Write N bytes at a time\ncount=N		Copy only N input blocks\nskip=N		Skip N input blocks\nseek=N		Skip N output blocks\nconv=notrunc	Don't truncate output file\nconv=noerror	Continue after read errors\nconv=sync	Pad blocks with zeros\nconv=fsync	Physically write data out before finishing\nstatus=noxfer	Don't show transfer rate\nstatus=none	Don't show transfer rate or records in/out\n\nNumbers may be suffixed by c (*1), w (*2), b (*512), kD (*1000), k (*1024),\nMD (*1000*1000), M (*1024*1024), GD (*1000*1000*1000) or G (*1024*1024*1024)."
+#define HELP_dd "usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [iflag=FLAGS] [oflag=FLAGS]\n        [bs=N] [count=N] [seek=N] [skip=N]\n        [conv=notrunc|noerror|sync|fsync] [status=noxfer|none]\n\nCopy/convert files.\n\nif=FILE		Read from FILE instead of stdin\nof=FILE		Write to FILE instead of stdout\nbs=N		Read and write N bytes at a time\nibs=N		Input block size\nobs=N		Output block size\ncount=N		Copy only N input blocks\nskip=N		Skip N input blocks\nseek=N		Skip N output blocks\niflag=FLAGS	Set input flags\noflag=FLAGS	Set output flags\nconv=notrunc	Don't truncate output file\nconv=noerror	Continue after read errors\nconv=sync	Pad blocks with zeros\nconv=fsync	Physically write data out before finishing\nstatus=noxfer	Don't show transfer rate\nstatus=none	Don't show transfer rate or records in/out\n\nFLAGS is a comma-separated list of:\n\ncount_bytes	(iflag) interpret count=N in bytes, not blocks\nseek_bytes	(oflag) interpret seek=N in bytes, not blocks\nskip_bytes	(iflag) interpret skip=N in bytes, not blocks\n\nNumbers may be suffixed by c (*1), w (*2), b (*512), kD (*1000), k (*1024),\nMD (*1000*1000), M (*1024*1024), GD (*1000*1000*1000) or G (*1024*1024*1024)."
 
 #define HELP_crontab "usage: crontab [-u user] FILE\n               [-u user] [-e | -l | -r]\n               [-c dir]\n\nFiles used to schedule the execution of programs.\n\n-c crontab dir\n-e edit user's crontab\n-l list user's crontab\n-r delete user's crontab\n-u user\nFILE Replace crontab by FILE ('-': stdin)"
 
@@ -542,7 +542,7 @@
 
 #define HELP_head "usage: head [-n number] [file...]\n\nCopy first lines from files to stdout. If no files listed, copy from\nstdin. Filename \"-\" is a synonym for stdin.\n\n-n	Number of lines to copy\n-c	Number of bytes to copy\n-q	Never print headers\n-v	Always print headers"
 
-#define HELP_grep "usage: grep [-EFrivwcloqsHbhn] [-ABC NUM] [-m MAX] [-e REGEX]... [-MS PATTERN]... [-f REGFILE] [FILE]...\n\nShow lines matching regular expressions. If no -e, first argument is\nregular expression to match. With no files (or \"-\" filename) read stdin.\nReturns 0 if matched, 1 if no match found, 2 for command errors.\n\n-e  Regex to match. (May be repeated.)\n-f  File listing regular expressions to match.\n\nfile search:\n-r  Recurse into subdirectories (defaults FILE to \".\")\n-M  Match filename pattern (--include)\n-S  Skip filename pattern (--exclude)\n--exclude-dir=PATTERN  Skip directory pattern\n-I  Ignore binary files\n\nmatch type:\n-A  Show NUM lines after     -B  Show NUM lines before match\n-C  NUM lines context (A+B)  -E  extended regex syntax\n-F  fixed (literal match)    -a  always text (not binary)\n-i  case insensitive         -m  match MAX many lines\n-v  invert match             -w  whole word (implies -E)\n-x  whole line               -z  input NUL terminated\n\ndisplay modes: (default: matched line)\n-c  count of matching lines  -l  show only matching filenames\n-o  only matching part       -q  quiet (errors only)\n-s  silent (no error msg)    -Z  output NUL terminated\n\noutput prefix (default: filename if checking more than 1 file)\n-H  force filename           -b  byte offset of match\n-h  hide filename            -n  line number of match"
+#define HELP_grep "usage: grep [-EFrivwcloqsHbhn] [-ABC NUM] [-m MAX] [-e REGEX]... [-MS PATTERN]... [-f REGFILE] [FILE]...\n\nShow lines matching regular expressions. If no -e, first argument is\nregular expression to match. With no files (or \"-\" filename) read stdin.\nReturns 0 if matched, 1 if no match found, 2 for command errors.\n\n-e  Regex to match. (May be repeated.)\n-f  File listing regular expressions to match.\n\nfile search:\n-r  Recurse into subdirectories (defaults FILE to \".\")\n-R  Recurse into subdirectories and symlinks to directories\n-M  Match filename pattern (--include)\n-S  Skip filename pattern (--exclude)\n--exclude-dir=PATTERN  Skip directory pattern\n-I  Ignore binary files\n\nmatch type:\n-A  Show NUM lines after     -B  Show NUM lines before match\n-C  NUM lines context (A+B)  -E  extended regex syntax\n-F  fixed (literal match)    -a  always text (not binary)\n-i  case insensitive         -m  match MAX many lines\n-v  invert match             -w  whole word (implies -E)\n-x  whole line               -z  input NUL terminated\n\ndisplay modes: (default: matched line)\n-c  count of matching lines  -l  show only matching filenames\n-o  only matching part       -q  quiet (errors only)\n-s  silent (no error msg)    -Z  output NUL terminated\n\noutput prefix (default: filename if checking more than 1 file)\n-H  force filename           -b  byte offset of match\n-h  hide filename            -n  line number of match"
 
 #define HELP_getconf "usage: getconf -a [PATH] | -l | NAME [PATH]\n\nGet system configuration values. Values from pathconf(3) require a path.\n\n-a	Show all (defaults to \"/\" if no path given)\n-l	List available value names (grouped by source)"
 
diff --git a/generated/newtoys.h b/generated/newtoys.h
index a05ac72..d18d293 100644
--- a/generated/newtoys.h
+++ b/generated/newtoys.h
@@ -54,7 +54,7 @@
 USE_DHCP(NEWTOY(dhcp, "V:H:F:x*r:O*A#<0=20T#<0=3t#<0=3s:p:i:SBRCaovqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCP6(NEWTOY(dhcp6, "r:A#<0T#<0t#<0s:p:i:SRvqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
 USE_DHCPD(NEWTOY(dhcpd, ">1P#<0>65535fi:S46[!46]", TOYFLAG_SBIN|TOYFLAG_ROOTONLY))
-USE_DIFF(NEWTOY(diff, "<2>2(color)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_DIFF(NEWTOY(diff, "<2>2(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 USE_DIRNAME(NEWTOY(dirname, "<1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_DMESG(NEWTOY(dmesg, "w(follow)CSTtrs#<1n#c[!Ttr][!Cc][!Sw]", TOYFLAG_BIN))
 USE_DOS2UNIX(NEWTOY(dos2unix, 0, TOYFLAG_BIN))
@@ -89,7 +89,7 @@
 USE_GETENFORCE(NEWTOY(getenforce, ">0", TOYFLAG_USR|TOYFLAG_SBIN))
 USE_GETFATTR(NEWTOY(getfattr, "(only-values)dhn:", 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)rsvwcl(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)))
 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))
@@ -184,7 +184,7 @@
 USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_PATCH(NEWTOY(patch, "(dry-run)"USE_TOYBOX_DEBUG("x")"ulp#d:i:Rs(quiet)", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_PIDOF(NEWTOY(pidof, "<1so:", TOYFLAG_BIN))
+USE_PIDOF(NEWTOY(pidof, "<1so:x", TOYFLAG_BIN))
 USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN))
 USE_PIVOT_ROOT(NEWTOY(pivot_root, "<2>2", TOYFLAG_SBIN))
diff --git a/generated/tags.h b/generated/tags.h
index 0bcfcdd..e1e4d31 100644
--- a/generated/tags.h
+++ b/generated/tags.h
@@ -1,3 +1,17 @@
+#define DD_conv_fsync                                    0
+#define _DD_conv_fsync                                   (1<<0)
+#define DD_conv_noerror                                  1
+#define _DD_conv_noerror                                 (1<<1)
+#define DD_conv_notrunc                                  2
+#define _DD_conv_notrunc                                 (1<<2)
+#define DD_conv_sync                                     3
+#define _DD_conv_sync                                    (1<<3)
+#define DD_iflag_count_bytes                              0
+#define _DD_iflag_count_bytes                             (1<<0)
+#define DD_iflag_skip_bytes                               1
+#define _DD_iflag_skip_bytes                              (1<<1)
+#define DD_oflag_seek_bytes                               0
+#define _DD_oflag_seek_bytes                              (1<<0)
 #define CP_mode                                     0
 #define _CP_mode                                    (1<<0)
 #define CP_ownership                                1
diff --git a/lib/commas.c b/lib/commas.c
index 03b2e34..2267684 100644
--- a/lib/commas.c
+++ b/lib/commas.c
@@ -59,7 +59,7 @@
   return start;
 }
 
-// check all instances of opt and "no"opt in optlist, return true if opt
+// Check all instances of opt and "no"opt in optlist, return true if opt
 // found and last instance wasn't no. If clean, remove each instance from list.
 int comma_scan(char *optlist, char *opt, int clean)
 {
@@ -97,3 +97,23 @@
 
   return i;
 }
+
+// Returns true and removes `opt` from `optlist` if present, false otherwise.
+// Doesn't have the magic "no" behavior of comma_scan.
+int comma_remove(char *optlist, char *opt)
+{
+  int optlen = strlen(opt), len, got = 0;
+
+  if (optlist) for (;;) {
+    char *s = comma_iterate(&optlist, &len);
+
+    if (!s) break;
+    if (optlen == len && !strncmp(opt, s, optlen)) {
+      got = 1;
+      if (optlist) memmove(s, optlist, strlen(optlist)+1);
+      else *s = 0;
+    }
+  }
+
+  return got;
+}
diff --git a/lib/lib.c b/lib/lib.c
index b488407..47e5ca2 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -1041,7 +1041,8 @@
 }
 
 // Execute a callback for each PID that matches a process name from a list.
-void names_to_pid(char **names, int (*callback)(pid_t pid, char *name))
+void names_to_pid(char **names, int (*callback)(pid_t pid, char *name),
+    int scripts)
 {
   DIR *dp;
   struct dirent *entry;
@@ -1050,18 +1051,20 @@
 
   while ((entry = readdir(dp))) {
     unsigned u = atoi(entry->d_name);
-    char *cmd = 0, *comm, **cur;
+    char *cmd = 0, *comm = 0, **cur;
     off_t len;
 
     if (!u) continue;
 
     // Comm is original name of executable (argv[0] could be #! interpreter)
     // but it's limited to 15 characters
-    sprintf(libbuf, "/proc/%u/comm", u);
-    len = sizeof(libbuf);
-    if (!(comm = readfileat(AT_FDCWD, libbuf, libbuf, &len)) || !len)
-      continue;
-    if (libbuf[len-1] == '\n') libbuf[--len] = 0;
+    if (scripts) {
+      sprintf(libbuf, "/proc/%u/comm", u);
+      len = sizeof(libbuf);
+      if (!(comm = readfileat(AT_FDCWD, libbuf, libbuf, &len)) || !len)
+        continue;
+      if (libbuf[len-1] == '\n') libbuf[--len] = 0;
+    }
 
     for (cur = names; *cur; cur++) {
       struct stat st1, st2;
@@ -1071,7 +1074,7 @@
       // Fast path: only matching a filename (no path) that fits in comm.
       // `len` must be 14 or less because with a full 15 bytes we don't
       // know whether the name fit or was truncated.
-      if (len<=14 && bb==*cur && !strcmp(comm, bb)) goto match;
+      if (scripts && len<=14 && bb==*cur && !strcmp(comm, bb)) goto match;
 
       // If we have a path to existing file only match if same inode
       if (bb!=*cur && !stat(*cur, &st1)) {
@@ -1093,7 +1096,7 @@
         cmd[len] = 0;
       }
       if (!strcmp(bb, getbasename(cmd))) goto match;
-      if (bb!=*cur && !strcmp(bb, getbasename(cmd+strlen(cmd)+1))) goto match;
+      if (scripts && !strcmp(bb, getbasename(cmd+strlen(cmd)+1))) goto match;
       continue;
 match:
       if (callback(u, *cur)) break;
diff --git a/lib/lib.h b/lib/lib.h
index 0da3d9d..9c5e9a3 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -45,6 +45,7 @@
 void llist_traverse(void *list, void (*using)(void *node));
 void *llist_pop(void *list);  // actually void **list
 void *dlist_pop(void *list);  // actually struct double_list **list
+void *dlist_lpop(void *list); // also struct double_list **list
 void dlist_add_nomalloc(struct double_list **list, struct double_list *new);
 struct double_list *dlist_add(struct double_list **list, char *data);
 void *dlist_terminate(void *list);
@@ -347,8 +348,10 @@
 void xsetsockopt(int fd, int level, int opt, void *val, socklen_t len);
 struct addrinfo *xgetaddrinfo(char *host, char *port, int family, int socktype,
   int protocol, int flags);
-int xconnect(struct addrinfo *ai);
-int xbind(struct addrinfo *ai);
+void xbind(int fd, const struct sockaddr *sa, socklen_t len);
+void xconnect(int fd, const struct sockaddr *sa, socklen_t len);
+int xconnectany(struct addrinfo *ai);
+int xbindany(struct addrinfo *ai);
 int xpoll(struct pollfd *fds, int nfds, int timeout);
 int pollinate(int in1, int in2, int out1, int out2, int timeout, int shutdown_timeout);
 char *ntop(struct sockaddr *sa);
@@ -365,6 +368,7 @@
 char *comma_iterate(char **list, int *len);
 int comma_scan(char *optlist, char *opt, int clean);
 int comma_scanall(char *optlist, char *scanlist);
+int comma_remove(char *optlist, char *opt);
 
 // deflate.c
 
@@ -397,7 +401,8 @@
 char *getdirname(char *name);
 char *getbasename(char *name);
 char *fileunderdir(char *file, char *dir);
-void names_to_pid(char **names, int (*callback)(pid_t pid, char *name));
+void names_to_pid(char **names, int (*callback)(pid_t pid, char *name),
+    int scripts);
 
 pid_t __attribute__((returns_twice)) xvforkwrap(pid_t pid);
 #define XVFORK() xvforkwrap(vfork())
diff --git a/lib/llist.c b/lib/llist.c
index 2969102..e1e6a56 100644
--- a/lib/llist.c
+++ b/lib/llist.c
@@ -51,6 +51,7 @@
   return (void *)next;
 }
 
+// Remove first item from &list and return it
 void *dlist_pop(void *list)
 {
   struct double_list **pdlist = (struct double_list **)list, *dlist = *pdlist;
@@ -66,6 +67,21 @@
   return dlist;
 }
 
+// remove last item from &list and return it (stack pop)
+void *dlist_lpop(void *list)
+{
+  struct double_list *dl = *(struct double_list **)list;
+  void *v = 0;
+
+  if (dl) {
+    dl = dl->prev;
+    v = dlist_pop(&dl);
+    if (!dl) *(void **)list = 0;
+  }
+
+  return v;
+}
+
 void dlist_add_nomalloc(struct double_list **list, struct double_list *new)
 {
   if (*list) {
diff --git a/lib/net.c b/lib/net.c
index 2bb720a..be69c9a 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -56,17 +56,27 @@
   return fd;
 }
 
-int xconnect(struct addrinfo *ai)
+int xconnectany(struct addrinfo *ai)
 {
   return xconnbind(ai, 0);
 }
 
 
-int xbind(struct addrinfo *ai)
+int xbindany(struct addrinfo *ai)
 {
   return xconnbind(ai, 1);
 }
 
+void xbind(int fd, const struct sockaddr *sa, socklen_t len)
+{
+  if (bind(fd, sa, len)) perror_exit("bind");
+}
+
+void xconnect(int fd, const struct sockaddr *sa, socklen_t len)
+{
+  if (connect(fd, sa, len)) perror_exit("connect");
+}
+
 int xpoll(struct pollfd *fds, int nfds, int timeout)
 {
   int i;
diff --git a/tests/cp.test b/tests/cp.test
index 6c798b4..af59593 100755
--- a/tests/cp.test
+++ b/tests/cp.test
@@ -111,6 +111,9 @@
   "-rw-r--r--\n" "" ""
 rm -rf walrus woot carpenter
 
+# duplicated --preserve= options are fine.
+testing "--preserve=mode,mode" "cp --preserve=mode,mode walrus walrus2" "" "" ""
+
 # cp -r ../source destdir
 # cp -r one/two/three missing
 # cp -r one/two/three two
diff --git a/tests/dd.test b/tests/dd.test
index 3ad15f2..7d7b794 100644
--- a/tests/dd.test
+++ b/tests/dd.test
@@ -92,3 +92,18 @@
 # status=noxfer|none
 testing "status=noxfer" "dd if=input status=noxfer ibs=1 2>&1" "input\n6+0 records in\n0+1 records out\n" "input\n" ""
 testing "status=none" "dd if=input status=none ibs=1 2>&1" "input\n" "input\n" ""
+
+testing "seek stdout" "yes 2> /dev/null | dd bs=8 seek=2 count=1 > out 2> /dev/null && xxd -p out" \
+  "00000000000000000000000000000000790a790a790a790a\n" "" ""
+
+# Duplicated options are fine.
+testing "conv=sync,sync" "dd conv=sync,sync $opt | head -n 1" "I WANT\n" "" "I WANT\n"
+
+# _bytes options
+testing "iflag=count_bytes" \
+  "dd if=input count=2 ibs=4096 iflag=count_bytes $opt" "hi" "high" ""
+testing "iflag=skip_bytes" \
+  "dd if=input skip=2 ibs=4096 iflag=skip_bytes $opt" "gh" "high" ""
+testing "oflag=seek_bytes" \
+  "dd if=input of=output seek=2 obs=4096 oflag=seek_bytes status=none && \
+   xxd -p output" "000030313233\n" "0123" ""
diff --git a/tests/diff.test b/tests/diff.test
index 9847758..f78eaa6 100644
--- a/tests/diff.test
+++ b/tests/diff.test
@@ -33,3 +33,8 @@
 echo food > tree2/file
 
 testing "-r" "diff -r -L tree1/file -L tree2/file tree1 tree2 |tee out" "$expected" "" ""
+
+echo -e "hello\r\nworld\r\n"> a
+echo -e "hello\nworld\n"> b
+testing "--strip-trailing-cr off" "diff -q a b" "Files a and b differ\n" "" ""
+testing "--strip-trailing-cr on" "diff -u --strip-trailing-cr a b" "" "" ""
diff --git a/tests/env.test b/tests/env.test
index 63b9094..00b5654 100755
--- a/tests/env.test
+++ b/tests/env.test
@@ -23,6 +23,8 @@
 testcmd "replace" "A=foo PATH= `which printenv` A" "foo\n" "" ""
 
 # env bypasses shell builtins
-ln -s "$(which echo)" true
+echo "#!$(which sh)
+echo \$@" > true
+chmod a+x true
 testcmd "norecurse" 'env PATH="$PWD:$PATH" true hello' "hello\n" "" ""
 rm true
diff --git a/tests/grep.test b/tests/grep.test
index dee5992..68c8dd8 100755
--- a/tests/grep.test
+++ b/tests/grep.test
@@ -175,3 +175,12 @@
 echo "hello world" > sub/no/test
 testing "--exclude-dir" 'grep --exclude-dir=no -r world sub' "sub/yes/test:hello world\n" "" ""
 rm -rf sub
+
+# -r and -R differ in that -R will dereference symlinks to directories.
+mkdir dir
+echo "hello" > dir/f
+mkdir sub
+ln -s ../dir sub/link
+testing "" "grep -rh hello sub" "" "" ""
+testing "" "grep -Rh hello sub" "hello\n" "" ""
+rm -rf sub real
diff --git a/tests/killall.test b/tests/killall.test
index 40f6cb3..7e171d4 100644
--- a/tests/killall.test
+++ b/tests/killall.test
@@ -5,10 +5,16 @@
 #testing "name" "command" "result" "infile" "stdin"
 
 echo "#!$(which sh)
-yes > /dev/null" > toybox.killall.test.script
+while true; do
+  sleep 0.1
+done" > toybox.killall.test.script
 chmod a+x toybox.killall.test.script
+cp toybox.killall.test.script toybox.test
+
+./toybox.test &
+testing "short name" "killall toybox.test && echo killed ; pgrep -l toybox.test || echo really" "killed\nreally\n" "" ""
 
 ./toybox.killall.test.script &
-testing "script" "killall toybox.killall.test.script && echo killed ; pgrep -l toybox.killall.test.script || echo really" "killed\nreally\n" "" ""
+testing "long name" "killall toybox.killall.test.script && echo killed ; pgrep -l toybox.killall.test.script || echo really" "killed\nreally\n" "" ""
 
-rm -f toybox.killall.test.script
+rm -f toybox.killall.test.script toybox.test
diff --git a/tests/pidof.test b/tests/pidof.test
new file mode 100644
index 0000000..7de31fc
--- /dev/null
+++ b/tests/pidof.test
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+#
+# pidof (unlike killall) doesn't match argv[1] unless you supply -x.
+#
+
+echo "#!$(which sh)
+while true; do
+  sleep 0.1
+done" > toybox.pidof.test.script
+chmod a+x toybox.pidof.test.script
+cp toybox.pidof.test.script pidof.test
+
+./pidof.test &
+pid=$!
+testcmd "short argv[1]" "pidof.test" "" "" ""
+testcmd "short argv[1] -x" "-x pidof.test" "$pid\n" "" ""
+kill $pid
+
+./toybox.pidof.test.script &
+pid=$!
+testcmd "long argv[1]" "toybox.pidof.test.script" "" "" ""
+testcmd "long argv[1] -x" "-x toybox.pidof.test.script" "$pid\n" "" ""
+kill $pid
+
+rm -f toybox.pidof.test.script toybox.test
+
+# pidof (unlike killall) will match itself.
+testcmd "pidof pidof" "pidof > /dev/null && echo found" "found\n" "" ""
diff --git a/toys/lsb/killall.c b/toys/lsb/killall.c
index 119e01f..c81360b 100644
--- a/toys/lsb/killall.c
+++ b/toys/lsb/killall.c
@@ -39,7 +39,7 @@
 
   if (pid == TT.cur_pid) return 0;
 
-  if (toys.optflags & FLAG_i) {
+  if (FLAG(i)) {
     fprintf(stderr, "Signal %s(%d)", name, (int)pid);
     if (!yesno(0)) return 0;
   }
@@ -53,8 +53,8 @@
     } else offset++;
   }
   if (errno) {
-    if (!(toys.optflags & FLAG_q)) perror_msg("pid %d", (int)pid);
-  } else if (toys.optflags & FLAG_v)
+    if (!FLAG(q)) perror_msg("pid %d", (int)pid);
+  } else if (FLAG(v))
     printf("Killed %s(%d) with signal %d\n", name, pid, TT.signum);
 
   return 0;
@@ -67,14 +67,14 @@
   TT.names = toys.optargs;
   TT.signum = SIGTERM;
 
-  if (toys.optflags & FLAG_l) {
+  if (FLAG(l)) {
     list_signals();
     return;
   }
 
   if (TT.s || (*TT.names && **TT.names == '-')) {
     if (0 > (TT.signum = sig_to_num(TT.s ? TT.s : (*TT.names)+1))) {
-      if (toys.optflags & FLAG_q) exit(1);
+      if (FLAG(q)) exit(1);
       error_exit("Invalid signal");
     }
     if (!TT.s) {
@@ -83,13 +83,13 @@
     }
   }
 
-  if (!(toys.optflags & FLAG_l) && !toys.optc) help_exit("no name");
+  if (!toys.optc) help_exit("no name");
 
   TT.cur_pid = getpid();
 
   TT.err = xmalloc(2*toys.optc);
   for (i=0; i<toys.optc; i++) TT.err[i] = ESRCH;
-  names_to_pid(TT.names, kill_process);
+  names_to_pid(TT.names, kill_process, 1);
   for (i=0; i<toys.optc; i++) {
     if (TT.err[i]) {
       toys.exitval = 1;
diff --git a/toys/lsb/pidof.c b/toys/lsb/pidof.c
index 4f266b8..cd705a7 100644
--- a/toys/lsb/pidof.c
+++ b/toys/lsb/pidof.c
@@ -5,7 +5,7 @@
  *
  * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/pidof.html
 
-USE_PIDOF(NEWTOY(pidof, "<1so:", TOYFLAG_BIN))
+USE_PIDOF(NEWTOY(pidof, "<1so:x", TOYFLAG_BIN))
 
 config PIDOF
   bool "pidof"
@@ -17,6 +17,7 @@
 
     -s	Single shot, only return one pid
     -o	Omit PID(s)
+    -x	Match shell scripts too
 */
 
 #define FOR_pidof
@@ -39,6 +40,6 @@
 void pidof_main(void)
 {
   toys.exitval = 1;
-  names_to_pid(toys.optargs, print_pid);
+  names_to_pid(toys.optargs, print_pid, FLAG(x));
   if (!toys.exitval) xputc('\n');
 }
diff --git a/toys/net/ftpget.c b/toys/net/ftpget.c
index ad3c303..05c5350 100644
--- a/toys/net/ftpget.c
+++ b/toys/net/ftpget.c
@@ -105,7 +105,7 @@
   if (!remote) remote = toys.optargs[1];
 
   // connect
-  TT.fd = xconnect(xgetaddrinfo(*toys.optargs, TT.p, 0, SOCK_STREAM, 0,
+  TT.fd = xconnectany(xgetaddrinfo(*toys.optargs, TT.p, 0, SOCK_STREAM, 0,
     AI_ADDRCONFIG));
   if (getpeername(TT.fd, (void *)&si6, &sl)) perror_exit("getpeername");
 
@@ -147,7 +147,7 @@
     if (!s || port<1 || port>65535) error_exit_raw(toybuf);
     si6.sin6_port = SWAP_BE16(port); // same field size/offset for v4 and v6
     port = xsocket(si6.sin6_family, SOCK_STREAM, 0);
-    if (connect(port, (void *)&si6, sizeof(si6))) perror_exit("connect");
+    xconnect(port, (void *)&si6, sizeof(si6));
 
     // RETR blocks until file data read from data port, so use SIZE to check
     // if file exists before creating local copy
diff --git a/toys/net/netcat.c b/toys/net/netcat.c
index 65c41ac..0a235d1 100644
--- a/toys/net/netcat.c
+++ b/toys/net/netcat.c
@@ -111,16 +111,10 @@
         sockaddr.sun_family = AF_UNIX;
 
         sockfd = xsocket(AF_UNIX, type | SOCK_CLOEXEC, 0);
-        if (connect(sockfd, (struct sockaddr*)&sockaddr,
-                    sizeof(sockaddr)) != 0) {
-          perror_exit("could not bind to unix domain socket");
-        }
-
+        xconnect(sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
       } else {
-        struct addrinfo *addr = xgetaddrinfo(toys.optargs[0], toys.optargs[1],
-                                             family, type, 0, 0);
-
-        sockfd = xconnect(addr);
+        sockfd = xconnectany(xgetaddrinfo(toys.optargs[0], toys.optargs[1],
+                                          family, type, 0, 0));
       }
 
       // We have a connection. Disarm timeout.
@@ -145,13 +139,10 @@
         sockaddr.sun_family = AF_UNIX;
 
         sockfd = xsocket(AF_UNIX, type | SOCK_CLOEXEC, 0);
-        if (bind(sockfd, (struct sockaddr*)&sockaddr,
-                 sizeof(struct sockaddr_un)) != 0) {
-          perror_exit("unable to bind to UNIX domain socket");
-        }
+        xbind(sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
       } else {
         sprintf(toybuf, "%ld", TT.p);
-        sockfd = xbind(xgetaddrinfo(TT.s, toybuf, family, type, 0, 0));
+        sockfd = xbindany(xgetaddrinfo(TT.s, toybuf, family, type, 0, 0));
       }
 
       if (listen(sockfd, 5)) error_exit("listen");
diff --git a/toys/net/ping.c b/toys/net/ping.c
index 81dca99..9ae7c85 100644
--- a/toys/net/ping.c
+++ b/toys/net/ping.c
@@ -155,7 +155,7 @@
     }
     xexit();
   }
-  if (TT.I && bind(TT.sock, sa, sizeof(srcaddr))) perror_exit("bind");
+  if (TT.I) xbind(TT.sock, sa, sizeof(srcaddr));
 
   if (toys.optflags&FLAG_m) {
       int mark = TT.m;
diff --git a/toys/net/sntp.c b/toys/net/sntp.c
index b1ecb1b..b1f3685 100644
--- a/toys/net/sntp.c
+++ b/toys/net/sntp.c
@@ -88,7 +88,7 @@
 
   // Act as server if necessary
   if (FLAG(S)|FLAG(m)) {
-    fd = xbind(ai);
+    fd = xbindany(ai);
     if (TT.m) {
       struct ip_mreq group;
 
diff --git a/toys/other/nbd_client.c b/toys/other/nbd_client.c
index fcd0fca..ad3440a 100644
--- a/toys/other/nbd_client.c
+++ b/toys/other/nbd_client.c
@@ -52,7 +52,7 @@
 
     // Find and connect to server
 
-    sock = xconnect(xgetaddrinfo(host, port, AF_UNSPEC, SOCK_STREAM, 0, 0));
+    sock = xconnectany(xgetaddrinfo(host, port, AF_UNSPEC, SOCK_STREAM, 0, 0));
     temp = 1;
     setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &temp, sizeof(int));
 
diff --git a/toys/pending/arping.c b/toys/pending/arping.c
index 6007845..68ee5ea 100644
--- a/toys/pending/arping.c
+++ b/toys/pending/arping.c
@@ -242,15 +242,13 @@
     saddr.sin_family = AF_INET;
     if (src_addr.s_addr) {
       saddr.sin_addr = src_addr;
-      if (bind(p_fd, (struct sockaddr*)&saddr, sizeof(saddr))) 
-        perror_exit("bind");
+      xbind(p_fd, (struct sockaddr*)&saddr, sizeof(saddr));
     } else {
       uint32_t oip;
 
       saddr.sin_port = htons(1025);
       saddr.sin_addr = dest_addr;
-      if (connect(p_fd, (struct sockaddr *) &saddr, sizeof(saddr)))
-        perror_exit("cannot connect to remote host");
+      xconnect(p_fd, (struct sockaddr *) &saddr, sizeof(saddr));
       get_interface(TT.iface, NULL, &oip, NULL);
       src_addr.s_addr = htonl(oip);
     }
@@ -259,8 +257,7 @@
 
   src_pk.sll_family = AF_PACKET;
   src_pk.sll_protocol = htons(ETH_P_ARP);
-  if (bind(TT.sockfd, (struct sockaddr *)&src_pk, sizeof(src_pk))) 
-    perror_exit("bind");
+  xbind(TT.sockfd, (struct sockaddr *)&src_pk, sizeof(src_pk));
 
   socklen_t alen = sizeof(src_pk);
   getsockname(TT.sockfd, (struct sockaddr *)&src_pk, &alen);
diff --git a/toys/pending/bootchartd.c b/toys/pending/bootchartd.c
index 7e5a136..1fe6aff 100644
--- a/toys/pending/bootchartd.c
+++ b/toys/pending/bootchartd.c
@@ -34,24 +34,9 @@
   int proc_accounting;
   int is_login;
 
-  void *head;
+  pid_t cur_pid;
 )
 
-struct pid_list {
-  struct pid_list *next, *prev;
-  int pid;
-};
-
-static int push_pids_in_list(pid_t pid, char *name)
-{
-  struct pid_list *new = xzalloc(sizeof(struct pid_list));
-
-  new->pid = pid;
-  dlist_add_nomalloc((void *)&TT.head, (void *)new);
-
-  return 0;
-}
-
 static void dump_data_in_file(char *fname, int wfd)
 {
   int rfd = open(fname, O_RDONLY);
@@ -253,13 +238,21 @@
   }
 }
 
+static int signal_pid(pid_t pid, char *name)
+{
+  if (pid != TT.cur_pid) kill(pid, SIGUSR1);
+  return 0;
+}
+
 void bootchartd_main()
 {
-  pid_t lgr_pid, self_pid = getpid();
+  pid_t lgr_pid;
   int bchartd_opt = 0; // 0=PID1, 1=start, 2=stop, 3=init
+
+  TT.cur_pid = getpid();
   TT.smpl_period_usec = 200 * 1000;
 
-  TT.is_login = (self_pid == 1);
+  TT.is_login = (TT.cur_pid == 1);
   if (*toys.optargs) {
     if (!strcmp("start", *toys.optargs)) bchartd_opt = 1;
     else if (!strcmp("stop", *toys.optargs)) bchartd_opt = 2;
@@ -267,16 +260,9 @@
     else error_exit("Unknown option '%s'", *toys.optargs);
 
     if (bchartd_opt == 2) {
-      struct pid_list *temp;
       char *process_name[] = {"bootchartd", NULL};
 
-      names_to_pid(process_name, push_pids_in_list);
-      temp = TT.head;
-      if (temp) temp->prev->next = 0;
-      for (; temp; temp = temp->next) 
-        if (temp->pid != self_pid) kill(temp->pid, SIGUSR1);
-      llist_traverse(TT.head, free);
-
+      names_to_pid(process_name, signal_pid, 0);
       return;
     }
   } else if (!TT.is_login) error_exit("not PID 1");
diff --git a/toys/pending/dd.c b/toys/pending/dd.c
index e37f8b2..80a7595 100644
--- a/toys/pending/dd.c
+++ b/toys/pending/dd.c
@@ -4,8 +4,6 @@
  * Copyright 2013 Kyungwan Han <asura321@gmail.com>
  *
  * See  http://opengroup.org/onlinepubs/9699919799/utilities/dd.html
- *
- * todo: ctrl-c doesn't work, the read() is restarting.
 
 USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN))
 
@@ -13,19 +11,22 @@
   bool "dd"
   default n
   help
-    usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [bs=N] [count=N] [skip=N]
-            [seek=N] [conv=notrunc|noerror|sync|fsync] [status=noxfer|none]
+    usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [iflag=FLAGS] [oflag=FLAGS]
+            [bs=N] [count=N] [seek=N] [skip=N]
+            [conv=notrunc|noerror|sync|fsync] [status=noxfer|none]
 
     Copy/convert files.
 
     if=FILE		Read from FILE instead of stdin
     of=FILE		Write to FILE instead of stdout
     bs=N		Read and write N bytes at a time
-    ibs=N		Read N bytes at a time
-    obs=N		Write N bytes at a time
+    ibs=N		Input block size
+    obs=N		Output block size
     count=N		Copy only N input blocks
     skip=N		Skip N input blocks
     seek=N		Skip N output blocks
+    iflag=FLAGS	Set input flags
+    oflag=FLAGS	Set output flags
     conv=notrunc	Don't truncate output file
     conv=noerror	Continue after read errors
     conv=sync	Pad blocks with zeros
@@ -33,6 +34,12 @@
     status=noxfer	Don't show transfer rate
     status=none	Don't show transfer rate or records in/out
 
+    FLAGS is a comma-separated list of:
+
+    count_bytes	(iflag) interpret count=N in bytes, not blocks
+    seek_bytes	(oflag) interpret seek=N in bytes, not blocks
+    skip_bytes	(iflag) interpret skip=N in bytes, not blocks
+
     Numbers may be suffixed by c (*1), w (*2), b (*512), kD (*1000), k (*1024),
     MD (*1000*1000), M (*1024*1024), GD (*1000*1000*1000) or G (*1024*1024*1024).
 */
@@ -51,12 +58,24 @@
     long sz, count;
     unsigned long long offset;
   } in, out;
+  unsigned conv, iflag, oflag;
 );
 
-#define C_FSYNC   1
-#define C_NOERROR 2
-#define C_NOTRUNC 4
-#define C_SYNC    8
+struct dd_flag {
+  char *name;
+};
+
+static const struct dd_flag dd_conv[] = TAGGED_ARRAY(DD_conv,
+  {"fsync"}, {"noerror"}, {"notrunc"}, {"sync"},
+);
+
+static const struct dd_flag dd_iflag[] = TAGGED_ARRAY(DD_iflag,
+  {"count_bytes"}, {"skip_bytes"},
+);
+
+static const struct dd_flag dd_oflag[] = TAGGED_ARRAY(DD_oflag,
+  {"seek_bytes"},
+);
 
 static void status()
 {
@@ -79,6 +98,12 @@
   }
 }
 
+static void dd_sigint(int sig) {
+  status();
+  toys.exitval = sig|128;
+  xexit();
+}
+
 static void write_out(int all)
 {
   TT.out.bp = TT.out.buff;
@@ -97,18 +122,24 @@
   if (TT.out.count) memmove(TT.out.buff, TT.out.bp, TT.out.count); //move remainder to front
 }
 
-int strstarteq(char **a, char *b)
+static void parse_flags(char *what, char *arg,
+    const struct dd_flag* flags, int flag_count, unsigned *result)
 {
-  char *aa = *a;
+  char *pre = xstrdup(arg);
+  int i;
 
-  return strstart(&aa, b) && *aa == '=' && (*a = aa+1);
+  for (i=0; i<flag_count; ++i) {
+    while (comma_remove(pre, flags[i].name)) *result |= 1<<i;
+  }
+  if (*pre) error_exit("bad %s=%s", what, pre);
+  free(pre);
 }
 
 void dd_main()
 {
   char **args;
   unsigned long long bs = 0;
-  int trunc = O_TRUNC, conv = 0;
+  int trunc = O_TRUNC;
 
   TT.show_xfer = TT.show_records = 1;
   TT.c_count = ULLONG_MAX;
@@ -117,51 +148,46 @@
   for (args = toys.optargs; *args; args++) {
     char *arg = *args;
 
-    if (strstarteq(&arg, "bs")) bs = atolx_range(arg, 1, LONG_MAX);
-    else if (strstarteq(&arg, "ibs")) TT.in.sz = atolx_range(arg, 1, LONG_MAX);
-    else if (strstarteq(&arg, "obs")) TT.out.sz = atolx_range(arg, 1, LONG_MAX);
-    else if (strstarteq(&arg, "count"))
+    if (strstart(&arg, "bs=")) bs = atolx_range(arg, 1, LONG_MAX);
+    else if (strstart(&arg, "ibs=")) TT.in.sz = atolx_range(arg, 1, LONG_MAX);
+    else if (strstart(&arg, "obs=")) TT.out.sz = atolx_range(arg, 1, LONG_MAX);
+    else if (strstart(&arg, "count="))
       TT.c_count = atolx_range(arg, 0, LLONG_MAX);
-    else if (strstarteq(&arg, "if")) TT.in.name = arg;
-    else if (strstarteq(&arg, "of")) TT.out.name = arg;
-    else if (strstarteq(&arg, "seek"))
+    else if (strstart(&arg, "if=")) TT.in.name = arg;
+    else if (strstart(&arg, "of=")) TT.out.name = arg;
+    else if (strstart(&arg, "seek="))
       TT.out.offset = atolx_range(arg, 0, LLONG_MAX);
-    else if (strstarteq(&arg, "skip"))
+    else if (strstart(&arg, "skip="))
       TT.in.offset = atolx_range(arg, 0, LLONG_MAX);
-    else if (strstarteq(&arg, "status")) {
+    else if (strstart(&arg, "status=")) {
       if (!strcmp(arg, "noxfer")) TT.show_xfer = 0;
       else if (!strcmp(arg, "none")) TT.show_xfer = TT.show_records = 0;
       else error_exit("unknown status '%s'", arg);
-    } else if (strstarteq(&arg, "conv")) {
-      char *ss, *convs[] = {"fsync", "noerror", "notrunc", "sync"};
-      int i, len;
-
-      while ((ss = comma_iterate(&arg, &len))) {
-        for (i = 0; i<ARRAY_LEN(convs); i++)
-          if (len == strlen(convs[i]) && !strncmp(ss, convs[i], len)) break;
-        if (i == ARRAY_LEN(convs)) error_exit("bad conv=%.*s", len, ss);
-        conv |= 1<<i;
-      }
-    } else error_exit("bad arg %s", arg);
+    } else if (strstart(&arg, "conv=")) {
+      parse_flags("conv", arg, dd_conv, ARRAY_LEN(dd_conv), &TT.conv);
+      fprintf(stderr, "conv=%x\n", TT.conv);
+    } else if (strstart(&arg, "iflag="))
+      parse_flags("iflag", arg, dd_iflag, ARRAY_LEN(dd_iflag), &TT.iflag);
+    else if (strstart(&arg, "oflag="))
+      parse_flags("oflag", arg, dd_oflag, ARRAY_LEN(dd_oflag), &TT.oflag);
+    else error_exit("bad arg %s", arg);
   }
   if (bs) TT.in.sz = TT.out.sz = bs;
 
-  signal(SIGINT, generic_signal);
+  signal(SIGINT, dd_sigint);
   signal(SIGUSR1, generic_signal);
   gettimeofday(&TT.start, NULL);
 
-  /* for bs=, in/out is done as it is. so only in.sz is enough.
-   * With Single buffer there will be overflow in a read following partial read
-   */
+  // For bs=, in/out is done as it is. so only in.sz is enough.
+  // With Single buffer there will be overflow in a read following partial read.
   TT.in.buff = TT.out.buff = xmalloc(TT.in.sz + (bs ? 0 : TT.out.sz));
   TT.in.bp = TT.out.bp = TT.in.buff;
-  //setup input
+
   if (!TT.in.name) TT.in.name = "stdin";
   else TT.in.fd = xopenro(TT.in.name);
 
-  if (conv&C_NOTRUNC) trunc = 0;
+  if (TT.conv & _DD_conv_notrunc) trunc = 0;
 
-  //setup output
   if (!TT.out.name) {
     TT.out.name = "stdout";
     TT.out.fd = 1;
@@ -170,30 +196,43 @@
 
   // Implement skip=
   if (TT.in.offset) {
-    if (lseek(TT.in.fd, (off_t)(TT.in.offset * TT.in.sz), SEEK_CUR) < 0) {
-      while (TT.in.offset--) {
-        ssize_t n = read(TT.in.fd, TT.in.bp, TT.in.sz);
+    off_t off = TT.in.offset;
+
+    if (!(TT.iflag & _DD_iflag_skip_bytes)) off *= TT.in.sz;
+    if (lseek(TT.in.fd, off, SEEK_CUR) < 0) {
+      while (off > 0) {
+        int chunk = off < TT.in.sz ? off : TT.in.sz;
+        ssize_t n = read(TT.in.fd, TT.in.bp, chunk);
 
         if (n < 0) {
           perror_msg("%s", TT.in.name);
-          if (conv&C_NOERROR) status();
+          if (TT.conv & _DD_conv_noerror) status();
           else return;
         } else if (!n) {
           xprintf("%s: Can't skip\n", TT.in.name);
           return;
         }
+        off -= chunk;
       }
     }
   }
 
-  // seek/truncate as necessary. We handled position zero truncate with
-  // O_TRUNC on open, so output to /dev/null and such doesn't error.
-  if (TT.out.fd!=1 && (bs = TT.out.offset*TT.out.sz)) {
+  // Implement seek= and truncate as necessary. We handled position zero
+  // truncate with O_TRUNC on open, so output to /dev/null and such doesn't
+  // error.
+  bs = TT.out.offset;
+  if (!(TT.oflag & _DD_oflag_seek_bytes)) bs *= TT.out.sz;
+  if (bs) {
     xlseek(TT.out.fd, bs, SEEK_CUR);
     if (trunc && ftruncate(TT.out.fd, bs)) perror_exit("ftruncate");
   }
 
-  while (TT.c_count==ULLONG_MAX || (TT.in_full + TT.in_part) < TT.c_count) {
+  unsigned long long bytes_left = TT.c_count;
+  if (TT.c_count != ULLONG_MAX && !(TT.iflag & _DD_iflag_count_bytes)) {
+    bytes_left *= TT.in.sz;
+  }
+  while (bytes_left) {
+    int chunk = bytes_left < TT.in.sz ? bytes_left : TT.in.sz;
     ssize_t n;
 
     // Show progress and exit on SIGINT or just continue on SIGUSR1.
@@ -204,16 +243,16 @@
     }
 
     TT.in.bp = TT.in.buff + TT.in.count;
-    if (conv&C_SYNC) memset(TT.in.bp, 0, TT.in.sz);
-    if (!(n = read(TT.in.fd, TT.in.bp, TT.in.sz))) break;
-    if (n < 0) { 
+    if (TT.conv & _DD_conv_sync) memset(TT.in.bp, 0, TT.in.sz);
+    if (!(n = read(TT.in.fd, TT.in.bp, chunk))) break;
+    if (n < 0) {
       if (errno == EINTR) continue;
       //read error case.
       perror_msg("%s: read error", TT.in.name);
-      if (!(conv&C_NOERROR)) exit(1);
+      if (!(TT.conv & _DD_conv_noerror)) exit(1);
       status();
       xlseek(TT.in.fd, TT.in.sz, SEEK_CUR);
-      if (!(conv&C_SYNC)) continue;
+      if (!(TT.conv & _DD_conv_sync)) continue;
       // if SYNC, then treat as full block of nuls
       n = TT.in.sz;
     }
@@ -222,9 +261,10 @@
       TT.in.count += n;
     } else {
       TT.in_part++;
-      if (conv&C_SYNC) TT.in.count += TT.in.sz;
+      if (TT.conv & _DD_conv_sync) TT.in.count += TT.in.sz;
       else TT.in.count += n;
     }
+    bytes_left -= n;
 
     TT.out.count = TT.in.count;
     if (bs) {
@@ -239,7 +279,7 @@
     }
   }
   if (TT.out.count) write_out(1); //write any remaining input blocks
-  if ((conv&C_FSYNC) && fsync(TT.out.fd)<0)
+  if ((TT.conv & _DD_conv_fsync) && fsync(TT.out.fd)<0)
     perror_exit("%s: fsync", TT.out.name);
 
   close(TT.in.fd);
diff --git a/toys/pending/dhcp6.c b/toys/pending/dhcp6.c
index c69c4ae..728dc7d 100644
--- a/toys/pending/dhcp6.c
+++ b/toys/pending/dhcp6.c
@@ -249,10 +249,7 @@
   sockll.sll_family = AF_PACKET;
   sockll.sll_protocol = htons(ETH_P_IPV6);
   sockll.sll_ifindex = if_nametoindex(TT.interface_name);
-  if (bind(TT.sock, (struct sockaddr *) &sockll, sizeof(sockll))) {
-    xclose(TT.sock);
-    error_exit("MODE RAW : Bind fail.\n");
-  } 
+  xbind(TT.sock, (struct sockaddr *) &sockll, sizeof(sockll));
   if (setsockopt(TT.sock, SOL_PACKET, PACKET_HOST,&constone, sizeof(int)) < 0) {
     if (errno != ENOPROTOOPT) error_exit("MODE RAW : Bind fail.\n");
   }
@@ -575,10 +572,7 @@
   
   xsetsockopt(TT.sock1, SOL_SOCKET, SO_REUSEADDR, &constone, sizeof(constone));
   
-  if (bind(TT.sock1, (struct sockaddr *)&sinaddr6, sizeof(sinaddr6))) {
-    xclose(TT.sock1);
-    error_exit("bind failed");
-  }
+  xbind(TT.sock1, (struct sockaddr *)&sinaddr6, sizeof(sinaddr6));
   
   mode_raw();
   set_timeout(0);
diff --git a/toys/pending/diff.c b/toys/pending/diff.c
index d865e8d..2d13d97 100644
--- a/toys/pending/diff.c
+++ b/toys/pending/diff.c
@@ -5,7 +5,7 @@
  *
  * See: http://cm.bell-labs.com/cm/cs/cstr/41.pdf
 
-USE_DIFF(NEWTOY(diff, "<2>2(color)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_DIFF(NEWTOY(diff, "<2>2(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)L(label)*S(starting-file):N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 
 config DIFF
   bool "diff"
@@ -13,23 +13,25 @@
   help
   usage: diff [-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2
 
-  -a  Treat all files as text
-  -b  Ignore changes in the amount of whitespace
-  -B  Ignore changes whose lines are all blank
-  -d  Try hard to find a smaller set of changes
-  -i  Ignore case differences
-  -L  Use LABEL instead of the filename in the unified header
-  -N  Treat absent files as empty
-  -q  Output only whether files differ
-  -r  Recurse
-  -S  Start with FILE when comparing directories
-  -T  Make tabs line up by prefixing a tab when necessary
-  -s  Report when two files are the same
-  -t  Expand tabs to spaces in output
-  -U  Output LINES lines of context
-  -w  Ignore all whitespace
+  -a	Treat all files as text
+  -b	Ignore changes in the amount of whitespace
+  -B	Ignore changes whose lines are all blank
+  -d	Try hard to find a smaller set of changes
+  -i	Ignore case differences
+  -L	Use LABEL instead of the filename in the unified header
+  -N	Treat absent files as empty
+  -q	Output only whether files differ
+  -r	Recurse
+  -S	Start with FILE when comparing directories
+  -T	Make tabs line up by prefixing a tab when necessary
+  -s	Report when two files are the same
+  -t	Expand tabs to spaces in output
+  -u	Unified diff
+  -U	Output LINES lines of context
+  -w	Ignore all whitespace
 
-  --color  Colored output
+  --color              Colored output
+  --strip-trailing-cr  Strip trailing '\r's from input lines
 */
 
 #define FOR_diff
@@ -196,8 +198,18 @@
 
   tok |= empty;
   while (!(tok & eol)) {
-
     t = fgetc(fp);
+
+    if (FLAG(strip_trailing_cr) && t == '\r') {
+      int t2 = fgetc(fp);
+      if (t2 == '\n') {
+        t = t2;
+        if (off) (*off)++;
+      } else {
+        ungetc(t2, fp);
+      }
+    }
+
     if (off && t != EOF) *off += 1;
     is_space = isspace(t) || (t == EOF);
     tok |= (t & (eof + eol)); //set tok eof+eol when t is eof
diff --git a/toys/pending/host.c b/toys/pending/host.c
index fa830a7..fe0f23a 100644
--- a/toys/pending/host.c
+++ b/toys/pending/host.c
@@ -121,9 +121,8 @@
 
     if ((ret = getaddrinfo(nsname, "53", &ns_hints, &ai)) < 0)
       error_exit("Error looking up server name: %s", gai_strerror(ret));
-    int s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-    if (s < 0 || connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
-      perror_exit("Socket error");
+    int s = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+    xconnect(s, ai->ai_addr, ai->ai_addrlen);
     setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &(struct timeval){ .tv_sec = 5 },
       sizeof(struct timeval));
     printf("Using domain server %s:\n", nsname);
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index e8a11ce..1950438 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -212,22 +212,32 @@
   if (*toys.optargs) xexit();
 }
 
+// return length of match found at this point
+static int anyof(char *s, char **try)
+{
+  while (*try) {
+    if (strstart(&s, *try)) return strlen(*try);
+    try++;
+  }
 
-// Parse one word from the command line, appending one or more argv[] entries
-// to struct command.  Handles environment variable substitution and
-// substrings.  Returns pointer to next used byte, or NULL if it
-// hit an ending token.
-
-// caller eats leading spaces
+  return 0;
+}
 
 // parse next word from command line. Returns end, or 0 if need continuation
+// caller eats leading spaces
 static char *parse_word(char *start)
 {
   int i, quote = 0;
   char *end = start, *s;
 
-  // find end of string
+  // Skip leading whitespace/comment
+  for (;;) {
+    if (isspace(*start)) ++start;
+    else if (*start=='#') while (*start && *start != '\n') ++start;
+    else break;
+  }
 
+  // find end of this word
   while (*end) {
     i = 0;
 
@@ -243,11 +253,20 @@
       // start quote
       if (strchr("\"'`", *end)) toybuf[quote++] = *end++;
       else if (strstart(&end, "<(") || strstart(&end,">(")) toybuf[quote++]=')';
-      else if (*end==')') return end+(end==start);
       else {
-        // control chars
-        for (s = end; strchr(";|&<>(", *s); s++);
-        if (s != end) return (end == start) ? s : end;
+        // control chars.
+        // 123<<file- parses as 2 args: "123<<" "file-".
+        // note: >&; becomes ">&" ";" because first loop, then second loop.
+        s = end;
+        if (*s == '{') s++;
+        for (s = end; isdigit(*s); s++);
+        if (*end == '{' && *s == '}') s++;
+        s += anyof(s, (char *[]){"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
+          ">&", ">|", ">", 0});
+        if (s == end || isdigit(s[-1]))
+          s += anyof(s, (char *[]){";;&", ";;", ";&", ";", "||", "|&", "|",
+            "&&", "&>>", "&>", "&", "(", ")", 0});
+        if (s != end && !isdigit(*s)) return (end == start) ? s : end;
         i++;
       }
     }
@@ -259,7 +278,7 @@
 
     // backslash escapes
     if (*end == '\\') {
-      if (!end[1]) return 0;
+      if (!end[1] || (end[1]=='\n' && !end[2])) return 0;
       end += 2;
     } else if (*end == '$') {
       // barf if we're near overloading quote stack (nesting ridiculously deep)
@@ -286,228 +305,359 @@
   return quote ? 0 : end;
 }
 
-// Consume a line of shell script and do what it says. Returns 0 if finished,
-// pointer to start of unused part of line if it needs another line of input.
-static char *parse_line(char *line, struct double_list **pipeline)
+// Parse flow control statement(s), returns index of first statement to execute,
+// pp->arg->c if none, -1 if we need to flush due to syntax error
+int flow_control(int why, struct sh_arg *arg, struct double_list **expect,
+  char **end)
 {
-  char *start = line, *end, *s, *ex, *add;
-  struct sh_arg *arg = 0;
-  struct double_list *pl, *expect = 0;
-  unsigned i, paren = 0;
+  char *add = 0;
+  int i, pend = 0;
 
-  // Resume appending to last pipeline's last argument list
-  if (*pipeline) arg = (void *)(*pipeline)->prev->data;
-  if (arg) for (i = 0; i<arg->c; i++) {
-    if (!strcmp(arg->v[i], "(")) paren++;
-    else if (!strcmp(arg->v[i], ")")) paren--;
+  // Blank line shouldn't change end, but two ends in a row are an error
+  if (!arg->c) {
+    if (arg->v[0]) {
+      syntax_err("bad %s", arg->v[0]);
+      return -1;
+    }
+    return 0;
   }
 
-  // Loop handling each word
-  for (;;) {
-    // Skip leading whitespace/comment
-    while (isspace(*start)) ++start;
-    if (*start=='#') {
-      while (*start && *start != '\n') start++;
-      continue;
+  // parse flow control statements in this command line
+  for (i = 0; ; i++) {
+    char *ex = *expect ? (*expect)->prev->data : 0, *s = arg->v[i];
+
+    // push word to expect at end of block, and expect a command first
+    if (add) {
+      dlist_add(expect, add);                  // end of context
+      if (why) dlist_add(expect, arg->v[i-1]); // context for command
+      dlist_add(expect, add = 0);              // expect a command
     }
 
-    // Parse next word and detect continuation/overflow.
-    if ((end = parse_word(start)) == (void *)1) return 0;
-    if (!end) return start;
+    // end of argument list?
+    if (i == arg->c) break;
 
-    // Extend pipeline and argv[], handle EOL
+    // When waiting for { it must be next symbol, but can be on a new line.
+    if (ex && !strcmp(ex, "{")) {
+      if (strcmp(s, "{") || (!i && *end && strcmp(*end, ";"))) {
+        syntax_err("need {");
+        return -1;
+      }
+    }
+
+    if (!strcmp(s, "if")) add = "then";
+    else if (!strcmp(s, "for") || !strcmp(s, "select")
+        || !strcmp(s, "while") || !strcmp(s, "until")) add = "do";
+    else if (!strcmp(s, "case")) add = "esac";
+    else if (!strcmp(s, "{")) add = "}";
+    else if (!strcmp(s, "[[")) add = "]]";
+    else if (!strcmp(s, "(")) add = ")";
+
+    // function NAME () [nl] { [nl] body ; }
+    // Why can you to declare functions inside other functions?
+    else if (arg->c>i+1 && !strcmp(arg->v[i+1], "(")) goto funky;
+    else if (!strcmp(s, "function")) {
+      i++;
+funky:
+      // At this point we can only have a function: barf if it's invalid
+      if (arg->c<i+3 || !strcmp(arg->v[i+1], "(") || !strcmp(arg->v[i+2], ")")){
+        syntax_err("bad function ()");
+        return -1;
+      }
+      // perform abnormal add (one extra piece of info) manually.
+      dlist_add(expect, "}");
+      dlist_add(expect, "function");
+      dlist_add(expect, 0);
+      dlist_add(expect, "{");
+
+      continue;
+
+    // Expecting NULL means a statement: any otherwise unrecognized word
+    } else if (expect && !ex) {
+      free(dlist_pop(expect));
+
+      // if (why) context in which statement executes now at top of expect stack
+
+      // Does this statement end with a close parentheses?
+      if (!strcmp(")", arg->v[arg->c-1])) {
+
+        // Did we expect one?
+        if (!*expect || !strcmp(")", (*expect)->prev->data)) {
+          syntax_err("bad %s", ")");
+          return -1;
+        }
+
+        free(dlist_pop(expect));
+        // only need one statement in ( ( ( echo ) ) )
+        if (*expect && !(*expect)->prev->data) free(dlist_pop(expect));
+
+        pend++;
+        goto gotparen;
+      }
+      break;
+
+    // If we aren't expecting and didn't just start a new flow control block,
+    // rest of statement is a command and arguments, so stop now
+    } else if (!ex) break;
+
+    if (add) continue;
+
+    // If we got here we expect a specific word to end this block: is this it?
+    if (!strcmp(arg->v[i], ex)
+      || (!strcmp(ex, ")") && !strcmp(ex, arg->v[arg->c-1])))
+    {
+      // can't "if | then" or "while && do", only ; & or newline works
+      if (*end && strcmp(*end, ";") && strcmp(*end, "&")) {
+        syntax_err("bad %s", *end);
+        return -1;
+      }
+
+gotparen:
+      free(dlist_pop(expect));
+      // Only innermost statement needed in { { { echo ;} ;} ;} and such
+      if (*expect && !(*expect)->prev->data) free(dlist_pop(expect));
+
+      // If this was a command ending in parentheses
+      if (pend) break;
+
+      // if it's a multipart block, what comes next?
+      if (!strcmp(s, "do")) ex = "done";
+      else if (!strcmp(s, "then")) add = "fi\0A";
+    // fi could have elif, which queues a then.
+    } else if (!strcmp(ex, "fi")) {
+      if (!strcmp(s, "elif")) {
+        free(dlist_pop(expect));
+        add = "then";
+      // catch duplicate else while we're here
+      } else if (!strcmp(s, "else")) {
+        if (ex[3] != 'A') {
+          syntax_err("2 else");
+          return -1;
+        }
+        free(dlist_pop(expect));
+        add = "fi\0B";
+      }
+    }
+  }
+
+  // Record how the previous stanza ended: ; | & ;; || && ;& ;;& |& NULL
+  *end = arg->v[arg->c];
+
+  return i;
+}
+
+// Consume a line of shell script and do what it says. Returns 0 if finished,
+// 1 to request another line of input.
+
+struct sh_parse {
+  struct double_list *pipeline, *plstart, *expect, *here;
+  char *end;
+};
+
+// pipeline and expect are scratch space, state held between calls which
+// I don't want to make global yet because this could be reentrant.
+// returns 1 to request another line (> prompt), 0 if line consumed.
+static int parse_line(char *line, struct sh_parse *sp)
+{
+  char *start = line, *delete = 0, *end, *s;
+  struct sh_arg *arg = 0;
+  struct double_list *pl;
+  long i;
+
+  // Resume appending to last statement?
+  if (sp->pipeline) {
+    arg = (void *)sp->pipeline->prev->data;
+
+    // Extend/resume quoted block
+    if (arg->c<0) {
+      start = delete = xmprintf("%s%s", arg->v[arg->c = (-arg->c)-1], start);
+      free(arg->v[arg->c]);
+      arg->v[arg->c] = 0;
+
+    // is a HERE document in progress?
+    } else if (sp->here && ((struct sh_arg *)sp->here->data)->c<0) {
+      unsigned long c;
+
+      arg = (void *)sp->here->data;
+      c = -arg->c - 1;
+
+      // HERE's arg->c < 0 means still adding to it, EOF string is last entry
+      if (!(31&c)) arg->v = xrealloc(arg->v, (32+c)*sizeof(void *));
+      if (strcmp(line, arg->v[c])) {
+        // Add this line
+        arg->v[c+1] = arg->v[c];
+        arg->v[c] = xstrdup(line);
+        arg->c--;
+      } else {
+        // EOF hit, end HERE document
+        arg->v[arg->c = c] = 0;
+        sp->here = sp->here->next;
+      }
+      start = 0;
+    }
+  }
+
+  // Parse words, assemble argv[] pipelines, check flow control and HERE docs
+  if (start) for (;;) {
+    s = 0;
+
+    // Parse next word and detect overflow (too many nested quotes).
+    if ((end = parse_word(start)) == (void *)1) goto flush;
+
+    // Extend pipeline and argv[] to store result
     if (!arg)
-      dlist_add(pipeline, (void *)(arg = xzalloc(sizeof(struct sh_arg))));
+      dlist_add(&sp->pipeline, (void *)(arg = xzalloc(sizeof(struct sh_arg))));
     if (!(31&arg->c)) arg->v = xrealloc(arg->v, (32+arg->c)*sizeof(void *));
+
+    // Do we need to request another line to finish word (find ending quote)?
+    if (!end) {
+      // Save unparsed bit of this line, we'll need to re-parse it.
+      arg->v[arg->c] = xstrndup(start, strlen(start));
+      arg->c = -(arg->c+1);
+      free(delete);
+
+      return 1;
+    }
+
+    // Did we hit the end of this line of input?
     if (end == start) {
       arg->v[arg->c] = 0;
+
+      // Parse flow control data from last statement
+      if (-1 == flow_control(0, arg, &sp->expect, &sp->end)) goto flush;
+
+      // Grab HERE document(s)
+      for (pl = sp->plstart ? sp->plstart : sp->pipeline; pl;
+           pl = (pl->next == sp->pipeline) ? 0 : pl->next)
+      {
+        struct sh_arg *here;
+
+        arg = (void *)pl->data;
+
+        for (i = 0; i<arg->c; i++) {
+          // find [n]<<[-] with an argument after it
+          s = arg->v[i];
+          if (*s == '{') s++;
+          while (isdigit(*s)) s++;
+          if (*arg->v[i] == '{' && *s == '}') s++;
+          if (strcmp(s, "<<") && strcmp(s, "<<-")) continue;
+          if (i+1 == arg->c) goto flush;
+
+          here = xzalloc(sizeof(struct sh_arg));
+          here->v = xzalloc(32*sizeof(void *));
+          *here->v = arg->v[++i];
+          here->c = -1;
+        }
+      }
+
+      // Stop reading.
       break;
     }
 
-    // Save argument (strdup) and check if it's special
-    s = arg->v[arg->c] = xstrndup(start, end-start);
-    if (!strcmp(s, "(")) paren++;
-    else if (!strcmp(s, ")") && !paren--) syntax_err("bad %s", s);
-    if (paren || !strchr(";|&", *start)) arg->c++;
-    else {
-      if (!arg->c) {
-        syntax_err("bad %s", arg->v[arg->c]);
-        goto flush;
-      }
+    // ) only saved at start of a statement, else ends statement with NULL
+    if (arg->c && *start == ')') {
+      arg->v[arg->c] = 0;
+      end--;
+      if (-1 == flow_control(0, arg, &sp->expect, &sp->end)) goto flush;
       arg = 0;
+    } else {
+      // Save argument (strdup) and check if it's special
+      s = arg->v[arg->c] = xstrndup(start, end-start);
+      if (!strchr(");|&", *start)) arg->c++;
+      else {
+        // end of statement due to flow control character.
+        s = 0;
+        if (!arg->c) goto flush;
+        if (-1 == flow_control(0, arg, &sp->expect, &sp->end)) goto flush;
+        arg = 0;
+      }
     }
     start = end;
   }
+  free(delete);
 
-  // We parsed to the end of the line, which ended a pipeline.
-  // Now handle flow control commands, which can also need more lines.
+  // return if HERE document or more flow control
+  if (sp->expect || (sp->pipeline && sp->pipeline->prev->data==(void *)1))
+    return 1;
 
-  // array of command lines separated by | and such
-  // Note: don't preparse past ; because environment variables differ
+  // At this point, we've don't need more input and can start executing.
 
-  // Check for flow control continuations
-  end = 0;
-  for (pl = *pipeline; pl ; pl = (pl->next == *pipeline) ? 0 : pl->next) {
-    arg = (void *)pl->data;
-    if (!arg->c) continue;
-    add = 0;
+  // **************************** do the thing *******************************
 
-    // parse flow control statements in this command line
-    for (i = 0; ; i++) {
-      ex = expect ? expect->prev->data : 0;
-      s = arg->v[i];
-
-      // push word to expect to end this block, and expect a command first
-      if (add) {
-        dlist_add(&expect, add);
-        dlist_add(&expect, add = 0);
-      }
-
-      // end of statement?
-      if (i == arg->c) break;
-
-      // When waiting for { it must be next symbol, but can be on a new line.
-      if (ex && !strcmp(ex, "{") && (strcmp(s, "{") || (!i && end))) {
-        syntax_err("need {");
-        goto flush;
-      }
-
-      if (!strcmp(s, "if")) add = "then";
-      else if (!strcmp(s, "for") || !strcmp(s, "select")
-          || !strcmp(s, "while") || !strcmp(s, "until")) add = "do";
-      else if (!strcmp(s, "case")) add = "esac";
-      else if (!strcmp(s, "{")) add = "}";
-      else if (!strcmp(s, "[[")) add = "]]";
-
-      // function NAME () [nl] { [nl] body ; }
-      // Why can you to declare functions inside other functions?
-      else if (arg->c>i+1 && !strcmp(arg->v[i+1], "(")) goto funky;
-      else if (!strcmp(s, "function")) {
-        i++;
-funky:
-        // At this point we can only have a function: barf if it's invalid
-        if (arg->c<i+3 || !strcmp(arg->v[i+1], "(")
-            || !strcmp(arg->v[i+2], ")"))
-        {
-          syntax_err("bad function ()");
-          goto flush;
-        }
-        dlist_add(&expect, "}");
-        dlist_add(&expect, 0);
-        dlist_add(&expect, "{");
-
-      // Expecting NULL will take any otherwise unrecognized word
-      } else if (expect && !ex) {
-        free(dlist_pop(&expect));
-        continue;
-
-      // If we expect nothing and didn't just start a new flow control block,
-      // rest of statement is a command and arguments, so stop now
-      } else if (!ex) break;
-
-      if (add) continue;
-
-      // If we got here we expect a word to end this block: is this it?
-      if (!strcmp(arg->v[i], ex)) {
-        free(dlist_pop(&expect));
-
-        // can't "if | then" or "while && do", only ; or newline works
-        if (end && !strcmp(end, ";")) {
-          syntax_err("bad %s", end);
-          goto flush;
-        }
-
-        // if it's a multipart block, what comes next?
-        if (!strcmp(s, "do")) ex = "done";
-        else if (!strcmp(s, "then")) add = "fi\0A";
-      // fi could have elif, which queues a then.
-      } else if (!strcmp(ex, "fi")) {
-        if (!strcmp(s, "elif")) {
-          free(dlist_pop(&expect));
-          add = "then";
-        // catch duplicate else while we're here
-        } else if (!strcmp(s, "else")) {
-          if (ex[3] != 'A') {
-            syntax_err("2 else"); 
-            goto flush;
-          }
-          free(dlist_pop(&expect));
-          add = "fi\0B";
-        }
-      }
-    }
-    // Record how the previous stanza ended: ; | & ;; || && ;& ;;& |& NULL
-    end = arg->v[arg->c];
-  }
-
-  // Do we need more lines to finish a flow control statement?
-  if (expect || paren) {
-    llist_traverse(expect, free);
-    return start;
-  }
+  // Now we have a complete thought and can start running stuff.
 
   // iterate through the commands running each one
-  for (pl = *pipeline; pl ; pl = (pl->next == *pipeline) ? 0 : pl->next) {
+
+  // run a pipeline of commands
+
+  for (pl = sp->pipeline; pl ; pl = (pl->next == sp->pipeline) ? 0 : pl->next) {
     struct sh_process *pp = xzalloc(sizeof(struct sh_process));
 
-    for (i = 0; i<((struct sh_arg *)pl->data)->c; i++)
-      expand_arg(&pp->arg, ((struct sh_arg *)pl->data)->v[i]);
+    for (i = 0; i<arg->c; i++) expand_arg(&pp->arg, arg->v[i]);
     run_command(pp);
+    llist_traverse(pp->delete, free);
   }
 
+  s = 0;
 flush:
-  while ((pl = dlist_pop(pipeline))) {
+
+  if (s) syntax_err("bad %s", s);
+  while ((pl = dlist_pop(&sp->pipeline))) {
     arg = (void *)pl->data;
     free(pl);
     for (i = 0; i<arg->c; i++) free(arg->v[i]);
     free(arg->v);
     free(arg);
   }
-  *pipeline = 0;
+
+  while ((pl = dlist_pop(&sp->here))) {
+    arg = (void *)pl->data;
+    free(pl);
+    if (arg->c<0) arg->c = -arg->c - 1;
+    for (i = 0; i<arg->c; i++) free(arg->v[i]);
+    free(arg->v);
+    free(arg);
+  }
+
+  llist_traverse(sp->expect, free);
 
   return 0;
 }
 
 void sh_main(void)
 {
-  FILE *f = 0;
-  char *command = 0, *old = 0;
-  struct double_list *scratch = 0;
+  FILE *f;
+  struct sh_parse scratch;
+  int prompt = 0;
 
   // Set up signal handlers and grab control of this tty.
-  if (isatty(0)) toys.optflags |= FLAG_i;
 
-  if (*toys.optargs) f = xfopen(*toys.optargs, "r");
-  if (TT.command) command = parse_line(TT.command, &scratch);
-  else for (;;) {
+  memset(&scratch, 0, sizeof(scratch));
+  if (TT.command) f = fmemopen(TT.command, strlen(TT.command), "r");
+  else if (*toys.optargs) f = xfopen(*toys.optargs, "r");
+  else {
+    f = stdin;
+    if (isatty(0)) toys.optflags |= FLAG_i;
+  }
+
+  for (;;) {
     char *new = 0;
     size_t linelen = 0;
 
     // Prompt and read line
-    if (!f) {
-      char *s = getenv(command ? "PS2" : "PS1");
+    if (f == stdin) {
+      char *s = getenv(prompt ? "PS2" : "PS1");
 
-      if (!s) s = command ? "> " : (getpid() ? "\\$ " : "# ");
+      if (!s) s = prompt ? "> " : (getpid() ? "\\$ " : "# ");
       do_prompt(s);
-    }
-    if (1 > getline(&new, &linelen, f ? f : stdin)) break;
-    if (f) TT.lineno++;
-
-    // Append to unused portion of previous line if any
-    if (command) {
-      command = xmprintf("%s%s", command, new);
-      free(old);
-      free(new);
-      old = command;
-    } else {
-      free(old);
-      old = new;
-    }
+    } else TT.lineno++;
+    if (1>(linelen = getline(&new, &linelen, f ? f : stdin))) break;
+    if (new[linelen-1] == '\n') new[--linelen] = 0;
 
     // returns 0 if line consumed, command if it needs more data
-    command = parse_line(old, &scratch);
+    prompt = parse_line(new, &scratch);
+    free(new);
   }
 
-  if (command) error_exit("unfinished line");
+  if (prompt) error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
   toys.exitval = f && ferror(f);
 }
diff --git a/toys/pending/tcpsvd.c b/toys/pending/tcpsvd.c
index 31c2761..e5bd76b 100644
--- a/toys/pending/tcpsvd.c
+++ b/toys/pending/tcpsvd.c
@@ -248,7 +248,7 @@
   sockfd = xsocket(rp->ai_family, TT.udp ?SOCK_DGRAM :SOCK_STREAM, 0);
   setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
   if (TT.udp) setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set, sizeof(set));
-  if ((bind(sockfd, rp->ai_addr, rp->ai_addrlen)) < 0) perror_exit("Bind failed");
+  xbind(sockfd, rp->ai_addr, rp->ai_addrlen);
   if(haddr) memcpy(haddr, rp->ai_addr, rp->ai_addrlen);
   freeaddrinfo(res);
   return sockfd;
@@ -386,8 +386,7 @@
         free(serv);
         free(clie);
       }
-      if (TT.udp && (connect(newfd, (struct sockaddr *)buf, sizeof(buf)) < 0))
-          perror_exit("connect");
+      if (TT.udp) xconnect(newfd, (struct sockaddr *)buf, sizeof(buf));
 
       close(0);
       close(1);
diff --git a/toys/pending/telnet.c b/toys/pending/telnet.c
index e37b982..b8c9c14 100644
--- a/toys/pending/telnet.c
+++ b/toys/pending/telnet.c
@@ -306,7 +306,7 @@
   }
   terminal_size(&TT.win_width, &TT.win_height);
 
-  TT.sfd = xconnect(xgetaddrinfo(*toys.optargs, port, 0, SOCK_STREAM,
+  TT.sfd = xconnectany(xgetaddrinfo(*toys.optargs, port, 0, SOCK_STREAM,
     IPPROTO_TCP, 0));
   setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
   setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
diff --git a/toys/pending/telnetd.c b/toys/pending/telnetd.c
index 4198e63..ad39d8c 100644
--- a/toys/pending/telnetd.c
+++ b/toys/pending/telnetd.c
@@ -143,11 +143,8 @@
   if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1) 
     perror_exit("setsockopt");
 
-  if (bind(s, (struct sockaddr *)buf, ((af == AF_INET)?
-          (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6)))) == -1) {
-    close(s);
-    perror_exit("bind");
-  }
+  xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
+          (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
 
   if (listen(s, 1) < 0) perror_exit("listen");
   return s;
diff --git a/toys/pending/tftpd.c b/toys/pending/tftpd.c
index 9791ae4..b5d0558 100644
--- a/toys/pending/tftpd.c
+++ b/toys/pending/tftpd.c
@@ -252,9 +252,8 @@
   TT.sfd = xsocket(dstaddr.ss_family, SOCK_DGRAM, 0);
   if (setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&set,
         sizeof(set)) < 0) perror_exit("setsockopt failed");
-  if (bind(TT.sfd, (void *)&srcaddr, socklen)) perror_exit("bind");
-  if (connect(TT.sfd, (void *)&dstaddr, socklen) < 0)
-    perror_exit("can't connect to remote host");
+  xbind(TT.sfd, (void *)&srcaddr, socklen);
+  xconnect(TT.sfd, (void *)&dstaddr, socklen);
   // Error condition.
   if (recvmsg_len<4 || recvmsg_len>TFTPD_BLKSIZE || toybuf[recvmsg_len-1]) {
     send_errpkt((struct sockaddr*)&dstaddr, socklen, "packet format error");
diff --git a/toys/pending/traceroute.c b/toys/pending/traceroute.c
index d5ead9e..1cfdc48 100644
--- a/toys/pending/traceroute.c
+++ b/toys/pending/traceroute.c
@@ -587,8 +587,7 @@
       if (setsockopt(TT.snd_sock, IPPROTO_IP, IP_MULTICAST_IF,
             (struct sockaddr*)&source, sizeof(struct sockaddr_in)))
         perror_exit("can't set multicast source interface");
-      if (bind(TT.snd_sock,(struct sockaddr*)&source, 
-            sizeof(struct sockaddr_in)) < 0) perror_exit("bind");
+      xbind(TT.snd_sock,(struct sockaddr*)&source, sizeof(struct sockaddr_in));
     }
 
     if(TT.first_ttl > TT.max_ttl) 
@@ -607,9 +606,7 @@
       if(inet_pton(AF_INET6, TT.src_ip, &(source.sin6_addr)) <= 0)
         error_exit("bad address: %s", TT.src_ip);
 
-      if (bind(TT.snd_sock,(struct sockaddr*)&source, 
-            sizeof(struct sockaddr_in6)) < 0)
-        error_exit("bind: Cannot assign requested address");
+      xbind(TT.snd_sock,(struct sockaddr*)&source, sizeof(struct sockaddr_in6));
     } else {
       struct sockaddr_in6 prb;
       socklen_t len = sizeof(prb);
@@ -617,16 +614,13 @@
       if (toys.optflags & FLAG_i) bind_to_interface(p_fd);
 
       ((struct sockaddr_in6 *)&dest)->sin6_port = htons(1025);
-      if (connect(p_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_in6)) < 0)
-        perror_exit("can't connect to remote host");
+      xconnect(p_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_in6));
       if(getsockname(p_fd, (struct sockaddr *)&prb, &len)) 
         error_exit("probe addr failed");
       close(p_fd);
       prb.sin6_port = 0;
-      if (bind(TT.snd_sock, (struct sockaddr*)&prb, 
-            sizeof(struct sockaddr_in6))) perror_exit("bind");
-      if (bind(TT.recv_sock, (struct sockaddr*)&prb, 
-            sizeof(struct sockaddr_in6))) perror_exit("bind");
+      xbind(TT.snd_sock, (struct sockaddr*)&prb, sizeof(struct sockaddr_in6));
+      xbind(TT.recv_sock, (struct sockaddr*)&prb, sizeof(struct sockaddr_in6));
     }
 
     inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&dest)->sin6_addr, 
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index 5e7d99e..bba5480 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -377,9 +377,9 @@
   if (CFG_CP_PRESERVE && FLAG(preserve)) {
     char *pre = xstrdup(TT.c.preserve ? TT.c.preserve : "mot"), *s;
 
-    if (comma_scan(pre, "all", 1)) TT.pflags = ~0;
+    if (comma_remove(pre, "all")) TT.pflags = ~0;
     for (i=0; i<ARRAY_LEN(cp_preserve); i++)
-      if (comma_scan(pre, cp_preserve[i].name, 1)) TT.pflags |= 1<<i;
+      while (comma_remove(pre, cp_preserve[i].name)) TT.pflags |= 1<<i;
     if (*pre) {
 
       // Try to interpret as letters, commas won't set anything this doesn't.
diff --git a/toys/posix/file.c b/toys/posix/file.c
index 2370be7..d1e425b 100644
--- a/toys/posix/file.c
+++ b/toys/posix/file.c
@@ -216,7 +216,7 @@
   if (!len) xputs("empty");
   // 45 bytes: https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
   else if (len>=45 && strstart(&s, "\177ELF")) do_elf_file(fd);
-  else if (len>=8 && strstart(&s, "!<arch>\n")) xprintf("ar archive\n");
+  else if (len>=8 && strstart(&s, "!<arch>\n")) xputs("ar archive");
   else if (len>28 && strstart(&s, "\x89PNG\x0d\x0a\x1a\x0a")) {
     // PNG is big-endian: https://www.w3.org/TR/PNG/#7Integers-and-byte-order
     int chunk_length = peek_be(s, 4);
@@ -268,7 +268,7 @@
     xprintf("ASCII cpio archive (%s)\n", cpioformat);
   } else if (len>33 && (magic=peek(&s,2), magic==0143561 || magic==070707)) {
     if (magic == 0143561) printf("byte-swapped ");
-    xprintf("cpio archive\n");
+    xputs("cpio archive");
   // tar archive (old, ustar/pax, or gnu)
   } else if (len>500 && is_tar_header(s))
     xprintf("%s tar archive%s\n", s[257] ? "POSIX" : "old",
@@ -283,7 +283,7 @@
   } else if (len>4 && strstart(&s, "BZh") && isdigit(*s))
     xprintf("bzip2 compressed data, block size = %c00k\n", *s);
   else if (len > 31 && peek_be(s, 7) == 0xfd377a585a0000)
-    xprintf("xz compressed data");
+    xputs("xz compressed data");
   else if (len>10 && strstart(&s, "\x1f\x8b")) xputs("gzip compressed data");
   else if (len>32 && !memcmp(s+1, "\xfa\xed\xfe", 3)) {
     int bit = s[0]=='\xce'?32:64;
@@ -355,6 +355,10 @@
   } else if (len>12 && !memcmp(s, "ttcf\x00", 5)) {
     xprintf("TrueType font collection, version %d, %d fonts\n",
             (int)peek_be(s+4, 2), (int)peek_be(s+8, 4));
+
+  // https://docs.microsoft.com/en-us/typography/opentype/spec/otff
+  } else if (len>12 && !memcmp(s, "OTTO", 4)) {
+    xputs("OpenType font");
   } else if (len>4 && !memcmp(s, "BC\xc0\xde", 4)) {
     xputs("LLVM IR bitcode");
   } else if (strstart(&s, "-----BEGIN CERTIFICATE-----")) {
@@ -380,6 +384,26 @@
     int w = peek_le(s+0x12,4), h = peek_le(s+0x16,4), bpp = peek_le(s+0x1c,2);
 
     xprintf("BMP image, %d x %d, %d bpp\n", w, h, bpp);
+
+    // https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf.data-file-format.txt
+  } else if (len>=104 && !memcmp(s, "PERFILE2", 8)) {
+    xputs("Linux perf data");
+
+    // https://android.googlesource.com/platform/system/core/+/master/libsparse/sparse_format.h
+  } else if (len>28 && peek_le(s, 4) == 0xed26ff3a) {
+    xprintf("Android sparse image v%d.%d, %d %d-byte blocks (%d chunks)\n",
+        (int) peek_le(s+4, 2), (int) peek_le(s+6, 2), (int) peek_le(s+16, 4),
+        (int) peek_le(s+12, 4), (int) peek_le(s+20, 4));
+
+    // https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h
+  } else if (len>1632 && !memcmp(s, "ANDROID!", 8)) {
+    xprintf("Android boot image v%d\n", (int) peek_le(s+40, 4));
+
+    // https://source.android.com/devices/architecture/dto/partitions
+  } else if (len>32 && peek_be(s, 4) == 0xd7b7ab1e) {
+    xprintf("Android DTB/DTBO v%d, %d entries\n", (int) peek_be(s+28, 4),
+        (int) peek_be(s+16, 4));
+
   } else {
     char *what = 0;
     int i, bytes;
diff --git a/toys/posix/grep.c b/toys/posix/grep.c
index 30a9819..b92294e 100644
--- a/toys/posix/grep.c
+++ b/toys/posix/grep.c
@@ -10,7 +10,7 @@
 * 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)rsvwcl(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)))
 USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 
@@ -29,6 +29,7 @@
 
     file search:
     -r  Recurse into subdirectories (defaults FILE to ".")
+    -R  Recurse into subdirectories and symlinks to directories
     -M  Match filename pattern (--include)
     -S  Skip filename pattern (--exclude)
     --exclude-dir=PATTERN  Skip directory pattern
@@ -425,7 +426,7 @@
   if (S_ISDIR(new->st.st_mode)) {
     for (al = TT.exclude_dir; al; al = al->next)
       if (!fnmatch(al->arg, new->name, 0)) return 0;
-    return DIRTREE_RECURSE;
+    return DIRTREE_RECURSE|(FLAG(R)?DIRTREE_SYMFOLLOW:0);
   }
   if (TT.S || TT.M) {
     for (al = TT.S; al; al = al->next)
@@ -464,6 +465,8 @@
     TT.grey = "\033[0m";
   } else TT.purple = TT.cyan = TT.red = TT.green = TT.grey = "";
 
+  if (FLAG(R)) toys.optflags |= FLAG_r;
+
   // Grep exits with 2 for errors
   toys.exitval = 2;
 
diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c
index 9a3e9da..6f8c76f 100644
--- a/toys/posix/xargs.c
+++ b/toys/posix/xargs.c
@@ -76,7 +76,8 @@
       if (!*s) break;
       save = s;
 
-      TT.bytes += sizeof(char *);
+      // We ought to add sizeof(char *) to TT.bytes to be correct, but we don't
+      // for bug compatibility with busybox 1.30.1 and findutils 4.7.0.
 
       for (;;) {
         if (++TT.bytes >= TT.s && TT.s) return save;