--- toast	2004/07/19 03:52:38	1.336
+++ toast	2004/07/25 23:53:17	1.337
@@ -1572,6 +1572,195 @@
   error;
 }
 
+BEGIN # built-in gunzip (zcat)
+{
+  my($inbuf, $inlen, $outbuf, $written);
+  my(@llens) = qw[3 4 5 6 7 8 9 10 11 13 15 17 19 23 27 31 35 43 51 59 67 83
+      99 115 131 163 195 227 258];
+  my(@lbits) = qw[0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 0];
+  my(@dists) = qw[1 2 3 4 5 7 9 13 17 25 33 49 65 97 129 193 257 385 513 769
+      1025 1537 2049 3073 4097 6145 8193 12289 16385 24577];
+  my(@dbits) = qw[0 0 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12
+      12 13 13];
+  my(@order) = qw[16 17 18 0 8 7 9 6 10 5 11 4 12 3 13 2 14 1 15];
+
+  sub zread($)
+  {
+    my($len) = @_;
+    my($ofs, $buf) = 0;
+    while($len > 0)
+    {
+      my($ret) = read(STDIN, $buf, $len, $ofs);
+      defined($ret) or die("read: $!");
+      die("unexpected EOF") if $ret == 0;
+      $ofs += $ret;
+      $len -= $ret;
+    }
+    return $buf;
+  }
+
+  sub readbit()
+  {
+    ($inbuf, $inlen) = (unpack("C", zread(1)), 8) unless $inlen;
+    return ($inbuf & (1 << (8 - $inlen--))) ? 1 : 0;
+  }
+
+  sub nextbyte()
+  {
+    readbit && die("bad pad bit") while $inlen;
+  }
+
+  sub readbits($)
+  {
+    my($result, $bits) = (0, @_);
+    $result |= readbit << $_ for(0..$bits-1);
+    return $result;
+  }
+
+  sub inithuff(@)
+  {
+    my(@lens) = @_;
+    my($maxlen) = 0;
+    $_ > $maxlen && ($maxlen = $_) for @lens;
+    my(%result) = (maxlen => $maxlen);
+    my($code, $len) = 0;
+    for $len (1..$maxlen)
+    {
+      $code <<= 1;
+      for(0..$#lens)
+      {
+        $result{sprintf("\%0${len}b", $code++)} = $_ if $lens[$_] == $len;
+      }
+    }
+    return %result;
+  }
+
+  sub readhuff(%)
+  {
+    my(%map) = @_;
+    my($maxlen) = $map{"maxlen"};
+    my($bits) = "";
+    while(length($bits) < $maxlen)
+    {
+      $bits .= readbit;
+      return $map{$bits} if exists($map{$bits});
+    }
+    die("bad huffman code: $bits");
+  }
+
+  sub zwrite($)
+  {
+    my($data) = @_;
+    $written += length($data);
+    $outbuf .= $data;
+    substr($outbuf, 0, length($outbuf) - 32768) = "" if length($outbuf) > 65536;
+    print($data);
+  }
+
+  sub zcat()
+  {
+    my($id1, $id2, $cm, $flg) = unpack("C4", zread(10));
+    die("bad magic: $id1 $id2") unless $id1 == 31 && $id2 == 139;
+    die("bad cm: $cm") unless $cm == 8;
+    die("bad flags: $flg") if $flg & 0xe0;
+    zread(unpack("v", zread(2))) if $flg & 4; # FEXTRA
+    if($flg & 8) { while(zread(1) ne "\x00") { } } # FNAME
+    if($flg & 16) { while(zread(1) ne "\x00") { } } # FCOMMENT
+    zread(2) if $flg & 2; # FHCRC
+
+    ($written, $outbuf) = (0, "");
+    my($bfinal);
+    do
+    {
+      $bfinal = readbit;
+      my($btype) = readbits(2);
+      if($btype == 3)
+      {
+        die("bad btype");
+      }
+      elsif($btype == 0) # no compression
+      {
+        nextbyte;
+        my($len, $nlen) = unpack("v2", zread(4));
+        die("bad nlen: $len $nlen") if $nlen != (65535 - $len);
+        zwrite(zread($len));
+      }
+      else
+      {
+        my(%llmap, %dmap, $ll);
+        if($btype == 1) # fixed Huffman
+        {
+          $llmap{sprintf('%07b', $_)} = $_ + 256 for 0..23;
+          $llmap{sprintf('%08b', $_ + 48)} = $_ for 0..143;
+          $llmap{sprintf('%08b', $_ + 192)} = $_ + 280 for 0..7;
+          $llmap{sprintf('%09b', $_ + 400)} = $_ + 144 for 0..111;
+          $llmap{"maxlen"} = 9;
+          $dmap{sprintf('%05b', $_)} = $_ for 0..29;
+          $dmap{"maxlen"} = 5;
+        }
+        else # dynamic Huffman
+        {
+          my($hlit, $hdist, $hclen) = map(readbits($_), 5, 5, 4);
+          my(@rawclens, @clens, @lens);
+          push(@rawclens, readbits(3)) for 1..4+$hclen;
+          $clens[$order[$_]] = $rawclens[$_] || 0 for 0..$#order;
+          my(%cmap) = inithuff(@clens);
+          while(scalar(@lens) < $hlit + $hdist + 258)
+          {
+            my($code) = readhuff(%cmap);
+            if($code == 16)
+            {
+              die("no last code") unless @lens;
+              my($last) = $lens[$#lens];
+              push(@lens, $last) for 1..3+readbits(2);
+            }
+            elsif($code == 17)
+            {
+              push(@lens, 0) for 1..3+readbits(3);
+            }
+            elsif($code == 18)
+            {
+              push(@lens, 0) for 1..11+readbits(7);
+            }
+            else
+            {
+              push(@lens, $code);
+            }
+          }
+          %llmap = inithuff(@lens[0..$hlit+256]);
+          %dmap = inithuff(@lens[$hlit+257..$hlit+$hdist+257]);
+        }
+
+        while(256 != ($ll = readhuff(%llmap)))
+        {
+          if($ll < 256)
+          {
+            zwrite(chr($ll));
+          }
+          else
+          {
+            my($i) = $ll - 257;
+            my($len) = $llens[$i] + readbits($lbits[$i]);
+            my($dist) = $dists[$i = readhuff(%dmap)] + readbits($dbits[$i]);
+            zwrite(substr($outbuf, length($outbuf) - $dist, 1)) for(1..$len);
+          }
+        }
+      }
+    } until($bfinal);
+    my($crc32, $isize) = unpack("V2", zread(8));
+    die("bad isize: $isize != $written") unless $isize == $written;
+  }
+}
+
+sub zfork()
+{
+  explain("falling back on built-in gunzip");
+  return true if forkstdin;
+  zcat;
+  exit(0);
+  error;
+}
+
 sub extractstdin($)
 {
   my($type) = @_;
@@ -1588,7 +1777,7 @@
   if($type =~ /^\.(Z|gz|bz2)$/)
   {
     my($prog) = $type eq ".bz2" ? "bunzip2" : "gunzip";
-    open(STDIN, "$prog |") || error("$prog: $!");
+    open(STDIN, "$prog |") || $prog eq "gunzip" && zfork || error("$prog: $!");
     binmode(STDIN) || error("binmode stdin: $!"); # perl 5.8.0 utf8 bug
     autoextractstdin;
     error;
@@ -5499,21 +5688,20 @@
 Packages that already have at least one C<built> or C<armed> build (as
 reported by B<toast status>) are skipped by this command without causing
 an error; use B<toast rebuild> to force such packages to be rebuilt.
-Building may involve implicitly invoking B<toast get>, decompressing
-and extracting archives, applying patch files, compiling a new build
-of the package and installing it in a build-specific directory tree.
-Supported archive formats include compress, gzip, bzip2, zip, rpm,
-deb, cpio, tar, shar, patch, and most combinations of the above.
-You don't need to have RPM installed to extract .rpm files; gunzip and
-cpio usually suffice.  Similarly, only gunzip and tar should be required
-to extract .deb files.  Note that toast completely ignores dependencies
-and other meta-information in .rpm and .deb files; only the raw binaries
-are extracted and used.  Archives should contain either precompiled
-binaries or source code, which will be identified and/or built according
-to heuristics too mind-numbing to describe completely; in the case of
-source files, a C<configure> script, C<Makefile> or similar is often
-required.  Many options can influence this command's behavior; see the
-options reference for full details.
+Building may involve implicitly invoking B<toast get>, decompressing and
+extracting archives, applying patch files, compiling a new build of the
+package and installing it in a build-specific directory tree.  Supported
+archive formats include compress, gzip, bzip2, zip, rpm, deb, cpio,
+tar, shar, patch, and most combinations of the above.  You don't need to
+have RPM installed to extract .rpm files; cpio should usually suffice.
+Similarly, only tar should be required to extract .deb files.  Note that
+toast completely ignores dependencies and other meta-information in .rpm
+and .deb files; only the raw binaries are extracted and used.  Archives
+should contain either precompiled binaries or source code, which will
+be identified and/or built according to heuristics too mind-numbing to
+describe completely; in the case of source files, a C<configure> script,
+C<Makefile> or similar is often required.  Many options can influence
+this command's behavior; see the options reference for full details.
 
 =item S<B<toast arm> [ I<BUILD> | I<PACKAGE> ...]>
 
@@ -6227,6 +6415,8 @@
 
 Wish list:
 
+  - simpler setup for root: auto-create toast user? (email 7/21/04)
+  - deal better with missing gcc: clearer error msg? (email 7/21/04)
   - error messages that explain command usage (gale 2004-05-06 17:08:20)
   - convenient way to specify storedir, etc. during first time setup
   - work around lack of getenv(), mkdir(), etc. in microperl...?