Cleanup pass on dd: Implement 1x2x3 posix nonsense, detect overflow,
TT.buff is shared between input and output so shouldn't live in both.
diff --git a/toys/pending/dd.c b/toys/pending/dd.c
index 9a4d6df..77ebd88 100644
--- a/toys/pending/dd.c
+++ b/toys/pending/dd.c
@@ -17,7 +17,7 @@
     keyword=value modifiers (and their default values):
 
     if=FILE  Read FILE (stdin)          of=FILE  Write to FILE (stdout)
-       bs=N  Block size in bytes (512)  count=N  Stop after copying N blocks
+       bs=N  Block size in bytes (512)  count=N  Stop after copying N blocks (all)
       ibs=N  Input block size (bs=)       obs=N  Output block size (bs=)
      skip=N  Skip N input blocks (0)     seek=N  Skip N output blocks (0)
 
@@ -41,10 +41,10 @@
 GLOBALS(
   int show_xfer, show_records;
   unsigned long long bytes, in_full, in_part, out_full, out_part, start;
+  char *buff;
   struct {
-    char *name;
+    char *name, *bp;
     int fd;
-    unsigned char *buff, *bp;
     long sz, count;
     unsigned long long offset;
   } in, out;
@@ -86,9 +86,9 @@
 
 static void write_out(int all)
 {
-  TT.out.bp = TT.out.buff;
+  TT.out.bp = TT.buff;
   while (TT.out.count) {
-    ssize_t nw = writeall(TT.out.fd, TT.out.bp, ((all)? TT.out.count : TT.out.sz));
+    ssize_t nw = writeall(TT.out.fd, TT.out.bp, all ? TT.out.count : TT.out.sz);
 
     all = 0; //further writes will be on obs
     if (nw <= 0) perror_exit("%s: write error", TT.out.name);
@@ -99,7 +99,8 @@
     TT.bytes += nw;
     if (TT.out.count < TT.out.sz) break;
   }
-  if (TT.out.count) memmove(TT.out.buff, TT.out.bp, TT.out.count); //move remainder to front
+  // move remainder to front (if any)
+  if (TT.out.count) memmove(TT.buff, TT.out.bp, TT.out.count);
 }
 
 static void parse_flags(char *what, char *arg,
@@ -114,6 +115,40 @@
   free(pre);
 }
 
+// Multiply detecting overflow.
+static unsigned long long overmul(unsigned long long x, unsigned long long y)
+{
+  unsigned long long ll = x*y;
+
+  if (x && y && (ll<x || ll<y)) error_exit("overflow");
+
+  return ll;
+}
+
+// Handle funky posix 1x2x3 syntax.
+static unsigned long long argxarg(char *arg, int cap)
+{
+  long long ll = 1;
+  char x, *s, *new;
+
+  arg = xstrdup(arg);
+  for (new = s = arg;; new = s+1) {
+    // atolx() handlex 0x hex prefixes, so skip past those looking for separator
+    if ((s = strchr(new+2*!strncmp(new, "0x", 2), 'x'))) {
+      if (s==new) break;
+      x = *s;
+      *s = 0;
+    }
+    ll = overmul(ll, atolx(new));
+    if (s) *s = x;
+    else break;
+  }
+  if (s || ll<cap || ll>(cap ? LONG_MAX : LLONG_MAX)) error_exit("bad %s", arg);
+  free(arg);
+
+  return ll;
+}
+
 void dd_main()
 {
   char **args, *arg;
@@ -124,16 +159,14 @@
 
   TT.in.sz = TT.out.sz = 512; //default io block size
   for (args = toys.optargs; (arg = *args); args++) {
-    if (strstart(&arg, "bs=")) bs = atolx_range(arg, 1, LONG_MAX);
-    else if (strstart(&arg, "ibs=")) TT.in.sz = atolx_range(arg, 1, LONG_MAX);
-    else if (strstart(&arg, "obs=")) TT.out.sz = atolx_range(arg, 1, LONG_MAX);
-    else if (strstart(&arg, "count=")) count = atolx_range(arg, 0, LLONG_MAX);
+    if (strstart(&arg, "bs=")) bs = argxarg(arg, 1);
+    else if (strstart(&arg, "ibs=")) TT.in.sz = argxarg(arg, 1);
+    else if (strstart(&arg, "obs=")) TT.out.sz = argxarg(arg, 1);
+    else if (strstart(&arg, "count=")) count = argxarg(arg, 0);
     else if (strstart(&arg, "if=")) TT.in.name = arg;
     else if (strstart(&arg, "of=")) TT.out.name = arg;
-    else if (strstart(&arg, "seek="))
-      TT.out.offset = atolx_range(arg, 0, LLONG_MAX);
-    else if (strstart(&arg, "skip="))
-      TT.in.offset = atolx_range(arg, 0, LLONG_MAX);
+    else if (strstart(&arg, "seek=")) TT.out.offset = argxarg(arg, 0);
+    else if (strstart(&arg, "skip=")) TT.in.offset = argxarg(arg, 0);
     else if (strstart(&arg, "status=")) {
       if (!strcmp(arg, "noxfer")) TT.show_xfer = 0;
       else if (!strcmp(arg, "none")) TT.show_xfer = TT.show_records = 0;
@@ -154,8 +187,7 @@
 
   // For bs=, in/out is done as it is. so only in.sz is enough.
   // With Single buffer there will be overflow in a read following partial read.
-  TT.in.buff = TT.out.buff = xmalloc(TT.in.sz + (bs ? 0 : TT.out.sz));
-  TT.in.bp = TT.out.bp = TT.in.buff;
+  TT.in.bp = TT.out.bp = TT.buff = xmalloc(TT.in.sz+TT.out.sz*!bs);
 
   if (!TT.in.name) TT.in.name = "stdin";
   else TT.in.fd = xopenro(TT.in.name);
@@ -170,13 +202,12 @@
 
   // Implement skip=
   if (TT.in.offset) {
-    off_t off = TT.in.offset;
+    unsigned long long off = TT.in.offset;
 
     if (!(TT.iflag & _DD_iflag_skip_bytes)) off *= TT.in.sz;
     if (lseek(TT.in.fd, off, SEEK_CUR) < 0) {
       while (off > 0) {
-        int chunk = off < TT.in.sz ? off : TT.in.sz;
-        ssize_t n = read(TT.in.fd, TT.in.bp, chunk);
+        ssize_t n = read(TT.in.fd, TT.in.bp, minof(off, TT.in.sz));
 
         if (n < 0) {
           perror_msg("%s", TT.in.name);
@@ -203,19 +234,19 @@
       && ftruncate(TT.out.fd, bs)) perror_exit("truncate");
   }
 
-  if (!(TT.iflag & _DD_iflag_count_bytes) && count*TT.in.sz>count)
-    count *= TT.in.sz;
+  if (count!=ULLONG_MAX && !(TT.iflag & _DD_iflag_count_bytes))
+    count = overmul(count, TT.in.sz);
 
   while (count) {
-    int chunk = minof(count, TT.in.sz);
     ssize_t n;
 
-    TT.in.bp = TT.in.buff + TT.in.count;
+    TT.in.bp = TT.buff + TT.in.count;
     if (TT.conv & _DD_conv_sync) memset(TT.in.bp, 0, TT.in.sz);
     errno = 0;
-    if (!(n = read(TT.in.fd, TT.in.bp, chunk))) break;
-    if (n < 0) {
+    if (1>(n = read(TT.in.fd, TT.in.bp, minof(count, TT.in.sz)))) {
       if (errno == EINTR) continue;
+      if (!n) break;
+
       //read error case.
       perror_msg("%s: read error", TT.in.name);
       if (!(TT.conv & _DD_conv_noerror)) exit(1);
@@ -248,10 +279,12 @@
     }
   }
   if (TT.out.count) write_out(1); //write any remaining input blocks
-  if ((TT.conv & _DD_conv_fsync) && fsync(TT.out.fd)<0)
+  if ((TT.conv & _DD_conv_fsync) && fsync(TT.out.fd))
     perror_exit("%s: fsync", TT.out.name);
 
-  close(TT.in.fd);
-  close(TT.out.fd);
-  if (TT.in.buff) free(TT.in.buff);
+  if (CFG_TOYBOX_FREE) {
+    close(TT.in.fd);
+    close(TT.out.fd);
+    if (TT.buff) free(TT.buff);
+  }
 }