Note: You are viewing an old version of this page. View the current version.

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";

?>