Snap for 6269144 from 147afca1b8ff76145d3eedc1d131bf0a3ae93a9b to rvc-release

Change-Id: I057f63f2f715dc5d370f11205e3ea544b27e2bc9
diff --git a/Config.in b/Config.in
index 6758337..362b22f 100644
--- a/Config.in
+++ b/Config.in
@@ -165,22 +165,34 @@
 	help
 	  When commands like useradd/groupadd allocate user IDs, start here.
 
-config TOYBOX_MUSL_NOMMU_IS_BROKEN
-	bool "Workaround for musl-libc breakage on nommu systems."
+config TOYBOX_FORCE_NOMMU
+	bool "Enable nommu support when the build can't detect it."
 	default n
 	help
-	  When using musl-libc on a nommu system, you'll need to say "y" here.
+	  When using musl-libc on a nommu system, you'll need to say "y" here
+	  unless you used the patch in the mcm-buildall.sh script. You can also
+	  say "y" here to test the nommu codepaths on an mmu system.
 
-	  Although uclibc lets you detect support for things like fork() and
-	  daemon() at compile time, musl intentionally includes broken versions
-	  that always return -ENOSYS on nommu systems, and goes out of its way
-	  to prevent any cross-compile compatible compile-time probes for a
-	  nommu system. (It doesn't even #define __MUSL__ in features.h.)
+	  A nommu system can't use fork(), it can only vfork() which suspends
+	  the parent until the child calls exec() or exits. When a program
+	  needs a second instance of itself to run specific code at the same
+	  time as the parent, it must use a more complicated approach (such as
+	  exec("/proc/self/exe") then pass data to the new child through a pipe)
+	  which is larger and slower, especially for things like toysh subshells
+	  that need to duplicate a lot of internal state in the child process
+	  fork() gives you for free.
 
-	  Musl does this despite the fact that a nommu system can't even run
-	  standard ELF binaries, and requires specially packaged executables.
-	  So our only choice is to manually provide a musl nommu bug workaround
-	  you can manually select to enable (larger, slower) nommu support with
-	  musl.
+	  Libraries like uclibc omit fork() on nommu systems, allowing
+	  compile-time probes to select which codepath to use. But musl
+	  intentionally includes a broken version of fork() that always returns
+	  -ENOSYS on nommu systems, and goes out of its way to prevent any
+	  cross-compile compatible compile-time probes for a nommu system.
+	  (It doesn't even #define __MUSL__ in features.h.) Musl does this
+	  despite the fact that a nommu system can't even run standard ELF
+	  binaries (requiring specially packaged executables) because it wants
+	  to force every program to either include all nommu code in every
+	  instance ever built, or drop nommu support altogether.
+
+	  Building a toolchain scripts/mcm-buildall.sh patches musl to fix this.
 
 endmenu
diff --git a/android/device/generated/flags.h b/android/device/generated/flags.h
index 155cfc9..fcf9e60 100644
--- a/android/device/generated/flags.h
+++ b/android/device/generated/flags.h
@@ -2,7 +2,7 @@
 #undef FORCED_FLAGLL
 #ifdef FORCE_FLAGS
 #define FORCED_FLAG 1
-#define FORCED_FLAGLL 1LL
+#define FORCED_FLAGLL 1ULL
 #else
 #define FORCED_FLAG 0
 #define FORCED_FLAGLL 0
@@ -3849,7 +3849,7 @@
 #define FLAG_C (FORCED_FLAG<<28)
 #define FLAG_D (FORCED_FLAG<<29)
 #define FLAG_E (FORCED_FLAG<<30)
-#define FLAG_F (FORCED_FLAG<<31)
+#define FLAG_F (FORCED_FLAGLL<<31)
 #define FLAG_G (FORCED_FLAGLL<<32)
 #define FLAG_H (FORCED_FLAGLL<<33)
 #define FLAG_I (FORCED_FLAGLL<<34)
@@ -4343,7 +4343,7 @@
 #define FLAG_S (1<<28)
 #define FLAG_exclude_dir (1<<29)
 #define FLAG_color (1<<30)
-#define FLAG_line_buffered (1<<31)
+#define FLAG_line_buffered (1LL<<31)
 #endif
 
 #ifdef FOR_groupadd
@@ -4804,7 +4804,7 @@
 #define FLAG_g (1<<28)
 #define FLAG_Z (1<<29)
 #define FLAG_show_control_chars (1<<30)
-#define FLAG_full_time (1<<31)
+#define FLAG_full_time (1LL<<31)
 #define FLAG_color (1LL<<32)
 #endif
 
diff --git a/android/device/generated/globals.h b/android/device/generated/globals.h
index 6b93ddd..4a201bf 100644
--- a/android/device/generated/globals.h
+++ b/android/device/generated/globals.h
@@ -314,6 +314,8 @@
   long p;
 
   long add, rm, set;
+  // !add and !rm tell us whether they were used, but `chattr =` is meaningful.
+  int have_set;
 };
 
 // toys/other/lspci.c
@@ -1324,9 +1326,8 @@
   char *i, *d;
   long p, g, F;
 
-  struct double_list *current_hunk;
-  long oldline, oldlen, newline, newlen;
-  long linenum;
+  void *current_hunk;
+  long oldline, oldlen, newline, newlen, linenum, outnum;
   int context, state, filein, fileout, filepatch, hunknum;
   char *tempname;
 };
diff --git a/android/device/generated/help.h b/android/device/generated/help.h
index 863a9df..55613a5 100644
--- a/android/device/generated/help.h
+++ b/android/device/generated/help.h
@@ -1,4 +1,4 @@
-#define HELP_toybox_musl_nommu_is_broken "When using musl-libc on a nommu system, you'll need to say \"y\" here.\n\nAlthough uclibc lets you detect support for things like fork() and\ndaemon() at compile time, musl intentionally includes broken versions\nthat always return -ENOSYS on nommu systems, and goes out of its way\nto prevent any cross-compile compatible compile-time probes for a\nnommu system. (It doesn't even #define __MUSL__ in features.h.)\n\nMusl does this despite the fact that a nommu system can't even run\nstandard ELF binaries, and requires specially packaged executables.\nSo our only choice is to manually provide a musl nommu bug workaround\nyou can manually select to enable (larger, slower) nommu support with\nmusl."
+#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a toolchain scripts/mcm-buildall.sh patches musl to fix this."
 
 #define HELP_toybox_uid_usr "When commands like useradd/groupadd allocate user IDs, start here."
 
diff --git a/android/linux/generated/flags.h b/android/linux/generated/flags.h
index 63c9182..a87b935 100644
--- a/android/linux/generated/flags.h
+++ b/android/linux/generated/flags.h
@@ -2,7 +2,7 @@
 #undef FORCED_FLAGLL
 #ifdef FORCE_FLAGS
 #define FORCED_FLAG 1
-#define FORCED_FLAGLL 1LL
+#define FORCED_FLAGLL 1ULL
 #else
 #define FORCED_FLAG 0
 #define FORCED_FLAGLL 0
@@ -3849,7 +3849,7 @@
 #define FLAG_C (FORCED_FLAG<<28)
 #define FLAG_D (FORCED_FLAG<<29)
 #define FLAG_E (FORCED_FLAG<<30)
-#define FLAG_F (FORCED_FLAG<<31)
+#define FLAG_F (FORCED_FLAGLL<<31)
 #define FLAG_G (FORCED_FLAGLL<<32)
 #define FLAG_H (FORCED_FLAGLL<<33)
 #define FLAG_I (FORCED_FLAGLL<<34)
@@ -4343,7 +4343,7 @@
 #define FLAG_S (1<<28)
 #define FLAG_exclude_dir (1<<29)
 #define FLAG_color (1<<30)
-#define FLAG_line_buffered (1<<31)
+#define FLAG_line_buffered (1LL<<31)
 #endif
 
 #ifdef FOR_groupadd
@@ -4804,7 +4804,7 @@
 #define FLAG_g (1<<28)
 #define FLAG_Z (1<<29)
 #define FLAG_show_control_chars (1<<30)
-#define FLAG_full_time (1<<31)
+#define FLAG_full_time (1LL<<31)
 #define FLAG_color (1LL<<32)
 #endif
 
diff --git a/android/linux/generated/globals.h b/android/linux/generated/globals.h
index 6b93ddd..4a201bf 100644
--- a/android/linux/generated/globals.h
+++ b/android/linux/generated/globals.h
@@ -314,6 +314,8 @@
   long p;
 
   long add, rm, set;
+  // !add and !rm tell us whether they were used, but `chattr =` is meaningful.
+  int have_set;
 };
 
 // toys/other/lspci.c
@@ -1324,9 +1326,8 @@
   char *i, *d;
   long p, g, F;
 
-  struct double_list *current_hunk;
-  long oldline, oldlen, newline, newlen;
-  long linenum;
+  void *current_hunk;
+  long oldline, oldlen, newline, newlen, linenum, outnum;
   int context, state, filein, fileout, filepatch, hunknum;
   char *tempname;
 };
diff --git a/android/linux/generated/help.h b/android/linux/generated/help.h
index 6f1f4eb..6621ed1 100644
--- a/android/linux/generated/help.h
+++ b/android/linux/generated/help.h
@@ -1,4 +1,4 @@
-#define HELP_toybox_musl_nommu_is_broken "When using musl-libc on a nommu system, you'll need to say \"y\" here.\n\nAlthough uclibc lets you detect support for things like fork() and\ndaemon() at compile time, musl intentionally includes broken versions\nthat always return -ENOSYS on nommu systems, and goes out of its way\nto prevent any cross-compile compatible compile-time probes for a\nnommu system. (It doesn't even #define __MUSL__ in features.h.)\n\nMusl does this despite the fact that a nommu system can't even run\nstandard ELF binaries, and requires specially packaged executables.\nSo our only choice is to manually provide a musl nommu bug workaround\nyou can manually select to enable (larger, slower) nommu support with\nmusl."
+#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a toolchain scripts/mcm-buildall.sh patches musl to fix this."
 
 #define HELP_toybox_uid_usr "When commands like useradd/groupadd allocate user IDs, start here."
 
diff --git a/android/mac/generated/flags.h b/android/mac/generated/flags.h
index 8159897..7b026e6 100644
--- a/android/mac/generated/flags.h
+++ b/android/mac/generated/flags.h
@@ -2,7 +2,7 @@
 #undef FORCED_FLAGLL
 #ifdef FORCE_FLAGS
 #define FORCED_FLAG 1
-#define FORCED_FLAGLL 1LL
+#define FORCED_FLAGLL 1ULL
 #else
 #define FORCED_FLAG 0
 #define FORCED_FLAGLL 0
@@ -3849,7 +3849,7 @@
 #define FLAG_C (FORCED_FLAG<<28)
 #define FLAG_D (FORCED_FLAG<<29)
 #define FLAG_E (FORCED_FLAG<<30)
-#define FLAG_F (FORCED_FLAG<<31)
+#define FLAG_F (FORCED_FLAGLL<<31)
 #define FLAG_G (FORCED_FLAGLL<<32)
 #define FLAG_H (FORCED_FLAGLL<<33)
 #define FLAG_I (FORCED_FLAGLL<<34)
@@ -4343,7 +4343,7 @@
 #define FLAG_S (1<<28)
 #define FLAG_exclude_dir (1<<29)
 #define FLAG_color (1<<30)
-#define FLAG_line_buffered (1<<31)
+#define FLAG_line_buffered (1LL<<31)
 #endif
 
 #ifdef FOR_groupadd
@@ -4804,7 +4804,7 @@
 #define FLAG_g (1<<28)
 #define FLAG_Z (1<<29)
 #define FLAG_show_control_chars (1<<30)
-#define FLAG_full_time (1<<31)
+#define FLAG_full_time (1LL<<31)
 #define FLAG_color (1LL<<32)
 #endif
 
diff --git a/android/mac/generated/globals.h b/android/mac/generated/globals.h
index 6b93ddd..4a201bf 100644
--- a/android/mac/generated/globals.h
+++ b/android/mac/generated/globals.h
@@ -314,6 +314,8 @@
   long p;
 
   long add, rm, set;
+  // !add and !rm tell us whether they were used, but `chattr =` is meaningful.
+  int have_set;
 };
 
 // toys/other/lspci.c
@@ -1324,9 +1326,8 @@
   char *i, *d;
   long p, g, F;
 
-  struct double_list *current_hunk;
-  long oldline, oldlen, newline, newlen;
-  long linenum;
+  void *current_hunk;
+  long oldline, oldlen, newline, newlen, linenum, outnum;
   int context, state, filein, fileout, filepatch, hunknum;
   char *tempname;
 };
diff --git a/android/mac/generated/help.h b/android/mac/generated/help.h
index 6f1f4eb..6621ed1 100644
--- a/android/mac/generated/help.h
+++ b/android/mac/generated/help.h
@@ -1,4 +1,4 @@
-#define HELP_toybox_musl_nommu_is_broken "When using musl-libc on a nommu system, you'll need to say \"y\" here.\n\nAlthough uclibc lets you detect support for things like fork() and\ndaemon() at compile time, musl intentionally includes broken versions\nthat always return -ENOSYS on nommu systems, and goes out of its way\nto prevent any cross-compile compatible compile-time probes for a\nnommu system. (It doesn't even #define __MUSL__ in features.h.)\n\nMusl does this despite the fact that a nommu system can't even run\nstandard ELF binaries, and requires specially packaged executables.\nSo our only choice is to manually provide a musl nommu bug workaround\nyou can manually select to enable (larger, slower) nommu support with\nmusl."
+#define HELP_toybox_force_nommu "When using musl-libc on a nommu system, you'll need to say \"y\" here\nunless you used the patch in the mcm-buildall.sh script. You can also\nsay \"y\" here to test the nommu codepaths on an mmu system.\n\nA nommu system can't use fork(), it can only vfork() which suspends\nthe parent until the child calls exec() or exits. When a program\nneeds a second instance of itself to run specific code at the same\ntime as the parent, it must use a more complicated approach (such as\nexec(\"/proc/self/exe\") then pass data to the new child through a pipe)\nwhich is larger and slower, especially for things like toysh subshells\nthat need to duplicate a lot of internal state in the child process\nfork() gives you for free.\n\nLibraries like uclibc omit fork() on nommu systems, allowing\ncompile-time probes to select which codepath to use. But musl\nintentionally includes a broken version of fork() that always returns\n-ENOSYS on nommu systems, and goes out of its way to prevent any\ncross-compile compatible compile-time probes for a nommu system.\n(It doesn't even #define __MUSL__ in features.h.) Musl does this\ndespite the fact that a nommu system can't even run standard ELF\nbinaries (requiring specially packaged executables) because it wants\nto force every program to either include all nommu code in every\ninstance ever built, or drop nommu support altogether.\n\nBuilding a toolchain scripts/mcm-buildall.sh patches musl to fix this."
 
 #define HELP_toybox_uid_usr "When commands like useradd/groupadd allocate user IDs, start here."
 
diff --git a/kconfig/macos_miniconfig b/kconfig/macos_miniconfig
index ac4af14..b9bf6a3 100644
--- a/kconfig/macos_miniconfig
+++ b/kconfig/macos_miniconfig
@@ -84,7 +84,6 @@
 CONFIG_FLOCK=y
 CONFIG_FMT=y
 CONFIG_HELP=y
-CONFIG_HELP_EXTRAS=y
 CONFIG_HEXEDIT=y
 CONFIG_PRINTENV=y
 CONFIG_PWDX=y
diff --git a/lib/env.c b/lib/env.c
index 92cc811..614a504 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -2,7 +2,7 @@
 
 #include "toys.h"
 
-// In libc, populated by start code,used by getenv() and exec() and friends.
+// In libc, populated by start code, used by getenv() and exec() and friends.
 extern char **environ;
 
 // Returns the number of bytes taken by the environment variables. For use
@@ -26,9 +26,8 @@
     int i;
 
     for (i = 0; environ[i]; i++) if (i>=toys.envc) free(environ[i]);
-    free(environ);
-  }
-  toys.envc = 0;
+  } else environ = xmalloc(256*sizeof(char *));
+  toys.envc = 1;
   *environ = 0;
 }
 
diff --git a/run-tests-on-android.sh b/run-tests-on-android.sh
index 744fc8b..d85cca3 100755
--- a/run-tests-on-android.sh
+++ b/run-tests-on-android.sh
@@ -62,12 +62,7 @@
   elif [ "$non_toy" = "true" ]; then
     non_toy_failures="$non_toy_failures $toy"
   else
-    # The chattr tests are currently broken on cuttlefish. Working on it...
-    if [[ "$toy" = "chattr" ]]; then
-      non_toy_failures="$non_toy_failures $toy"
-    else
-      failures="$failures $toy"
-    fi
+    failures="$failures $toy"
   fi
 }
 
diff --git a/scripts/cross.sh b/scripts/cross.sh
index 6dac8ec..2570713 100755
--- a/scripts/cross.sh
+++ b/scripts/cross.sh
@@ -35,6 +35,7 @@
   do
     {
       export TARGET
+      echo -en "\033]2;$TARGET $*\007"
       "$0" $TARGET "$@" 2>&1 || mv cross-log-$TARGET.{txt,failed}
     } | tee cross-log-$TARGET.txt
   done
diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh
index cc2bd76..b5637f8 100755
--- a/scripts/genconfig.sh
+++ b/scripts/genconfig.sh
@@ -92,7 +92,7 @@
     #include <unistd.h>
     int main(int argc, char *argv[]) { return fork(); }
 EOF
-  echo -e '\tdepends on !TOYBOX_MUSL_NOMMU_IS_BROKEN'
+  echo -e '\tdepends on !TOYBOX_FORCE_NOMMU'
 
   probesymbol TOYBOX_PRLIMIT << EOF
     #include <sys/types.h>
diff --git a/scripts/make.sh b/scripts/make.sh
index 538ba12..e4333ba 100755
--- a/scripts/make.sh
+++ b/scripts/make.sh
@@ -3,10 +3,15 @@
 # Grab default values for $CFLAGS and such.
 
 if [ ! -z "$ASAN" ]; then
-  # Turn ASan on.
-  CFLAGS="-fsanitize=address $CFLAGS"
-  # Optional, but effectively necessary if you want useful backtraces.
-  CFLAGS="-O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls $CFLAGS"
+  echo "Enabling ASan..."
+  # Turn ASan on. Everything except -fsanitize=address is optional, but
+  # but effectively required for useful backtraces.
+  asan_flags="-fsanitize=address \
+    -O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls"
+  CFLAGS="$asan_flags $CFLAGS"
+  HOSTCC="$HOSTCC $asan_flags"
+  # Ignore leaks on exit.
+  export ASAN_OPTIONS="detect_leaks=0"
 fi
 
 export LANG=c
diff --git a/scripts/mcm-buildall.sh b/scripts/mcm-buildall.sh
index 6e36c7c..e82ce2d 100755
--- a/scripts/mcm-buildall.sh
+++ b/scripts/mcm-buildall.sh
@@ -127,6 +127,94 @@
   done
 }
 
+# Packages detect nommu via the absence of fork(). Musl provides a broken fork()
+# on nommu builds that always returns -ENOSYS at runtime. Rip it out.
+# (Currently only for superh/jcore.)
+fix_nommu()
+{
+  # Rich won't merge this
+  sed -i 's/--enable-fdpic$/& --enable-twoprocess/' litecross/Makefile
+
+  PP=patches/musl-"$(sed -n 's/MUSL_VER[ \t]*=[ \t]*//p' Makefile)"
+  mkdir -p "$PP" &&
+  cat > "$PP"/0001-nommu.patch << 'EOF'
+--- a/include/features.h
++++ b/include/features.h
+@@ -3,2 +3,4 @@
+ 
++#define __MUSL__ 1
++
+ #if defined(_ALL_SOURCE) && !defined(_GNU_SOURCE)
+--- a/src/legacy/daemon.c
++++ b/src/legacy/daemon.c
+@@ -17,3 +17,3 @@
+ 
+-	switch(fork()) {
++	switch(vfork()) {
+ 	case 0: break;
+@@ -25,3 +25,3 @@
+ 
+-	switch(fork()) {
++	switch(vfork()) {
+ 	case 0: break;
+--- a/src/misc/forkpty.c
++++ b/src/misc/forkpty.c
+@@ -8,2 +8,3 @@
+ 
++#ifndef __SH_FDPIC__
+ int forkpty(int *pm, char *name, const struct termios *tio, const struct winsize *ws)
+@@ -57,1 +58,2 @@
+ }
++#endif
+--- a/src/misc/wordexp.c
++++ b/src/misc/wordexp.c
+@@ -25,2 +25,3 @@
+ 
++#ifndef __SH_FDPIC__
+ static int do_wordexp(const char *s, wordexp_t *we, int flags)
+@@ -177,2 +178,3 @@
+ }
++#endif
+ 
+--- a/src/process/fork.c
++++ b/src/process/fork.c
+@@ -7,2 +7,3 @@
+ 
++#ifndef __SH_FDPIC__
+ static void dummy(int x)
+@@ -37,1 +38,2 @@
+ }
++#endif
+--- a/Makefile
++++ b/Makefile
+@@ -100,3 +100,3 @@
+ 	cp $< $@
+-	sed -n -e s/__NR_/SYS_/p < $< >> $@
++	sed -e s/__NR_/SYS_/ < $< >> $@
+ 
+--- a/arch/sh/bits/syscall.h.in
++++ b/arch/sh/bits/syscall.h.in
+@@ -2,3 +2,5 @@
+ #define __NR_exit                   1
++#ifndef __SH_FDPIC__
+ #define __NR_fork                   2
++#endif
+ #define __NR_read                   3
+EOF
+
+  # I won't sign the FSF's copyright assignment
+  tee $(for i in patches/gcc-*; do echo $i/099-vfork.patch; done) > /dev/null << 'EOF'
+--- gcc-8.3.0/fixincludes/procopen.c	2005-08-14 19:50:43.000000000 -0500
++++ gcc-bak/fixincludes/procopen.c	2020-02-06 23:27:15.408071708 -0600
+@@ -116,3 +116,3 @@
+    */
+-  ch_id = fork ();
++  ch_id = vfork ();
+   switch (ch_id)
+EOF
+}
+
+fix_nommu || exit 1
 mkdir -p "$OUTPUT"/log
 
 # Make bootstrap compiler (no $TYPE, dynamically linked against host libc)
diff --git a/scripts/mkflags.c b/scripts/mkflags.c
index 9329488..fff9dd4 100644
--- a/scripts/mkflags.c
+++ b/scripts/mkflags.c
@@ -154,7 +154,7 @@
 
 int main(int argc, char *argv[])
 {
-  char command[256], flags[1023], allflags[1024];
+  char command[256], flags[1024], allflags[1024];
   char *out, *outbuf = malloc(1024*1024);
 
   // Yes, the output buffer is 1 megabyte with no bounds checking.
@@ -162,7 +162,7 @@
   if (!(out = outbuf)) return 1;
 
   printf("#undef FORCED_FLAG\n#undef FORCED_FLAGLL\n"
-    "#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1\n#define FORCED_FLAGLL 1LL\n"
+    "#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1\n#define FORCED_FLAGLL 1ULL\n"
     "#else\n#define FORCED_FLAG 0\n#define FORCED_FLAGLL 0\n#endif\n\n");
 
   for (;;) {
@@ -220,7 +220,7 @@
     out += strlen(out);
 
     while (aflist) {
-      char *llstr = bit>31 ? "LL" : "", *s = (char []){0, 0, 0, 0};
+      char *llstr = bit>30 ? "LL" : "", *s = (char []){0, 0, 0, 0};
       int enabled = 0;
 
       // Output flag macro for bare longopts
diff --git a/scripts/mkroot.sh b/scripts/mkroot.sh
index d486907..371ba48 100755
--- a/scripts/mkroot.sh
+++ b/scripts/mkroot.sh
@@ -43,7 +43,6 @@
 if [ ! -z "$CROSS_COMPILE" ]; then
   if [ ! -e "$AIRLOCK/toybox" ]; then
     echo === Create airlock dir
-
     PREFIX="$AIRLOCK" KCONFIG_CONFIG="$TOP"/.airlock CROSS_COMPILE= \
       make clean defconfig toybox install_airlock &&
     rm "$TOP"/.airlock || exit 1
@@ -59,8 +58,7 @@
 cat > "$ROOT"/init << 'EOF' &&
 #!/bin/sh
 
-export HOME=/home
-export PATH=/bin:/sbin
+export HOME=/home PATH=/bin:/sbin
 
 mountpoint -q proc || mount -t proc proc proc
 mountpoint -q sys || mount -t sysfs sys sys
@@ -80,12 +78,9 @@
   [ "$(date +%s)" -lt 1000 ] && rdate 10.0.2.2 # Ask QEMU what time it is
   [ "$(date +%s)" -lt 10000000 ] && ntpd -nq -p north-america.pool.ntp.org
 
-  [ -z "$CONSOLE" ] &&
-    CONSOLE="$(sed -n 's@.* console=\(/dev/\)*\([^ ]*\).*@\2@p' /proc/cmdline)"
-
+  [ -z "$CONSOLE" ] && CONSOLE="$(</sys/class/tty/console/active)"
   [ -z "$HANDOFF" ] && HANDOFF=/bin/sh && echo Type exit when done.
-  [ -z "$CONSOLE" ] && CONSOLE=console
-  exec /sbin/oneit -c /dev/"$CONSOLE" $HANDOFF
+  exec /sbin/oneit -c /dev/"${CONSOLE:-console}" $HANDOFF
 else
   /bin/sh
   umount /dev/pts /dev /sys /proc
@@ -100,21 +95,12 @@
 nobody:x:65534:65534:nobody:/proc/self:/dev/null
 EOF
 echo -e 'root:x:0:\nguest:x:500:\nnobody:x:65534:' > "$ROOT"/etc/group &&
-
-# /etc/resolv.conf using Google's public nameserver. (We could use QEMU's
-# 10.0.2.2 forwarder here, but this way works in both chroot and QEMU.)
+# Google's public nameserver.
 echo "nameserver 8.8.8.8" > "$ROOT"/etc/resolv.conf || exit 1
 
 # Build toybox
 make clean
-if [ -z .config ]; then
-  make defconfig
-  # Work around musl-libc design flaw.
-  [ "${CROSS_BASE/fdpic//}" != "$CROSS_BASE" ] &&
-    sed -i 's/.*\(CONFIG_TOYBOX_MUSL_NOMMU_IS_BROKEN\).*/\1=y/' .config
-else
-  make silentoldconfig
-fi
+make $([ -z .config ] && echo defconfig || echo silentoldconfig)
 LDFLAGS=--static PREFIX="$ROOT" make toybox install || exit 1
 
 write_miniconfig()
@@ -153,7 +139,7 @@
     # This could use the same VIRT board as armv7, but let's demonstrate a
     # different one requiring a separate device tree binary.
     QEMU="arm -M versatilepb -net nic,model=rtl8139 -net user"
-    KARCH=arm KARGS="console=ttyAMA0" VMLINUX=arch/arm/boot/zImage
+    KARCH=arm KARGS=ttyAMA0 VMLINUX=arch/arm/boot/zImage
     KCONF=CPU_ARM926T,MMU,VFP,ARM_THUMB,AEABI,ARCH_VERSATILE,ATAGS,DEPRECATED_PARAM_STRUCT,ARM_ATAG_DTB_COMPAT,ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND,SERIAL_AMBA_PL011,SERIAL_AMBA_PL011_CONSOLE,RTC_CLASS,RTC_DRV_PL031,RTC_HCTOSYS,PCI,PCI_VERSATILE,BLK_DEV_SD,SCSI,SCSI_LOWLEVEL,SCSI_SYM53C8XX_2,SCSI_SYM53C8XX_MMIO,NET_VENDOR_REALTEK,8139CP
     KERNEL_CONFIG="CONFIG_SCSI_SYM53C8XX_DMA_ADDRESSING_MODE=0"
     DTB=arch/arm/boot/dts/versatile-pb.dtb
@@ -164,7 +150,7 @@
     else
       QEMU="arm -M virt" KARCH=arm VMLINUX=arch/arm/boot/zImage
     fi
-    KARGS="console=ttyAMA0"
+    KARGS=ttyAMA0
     KCONF=MMU,ARCH_MULTI_V7,ARCH_VIRT,SOC_DRA7XX,ARCH_OMAP2PLUS_TYPICAL,ARCH_ALPINE,ARM_THUMB,VDSO,CPU_IDLE,ARM_CPUIDLE,KERNEL_MODE_NEON,SERIAL_AMBA_PL011,SERIAL_AMBA_PL011_CONSOLE,RTC_CLASS,RTC_HCTOSYS,RTC_DRV_PL031,NET_CORE,VIRTIO_MENU,VIRTIO_NET,PCI,PCI_HOST_GENERIC,VIRTIO_BLK,VIRTIO_PCI,VIRTIO_MMIO,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,PATA_PLATFORM,PATA_OF_PLATFORM,ATA_GENERIC
   elif [ "$TARGET" == i486 ] || [ "$TARGET" == i686 ] ||
        [ "$TARGET" == x86_64 ] || [ "$TARGET" == x32 ]; then
@@ -176,35 +162,31 @@
       QEMU=x86_64 KCONF=64BIT
       [ "$TARGET" == x32 ] && KCONF=X86_X32
     fi
-    KARCH=x86 KARGS="console=ttyS0" VMLINUX=arch/x86/boot/bzImage
+    KARCH=x86 KARGS=ttyS0 VMLINUX=arch/x86/boot/bzImage
     KCONF=$KCONF,UNWINDER_FRAME_POINTER,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,NET_VENDOR_INTEL,E1000,SERIAL_8250,SERIAL_8250_CONSOLE,RTC_CLASS
   elif [ "$TARGET" == mips ] || [ "$TARGET" == mipsel ]; then
-    QEMU="mips -M malta" KARCH=mips KARGS="console=ttyS0" VMLINUX=vmlinux
+    QEMU="mips -M malta" KARCH=mips KARGS=ttyS0 VMLINUX=vmlinux
     KCONF=MIPS_MALTA,CPU_MIPS32_R2,SERIAL_8250,SERIAL_8250_CONSOLE,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,NET_VENDOR_AMD,PCNET32,POWER_RESET,POWER_RESET_SYSCON
     [ "$TARGET" == mipsel ] && KCONF=$KCONF,CPU_LITTLE_ENDIAN &&
       QEMU="mipsel -M malta"
   elif [ "$TARGET" == powerpc ]; then
-    KARCH=powerpc QEMU="ppc -M g3beige" KARGS="console=ttyS0" VMLINUX=vmlinux
+    KARCH=powerpc QEMU="ppc -M g3beige" KARGS=ttyS0 VMLINUX=vmlinux
     KCONF=ALTIVEC,PPC_PMAC,PPC_OF_BOOT_TRAMPOLINE,IDE,IDE_GD,IDE_GD_ATA,BLK_DEV_IDE_PMAC,BLK_DEV_IDE_PMAC_ATA100FIRST,MACINTOSH_DRIVERS,ADB,ADB_CUDA,NET_VENDOR_NATSEMI,NET_VENDOR_8390,NE2K_PCI,SERIO,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE,BOOTX_TEXT
+
   elif [ "$TARGET" == powerpc64le ]; then
-    KARCH=powerpc QEMU="ppc64 -M pseries -vga none" KARGS="console=/dev/hvc0"
+    KARCH=powerpc QEMU="ppc64 -M pseries -vga none" KARGS=/dev/hvc0
     VMLINUX=vmlinux
     KCONF=PPC64,PPC_PSERIES,CPU_LITTLE_ENDIAN,PPC_OF_BOOT_TRAMPOLINE,BLK_DEV_SD,SCSI_LOWLEVEL,SCSI_IBMVSCSI,ATA,NET_VENDOR_IBM,IBMVETH,HVC_CONSOLE,PPC_TRANSACTIONAL_MEM,PPC_DISABLE_WERROR,SECTION_MISMATCH_WARN_ONLY
+
   elif [ "$TARGET" = s390x ] ; then
     QEMU="s390x" KARCH=s390 VMLINUX=arch/s390/boot/bzImage
     KCONF=MARCH_Z900,PACK_STACK,NET_CORE,VIRTIO_NET,VIRTIO_BLK,SCLP_TTY,SCLP_CONSOLE,SCLP_VT220_TTY,SCLP_VT220_CONSOLE,S390_GUEST
   elif [ "$TARGET" == sh4 ] ; then
     QEMU="sh4 -M r2d -serial null -serial mon:stdio" KARCH=sh
-    KARGS="console=ttySC1 noiotrap" VMLINUX=arch/sh/boot/zImage
+    KARGS="ttySC1 noiotrap" VMLINUX=arch/sh/boot/zImage
     KERNEL_CONFIG="CONFIG_MEMORY_START=0x0c000000"
     KCONF=CPU_SUBTYPE_SH7751R,MMU,VSYSCALL,SH_FPU,SH_RTS7751R2D,RTS7751R2D_PLUS,SERIAL_SH_SCI,SERIAL_SH_SCI_CONSOLE,PCI,NET_VENDOR_REALTEK,8139CP,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,PATA_PLATFORM,BINFMT_ELF_FDPIC,BINFMT_FLAT
-#CONFIG_SPI=y
-#CONFIG_SPI_SH_SCI=y
-#CONFIG_MFD_SM501=y
-#CONFIG_RTC_CLASS=y
-#CONFIG_RTC_DRV_R9701=y
-#CONFIG_RTC_DRV_SH=y
-#CONFIG_RTC_HCTOSYS=y
+#see also SPI SPI_SH_SCI MFD_SM501 RTC_CLASS RTC_DRV_R9701 RTC_DRV_SH RTC_HCTOSYS
   else
     echo "Unknown \$TARGET"
     exit 1
@@ -213,13 +195,13 @@
   # Write the qemu launch script
   echo "qemu-system-$QEMU" '"$@"' -nographic -no-reboot -m 256 \
        "-kernel $(basename "$VMLINUX") -initrd ${CROSS_BASE}root.cpio.gz" \
-       "-append \"panic=1 HOST=$TARGET $KARGS \$KARGS\"" \
+       "-append \"panic=1 HOST=$TARGET console=$KARGS \$KARGS\"" \
        ${DTB:+-dtb "$(basename "$DTB")"} > "$OUTPUT/qemu-$TARGET.sh" &&
   chmod +x "$OUTPUT/qemu-$TARGET.sh" &&
 
   echo "Build linux for $KARCH"
+  pushd "$LINUX" && make distclean && popd &&
   cp -sfR "$LINUX" "$MYBUILD/linux" && pushd "$MYBUILD/linux" || exit 1
-  make distclean &&
   write_miniconfig > "$OUTPUT/miniconfig-$TARGET" &&
   make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG="$OUTPUT/miniconfig-$TARGET" &&
   make ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" -j $(nproc) || exit 1
diff --git a/tests/sh.test b/tests/sh.test
index ca6a283..dd18af7 100755
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -9,9 +9,36 @@
 #testing "name" "command" "result" "infile" "stdin"
 
 [ -z "$SH" ] && { [ -z "$TEST_HOST" ] && SH="sh" || export SH="bash" ; }
+
+# Test the sh -c stuff before changing EVAL
+testing '-c "" exit status 0' '$SH -c "" && echo $?' '0\n' '' ''
+testing '-c args' "\$SH -c 'echo \$0,\$1,\$2,\$3' one two three four five" \
+  "one,two,three,four\n" "" ""
+testing '-c arg split' \
+  "$SH -c 'for i in a\"\$@\"b;do echo =\$i=;done;echo \$0' 123 456 789" \
+  "=a456=\n=789b=\n123\n" "" ""
+testing "exec3" '$C -c "{ exec readlink /proc/self/fd/0;} < /proc/self/exe"' \
+  "$(readlink -f $C)\n" "" ""
+
+testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" ""
+testing 'simple script' '$SH input' 'input\n' 'echo $0' ''
+testing 'simple script2' '$SH ./input two;echo $?' './input+two\n42\n' \
+  '\necho $0+$1\n\nexit 42' ''
+mkdir sub
+echo echo hello > sub/script
+testing 'simple script in PATH' "PATH='$PWD/sub:$PATH' $SH script" \
+  'hello\n' '' ''
+rm -rf sub
+
+# Change EVAL to call sh -c for us, using "bash" explicitly for the host.
 export EVAL="$SH -c"
 
 testing "smoketest" "echo hello" "hello\n" "" ""
+testing "eval" "eval echo hello" "hello\n" "" ""
+testing "eval2" "eval 'echo hello'; echo $?" "hello\n0\n" "" ""
+testing "eval3" 'X="echo hello"; eval "$X"' "hello\n" "" ""
+testing "exec" "exec echo hello" "hello\n" "" ""
+testing "exec2" "exec echo hello; echo $?" "hello\n" "" "" 
 
 # ; | && ||
 testing "semicolon" "echo one;echo two" "one\ntwo\n" "" ""
@@ -24,11 +51,14 @@
 
 # redirection
 
-testing "" "cat < input" "hello\n" "hello\n" ""
-testing "" "echo blah >out; cat out" "blah\n" "" ""
-testing "" "touch /not/exist 2>out||grep -o /not/exist out" "/not/exist\n" "" ""
-#testing "" 'echo hello | (read i <input; echo $i; read i; echo $i)' \
-#  "there\nhello\n" "there\n" ""
+testing "redir1" "cat < input" "hello\n" "hello\n" ""
+testing "redir2" "echo blah >out; cat out" "blah\n" "" ""
+testing "redir3" "echo more >>out; cat out" "blah\nmore\n" "" ""
+testing "redir4" "touch /not/exist 2>out||grep -o /not/exist out" \
+  "/not/exist\n" "" ""
+testing "redir5" "ls out /not/exist &> out2 || wc -l < out2" "2\n" "" ""
+testing "redir6" "ls out /not/exist |& wc -l" "2\n" "" ""
+testing "redir7" 'echo -n $(<input)' "boing" "boing\n" ""
 
 testing "tilde expansion" "echo ~" "$HOME\n" "" ""
 testing "tilde2" "echo ~/dir" "$HOME/dir\n" "" ""
@@ -47,22 +77,113 @@
 for i in /root /var/root /; do [ -e $i ] && EXPECT=$i && break; done
 testing "bracket+tilde" "echo {~,~root}/pwd" "$HOME/pwd $EXPECT/pwd\n" "" ""
 
+#testing "backtick1" 'X=fred; echo `echo $x`' 'fred\n' "" ""
+#testing "backtick2" 'X=fred; echo `x=y; echo $x`' 'y\n' "" ""
+testing '$(( ) )' 'echo $((echo hello) | tr e x)' "hxllo\n" "" ""
+
 testing "leading variable assignment" 'abc=def env | grep ^abc=; echo $abc' \
   "abc=def\n\n" "" ""
 testing "leading variable assignments" \
   "abc=def ghi=jkl env | egrep '^(abc|ghi)=' | sort; echo \$abc \$ghi" \
   "abc=def\nghi=jkl\n\n" "" ""
+
+#$ IFS=x X=xyxz; for i in abc${X}def; do echo =$i=; done
+#=abc=
+#=y=
+#=zdef=
+
+testing "IFS whitespace before/after" \
+  'IFS=" x"; A=" x " B=" x" C="x " D=x E="   "; for i in $A $B $C $D L$A L$B L$C L$D $A= $B= $C= $D= L$A= L$B= L$C= L$D=; do echo -n {$i}; done' \
+  "{}{}{}{}{L}{L}{L}{L}{}{=}{}{=}{}{=}{}{=}{L}{=}{L}{=}{L}{=}{L}{=}" "" ""
+testing "quotes and whitespace" \
+  'A="   abc   def   "; for i in ""$A""; do echo =$i=; done' \
+  "==\n=abc=\n=def=\n==\n" "" ""
+testing "quotes and whitespace2" \
+  'A="   abc   def   "; for i in """"$A""; do echo =$i=; done' \
+  "==\n=abc=\n=def=\n==\n" "" ""
+testing "quotes and whitespace3" \
+  'A="   abc   def   "; for i in ""x""$A""; do echo =$i=; done' \
+  "=x=\n=abc=\n=def=\n==\n" "" ""
+
+testing "IFS" 'IFS=x; A=abx; echo -n "$A"' "abx" "" ""
+testing "IFS2" 'IFS=x; A=abx; echo -n $A' "ab" "" ""
+testing "IFS3" 'IFS=x; echo "$(echo abx)"' "abx\n" "" ""
+testing "IFS4" "IFS=x; echo \"\$(echo ab' ')\"" "ab \n" "" ""
+
+testing '$*' 'cc(){ for i in $*;do echo =$i=;done;};cc "" "" "" "" ""' \
+  "" "" ""
+testing '$*2' 'cc(){ for i in "$*";do echo =$i=;done;};cc ""' \
+  "==\n" "" ""
+testing '$*3... Flame. Flames. Flames, on the side of my face...' \
+  'cc(){ for i in "$*";do echo =$i=;done;};cc "" ""' "= =\n" "" ""
+testing 'why... oh.' \
+  'cc() { echo ="$*"=; for i in =$*=; do echo -$i-; done;}; cc "" ""; echo and; cc ""' \
+  '= =\n-=-\n-=-\nand\n==\n-==-\n' "" ""
+testing 'really?' 'cc() { for i in $*; do echo -$i-; done;}; cc "" "" ""' \
+  "" "" ""
+testing 'Sigh.' 'cc() { echo =$1$2=;}; cc "" ""' "==\n" "" ""
+testing '$*4' 'cc(){ for i in "$*";do echo =$i=;done;};cc "" "" "" "" ""' \
+  "= =\n" "" ""
+testing '$*5' 'cc(){ for i in "$*";do echo =$i=;done;};cc "" "abc" ""' \
+  "= abc =\n" "" ""
+
+# creating empty arguments without quotes
+testing '$* + IFS' \
+  'IFS=x; cc(){ for i in $*; do echo =$i=;done;};cc xabcxx' \
+  "==\n=abc=\n==\n" "" ""
+testing '$@' 'cc(){ for i in "$@";do echo =$i=;done;};cc "" "" "" "" ""' \
+  "==\n==\n==\n==\n==\n" "" ""
+testing "IFS10" 'IFS=bcd; A=abcde; for i in $A; do echo =$i=; done' \
+  "=a=\n==\n==\n=e=\n" "" ""
+
+testing "IFS combinations" \
+  'IFS=" x"; A=" x " B=" x" C="x " D=x E="   "; for i in $A $B $C $D L$A L$B L$C L$D $A= $B= $C= $D= L$A= L$B= L$C= L$D=; do echo -n {$i}; done' \
+  "{}{}{}{}{L}{L}{L}{L}{}{=}{}{=}{}{=}{}{=}{L}{=}{L}{=}{L}{=}{L}{=}" "" ""
+
+testing "! isn't special" "echo !" "!\n" "" ""
+testing "! by itself" '!; echo $?' "1\n" "" ""
+testing "! true" '! true; echo $?' "1\n" "" ""
+testing "! ! true" '! ! true; echo $?' "0\n" "" ""
+
+# The bash man page doesn't say quote removal here, and yet:
+testing "case quoting" 'case a in "a") echo hello;; esac' 'hello\n' "" ""
+
+testing "subshell splitting" 'for i in $(true); do echo =$i=; done' "" "" ""
+#testing "subshell split 2"
+
+# variable assignment argument splitting only performed for "$@"
+testing "assignment splitting" 'X="one two"; Y=$X; echo $Y' "one two\n" "" ""
+testing "argument splitting" \
+  'chicken() { for i in a"$@"b;do echo =$i=;done;}; chicken 123 456 789' \
+  "=a123=\n=456=\n=789b=\n" "" ""
+testing "assignment splitting2" 'pop(){ X="$@";};pop one two three; echo $X' \
+  "one two three\n" "" ""
+
 #testing "leading assignments don't affect current line" \
 #  'VAR=12345 echo ${VAR}a' "a\n" "" ""
 #testing "can't have space before first : but yes around arguments" \
 #  'BLAH=abcdefghi; echo ${BLAH: 1 : 3 }' "bcd\n" "" ""
 
+testing "subshell exit err" '(exit 42); echo $?' "42\n" "" ""
+
+# Same thing twice, but how do we cmp if exec exited?
+#testing 'exec and $$' testing 'echo $$;exec readlink /proc/self' 
+
+X="$(realpath $(which readlink))"
+testing "exec in paren" \
+  '(exec readlink /proc/self/exe);echo hello' "$X\nhello\n" "" ""
+testing "exec in brackets" \
+  "{ exec readlink /proc/self/exe;};echo hi" "$X\n" "" ""
+
 NOSPACE=1 testing "curly brackets and pipe" \
   '{ echo one; echo two ; } | tee blah.txt; wc blah.txt' \
   "one\ntwo\n2 2 8 blah.txt\n" "" ""
 NOSPACE=1 testing "parentheses and pipe" \
   '(echo two;echo three)|tee blah.txt;wc blah.txt' \
   "two\nthree\n2 2 10 blah.txt\n" "" ""
+#testing "pipe into parentheses" \
+#  'echo hello | (read i <input; echo $i; read i; echo $i)' \
+#  "there\nhello\n" "there\n" ""
 
 # texpect "name" "command" E/O/I"string"
 
@@ -77,7 +198,10 @@
 txpect "wait for <(exit)" "$SH" "E$P" "Icat <(echo hello 1>&2)\n" $'Ehello\n' \
   "E$P" X0
 
-
+# TODO: The txpect plumbing does not work right yet even on TEST_HOST
+#txpect "backtick0" "$SH" "E$P" 'IX=fred; echo `echo \\\\$x`'$'\n' 'Ofred' "E$P" X0
+#txpect "backtick1" "$SH" "E$P" 'IX=fred; echo `echo $x`'$'\n' 'Ofred'$'\n' "E$P" X0
+#txpect "backtick2" "$SH" "E$P" 'IX=fred; echo `x=y; echo $x`' $'Oy\n' "E$P" X0
 
 # $@ $* $# $? $- $$ $! $0
 # always exported: PWD SHLVL _
diff --git a/toys/net/ping.c b/toys/net/ping.c
index 9ae7c85..63be72c 100644
--- a/toys/net/ping.c
+++ b/toys/net/ping.c
@@ -5,7 +5,7 @@
  * Not in SUSv4.
  *
  * Note: ping_group_range should never have existed. To disable it, do:
- *   echo 0 $(((1<<31)-1)) > /proc/sys/net/ipv4/ping_group_range
+ *   echo 0 999999999 > /proc/sys/net/ipv4/ping_group_range
  * (Android does this by default in its init script.)
  *
  * Yes, I wimped out and capped -s at sizeof(toybuf), waiting for a complaint...
@@ -58,12 +58,13 @@
 // Print a summary. Called as a single handler or at exit.
 static void summary(int sig)
 {
-  if (!(toys.optflags&FLAG_q) && TT.sent && TT.sa) {
+  if (!FLAG(q) && TT.sent && TT.sa) {
     printf("\n--- %s ping statistics ---\n", ntop(TT.sa));
     printf("%lu packets transmitted, %lu received, %ld%% packet loss\n",
       TT.sent, TT.recv, ((TT.sent-TT.recv)*100)/(TT.sent?TT.sent:1));
-    printf("round-trip min/avg/max = %lu/%lu/%lu ms\n",
-      TT.min, TT.max, TT.fugit/(TT.recv?TT.recv:1));
+    if (TT.recv)
+      printf("round-trip min/avg/max = %lu/%lu/%lu ms\n",
+        TT.min, TT.fugit/TT.recv, TT.max);
   }
   TT.sa = 0;
 }
@@ -96,10 +97,10 @@
   unsigned short seq = 0, pkttime;
 
   // Set nonstatic default values
-  if (!(toys.optflags&FLAG_i)) TT.i = (toys.optflags&FLAG_f) ? 200 : 1000;
+  if (!FLAG(i)) TT.i = FLAG(f) ? 200 : 1000;
   else if (TT.i<200 && getuid()) error_exit("need root for -i <200");
-  if (!(toys.optflags&FLAG_s)) TT.s = 56; // 64-PHDR_LEN
-  if ((toys.optflags&(FLAG_f|FLAG_c)) == FLAG_f) TT.c = 15;
+  if (!FLAG(s)) TT.s = 56; // 64-PHDR_LEN
+  if (FLAG(f) && !FLAG(c)) TT.c = 15;
 
   // ipv4 or ipv6? (0 = autodetect if -I or arg have only one address type.)
   if (FLAG(6) || strchr(toys.which->name, '6')) family = AF_INET6;
@@ -109,12 +110,10 @@
   // If -I srcaddr look it up. Allow numeric address of correct type.
   memset(&srcaddr, 0, sizeof(srcaddr));
   if (TT.I) {
-    if (!(toys.optflags&FLAG_6) && inet_pton(AF_INET, TT.I,
-      (void *)&srcaddr.in.sin_addr))
-        family = AF_INET;
-    else if (!(toys.optflags&FLAG_4) && inet_pton(AF_INET6, TT.I,
-      (void *)&srcaddr.in6.sin6_addr))
-        family = AF_INET6;
+    if (!FLAG(6) && inet_pton(AF_INET, TT.I, (void *)&srcaddr.in.sin_addr))
+      family = AF_INET;
+    else if (!FLAG(4) && inet_pton(AF_INET6, TT.I, (void *)&srcaddr.in6.sin6_addr))
+      family = AF_INET6;
     else if (getifaddrs(&ifa2)) perror_exit("getifaddrs");
   }
 
@@ -150,28 +149,26 @@
   if (TT.sock == -1) {
     perror_msg("socket SOCK_DGRAM %x", len);
     if (errno == EACCES) {
-      fprintf(stderr, "Kernel bug workaround (as root):\n");
-      fprintf(stderr, "echo 0 9999999 > /proc/sys/net/ipv4/ping_group_range\n");
+      fprintf(stderr, "Kernel bug workaround:\n"
+        "echo 0 99999999 | sudo tee /proc/sys/net/ipv4/ping_group_range\n");
     }
     xexit();
   }
   if (TT.I) xbind(TT.sock, sa, sizeof(srcaddr));
 
-  if (toys.optflags&FLAG_m) {
-      int mark = TT.m;
-
-      xsetsockopt(TT.sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark));
+  if (FLAG(m)) {
+    len = TT.m;
+    xsetsockopt(TT.sock, SOL_SOCKET, SO_MARK, &len, 4);
   }
 
   if (TT.t) {
     len = TT.t;
-
     if (ai->ai_family == AF_INET)
       xsetsockopt(TT.sock, IPPROTO_IP, IP_TTL, &len, 4);
     else xsetsockopt(TT.sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &len, 4);
   }
 
-  if (!(toys.optflags&FLAG_q)) {
+  if (!FLAG(q)) {
     printf("Ping %s (%s)", *toys.optargs, ntop(TT.sa));
     if (TT.I) {
       *toybuf = 0;
@@ -180,6 +177,7 @@
     // 20 byte TCP header, 8 byte ICMP header, plus data payload
     printf(": %ld(%ld) bytes.\n", TT.s, TT.s+28);
   }
+  TT.min = ULONG_MAX;
   toys.exitval = 1;
 
   tW = tw = 0;
@@ -214,11 +212,10 @@
       ih->un.echo.sequence = ++seq;
       if (TT.s >= 4) *(unsigned *)(ih+1) = tnow;
 
-      ih->checksum = 0;
       ih->checksum = pingchksum((void *)toybuf, TT.s+sizeof(*ih));
       xsendto(TT.sock, toybuf, TT.s+sizeof(*ih), TT.sa);
       TT.sent++;
-      if ((toys.optflags&(FLAG_f|FLAG_q)) == FLAG_f) xputc('.');
+      if (FLAG(f) && !FLAG(q)) xputc('.');
 
       // last packet?
       if (TT.c) if (!--TT.c) {
@@ -238,16 +235,17 @@
 
     TT.recv++;
     TT.fugit += (pkttime = millitime()-*(unsigned *)(ih+1));
+    if (pkttime < TT.min) TT.min = pkttime;
+    if (pkttime > TT.max) TT.max = pkttime;
 
     // reply id == 0 for ipv4, 129 for ipv6
 
-    if (!(toys.optflags&FLAG_q)) {
-      if (toys.optflags&FLAG_f) xputc('\b');
+    if (!FLAG(q)) {
+      if (FLAG(f)) xputc('\b');
       else {
         printf("%d bytes from %s: icmp_seq=%d ttl=%d", len, ntop(&srcaddr2.s),
                ih->un.echo.sequence, 0);
-        if (len >= sizeof(*ih)+4)
-          printf(" time=%u ms", pkttime);
+        if (len >= sizeof(*ih)+4) printf(" time=%u ms", pkttime);
         xputc('\n');
       }
     }
diff --git a/toys/other/lsattr.c b/toys/other/lsattr.c
index cd236b9..3e32792 100644
--- a/toys/other/lsattr.c
+++ b/toys/other/lsattr.c
@@ -67,6 +67,8 @@
   long p;
 
   long add, rm, set;
+  // !add and !rm tell us whether they were used, but `chattr =` is meaningful.
+  int have_set;
 )
 
 #define FS_PROJINHERT_FL 0x20000000 // Linux 4.5
@@ -264,6 +266,7 @@
           TT.add |= get_flag_val(*ptr);
         break;
       case '=':
+        TT.have_set = 1;
         for (ptr = ++arg; *ptr; ptr++)
           TT.set |= get_flag_val(*ptr);
         break;
@@ -297,7 +300,7 @@
   }
 
   // Any potential flag changes?
-  if (TT.set | TT.add | TT.set) {
+  if (TT.have_set | TT.add | TT.rm) {
     unsigned long orig, new;
 
     // Read current flags.
@@ -308,7 +311,7 @@
       return DIRTREE_ABORT;
     }
     // Apply the requested changes.
-    if (TT.set) new = TT.set; // '='.
+    if (TT.have_set) new = TT.set; // '='.
     else { // '-' and/or '+'.
       new = orig;
       new &= ~(TT.rm);
@@ -347,10 +350,10 @@
   if (TT.p < 0 || TT.p > UINT_MAX) error_exit("bad projid %lu", TT.p);
   if (TT.v < 0 || TT.v > UINT_MAX) error_exit("bad version %ld", TT.v);
   if (!*argv) help_exit("no file");
-  if (TT.set && (TT.add || TT.rm))
+  if (TT.have_set && (TT.add || TT.rm))
     error_exit("no '=' with '-' or '+'");
   if (TT.rm & TT.add) error_exit("set/unset same flag");
-  if (!(TT.add || TT.rm || TT.set || FLAG(p) || FLAG(v)))
+  if (!(TT.add || TT.rm || TT.have_set || FLAG(p) || FLAG(v)))
     error_exit("need '-p', '-v', '=', '-', or '+'");
   for (; *argv; argv++) dirtree_read(*argv, update_attr);
 }
diff --git a/toys/pending/wget.c b/toys/pending/wget.c
index 16672a2..21d4446 100644
--- a/toys/pending/wget.c
+++ b/toys/pending/wget.c
@@ -144,8 +144,7 @@
   if(!toys.optargs[0]) help_exit("no URL");
   get_info(toys.optargs[0], hostname, port, path);
 
-
-  sprintf("/%s", TOYBOX_VERSION);
+  sprintf(ua+11, "/%s", TOYBOX_VERSION);
   for (;; redirects--) {
     sock = conn_svr(hostname, port);
     // compose HTTP request
diff --git a/toys/posix/patch.c b/toys/posix/patch.c
index f26e0d7..d3d7779 100644
--- a/toys/posix/patch.c
+++ b/toys/posix/patch.c
@@ -49,9 +49,8 @@
   char *i, *d;
   long p, g, F;
 
-  struct double_list *current_hunk;
-  long oldline, oldlen, newline, newlen;
-  long linenum;
+  void *current_hunk;
+  long oldline, oldlen, newline, newlen, linenum, outnum;
   int context, state, filein, fileout, filepatch, hunknum;
   char *tempname;
 )
@@ -65,20 +64,17 @@
 
 static void do_line(void *data)
 {
-  struct double_list *dlist = (struct double_list *)data;
+  struct double_list *dlist = data;
 
-  if (TT.state>1 && *dlist->data != TT.state) {
-    char *s = dlist->data+(TT.state>3);
-    int i = TT.state == 2 ? 2 : TT.fileout;
+  TT.outnum++;
+  if (TT.state>1)
+    if (0>dprintf(TT.state==2 ? 2 : TT.fileout,"%s\n",dlist->data+(TT.state>3)))
+      perror_exit("write");
 
-    xwrite(i, s, strlen(s));
-    xwrite(i, "\n", 1);
-  }
+  if (FLAG(x))
+    fprintf(stderr, "DO %d %ld: %s\n", TT.state, TT.outnum, dlist->data);
 
-  if (FLAG(x)) fprintf(stderr, "DO %d: %s\n", TT.state, dlist->data);
-
-  free(dlist->data);
-  free(data);
+  llist_free_double(data);
 }
 
 static void finish_oldfile(void)
@@ -121,26 +117,24 @@
 
 // Given a hunk of a unified diff, make the appropriate change to the file.
 // This does not use the location information, but instead treats a hunk
-// as a sort of regex.  Copies data from input to output until it finds
+// as a sort of regex. Copies data from input to output until it finds
 // the change to be made, then outputs the changed data and returns.
-// (Finding EOF first is an error.)  This is a single pass operation, so
+// (Finding EOF first is an error.) This is a single pass operation, so
 // multiple hunks must occur in order in the file.
 
 static int apply_one_hunk(void)
 {
-  struct double_list *plist, *buf = NULL, *check;
-  int matcheof, trailing = 0, reverse = FLAG(R), backwarn = 0, allfuzz=0, fuzz;
-  int (*lcmp)(char *aa, char *bb);
-
-  lcmp = FLAG(l) ? (void *)loosecmp : (void *)strcmp;
-  dlist_terminate(TT.current_hunk);
+  struct double_list *plist, *buf = 0, *check;
+  int matcheof, trail = 0, reverse = FLAG(R), backwarn = 0, allfuzz = 0, fuzz,i;
+  int (*lcmp)(char *aa, char *bb) = FLAG(l) ? (void *)loosecmp : (void *)strcmp;
 
   // Match EOF if there aren't as many ending context lines as beginning
+  dlist_terminate(TT.current_hunk);
   for (plist = TT.current_hunk; plist; plist = plist->next) {
     char c = *plist->data, *s;
 
-    if (c==' ') trailing++;
-    else trailing = 0;
+    if (c==' ') trail++;
+    else trail = 0;
 
     // Only allow fuzz if 2 context lines have multiple nonwhitespace chars.
     // avoids the "all context was blank or } lines" issue. Removed lines
@@ -153,9 +147,11 @@
 
     if (FLAG(x)) fprintf(stderr, "HUNK:%s\n", plist->data);
   }
-  matcheof = !trailing || trailing < TT.context;
+  matcheof = !trail || trail < TT.context;
   if (allfuzz<2) allfuzz = 0;
   else allfuzz = FLAG(F) ? TT.F : TT.context ? TT.context-1 : 0;
+  if (allfuzz>=sizeof(toybuf)/sizeof(long))
+    allfuzz = (sizeof(toybuf)/sizeof(long))-1;
 
   if (FLAG(x)) fprintf(stderr,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
 
@@ -163,13 +159,10 @@
   // lines and all lines to be removed until we've found the end of a
   // complete hunk.
   plist = TT.current_hunk;
-  fuzz = allfuzz;
-  buf = NULL;
-
+  fuzz = 0;
   for (;;) {
     char *data = get_line(TT.filein);
 
-    TT.linenum++;
     // Figure out which line of hunk to compare with next. (Skip lines
     // of the hunk we'd be adding.)
     while (plist && *plist->data == "+-"[reverse]) {
@@ -192,9 +185,11 @@
       // File ended before we found a place for this hunk.
       fail_hunk();
       goto done;
-    } else if (FLAG(x)) fprintf(stderr, "IN: %s\n", data);
+    } else {
+      TT.linenum++;
+      if (FLAG(x)) fprintf(stderr, "IN: %s\n", data);
+    }
     check = dlist_add(&buf, data);
-
     // Compare this line with next expected line of hunk.
 
     // A match can fail because the next line doesn't match, or because
@@ -203,11 +198,13 @@
     // If match failed, flush first line of buffered data and
     // recheck buffered data for a new match until we find one or run
     // out of buffer.
-
-    for (;;) {
+    for (i = 0;; i++) {
       if (!plist || lcmp(check->data, plist->data+1)) {
-        if (plist && *plist->data == ' ' && fuzz-->0) {
-          if (FLAG(x)) fprintf(stderr, "FUZZED: %s\n", plist->data);
+        if (plist && *plist->data == ' ' && fuzz<allfuzz) {
+          if (FLAG(x))
+            fprintf(stderr, "FUZZED: %ld %s\n", TT.linenum, plist->data);
+          ((long *)toybuf)[fuzz++] = TT.outnum+i;
+
           goto fuzzed;
         }
 
@@ -226,7 +223,7 @@
         }
 
         // If this hunk must match start of file, fail if it didn't.
-        if (!TT.context || trailing>TT.context) {
+        if (!TT.context || trail>TT.context) {
           fail_hunk();
           goto done;
         }
@@ -234,7 +231,8 @@
         TT.state = 3;
         do_line(check = dlist_pop(&buf));
         plist = TT.current_hunk;
-        fuzz = allfuzz;
+        memset(toybuf, 0, (fuzz+7)/8);
+        fuzz = 0;
 
         // If we've reached the end of the buffer without confirming a
         // match, read more lines.
@@ -254,14 +252,21 @@
 out:
   // We have a match.  Emit changed data.
   TT.state = "-+"[reverse];
-  llist_traverse(TT.current_hunk, do_line);
-  TT.current_hunk = NULL;
+  allfuzz = 0;
+  while ((plist = dlist_pop(&TT.current_hunk))) {
+    if (TT.state == *plist->data || *plist->data == ' ') {
+      if (((long *)toybuf)[allfuzz] == ++TT.outnum) {
+        dprintf(TT.fileout, "%s\n", buf->data);
+        allfuzz++;
+      } else if (*plist->data == ' ') dprintf(TT.fileout, "%s\n",plist->data+1);
+      llist_free_double(dlist_pop(&buf));
+    } else dprintf(TT.fileout, "%s\n", plist->data+1);
+    llist_free_double(plist);
+  }
+  TT.current_hunk = 0;
   TT.state = 1;
 done:
-  if (buf) {
-    dlist_terminate(buf);
-    llist_traverse(buf, do_line);
-  }
+  llist_traverse(buf, do_line);
 
   return TT.state;
 }
@@ -336,7 +341,7 @@
     // Are we assembling a hunk?
     if (state >= 2) {
       if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
-        dlist_add(&TT.current_hunk, patchline);
+        dlist_add((void *)&TT.current_hunk, patchline);
 
         if (*patchline != '+') TT.oldlen--;
         if (*patchline != '-') TT.newlen--;
@@ -455,8 +460,7 @@
           }
           if (FLAG(dry_run)) TT.fileout = xopen("/dev/null", O_RDWR);
           else TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname);
-          TT.linenum = 0;
-          TT.hunknum = 0;
+          TT.linenum = TT.outnum = TT.hunknum = 0;
         }
       }
 
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index 0c8e8e8..cd8c73e 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -311,13 +311,14 @@
 #define XX 64 // force string representation for sorting, etc
 
 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
+// TODO: ideally, PID and PPID would auto-size too.
 struct typography {
   char *name, *help;
   signed char width, slot;
 } static const typos[] = TAGGED_ARRAY(PS,
   // Numbers. (What's in slot[] is what's displayed, sorted numerically.)
-  {"PID", "Process ID", 5, SLOT_pid},
-  {"PPID", "Parent Process ID", 5, SLOT_ppid},
+  {"PID", "Process ID", 6, SLOT_pid},
+  {"PPID", "Parent Process ID", 6, SLOT_ppid},
   {"PRI", "Priority (dynamic 0 to 139)", 3, SLOT_priority},
   {"NI", "Niceness (static 19 to -20)", 3, SLOT_nice},
   {"ADDR", "Instruction pointer", 4+sizeof(long), SLOT_eip},