OSDN Git Service

Merge changes Ie1705960,I8b94351f,I661540a9,Id2dbdcab,I1223578c, ...
authorElliott Hughes <enh@google.com>
Tue, 6 Oct 2015 21:46:27 +0000 (21:46 +0000)
committerGerrit Code Review <noreply-gerritcodereview@google.com>
Tue, 6 Oct 2015 21:46:27 +0000 (21:46 +0000)
* changes:
  Merge remote-tracking branch 'toybox/master' into HEAD
  Decode netlink sockets in lsof.
  New command: flock.
  Fix bug in od that screwed up position indicator on arm and mips.
  Fix allnoconfig. (Exporting HOSTCC before HOSTCC?=cc caused a problem.)
  Export makefile variables so you can say "make CROSS_COMPILE=blah-" as well as "CROSS_COMPILE=blah- make".
  Minor cleanup and add "MODALIAS" handler (suggested by Isaac Dunham).
  Back to chipping away at ps...
  Add uClinux analysis to roadmap, and a few other pending changes.
  help_exit() tweak.
  xvfork went away.
  Make defconfig build for nommu.
  Another chunk of nommu support, replacing toys.recursion with toys.stacktop.
  xexec() is noreturn.
  Add xvfork() as a static inline and use it from various places.
  The -o /dev/null trick in probing isn't compatible with elf2flt, so use tempfile.
  Fix a couple things gcc is too dumb to figure out on its own.
  Add find tests.

30 files changed:
Makefile
configure
lib/lib.c
lib/lib.h
lib/xwrap.c
main.c
scripts/config2help.c
scripts/make.sh
tests/find.test
toys.h
toys/lsb/su.c
toys/other/blkid.c
toys/other/flock.c [new file with mode: 0644]
toys/other/login.c
toys/other/nbd_client.c
toys/other/netcat.c
toys/other/nsenter.c
toys/other/setsid.c
toys/other/sysctl.c
toys/other/timeout.c
toys/pending/arping.c
toys/pending/lsof.c
toys/pending/mdev.c
toys/pending/ps.c
toys/pending/sh.c
toys/posix/cpio.c
toys/posix/od.c
toys/posix/time.c
toys/posix/xargs.c
www/roadmap.html

index 1ea6929..5422efe 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,14 @@
 # Makefile for toybox.
 # Copyright 2006 Rob Landley <rob@landley.net>
 
+# If people set these on the make command line, use 'em
+# Note that CC defaults to "cc" so the one in configure doesn't get
+# used when scripts/make.sh and care called through "make".
+
+HOSTCC?=cc
+
+export CROSS_COMPILE CFLAGS OPTIMIZE LDOPTIMIZE CC HOSTCC V
+
 all: toybox
 
 KCONFIG_CONFIG ?= .config
@@ -20,8 +28,6 @@ $(KCONFIG_TOP): generated/Config.in
 generated/Config.in: toys/*/*.c scripts/genconfig.sh
        scripts/genconfig.sh
 
-HOSTCC?=cc
-
 # Development targets
 baseline: toybox_unstripped
        @cp toybox_unstripped toybox_old
index 21d56f3..7b10f6e 100644 (file)
--- a/configure
+++ b/configure
 [ -z "$CFLAGS" ] && CFLAGS="-Wall -Wundef -Wno-char-subscripts"
 # Required for our expected ABI. we're 8-bit clean thus "char" must be unsigned.
 CFLAGS="$CFLAGS -funsigned-char"
-[ -z "$OPTIMIZE" ] && OPTIMIZE="-Os -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables"
+[ -z "$OPTIMIZE" ] && OPTIMIZE="-Os -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables -fno-strict-aliasing"
 
 # We accept LDFLAGS, but by default don't have anything in it
 [ -z "$LDOPTIMIZE" ] && LDOPTIMIZE="-Wl,--gc-sections"
 
+# The makefile provides defaults for these, so this only gets used if
+# you call scripts/make.sh and friends directly.
+
 [ -z "$CC" ] && CC=cc
 
 # If HOSTCC needs CFLAGS or LDFLAGS, just add them to the variable
 # ala HOSTCC="blah-cc --static"
-[ -z "$HOSTCC" ] && HOSTCC=gcc
+[ -z "$HOSTCC" ] && HOSTCC=cc
index 9e0a3a5..d9bea89 100644 (file)
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -56,9 +56,11 @@ void help_exit(char *msg, ...)
 
   if (CFG_TOYBOX_HELP) show_help(stderr);
 
-  va_start(va, msg);
-  verror_msg(msg, 0, va);
-  va_end(va);
+  if (msg) {
+    va_start(va, msg);
+    verror_msg(msg, 0, va);
+    va_end(va);
+  }
 
   xexit();
 }
index 2b49dc1..90f44dc 100644 (file)
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -98,6 +98,7 @@ void xputc(char c);
 void xflush(void);
 void xexec(char **argv);
 pid_t xpopen_both(char **argv, int *pipes);
+int xwaitpid(pid_t pid);
 int xpclose_both(pid_t pid, int *pipes);
 pid_t xpopen(char **argv, int *pipe, int stdout);
 pid_t xpclose(pid_t pid, int pipe);
@@ -246,5 +247,8 @@ void mode_to_string(mode_t mode, char *buf);
 char *basename_r(char *name);
 void names_to_pid(char **names, int (*callback)(pid_t pid, char *name));
 
+pid_t xvforkwrap(pid_t pid);
+#define XVFORK() xvforkwrap(vfork())
+
 // Functions in need of further review/cleanup
 #include "lib/pending.h"
index 8086282..4880bbe 100644 (file)
@@ -129,11 +129,26 @@ void xflush(void)
   if (fflush(stdout) || ferror(stdout)) perror_exit("write");;
 }
 
+// This is called through the XVFORK macro because parent/child of vfork
+// share a stack, so child returning from a function would stomp the return
+// address parent would need. Solution: make vfork() an argument so processes
+// diverge before function gets called.
+pid_t xvforkwrap(pid_t pid)
+{
+  if (pid == -1) perror_exit("vfork");
+
+  // Signal to xexec() and friends that we vforked so can't recurse
+  toys.stacktop = 0;
+
+  return pid;
+}
+
 // Die unless we can exec argv[] (or run builtin command).  Note that anything
 // with a path isn't a builtin, so /bin/sh won't match the builtin sh.
 void xexec(char **argv)
 {
-  if (CFG_TOYBOX && !CFG_TOYBOX_NORECURSE) toy_exec(argv);
+  // Only recurse to builtin when we have multiplexer and !vfork context.
+  if (CFG_TOYBOX && !CFG_TOYBOX_NORECURSE && toys.stacktop) toy_exec(argv);
   execvp(argv[0], argv);
 
   perror_msg("exec %s", argv[0]);
@@ -143,25 +158,27 @@ void xexec(char **argv)
 }
 
 // Spawn child process, capturing stdin/stdout.
-// argv[]: command to exec. If null, child returns to original program.
-// pipes[2]: stdin, stdout of new process. If -1 will not have pipe allocated.
+// argv[]: command to exec. If null, child re-runs original program with
+//         toys.stacktop zeroed.
+// pipes[2]: stdin, stdout of new process, only allocated if zero on way in,
+//           pass NULL to skip pipe allocation entirely.
 // return: pid of child process
 pid_t xpopen_both(char **argv, int *pipes)
 {
   int cestnepasun[4], pid;
 
-  // Make the pipes? Not this won't set either pipe to 0 because if fds are
+  // Make the pipes? Note this won't set either pipe to 0 because if fds are
   // allocated in order and if fd0 was free it would go to cestnepasun[0]
   if (pipes) {
     for (pid = 0; pid < 2; pid++) {
-      if (pipes[pid] == -1) continue;
+      if (pipes[pid] != 0) continue;
       if (pipe(cestnepasun+(2*pid))) perror_exit("pipe");
       pipes[pid] = cestnepasun[pid+1];
     }
   }
 
-  // Child process
-  if (!(pid = xfork())) {
+  // Child process.
+  if (!(pid = CFG_TOYBOX_FORK ? xfork() : XVFORK())) {
     // Dance of the stdin/stdout redirection.
     if (pipes) {
       // if we had no stdin/out, pipe handles could overlap, so test for it
@@ -180,16 +197,31 @@ pid_t xpopen_both(char **argv, int *pipes)
         if (cestnepasun[3] > 2 || !cestnepasun[3]) close(cestnepasun[3]);
       }
     }
-    if (argv) {
-      if (CFG_TOYBOX) toy_exec(argv);
-      execvp(argv[0], argv);
+    if (argv) xexec(argv);
+
+    // In fork() case, force recursion because we know it's us.
+    if (CFG_TOYBOX_FORK) {
+      toy_init(toys.which, toys.argv);
+      toys.stacktop = 0;
+      toys.which->toy_main();
+      xexit();
+    // In vfork() case, exec /proc/self/exe with high bit of first letter set
+    // to tell main() we reentered.
+    } else {
+      char *s = "/proc/self/exe";
+
+      // We did a nommu-friendly vfork but must exec to continue.
+      // setting high bit of argv[0][0] to let new process know
+      **toys.argv |= 0x80;
+      execv(s, toys.argv);
+      perror_msg(s);
+
       _exit(127);
     }
-    return 0;
-
   }
 
   // Parent process
+  if (!CFG_TOYBOX_FORK) **toys.argv &= 0x7f;
   if (pipes) {
     if (pipes[0] != -1) close(cestnepasun[0]);
     if (pipes[1] != -1) close(cestnepasun[3]);
@@ -198,17 +230,24 @@ pid_t xpopen_both(char **argv, int *pipes)
   return pid;
 }
 
-int xpclose_both(pid_t pid, int *pipes)
+// Wait for child process to exit, then return adjusted exit code.
+int xwaitpid(pid_t pid)
 {
-  int rc = 127;
+  int status;
 
+  while (-1 == waitpid(pid, &status, 0) && errno == EINTR);
+
+  return WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+127;
+}
+
+int xpclose_both(pid_t pid, int *pipes)
+{
   if (pipes) {
     close(pipes[0]);
     close(pipes[1]);
   }
-  waitpid(pid, &rc, 0);
 
-  return WIFEXITED(rc) ? WEXITSTATUS(rc) : WTERMSIG(rc) + 127;
+  return xwaitpid(pid);
 }
 
 // Wrapper to xpopen with a pipe for just one of stdin/stdout
diff --git a/main.c b/main.c
index dcd486e..1e562b0 100644 (file)
--- a/main.c
+++ b/main.c
@@ -67,7 +67,7 @@ static const int NEED_OPTIONS =
 #include "generated/newtoys.h"
 0;  // Ends the opts || opts || opts...
 
-// Subset of init needed by singlemain
+// Setup toybox global state for this command.
 static void toy_singleinit(struct toy_list *which, char *argv[])
 {
   toys.which = which;
@@ -85,7 +85,7 @@ static void toy_singleinit(struct toy_list *which, char *argv[])
   if (NEED_OPTIONS && which->options) get_optflags();
   else {
     toys.optargs = argv+1;
-    for (toys.optc=0; toys.optargs[toys.optc]; toys.optc++);
+    for (toys.optc = 0; toys.optargs[toys.optc]; toys.optc++);
   }
   toys.old_umask = umask(0);
   if (!(which->flags & TOYFLAG_UMASK)) umask(toys.old_umask);
@@ -93,8 +93,7 @@ static void toy_singleinit(struct toy_list *which, char *argv[])
   toys.toycount = ARRAY_LEN(toy_list);
 }
 
-// Setup toybox global state for this command.
-
+// Full init needed by multiplexer or reentrant calls, calls singleinit at end
 void toy_init(struct toy_list *which, char *argv[])
 {
   // Drop permissions for non-suid commands.
@@ -114,12 +113,12 @@ void toy_init(struct toy_list *which, char *argv[])
   }
 
   // Free old toys contents (to be reentrant), but leave rebound if any
-
-  if (toys.optargs != toys.argv+1) free(toys.optargs);
+  // don't blank old optargs if our new argc lives in the old optargs.
+  if (argv<toys.optargs || argv>toys.optargs+toys.optc) free(toys.optargs);
   memset(&toys, 0, offsetof(struct toy_context, rebound));
-  if (toys.recursion > 1) memset(&this, 0, sizeof(this));
+  if (toys.which) memset(&this, 0, sizeof(this));
 
-  // Subset of init needed by singlemain.
+  // Continue to portion of init needed by standalone commands
   toy_singleinit(which, argv);
 }
 
@@ -129,14 +128,15 @@ void toy_exec(char *argv[])
 {
   struct toy_list *which;
 
-  // Return if we can't find it, or need to re-exec to acquire root,
-  // or if stack depth is getting silly.
-  if (!(which = toy_find(argv[0]))) return;
-  if (toys.recursion && (which->flags & TOYFLAG_ROOTONLY) && getuid()) return;
-  if (toys.recursion++ > 5) return;
+  // Return if we can't find it (which includes no multiplexer case),
+  if (!(which = toy_find(*argv))) return;
 
-  // don't blank old optargs if our new argc lives in the old optargs.
-  if (argv>=toys.optargs && argv<=toys.optargs+toys.optc) toys.optargs = 0;
+  // Return if stack depth getting noticeable (proxy for leaked heap, etc).
+  if (toys.stacktop && labs((char *)toys.stacktop-(char *)&which)>6000)
+    return;
+
+  // Return if we need to re-exec to acquire root via suid bit.
+  if (toys.which && (which->flags&TOYFLAG_ROOTONLY) && getuid()) return;
 
   // Run command
   toy_init(which, argv);
@@ -146,16 +146,19 @@ void toy_exec(char *argv[])
 
 // Multiplexer command, first argument is command to run, rest are args to that.
 // If first argument starts with - output list of command install paths.
-
 void toybox_main(void)
 {
   static char *toy_paths[]={"usr/","bin/","sbin/",0};
   int i, len = 0;
 
+  // fast path: try to exec immediately.
+  // (Leave toys.which null to disable suid return logic.)
+  if (toys.argv[1]) toy_exec(toys.argv+1);
+
+  // For early error reporting
   toys.which = toy_list;
+
   if (toys.argv[1]) {
-    toys.optc = toys.recursion = 0;
-    toy_exec(toys.argv+1);
     if (!strcmp("--version", toys.argv[1])) {
       xputs(TOYBOX_VERSION);
       xexit();
@@ -185,13 +188,27 @@ void toybox_main(void)
 
 int main(int argc, char *argv[])
 {
-  // We check our own stdout errors, disable sigpipe killer
-  signal(SIGPIPE, SIG_IGN);
+  if (!*argv) return 127;
 
-  if (CFG_TOYBOX) {
-    // Trim path off of command name
-    *argv = basename(*argv);
+  // Snapshot stack location so we can detect recursion depth later.
+  // This is its own block so probe doesn't permanently consume stack.
+  else {
+    int stack;
+
+    toys.stacktop = &stack;
+  }
+  *argv = basename_r(*argv);
+
+  // If nommu can't fork, special reentry path.
+  // Use !stacktop to signal "vfork happened", both before and after xexec()
+  if (!CFG_TOYBOX_FORK) {
+    if (0x80 & **argv) {
+      **argv &= 0x7f;
+      toys.stacktop = 0;
+    }
+  }
 
+  if (CFG_TOYBOX) {
     // Call the multiplexer, adjusting this argv[] to be its' argv[1].
     // (It will adjust it back before calling toy_exec().)
     toys.argv = argv-1;
index 2ed189a..f3bb8c3 100644 (file)
@@ -5,6 +5,7 @@ struct toy_context toys;
 char libbuf[4096], toybuf[4096];
 void show_help(FILE *out) {;}
 void toy_exec(char *argv[]) {;}
+void toy_init(struct toy_list *which, char *argv[]) {;}
 
 // Parse config files into data structures.
 
index f6e0c6d..a81f728 100755 (executable)
@@ -96,10 +96,11 @@ then
   for i in util crypt m resolv selinux smack attr
   do
     echo "int main(int argc, char *argv[]) {return 0;}" | \
-    ${CROSS_COMPILE}${CC} $CFLAGS -xc - -o /dev/null -Wl,--as-needed -l$i > /dev/null 2>/dev/null &&
+    ${CROSS_COMPILE}${CC} $CFLAGS -xc - -o generated/libprobe -Wl,--as-needed -l$i > /dev/null 2>/dev/null &&
     echo -l$i >> generated/optlibs.dat
     echo -n .
   done
+  rm -f generated/libprobe
   echo
 fi
 
index 65bbb50..4e856f4 100755 (executable)
@@ -43,4 +43,18 @@ testing "find -print -o -print" \
 # find -type f -user nobody -exec : \;
 # find -type f -user nobody -exec : -exec : \;
 
+# Testing previous failures
+
+testing "find -type f -user -exec" \
+  "find dir -type f -user $USER -exec ls {} \\;" "dir/file\n" "" ""
+testing "find -type l -newer -exec" \
+  "find dir -type l -newer dir/file -exec ls {} \\;" "dir/link\n" "" ""
+testing "find unterminated -exec {}" \
+  "find dir -type f -exec ls {}" "" "" ""
+
+# Still fails
+
+testing "find -exec {} +" \
+  "find dir -type f -exec ls {} +" "dir/file\n" "" ""
+
 rm -rf dir
diff --git a/toys.h b/toys.h
index 6b53955..9c33ff2 100644 (file)
--- a/toys.h
+++ b/toys.h
@@ -132,7 +132,7 @@ extern struct toy_context {
 
   // This is at the end so toy_init() doesn't zero it.
   jmp_buf *rebound;        // longjmp here instead of exit when do_rebound set
-  int recursion;           // How many nested calls to toy_exec()
+  void *stacktop;          // nested toy_exec() call count, or -1 if vforked
 } toys;
 
 // Two big temporary buffers: one for use by commands, one for library functions
index 612daef..7ab2487 100644 (file)
@@ -92,7 +92,6 @@ void su_main()
   }
   while ((*(argv++) = *(toys.optargs++)));
   xexec(argu);
-  perror_exit("can't exec %s", *argu);
 
 deny:
   puts("No.");
index fad1159..8d3e770 100644 (file)
@@ -34,7 +34,7 @@ struct fstype {
 };
 
 static const struct fstype fstypes[] = {
-  {"swap", 0x4341505350415753, 8, 4086, 1036, 15, 1052},
+  {"swap", 0x4341505350415753LL, 8, 4086, 1036, 15, 1052},
   {"ext2", 0xEF53, 2, 1080, 1128, 16, 1144}, // keep this first for ext3/4 check
   // NTFS label actually 8/16 0x4d80 but horrible: 16 bit wide characters via
   // codepage, something called a uuid that's only 8 bytes long...
diff --git a/toys/other/flock.c b/toys/other/flock.c
new file mode 100644 (file)
index 0000000..1d56a56
--- /dev/null
@@ -0,0 +1,40 @@
+/* flock.c - manage advisory file locks
+ *
+ * Copyright 2015 The Android Open Source Project
+
+USE_FLOCK(NEWTOY(flock, "<1>1nsux[-sux]", TOYFLAG_USR|TOYFLAG_BIN))
+
+config FLOCK
+  bool "flock"
+  default y
+  help
+    usage: flock [-sxun] fd
+
+    Manage advisory file locks.
+
+    -s Shared lock.
+    -x Exclusive lock (default).
+    -u Unlock.
+    -n Non-blocking: fail rather than wait for the lock.
+*/
+
+#define FOR_flock
+#include "toys.h"
+
+#include <sys/file.h>
+
+void flock_main(void)
+{
+  int fd = xstrtol(*toys.optargs, NULL, 10);
+  int op;
+
+  if ((toys.optflags & FLAG_u)) op = LOCK_UN;
+  else op = (toys.optflags & FLAG_s) ? LOCK_SH : LOCK_EX;
+
+  if ((toys.optflags & FLAG_n)) op |= LOCK_NB;
+
+  if (flock(fd, op)) {
+    if ((op & LOCK_NB) && errno == EAGAIN) toys.exitval = 1;
+    else perror_exit("flock");
+  }
+}
index c727bf9..7f9559a 100644 (file)
@@ -159,9 +159,7 @@ void login_main(void)
   syslog(LOG_INFO, "%s logged in on %s %s %s", pwd->pw_name,
     ttyname(tty), hh ? "from" : "", hh ? TT.hostname : "");
 
-  // can't xexec here because name doesn't match argv[0]
-  snprintf(toybuf, sizeof(toybuf)-1, "-%s", basename_r(pwd->pw_shell));
-  toy_exec((char *[]){toybuf, 0});
-  execl(pwd->pw_shell, toybuf, NULL);
-  error_exit("Failed to spawn shell");
+  // not using xexec(), login calls absolute path from filesystem so must exec()
+  execl(pwd->pw_shell, xmprintf("-%s", pwd->pw_shell), (char *)0);
+  perror_exit("exec shell '%s'", pwd->pw_shell);
 }
index a82ff7c..3ad366f 100644 (file)
@@ -12,6 +12,7 @@ USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, TOYFLAG_USR|TOYFLAG_BIN))
 
 config NBD_CLIENT
   bool "nbd-client"
+  depends on TOYBOX_FORK
   default y
   help
     usage: nbd-client [-ns] HOST PORT DEVICE
index 3cc3f0a..1c75eb2 100644 (file)
@@ -26,10 +26,10 @@ config NETCAT_LISTEN
   bool "netcat server options (-let)"
   default y
   depends on NETCAT
+  depends on TOYBOX_FORK
   help
-    usage: netcat [-t] [-lL COMMAND...]
+    usage: netcat [-lL COMMAND...]
 
-    -t allocate tty (must come before -l or -L)
     -l listen for one incoming connection.
     -L listen for multiple incoming connections (server mode).
 
@@ -38,6 +38,16 @@ config NETCAT_LISTEN
 
     For a quick-and-dirty server, try something like:
     netcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l
+
+config NETCAT_LISTEN_TTY
+  bool
+  default y
+  depends on NETCAT_LISTEN
+  depends on TOYBOX_FORK
+  help
+    usage: netcat [-t]
+
+    -t allocate tty (must come before -l or -L)
 */
 
 #define FOR_netcat
@@ -139,7 +149,7 @@ void netcat_main(void)
       // Do we need to return immediately because -l has arguments?
 
       if ((toys.optflags & FLAG_l) && toys.optc) {
-        if (xfork()) goto cleanup;
+        if (CFG_TOYBOX_FORK && xfork()) goto cleanup;
         close(0);
         close(1);
         close(2);
@@ -161,7 +171,10 @@ void netcat_main(void)
         // Do we need to fork and/or redirect for exec?
 
         else {
-          if (toys.optflags&FLAG_L) child = fork();
+          if (toys.optflags&FLAG_L) {
+            toys.stacktop = 0;
+            child = vfork();
+          }
           if (!child && toys.optc) {
             int fd = pollfds[0].fd;
 
@@ -183,7 +196,7 @@ void netcat_main(void)
   // (Does not play well with -L, but what _should_ that do?)
   set_alarm(0);
 
-  if (CFG_NETCAT_LISTEN && (toys.optflags&(FLAG_L|FLAG_l) && toys.optc))
+  if (CFG_NETCAT_LISTEN && ((toys.optflags&(FLAG_L|FLAG_l)) && toys.optc))
     xexec(toys.optargs);
 
   // Poll loop copying stdin->socket and socket->stdout.
index 18a2cd2..bda77ac 100644 (file)
@@ -1,14 +1,14 @@
 /* nsenter.c - Enter existing namespaces
  *
- * Copyright 2014 andy Lutomirski <luto@amacapital.net>
+ * Copyright 2014 Andy Lutomirski <luto@amacapital.net>
  *
- * No standard
+ * See http://man7.org/linux/man-pages/man1/nsenter.1.html
  *
  * unshare.c - run command in new context
  *
  * Copyright 2011 Rob Landley <rob@landley.net>
  *
- * No Standard
+ * See http://man7.org/linux/man-pages/man1/unshare.1.html
  *
 
 // Note: flags go in same order (right to left) for shared subset
@@ -149,12 +149,9 @@ void unshare_main(void)
     }
 
     if ((toys.optflags & FLAG_p) && !(toys.optflags & FLAG_F)) {
-      pid_t pid = xfork();
+      toys.exitval = xrun(toys.optargs);
 
-      if (pid) {
-        while (waitpid(pid, 0, 0) == -1 && errno == EINTR);
-        return;
-      }
+      return;
     }
   }
 
index 59a1d78..9569826 100644 (file)
@@ -19,9 +19,9 @@ config SETSID
 
 void setsid_main(void)
 {
-  while (setsid()<0) if (vfork()) _exit(0);
+  while (setsid()<0) if (XVFORK()) _exit(0);
   if (toys.optflags) {
-    setpgid(0,0);
+    setpgid(0, 0);
     tcsetpgrp(0, getpid());
   }
   xexec(toys.optargs);
index d4ed1b0..a123110 100644 (file)
@@ -19,7 +19,7 @@ config SYSCTL
     -e Don't warn about unknown keys
     -N Don't print key values
     -n Don't print key names
-    -p [FILE]  Read values from FILE (default /etc/sysctl.conf)
+    -p Read values from FILE (default /etc/sysctl.conf)
     -q Don't show value after write
     -w Only write values (object to reading)
 */
@@ -148,5 +148,8 @@ void sysctl_main()
     fclose(fp);
 
   // Loop through arguments, displaying or assigning as appropriate
-  } else for (args = toys.optargs; *args; args++) process_key(*args, 0);
+  } else {
+    if (!*toys.optargs) help_exit(0);
+    for (args = toys.optargs; *args; args++) process_key(*args, 0);
+  }
 }
index 06b1e89..0e912f7 100644 (file)
@@ -62,14 +62,10 @@ void timeout_main(void)
   if (TT.s_signal && -1 == (TT.nextsig = sig_to_num(TT.s_signal)))
     error_exit("bad -s: '%s'", TT.s_signal);
 
-  if (!(TT.pid = xfork())) xexec(toys.optargs+1);
+  if (!(TT.pid = XVFORK())) xexec(toys.optargs+1);
   else {
-    int status;
-
     xsignal(SIGALRM, handler);
     setitimer(ITIMER_REAL, &TT.itv, (void *)toybuf);
-    while (-1 == waitpid(TT.pid, &status, 0) && errno == EINTR);
-    toys.exitval = WIFEXITED(status)
-      ? WEXITSTATUS(status) : WTERMSIG(status) + 127;
+    toys.exitval = xwaitpid(TT.pid);
   }
 }
index 3e522bd..be43cab 100644 (file)
@@ -39,15 +39,9 @@ GLOBALS(
     char *src_ip;
 
     int sockfd;
-    unsigned start;
-    unsigned end;
-    unsigned sent_at;
-    unsigned sent_nr;
-    unsigned rcvd_nr;
-    unsigned brd_sent;
-    unsigned rcvd_req;
-    unsigned brd_rcv;
-    unsigned unicast_flag;
+    unsigned long start, end;
+    unsigned sent_at, sent_nr, rcvd_nr, brd_sent, rcvd_req, brd_rcv,
+             unicast_flag;
 )
 
 struct sockaddr_ll src_pk, dst_pk; 
index ac38fdd..18013f7 100644 (file)
@@ -72,7 +72,7 @@ static void print_header()
   char* names[] = {
     "COMMAND", "PID", "USER", "FD", "TYPE", "DEVICE", "SIZE/OFF", "NODE", "NAME"
   };
-  printf("%-9s %5s %10s %4s   %7s %18s %9s %10s %s\n", names[0], names[1],
+  printf("%-9s %5s %10.10s %4s   %7s %18s %9s %10s %s\n", names[0], names[1],
          names[2], names[3], names[4], names[5], names[6], names[7], names[8]);
   TT.shown_header = 1;
 }
@@ -88,7 +88,7 @@ static void print_info(void *data)
       printf("%d\n", (TT.last_shown_pid = fi->pi.pid));
   } else {
     if (!TT.shown_header) print_header();
-    printf("%-9s %5d %10s %4s%c%c %7s %18s %9s %10s %s\n",
+    printf("%-9s %5d %10.10s %4s%c%c %7s %18s %9s %10s %s\n",
            fi->pi.cmd, fi->pi.pid, fi->pi.user,
            fi->fd, fi->rw, fi->locks, fi->type, fi->device, fi->size_off,
            fi->node, fi->name);
@@ -129,9 +129,11 @@ static char *chomp(char *s)
   return s;
 }
 
-static int find_unix_socket(struct file_info *fi, long sought_inode)
+static int scan_proc_net_file(char *path, int family, char type,
+    void (*fn)(char *, int, char, struct file_info *, long),
+    struct file_info *fi, long sought_inode)
 {
-  FILE *fp = fopen("/proc/net/unix", "r");
+  FILE *fp = fopen(path, "r");
   char *line = NULL;
   size_t line_length = 0;
 
@@ -140,19 +142,8 @@ static int find_unix_socket(struct file_info *fi, long sought_inode)
   if (!getline(&line, &line_length, fp)) return 0; // Skip header.
 
   while (getline(&line, &line_length, fp) > 0) {
-    long inode;
-    int path_pos;
-
-    if (sscanf(line, "%*p: %*X %*X %*X %*X %*X %lu %n",
-               &inode, &path_pos) >= 1) {
-      if (inode == sought_inode) {
-        char *name = chomp(line + path_pos);
-
-        strcpy(fi->type, "unix");
-        fi->name = strdup(*name ? name : "socket");
-        break;
-      }
-    }
+    fn(line, family, type, fi, sought_inode);
+    if (fi->name != 0) break;
   }
 
   free(line);
@@ -161,87 +152,102 @@ static int find_unix_socket(struct file_info *fi, long sought_inode)
   return fi->name != 0;
 }
 
-// Matches lines in either /proc/net/tcp or /proc/net/tcp6, depending on 'af'.
-static int ip_match(int af, char* line, struct in6_addr* l, int* l_port,
-                    struct in6_addr* r, int* r_port, int* state, long* inode)
+static void match_unix(char *line, int af, char type, struct file_info *fi,
+                       long sought_inode)
 {
-  if (af == AF_INET) {
-    return sscanf(line, " %*d: %x:%x %x:%x %x %*x:%*x %*X:%*X %*X %*d %*d %ld",
-                  &(l->s6_addr32[0]), l_port, &(r->s6_addr32[0]), r_port,
-                  state, inode) == 6;
-  } else {
-    return sscanf(line, " %*d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x "
-                  "%*x:%*x %*X:%*X %*X %*d %*d %ld",
-                  &(l->s6_addr32[0]), &(l->s6_addr32[1]), &(l->s6_addr32[2]),
-                  &(l->s6_addr32[3]), l_port, &(r->s6_addr32[0]),
-                  &(r->s6_addr32[1]), &(r->s6_addr32[2]), &(r->s6_addr32[3]),
-                  r_port, state, inode) == 12;
+  long inode;
+  int path_pos;
+
+  if (sscanf(line, "%*p: %*X %*X %*X %*X %*X %lu %n", &inode, &path_pos) >= 1 &&
+        inode == sought_inode) {
+    char *name = chomp(line + path_pos);
+
+    strcpy(fi->type, "unix");
+    fi->name = strdup(*name ? name : "socket");
   }
 }
 
-static int find_ip_socket(struct file_info *fi, const char *path,
-                          int af, int type, long sought_inode)
+static void match_netlink(char *line, int af, char type, struct file_info *fi,
+                          long sought_inode)
+{
+  unsigned state;
+  long inode;
+  char *netlink_states[] = {
+    "ROUTE", "UNUSED", "USERSOCK", "FIREWALL", "SOCK_DIAG", "NFLOG", "XFRM",
+    "SELINUX", "ISCSI", "AUDIT", "FIB_LOOKUP", "CONNECTOR", "NETFILTER",
+    "IP6_FW", "DNRTMSG", "KOBJECT_UEVENT", "GENERIC", "DM", "SCSITRANSPORT",
+    "ENCRYPTFS", "RDMA", "CRYPTO"
+  };
+
+  if (sscanf(line, "%*p %u %*u %*x %*u %*u %*u %*u %*u %lu",
+             &state, &inode) < 2 || inode != sought_inode) {
+    return;
+  }
+
+  strcpy(fi->type, "netlink");
+  fi->name =
+      strdup(state < ARRAY_LEN(netlink_states) ? netlink_states[state] : "?");
+}
+
+static void match_ip(char *line, int af, char type, struct file_info *fi,
+                     long sought_inode)
 {
-  FILE *fp = fopen(path, "r");
-  char *line = NULL;
-  size_t line_length = 0;
   char *tcp_states[] = {
     "UNKNOWN", "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1", "FIN_WAIT2",
     "TIME_WAIT", "CLOSE", "CLOSE_WAIT", "LAST_ACK", "LISTEN", "CLOSING"
   };
+  char local_ip[INET6_ADDRSTRLEN] = {0};
+  char remote_ip[INET6_ADDRSTRLEN] = {0};
+  struct in6_addr local, remote;
+  int local_port, remote_port, state;
+  long inode;
+  int ok;
 
-  if (!fp) return 0;
-
-  if (!getline(&line, &line_length, fp)) return 0; // Skip header.
-
-  while (getline(&line, &line_length, fp) > 0) {
-    struct in6_addr local, remote;
-    int local_port, remote_port, state;
-    long inode;
-
-    if (ip_match(af, line, &local, &local_port, &remote, &remote_port,
-                  &state, &inode)) {
-      if (inode == sought_inode) {
-        char local_ip[INET6_ADDRSTRLEN] = {0};
-        char remote_ip[INET6_ADDRSTRLEN] = {0};
-
-        strcpy(fi->type, af == AF_INET ? "IPv4" : "IPv6");
-        inet_ntop(af, &local, local_ip, sizeof(local_ip));
-        inet_ntop(af, &remote, remote_ip, sizeof(remote_ip));
-        if (type == SOCK_STREAM) {
-          if (state < 0 || state > TCP_CLOSING) state = 0;
-          fi->name = xmprintf(af == AF_INET ?
-                              "TCP %s:%d->%s:%d (%s)" :
-                              "TCP [%s]:%d->[%s]:%d (%s)",
-                              local_ip, local_port, remote_ip, remote_port,
-                              tcp_states[state]);
-        } else {
-          fi->name = xmprintf(af == AF_INET ?
-                              "%s %s:%d->%s:%d" : "%s [%s]:%d->[%s]:%d",
-                              type == SOCK_DGRAM ? "UDP" : "RAW",
-                              local_ip, local_port, remote_ip, remote_port);
-        }
-        break;
-      }
-    }
+  if (af == 4) {
+    ok = sscanf(line, " %*d: %x:%x %x:%x %x %*x:%*x %*X:%*X %*X %*d %*d %ld",
+                &(local.s6_addr32[0]), &local_port,
+                &(remote.s6_addr32[0]), &remote_port,
+                &state, &inode) == 6;
+  } else {
+    ok = sscanf(line, " %*d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x "
+                "%*x:%*x %*X:%*X %*X %*d %*d %ld",
+                &(local.s6_addr32[0]), &(local.s6_addr32[1]),
+                &(local.s6_addr32[2]), &(local.s6_addr32[3]),
+                &local_port,
+                &(remote.s6_addr32[0]), &(remote.s6_addr32[1]),
+                &(remote.s6_addr32[2]), &(remote.s6_addr32[3]),
+                &remote_port, &state, &inode) == 12;
+  }
+  if (!ok || inode != sought_inode) return;
+
+  strcpy(fi->type, af == 4 ? "IPv4" : "IPv6");
+  inet_ntop(af, &local, local_ip, sizeof(local_ip));
+  inet_ntop(af, &remote, remote_ip, sizeof(remote_ip));
+  if (type == 't') {
+    if (state < 0 || state > TCP_CLOSING) state = 0;
+    fi->name = xmprintf(af == 4 ?
+                        "TCP %s:%d->%s:%d (%s)" :
+                        "TCP [%s]:%d->[%s]:%d (%s)",
+                        local_ip, local_port, remote_ip, remote_port,
+                        tcp_states[state]);
+  } else {
+    fi->name = xmprintf(af == 4 ? "%s %s:%d->%s:%d" : "%s [%s]:%d->[%s]:%d",
+                        type == 'u' ? "UDP" : "RAW",
+                        local_ip, local_port, remote_ip, remote_port);
   }
-
-  free(line);
-  fclose(fp);
-
-  return fi->name != 0;
 }
 
 static int find_socket(struct file_info *fi, long inode)
 {
-  // TODO: other protocols (netlink).
-  return find_unix_socket(fi, inode) ||
-         find_ip_socket(fi, "/proc/net/tcp", AF_INET, SOCK_STREAM, inode) ||
-         find_ip_socket(fi, "/proc/net/tcp6", AF_INET6, SOCK_STREAM, inode) ||
-         find_ip_socket(fi, "/proc/net/udp", AF_INET, SOCK_DGRAM, inode) ||
-         find_ip_socket(fi, "/proc/net/udp6", AF_INET6, SOCK_DGRAM, inode) ||
-         find_ip_socket(fi, "/proc/net/raw", AF_INET, SOCK_RAW, inode) ||
-         find_ip_socket(fi, "/proc/net/raw6", AF_INET6, SOCK_RAW, inode);
+  // TODO: other protocols (packet).
+  return scan_proc_net_file("/proc/net/tcp", 4, 't', match_ip, fi, inode) ||
+    scan_proc_net_file("/proc/net/tcp6", 6, 't', match_ip, fi, inode) ||
+    scan_proc_net_file("/proc/net/udp", 4, 'u', match_ip, fi, inode) ||
+    scan_proc_net_file("/proc/net/udp6", 6, 'u', match_ip, fi, inode) ||
+    scan_proc_net_file("/proc/net/raw", 4, 'r', match_ip, fi, inode) ||
+    scan_proc_net_file("/proc/net/raw6", 6, 'r', match_ip, fi, inode) ||
+    scan_proc_net_file("/proc/net/unix", 0, 0, match_unix, fi, inode) ||
+    scan_proc_net_file("/proc/net/netlink", 0, 0, match_netlink, fi, inode);
 }
 
 static void fill_stat(struct file_info *fi, const char* path)
@@ -269,8 +275,9 @@ static void fill_stat(struct file_info *fi, const char* path)
 
   // Fill DEVICE.
   dev = (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) ? sb.st_rdev : sb.st_dev;
-  snprintf(fi->device, sizeof(fi->device), "%ld,%ld",
-           (long)major(dev), (long)minor(dev));
+  if (!S_ISSOCK(sb.st_mode))
+    snprintf(fi->device, sizeof(fi->device), "%ld,%ld",
+             (long)major(dev), (long)minor(dev));
 
   // Fill SIZE/OFF.
   if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))
index a13a53d..e7adc99 100644 (file)
@@ -24,7 +24,7 @@ config MDEV_CONF
     hd[a-z][0-9]* 0:3 660
 
     Each line must contain three whitespace separated fields. The first
-    field is a regular expression matching one or more device names, and
+    field is a regular expression matching one or more device names,
     the second and third fields are uid:gid and file permissions for
     matching devies.
 */
@@ -34,9 +34,8 @@ config MDEV_CONF
 // mknod in /dev based on a path like "/sys/block/hda/hda1"
 static void make_device(char *path)
 {
-  char *device_name = NULL, *s, *temp;
-  int major = 0, minor = 0, type, len, fd;
-  int mode = 0660;
+  char *device_name = 0, *s, *temp;
+  int major = 0, minor = 0, type, len, fd, mode = 0660;
   uid_t uid = 0;
   gid_t gid = 0;
 
@@ -45,7 +44,7 @@ static void make_device(char *path)
 
     temp = strrchr(path, '/');
     fd = open(path, O_RDONLY);
-    *temp=0;
+    *temp = 0;
     len = read(fd, toybuf, 64);
     close(fd);
     if (len<1) return;
@@ -58,19 +57,15 @@ static void make_device(char *path)
   } else {
     // if (!path), do hotplug
 
-    if (!(temp = getenv("SUBSYSTEM")))
-      return;
+    if (!(temp = getenv("MODALIAS"))) xrun((char *[]){"modprobe", temp, 0});
+    if (!(temp = getenv("SUBSYSTEM"))) return;
     type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK;
-    if (!(temp = getenv("MAJOR")))
-      return;
+    if (!(temp = getenv("MAJOR"))) return;
     sscanf(temp, "%u", &major);
-    if (!(temp = getenv("MINOR")))
-      return;
+    if (!(temp = getenv("MINOR"))) return;
     sscanf(temp, "%u", &minor);
-    path = getenv("DEVPATH");
+    if (!(path = getenv("DEVPATH"))) return;
     device_name = getenv("DEVNAME");
-    if (!path)
-      return;
   }
   if (!device_name)
     device_name = strrchr(path, '/') + 1;
index 29111d5..def59b6 100644 (file)
  * the same field should be called "TT" which is _INSANE_ and I'm not doing it.
  * It also says that -o "args" and "comm" should behave differently but use
  * the same title, which is not the same title as the default output. No.
+ *
+ * ps aux
+ * TODO: -o maj_flt, min_flt
+ * TODO: --sort
 
 USE_PS(NEWTOY(ps, "aAdeflo*[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
 
@@ -97,7 +101,7 @@ static int do_ps(struct dirtree *new)
   char *name, *s, state;
   int nlen, i, fd, len, width = TT.width;
 
-  if (!new->parent) return DIRTREE_RECURSE;
+  if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP;
   if (!(*slot = atol(new->name))) return 0;
 
   // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars:
@@ -267,9 +271,9 @@ void ps_main(void)
 
       // Set title, length of title, type, end of type, and display width
       while ((type = comma_iterate(&arg, &length))) {
-        if ((end = strchr(type, '=')) && length<(end-type)) {
+        if ((end = strchr(type, '=')) && length>(end-type)) {
           title = end+1;
-          length = (end-type)-1;
+          length -= (end-type)+1;
         } else {
           end = type+length;
           title = 0;
@@ -282,11 +286,8 @@ void ps_main(void)
 
         // Allocate structure, copy title
         field = xmalloc(sizeof(struct strawberry)+length+1);
-        if (title) {
-          memcpy(field->title, title, length);
-          field->title[length] = 0;
-        }
-        field->len = length;
+        memcpy(field->title, title ? title : type, length);
+        field->title[field->len = length] = 0;
 
         if (width) {
           field->len = strtol(++width, &title, 10);
@@ -296,7 +297,7 @@ void ps_main(void)
         }
 
         // Find type (reuse width as temp because we're done with it)
-        for (i = 0; i<ARRAY_LEN(typos) && !field->which; i++) {
+        for (i = 0; i<ARRAY_LEN(typos); i++) {
           int j, k;
           char *s;
 
@@ -304,7 +305,8 @@ void ps_main(void)
           for (j = 0; j < 2; j++) {
             if (!j) s = typos[i];
             // posix requires alternate names for some fields
-            else if (-1 == (k = stridx((char []){7, 14, 15, 16}, i))) break;
+            else if (-1 == (k = stridx((char []){7, 14, 15, 16, 0}, i)))
+              continue;
             else s = ((char *[]){"nice", "args", "comm", "etime"})[k];
 
             if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
index 499c5db..e221960 100644 (file)
@@ -26,6 +26,9 @@ USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
 
 USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN))
 USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
+// Login lies in argv[0], so add some aliases to catch that
+USE_SH(OLDTOY(-sh, sh, 0))
+USE_SH(OLDTOY(-toysh, sh, 0))
 
 config SH
   bool "sh (toysh)"
index 312bb93..a442f0d 100644 (file)
@@ -82,12 +82,15 @@ void cpio_main(void)
   // In passthrough mode, parent stays in original dir and generates archive
   // to pipe, child does chdir to new dir and reads archive from stdin (pipe).
   if (TT.pass) {
-    if (!(pid = xpopen(0, &pipe, 0))) {
+    if (toys.stacktop) {
+      // xpopen() doesn't return from child due to vfork(), instead restarts
+      // with !toys.stacktop
+      pid = xpopen(0, &pipe, 0);
+      afd = pipe;
+    } else {
+      // child
       toys.optflags |= FLAG_i;
       xchdir(TT.pass);
-    } else {
-      toys.optflags |= FLAG_o;
-      afd = pipe;
     }
   }
 
index 880432e..8919ad1 100644 (file)
@@ -125,7 +125,7 @@ static int od_out_t(struct odtype *t, char *buf, int *offset)
 static void od_outline(void)
 {
   unsigned flags = toys.optflags;
-  char buf[128], *abases[] = {"", "%07d", "%07o", "%06x"};
+  char buf[128], *abases[] = {"", "%07lld", "%07llo", "%06llx"};
   struct odtype *types = (struct odtype *)toybuf;
   int i, j, len, pad;
 
@@ -144,7 +144,8 @@ static void od_outline(void)
   } else {
     TT.star = 0;
 
-    xprintf(abases[TT.address_idx], TT.pos);
+    // off_t varies so expand it to largest possible size
+    xprintf(abases[TT.address_idx], (long long)TT.pos);
     if (!TT.leftover) {
       if (TT.address_idx) xputc('\n');
       return;
index 70d2997..2151826 100644 (file)
@@ -28,7 +28,7 @@ void time_main(void)
   struct timeval tv, tv2;
 
   gettimeofday(&tv, NULL);
-  if (!(pid = xfork())) xexec(toys.optargs);
+  if (!(pid = XVFORK())) xexec(toys.optargs);
   else {
     int stat;
     struct rusage ru;
index 8178bf0..b4cb80a 100644 (file)
@@ -111,6 +111,7 @@ void xargs_main(void)
   struct double_list *dlist = NULL, *dtemp;
   int entries, bytes, done = 0, status;
   char *data = NULL, **out;
+  pid_t pid;
 
   if (!(toys.optflags & FLAG_0)) TT.delim = '\n';
 
@@ -168,8 +169,7 @@ void xargs_main(void)
     for (dtemp = dlist; dtemp; dtemp = dtemp->next)
       handle_entries(dtemp->data, out+entries);
 
-    pid_t pid=xfork();
-    if (!pid) {
+    if (!(pid = XVFORK())) {
       xclose(0);
       open("/dev/null", O_RDONLY);
       xexec(out);
index afb49ba..0ac70b7 100755 (executable)
@@ -35,7 +35,8 @@ and progress towards implementing it.</p>
 <li><a href=#android>Android Toolbox</a></li>
 <li><a href=#tizen>Tizen Core</a></li>
 <li>Miscelaneous: <a href=#klibc>klibc</a>, <a href=#glibc>glibc</a>,
-<a href=#sash>sash</a>, <a href=#sbase>sbase</a>, <a href=#s6>s6</a>...</li>
+<a href=#sash>sash</a>, <a href=#sbase>sbase</a>, <a href=#s6>s6</a>,
+<a href=#uclinux>uclinux</a>...</li>
 </ul>
 
 <hr />
@@ -186,13 +187,13 @@ C library, those are outside the scope of this project.)</p>
 <span id=development>
 bzcat cat cp dirname echo env patch rmdir sha1sum sleep sort sync
 true uname wc which yes zcat
-awk basename bzip2 chmod chown cmp cut date dd diff
+awk basename chmod chown cmp cut date dd diff
 egrep expr find grep gzip head hostname id install ln ls
 mkdir mktemp mv od readlink rm sed sh tail tar touch tr uniq
-wget whoami xargs chgrp comm gunzip less logname man split
+wget whoami xargs chgrp comm gunzip less logname split
 tee test time bunzip2 chgrp chroot comm cpio dmesg
 dnsdomainname ftpd ftpget ftpput gunzip ifconfig init less
-logname losetup man mdev mount mountpoint nc pgrep pkill 
+logname losetup mdev mount mountpoint nc pgrep pkill 
 pwd route split stat switch_root tac umount vi
 </span>
 </b></blockquote>
@@ -207,13 +208,14 @@ self-bootstrapping build still uses the following busybox commands,
 not yet supplied by toybox:</p>
 
 <blockquote><p>
-ash awk bunzip2 bzip2 dd diff expr fdisk ftpd ftpget ftpput gunzip
-gzip less man pgrep ping pkill ps route sed sh sha512sum tar test tr unxz vi
-wget xzcat zcat</p></blockquote>
+awk dd diff expr fdisk ftpd ftpget ftpput gunzip
+gzip less pgrep ping pkill ps route sh sha512sum tar test tr unxz vi
+wget xzcat zcat
+</p></blockquote>
 
 <p>Many of those are in "pending". Most of the archive commands are needed
 because busybox tar doesn't call external versions. The remaining "difficult"
-commands are vi, awk, and ash.</p>
+commands are vi, awk, and sh.</p>
 
 <hr />
 <h2><a name=android /><a href="#android">Use case: Replacing Android Toolbox</a></h2>
@@ -701,6 +703,218 @@ OpenSolaris.</p>
 <p>Verdict: ignore.</p>
 
 <hr />
+<a name=uclinux />
+<h2>uClinux</h2>
+
+<p>Long ago a hardware developer named Jeff Dionne put together a
+nommu Linux distribution, which involved rewriting a lot of command line
+utilities that relied on <a href=http://nommu.org/memory-faq.txt>features
+unavailable on nommu</a> hardware.</p>
+
+<p>In 2003 Jeff moved to Japan and handed
+the project off to people who allowed it to roll to a stop. The website
+turned into a mess of 404 links, the navigation indexes stopped being
+updated over a decade ago, and the project's CVS repository suffered a
+hard drive failure for which there were no backups. The project continued
+to put out "releases" through 2014 (you have to scroll down in the "news"
+section to find them, the "HTTP download" section in the nav bar on the
+left hasn't been updated in over a decade), which were hand-updated tarball
+snapshots mostly consisting of software from the 1990's. For example the
+2014 release still contained ipfwadm, the package which predated ipchains,
+which predated iptables, which is in the process of being replaced by
+nftables.</p>
+
+<p>Nevertheless, people still try to use this because (at least until the
+launch of <a href=http://nommu.org>nommu.org</a>) the project was viewed
+as the place to discuss, develop, and learn about nommu Linux.
+The role of uclinux.org as an educational resource kept people coming
+to it long after it had collapsed as a Linux distro.</p>
+
+<p>Starting around 0.6.0 toybox began to address nommu support with the goal
+of putting uClinux out of its misery.</p>
+
+<p>An analysis of <a href=http://www.uclinux.org/pub/uClinux/dist/uClinux-dist-20140504.tar.bz2>uClinux-dist-20140504</a> found 312 package
+subdirectories under "user".</p>
+
+<h3>Taking out the trash</h3>
+
+<p>A bunch of packages (<b>inotify-tools, input-event-demon, ipsec-tools, netifd,
+keepalived, mobile-broadband-provider-info, nuttp, readline, snort,
+snort-barnyard, socat, sqlite, sysklogd, sysstat, tcl, ubus, uci, udev,
+unionfs, uqmi, usb_modeswitch, usbutils, util-linux</b>)
+are hard to evaluate because
+uclinux has directories for them, but their source isn't actually in the
+uclinux tree. In some of these the makefiles download a git repo during
+the build, so I'm assuming you can build the external package if you really
+care. (Even when I know what these packages do, I'm skipping them
+because uclinux doesn't actually contain them, and any given snapshot
+of the build system will bitrot as external web links change over time.)</p>
+
+<p>Other packages are orphaned, meaning they're not mentioned from any Kconfig
+or Makefiles outside of their directory, so uclinux can't actually build
+them: <b>mbus</b> is an orphaned i2c test program expecting to run in some sort
+of hardwired hardware context, <b>mkeccbin</b> is an orphaned "ECC annotated
+binary file" generator (meaning it's half of a flash writer),
+<b>wsc_upnp</b> is a "Ralink WPS" driver (some sort of stale wifi chip)...</p>
+
+<p>The majority of the remaining packages are probably not of interest to
+toybox due to being so obsolete or special purpose they may not actually be
+of interest to anybody anymore. (This list also includes a lot of
+special-purpose network back-end stuff that's hard for anybody but
+datacenter admins to evaluate the current relevance of.)</p>
+
+<blockquote><b><p>
+arj asterisk boottools bpalogin br2684ctl camserv can4linux cgi_generic
+cgihtml clamav clamsmtp conntrack-tools cramfs crypto-tools cxxtest
+ddns3-client de2ts-cal debug demo diald discard dnsmasq dnsmasq2
+ethattach expat-examples ez-ipupdate fakeidentd
+fconfig ferret flatfs flthdr freeradius freeswan frob-led frox fswcert
+game gettyd gnugk haserl horch
+hostap hping httptunnel ifattach ipchains
+ipfwadm ipmasqadm ipportfw ipredir ipset iso_client
+jamvm jffs-tools jpegview jquery-ui kendin-config kismet klaxon kmod
+l2tpd lcd ledcmd ledcon lha lilo lirc lissa load loattach
+lpr lrpstat lrzsz mail mbus mgetty microwin ModemManager msntp musicbox
+nooom null openswan openvpn palmbot pam_* pcmcia-cs playrt plugdaemon pop3proxy
+potrace qspitest quagga radauth
+ramimage readprofile rdate readprofile routed rrdtool rtc-ds1302
+sendip ser sethdlc setmac setserial sgutool sigs siproxd slattach
+smtpclient snmpd net-snmp snortrules speedtouch squashfs scep sslwrap stp
+stunnel tcpblast tcpdump tcpwrappers threaddemos tinylogin tinyproxy
+tpt tripwire unrar unzoo version vpnled w3cam xl2tpd zebra
+</p></b></blockquote>
+    
+<p>This stuff is all over the place: arj, lha, rar, and zoo are DOS archivers,
+ethattach describes itself as just "a network tool",
+mail is a textmode smtp mailer literally described as "Some kind of mail
+proggy" in uclinux's kconfig (as opposed to clamsmtp and smtpclient and
+so on), this gettyd isn't a generic version but specifically a
+hardwired ppp dialin utility, mgetty isn't a generic version but is combined
+with "sendfax", hostap is an intersil prism driver, wlan-ng is also an
+intersil prism dirver, null is a program to intentionally dereference a
+null pointer (in case you needed one), iso_client is a
+"Demo Application for the USB Device Driver", kendin-config is
+"for configuring the Micrel Kendin KS8995M over QSPI", speedtouch configures
+a specific brand of asdl modem, portmap is part of Anfs,
+ferret, linux-igd, and miniupnp are all upnp packages,
+lanbypass "can be used to control the LAN
+bypass switches on the Advantech x86 based hardware platforms", lcd is
+"test of lcddma device driver" (an out-of-tree Coldfire driver apparently
+lost to history, the uclinux linux-2.4.x directory has a config symbol for
+it, but nothing in the code actually _uses_ it...), qspitest is another
+coldfire thing, mii-tool-fec is
+"strictly for the FEC Ethernet driver as implemented (and modified) for
+the uCdimm5272", rtc-ds1302 and rtc-m41t11 are usermode drivers for specific
+clock chips, stunnel is basically "openssl s_client -quiet -connect",
+potrace is a bitmap to vector graphic converter, radauth performs command line
+authentication against a radius server,
+clamav, klaxon, ferret, l7-protocols, and nessus are very old network security
+software (it's got a stale snapshot of nmap too), xl2tpd is a PPP over UDP
+tunnel (rfc 2661), zebra is the package quagga replaced,
+lilo is the x86-only bootloader that predated grub (and recently discontinued
+development), lissa is a "framebuffer graphics demo" from
+1998, the squashfs package here is the out of tree patches for 2.4 kernels
+and such before the filesystem was merged upstream (as opposed to the
+squashfs-new package which is a snapshot of the userspace tool from 2011),
+load is basically "dd file /dev/spi", version is basically "cat /proc/version",
+microwin is a port of the WinCE graphics API to Linux, scep is a 2003
+implementation of an IETF draft abandoned in 2010, tpt depends on
+Andrew Morton's 15 year old unmerged "timepegs" kernel patch using the pentium
+cycle counter, vpnled controls a light that reboots systems (what?),
+w3cam is a video4linux 1.0 client (v4l2 showed up during 2.5 and support for
+the old v4l1 was removed in 2.6.38 back in 2011), busybox ate tinylogin
+over a decade ago, lrpstat is a java network monitor
+from 2001, lrzsz is zmodem/ymodem/zmodem, msntp and stp implement rfc2030
+meaning it overflows in 2036 (the package was last updated in 2000), rdate
+is rfc 868 meaning it also overflows in 2036 (which is why ntp was invented
+a few decades back), reiserfsprogs development stopped abruptly after
+Hans Reiser was convicted of murdering his wife Nina (denying it on the
+stand and then leading them to the body as part of his plea bargain during
+sentencing)...
+</p>
+
+<p>Seriously, there's a lot of crap in there. It's hard to analyze most
+of it far enough to prove it _doesn't_ do anything.</p>
+
+<h3>Non-toybox programs</h3>
+
+<p>The following software may actually still do something intelligible
+(although the package versions tend to be years out of date), but
+it's not a direction toybox has chosen to go in.</p>
+
+<p>There are several programming languages (<b>bash, lua, jamvm, tinytcl,
+perl, python</b>) in there. Maybe someone somewhere wants a 2008 release of a
+java virtual machine tested to work on nommu systems (jamvm), but it's out
+of scope for toybox.</p>
+
+<p>A bunch of benchmark programs: <b>cpu, dhrystone, mathtest, nbench, netperf,
+netpipe, and whetstone</b>.</p>
+
+<p>A bunch of web servers: <b>appWeb, boa, fnord (via tcpserver), goahead, httpd,
+mini_httpd, and thttpd</b>.</p>
+
+<p>A bunch of shells: <b>msh</b> is a clever (I.E. obfuscated) little shell,
+<b>nwsh</b> is "new shell" (that's what it called itself in 1999 anyway),
+<b>sash</b> is another shell with a bunch of builtins (ls, ps, df, cp, date, reboot,
+and shutdown, this roadmap analyzes it <a href="#sash">elsewhere</a>),
+<b>sh</b> is a very old minix shell fork, and <b>tcsh</b> is also a shell.</p>
+
+<p>Also in this category, we have:</p>
+
+<blockquote><b><p>
+dropbear jffs-tools jpegview kexec-tools bind ctorrent
+iperf iproute2 ip-sentinel iptables kexec
+nmap oggplay openssl oprofile p7zip pppd pptp play vplay
+hdparm mp3play at clock
+mtd-utils mysql logrotate brcfg bridge-utils flashw
+ebtables etherwake ethtool expect gdb gdbserver hostapd
+lm_sensors load netflash netstat-nat
+radvd recover rootloader resolveip rp-pppoe
+rsyslog rsyslogd samba smbmount squashfs-new squid ssh strace tip
+uboot-envtools ulogd usbhubctrl vconfig vixie-cron watchdogd
+wireless_tools wpa_supplicant
+</p></b></blockquote>
+
+<p>An awful lot of those are borderline: play and vplay are wav file
+audio players, there's oprofile _and_ readprofile (which just reads kernel
+profiling data from /proc/profile),
+radvd is a "routr advertisement daemon" (ipv6 stateless autoconf),
+ctorrent is a bittorent client, 
+lm_sensors is hardware (heat?) monitoring,
+resolveip is dig only less so,
+rp-pppoe is ppp over ethernet,
+ebtables is an ethernet version of iptables (for bridging),
+their dropbear is from 2012, and that ssh version is from 2011
+(which means it's about nine months too _old_ to have the heartbleed bug).
+There's both ulogd and ulogd2 (no idea why), and pppd is version 2.4 but
+there's a ppd-2.3 directory also.</p>
+
+<p>Lots of flash stuff:
+flashw is a flash writer, load is an spi flash loader, netflash writes
+to flash via tftp,
+recover is also a reflash daemon intended to come up when the system can't boot,
+rootloader seems to be another reflash daemon but without dhcp.</p>
+
+<h3>Already in roadmap</h3>
+
+<p>The following packages contain commands already in the toybox roadmap:</p>
+
+<blockquote><b><p>
+agetty cal cksum cron dhcpcd dhcpcd-new dhcpd dhcp-isc dosfstools e2fsprogs
+elvis-tiny levee fdisk fileutils ftp ftpd grep hd hwclock inetd init ntp
+iputils login module-init-tools netcat shutils ntpdate lspci ping procps
+proftpd rsync shadow shutils stty sysutils telnet telnetd tftp tftpd traceroute
+unzip wget mawk net-tools
+</p></b></blockquote>
+
+<p>There are some duplicates in there, levee is a tiny vi implementation
+like elvis-tiny, ntp and ntpdate overlap, etc.</p>
+
+<p>Verdict: We don't really need to do a whole lot special for nommu
+systems, just get the existing toybox roadmap working on nommu and
+we're good. The uClinux project can rest in peace.</p>
+
+<hr />
 <h2>Requests:</h2>
 
 <p>The following additional commands have been requested (and often submitted)
@@ -722,6 +936,7 @@ ipaddr iplink iproute blockdev rpm2cpio arping brctl dumpleases fsck
 tcpsvd tftpd
 factor fallocate fsfreeze inotifyd lspci nbd-client partprobe strings
 base64 mix
+reset hexedit nsenter shred
 </span>
 </b></blockquote>