From d03b8308a55060852ca25235e94d39ac7ebd2515 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 12 Apr 2019 16:48:34 -0700 Subject: [PATCH] backup: Use tar --transform to arrange the tarball instead of symlinks. This allows tar to print the real paths in error messages if something goes wrong. Signed-off-by: Anders Kaseorg --- scripts/setup/restore-backup | 16 +++++++---- zerver/management/commands/backup.py | 42 ++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/scripts/setup/restore-backup b/scripts/setup/restore-backup index 8d7cb83c15..ed6d308494 100755 --- a/scripts/setup/restore-backup +++ b/scripts/setup/restore-backup @@ -2,6 +2,7 @@ import argparse import os +import re import subprocess import sys import tempfile @@ -56,13 +57,16 @@ if __name__ == "__main__": os.chown(path, uid, gid) os.setresuid(uid, uid, 0) - # We create symlinks so that we can do a single `tar -x` - # command to unpack the uploads, settings, etc. to their - # appropriate places. + assert not any("|" in name or "|" in path for name, path in paths) + transform_args = [ + r"--transform=s|^zulip-backup/{}(/.*)?$|{}\1|x".format( + re.escape(name), path.replace("\\", r"\\") + ) + for name, path in paths + ] + os.mkdir(os.path.join(tmp, "zulip-backup")) - for name, path in paths: - os.symlink(path, os.path.join(tmp, "zulip-backup", name)) - run(["tar", "-C", tmp, "--keep-directory-symlink", "-xzf", args.tarball]) + run(["tar", "-C", tmp] + transform_args + ["-xPzf", args.tarball]) # Now, restore the the database backup using pg_restore. db_name = settings.DATABASES["default"]["NAME"] diff --git a/zerver/management/commands/backup.py b/zerver/management/commands/backup.py index 726b248b7f..4cf6cda1b8 100644 --- a/zerver/management/commands/backup.py +++ b/zerver/management/commands/backup.py @@ -1,4 +1,5 @@ import os +import re import tempfile from argparse import ArgumentParser, RawTextHelpFormatter from typing import Any @@ -33,6 +34,7 @@ class Command(ZulipBaseCommand): ) as tmp: os.mkdir(os.path.join(tmp, "zulip-backup")) members = [] + paths = [] with open(os.path.join(tmp, "zulip-backup", "zulip-version"), "w") as f: print(ZULIP_VERSION, file=f) @@ -53,14 +55,15 @@ class Command(ZulipBaseCommand): members.append("zulip-backup/postgres-version") if settings.DEVELOPMENT: - os.symlink( - os.path.join(settings.DEPLOY_ROOT, "zproject"), - os.path.join(tmp, "zulip-backup", "zproject"), + members.append( + os.path.join(settings.DEPLOY_ROOT, "zproject", "dev-secrets.conf") + ) + paths.append( + ("zproject", os.path.join(settings.DEPLOY_ROOT, "zproject")) ) - members.append("zulip-backup/zproject/dev-secrets.conf") else: - os.symlink("/etc/zulip", os.path.join(tmp, "zulip-backup", "settings")) - members.append("zulip-backup/settings") + members.append("/etc/zulip") + paths.append(("settings", "/etc/zulip")) db_name = settings.DATABASES["default"]["NAME"] db_dir = os.path.join(tmp, "zulip-backup", "database") @@ -73,11 +76,23 @@ class Command(ZulipBaseCommand): if settings.LOCAL_UPLOADS_DIR is not None and os.path.exists( os.path.join(settings.DEPLOY_ROOT, settings.LOCAL_UPLOADS_DIR) ): - os.symlink( - os.path.join(settings.DEPLOY_ROOT, settings.LOCAL_UPLOADS_DIR), - os.path.join(tmp, "zulip-backup", "uploads"), + members.append( + os.path.join(settings.DEPLOY_ROOT, settings.LOCAL_UPLOADS_DIR) ) - members.append("zulip-backup/uploads") + paths.append( + ( + "uploads", + os.path.join(settings.DEPLOY_ROOT, settings.LOCAL_UPLOADS_DIR), + ) + ) + + assert not any("|" in name or "|" in path for name, path in paths) + transform_args = [ + r"--transform=s|^{}(/.*)?$|zulip-backup/{}\1|x".format( + re.escape(path), name.replace("\\", r"\\") + ) + for name, path in paths + ] try: if options["output"] is None: @@ -89,7 +104,12 @@ class Command(ZulipBaseCommand): else: tarball_path = options["output"] - run(["tar", "-C", tmp, "-chzf", tarball_path, "--"] + members) + run( + ["tar", "-C", tmp, "-cPzf", tarball_path] + + transform_args + + ["--"] + + members + ) print("Backup tarball written to %s" % (tarball_path,)) except BaseException: if options["output"] is None: