Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!


Click here to return to the 'how to set the compression level, plus an updated script' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
how to set the compression level, plus an updated script
Authored by: cactus on May 05, '03 05:14:55PM
Here is a script I wrote which creates a backup, then compresses and encrypts the image. It performs some sanity checks and prints copious output. Note that the tar I'm using is from the Fink distribution, in /sw/bin. Without this version, you need to adjust the pathname (I haven't tested this).
#!/usr/bin/perl

use Getopt::Std;
use Cwd;
use File::Basename;

$DF='/bin/df';
$DU='/usr/bin/du';
$HDIUTIL='/usr/bin/hdiutil';
$HDID='/usr/bin/hdid';
$DITTO='/usr/bin/ditto';
$DISKUTIL='/usr/sbin/diskutil';
$DISKTOOL='/usr/sbin/disktool';
$MOUNT='/sbin/mount';
$UMOUNT='/sbin/umount';
$ID='/usr/bin/id';
$STTY='/bin/stty';
$TAR='/sw/bin/tar';
$SUDO = '/usr/bin/sudo';
$NEWFS_HFS = '/sbin/newfs_hfs';
$CHFLAGS = '/usr/bin/chflags';

$MAX_DU_LEN = 160;
$MIN_SLACK  = 32;
$SLACK_FACTOR = 0.05;
$MIN_HFS = 4 * 1024 + 64;

sub checkexist(@);
sub getpasswd($);
sub getinput($);
sub diskusage(@);
sub changeflags(@);
sub addslack($);
sub setuser();
sub makevolume($$);
sub attach($);
sub makehfs($$);
sub mount($$);
sub ditto($@);
sub unmount($);
sub detach($);
sub convert2cryptcompressed($$$);
sub attachcrypt($$);
sub mountreadonly($$);
sub ducom($);
sub debugdu($$$);
sub ditto_link($$);
sub ditto_dir($$);
sub ditto_file($$);
sub ditto_path($$);
sub docommand($);
sub real_docommand($$);

############################################################################

%opts = ();

getopts('i:m:p:v:', \%opts) or
    die("usage:\n" .
        "   $0 [-i] [-m] [-p] " .
        "[-v] node1 [...]\n");

checkexist(@ARGV) || die("$0: checkexist() failed\n");

$mypass = ($opts{"p"}) ? $opts{"p"} : getpasswd("Passwd");
$myvolume = ($opts{"v"}) ? $opts{"v"} : getinput("Volume Name");

$imagename = ($opts{"i"}) ? $opts{"i"} : getinput("Image Filename");
unless ($imagename =~ /^.+\.dmg$/)
{
    $imagename =~ s/$/.dmg/;
}
$tmpimage = $imagename;
unless ($tmpimage =~ s/^(.+)(\.dmg)$/\1.TEMP\2/)
{
    die("$0: trying to create temp filename from '${imagename}'\n" .
        "    '${tmpimage}'\n");
}
if (-e $tmpimage)
{
    die("$0: temporary image filename '${tmpimage}' exists\n");
}

$mountdir = ($opts{"m"}) ? $opts{"m"} : getinput("Mount Directory");
unless (-d $mountdir)
{
    die("$0: directory '${mountdir}' doesn't exist\n");
}

($dulist, $origsize) = diskusage(@ARGV);
changeflags(@ARGV);
$totsize = addslack($origsize);
setuser();
makevolume($totsize, $tmpimage);
$diskname = attach($tmpimage);
makehfs($diskname, $myvolume);
mount($diskname, $mountdir);
$targetlist = ditto($mountdir, @ARGV);
unmount($mountdir);
detach($diskname);
convert2cryptcompressed($tmpimage, $imagename, $mypass);
print("@@ unlink(${tmpimage})\n");
unlink($tmpimage) ||
    warn("$0: couldn't remove temp image '${tmpimage}': $!\n");

$diskname = attachcrypt($imagename, $mypass);
mountreadonly($diskname, $mountdir);
($imgdulist, $imgsize) = diskusage(@$targetlist);
unmount($mountdir);
detach($diskname);

print("\n\n");
print("original disk usage = ${origsize} K\n");
print("image disk usage    = ${imgsize} K\n");
print("volume name         = '${myvolume}'\n");
print("image file          = '${imagename}'\n");
print("====================================================\n");
debugdu($dulist, $imgdulist, \*STDOUT);
print("\n");



############################################################################
############################################################################
############################################################################

sub checkexist(@)
{
    my @nodes = @_;
    my $node;
    my $retval = 1;

    foreach $node (@nodes)
    {
        unless (-r $node)
        {
            warn("$0: checkexist(): `$node' doesn't exist or isn't readable\n");
            $retval = 0;
        }
    }

    return($retval);
}

sub getpasswd($)
{
    my ($prompt) = @_;
    my $word;
    my $command;

    open(TTY, ">/dev/tty") ||
        die("$0: getpasswd(): couldn't open /dev/tty: $!\n");

    $command = "${STTY} -echo";

    unless (docommand($command))
    {
        docommand("${STTY} echo");
        die("$0: bailing\n" .
            "'${command}'\n");
    }
    print TTY ("$prompt: ");
    chomp($word = );
    print TTY ("\n");

    $command = "${STTY} echo";
    docommand($command) ||
        die("$0: bailing\n" .
            "'${command}'\n");
    close(TTY);

    return($word);
}

sub getinput($)
{
    my ($prompt) = @_;
    my $word;

    open(TTY, ">/dev/tty") ||
        die("$0: getvolname(): couldn't open /dev/tty: $!\n");;
    print TTY ("$prompt: ");
    chomp($word = );
    close(TTY);

    return($word);
}

sub diskusage(@)
{
    my @nodes = @_;
    my ($node, $size, $totk, $fullist);

    $totk = 0;
    $fulllist = [ ];

    foreach $node (@nodes)
    {
        $size = ducom($node);
        $totk += $size;
        push(@$fulllist, [ $node, $size ]);
    }

    return($fulllist, $totk);
}

sub changeflags(@)
{
    my $command;
    my $node;

    foreach $node (@_)
    {
        $command = qq(${CHFLAGS} -R nouchg,noschg "$node");

        docommand($command) ||
            die("$0: changeflags(): command failed\n" .
                "'${command}'\n");
    }
}

sub addslack($)
{
    my ($original) = @_;
    my $slack;

    $slack = int(0.5 + $original * $SLACK_FACTOR);
    $slack = ($slack > $MIN_SLACK) ? $slack : $MIN_SLACK;

    return ($original + $slack);
}

sub setuser()
{
    my $command;

    $command = "${DISKTOOL} -c $;
    print $line;
    unless ($line =~ m{^/dev/disk\d+\s+Apple_partition_scheme\s*$})
    {
        die("$0: attach(): unexpected output (line 1) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless ($line =~ m{^/dev/disk\d+s1\s+Apple_partition_map\s*$})
    {
        die("$0: attach(): unexpected output (line 2) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless (($diskname) = ($line =~ m{^/dev/(disk\d+)s2\s+Apple_HFS\s*$}))
    {
        die("$0: attach(): unexpected output (line 3) from command\n" .
            "'${command}'\n");
    }

    close(HDID);

    return($diskname);
}

sub makehfs($$)
{
    my ($diskname, $volname) = @_;
    my $command;

    $command = qq(${SUDO} ${NEWFS_HFS} -v "${volname}" /dev/${diskname}s2);

    docommand($command) ||
        die("$0: makehfs(): command failed\n" .
            "'${command}'\n");
}

sub mount($$)
{
    my ($diskname, $mountdir) = @_;
    my $command;

    $command = qq(${MOUNT} -thfs /dev/${diskname}s2 "${mountdir}");

    docommand($command) ||
        die("$0: mount(): command failed\n" .
            "'${command}'\n");
}

sub ditto($@)
{
    my $mountdir = shift(@_);
    my ($node, $targetlist, $target);

    $targetlist = [ ];
    foreach $node (@_)
    {
        if (-l $node)
        {
            $target = ditto_link($mountdir, $node);
        }
        elsif ((-f $node) || (-b $node) || (-c $node))
        {
            $target = ditto_file($mountdir, $node);
        }
        elsif (-d $node)
        {
            $target = ditto_dir($mountdir, $node);
        }
        else
        {
            warn("$0: ditto(): skipping pipe/socket/unknown:\n  '$node'\n");
        }
        push(@$targetlist, $target);
    }

    return($targetlist);
}

sub unmount($)
{
    my ($mountdir) = @_;
    my $command;

    $command = qq(${UMOUNT} "${mountdir}");

    docommand($command) ||
        die("$0: unmount(): command failed\n" .
            "'${command}'\n");
}

sub detach($)
{
    my ($diskname) = @_;
    my $command;

    $command = "${HDIUTIL} detach ${diskname}";

    docommand($command) ||
        die("$0: detach(): command failed\n" .
            "'${command}'\n");
}

sub convert2cryptcompressed($$$)
{
    my ($oldimage, $newimage, $password) = @_;
    my ($command, $printcommand);

    $command = qq(${HDIUTIL} convert -encryption -passphrase "${password}" ) .
               qq(-format 'UDZO UDIF' -tgtimagekey zlib-level=9 "${oldimage}" ) .
               qq(-o "${newimage}");

    $printcommand = $command;
    $printcommand =~ s/-passphrase "${password}"/-passphrase "********"/;

    real_docommand($command, $printcommand) ||
        die("$0: convert2cryptcompressed(): command failed\n" .
            "'${command}'\n");
}

sub attachcrypt($$)
{
    my ($image, $password) = @_;
    my ($command, $line, $diskname, $printcommand);

    $command = qq(${HDID} -encryption -passphrase "${mypass}" -nomount "$image");

    $printcommand = $command;
    $printcommand =~ s/-passphrase "${password}"/-passphrase "********"/;

    print("@ ${printcommand}\n");
    open(HDID, "${command} |") ||
        die("$0: attachcrypt(): command failed: $!\n'${command}'\n");

    $line = ;
    print $line;
    unless ($line =~ m{^/dev/disk\d+\s+Apple_partition_scheme\s*$})
    {
        die("$0: attachcrypt(): unexpected output (line 1) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless ($line =~ m{^/dev/disk\d+s1\s+Apple_partition_map\s*$})
    {
        die("$0: attachcrypt(): unexpected output (line 2) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless (($diskname) = ($line =~ m{^/dev/(disk\d+)s2\s+Apple_HFS\s*$}))
    {
        die("$0: attachcrypt(): unexpected output (line 3) from command\n" .
            "'${command}'\n");
    }

    close(HDID);

    return($diskname);
}

sub mountreadonly($$)
{
    my ($diskname, $mountdir) = @_;
    my $command;

    $command = qq(${MOUNT} -r -thfs /dev/${diskname}s2 "${mountdir}");

    docommand($command) ||
        die("$0: mountreadonly(): command failed\n" .
            "'${command}'\n");
}

sub ducom($)
{
    my ($node) = @_;
    my ($dulist, $command, $line, $expectnode, $nodename,
        $size, $totsize);

    $command = qq(${DU} -sk "${node}");

    print("@ ${command}\n");
    open(DU, "${command} |") ||
        die("$0: ducom(): command failed : $!\n" .
            "'${command}'\n");

    chomp($line = );
    print("$line\n");
    close(DU);

    unless (($size, $nodename) = ($line =~ /^(\d+)\s+($node)\s*$/))
    {
        die("$0: ducom(): parse error checking for " .
            "'$node' in line:\n'$line'\n");
    }

    return($size);
}

sub debugdu($$$)
{
    my ($dulist_orig, $dulist_new, $fh) = @_;
    my $maxlen = 0;
    my $maxsize = 0;
    my ($item, $name, $size, $mylen, $mysizelen, $i, $matchall);
    my ($nname, $nsize);

    foreach $item (@$dulist_orig)
    {
        ($name, $size) = @$item;
        $mylen = length($name);
        $mysizelen = length($size);
        $maxlen = ($maxlen [$i]};
        ($nname, $nsize) = @{$dulist_new->[$i]};
        ($size == $nsize) or
            $matchall = 0;
        printf $fh ("%-*s : %*d\n",
                    $maxlen, $name,
                    $maxsize, $size);
        printf $fh ("%-*s : %*d\n",
                    $maxlen, $nname,
                    $maxsize, $nsize);
    }

    print $fh "\n";

    if ($matchall)
    {
        print $fh "Disk usage matches\n";
    }
    else
    {
        print $fh "ERROR: DISK USAGE DOES NOT MATCH\n";
    }
}

sub ditto_link($$)
{
    my ($mountdir, $srcnode) = @_;
    my ($srcdir, $destdir, $command);
    my ($srcname, $path, $srcpath, $orig_wd, $target);

    unless (-l $srcnode)
    {
        die("$0: ditto_link(): invalid argument (not a link):\n" .
            "    '${srcnode}'\n");
    }

    ($srcname, $path) = fileparse($srcnode);
    $srcpath = &Cwd::realpath($path);

    unless (-d $srcpath)
    {
        die("$0: ditto_link(): missing directory:\n" .
            "    '${srcpath}'\n" .
            "resolved from link name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    unless ($srcpath =~ m{^/})
    {
        die("$0: ditto_link(): srcpath missing leading /:\n" .
            "    '${srcpath}'\n" .
            "resolved from link name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    $destdir = "${mountdir}${srcpath}";
    $target = "${destdir}/${srcname}";

    unless (-d $destdir)
    {
        ditto_path(${srcpath}, ${destdir});
    }

    $orig_wd = &Cwd::getcwd;
    chdir(${srcpath}) ||
        die("$0: ditto_link(): couldn't chdir to '${srcpath}': $!\n");

    unless (-l ${srcname})
    {
        die("$0: ditto_link(): link '${srcname}' not found in '${srcpath}'\n");
    }

    $command = qq[${TAR} cf - "${srcname}" | ( cd "${destdir}" ; ${TAR} xf - )];

    docommand($command) ||
        die("$0: ditto_link(): command failed\n" .
            "'${command}'\n");

    chdir(${orig_wd}) ||
        die("$0: ditto_link(): couldn't chdir back to '${orig_wd}': $!\n");

    unless (-l $target)
    {
        die("$0: ditto_link(): target not created:\n" .
            "    source = '${srcnode}'\n" .
            "    target = '${target}'\n");
    }

    return($target);
}

sub ditto_dir($$)
{
    my ($mountdir, $srcdir) = @_;
    my ($srcpath, $destdir, $target);

    unless (-d $srcdir)
    {
        die("$0: ditto_dir(): invalid argument (not a directory):\n" .
            "    '${srcdir}'\n");
    }

    $srcpath = &Cwd::realpath($srcdir);

    unless (-d $srcpath)
    {
        die("$0: ditto_dir(): missing directory:\n" .
            "    '${srcpath}'\n" .
            "resolved from directory name:\n" .
            "    '${srcdir}'\n");
    }

    $destdir = "${mountdir}${srcpath}";
    $target = $destdir;

    $command = qq(${DITTO} -v -rsrcFork "${srcpath}" "${destdir}");

    docommand($command) ||
        die("$0: ditto_dir(): command failed\n" .
            "'${command}'\n");

    unless (-d $target)
    {
        die("$0: ditto_dir(): target not created:\n" .
            "    source = '${srcnode}'\n" .
            "    target = '${target}'\n");
    }

    return($target);
}

sub ditto_file($$)
{
    my ($mountdir, $srcnode) = @_;
    my ($srcdir, $destdir, $command);
    my ($srcname, $path, $srcpath, $orig_wd, $target);

    unless ((-f $srcnode) || (-c $srcnode) || (-b $srcnode))
    {
        die("$0: ditto_file(): invalid argument (not a file/char/block):\n" .
            "    '${srcnode}'\n");
    }

    ($srcname, $path) = fileparse($srcnode);
    $srcpath = &Cwd::realpath($path);

    unless (-d $srcpath)
    {
        die("$0: ditto_file(): missing directory:\n" .
            "    '${srcpath}'\n" .
            "resolved from file name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    unless ($srcpath =~ m{^/})
    {
        die("$0: ditto_file(): srcpath missing leading /:\n" .
            "    '${srcpath}'\n" .
            "resolved from file name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    $destdir = "${mountdir}${srcpath}";
    $target = "${destdir}/${srcname}";

    unless (-d $destdir)
    {
        ditto_path(${srcpath}, ${destdir});
    }

    $command = qq(${DITTO} -v -rsrcFork "${srcpath}/${srcname}" ) .
               qq("${target}");

    docommand($command) ||
        die("$0: ditto_file(): command failed\n" .
            "'${command}'\n");

    unless ((-f $target) || (-c $target) || (-b $target))
    {
        die("$0: ditto_file(): target not created:\n" .
            "    source = '${srcnode}'\n" .
            "    target = '${target}'\n");
    }

    return($target);
}

sub ditto_path($$)
{
    my ($srcpath, $destpath) = @_;
    my ($tmpdir, $tmpnum, $command, $dir, $srctemp, $desttemp);

    $tmpnum = 0;
    $tmpdir = sprintf("TEMPDIR.%04d.$$", $tmpnum);
    $srctemp = "${srcpath}/${tmpdir}";
    $desttemp = "${destpath}/${tmpdir}";
    until ((! -e $srctemp) && (! -e $desttemp))
    {
        if ($tmpnum > 9999)
        {
            die("$0: ditto_path(): could not find available tmpname:\n" .
                "    '${srctemp}'\n" .
                "    '${desttemp}'\n");
        }

        $tmpnum++;
        $tmpdir = sprintf("TEMPDIR.%04d.$$", $tmpnum);
        $srctemp = "${srcpath}/${tmpdir}";
        $desttemp = "${destpath}/${tmpdir}";
    }

    if (-d $desttemp)
    {
        die("$0: ditto_path(): I was going to copy '${srctemp}'\n" .
            "   to '${desttemp}' but it already exists!\n");
    }

    print("@@ mkdir(${srctemp}, 0700)\n");
    mkdir($srctemp, 0700) ||
        die("$0: ditto_path(): mkdir('${srctemp}', 0700) failed: $!\n");

    $command = qq(${DITTO} -v "${srctemp}" "${desttemp}");

    docommand($command) ||
        die("$0: ditto_path(): command failed\n" .
            "'${command}'\n");

    $dir = "${srctemp}";
    print("@@ rmdir(${dir})\n");
    rmdir($dir) ||
        die("$0: ditto_path(): rmdir('${dir}') failed: $!\n");

    $dir = "${desttemp}";
    print("@@ rmdir(${dir})\n");
    rmdir($dir) ||
        die("$0: ditto_path(): rmdir('${dir}') failed: $!\n");
}

sub docommand($)
{
    my ($command) = @_;

    real_docommand($command, $command);
}

sub real_docommand($$)
{
    my ($command, $printcommand) = @_;
    my $rc;

    print("@ '${printcommand}'\n");

    $rc = 0xffff & system($command);
    if ($rc == 0)
    {
        return(1);
    }
    if ($rc == 0xff00)
    {
        warn("command failed: $!\n'$command'\n");
    }
    elsif ($rc > 0x80)
    {
        $rc >>= 8;
        warn("non-zero exit status $rc\n'$command'\n");
    }
    else
    {
        $core = ($rc & 0x80);
        if ($core)
        {
            $rc &= ~0x80;
        }
        warn("signal $rc");
        if ($core)
        {
            warn(" (core dump)");
        }
        warn("\n'$command'\n");
    }

    return(0);
}


[ Reply to This | # ]