In iOS 4.0 Apple has redesigned the backup process.
iTunes used to store a list of filenames associated with backup files in the Manifest.plist file, but in iOS 4.
This python script is awesome.
Here's my Ruby version of it (with minor improvement) and search capabilities. (for iOS 5)
# encoding: utf-8
require 'fileutils'
require 'digest/sha1'
class ManifestParser
def initialize(mbdb_filename, verbose = false)
@verbose = verbose
process_mbdb_file(mbdb_filename)
end
# Returns the numbers of records in the Manifest files.
def record_number
@mbdb.size
end
# Returns a huge string containing the parsing of the Manifest files.
def to_s
s = ''
@mbdb.each do |v|
s += "#{fileinfo_str(v)}\n"
end
s
end
def to_file(filename)
File.open(filename, 'w') do |f|
@mbdb.each do |v|
f.puts fileinfo_str(v)
end
end
end
# Copy the backup files to their real path/name.
# * domain_match Can be a regexp to restrict the files to copy.
# * filename_match Can be a regexp to restrict the files to copy.
def rename_files(domain_match = nil, filename_match = nil)
@mbdb.each do |v|
if v[:type] == '-' # Only rename files.
if (domain_match.nil? or v[:domain] =~ domain_match) and (filename_match.nil? or v[:filename] =~ filename_match)
dst = "#{v[:domain]}/#{v[:filename]}"
puts "Creating: #{dst}"
FileUtils.mkdir_p(File.dirname(dst))
FileUtils.cp(v[:fileID], dst)
end
end
end
end
# Return the filename that math the given regexp.
def search(regexp)
result = Array.new
@mbdb.each do |v|
if "#{v[:domain]}::#{v[:filename]}" =~ regexp
result << v
end
end
result
end
private
# Retrieve an integer (big-endian) and new offset from the current offset
def getint(data, offset, intsize)
value = 0
while intsize > 0
value = (value<<8) + data[offset].ord
offset += 1
intsize -= 1
end
return value, offset
end
# Retrieve a string and new offset from the current offset into the data
def getstring(data, offset)
return '', offset + 2 if data[offset] == 0xFF.chr and data[offset + 1] == 0xFF.chr # Blank string
length, offset = getint(data, offset, 2) # 2-byte length
value = data[offset...(offset + length)]
return value, (offset + length)
end
def process_mbdb_file(filename)
@mbdb = Array.new
data = File.open(filename, 'rb') { |f| f.read }
puts "MBDB file read. Size: #{data.size}"
raise 'This does not look like an MBDB file' if data[0...4] != 'mbdb'
offset = 4
offset += 2 # value x05 x00, not sure what this is
while offset < data.size
fileinfo = Hash.new
fileinfo[:start_offset] = offset
fileinfo[:domain], offset = getstring(data, offset)
fileinfo[:filename], offset = getstring(data, offset)
fileinfo[:linktarget], offset = getstring(data, offset)
fileinfo[:datahash], offset = getstring(data, offset)
fileinfo[:unknown1], offset = getstring(data, offset)
fileinfo[:mode], offset = getint(data, offset, 2)
if (fileinfo[:mode] & 0xE000) == 0xA000 # Symlink
fileinfo[:type] = 'l'
elsif (fileinfo[:mode] & 0xE000) == 0x8000 # File
fileinfo[:type] = '-'
elsif (fileinfo[:mode] & 0xE000) == 0x4000 # Dir
fileinfo[:type] = 'd'
else
# $stderr.puts "Unknown file type %04x for #{fileinfo_str(f, false)}" % f['mode']
fileinfo[:type] = '?'
end
fileinfo[:unknown2], offset = getint(data, offset, 4)
fileinfo[:unknown3], offset = getint(data, offset, 4)
fileinfo[:userid], offset = getint(data, offset, 4)
fileinfo[:groupid], offset = getint(data, offset, 4)
fileinfo[:mtime], offset = getint(data, offset, 4)
fileinfo[:atime], offset = getint(data, offset, 4)
fileinfo[:ctime], offset = getint(data, offset, 4)
fileinfo[:filelen], offset = getint(data, offset, 8)
fileinfo[:flag], offset = getint(data, offset, 1)
fileinfo[:numprops], offset = getint(data, offset, 1)
fileinfo[:properties] = Hash.new
(0...(fileinfo[:numprops])).each do |ii|
propname, offset = getstring(data, offset)
propval, offset = getstring(data, offset)
fileinfo[:properties][propname] = propval
end
# Compute the ID of the file.
fullpath = fileinfo[:domain] + '-' + fileinfo[:filename]
fileinfo[:fileID] = Digest::SHA1.hexdigest(fullpath)
# We add the file to the list of files.
@mbdb << fileinfo
end
@mbdb
end
def modestr(val)
def mode(val)
r = (val & 0x4) ? 'r' : '-'
w = (val & 0x2) ? 'w' : '-'
x = (val & 0x1) ? 'x' : '-'
r + w + x
end
mode(val >> 6) + mode(val >> 3) + mode(val)
end
def fileinfo_str(f)
return "(#{f[:fileID]})#{f[:domain]}::#{f[:filename]}" unless @verbose
data = [f[:type], modestr(f[:mode]), f[:userid], f[:groupid], f[:filelen], f[:mtime], f[:atime], f[:ctime], f[:fileID], f[:domain], f[:filename]]
info = "%s%s %08x %08x %7d %10d %10d %10d (%s)%s::%s" % data
info += ' -> ' + f[:linktarget] if f[:type] == 'l' # Symlink destination
f[:properties].each do |k, v|
info += " #{k}=#{v.inspect}"
end
info
end
end
if __FILE__ == $0
mp = ManifestParser.new 'Manifest.mbdb', true
mp.to_file 'filenames.txt'
end