Created
February 9, 2010 09:36
-
-
Save jkk/299054 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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