Commit e2ae37f6 authored by bescoto's avatar bescoto

Added Daniel Hazelbaker's resource fork code, plus detection to fs_abilities...

Added Daniel Hazelbaker's resource fork code, plus detection to fs_abilities and a new unittest file.


git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup@345 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
parent 039fd3fd
New in v0.13.0 (2003/07/02) New in v0.13.0 (2003/07/22)
--------------------------- ---------------------------
To prevent the buildup of confusing and error-prone options, the To prevent the buildup of confusing and error-prone options, the
...@@ -17,6 +17,9 @@ Support for access control lists (ACLs) was also added. An ACL ...@@ -17,6 +17,9 @@ Support for access control lists (ACLs) was also added. An ACL
capable file system and the python package pylibacl (which exports the capable file system and the python package pylibacl (which exports the
posix1e module) are required. posix1e module) are required.
Thanks to patches by Daniel Hazelbaker, rdiff-backup now reads and
writes Mac OS X style resource forks!
Added --list-increment-sizes switch, which tells you how much space Added --list-increment-sizes switch, which tells you how much space
the various backup files take up. (Suggested by Andrew Bressen) the various backup files take up. (Suggested by Andrew Bressen)
......
...@@ -71,16 +71,24 @@ read_eas = None ...@@ -71,16 +71,24 @@ read_eas = None
# If true, preserve the extended attributes on the mirror directory # If true, preserve the extended attributes on the mirror directory
# when backing up, or write them to the restore directory. This # when backing up, or write them to the restore directory. This
# implies read_eas. # requires read_eas.
write_eas = None write_eas = None
# If true, save access control lists when backup up. # If true, save access control lists when backup up.
read_acls = None read_acls = None
# If true, write access control list information to the destination # If true, write access control list information to the destination
# when backing up or restoring. Implies read_acls. # when backing up or restoring. Requires read_acls.
write_acls = None write_acls = None
# If true, look for and save resource fork information when backing
# up.
read_resource_forks = None
# If true, write resource fork information to destination when backing
# up or restoring. Requires read_resource_forks.
write_resource_forks = 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
......
...@@ -323,29 +323,28 @@ def backup_get_mirrortime(): ...@@ -323,29 +323,28 @@ def backup_get_mirrortime():
def backup_set_fs_globals(rpin, rpout): def backup_set_fs_globals(rpin, rpout):
"""Use fs_abilities to set the globals that depend on filesystem""" """Use fs_abilities to set the globals that depend on filesystem"""
def update_bool_global(attr, bool):
"""If bool is not None, update Globals.attr accordingly"""
if Globals.get(attr) is not None:
SetConnections.UpdateGlobal(attr, bool)
src_fsa = fs_abilities.FSAbilities('source').init_readonly(rpin) src_fsa = fs_abilities.FSAbilities('source').init_readonly(rpin)
Log(str(src_fsa), 3) Log(str(src_fsa), 3)
if Globals.read_acls is None: update_bool_global('read_acls', src_fsa.acls)
SetConnections.UpdateGlobal('read_acls', src_fsa.acls) update_bool_global('read_eas', src_fsa.eas)
if src_fsa.eas: rpin.get_ea() update_bool_global('read_resource_forks', src_fsa.resource_forks)
if Globals.read_eas is None:
SetConnections.UpdateGlobal('read_eas', src_fsa.eas)
dest_fsa = fs_abilities.FSAbilities('destination').init_readwrite( dest_fsa = fs_abilities.FSAbilities('destination').init_readwrite(
Globals.rbdir, override_chars_to_quote = Globals.chars_to_quote) Globals.rbdir, override_chars_to_quote = Globals.chars_to_quote)
Log(str(dest_fsa), 3) Log(str(dest_fsa), 3)
SetConnections.UpdateGlobal('preserve_hardlinks', dest_fsa.hardlinks) SetConnections.UpdateGlobal('preserve_hardlinks', dest_fsa.hardlinks)
SetConnections.UpdateGlobal('fsync_directories', dest_fsa.fsync_dirs) SetConnections.UpdateGlobal('fsync_directories', dest_fsa.fsync_dirs)
if Globals.write_acls is None:
SetConnections.UpdateGlobal('write_acls',
Globals.read_acls and dest_fsa.acls)
if Globals.write_eas is None:
SetConnections.UpdateGlobal('write_eas',
Globals.read_eas and dest_fsa.eas)
SetConnections.UpdateGlobal('change_ownership', dest_fsa.ownership) SetConnections.UpdateGlobal('change_ownership', dest_fsa.ownership)
if Globals.change_dir_inc_perms is None: update_bool_global('write_acls', Globals.read_acls and dest_fsa.acls)
SetConnections.UpdateGlobal('change_dir_inc_perms', update_bool_global('write_eas', Globals.read_eas and dest_fsa.eas)
dest_fsa.dir_inc_perms) update_bool_global('write_resource_forks',
Globals.read_resource_forks and dest_fsa.resource_forks)
update_bool_global('change_dir_inc_perms', dest_fsa.dir_inc_perms)
SetConnections.UpdateGlobal('chars_to_quote', dest_fsa.chars_to_quote) SetConnections.UpdateGlobal('chars_to_quote', dest_fsa.chars_to_quote)
if Globals.chars_to_quote: if Globals.chars_to_quote:
for conn in Globals.connections: for conn in Globals.connections:
...@@ -417,20 +416,22 @@ def restore_init_quoting(src_rp): ...@@ -417,20 +416,22 @@ def restore_init_quoting(src_rp):
def restore_set_fs_globals(target): def restore_set_fs_globals(target):
"""Use fs_abilities to set the globals that depend on filesystem""" """Use fs_abilities to set the globals that depend on filesystem"""
target_fsa = fs_abilities.FSAbilities().init_readwrite(target, 0) def update_bool_global(attr, bool):
if Globals.read_acls is None: """If bool is not None, update Globals.attr accordingly"""
SetConnections.UpdateGlobal('read_acls', target_fsa.acls) if Globals.get(attr) is not None:
if Globals.write_acls is None: SetConnections.UpdateGlobal(attr, bool)
SetConnections.UpdateGlobal('write_acls', target_fsa.acls)
if Globals.read_eas is None: target_fsa = fs_abilities.FSAbilities('destination').init_readwrite(
SetConnections.UpdateGlobal('read_eas', target_fsa.eas) target, 0)
if Globals.write_eas is None: update_bool_global('read_acls', target_fsa.acls)
SetConnections.UpdateGlobal('write_eas', target_fsa.eas) update_bool_global('write_acls', target_fsa.acls)
if Globals.read_eas: target.get_ea() update_bool_global('read_eas', target_fsa.eas)
update_bool_global('write_eas', target_fsa.eas)
SetConnections.UpdateGlobal('preserve_hardlinks', target_fsa.hardlinks) SetConnections.UpdateGlobal('preserve_hardlinks', target_fsa.hardlinks)
SetConnections.UpdateGlobal('change_ownership', target_fsa.ownership) SetConnections.UpdateGlobal('change_ownership', target_fsa.ownership)
mirror_fsa = fs_abilities.FSAbilities().init_readwrite(Globals.rbdir) mirror_fsa = fs_abilities.FSAbilities('source').init_readwrite(
Globals.rbdir)
if Globals.chars_to_quote is None: # otherwise already overridden if Globals.chars_to_quote is None: # otherwise already overridden
if mirror_fsa.chars_to_quote: if mirror_fsa.chars_to_quote:
SetConnections.UpdateGlobal('chars_to_quote', SetConnections.UpdateGlobal('chars_to_quote',
......
...@@ -28,7 +28,7 @@ FSAbilities object describing it. ...@@ -28,7 +28,7 @@ FSAbilities object describing it.
""" """
import errno import errno
import Globals, log, TempFile import Globals, log, TempFile, selection
class FSAbilities: class FSAbilities:
"""Store capabilities of given file system""" """Store capabilities of given file system"""
...@@ -38,9 +38,10 @@ class FSAbilities: ...@@ -38,9 +38,10 @@ class FSAbilities:
eas = None # True if extended attributes supported eas = None # True if extended attributes supported
hardlinks = None # True if hard linking supported hardlinks = None # True if hard linking supported
fsync_dirs = None # True if directories can be fsync'd fsync_dirs = None # True if directories can be fsync'd
read_only = None # True if capabilities were determined non-destructively
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
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
def __init__(self, name = None): def __init__(self, name = None):
"""FSAbilities initializer. name is only used in logging""" """FSAbilities initializer. name is only used in logging"""
...@@ -48,31 +49,50 @@ class FSAbilities: ...@@ -48,31 +49,50 @@ class FSAbilities:
def __str__(self): def __str__(self):
"""Return pretty printable version of self""" """Return pretty printable version of self"""
s = ['-' * 60] assert self.read_only == 0 or self.read_only == 1, self.read_only
s = ['-' * 65]
def addline(desc, val_text): def addline(desc, val_text):
"""Add description line to s""" """Add description line to s"""
s.append(' %s%s%s' % (desc, ' ' * (45-len(desc)), val_text)) s.append(' %s%s%s' % (desc, ' ' * (45-len(desc)), val_text))
if self.name: def add_boolean_list(pair_list):
s.append('Detected abilities for %s file system:' % (self.name,)) """Add lines from list of (desc, boolean) pairs"""
else: s.append('Detected abilities for file system') for desc, boolean in pair_list:
if boolean: val_text = 'On'
ctq_str = (self.chars_to_quote is None and 'N/A' elif boolean is None: val_text = 'N/A'
or repr(self.chars_to_quote)) else:
addline('Characters needing quoting', ctq_str) assert boolean == 0
val_text = 'Off'
for desc, val in [('Ownership changing', self.ownership), addline(desc, val_text)
('Access control lists', self.acls),
def get_title_line():
"""Add the first line, mostly for decoration"""
read_string = self.read_only and "read only" or "read/write"
if self.name:
return ('Detected abilities for %s (%s) file system:' %
(self.name, read_string))
else: return ('Detected abilities for %s file system' %
(read_string,))
def add_ctq_line():
"""Get line describing chars to quote"""
ctq_str = (self.chars_to_quote is None and 'N/A'
or repr(self.chars_to_quote))
addline('Characters needing quoting', ctq_str)
s.append(get_title_line())
if not self.read_only:
add_ctq_line()
add_boolean_list([('Ownership changing', self.ownership),
('Hard linking', self.hardlinks),
('fsync() directories', self.fsync_dirs),
('Directory inc permissions',
self.dir_inc_perms)])
add_boolean_list([('Access control lists', self.acls),
('Extended attributes', self.eas), ('Extended attributes', self.eas),
('Hard linking', self.hardlinks), ('Mac OS X style resource forks',
('fsync() directories', self.fsync_dirs), self.resource_forks)])
('Directory inc permissions', self.dir_inc_perms)]:
if val: val_text = 'On'
elif val is None: val_text = 'N/A'
else:
assert val == 0
val_text = 'Off'
addline(desc, val_text)
s.append(s[0]) s.append(s[0])
return '\n'.join(s) return '\n'.join(s)
...@@ -90,6 +110,7 @@ class FSAbilities: ...@@ -90,6 +110,7 @@ class FSAbilities:
self.read_only = 1 self.read_only = 1
self.set_eas(rp, 0) self.set_eas(rp, 0)
self.set_acls(rp) self.set_acls(rp)
self.set_resource_fork_readonly(rp)
return self return self
def init_readwrite(self, rbdir, use_ctq_file = 1, def init_readwrite(self, rbdir, use_ctq_file = 1,
...@@ -121,6 +142,7 @@ class FSAbilities: ...@@ -121,6 +142,7 @@ class FSAbilities:
self.set_eas(subdir, 1) self.set_eas(subdir, 1)
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)
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)
...@@ -258,6 +280,49 @@ rdiff-backup-data/chars_to_quote. ...@@ -258,6 +280,49 @@ 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_resource_fork_readwrite(self, dir_rp):
"""Test for resource forks by writing to regular_file/rsrc"""
reg_rp = dir_rp.append('regfile')
reg_rp.touch()
rfork = reg_rp.append('rsrc')
assert not rfork.lstat()
s = 'test string---this should end up in resource fork'
try:
fp_write = rfork.open('wb')
fp_write.write(s)
assert not fp_write.close()
fp_read = rfork.open('rb')
s_back = fp_read.read()
assert not fp.read.close()
except (OSError, IOError), e: self.resource_forks = 0
else: self.resource_forks = (s_back == s)
reg_rp.delete()
def set_resource_fork_readonly(self, dir_rp):
"""Test for resource fork support by testing an regular file
Launches search for regular file in given directory. If no
regular file is found, resource_fork support will be turned
off by default.
"""
for rp in selection.Select(dir_rp).set_iter():
if rp.isreg():
try:
rfork = rp.append('rsrc')
fp = rfork.open('rb')
fp.read()
assert not fp.close()
except (OSError, IOError), e:
self.resource_forks = 0
return
self.resource_forks = 1
return
self.resource_forks = 0
def test_eas_local(rp, write): def test_eas_local(rp, write):
"""Test ea support. Must be called locally. Usedy by set_eas above.""" """Test ea support. Must be called locally. Usedy by set_eas above."""
assert Globals.local_connection is rp.conn assert Globals.local_connection is rp.conn
......
...@@ -55,7 +55,7 @@ field names and values. ...@@ -55,7 +55,7 @@ field names and values.
""" """
from __future__ import generators from __future__ import generators
import re, gzip, os import re, gzip, os, binascii
import log, Globals, rpath, Time, robust, increment, static import log, Globals, rpath, Time, robust, increment, static
class ParsingError(Exception): class ParsingError(Exception):
...@@ -74,6 +74,12 @@ def RORP2Record(rorpath): ...@@ -74,6 +74,12 @@ def RORP2Record(rorpath):
if type == "reg": if type == "reg":
str_list.append(" Size %s\n" % rorpath.getsize()) str_list.append(" Size %s\n" % rorpath.getsize())
# If there is a resource fork, save it.
if rorpath.has_resource_fork():
if not rorpath.get_resource_fork(): rf = "None"
else: rf = binascii.hexlify(rorpath.get_resource_fork())
str_list.append(" ResourceFork %s\n" % (rf,))
# If file is hardlinked, add that information # If file is hardlinked, add that information
if Globals.preserve_hardlinks: if Globals.preserve_hardlinks:
numlinks = rorpath.getnumlinks() numlinks = rorpath.getnumlinks()
...@@ -81,6 +87,7 @@ def RORP2Record(rorpath): ...@@ -81,6 +87,7 @@ def RORP2Record(rorpath):
str_list.append(" NumHardLinks %s\n" % numlinks) str_list.append(" NumHardLinks %s\n" % numlinks)
str_list.append(" Inode %s\n" % rorpath.getinode()) str_list.append(" Inode %s\n" % rorpath.getinode())
str_list.append(" DeviceLoc %s\n" % rorpath.getdevloc()) str_list.append(" DeviceLoc %s\n" % rorpath.getdevloc())
elif type == "None": return "".join(str_list) elif type == "None": return "".join(str_list)
elif type == "dir" or type == "sock" or type == "fifo": pass elif type == "dir" or type == "sock" or type == "fifo": pass
elif type == "sym": elif type == "sym":
...@@ -122,6 +129,9 @@ def Record2RORP(record_string): ...@@ -122,6 +129,9 @@ def Record2RORP(record_string):
if data == "None": data_dict['type'] = None if data == "None": data_dict['type'] = None
else: data_dict['type'] = data else: data_dict['type'] = data
elif field == "Size": data_dict['size'] = long(data) elif field == "Size": data_dict['size'] = long(data)
elif field == "ResourceFork":
if data == "None": data_dict['resourcefork'] = ""
else: data_dict['resourcefork'] = binascii.unhexlify(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)
......
...@@ -157,6 +157,8 @@ def copy_attribs(rpin, rpout, acls = 1): ...@@ -157,6 +157,8 @@ def copy_attribs(rpin, rpout, acls = 1):
if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid()) if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid())
if Globals.change_permissions: rpout.chmod(rpin.getperms()) if Globals.change_permissions: rpout.chmod(rpin.getperms())
if Globals.write_acls and acls: rpout.write_acl(rpin.get_acl()) if Globals.write_acls and acls: rpout.write_acl(rpin.get_acl())
if Globals.write_resource_forks and rpin.isreg() and rpout.isreg():
rpout.write_resource_fork(rpin.get_resource_fork())
if not rpin.isdev(): rpout.setmtime(rpin.getmtime()) if not rpin.isdev(): rpout.setmtime(rpin.getmtime())
def cmp_attribs(rp1, rp2): def cmp_attribs(rp1, rp2):
...@@ -271,6 +273,8 @@ class RORPath: ...@@ -271,6 +273,8 @@ class RORPath:
elif key == 'size' and not self.isreg(): pass elif key == 'size' and not self.isreg(): pass
elif key == 'ea' and not Globals.read_eas: pass elif key == 'ea' and not Globals.read_eas: pass
elif key == 'acl' and not Globals.read_acls: pass elif key == 'acl' and not Globals.read_acls: pass
elif key == 'resourcefork' and not Globals.read_resource_forks:
pass
elif (key == 'inode' and elif (key == 'inode' and
(not self.isreg() or self.getnumlinks() == 1 or (not self.isreg() or self.getnumlinks() == 1 or
not Globals.compare_inode or not Globals.compare_inode or
...@@ -303,10 +307,10 @@ class RORPath: ...@@ -303,10 +307,10 @@ class RORPath:
elif key == 'size' and not self.isreg(): pass elif key == 'size' and not self.isreg(): pass
elif key == 'perms' and not Globals.change_permissions: pass elif key == 'perms' and not Globals.change_permissions: pass
elif key == 'inode': pass elif key == 'inode': pass
elif (key == 'ea' and elif key == 'ea' and not Globals.write_eas: pass
not (Globals.read_eas and Globals.write_eas)): pass elif key == 'acl' and not Globals.write_acls: pass
elif (key == 'acl' and elif key == 'resourcefork' and not Globals.write_resource_forks:
not (Globals.read_acls and Globals.write_acls)): pass pass
elif (not other.data.has_key(key) or elif (not other.data.has_key(key) or
self.data[key] != other.data[key]): return 0 self.data[key] != other.data[key]): return 0
return 1 return 1
...@@ -547,6 +551,18 @@ class RORPath: ...@@ -547,6 +551,18 @@ class RORPath:
"""Return extended attributes object""" """Return extended attributes object"""
return self.data['ea'] return self.data['ea']
def has_resource_fork(self):
"""True if rpath has a resourcefork parameter"""
return self.data.has_key('resourcefork')
def get_resource_fork(self):
"""Return the resource fork in binary data"""
return self.data['resourcefork']
def set_resource_fork(self, rfork):
"""Record resource fork in dictionary. Does not write"""
self.data['resourcefork'] = rfork
class RPath(RORPath): class RPath(RORPath):
"""Remote Path class - wrapper around a possibly non-local pathname """Remote Path class - wrapper around a possibly non-local pathname
...@@ -608,6 +624,8 @@ class RPath(RORPath): ...@@ -608,6 +624,8 @@ class RPath(RORPath):
self.data = self.conn.C.make_file_dict(self.path) self.data = self.conn.C.make_file_dict(self.path)
if Globals.read_eas and self.lstat(): self.get_ea() if Globals.read_eas and self.lstat(): self.get_ea()
if Globals.read_acls and self.lstat(): self.get_acl() if Globals.read_acls and self.lstat(): self.get_acl()
if Globals.read_resource_forks and self.isreg():
self.get_resource_fork()
def make_file_dict_old(self): def make_file_dict_old(self):
"""Create the data dictionary""" """Create the data dictionary"""
...@@ -998,6 +1016,21 @@ class RPath(RORPath): ...@@ -998,6 +1016,21 @@ class RPath(RORPath):
ea.write_to_rp(self) ea.write_to_rp(self)
self.data['ea'] = ea self.data['ea'] = ea
def get_resource_fork(self):
"""Return resource fork data, setting if necessary"""
assert self.isreg()
try: rfork = self.data['resourcefork']
except KeyError:
rfork = self.append('rsrc').get_data()
self.data['resourcefork'] = rfork
return rfork
def write_resource_fork(self, rfork_data):
"""Write new resource fork to self"""
fp = self.append('rsrc').open('wb')
fp.write(rfork_data)
assert not fp.close()
class RPathFileHook: class RPathFileHook:
"""Look like a file, but add closing hook""" """Look like a file, but add closing hook"""
......
...@@ -144,7 +144,7 @@ class Select: ...@@ -144,7 +144,7 @@ class Select:
delayed_rp_stack.append(rpath) delayed_rp_stack.append(rpath)
diryield_stack.append(diryield(rpath)) diryield_stack.append(diryield(rpath))
def Iterate(self, rpath, rec_func, sel_func): def Iterate(self, rp, rec_func, sel_func):
"""Return iterator yielding rpaths in rpath """Return iterator yielding rpaths in rpath
rec_func is usually the same as this function and is what rec_func is usually the same as this function and is what
...@@ -155,21 +155,21 @@ class Select: ...@@ -155,21 +155,21 @@ class Select:
is usually self.Select. is usually self.Select.
""" """
s = sel_func(rpath) s = sel_func(rp)
if s == 0: return if s == 0: return
elif s == 1: # File is included elif s == 1: # File is included
yield rpath yield rp
if rpath.isdir(): if rp.isdir():
for rp in self.iterate_in_dir(rpath, rec_func, sel_func): for rp2 in self.iterate_in_dir(rp, rec_func, sel_func):
yield rp yield rp2
elif s == 2: elif s == 2:
if rpath.isdir(): # Directory is merely scanned if rp.isdir(): # Directory is merely scanned
iid = self.iterate_in_dir(rpath, rec_func, sel_func) iid = self.iterate_in_dir(rp, rec_func, sel_func)
try: first = iid.next() try: first = iid.next()
except StopIteration: return # no files inside; skip rp except StopIteration: return # no files inside; skip rp
yield rpath yield rp
yield first yield first
for rp in iid: yield rp for rp2 in iid: yield rp2
else: assert 0, "Invalid selection result %s" % (str(s),) else: assert 0, "Invalid selection result %s" % (str(s),)
def listdir(self, dir_rp): def listdir(self, dir_rp):
......
...@@ -18,6 +18,7 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -18,6 +18,7 @@ class FSAbilitiesTest(unittest.TestCase):
ownership = (os.getuid() == 0) ownership = (os.getuid() == 0)
hardlinks = fsync_dirs = 1 hardlinks = fsync_dirs = 1
dir_inc_perms = 1 dir_inc_perms = 1
resource_forks = 0
# Describes MS-Windows style file system # Describes MS-Windows style file system
#dir_to_test = "/mnt/fat" #dir_to_test = "/mnt/fat"
...@@ -25,7 +26,8 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -25,7 +26,8 @@ class FSAbilitiesTest(unittest.TestCase):
#chars_to_quote = "^a-z0-9_ -" #chars_to_quote = "^a-z0-9_ -"
#ownership = hardlinks = 0 #ownership = hardlinks = 0
#fsync_dirs = 1 #fsync_dirs = 1
#dir_inc_perms = XXX #dir_inc_perms = 0
#resource_forks = 0
def testReadOnly(self): def testReadOnly(self):
"""Test basic querying read only""" """Test basic querying read only"""
...@@ -35,6 +37,7 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -35,6 +37,7 @@ class FSAbilitiesTest(unittest.TestCase):
assert fsa.read_only == 1, fsa.read_only assert fsa.read_only == 1, fsa.read_only
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
def testReadWrite(self): def testReadWrite(self):
"""Test basic querying read/write""" """Test basic querying read/write"""
...@@ -55,6 +58,7 @@ class FSAbilitiesTest(unittest.TestCase): ...@@ -55,6 +58,7 @@ class FSAbilitiesTest(unittest.TestCase):
assert fsa.hardlinks == self.hardlinks, fsa.hardlinks assert fsa.hardlinks == self.hardlinks, fsa.hardlinks
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
ctq_rp = new_dir.append("chars_to_quote") ctq_rp = new_dir.append("chars_to_quote")
assert ctq_rp.lstat() assert ctq_rp.lstat()
......
import unittest
from commontest import *
from rdiff_backup import rpath
from rdiff_backup import metadata
"""***NOTE***
None of these tests should work unless your system supports resource
forks. So basically these tests should only be run on Mac OS X.
"""
Globals.read_resource_forks = Globals.write_resource_forks = 1
class ResourceForkTest(unittest.TestCase):
"""Test dealing with Mac OS X style resource forks"""
tempdir = rpath.RPath(Globals.local_connection,
'testfiles/resource_fork_test')
def make_temp(self):
"""Make temp directory testfiles/resource_fork_test"""
if self.tempdir.lstat(): self.tempdir.delete()
self.tempdir.mkdir()
def testBasic(self):
"""Test basic reading and writing of resource forks"""
self.make_temp()
rp = self.tempdir.append('test')
rp.touch()
assert rp.get_resource_fork() == '', rp.get_resource_fork()
s = 'new resource fork data'
rp.write_resource_fork(s)
assert rp.get_resource_fork() == s, rp.get_resource_fork()
rp2 = self.tempdir.append('test')
assert rp2.isreg()
assert rp2.get_resource_fork() == s, rp2.get_resource_fork()
def testRecord(self):
"""Test reading, writing, and comparing of records with rforks"""
self.make_temp()
rp = self.tempdir.append('test')
rp.touch()
rp.set_resource_fork('hello')
record = metadata.RORP2Record(rp)
#print record
rorp_out = metadata.Record2RORP(record)
assert rorp_out == rp, (rorp_out, rp)
assert rorp_out.get_resource_fork() == 'hello'
if __name__ == "__main__": unittest.main()
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