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

Change-Id: Icec8c3a977acd7e8123d1a7f0a2adf61cc6f59bb
diff --git a/Config.in b/Config.in
index 8fb1f13..2bfe30a 100644
--- a/Config.in
+++ b/Config.in
@@ -135,6 +135,12 @@
 	  Enable extra checks for debugging purposes. All of them catch
 	  things that can only go wrong at development time, not runtime.
 
+config TOYBOX_PEDANTIC_ARGS
+	bool "Pedantic argument checking"
+	default n
+	help
+	  Check arguments for commands that have no arguments.
+
 config TOYBOX_UID_SYS
 	int "First system UID"
 	default 100
diff --git a/generated/flags.h b/generated/flags.h
index c320588..2103e10 100644
--- a/generated/flags.h
+++ b/generated/flags.h
@@ -2997,12 +2997,13 @@
 #undef FLAG_f
 #endif
 
-// uptime    
+// uptime >0s >0s
 #undef OPTSTR_uptime
-#define OPTSTR_uptime 0
+#define OPTSTR_uptime ">0s"
 #ifdef CLEANUP_uptime
 #undef CLEANUP_uptime
 #undef FOR_uptime
+#undef FLAG_s
 #endif
 
 // useradd   <1>2u#<0G:s:g:h:SDH
@@ -5718,6 +5719,7 @@
 #ifndef TT
 #define TT this.uptime
 #endif
+#define FLAG_s (1<<0)
 #endif
 
 #ifdef FOR_useradd
diff --git a/generated/globals.h b/generated/globals.h
index 1362502..e55cecb 100644
--- a/generated/globals.h
+++ b/generated/globals.h
@@ -1192,7 +1192,9 @@
 // toys/posix/paste.c
 
 struct paste_data {
-  char *delim;
+  char *d;
+
+  int files;
 };
 
 // toys/posix/patch.c
diff --git a/generated/help.h b/generated/help.h
index de26538..26e8b00 100644
--- a/generated/help.h
+++ b/generated/help.h
@@ -4,6 +4,8 @@
 
 #define HELP_toybox_uid_sys "When commands like useradd/groupadd allocate system IDs, start here.\n\n"
 
+#define HELP_toybox_pedantic_args "Check arguments for commands that have no arguments.\n\n"
+
 #define HELP_toybox_debug "Enable extra checks for debugging purposes. All of them catch\nthings that can only go wrong at development time, not runtime.\n\n"
 
 #define HELP_toybox_norecurse "When one toybox command calls another, usually it just calls the new\ncommand's main() function rather than searching the $PATH and calling\nexec on another file (which is much slower).\n\nThis disables that optimization, so toybox will run external commands\n       even when it has a built-in version of that command. This requires\n       toybox symlinks to be installed in the $PATH, or re-invoking the\n       \"toybox\" multiplexer command by name.\n\n"
@@ -134,7 +136,7 @@
 
 #define HELP_usleep "usage: usleep MICROSECONDS\n\nPause for MICROSECONDS microseconds.\n\n"
 
-#define HELP_uptime "usage: uptime\n\nTell how long the system has been running and the system load\naverages for the past 1, 5 and 15 minutes.\n\n"
+#define HELP_uptime "usage: uptime [-s]\n\nTell the current time, how long the system has been running, the number\nof users, and the system load averages for the past 1, 5 and 15 minutes.\n\n-s	Since when has the system been up?\n\n"
 
 #define HELP_truncate "usage: truncate [-c] -s SIZE file...\n\nSet length of file(s), extending sparsely if necessary.\n\n-c	Don't create file if it doesn't exist.\n-s	New size (with optional prefix and suffix)\n\nSIZE prefix: + add, - subtract, < shrink to, > expand to,\n             / multiple rounding down, % multiple rounding up\nSIZE suffix: k=1024, m=1024^2, g=1024^3, t=1024^4, p=1024^5, e=1024^6\n\n"
 
@@ -496,7 +498,7 @@
 
 #define HELP_patch "usage: patch [-d DIR] [-i file] [-p depth] [-Rlu] [--dry-run]\n\nApply a unified diff to one or more files.\n\n-d	modify files in DIR\n-i	Input file (defaults=stdin)\n-l	Loose match (ignore whitespace)\n-p	Number of '/' to strip from start of file paths (default=all)\n-R	Reverse patch.\n-u	Ignored (only handles \"unified\" diffs)\n--dry-run Don't change files, just confirm patch applies\n\nThis version of patch only handles unified diffs, and only modifies\na file when all all hunks to that file apply.  Patch prints failed\nhunks to stderr, and exits with nonzero status if any hunks fail.\n\nA file compared against /dev/null (or with a date <= the epoch) is\ncreated/deleted as appropriate.\n\n"
 
-#define HELP_paste "usage: paste [-s] [-d list] [file...]\n\nReplace newlines in files.\n\n-d list    list of delimiters to separate lines\n-s         process files sequentially instead of in parallel\n\nBy default print corresponding lines separated by <tab>.\n\n"
+#define HELP_paste "usage: paste [-s] [-d DELIMITERS] [FILE...]\n\nMerge corresponding lines from each input file.\n\n-d	list of delimiter characters to separate fields with (default is \\t)\n-s	sequential mode: turn each input file into one line of output\n\n"
 
 #define HELP_od "usage: od [-bcdosxv] [-j #] [-N #] [-w #] [-A doxn] [-t acdfoux[#]]\n\n-A	Address base (decimal, octal, hexdecimal, none)\n-j	Skip this many bytes of input\n-N	Stop dumping after this many bytes\n-t	Output type a(scii) c(har) d(ecimal) f(loat) o(ctal) u(nsigned) (he)x\n	plus optional size in bytes\n	aliases: -b=-t o1, -c=-t c, -d=-t u2, -o=-t o2, -s=-t d2, -x=-t x2\n-v	Don't collapse repeated lines together\n-w	Total line width in bytes (default 16).\n\n"
 
diff --git a/generated/newtoys.h b/generated/newtoys.h
index 8afcc8b..2eba304 100644
--- a/generated/newtoys.h
+++ b/generated/newtoys.h
@@ -167,7 +167,7 @@
 USE_OPENVT(NEWTOY(openvt, "c#<1>63sw", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 USE_PARTPROBE(NEWTOY(partprobe, "<1", TOYFLAG_SBIN))
 USE_PASSWD(NEWTOY(passwd, ">1a:dlu", TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN))
-USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_BIN))
+USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_PATCH(NEWTOY(patch, "(dry-run)"USE_TOYBOX_DEBUG("x")"d:ulp#i:R", 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))
@@ -260,7 +260,7 @@
 USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN))
 USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN))
-USE_UPTIME(NEWTOY(uptime, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_UPTIME(NEWTOY(uptime, ">0s", TOYFLAG_USR|TOYFLAG_BIN))
 USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN))
 USE_USERDEL(NEWTOY(userdel, "<1>1r", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 USE_USLEEP(NEWTOY(usleep, "<1", TOYFLAG_BIN))
diff --git a/lib/toyflags.h b/lib/toyflags.h
index e809901..859cca2 100644
--- a/lib/toyflags.h
+++ b/lib/toyflags.h
@@ -28,3 +28,9 @@
 
 // Suppress default --help processing
 #define TOYFLAG_NOHELP   (1<<9)
+
+#if CFG_TOYBOX_PEDANTIC_ARGS
+#define NO_ARGS ">0"
+#else
+#define NO_ARGS 0
+#endif
diff --git a/scripts/make.sh b/scripts/make.sh
index 6b85384..5792847 100755
--- a/scripts/make.sh
+++ b/scripts/make.sh
@@ -182,6 +182,7 @@
     else
       $SED '/USE_.*([^)]*)$/s/$/ __VA_ARGS__/' generated/config.h
     fi
+    echo '#include "lib/toyflags.h"'
     cat generated/newtoys.h
 
     # Run result through preprocessor, glue together " " gaps leftover from USE
diff --git a/tests/paste.test b/tests/paste.test
new file mode 100755
index 0000000..c067f18
--- /dev/null
+++ b/tests/paste.test
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+UTFTEST="$(echo -e '\xcc\xb4\xcc\x97\xcc\x9e\xcc\xa0\x65')"
+echo -e "one1\none2\none3" > one
+echo -e "two1\ntwo2\ntwo3" > two
+touch three four
+echo five > five
+testing "" "paste one two" "one1\ttwo1\none2\ttwo2\none3\ttwo3\n" "" ""
+testing "" "paste -s one two" "one1\tone2\tone3\ntwo1\ttwo2\ttwo3\n" "" ""
+testing "" "paste -s three four" "\n\n" "" ""
+testing "" "paste three four" "" "" ""
+testing "" "paste - - -" "uno\tdos\tdesqview\nunix\t\t\n" "" "uno\ndos\ndesqview\nunix\n"
+testing "" "paste - - - -d ''" "unodosdesqview\nunix\n" "" "uno\ndos\ndesqview\nunix\n"
+testing "" "paste one two one two" \
+  "one1\ttwo1\tone1\ttwo1\none2\ttwo2\tone2\ttwo2\none3\ttwo3\tone3\ttwo3\n" \
+  "" ""
+testing "" "paste five three two" "five\t\ttwo1\n\t\ttwo2\n\t\ttwo3\n" "" ""
+testing "" "paste -d '私\0${UTFTEST}q' - - - - - - " \
+  "one私twothree${UTFTEST}fourqfive私six\n7私89${UTFTEST}q私\n" \
+  "" "one\ntwo\nthree\nfour\nfive\nsix\n7\n8\n9\n"
+rm -f one two three four
+unset UTFTEST
+
+# test -d \n
+# test -d \x
+# test 
diff --git a/tests/uptime.test b/tests/uptime.test
new file mode 100644
index 0000000..df65cd0
--- /dev/null
+++ b/tests/uptime.test
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+testing "uptime" "uptime | grep -q 'load average:' && echo t" "t\n" "" ""
+testing "uptime -s" \
+        "uptime -s | grep -q '^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]$' && echo t" \
+        "t\n" "" ""
diff --git a/toys/other/uptime.c b/toys/other/uptime.c
index 91887df..485dfee 100644
--- a/toys/other/uptime.c
+++ b/toys/other/uptime.c
@@ -4,43 +4,54 @@
  * Copyright 2012 Luis Felipe Strano Moraes <lfelipe@profusion.mobi>
  * Copyright 2013 Jeroen van Rijn <jvrnix@gmail.com>
 
-
-USE_UPTIME(NEWTOY(uptime, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_UPTIME(NEWTOY(uptime, ">0s", TOYFLAG_USR|TOYFLAG_BIN))
 
 config UPTIME
   bool "uptime"
   default y
   depends on TOYBOX_UTMPX
   help
-    usage: uptime
+    usage: uptime [-s]
 
-    Tell how long the system has been running and the system load
-    averages for the past 1, 5 and 15 minutes.
+    Tell the current time, how long the system has been running, the number
+    of users, and the system load averages for the past 1, 5 and 15 minutes.
+
+    -s	Since when has the system been up?
 */
 
+#define FOR_uptime
 #include "toys.h"
 
 void uptime_main(void)
 {
   struct sysinfo info;
-  time_t tmptime;
-  struct tm * now;
+  time_t t;
+  struct tm *tm;
   unsigned int days, hours, minutes;
   struct utmpx *entry;
   int users = 0;
 
   // Obtain the data we need.
   sysinfo(&info);
-  time(&tmptime);
-  now = localtime(&tmptime);
+  time(&t);
+
+  // Just show the time of boot?
+  if (toys.optflags & FLAG_s) {
+    t -= info.uptime;
+    tm = localtime(&t);
+    strftime(toybuf, sizeof(toybuf), "%F %T", tm);
+    xputs(toybuf);
+    return;
+  }
 
   // Obtain info about logged on users
   setutxent();
   while ((entry = getutxent())) if (entry->ut_type == USER_PROCESS) users++;
   endutxent();
 
-  // Time
-  xprintf(" %02d:%02d:%02d up ", now->tm_hour, now->tm_min, now->tm_sec);
+  // Current time
+  tm = localtime(&t);
+  xprintf(" %02d:%02d:%02d up ", tm->tm_hour, tm->tm_min, tm->tm_sec);
   // Uptime
   info.uptime /= 60;
   minutes = info.uptime%60;
diff --git a/toys/posix/paste.c b/toys/posix/paste.c
index 5ab3448..42e6a28 100644
--- a/toys/posix/paste.c
+++ b/toys/posix/paste.c
@@ -1,99 +1,131 @@
-/* paste.c - Replace newlines
+/* paste.c - Merge corresponding lines
  *
  * Copyright 2012 Felix Janda <felix.janda@posteo.de>
  *
  * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/paste.html 
  *
-USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_BIN))
+ * Deviations from posix: the FILE argument isn't mandatory, none == '-'
+
+USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_BIN|TOYFLAG_LOCALE))
 
 config PASTE
   bool "paste"
   default y
   help
-    usage: paste [-s] [-d list] [file...]
+    usage: paste [-s] [-d DELIMITERS] [FILE...]
 
-    Replace newlines in files.
+    Merge corresponding lines from each input file.
 
-    -d list    list of delimiters to separate lines
-    -s         process files sequentially instead of in parallel
-
-    By default print corresponding lines separated by <tab>.
+    -d	list of delimiter characters to separate fields with (default is \t)
+    -s	sequential mode: turn each input file into one line of output
 */
+
 #define FOR_paste
 #include "toys.h"
 
 GLOBALS(
-  char *delim;
+  char *d;
+
+  int files;
 )
 
+// \0 is weird, and -d "" is also weird.
+
+static void paste_files(void)
+{
+  FILE **fps = (void *)toybuf;
+  char *dpos, *dstr, *buf, c;
+  int i, any, dcount, dlen, len, seq = toys.optflags&FLAG_s;
+
+  // Loop through lines until no input left
+  for (;;) {
+
+    // Start of each line/file resets delimiter cycle
+    dpos = TT.d;
+    mbtowc(0, 0, 0);
+
+    for (i = any = dcount = dlen = 0; seq || i<TT.files; i++) {
+      size_t blen;
+      wchar_t wc;
+      FILE *ff = seq ? *fps : fps[i];
+
+      // Read and output line, preserving embedded NUL bytes.
+
+      buf = 0;
+      len = 0;
+      if (!ff || 0>=(len = getline(&buf, &blen, ff))) {
+        if (ff && ff!=stdin) fclose(ff);
+        if (seq) return;
+        fps[i] = 0;
+        if (!any) continue;
+      }
+      dcount = any ? 1 : i;
+      any = 1;
+
+      // Output delimiters as necessary: not at beginning/end of line,
+      // catch up if first few files had no input but a later one did.
+      // Entire line with no input means no output.
+
+      while (dcount) {
+
+        // Find next delimiter, which can be "", \n, or UTF8 w/combining chars
+        dstr = dpos;
+        dlen = 0;
+        dcount--;
+
+        if (!*TT.d) {;}
+        else if (*dpos == '\\') {
+          if (*++dpos=='0') dpos++;
+          else {
+            dlen = 1;
+            if ((c = unescape(*dpos))) {
+              dstr = &c;
+              dpos++;
+            }
+          }
+        } else {
+          while (0<(dlen = mbtowc(&wc, dpos, 99))) {
+            dpos += dlen;
+            if (!(dlen = wcwidth(wc))) continue;
+            if (dlen<0) dpos = dstr+1;
+            break;
+          }
+          dlen = dpos-dstr;
+        }
+        if (!*dpos) dpos = TT.d;
+
+        if (dlen) fwrite(dstr, dlen, 1, stdout);
+      }
+
+      if (0<len) {
+        fwrite(buf, len-(buf[len-1]=='\n'), 1, stdout);
+        free(buf);
+      }
+    }
+
+    // Only need a newline if we output something
+    if (any) xputc('\n');
+    else break;
+  }
+}
+
+static void do_paste(int fd, char *name)
+{
+  FILE **fps = (void *)toybuf;
+
+  if (!(fps[TT.files++] = (fd ? fdopen(fd, "r") : stdin))) perror_exit(0);
+  if (TT.files >= sizeof(toybuf)/sizeof(FILE *)) perror_exit("tilt");
+  if (toys.optflags&FLAG_s) {
+    paste_files();
+    xputc('\n');
+    TT.files = 0;
+  }
+}
+
 void paste_main(void)
 {
-  char *p, *buf = toybuf, **args = toys.optargs;
-  size_t ndelim = 0;
-  int i, j, c;
+  if (!(toys.optflags&FLAG_d)) TT.d = "\t";
 
-  // Process delimiter list
-  // TODO: Handle multibyte characters
-  if (!(toys.optflags & FLAG_d)) TT.delim = "\t";
-  for (p = TT.delim; *p; p++, buf++, ndelim++) {
-    if (*p == '\\') {
-      p++;
-      if (-1 == (i = stridx("nt\\0", *p)))
-        error_exit("bad delimiter: \\%c", *p);
-      *buf = "\n\t\\\0"[i];
-    } else *buf = *p;
-  }
-  *buf = 0;
-
-  if (toys.optflags & FLAG_s) { // Sequential
-    FILE *f;
-
-    for (; *args; args++) {
-      if ((*args)[0] == '-' && !(*args)[1]) f = stdin;
-      else if (!(f = fopen(*args, "r"))) perror_exit_raw(*args);
-      for (i = 0, c = 0; c != EOF;) {
-        switch(c = getc(f)) {
-        case '\n':
-          putchar(toybuf[i++ % ndelim]);
-        case EOF:
-          break;
-        default:
-          putchar(c);
-        }
-      }
-      if (f != stdin) fclose(f);
-      putchar('\n');
-    }
-  } else { // Parallel
-    // Need to be careful not to print an extra line at the end
-    FILE **files;
-    int anyopen = 1;
-
-    files = (FILE**)(buf + 1);
-    for (; *args; args++, files++) {
-      if ((*args)[0] == '-' && !(*args)[1]) *files = stdin;
-      else if (!(*files = fopen(*args, "r"))) perror_exit_raw(*args);
-    }
-    while (anyopen) {
-      anyopen = 0;
-      for (i = 0; i < toys.optc; i++) {
-        FILE **f = (FILE**)(buf + 1) + i;
-
-        if (*f) for (;;) {
-          c = getc(*f);
-          if (c != EOF) {
-            if (!anyopen++) for (j = 0; j < i; j++) putchar(toybuf[j % ndelim]);
-            if (c != '\n') putchar(c);
-            else break;
-          }
-          else {
-            if (*f != stdin) fclose(*f);
-            *f = 0;
-            break;
-          }
-        }
-        if (anyopen) putchar((i + 1 == toys.optc) ? toybuf[i % ndelim] : '\n');
-      }
-    }
-  }
+  loopfiles_rw(toys.optargs, O_RDONLY, 0, do_paste);
+  if (!(toys.optflags&FLAG_s)) paste_files();
 }
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index 41ddf6a..0d79b5c 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -896,6 +896,7 @@
   kcount = TT.kcount;
   sprintf(toybuf, "/proc/%u/task", pid);
   new->child = dirtree_flagread(toybuf, DIRTREE_SHUTUP|DIRTREE_PROC, get_ps);
+  if (new->child == DIRTREE_ABORTVAL) new->child = 0;
   TT.threadparent = 0;
   kcount = TT.kcount-kcount+1;
   tb = (void *)new->extra;
@@ -1236,7 +1237,7 @@
     ((toys.optflags&FLAG_T) || (TT.bits&(_PS_TID|_PS_TCNT)))
       ? get_threads : get_ps);
 
-  if (toys.optflags&(FLAG_k|FLAG_M)) {
+  if ((dt != DIRTREE_ABORTVAL) && toys.optflags&(FLAG_k|FLAG_M)) {
     struct carveup **tbsort = collate(TT.kcount, dt);
 
     if (toys.optflags&FLAG_M) {
@@ -1365,6 +1366,7 @@
     dt = dirtree_flagread("/proc", DIRTREE_SHUTUP|DIRTREE_PROC,
       ((toys.optflags&FLAG_H) || (TT.bits&(_PS_TID|_PS_TCNT)))
         ? get_threads : get_ps);
+    if (dt == DIRTREE_ABORTVAL) error_exit("no /proc");
     plnew->tb = collate(plnew->count = TT.kcount, dt);
     TT.kcount = 0;
 
diff --git a/www/faq.html b/www/faq.html
index 2e79c9c..6cc7cdb 100755
--- a/www/faq.html
+++ b/www/faq.html
@@ -3,7 +3,8 @@
 
 <ul>
 <li><h2><a href="#capitalize">Do you capitalize toybox?</a></h2></li>
-<li><h2><a href="#why_toybox">Why is there toybox? What was wrong with busybox?
+<li><h2><a href="#why_toybox">Why toybox? (What was wrong with busybox?)</a></h2></li>
+<li><h2><a href="#support_horizon">Why a 7 year support horizon?</a></h2></li>
 </ul>
 
 <a name="capitalize" />
@@ -16,26 +17,31 @@
 <a name="why_toybox" />
 <h2>Q: "Why is there toybox? What was wrong with busybox?"</h2>
 
-<p>A: To answer the first part: Toybox dates back to when its maintainer
+<p>A: Toybox started back in 2006 when I
 <a href=https://lwn.net/Articles/202106/>handed off BusyBox maintainership</a>
 and <a href=http://landley.net/notes-2006.html#28-09-2006>started over from
 scratch</a> on a new codebase after a
-<a href=http://lists.busybox.net/pipermail/busybox/2006-September/058617.html>protracted licensing argument</a> took all the fun out of
-working on BusyBox. Toybox was just a personal project until it got
-<a href=https://lwn.net/Articles/478308/>relicensed years
-later</a> after its author did a lot of thinking
-<a href=http://landley.net/talks/ohio-2013.txt>about licenses</a>
-and about <a href=http://landley.net/notes-2011.html#21-03-2011>the
-transition to smartphones</a>. This led to the
+<a href=http://lists.busybox.net/pipermail/busybox/2006-September/058617.html>protracted licensing argument</a> took all the fun out of working on BusyBox.</p>
+
+<p>Toybox was just a personal project until it got
+<a href=http://landley.net/notes-2011.html#13-11-2011>relaunched
+in November 2011</a> with a new goal to
+<a href=http://landley.net/aboriginal/about.html#selfhost>make Android
+self-hosting</a>. This involved me relicensing my own
+code, which <a href=https://lwn.net/Articles/478308/>made people who had
+never used or participated in the project loudly angry</a>. The switch came
+after a lot of thinking <a href=http://landley.net/talks/ohio-2013.txt>about
+licenses</a> and <a href=http://landley.net/notes-2011.html#21-03-2011>the
+transition to smartphones</a>, which led to a
 <a href=http://landley.net/talks/celf-2013.txt>2013</a>
 <a href=https://www.youtube.com/watch?v=SGmtP5Lg_t0>talk</a> laying
-out a strategy to make Android self-hosting, which helped
+out a strategy to make Android self-hosting using toybox. This helped
 <a href=https://code.google.com/p/android/issues/detail?id=76861>bring
 it to Android's attention</a>, and they
 <a href=https://lwn.net/Articles/629362/>merged it</a> into Android M.</p>
 
-<p>To answer the second part: BusyBox predates Android
-by almost a decade, but Android still doesn't ship with it because GPLv3 came
+<p>The answer to the second question is "licensing". BusyBox predates Android
+by almost a decade but Android still doesn't ship with it because GPLv3 came
 out around the same time Android did and caused many people to throw
 out the GPLv2 baby with the GPLv3 bathwater.
 Android <a href=https://source.android.com/source/licenses.html>explicitly
@@ -47,9 +53,50 @@
 implemented its SMB server from scratch to replace samba,
 <a href=http://meta.ath0.com/2012/02/05/apples-great-gpl-purge/>and so
 on</a>. Toybox itself exists because somebody with in a legacy position
-just wouldn't shut up about GPLv3, otherwise its maintainer would probably
-still happily be maintaining BusyBox. (For more on how said maintainer wound
+just wouldn't shut up about GPLv3, otherwise I would probably
+still happily be maintaining BusyBox. (For more on how I wound
 up working on busybox in the first place,
 <a href=http://landley.net/aboriginal/history.html>see here</a>.)</p>
 
+<h2><a name="support_horizon">Q: Why a 7 year support horizon?</a></h2>
+
+<p>A: Our <a href=http://lists.busybox.net/pipermail/busybox/2006-September/058440.html>longstanding rule of thumb</a> is to try to run and build on
+hardware and distributions released up to 7 years ago, and feel ok dropping
+support for stuff older than that. (This is a little longer than Ubuntu's
+Long Term Support, but not by much.)</p>
+
+<p>If a kernel or libc feature is less than 7 years old, I try to have a
+build-time configure test for it and let the functionality cleanly drop out.
+I also keep old Ubuntu images around in VMs and perform the occasional
+defconfig build there to see what breaks. (I'm not perfect about this,
+but I accept bug reports.)</p>
+
+<p>My original theory was "4 to 5 18-month cycles of moore's law should cover
+the vast majority of the installed base of PC hardware", loosely based on some
+research I did <a href=http://www.catb.org/esr/halloween/halloween9.html#id2867629>back in 2003</a>
+and <a href=http://catb.org/esr/writings/world-domination/world-domination-201.html#id248066>updated in 2006</a>
+which said that low end systems were 2 iterations of moore's
+law below the high end systems, and that another 2-3 iterations should cover
+the useful lifetime of most systems no longer being sold but still in use and
+potentially being upgraded to new software releases.</p>
+
+<p>It turns out I missed industry changes in the 1990's that stretched the gap
+from low end to high end from 2 cycles to 4 cycles (<a href=http://landley.net/notes-2011.html#26-06-2011>here's my writeup on that</a>; and _that_ analysis
+ignored the switch from PC to smartphone cutting off the R&D air supply of the
+laptop market.  Meanwhile the Moore's Law s-curve started bending
+down in 2000 and these days is pretty flat: the drive for faster clock speeds
+<a href=http://www.anandtech.com/show/613>stumbled</a>
+then <a href=http://www.pcworld.com/article/118603/article.html>died</a>,
+the subsequent drive to go wide maxed out around 4x SMP with ~2 megabyte
+caches for most applications. These days the switch from exponential to
+linear growth in hardware capabilities is
+<a href=https://www.cnet.com/news/end-of-moores-law-its-not-just-about-physics/>common</a>
+<a href=http://www.acm.org/articles/people-of-acm/2016/david-patterson>knowledge</a>.)</p>
+
+<p>But the 7 year rule of thumb stuck around anyway: if a kernel or libc
+feature is less than 7 years old, I try to have a build-time configure test
+for it and let the functionality cleanly drop out. I also keep old Ubuntu
+images around in VMs and perform the occasional defconfig build there to
+see what breaks.</p>
+
 <!--#include file="footer.html" -->
diff --git a/www/news.html b/www/news.html
index ae1170c..fadd895 100755
--- a/www/news.html
+++ b/www/news.html
@@ -8,6 +8,117 @@
 
 <h2>News</h2>
 
+<a name="21-02-2017" /><a href="#21-02-2017"><hr><h2><b>February 21, 2017</b></h2></a>
+<blockquote><p>Only six people in the Galaxy knew that the job of the
+Galactic President was not to wield power but to attract attention
+away from it. Zaphod Beeblebrox was amazingly good at his job.</p>
+<p>- The Hitchhiker's Guide to the Galaxy</p></blockquote>
+
+<p>Despite everything, <a href=downloads/toybox-0.7.3.tar.gz>Toybox 0.7.3</a>
+(<a href=https://github.com/landley/toybox/releases/tag/0.7.3>git commit</a>)
+is out. The <u>new commands</u> this time are <b>ftpget</b>, <b>ftpput</b>, <b>microcom</b>, and <b>ascii</b>.<p>
+
+<p>We also had two command _demotions_ out of defconfig:
+<b>hostid</b> got moved to toys/example and
+switched to "default n" because despite <a href=http://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostid.html>still being in posix</a>
+the concept of a unique 32 bit number identifying a system is something
+Linux outgrew about the time Pauline Middelink wrote the first IP
+Masquerading code. And Elliott did a complete rewrite of <b>dmesg</b> introducing
+two codepaths that I didn't get a chance to unify and didn't want to
+hold up the release for, so that's back in pending.</p>
+
+<p><u>New features</u>: Rob added units to <b>find</b> -atime and friends
+(with the legacy -amin alias). Elliott added color and -w to dmesg, fallocate
+-o, and improved file's ELF parsing. Steve Muckle added -d and finit_module
+support to modprobe. Rob and Elliott tweaked the
+ps/top display format a bit more (extending the USER field from 8 to 18 chars
+and putting + at the end of string fields that got truncated).
+df -a isn't entirely new, but wasn't documented and needed a bugfix.</p>
+
+<p><u>Bugfixes</u>:
+Last release broke oneit because -c didn't get moved to xopen_stdio() (oops).
+Rob and Elliott simultaneously spotted ps padding each line to 99999
+chars when there's no tty (serial console or adb); now it pads to 80 in
+that case but also switches on -w to avoid field truncation. The "tty"
+field also sometimes had trailing debris (that's fixed now). And "top" was
+endlessly redrawing with out tty because receipt of the ANSI size probe
+results would set SIGWINCH, and handling that sent another ansi probe. (Sigh.)
+And while we're there, replace "ADDR" with "BIT" in ps -l so there are
+more than 4 chars left for the "CMD" field on 64 bit systems.</p>
+
+<p>Izabera pointed out that split -b and -l can't mix, and suggested seq should
+multiply to avoid accumulating rounding errors from repeated fractional
+increments. Wang Xiao Jian fixed a bug in sort -k.
+Elliott let getprop use the @ character in property names, and
+Dimitry Invaov removed the name length limit for system properties.
+Elliott also improved some error reporting and improved top -H's display
+of thread names.
+
+<p>Josh Gao pointed out that recursive operations on . and .. could be ignored
+in chmod -R (and the resulting generic fix to dirtree_notdotdot() fixed
+it in several other places).</p>
+
+<p>Justin Cormack caught tar producing a warning to stdout that screwed up
+"tar c" to stdout.
+Rob fixed an option parsing bug (where switching off a --longopt in menuconfig
+confused the parser), and another one where an option excluding itself
+(ala "abc[-ab][!abc]" with "command -a -b") would segfault.</p>
+
+<p>There's some sort of gcc stack over-optimization bug where musl-libc's
+version of vfork() doesn't get marked with attribute(returns_twice) so
+stack varabiles in the same function after that get semi-randomly overwritten
+when the optimizer decides to reclaim the space. So add the attribute
+to the function the XVFORK() wrapper macro calls. (It's a nommu thing.)</p>
+
+<p>Fixed a couple variable size mismatch bugs that were only tested on 64 bit
+(printf %x 64) or only tested on 32 bit (modprobe), removed some
+unnecessary casts in stat.</p>
+
+<p>Continuing attempts to build under Android NDK brought up that posix
+defines the global 'stdout' as a macro, which bionic turns into an array
+member, but a function was using it as an argument name. (How that ever
+worked in the AOSP build, I couldn't tell you, but the argument got
+renamed anyway.)</p>
+
+<p>Several commits argued with clang's warning generation, eventually
+settling on a variant of __attribute__((__shut_up__)).</p>
+
+<p>Android should no longer give spurious error messages
+when you "ps -A | head" about EPIPE on output. (Older versions of bionic
+set an error handler on SIGPIPE, but it shouldn't do that now. More recent
+versions of adb set the SIGPIPE handler to SIGIGN instead of SIGDFL,
+leading to write returning an error message instead of silently killing
+the program. So we set it back to the default.)</p>
+
+<p><u>Docs</u>:
+Removed website link to the gmane archive (which didn't survive gmane's
+change of ownership). The FAQ now answers a _second_ question. (Woo!)
+Some roadmap updates.</p>
+
+<p><u>Build tweaks</u>:
+Upgraded "make install_airlock" target to only warn about missing
+commands (unless $PEDANTIC is set) when it sets up the hermetic build
+path. (The plan is still to implement everything but the toolchain
+binaries in toybox, but in the meantime we're symlinking other stuff from
+the $HOST that isn't ready yet. See
+<a href=https://github.com/landley/mkroot>mkroot</a> for an example using
+this.)</p>
+
+<p>Elliott and Rob continue to poke at building toybox with Android's NDK,
+but it's a work in progress (<a href=http://lists.landley.net/pipermail/toybox-landley.net/2016-December/008767.html>thread</a>). Various changes
+removing libcutils dependencies and adding an selinux dependency to getprop
+are fallout from this.</p>
+
+<p>Cross-compiling from Macs needs to use "gsed" instead of apple's
+version, so teach the build to use that name if it exists in the $PATH.
+If you try to build without running config first, you should get better
+error reporting now. Added a workaround for Centos' broken "which" command
+producing output when it _can't_ find a name in the $PATH.</p>
+
+<p><u>Library</u>:
+The new dirtree flag DIRTREE_PROC skips non-numeric entries so things
+like ps and top can scan /proc more efficiently.</p>
+
 <a name="21-10-2016" /><a href="#21-10-2016"><hr><h2><b>October 21, 2016</b></h2></a>
 <blockquote><p>Probability factor of one to one. We have normality. I repeat,
 we have normality. Anything you still can't cope with is therefore your