Skip to content

Instantly share code, notes, and snippets.

@vjt
Last active March 8, 2024 17:05
Show Gist options
  • Save vjt/5183305 to your computer and use it in GitHub Desktop.
Save vjt/5183305 to your computer and use it in GitHub Desktop.
Copy data from a Time Machine volume mounted on a Linux box.
#!/bin/bash
#
# Copy data from a Time Machine volume mounted on a Linux box.
#
# Usage: copy-from-time-machine.sh <source> <target>
#
# source: the source directory inside a time machine backup
# target: the target directory in which to copy the reconstructed
# directory trees. Created if it does not exists.
#
# Details:
#
# Time machine implements directory hard links by creating an
# empty file in place of the directory and storing in its
# "number of hard links" metadata attribute a pointer to a
# real directory in "/.HFS Private directory data^M" named
# "dir_$number".
#
# This script reconstructs a plain directory tree from this
# really ugly apple hack. Tested on a 650GB backup from OSX
# 10.6 mounted on a Linux 3.2.0-38 Ubuntu box. YMMV.
#
# MIT License.
#
# - vjt@openssl.it
#
self="$0"
source="$1"
target="$2"
hfsd="$3"
set -e
if [ -z "$source" -o -z "$target" ]; then
echo "Usage: $self <source> <target>"
exit -1
fi
if [ ! -d "$target" ]; then
mkdir -p "$target"
fi
if [ -z "$hfsd" ]; then
# Look for HFS Private directory data
sysname="$(echo -ne '.HFS+ Private Directory Data\r')"
hfsd=$source
while [ "$hfsd" != "/" -a ! -d "$hfsd/$sysname" ]; do
hfsd=`dirname "$hfsd"`;
done
if [ "$hfsd" = '/' ]; then
echo "HFS Private Directory Data not found in $source, is it an HFS filesystem?"
exit -2
else
echo "HFS Private Directory Data found in '$hfsd'"
hfsd="$hfsd/$sysname"
fi
fi
find "$source" -mindepth 1 -maxdepth 1 -and -not -name . -and -not -name .. | while read entry; do
dest="$target/`basename "$entry"`"
read hlnum type <<<$(stat -c '%h %F' "$entry")
case $type in
'regular file'|'symbolic link')
cp -van "$entry" "$dest"
;;
'directory')
# Recurse
$self "$entry" "$dest" "$hfsd"
;;
'regular empty file')
if [ -d "$hfsd/dir_$hlnum" ]; then
# Recurse
$self "$hfsd/dir_$hlnum" "$dest" "$hfsd"
else
echo "Skipping empty file $entry"
fi
;;
esac
done
@ticky
Copy link

ticky commented Mar 22, 2014

Thank you so much for this. This has really saved me a whole bunch of time.

@dragonee
Copy link

dragonee commented Apr 27, 2014

Thanks! You have saved my life with that. :)

Two suggestions after running this gist:

  1. In case of read failures on disk, set -e should be commented out to recover as many files as much you can.
  2. I've found it necessary to add LANG=C to stat, because it returned localized strings for file types and the case statement did not recognize output.

@itsmrgomez
Copy link

Hello! I've made sure the scrips is executable then ran:

sudo bash copy-from-time-machine.sh "source" "target"

with my appropriate directories of course, but I keep getting:

HFS Private Directory Data found in '/media/Time Machine/'
copy-from-time-machine.sh: line 72: copy-from-time-machine.sh: command not found

Please help, did I do something wrong?

@iimog
Copy link

iimog commented Nov 1, 2014

Thanks for the nice script! I also needed the LANG=C option.
@itsmrgomez: make the script executable and call it as ./copy-from-time-machine.sh
The script recursively calls itself using the $0 variable. In case of your call it is just
copy-from-time-machine.sh which is not in your $PATH (therefore command not found).
If you call it directly as ./copy-from-time-machine.sh the recursive calls use the same and it should work.
Hope that helps!

@yongkangchen
Copy link

stat: cannot stat `/mnt/usb_1/.HFS+ Private Directory Data\r/dir_1027471/2014/ImportersPhotoshopImport.xml': No such file or directory

There occur error with stat when path contains \
The right path is:

/mnt/usb_1/.HFS+ Private Directory Data?/dir_1027471/2014/Importers\PhotoshopImport.xml

@grandmasterB
Copy link

Hi, when I run this script this is the output:

Syslnx ~ # ./timemachine.sh /media/thomas/MAXELL1 /tmp/osx
HFS Private Directory Data found in '/media/thomas/MAXELL1'

But nothing else happened. (the /tmp/osx dir still empty)
What should I do?

@er4z0r
Copy link

er4z0r commented Jun 16, 2015

Same problem as @grandmasterB. Script finds the private data and then no output is generated

@FlimFlam
Copy link

Same here :(

@magicoli
Copy link

magicoli commented Aug 9, 2015

Nothing happens when your locale is set to something else than english.
Add
unset LANG
or
export LANG=C
at the beginning of the script and it will fix it.

I made a fork including this fix:
https://gist.github.com/magicoli/283785bdf21ebafd2202

@magicoli
Copy link

magicoli commented Aug 9, 2015

Scritp does not work either if the source is a Time-Machine hard link. My fork https://gist.github.com/magicoli/283785bdf21ebafd2202 fixes that too.

@lifeofguenter
Copy link

I am getting a lot of the following errors:

stat: cannot stat ‘/xxxx’: Cannot allocate memory

Is there any way to fix this?

@BrunoCodeman
Copy link

You saved my life, lol! Thanks!

@shushugah
Copy link

Is it intended behavior that my Backups.backupdb folder is now empty? How can I use this with a mac again?

@ekortright
Copy link

Thank you very much for making this script available!

@shushugah's comment worried me, but if you read through the script you can see that no files are deleted or modified in any way in the source folder. I can see all the files in my Backups.backupdb folder after extracting everything I needed.

@kadirmalak
Copy link

Thank you :)

@alexcthomas
Copy link

This was so useful I created a python clone here:
https://gist.github.com/alexcthomas/6df11f8a7b10a40a1dbc6adf7440995f

Copy link

ghost commented May 9, 2019

Hey Guys ,

I'm a complete noob when it comes to programming. I have written small C programms for school but other than that, I'm useless hahah.
I wanted to ask how exactly to I proceed with this? I copied the code to my atom editor (but the version with the "unset LANG" at the beginning of the code) saved it as tmbackup.sh and compiled with chmod +x.
Then i tried to run the programm by typing ./tmbackup.sh /dev/sdb2/ desktop. This means my source is the /dev/sdb2/ and i wanted to test out if it works, so i used the desktop as my target (was that ok or completely wrong??) .
After executing that last line, i get the following error:*** HFS Private Directory Data not found in /dev/sdb2/, is it an HFS filesystem? ***

I would be very thankful for any advice, and excuse my lacking skills ^^

@dr0i
Copy link

dr0i commented Nov 9, 2019

Copied the script into a repo and updated it with some improvements from others forks. This also should help to better manage issues (thus having a more "clear" snapshot of the status quo in the README).
I do this because @vjt's script is a really good thing, but I had some issues (which I solved myself recognizing belatedly that others already solved them, too.)
@sepaepa try to use the mount point of mounted devices, not devices themselves, i.e. not /dev/sdb2.

@dragonee
Copy link

dragonee commented Nov 9, 2019

@dr0i I've replaced the link in my article to your repo.

@stanfrbd
Copy link

Wow, thank you! This just saved my life ahah.

@vjt
Copy link
Author

vjt commented Mar 13, 2022

@stanfrbd haha I am super glad to hear that! that’s what OSS is for :-) and luckily these internals rarely change over time :D cheers!

@ik1dial
Copy link

ik1dial commented Aug 28, 2023

damn, this script keeps on giving! Life-saving 10 years after its release — thank you @vjt !

@vjt
Copy link
Author

vjt commented Aug 29, 2023

damn, this script keeps on giving! Life-saving 10 years after its release — thank you @vjt !

glad it’s useful! enjoy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment