Skip to content

Instantly share code, notes, and snippets.

@jkk
Created February 9, 2010 09:36
Show Gist options
  • Select an option

  • Save jkk/299054 to your computer and use it in GitHub Desktop.

Select an option

Save jkk/299054 to your computer and use it in GitHub Desktop.
#!/usr/bin/env perl
# Restores directories from an HFS+ Time Machine backup. Time Machine
# uses hard links to directories, which Linux does not support, so we have
# to suss out the actual directory locations and contents.
#
# Hard-linked directories have their "link count" stat set to a number which
# corresponds to a directory in the ".HFS+ Private Directory Data"
# directory, which can be found in the root of the volume.
#
# This code was hacked together quickly and isn't very efficient (it looks
# up each file's actual path separately) but it gets the job done.
#
# No particular effort is made to maintain ownership or permissions, so
# they are probably going to be wrong. I recommend running the script with
# sudo.
#
# USE WITH CAUTION. IF THIS SCRIPT SOMEHOW OBLITERATES YOUR PRECIOUS FILES,
# DON'T BLAME ME!
#
# Justin Kramer <jkkramer@gmail.com>
use File::Basename;
use File::Copy;
use File::stat;
use Cwd;
if (@ARGV < 4) {
print "Usage:\n" .
" $0 <mount dir> <machine name> <HD name and dir> <dest>\n" .
"Example:\n" .
" $0 /mnt/backup MacBook MacHD/Users/joe/Pictures /home/joe\n";
exit 1;
}
$mnt_dir = $ARGV[0];
$mnt_dir_esc = $mnt_dir;
$mnt_dir_esc =~ s/ /\\ /g;
$machine = $ARGV[1];
$src = $ARGV[2];
$dest = $ARGV[3];
# The ".HFS Private Directory Data" dir has a CR or LF in its name, so we
# find it using glob to make things easier.
my($hfspdd_root) = grep { basename($_) =~ /^\.HFS/ } glob($mnt_dir_esc . "/.*");
$hfspdd_root_esc = $hfspdd_root;
$hfspdd_root_esc =~ s/(\s)/\\$1/g;
%hfspdd_dirs = map { basename($_), 1 } glob($hfspdd_root_esc . "/*");
# Turns a given path, which may contain any number of hard-link dirs,
# into an actual, full path which Linux can read.
sub get_hfs_file {
$root = shift(@_);
@dirs = split(/\//, $root);
while (@dirs) {
$dir = shift(@dirs);
if ($dir eq "") {
$dir .= "/" . shift(@dirs);
}
if (-d $dir) {
chdir($dir);
} elsif (!-e $dir and !-l $dir) {
die "$dir does not exist\n";
} else {
$sb = stat($dir);
if (!defined($sb) and -l $dir) {
return cwd() . "/$dir";
}
$hfspdd_dir = "dir_" . $sb->nlink;
if ($hfspdd_dirs{$hfspdd_dir}) {
# Hard-linked directory -- use actual dir
chdir($hfspdd_root . "/$hfspdd_dir");
} else {
return cwd() . "/$dir";
}
}
}
return cwd();
}
@src_parts = split(/\//, $src);
$f = pop(@src_parts);
$src_root = $mnt_dir . "/Backups.backupdb/$machine/Latest/" . join("/", @src_parts);
@files = ($f);
while (@files) {
$f = pop(@files);
print "$f\n";
$src_f = get_hfs_file("$src_root/$f");
if (-d $src_f) {
mkdir("$dest/$f");
opendir(DIR, $src_f) or die "Error: $!";
while (defined($file = readdir(DIR))) {
next if ($file eq "." or $file eq "..");
push(@files, "$f/$file");
}
closedir(DIR);
} else {
copy($src_f, "$dest/$f");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment