Permalink
Browse files

Fixed a bug due to which no permissions has been stored for backup it…

…ems' top level directories.
  • Loading branch information...
1 parent 33f40c9 commit 2ec1465204ce6c2733fd65117da162a432b6f6cb @KonishchevDmitry committed Jan 17, 2013
Showing with 124 additions and 22 deletions.
  1. +2 −0 ChangeLog
  2. +34 −2 pyvsb/backuper.py
  3. +4 −1 setup.py
  4. +84 −19 tests/test.py
View
@@ -1,6 +1,8 @@
-----------
* Using pax tar archive format for storing backup data to support Unicode
and long file names.
+ * Fixed a bug due to which no permissions has been stored for backup items'
+ top level directories.
Version 0.3
View
@@ -11,7 +11,7 @@
import psh
system = psh.Program("sh", "-c", _defer = False)
-from .core import LogicalError
+from .core import Error, LogicalError
from .backup import Backup
from .storage import Storage
@@ -68,6 +68,10 @@ def wrapper(*args):
# Holds backup writing logic
self.__backup = Backup(config, storage)
+ # A list of backup items' top level directories that has been added to
+ # the backup
+ self.__toplevel_dirs = set()
+
def __enter__(self):
return self
@@ -84,7 +88,14 @@ def backup(self):
try:
for path, params in self.__config["backup_items"].items():
if self.__run_script(params.get("before")):
- self.__ok &= self.__backup_path(path, params.get("filter", []), path)
+ try:
+ self.__add_toplevel_dirs(path)
+ except Exception as e:
+ LOG.error("Failed to backup '%s': %s.", path, psys.e(e))
+ self.__ok = False
+ else:
+ self.__ok &= self.__backup_path(path, params.get("filter", []), path)
+
self.__ok &= self.__run_script(params.get("after"))
else:
self.__ok = False
@@ -102,6 +113,27 @@ def close(self):
self.__backup.close()
+ def __add_toplevel_dirs(self, path):
+ """
+ Adds all top level directories of the specified path to the backup.
+ """
+
+ toplevel_dir = "/"
+
+ for directory in path.split(os.path.sep)[1:-1]:
+ toplevel_dir = os.path.join(toplevel_dir, directory)
+ if toplevel_dir in self.__toplevel_dirs:
+ continue
+
+ stat_info = os.lstat(toplevel_dir)
+
+ if not stat.S_ISDIR(stat_info.st_mode):
+ raise Error("'{}' is not a directory", toplevel_dir)
+
+ self.__toplevel_dirs.add(toplevel_dir)
+ self.__backup.add_file(toplevel_dir, stat_info)
+
+
def __backup_path(self, path, filters, toplevel):
"""Backups the specified path."""
View
@@ -9,7 +9,10 @@
class PyTest(Test):
def finalize_options(self):
Test.finalize_options(self)
- self.test_args = [ "tests/test.py" ]
+ self.test_args = [
+ "tests/test.py",
+ #"--capture=no",
+ ]
self.test_suite = True
def run_tests(self):
View
@@ -67,6 +67,20 @@ def test_simple(env):
assert _hash_tree(env["restore_path"] + env["data_path"]) == source_tree
+def test_topdirs_permissions(env):
+ source_tree = _hash_tree(env["data_path"], prefix = "/")
+
+ with Backuper(env["config"]) as backuper:
+ assert backuper.backup()
+
+ with Restore(_get_backups(env)[-1], env["restore_path"]) as restorer:
+ assert restorer.restore()
+
+ assert _hash_tree(env["data_path"], prefix = "/") == source_tree
+ assert _hash_tree(env["restore_path"] + env["data_path"],
+ prefix = env["restore_path"]) == source_tree
+
+
@pytest.mark.parametrize("max_backups", ( 1, 10 ))
def test_double(env, max_backups):
source_tree = _hash_tree(env["data_path"])
@@ -193,11 +207,12 @@ def test_complex(env, config):
changing_file.write("first revision")
source_tree = _hash_tree(env["data_path"])
- source_tree["files"] = { name: tree
- for name, tree in source_tree["files"].items()
+ source_tree["data"]["files"] = {
+ name: tree
+ for name, tree in source_tree["data"]["files"].items()
if name in ( "etc", "home" )
}
- del source_tree["files"]["home"]["mtime"]
+ del source_tree["data"]["files"]["home"]["mtime"]
with Backuper(env["config"]) as backuper:
assert backuper.backup()
@@ -216,8 +231,9 @@ def test_complex(env, config):
changing_file.write("second revision")
source_tree = _hash_tree(env["data_path"])
- source_tree["files"] = { name: tree
- for name, tree in source_tree["files"].items()
+ source_tree["data"]["files"] = {
+ name: tree
+ for name, tree in source_tree["data"]["files"].items()
if name == "etc"
}
@@ -240,11 +256,12 @@ def test_complex(env, config):
changing_file.write("third revision")
source_tree = _hash_tree(env["data_path"])
- source_tree["files"] = { name: tree
- for name, tree in source_tree["files"].items()
+ source_tree["data"]["files"] = {
+ name: tree
+ for name, tree in source_tree["data"]["files"].items()
if name in ( "etc", "tmp" )
}
- del source_tree["files"]["etc"]["files"]["bash_completion.d"]
+ del source_tree["data"]["files"]["etc"]["files"]["bash_completion.d"]
with Backuper(env["config"]) as backuper:
assert backuper.backup()
@@ -277,14 +294,15 @@ def test_complex(env, config):
unix_socket.bind(os.path.join(tmp_path, "socket"))
source_tree = _hash_tree(env["data_path"])
- source_tree["files"] = { name: tree
- for name, tree in source_tree["files"].items()
+ source_tree["data"]["files"] = {
+ name: tree
+ for name, tree in source_tree["data"]["files"].items()
if name in ( "etc", "home", "tmp" )
}
- del source_tree["files"]["tmp"]["files"]["socket"]
+ del source_tree["data"]["files"]["tmp"]["files"]["socket"]
if not env["config"]["preserve_hard_links"]:
- source_tree["files"]["tmp"]["files"]["hardlink"]["links"] = 1
- source_tree["files"]["tmp"]["files"]["hardlink-target"]["links"] = 1
+ source_tree["data"]["files"]["tmp"]["files"]["hardlink"]["links"] = 1
+ source_tree["data"]["files"]["tmp"]["files"]["hardlink-target"]["links"] = 1
env["config"]["backup_items"] = {
env["data_path"] + "/etc": {},
@@ -306,8 +324,8 @@ def test_complex(env, config):
restore_tree = _hash_tree(env["restore_path"] + env["data_path"])
if backup_id == 0:
- del restore_tree["files"]["home"]["mtime"]
- del restore_tree["files"]["home"]["files"]["script_test"]
+ del restore_tree["data"]["files"]["home"]["mtime"]
+ del restore_tree["data"]["files"]["home"]["files"]["script_test"]
with open(env["restore_path"] + env["data_path"] + "/home/script_test") as script_test:
assert script_test.read() == "SCRIPT_TEST\n"
@@ -370,18 +388,52 @@ def _get_groups(env):
return sorted(os.listdir(env["backup_path"]))
-def _hash_tree(path, root = True):
+def _hash_tree(path, prefix = None, root = True):
"""Hashes a directory tree to compare directories for equality."""
+# TODO
+ if root:
+ time.sleep(2)
+ if root == True:
+ topdirs = []
+ directory = path
+
+ while prefix is not None:
+ directory = os.path.dirname(directory)
+ if directory == prefix:
+ break
+
+ stat_info = os.lstat(directory)
+
+ tree = {
+ "name": os.path.basename(directory),
+ "mode": stat_info.st_mode,
+ "files": {},
+ }
+
+ if os.geteuid() == 0:
+ tree.update({
+ "uid": stat_info.st_uid,
+ "gid": stat_info.st_gid,
+ })
+
+ topdirs.append(tree)
+
+ topdirs.append({ "files": {} })
+
stat_info = os.lstat(path)
tree = {
"name": os.path.basename(path),
- "uid": stat_info.st_uid,
- "gid": stat_info.st_gid,
"mode": stat_info.st_mode,
}
+ if os.geteuid() == 0:
+ tree.update({
+ "uid": stat_info.st_uid,
+ "gid": stat_info.st_gid,
+ })
+
if not stat.S_ISLNK(stat_info.st_mode):
tree["mtime"] = int(stat_info.st_mtime)
@@ -407,4 +459,17 @@ def _hash_tree(path, root = True):
elif stat.S_ISLNK(stat_info.st_mode):
tree["target"] = os.readlink(path)
- return { "files": tree["files"] } if root else tree
+ if root:
+ prev_tree = None
+
+ for cur_tree in topdirs:
+ if prev_tree is None:
+ cur_tree["files"][tree["name"]] = tree
+ else:
+ cur_tree["files"][prev_tree["name"]] = prev_tree
+
+ prev_tree = cur_tree
+
+ return cur_tree["files"]
+ else:
+ return tree

0 comments on commit 2ec1465

Please sign in to comment.