Originally built to copy a block device (a mac mini in firewire mass storage mode) to an image file, excluding all-null blocks (sparse file) to save space.
Also contains examples of thousands-separator number formatting (i.e. 10,000,000) and human-readable magnitude number formatting (i.e. 10.0GB) functions.
#!/usr/bin/env php <?php declare(ticks=1); bcscale(3); // Read a file and process it in chunks. $infile = "/dev/sde"; $outfile = "/media/recover/MacMini-whole-sparse.img"; $blksz = 4096; $blkct = 19537686; $blkct_b = bcmul($blkct, $blksz, 0); function ksep($number) { $number = (string) $number; $max = strlen($number); $recip = $max; for ($i = 0; $i < $max; ++$i) { --$recip; if ($i && !($i % 3)) $out = "," . $out; $out = $number{$recip} . $out; } return $out; } function prettyGigs($bytes) { $powers = " kMGTPEZY"; $power = 0; $mod = 1; while (bcdiv($bytes, $mod) > 1024) { $mod = bcmul($mod, 1024); ++$power; } $num = bcdiv($bytes, $mod); switch (((int) $num != $num)? strpos($num, "."): 0) { case "0": $fmt = "%0.0f%s"; break; case "1": $fmt = "%0.3f%s"; break; case "2": $fmt = "%0.2f%s"; break; case "3": default: $fmt = "%0.1f%s"; break; } if ($power) $power = $powers{$power}."B"; else $power = "B"; return sprintf($fmt, $num, $power); } function stats() { pcntl_alarm(1); $blksz = $GLOBALS['blksz']; $blkct = $GLOBALS['blkct']; $blkct_b = $GLOBALS['blkct_b']; $data = $GLOBALS['data']; $data_b = bcmul($data, $blksz, 0); $sparse = $GLOBALS['sparse']; $sparse_b = bcmul($sparse, $blksz, 0); $count = bcadd($GLOBALS['data'], $GLOBALS['sparse'], 0); $count_b = bcmul($count, $blksz, 0); $pct = bcmul(bcdiv($count, $GLOBALS['blkct'], 10), 100.0); $speed = bcmul(bcsub($count, $GLOBALS['old_count']), $blksz); printf("\rD: %ss (%s) S: %ss (%s) +: %ss (%s) T: %ss (%s) %%: %0.4f %s/sec ", ksep($data), prettyGigs($data_b), ksep($sparse), prettyGigs($sparse_b), ksep($count), prettyGigs($count_b), ksep($blkct), prettyGigs($blkct_b), $pct, prettyGigs($speed)); /* printf("\rBlk: %s Spa: %s Sum: %s Tot: %s Pct: %0.4f ", thousands($GLOBALS['data']), thousands($GLOBALS['sparse']), thousands($count), thousands($GLOBALS['blkct']), $pct); */ $GLOBALS['old_count'] = $count; } pcntl_signal(SIGALRM, "stats", true); pcntl_alarm(1); echo "\n\nPrecaching..."; /* No longer required // Precalculate crc $bufr = str_repeat("\0", 4096); $zero = crc32($bufr); */ $input = fopen($infile, "rb"); $output = fopen($outfile, "wb"); if (!is_resource($input)) die(sprintf("%s failed!\n", $infile)); if (!is_resource($output)) die(sprintf("%s failed!\n", $outfile)); $data = 0; $sparse = 0; while (!feof($input)) { $bufr = fread($input, $blksz); $chars = count_chars($bufr, 3); if ($chars === "\0") { // Nul block. Skip it. fseek($output, ftell($output) + $blksz); $sparse++; continue; } /* Counting chars takes roughly half the time as computing the crc32. if (crc32($bufr) == $zero) { // Matching block? compare by bytes $chars = count_chars($bufr, 3); if ($chars === "\0") { // Null block. Skip it. fseek($output, ftell($output) + $blksz); continue; } } */ fwrite($output, $bufr, $blksz); $data++; } fclose($input); fclose($output); echo "\n\n"; ?>