changeset 1251:6ca31490f581

roadmap: describe glibc commands. Some glibc commands are irrelevant because they're for functionality that is excluded from musl (mtrace, rpc*, localedef, iconvconfig, nscd). getconf and catchsegv look like candidates for the development toolchain; locale and iconv were already triaged. getent is pretty lame, but it and the timezone stuff (tzselect zic zdump) are the only new possibly interesting commands.
author Isaac Dunham <ibid.ag@gmail.com>
date Sat, 12 Apr 2014 17:26:44 -0500
parents 22f02dfb33ab
children be6b0a0204ee
files toys/other/hello.c toys/other/vconfig.c toys/pending/compress.c toys/pending/init.c toys/posix/date.c toys/posix/du.c toys/posix/grep.c www/roadmap.html
diffstat 8 files changed, 453 insertions(+), 156 deletions(-) [+]
line wrap: on
line diff
--- a/toys/other/hello.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/other/hello.c	Sat Apr 12 17:26:44 2014 -0500
@@ -8,17 +8,28 @@
 // Accept many different kinds of command line argument:
 
 USE_HELLO(NEWTOY(hello, "(walrus)(blubber):;(also):e@d*c#b:a", TOYFLAG_USR|TOYFLAG_BIN))
+USE_HELLO_ALIAS(NEWTOY(hello_alias, "b:dq", TOYFLAG_USR|TOYFLAG_BIN))
 
 config HELLO
   bool "hello"
   default n
   help
-    usage: hello [-a] [-b string] [-c number] [-d list] [-e count] [...]
+    usage: hello [-a] [-b STRING] [-c NUMBER] [-d LIST] [-e COUNT] [...]
 
     A hello world program.  You don't need this.
 
     Mostly used as an example/skeleton file for adding new commands,
     occasionally nice to test kernel booting via "init=/bin/hello".
+
+config HELLO_ALIAS
+  bool "hello_alias"
+  default n
+  depends on HELLO
+  help
+    usage: hello_alias [-dq] [-b NUMBER]
+
+    Example of a second command with different arguments in the same source
+    file as the first. Allows shared infrastructure not added to lib.
 */
 
 #define FOR_hello
@@ -27,12 +38,19 @@
 // Hello doesn't use these globals, they're here for example/skeleton purposes.
 
 GLOBALS(
-  char *b_string;
-  long c_number;
-  struct arg_list *d_list;
-  long e_count;
-  char *also_string;
-  char *blubber_string;
+  union {
+    struct {
+      char *b_string;
+      long c_number;
+      struct arg_list *d_list;
+      long e_count;
+      char *also_string;
+      char *blubber_string;
+    } h;
+    struct {
+      long b_number;
+    } a;
+  };
 
   int more_globals;
 )
@@ -47,15 +65,25 @@
 
   if (toys.optflags) printf("flags=%x\n", toys.optflags);
   if (toys.optflags & FLAG_a) printf("Saw a\n");
-  if (toys.optflags & FLAG_b) printf("b=%s\n", TT.b_string);
-  if (toys.optflags & FLAG_c) printf("c=%ld\n", TT.c_number);
-  while (TT.d_list) {
-    printf("d=%s\n", TT.d_list->arg);
-    TT.d_list = TT.d_list->next;
+  if (toys.optflags & FLAG_b) printf("b=%s\n", TT.h.b_string);
+  if (toys.optflags & FLAG_c) printf("c=%ld\n", TT.h.c_number);
+  while (TT.h.d_list) {
+    printf("d=%s\n", TT.h.d_list->arg);
+    TT.h.d_list = TT.h.d_list->next;
   }
-  if (TT.e_count) printf("e was seen %ld times\n", TT.e_count);
+  if (TT.h.e_count) printf("e was seen %ld times\n", TT.h.e_count);
   for (optargs = toys.optargs; *optargs; optargs++)
     printf("optarg=%s\n", *optargs);
   if (toys.optflags & FLAG_walrus) printf("Saw --walrus\n");
-  if (TT.blubber_string) printf("--blubber=%s\n", TT.blubber_string);
+  if (TT.h.blubber_string) printf("--blubber=%s\n", TT.h.blubber_string);
 }
+
+#define CLEANUP_hello
+#define FOR_hello_alias
+#include "generated/flags.h"
+
+void hello_alias_main(void)
+{
+  printf("hello world %x\n", toys.optflags);
+  if (toys.optflags & FLAG_b) printf("b=%ld", TT.a.b_number);
+}
--- a/toys/other/vconfig.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/other/vconfig.c	Sat Apr 12 17:26:44 2014 -0500
@@ -47,6 +47,18 @@
   memset(&request, 0, sizeof(struct vlan_ioctl_args));
   cmd = toys.optargs[0];
 
+//add ADD_VLAN_CMD 4094 0     // ADD_VLAN_CMD
+//rem DEL_VLAN_CMD 0 0        // DEL_VLAN_CMD
+//set_ingress_map  INT_MAX 0  // SET_VLAN_INGRESS_PRIORITY_CMD
+//set_egress_map              // SET_VLAN_EGRESS_PRIORITY_CMD
+//GET_VLAN_INGRESS_PRIORITY_CMD,
+//GET_VLAN_EGRESS_PRIORITY_CMD,
+//set_name_type               // SET_VLAN_NAME_TYPE_CMD
+//set_flag                    // SET_VLAN_FLAG_CMD,
+//GET_VLAN_REALDEV_NAME_CMD,
+//GET_VLAN_VID_CMD
+
+
   if (!strcmp(cmd, "set_name_type")) {
     char *types[] = {"VLAN_PLUS_VID", "DEV_PLUS_VID", "VLAN_PLUS_VID_NO_PAD",
                      "DEV_PLUS_VID_NO_PAD"};
@@ -65,7 +77,7 @@
   }
 
   // Store interface name
-  xstrncpy(request.device1, toys.optargs[1], 16);
+  xstrncpy(request.device1, toys.optargs[1], 23);
 
   if (!strcmp(cmd, "add")) {
     request.cmd = ADD_VLAN_CMD;
--- a/toys/pending/compress.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/pending/compress.c	Sat Apr 12 17:26:44 2014 -0500
@@ -5,7 +5,8 @@
  * The inflate/deflate code lives here, so the various things that use it
  * either live here or call these commands to pipe data through them.
  *
- * Divergence from posix: replace obsolete "compress" with mutiplexer.
+ * Divergence from posix: replace obsolete/patented "compress" with mutiplexer.
+ * (gzip already replaces "uncompress".)
  *
  * See RFCs 1950 (zlib), 1951 (deflate), and 1952 (gzip)
  * LSB 4.1 has gzip, gunzip, and zcat
@@ -14,8 +15,10 @@
 // Accept many different kinds of command line argument.
 // Leave Lrg at end so flag values line up.
 
-USE_COMPRESS(NEWTOY(compress, "zcd9Lrg[-cd][!zgLr]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_COMPRESS(NEWTOY(zcat, "aLrg[!aLrg]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_COMPRESS(NEWTOY(compress, "zcd9lrg[-cd][!zgLr]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_COMPRESS(NEWTOY(gzip, USE_GZIP_D("d")"19dcflqStvgLRz[!gLRz]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_DECOMPRESS(NEWTOY(zcat, 0, TOYFLAG_USR|TOYFLAG_BIN))
+USE_DECOMPRESS(NEWTOY(gunzip, "cflqStv", TOYFLAG_USR|TOYFLAG_BIN))
 
 //zip unzip gzip gunzip zcat
 
@@ -23,38 +26,114 @@
   bool "compress"
   default n
   help
+    usage: compress [-zgLR19] [FILE]
+
+    Compress or decompress file (or stdin) using "deflate" algorithm.
+
+    -1	min compression
+    -9	max compression (default)
+    -g	gzip (default)
+    -L	zlib
+    -R	raw
+    -z	zip
+
+config GZIP
+  bool "gzip"
+  default y
+  depends on COMPRESS
+  help
+    usage: gzip [-19cfqStvzgLR] [FILE...]
+
+    Compess (deflate) file(s). With no files, compress stdin to stdout.
+
+    On successful decompression, compressed files are replaced with the
+    uncompressed version. The input file is removed and replaced with
+    a new file without the .gz extension (with same ownership/permissions).
+
+    -1	Minimal compression (fastest)
+    -9	Max compression (default)
+    -c	cat to stdout (act as zcat)
+    -f	force (if output file exists, input is tty, unrecognized extension)
+    -q	quiet (no warnings)
+    -S	specify exension (default .*)
+    -t	test compressed file(s)
+    -v	verbose (like -l, but compress files)
+
+    Compression type:
+    -g gzip (default)    -L zlib    -R raw    -z zip
+
+config GZIP_D
+  bool
+  default y
+  depends on GZIP && DECOMPRESS
+  help
+    usage: gzip [-d]
+
+    -d	decompress (act as gunzip)
+
+config DECOMPRESS
+  bool "decompress"
+  default n
+  help
     usage: compress [-zglrcd9] [FILE]
 
     Compress or decompress file (or stdin) using "deflate" algorithm.
 
-    -c	compress with -g gzip (default)  -L zlib  -r raw  -z zip
+    -c	compress with -g gzip (default)  -l zlib  -r raw  -z zip
     -d	decompress (autodetects type)
 
+
 config ZCAT
   bool "zcat"
-  default n
-  depends on COMPRESS
+  default y
+  depends on DECOMPRESS
   help
     usage: zcat [FILE...]
 
     Decompress deflated file(s) to stdout
+
+config GUNZIP
+  bool "gunzip"
+  default y
+  depends on DECOMPRESS
+  help
+    usage: gunzip [-cflqStv] [FILE...]
+
+    Decompess (deflate) file(s). With no files, compress stdin to stdout.
+
+    On successful decompression, compressed files are replaced with the
+    uncompressed version. The input file is removed and replaced with
+    a new file without the .gz extension (with same ownership/permissions).
+
+    -c	cat to stdout (act as zcat)
+    -f	force (output file exists, input is tty, unrecognized extension)
+    -l	list compressed/uncompressed/ratio/name for each input file.
+    -q	quiet (no warnings)
+    -S	specify exension (default .*)
+    -t	test compressed file(s)
+    -v	verbose (like -l, but decompress files)
 */
 
 #define FOR_compress
 #include "toys.h"
 
 GLOBALS(
-  // base offset and extra bits tables (length and distance)
+  // Huffman codes: base offset and extra bits tables (length and distance)
   char lenbits[29], distbits[30];
   unsigned short lenbase[29], distbase[30];
   void *fixdisthuff, *fixlithuff;
 
+  // CRC
   void (*crcfunc)(char *data, int len);
-  unsigned crc, len;
+  unsigned crc;
 
-  char *outbuf;
-  unsigned outlen;
-  int outfd;
+  // Compressed data buffer
+  char *data;
+  unsigned pos, len;
+  int fd;
+
+  // Tables only used for deflation
+  unsigned short *head, *chain;
 )
 
 // little endian bit buffer
@@ -125,12 +204,12 @@
   return result;
 }
 
-static void outbuf_crc(char sym)
+static void data_crc(char sym)
 {
-  TT.outbuf[TT.outlen++ & 32767] = sym;
+  TT.data[TT.pos++ & 32767] = sym;
 
-  if (!(TT.outlen & 32767)) {
-    xwrite(TT.outfd, TT.outbuf, 32768);
+  if (!(TT.pos & 32767)) {
+    xwrite(TT.fd, TT.data, 32768);
     if (TT.crcfunc) TT.crcfunc(0, 32768);
   }
 }
@@ -146,7 +225,7 @@
 
 // Create simple huffman tree from array of bit lengths.
 
-// The symbols in deflate's huffman trees are sorted (first by bit length
+// The symbols in the huffman trees are sorted (first by bit length
 // of the code to reach them, then by symbol number). This means that given
 // the bit length of each symbol, we can construct a unique tree.
 static void len2huff(struct huff *huff, char bitlen[], int len)
@@ -185,6 +264,33 @@
   return huff->symbol[start + offset];
 }
 
+// For deflate, TT.len = input read, TT.pos = input consumed
+static void deflate(struct bitbuf *bb)
+{
+  char *data = TT.data;
+  int len, end = 0;
+
+  TT.crc = ~0;
+
+  // Read next half-window of data if we haven't hit EOF yet.
+read_more:
+  len = readall(TT.fd, data + (TT.len & 32768), 32768);
+fprintf(stderr, "read %d@%d\n", len, TT.pos);
+  if (len < 0) perror_exit("read"); // todo: add filename
+  if (len != 32768) end++;
+  TT.len += len;
+
+  // repeat until spanked
+  while (TT.pos != TT.len) {
+    unsigned pos = TT.pos & 65535;
+
+    if (!(pos & 32767) && !end) goto read_more;
+
+    TT.pos++;
+  }
+fprintf(stderr, "numberwang %d\n", TT.pos);
+}
+
 // Decompress deflated data from bitbuf to filehandle.
 static void inflate(struct bitbuf *bb)
 {
@@ -216,7 +322,7 @@
         // dump bytes until done or end of current bitbuf contents
         if (bblen > len) bblen = len;
         pos = bblen;
-        while (pos--) outbuf_crc(*(p++));
+        while (pos--) data_crc(*(p++));
         bitbuf_skip(bb, bblen << 3);
         len -= bblen;
       }
@@ -276,7 +382,7 @@
         int sym = huff_and_puff(bb, lithuff);
 
         // Literal?
-        if (sym < 256) outbuf_crc(sym);
+        if (sym < 256) data_crc(sym);
 
         // Copy range?
         else if (sym > 256) {
@@ -286,9 +392,9 @@
           len = TT.lenbase[sym] + bitbuf_get(bb, TT.lenbits[sym]);
           sym = huff_and_puff(bb, disthuff);
           dist = TT.distbase[sym] + bitbuf_get(bb, TT.distbits[sym]);
-          sym = TT.outlen & 32767;
+          sym = TT.pos & 32767;
 
-          while (len--) outbuf_crc(TT.outbuf[(TT.outlen-dist) & 32767]);
+          while (len--) data_crc(TT.data[(TT.pos-dist) & 32767]);
 
         // End of block
         } else break;
@@ -299,18 +405,25 @@
     if (final) break;
   }
 
-  if (TT.outlen & 32767) {
-    xwrite(TT.outfd, TT.outbuf, TT.outlen & 32767);
-    if (TT.crcfunc) TT.crcfunc(0, TT.outlen & 32767);
+  if (TT.pos & 32767) {
+    xwrite(TT.fd, TT.data, TT.pos & 32767);
+    if (TT.crcfunc) TT.crcfunc(0, TT.pos & 32767);
   }
 }
 
-static void init_deflate(void)
+static void init_deflate(int compress)
 {
-  int i, n = 1;
+  int i, n = 1, size = 32768;
 
   // Ye olde deflate window
-  TT.outbuf = xmalloc(32768);
+  TT.data = xmalloc(32768*(compress+1));
+  if (compress) {
+    TT.head = (unsigned short *)(TT.data+65536);
+    TT.chain = TT.head +
+
+HASH_SIZE = 1 << 15;
+
+  }
 
   // Calculate lenbits, lenbase, distbits, distbase
   *TT.lenbase = 3;
@@ -365,17 +478,22 @@
   unsigned crc, *crc_table = (unsigned *)(toybuf+sizeof(toybuf)-1024);
 
   crc = TT.crc;
-  for (i=0; i<len; i++) crc = crc_table[(crc^TT.outbuf[i])&0xff] ^ (crc>>8);
+  for (i=0; i<len; i++) crc = crc_table[(crc^TT.data[i])&0xff] ^ (crc>>8);
   TT.crc = crc;
   TT.len += len;
 }
 
+static void do_compress(int fd, char *name)
+{
+  xwrite(1, "\x1f\x8b\x08\0\0\0\0\0\x02\xff", 10);
+}
+
 static void do_zcat(int fd, char *name)
 {
   struct bitbuf *bb = bitbuf_init(fd, sizeof(toybuf));
 
   if (!is_gzip(bb)) error_exit("not gzip");
-  TT.outfd = 1;
+  TT.fd = 1;
 
   // Use last 1k of toybuf for little endian crc table
   crc_init((unsigned *)(toybuf+sizeof(toybuf)-1024), 1);
@@ -398,13 +516,20 @@
   zcat_main();
 }
 
-//#define CLEANUP_compress
-//#define FOR_zcat
-//#include "generated/flags.h"
+#define CLEANUP_compress
+#define FOR_zcat
+#include "generated/flags.h"
 
 void zcat_main(void)
 {
-  init_deflate();
+  init_deflate(0);
 
   loopfiles(toys.optargs, do_zcat);
 }
+
+//void gunzip_main(void)
+//{
+//  init_deflate(0);
+//
+//  loopfiles(toys.optargs, do_zcat);
+//}
--- a/toys/pending/init.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/pending/init.c	Sat Apr 12 17:26:44 2014 -0500
@@ -13,7 +13,10 @@
   help
     usage: init
 
-    init the system.
+    System V style init.
+
+    First program to run (as PID 1) when the system comes up, reading
+    /etc/inittab to determine actions.
 */
 
 #include "toys.h"
@@ -41,8 +44,9 @@
 static void initialize_console(void)
 {
   int fd;
-  char *p = (p = getenv("CONSOLE")) ? p : getenv("console");
+  char *p = getenv("CONSOLE");
 
+  if (!p) p = getenv("console");
   if (!p) {
     fd = open("/dev/null", O_RDWR);
     if (fd >= 0) {
@@ -62,11 +66,11 @@
   if (!getenv("TERM")) putenv("TERM=linux");
 }
 
-static void set_sane_term(void)
+static void reset_term(int fd)
 {
   struct termios terminal;
  
-  tcgetattr(0, &terminal);
+  tcgetattr(fd, &terminal);
   terminal.c_cc[VINTR] = 3;    //ctrl-c
   terminal.c_cc[VQUIT] = 28;   /*ctrl-\*/
   terminal.c_cc[VERASE] = 127; //ctrl-?
@@ -77,12 +81,16 @@
   terminal.c_cc[VSUSP] = 26;   //ctrl-z
 
   terminal.c_line = 0;
-  terminal.c_cflag = terminal.c_cflag&(CRTSCTS|PARODD|PARENB|CSTOPB|CSIZE|CBAUDEX|CBAUD);
-  terminal.c_cflag = terminal.c_cflag|(CLOCAL|HUPCL|CREAD);
-  terminal.c_iflag = IXON|IXOFF|ICRNL;//enable start/stop input and output control + map CR to NL on input
-  terminal.c_oflag = ONLCR|OPOST;//Map NL to CR-NL on output
+  terminal.c_cflag &= CRTSCTS|PARODD|PARENB|CSTOPB|CSIZE|CBAUDEX|CBAUD;
+  terminal.c_cflag |= CLOCAL|HUPCL|CREAD;
+
+  //enable start/stop input and output control + map CR to NL on input
+  terminal.c_iflag = IXON|IXOFF|ICRNL;
+
+  //Map NL to CR-NL on output
+  terminal.c_oflag = ONLCR|OPOST;
   terminal.c_lflag = IEXTEN|ECHOKE|ECHOCTL|ECHOK|ECHOE|ECHO|ICANON|ISIG;
-  tcsetattr(0, TCSANOW, &terminal);
+  tcsetattr(fd, TCSANOW, &terminal);
 }
 
 static void add_new_action(uint8_t action,char *command,char *term)
@@ -250,7 +258,7 @@
       dup2(0, 2);
     }
   }
-  set_sane_term();
+  reset_term(0);
   run_command(x->command);
   _exit(-1);
 }
@@ -281,6 +289,7 @@
     if (kill(y, 0)) break;
   }
 }
+
 static void run_action_from_list(int action)
 {
   pid_t pid;
@@ -302,14 +311,7 @@
 {
   sigset_t signal_set_c;
 
-  signal(SIGUSR1,SIG_DFL);
-  signal(SIGUSR2,SIG_DFL);
-  signal(SIGTERM,SIG_DFL);
-  signal(SIGQUIT,SIG_DFL);
-  signal(SIGINT,SIG_DFL);
-  signal(SIGHUP,SIG_DFL);
-  signal(SIGTSTP,SIG_DFL);
-  signal(SIGSTOP,SIG_DFL);
+  sigatexit(SIG_DFL);
   sigfillset(&signal_set_c);
   sigprocmask(SIG_UNBLOCK,&signal_set_c, NULL);
 
@@ -357,6 +359,7 @@
 
   while(1) sleep(1);
 }
+
 static void restart_init_handler(int sig_no)
 {
   struct action_list_seed *x;
@@ -386,7 +389,7 @@
       } else {
         dup2(0, 1);
         dup2(0, 2);
-        set_sane_term();
+        reset_term(0);
         run_command(x->command);
       }
     }
@@ -440,7 +443,7 @@
   if (getpid() != 1) error_exit("Already running"); 
   printf("Started init\n"); 
   initialize_console();
-  set_sane_term();
+  reset_term(0);
 
   if (chdir("/")) perror_exit("Can't cd to /");
   setsid();
@@ -449,6 +452,7 @@
   putenv("PATH=/sbin:/usr/sbin:/bin:/usr/bin");
   putenv("SHELL=/bin/sh");
   putenv("USER=root");
+
   inittab_parsing();  
   signal(SIGUSR1, halt_poweroff_reboot_handler);//halt
   signal(SIGUSR2, halt_poweroff_reboot_handler);//poweroff
--- a/toys/posix/date.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/posix/date.c	Sat Apr 12 17:26:44 2014 -0500
@@ -7,21 +7,39 @@
  * Note: setting a 2 year date is 50 years back/forward from today,
  * not posix's hardwired magic dates.
 
-USE_DATE(NEWTOY(date, "r:u", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:s:r:u", TOYFLAG_BIN))
 
 config DATE
   bool "date"
   default y
   help
-    usage: date [-u] [-r FILE] [+FORMAT] | mmddhhmm[[cc]yy[.ss]]
+    usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-s SET_FORMAT] [SET]
 
-    Set/get the current date/time.
+    Set/get the current date/time. With no SET shows the current date.
 
-    Setting the date requires month, day, hour (0-23), and minute, each
-    two digits. It can optionally include year, century, and .seconds.
+    Default SET format is "MMDDhhmm[[CC]YY][.ss]", that's (2 digits each)
+    month, day, hour (0-23), and minute. Optionally century, year, and second.
 
-    -u	Use UTC timezone instead of current
-    -r	Use date from FILE instead of current date
+    -d	Show DATE instead of current time (convert date format)
+    -r	Use modification time of FILE instead of current date
+    -s	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])
+    -u	Use UTC instead of current timezone
+
+    +FORMAT specifies display format string using these escapes:
+
+    %% literal %             %n newline              %t tab
+    %S seconds (00-60)       %M minute (00-59)       %m month (01-12)
+    %H hour (0-23)           %I hour (01-12)         %p AM/PM
+    %y short year (00-99)    %Y year                 %C century
+    %a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)
+    %b short month name      %B month name           %Z timezone name
+    %j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)
+
+    %U Week of year (0-53 start sunday)   %W Week of year (0-53 start monday)
+    %V Week of year (1-53 start monday, week < 4 days not part of this year) 
+
+    %D = "%m/%d/%y"    %r = "%I : %M : %S %p"   %T = "%H:%M:%S"   %h = "%b"
+    %x locale date     %X locale time           %c locale date/time
 */
 
 #define FOR_date
@@ -29,72 +47,95 @@
 
 GLOBALS(
   char *file;
+  char *setfmt;
+  char *showdate;
 )
 
+// Handle default posix date format: mmddhhmm[[cc]yy]
+// returns 0 success, nonzero for error
+int parse_posixdate(char *str, struct tm *tm)
+{
+  struct timeval tv;
+  int len;
+
+  len = 0;
+  sscanf(str, "%2u%2u%2u%2u%n", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
+    &tm.tm_min, &len);
+  if (len != 8) goto bad_date;
+  str += len;
+  tm.tm_mon--;
+
+  // If year specified, overwrite one we fetched earlier
+  if (*str && *str != '.') {
+    unsigned year, r1 = tm.tm_year % 100, r2 = (tm.tm_year + 50) % 100,
+      century = tm.tm_year - r1;
+
+    len = 0;
+    sscanf(str, "%u%n", &year, &len);
+    if (len == 4) year -= 1900;
+    else if (len != 2) goto bad_date;
+    str += len;
+
+    // 2 digit years, next 50 years are "future", last 50 years are "past".
+    // A "future" date in past is a century ahead.
+    // A non-future date in the future is a century behind.
+    if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) {
+      if (year < r1) year += 100;
+    } else if (year > r1) year -= 100;
+    tm.tm_year = year + century;
+  }
+  if (*str == '.') {
+    len = 0;
+    sscanf(str, ".%u%n", &tm.tm_sec, &len);
+    str += len;
+  }
+
+  return *str;
+}
+
+
 void date_main(void)
 {
-  const char *format_string = "%a %b %e %H:%M:%S %Z %Y";
+  char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y",
+       *tz;
   time_t now = time(NULL);
   struct tm tm;
 
+  // We can't just pass a timezone to mktime because posix.
+  if (toys.optflags & FLAG_u) {
+    tz = CFG_TOYBOX_FREE ? getenv("TZ") : 0;
+    setenv("TZ", "UTC", 1);
+    tzset();
+  }
+
   if (TT.file) {
     struct stat st;
 
     xstat(TT.file, &st);
     now = st.st_mtim.tv_sec;
-  }
-  ((toys.optflags & FLAG_u) ? gmtime_r : localtime_r)(&now, &tm);
+  } else if (TT.showdate) {
+    if (TT.setfmt) {
+      char *s = strptime(TT.showdate, TT.setfmt, &tm);
 
+      if (!s || !*s) goto bad_date;
+    } else if (parse_posixdate(TT.showdate, &tm)) goto bad_date;
+  } else localtime_r(&now, &tm);
+
+  // Fall through if no arguments
+  if (!setdate);
   // Display the date?
-  if (!toys.optargs[0] || toys.optargs[0][0] == '+') {
-    if (toys.optargs[0]) format_string = toys.optargs[0]+1;
-    if (!strftime(toybuf, sizeof(toybuf), format_string, &tm)) goto bad_format;
-
-    puts(toybuf);
+  else if (*setdate == '+') {
+    format_string = toys.optargs[0]+1;
+    setdate = toys.optargs[1];
 
   // Set the date
-  } else {
-    struct timeval tv;
-    char *s = *toys.optargs;
-    int len;
-
-    // Date format: mmddhhmm[[cc]yy]
-    len = 0;
-    sscanf(s, "%2u%2u%2u%2u%n", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
-      &tm.tm_min, &len);
-    if (len != 8) goto bad_date;
-    s += len;
-    tm.tm_mon--;
-
-    // If year specified, overwrite one we fetched earlier
-    if (*s && *s != '.') {
-      unsigned year, r1 = tm.tm_year % 100, r2 = (tm.tm_year + 50) % 100,
-        century = tm.tm_year - r1;
-
-      len = 0;
-      sscanf(s, "%u%n", &year, &len);
-      if (len == 4) year -= 1900;
-      else if (len != 2) goto bad_date;
-      s += len;
-
-      // 2 digit years, next 50 years are "future", last 50 years are "past".
-      // A "future" date in past is a century ahead.
-      // A non-future date in the future is a century behind.
-      if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) {
-        if (year < r1) year += 100;
-      } else if (year > r1) year -= 100;
-      tm.tm_year = year + century;
-    }
-    if (*s == '.') {
-      len = 0;
-      sscanf(s, ".%u%n", &tm.tm_sec, &len);
-      s += len;
-    }
-    if (*s) goto bad_date;
+  } else if (setdate) {
+    if (parse_posixdate(setdate, tm)) goto bad_date;
 
     if (toys.optflags & FLAG_u) {
-      // Get the UTC version of a struct tm
+      // We can't just pass a timezone to mktime because posix.
       char *tz = CFG_TOYBOX_FREE ? getenv("TZ") : 0;
+
       setenv("TZ", "UTC", 1);
       tzset();
       tv.tv_sec = mktime(&tm);
@@ -107,11 +148,18 @@
     if (tv.tv_sec == (time_t)-1) goto bad_date;
 
     tv.tv_usec = 0;
-    if (!strftime(toybuf, sizeof(toybuf), format_string, &tm)) goto bad_format;
-    puts(toybuf);
     if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date");
   }
 
+  if (toys.optflags & FLAG_u) {
+    if (tz) setenv("TZ", tz, 1);
+    else unsetenv("TZ");
+    tzset();
+  }
+
+  if (!strftime(toybuf, sizeof(toybuf), format_string, &tm)) goto bad_format;
+  puts(toybuf);
+
   return;
 
 bad_date:
--- a/toys/posix/du.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/posix/du.c	Sat Apr 12 17:26:44 2014 -0500
@@ -54,18 +54,8 @@
 
   if (TT.maxdepth && TT.depth > TT.maxdepth) return;
 
-  if (toys.optflags & FLAG_h) {
-    char buf[32];
-    int index, sz;
-
-    for (index = 0; 1024 < size>>(10*index); index++);
-    sz = size>>(10*index);
-    if (sz < 10) {
-      sprintf(buf, "%llu", size>>(10*(index-1)));
-      printf("%c.%c", buf[0], buf[1]);
-    } else printf("%d", sz);
-    if (index) printf("%c", " KMGTPE"[index]);
-  } else {
+  if (toys.optflags & FLAG_h) printf("%s", human_readable(size));
+  else {
     int bits = 10;
 
     if (toys.optflags & FLAG_K) bits = 9;
--- a/toys/posix/grep.c	Thu Apr 10 19:40:14 2014 -0500
+++ b/toys/posix/grep.c	Sat Apr 12 17:26:44 2014 -0500
@@ -4,7 +4,7 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
 
-USE_GREP(NEWTOY(grep, "ZzEFHabhinorsvwclqe*f*m#x[!wx][!EFw]", TOYFLAG_BIN))
+USE_GREP(NEWTOY(grep, "A#B#C#ZzEFHabhinorsvwclqe*f*m#x[!wx][!EFw]", TOYFLAG_BIN))
 USE_GREP(OLDTOY(egrep, grep, OPTSTR_grep, TOYFLAG_BIN))
 USE_GREP(OLDTOY(fgrep, grep, OPTSTR_grep, TOYFLAG_BIN))
 
@@ -46,40 +46,67 @@
   long m;
   struct arg_list *f;
   struct arg_list *e;
+  long C;
+  long B;
+  long A;
 
   struct arg_list *regex;
+  struct double_list *blist;
 )
 
+struct dlist_off {
+  char *next, *prev;
+  long offset;
+  char *data;
+};
+
 static void do_grep(int fd, char *name)
 {
   FILE *file = fdopen(fd, "r");
   long offset = 0;
-  int lcount = 0, mcount = 0, which = toys.optflags & FLAG_w ? 2 : 0;
+  int lcount = 0, mcount = 0, which = toys.optflags & FLAG_w ? 2 : 0,
+      blines = 0, alines = 0, dash = 0;
   char indelim = '\n' * !(toys.optflags&FLAG_z),
        outdelim = '\n' * !(toys.optflags&FLAG_Z);
 
   if (!fd) name = "(standard input)";
 
+fprintf(stderr, "boo\n");
   if (!file) {
     perror_msg("%s", name);
     return;
   }
 
+  // Loop through lines of input
   for (;;) {
-    char *line = 0, *start;
+    char *oline = 0, *line = 0, *start;
     regmatch_t matches[3];
     size_t unused;
     long len;
     int mmatch = 0;
 
+    // Read next line of input
     lcount++;
     if (0 > (len = getdelim(&line, &unused, indelim, file))) break;
     if (line[len-1] == indelim) line[len-1] = 0;
+fprintf(stderr, "line=%s\n", line);
+    // Unconditionally add line to blist so output can always just dump blist.
+    dlist_add(&TT.blist, line);
+fprintf(stderr, "added=%s\n", TT.blist->data);
+fprintf(stderr, "prev=%s\n", TT.blist->prev->data);
+    if (blines <= TT.B) blines++;
+    else {
+      struct double_list *temp = dlist_pop(&TT.blist);
+fprintf(stderr, "bird=%s\n", temp->data);
+      free(temp->data);
+      free(temp);
+    }
 
     start = line;
 
-    for (;;)
-    {
+    // Loop to match multiple times within the same line (if necessary)
+    for (;;) {
+fprintf(stderr, "match?\n");
       int rc = 0, skip = 0;
 
       if (toys.optflags & FLAG_F) {
@@ -132,30 +159,51 @@
         }
         matches[which].rm_so = 0;
       } else if (rc) break;
-
+fprintf(stderr, "got match %s\n", line);
+      // We got a match, figure out how to display it
       mmatch++;
       toys.exitval = 0;
       if (toys.optflags & FLAG_q) xexit();
       if (toys.optflags & FLAG_l) {
         printf("%s%c", name, outdelim);
-        free(line);
-        fclose(file);
-        return;
+        goto finish;
       }
+
+      line = 0;
+fprintf(stderr, "here=%s\n", TT.blist->prev->data);
+      // Yes, -o sometimes counts things as a match (-c) but doesn't display it
       if (toys.optflags & FLAG_o)
         if (matches[which].rm_eo == matches[which].rm_so)
           break;
 
-      if (!(toys.optflags & FLAG_c)) {
-        if (toys.optflags & FLAG_H) printf("%s:", name);
-        if (toys.optflags & FLAG_n) printf("%d:", lcount);
-        if (toys.optflags & FLAG_b)
-          printf("%ld:", offset + (start-line) +
-              ((toys.optflags & FLAG_o) ? matches[which].rm_so : 0));
-        if (!(toys.optflags & FLAG_o)) xprintf("%s%c", line, outdelim);
+// List of lines that DIDN'T match, print backlog?
+// Except this includes the one we just matched...?
+      while (TT.blist) {
+        struct double_list *dlist = dlist_pop(&TT.blist);
+        char *ll = dlist->data;
+fprintf(stderr, "popped %s\n", ll);
+        if (dash) printf("--%c", outdelim);
+        dash = 0;
+
+        if (!(toys.optflags & FLAG_c)) {
+          if (toys.optflags & FLAG_H) printf("%s:", name);
+          if (toys.optflags & FLAG_n) printf("%d:", lcount);
+          if (toys.optflags & FLAG_b)
+            printf("%ld:", offset + (start - dlist->data) +
+                ((toys.optflags & FLAG_o) ? matches[which].rm_so : 0));
+          if (!(toys.optflags & FLAG_o)) xprintf("%s%c", dlist->data, outdelim);
+          else if (!TT.blist) {
+
+// TODO: FLAG_o prints multiple times, can't free it yet?
+            xprintf("%.*s%c", matches[which].rm_eo - matches[which].rm_so,
+                    start + matches[which].rm_so, outdelim);
+            line = dlist->data;
+          }
+        }
+        if (oline && !TT.blist) TT.blist = dlist;
         else {
-          xprintf("%.*s%c", matches[which].rm_eo - matches[which].rm_so,
-                  start + matches[which].rm_so, outdelim);
+          free(dlist->data);
+          free(dlist);
         }
       }
 
@@ -163,7 +211,7 @@
       if (!(toys.optflags & FLAG_o) || !*start) break;
     }
     offset += len;
-
+fprintf(stderr, "Spacious skies\n");
     free(line);
 
     if (mmatch) mcount++;
@@ -175,6 +223,14 @@
     xprintf("%d%c", mcount, outdelim);
   }
 
+finish:
+  while (CFG_TOYBOX_FREE && TT.blist) {
+    struct double_list *dlist = dlist_pop(&TT.blist);
+
+    free(dlist->data);
+    free(dlist);
+  }
+
   // loopfiles will also close the fd, but this frees an (opaque) struct.
   fclose(file);
 }
@@ -273,6 +329,10 @@
     toys.optc--;
   }
 
+  if (!TT.A) TT.A = TT.C;
+  if (!TT.B) TT.B = TT.C;
+  if (toys.optflags & (FLAG_l|FLAG_o)) TT.B = 0; // avoid memory leak
+
   parse_regex();
 
   if (!(toys.optflags & FLAG_h) && toys.optc>1) toys.optflags |= FLAG_H;
--- a/www/roadmap.html	Thu Apr 10 19:40:14 2014 -0500
+++ b/www/roadmap.html	Sat Apr 12 17:26:44 2014 -0500
@@ -424,8 +424,38 @@
 mtrace nscd rpcent rpcinfo tzselect zdump zic
 </b></blockquote>
 
-<p>Of those, musl libc only implements ldd. I have no idea which of the rest
-are relevant.</p>
+<p>Of those, musl libc only implements ldd.</p>
+<p>catchsegv is a rudimentary debugger, probably out of scope for toybox.</p>
+<p>iconv has been <a href="#susv4">previously discussed</a>.</p>
+<p>iconvconfig is only relevant if iconv is user-configurable; musl uses a
+non-configurable iconv.</p>
+<p>getconf is a posix utility which displays several variables from 
+unistd.h; it probably belongs in the development toolchain.</p>
+<p>getent handles retrieving entries from passwd-style databases, 
+in a rather lame way.</p>
+<p>locale was discussed under <a href=#susv4>posix</a>.
+localedef compiles locale definitions, which musl currently does not use.</p>
+
+<p>mtrace is a perl script to use the malloc debugging that glibc has built-in;
+this is not relevant for musl, and would necessarily vary with libc. </p>
+<p>nscd is a name service caching daemon, which is not yet relevant for musl.
+rpcinfo and rpcent are related to rpc, which musl does not include.</p>
+
+<p>tzselect outputs a TZ variable correponding to user input. 
+The documentation does not indicate how to use it in a script, but it seems
+that Debian may have done so.
+zdump prints current time in each of several timezones, optionally
+outputting a great deal of extra information about each timezone.
+zic converts a description of a timezone to a file in the tz format.</p>
+
+<p>So this leaves the following interesting commands:</p>
+
+<blockquote><b>
+<span id=glibc_cmd>
+getent
+tzselect zdump zic
+</span>
+</b></blockquote>
 
 <hr />
 <a name=sash />