helper.delete(upath)
helper.restore_directories_permissions()
+ helper.apply_delayed_links()
index_it.release()
os.chdir(cwd)
helper.cleanup()
return j, l_no
+RECOVER_OK = 0
+RECOVER_NO = 1
+RECOVER_INTERDIR_MADE = 2
+
class RestoreHelper(object):
'''
Class used to help to restore files from indices
# tarfile.extractall for details.
_directories = []
+ # collected symlinks to be restored at a later instant
+ _delayed_symlinks= []
+
def __init__(self, deltatar, cwd, index_list=[], backup_path=False,
tarobj=None):
'''
data['tarobj'].close()
data['tarobj'] = None
+ def apply_delayed_links(self):
+ data = self._data[0]
+ for member, path, set_attrs in self._delayed_symlinks:
+ data["tarobj"].extract(member, path, set_attrs=set_attrs)
+
def delete(self, path):
'''
Delete a file
# file might fail when trying to extract a multivolume member
index_data['tarobj'].volume_number = index_data['curr_vol_no']
+ def create_placeholder_file (tarinfo, path, set_attrs, recover=RECOVER_OK):
+ try:
+ fullpath = os.path.join(path, tarinfo.name)
+ fd = os.open(fullpath, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0)
+ except FileExistsError as exn: # == EEXIST
+ if recover != RECOVER_NO: # remove existing file and retry
+ os.unlink(fullpath)
+ return create_placeholder_file(tarinfo, path, set_attrs,
+ recover=RECOVER_NO)
+ raise exn # propagate error otherwise
+ except FileNotFoundError as exn: # == ENOENT
+ if recover == RECOVER_OK: # create interdir only once
+ os.makedirs(path)
+ return create_placeholder_file(tarinfo, path, set_attrs,
+ recover=RECOVER_INTERDIR_MADE)
+ os.close(fd)
+ return self._delayed_symlinks.append((member, path, set_attrs))
+
# finally, restore the file
- index_data['tarobj'].extract(member)
+ index_data['tarobj'].extract(member, symlink_cb=create_placeholder_file)
def add_member_dir(self, member):
'''
else:
self._dbg(1, "tarfile: %s" % e)
- def extract(self, member, path="", set_attrs=True):
+ def extract(self, member, path="", set_attrs=True, symlink_cb=None):
"""Extract a member from the archive to the current working directory,
using its full name. Its file information is extracted as accurately
as possible. `member' may be a filename or a TarInfo object. You can
specify a different directory using `path'. File attributes (owner,
mtime, mode) are set unless `set_attrs' is False.
+ ``symlink_cb`` is a hook accepting a function that is passed the
+ ``member``, ``path``, and ``set_attrs`` arguments if the tarinfo for
+ ``member`` indicates a symlink in which case only the callback
+ passed will be applied, skipping the actual extraction.
"""
self._check("r")
if tarinfo.islnk():
tarinfo._link_target = os.path.join(path, tarinfo.linkname)
+ if symlink_cb is not None and tarinfo.issym():
+ return symlink_cb(tarinfo, path, set_attrs)
+
try:
self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
set_attrs=set_attrs)