* modified files --- orig/rdiff_backup/Globals.py +++ mod/rdiff_backup/Globals.py @@ -85,6 +85,11 @@ resource_forks_write = 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 local_connection = None --- orig/rdiff_backup/Main.py +++ mod/rdiff_backup/Main.py @@ -383,6 +383,8 @@ update_triple(src_fsa.resource_forks, dest_fsa.resource_forks, ('resource_forks_active', 'resource_forks_write', '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: Log.FatalError("--never-drop-acls specified, but ACL support\n" "disabled on destination filesystem") @@ -487,6 +489,8 @@ update_triple(mirror_fsa.resource_forks, target_fsa.resource_forks, ('resource_forks_active', 'resource_forks_write', '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: Log.FatalError("--never-drop-acls specified, but ACL support\n" "disabled on destination filesystem") --- orig/rdiff_backup/fs_abilities.py +++ mod/rdiff_backup/fs_abilities.py @@ -40,6 +40,7 @@ fsync_dirs = None # True if directories can be fsync'd dir_inc_perms = None # True if regular files can have full permissions 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 read_only = None # True if capabilities were determined non-destructively @@ -92,7 +93,9 @@ add_boolean_list([('Access control lists', self.acls), ('Extended attributes', self.eas), ('Mac OS X style resource forks', - self.resource_forks)]) + self.resource_forks), + ('Mac OS X Finder information', + self.carbonfile)]) s.append(s[0]) return '\n'.join(s) @@ -112,6 +115,7 @@ self.set_eas(rp, 0) self.set_acls(rp) self.set_resource_fork_readonly(rp) + self.set_carbonfile() return self def init_readwrite(self, rbdir, use_ctq_file = 1, @@ -145,6 +149,7 @@ self.set_acls(subdir) self.set_dir_inc_perms(subdir) self.set_resource_fork_readwrite(subdir) + self.set_carbonfile() if override_chars_to_quote is None: self.set_chars_to_quote(subdir) else: self.chars_to_quote = override_chars_to_quote if use_ctq_file: self.compare_chars_to_quote(rbdir) @@ -325,6 +330,24 @@ else: self.dir_inc_perms = 0 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): """Test for resource forks by writing to regular_file/rsrc""" assert dir_rp.conn is Globals.local_connection --- orig/rdiff_backup/metadata.py +++ mod/rdiff_backup/metadata.py @@ -62,6 +62,31 @@ """This is raised when bad or unparsable data is received""" 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): """From RORPath, return text record of file's metadata""" @@ -79,6 +104,14 @@ if not rorpath.get_resource_fork(): rf = "None" else: rf = binascii.hexlify(rorpath.get_resource_fork()) 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 Globals.preserve_hardlinks: @@ -132,6 +165,11 @@ elif field == "ResourceFork": if data == "None": data_dict['resourcefork'] = "" 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 == "Inode": data_dict['inode'] = long(data) elif field == "DeviceLoc": data_dict['devloc'] = long(data) --- orig/rdiff_backup/rpath.py +++ mod/rdiff_backup/rpath.py @@ -155,6 +155,8 @@ if rpin.issym(): return # symlinks have no valid attributes if Globals.resource_forks_write and rpin.isreg(): 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.change_ownership: rpout.chown(*user_group.map_rpath(rpin)) rpout.chmod(rpin.getperms()) @@ -174,6 +176,8 @@ if rpin.issym(): return # symlinks have no valid attributes if Globals.resource_forks_write and rpin.isreg() and rpout.isreg(): 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.change_ownership: apply(rpout.chown, rpin.getuidgid()) if rpin.isdir() and not rpout.isdir(): @@ -294,6 +298,7 @@ elif key == 'size' and not self.isreg(): pass elif key == 'ea' and not Globals.eas_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: pass elif ((key == 'uname' or key == 'gname') and @@ -331,6 +336,7 @@ elif key == 'inode': pass elif key == 'ea' and not Globals.eas_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: pass elif (not other.data.has_key(key) or @@ -587,6 +593,18 @@ """Return extended attributes object""" 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): """True if rpath has a resourcefork parameter""" return self.data.has_key('resourcefork') @@ -1057,6 +1075,44 @@ ea.write_to_rp(self) 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): """Return resource fork data, setting if necessary""" assert self.isreg() @@ -1110,7 +1166,8 @@ if Globals.acls_conn: rpath.data['acl'] = acl_get(rpath) if Globals.resource_forks_conn and rpath.isreg(): 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 # import that module directly because of circular dependency problems.