Commit 0e93ca10 authored by bescoto's avatar bescoto

Checked in John Goerzen's carbonfile patch


git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup@508 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
parent e8a09645
New in v0.13.4 (??????????) New in v0.13.4 (??????????)
--------------------------- ---------------------------
Checked in patch by John Goerzen to support Mac OS X Finder
information. As John says:
> Specifically, it adds storage of:
>
> * 4-byte creator
> * 4-byte type
> * integer flags
> * dual integer location
Much thanks to John for adding this useful feature all by himself!
Regressing and restoring should now take less memory when processing Regressing and restoring should now take less memory when processing
large directories (noticed by Luke Mewburn and others). large directories (noticed by Luke Mewburn and others).
......
...@@ -85,6 +85,11 @@ resource_forks_active = None ...@@ -85,6 +85,11 @@ resource_forks_active = None
resource_forks_write = None resource_forks_write = None
resource_forks_conn = None resource_forks_conn = None
# Like the above, but applies to MacOS Carbon Finder creator/type info.
carbonfile_active = None
carbonfile_write = None
carbonfile_conn = None
# This will be set as soon as the LocalConnection class loads # This will be set as soon as the LocalConnection class loads
local_connection = None local_connection = None
......
...@@ -385,6 +385,8 @@ def backup_set_fs_globals(rpin, rpout): ...@@ -385,6 +385,8 @@ def backup_set_fs_globals(rpin, rpout):
update_triple(src_fsa.resource_forks, dest_fsa.resource_forks, update_triple(src_fsa.resource_forks, dest_fsa.resource_forks,
('resource_forks_active', 'resource_forks_write', ('resource_forks_active', 'resource_forks_write',
'resource_forks_conn')) 'resource_forks_conn'))
update_triple(src_fsa.carbonfile, dest_fsa.carbonfile,
('carbonfile_active', 'carbonfile_write', 'carbonfile_conn'))
if Globals.never_drop_acls and not Globals.acls_active: if Globals.never_drop_acls and not Globals.acls_active:
Log.FatalError("--never-drop-acls specified, but ACL support\n" Log.FatalError("--never-drop-acls specified, but ACL support\n"
"disabled on destination filesystem") "disabled on destination filesystem")
...@@ -489,6 +491,8 @@ def restore_set_fs_globals(target): ...@@ -489,6 +491,8 @@ def restore_set_fs_globals(target):
update_triple(mirror_fsa.resource_forks, target_fsa.resource_forks, update_triple(mirror_fsa.resource_forks, target_fsa.resource_forks,
('resource_forks_active', 'resource_forks_write', ('resource_forks_active', 'resource_forks_write',
'resource_forks_conn')) 'resource_forks_conn'))
update_triple(mirror_fsa.carbonfile, target_fsa.carbonfile,
('carbonfile_active', 'carbonfile_write', 'carbonfile_conn'))
if Globals.never_drop_acls and not Globals.acls_active: if Globals.never_drop_acls and not Globals.acls_active:
Log.FatalError("--never-drop-acls specified, but ACL support\n" Log.FatalError("--never-drop-acls specified, but ACL support\n"
"disabled on destination filesystem") "disabled on destination filesystem")
......
...@@ -40,6 +40,7 @@ class FSAbilities: ...@@ -40,6 +40,7 @@ class FSAbilities:
fsync_dirs = None # True if directories can be fsync'd fsync_dirs = None # True if directories can be fsync'd
dir_inc_perms = None # True if regular files can have full permissions dir_inc_perms = None # True if regular files can have full permissions
resource_forks = None # True if regular_file/rsrc holds resource fork resource_forks = None # True if regular_file/rsrc holds resource fork
carbonfile = None # True if Mac Carbon file data is supported.
name = None # Short string, not used for any technical purpose name = None # Short string, not used for any technical purpose
read_only = None # True if capabilities were determined non-destructively read_only = None # True if capabilities were determined non-destructively
...@@ -92,7 +93,8 @@ class FSAbilities: ...@@ -92,7 +93,8 @@ class FSAbilities:
add_boolean_list([('Access control lists', self.acls), add_boolean_list([('Access control lists', self.acls),
('Extended attributes', self.eas), ('Extended attributes', self.eas),
('Mac OS X style resource forks', ('Mac OS X style resource forks',
self.resource_forks)]) self.resource_forks),
('Mac OS X Finder information', self.carbonfile)])
s.append(s[0]) s.append(s[0])
return '\n'.join(s) return '\n'.join(s)
...@@ -112,6 +114,7 @@ class FSAbilities: ...@@ -112,6 +114,7 @@ class FSAbilities:
self.set_eas(rp, 0) self.set_eas(rp, 0)
self.set_acls(rp) self.set_acls(rp)
self.set_resource_fork_readonly(rp) self.set_resource_fork_readonly(rp)
self.set_carbonfile()
return self return self
def init_readwrite(self, rbdir, use_ctq_file = 1, def init_readwrite(self, rbdir, use_ctq_file = 1,
...@@ -145,6 +148,7 @@ class FSAbilities: ...@@ -145,6 +148,7 @@ class FSAbilities:
self.set_acls(subdir) self.set_acls(subdir)
self.set_dir_inc_perms(subdir) self.set_dir_inc_perms(subdir)
self.set_resource_fork_readwrite(subdir) self.set_resource_fork_readwrite(subdir)
self.set_carbonfile()
if override_chars_to_quote is None: self.set_chars_to_quote(subdir) if override_chars_to_quote is None: self.set_chars_to_quote(subdir)
else: self.chars_to_quote = override_chars_to_quote else: self.chars_to_quote = override_chars_to_quote
if use_ctq_file: self.compare_chars_to_quote(rbdir) if use_ctq_file: self.compare_chars_to_quote(rbdir)
...@@ -326,6 +330,23 @@ rdiff-backup-data/chars_to_quote. ...@@ -326,6 +330,23 @@ rdiff-backup-data/chars_to_quote.
else: self.dir_inc_perms = 0 else: self.dir_inc_perms = 0
test_rp.delete() test_rp.delete()
def set_carbonfile(self):
"""Test for support of the Mac Carbon library. This library
can be used to obtain Finder info (creator/type)."""
try:
import Carbon.File
import MacOS
except:
self.carbonfile = 0
return
try: x = Carbon.File.FSSpec('.')
except:
self.carbonfile = 0
return
self.carbonfile = 1
def set_resource_fork_readwrite(self, dir_rp): def set_resource_fork_readwrite(self, dir_rp):
"""Test for resource forks by writing to regular_file/rsrc""" """Test for resource forks by writing to regular_file/rsrc"""
assert dir_rp.conn is Globals.local_connection assert dir_rp.conn is Globals.local_connection
......
...@@ -62,6 +62,31 @@ class ParsingError(Exception): ...@@ -62,6 +62,31 @@ class ParsingError(Exception):
"""This is raised when bad or unparsable data is received""" """This is raised when bad or unparsable data is received"""
pass pass
def carbonfile2string(cfile):
"""Convert CarbonFile data to a string suitable for storing."""
retvalparts = []
retvalparts.append('creator:%s' % binascii.hexlify(cfile['creator']))
retvalparts.append('type:%s' % binascii.hexlify(cfile['type']))
retvalparts.append('location:%d,%d' % cfile['location'])
retvalparts.append('flags:%d' % cfile['flags'])
return '|'.join(retvalparts)
def string2carbonfile(data):
"""Re-constitute CarbonFile data from a string stored by
carbonfile2string."""
retval = {}
for component in data.split('|'):
key, value = component.split(':')
if key == 'creator':
retval['creator'] = binascii.unhexlify(value)
elif key == 'type':
retval['type'] = binascii.unhexlify(value)
elif key == 'location':
a, b = value.split(',')
retval['location'] = (int(a), int(b))
elif key == 'flags':
retval['flags'] = int(value)
return retval
def RORP2Record(rorpath): def RORP2Record(rorpath):
"""From RORPath, return text record of file's metadata""" """From RORPath, return text record of file's metadata"""
...@@ -79,6 +104,12 @@ def RORP2Record(rorpath): ...@@ -79,6 +104,12 @@ def RORP2Record(rorpath):
if not rorpath.get_resource_fork(): rf = "None" if not rorpath.get_resource_fork(): rf = "None"
else: rf = binascii.hexlify(rorpath.get_resource_fork()) else: rf = binascii.hexlify(rorpath.get_resource_fork())
str_list.append(" ResourceFork %s\n" % (rf,)) str_list.append(" ResourceFork %s\n" % (rf,))
# If there is Carbon data, save it.
if rorpath.has_carbonfile():
if not rorpath.get_carbonfile(): cfile = "None"
else: cfile = carbonfile2string(rorpath.get_carbonfile())
str_list.append(" CarbonFile %s\n" % (cfile,))
# If file is hardlinked, add that information # If file is hardlinked, add that information
if Globals.preserve_hardlinks: if Globals.preserve_hardlinks:
...@@ -132,6 +163,9 @@ def Record2RORP(record_string): ...@@ -132,6 +163,9 @@ def Record2RORP(record_string):
elif field == "ResourceFork": elif field == "ResourceFork":
if data == "None": data_dict['resourcefork'] = "" if data == "None": data_dict['resourcefork'] = ""
else: data_dict['resourcefork'] = binascii.unhexlify(data) else: data_dict['resourcefork'] = binascii.unhexlify(data)
elif field == "CarbonFile":
if data == "None": data_dict['carbonfile'] = None
else: data_dict['carbonfile'] = string2carbonfile(data)
elif field == "NumHardLinks": data_dict['nlink'] = int(data) elif field == "NumHardLinks": data_dict['nlink'] = int(data)
elif field == "Inode": data_dict['inode'] = long(data) elif field == "Inode": data_dict['inode'] = long(data)
elif field == "DeviceLoc": data_dict['devloc'] = long(data) elif field == "DeviceLoc": data_dict['devloc'] = long(data)
......
...@@ -155,6 +155,8 @@ def copy_attribs(rpin, rpout): ...@@ -155,6 +155,8 @@ def copy_attribs(rpin, rpout):
if rpin.issym(): return # symlinks have no valid attributes if rpin.issym(): return # symlinks have no valid attributes
if Globals.resource_forks_write and rpin.isreg(): if Globals.resource_forks_write and rpin.isreg():
rpout.write_resource_fork(rpin.get_resource_fork()) rpout.write_resource_fork(rpin.get_resource_fork())
if Globals.carbonfile_write and rpin.isreg():
rpout.write_carbonfile(rpin.get_carbonfile())
if Globals.eas_write: rpout.write_ea(rpin.get_ea()) if Globals.eas_write: rpout.write_ea(rpin.get_ea())
if Globals.change_ownership: rpout.chown(*user_group.map_rpath(rpin)) if Globals.change_ownership: rpout.chown(*user_group.map_rpath(rpin))
rpout.chmod(rpin.getperms()) rpout.chmod(rpin.getperms())
...@@ -174,6 +176,8 @@ def copy_attribs_inc(rpin, rpout): ...@@ -174,6 +176,8 @@ def copy_attribs_inc(rpin, rpout):
if rpin.issym(): return # symlinks have no valid attributes if rpin.issym(): return # symlinks have no valid attributes
if Globals.resource_forks_write and rpin.isreg() and rpout.isreg(): if Globals.resource_forks_write and rpin.isreg() and rpout.isreg():
rpout.write_resource_fork(rpin.get_resource_fork()) rpout.write_resource_fork(rpin.get_resource_fork())
if Globals.carbonfile_write and rpin.isreg() and rpout.isreg():
rpout.write_carbonfile(rpin.get_carbonfile())
if Globals.eas_write: rpout.write_ea(rpin.get_ea()) if Globals.eas_write: rpout.write_ea(rpin.get_ea())
if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid()) if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid())
if rpin.isdir() and not rpout.isdir(): if rpin.isdir() and not rpout.isdir():
...@@ -294,6 +298,7 @@ class RORPath: ...@@ -294,6 +298,7 @@ class RORPath:
elif key == 'size' and not self.isreg(): pass elif key == 'size' and not self.isreg(): pass
elif key == 'ea' and not Globals.eas_active: pass elif key == 'ea' and not Globals.eas_active: pass
elif key == 'acl' and not Globals.acls_active: pass elif key == 'acl' and not Globals.acls_active: pass
elif key == 'carbonfile' and not Globals.carbonfile_active: pass
elif key == 'resourcefork' and not Globals.resource_forks_active: elif key == 'resourcefork' and not Globals.resource_forks_active:
pass pass
elif key == 'uname' or key == 'gname': elif key == 'uname' or key == 'gname':
...@@ -333,6 +338,7 @@ class RORPath: ...@@ -333,6 +338,7 @@ class RORPath:
elif key == 'inode': pass elif key == 'inode': pass
elif key == 'ea' and not Globals.eas_write: pass elif key == 'ea' and not Globals.eas_write: pass
elif key == 'acl' and not Globals.acls_write: pass elif key == 'acl' and not Globals.acls_write: pass
elif key == 'carbonfile' and not Globals.carbonfile_write: pass
elif key == 'resourcefork' and not Globals.resource_forks_write: elif key == 'resourcefork' and not Globals.resource_forks_write:
pass pass
elif (not other.data.has_key(key) or elif (not other.data.has_key(key) or
...@@ -589,6 +595,18 @@ class RORPath: ...@@ -589,6 +595,18 @@ class RORPath:
"""Return extended attributes object""" """Return extended attributes object"""
return self.data['ea'] return self.data['ea']
def has_carbonfile(self):
"""True if rpath has a carbonfile parameter"""
return self.data.has_key('carbonfile')
def get_carbonfile(self):
"""Returns the carbonfile data"""
return self.data['carbonfile']
def set_carbonfile(self, cfile):
"""Record carbonfile data in dictionary. Does not write."""
self.data['carbonfile'] = cfile
def has_resource_fork(self): def has_resource_fork(self):
"""True if rpath has a resourcefork parameter""" """True if rpath has a resourcefork parameter"""
return self.data.has_key('resourcefork') return self.data.has_key('resourcefork')
...@@ -1059,6 +1077,41 @@ class RPath(RORPath): ...@@ -1059,6 +1077,41 @@ class RPath(RORPath):
ea.write_to_rp(self) ea.write_to_rp(self)
self.data['ea'] = ea self.data['ea'] = ea
def get_carbonfile(self):
"""Return resource fork data, loading from filesystem if
necessary."""
from Carbon.File import FSSpec
import MacOS
try: return self.data['cfile']
except KeyError: pass
try:
fsobj = FSSpec(self.path)
finderinfo = fsobj.FSpGetFInfo()
cfile = {'creator': finderinfo.Creator,
'type': finderinfo.Type,
'location': finderinfo.Location,
'flags': finderinfo.Flags}
self.data['carbonfile'] = cfile
return cfile
except MacOS.Error:
self.data['carbonfile'] = None
return self.data['carbonfile']
def write_carbonfile(self, cfile):
"""Write new carbon data to self."""
log.Log("Writing carbon data to %s" % (self.index,), 7)
from Carbon.File import FSSpec
import MacOS
fsobj = FSSpec(self.path)
finderinfo = fsobj.FSpGetFInfo()
finderinfo.Creator = cfile['creator']
finderinfo.Type = cfile['type']
finderinfo.Location = cfile['location']
finderinfo.Flags = cfile['flags']
fsobj.FSpSetFInfo(finderinfo)
self.set_carbonfile(cfile)
def get_resource_fork(self): def get_resource_fork(self):
"""Return resource fork data, setting if necessary""" """Return resource fork data, setting if necessary"""
assert self.isreg() assert self.isreg()
...@@ -1112,7 +1165,7 @@ def setdata_local(rpath): ...@@ -1112,7 +1165,7 @@ def setdata_local(rpath):
if Globals.acls_conn: rpath.data['acl'] = acl_get(rpath) if Globals.acls_conn: rpath.data['acl'] = acl_get(rpath)
if Globals.resource_forks_conn and rpath.isreg(): if Globals.resource_forks_conn and rpath.isreg():
rpath.get_resource_fork() rpath.get_resource_fork()
if Globals.carbonfile_conn and rpath.isreg(): rpath.get_carbonfile()
# These two are overwritten by the eas_acls.py module. We can't # These two are overwritten by the eas_acls.py module. We can't
# import that module directly because of circular dependency problems. # import that module directly because of circular dependency problems.
......
...@@ -19,8 +19,7 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -19,8 +19,7 @@ class FSAbilitiesTest(unittest.TestCase):
hardlinks = fsync_dirs = 1 hardlinks = fsync_dirs = 1
dir_inc_perms = 1 dir_inc_perms = 1
resource_forks = 0 resource_forks = 0
carbonfile = 0
# Describes MS-Windows style file system # Describes MS-Windows style file system
#dir_to_test = "/mnt/fat" #dir_to_test = "/mnt/fat"
...@@ -30,6 +29,7 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -30,6 +29,7 @@ class FSAbilitiesTest(unittest.TestCase):
#fsync_dirs = 1 #fsync_dirs = 1
#dir_inc_perms = 0 #dir_inc_perms = 0
#resource_forks = 0 #resource_forks = 0
#carbonfile = 0
def testReadOnly(self): def testReadOnly(self):
"""Test basic querying read only""" """Test basic querying read only"""
...@@ -40,6 +40,7 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -40,6 +40,7 @@ class FSAbilitiesTest(unittest.TestCase):
assert fsa.eas == self.eas, fsa.eas assert fsa.eas == self.eas, fsa.eas
assert fsa.acls == self.acls, fsa.acls assert fsa.acls == self.acls, fsa.acls
assert fsa.resource_forks == self.resource_forks, fsa.resource_forks assert fsa.resource_forks == self.resource_forks, fsa.resource_forks
assert fsa.carbonfile == self.carbonfile, fsa.carbonfile
def testReadWrite(self): def testReadWrite(self):
"""Test basic querying read/write""" """Test basic querying read/write"""
...@@ -61,7 +62,8 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -61,7 +62,8 @@ class FSAbilitiesTest(unittest.TestCase):
assert fsa.fsync_dirs == self.fsync_dirs, fsa.fsync_dirs assert fsa.fsync_dirs == self.fsync_dirs, fsa.fsync_dirs
assert fsa.dir_inc_perms == self.dir_inc_perms, fsa.dir_inc_perms assert fsa.dir_inc_perms == self.dir_inc_perms, fsa.dir_inc_perms
assert fsa.resource_forks == self.resource_forks, fsa.resource_forks assert fsa.resource_forks == self.resource_forks, fsa.resource_forks
assert fsa.carbonfile == self.carbonfile, fsa.carbonfile
ctq_rp = new_dir.append("chars_to_quote") ctq_rp = new_dir.append("chars_to_quote")
assert ctq_rp.lstat() assert ctq_rp.lstat()
fp = ctq_rp.open('rb') fp = ctq_rp.open('rb')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment