diff -u duplicity-0.6.18/debian/changelog duplicity-0.6.18/debian/changelog --- duplicity-0.6.18/debian/changelog +++ duplicity-0.6.18/debian/changelog @@ -1,3 +1,22 @@ +duplicity (0.6.18-0ubuntu3.5) precise; urgency=low + + * debian/patches/14-lp946988-fix-gnupassphrase-error.dpatch + - Backport upstream modification applied in LP: #946988 that fix + a failure to resume a backup with "bad passphrase" when the proper + passphrase is being used. + + * debian/patches/13-lp1266763-add-concurrency-locking.dpatch + - Implement locking mechanism to avoid concurrent execution under the same + cache directory. This functionality adds a dependency to python-lockfile + Fixes LP: #1266763 + + * debian/patches/12-lp1266753-exception-if-no-s3.dpatch + - Add exception handling in the case where no S3 connection is + available instead of silently deleting the local cache. + Fixes LP: #1266753 + + -- Louis Bouchard Thu, 23 Jan 2014 10:16:32 +0100 + duplicity (0.6.18-0ubuntu3.4) precise; urgency=low * debian/patches/11-dont-skip-first-chunk-on-restart.dpatch: diff -u duplicity-0.6.18/debian/control duplicity-0.6.18/debian/control --- duplicity-0.6.18/debian/control +++ duplicity-0.6.18/debian/control @@ -3,15 +3,16 @@ Priority: optional Maintainer: Ubuntu Developers XSBC-Original-Maintainer: Alexander Zangerl -Build-Depends: debhelper (>= 5.0.37.2), librsync-dev (>=0.9.6), python-dev (>= 2.6.6-3), dpatch, rdiff, gnupg +Build-Depends: debhelper (>= 5.0.37.2), librsync-dev (>=0.9.6), python-dev (>= 2.6.6-3), dpatch, rdiff, gnupg, python-lockfile Standards-Version: 3.9.1 X-Python-Version: >= 2.5 Package: duplicity Architecture: any Homepage: http://duplicity.nongnu.org/ -Depends: ${shlibs:Depends}, ${python:Depends}, ${misc:Depends}, python-gnupginterface (>=0.3.2-9.1) +Depends: ${shlibs:Depends}, ${python:Depends}, ${misc:Depends}, python-gnupginterface (>=0.3.2-9.1), python-lockfile Suggests: python-boto, ncftp, rsync, ssh, python-paramiko +Breaks: deja-dup (<< 22.0-0ubuntu5 ) Description: encrypted bandwidth-efficient backup Duplicity backs directories by producing encrypted tar-format volumes and uploading them to a remote or local file server. Because duplicity diff -u duplicity-0.6.18/debian/patches/00list duplicity-0.6.18/debian/patches/00list --- duplicity-0.6.18/debian/patches/00list +++ duplicity-0.6.18/debian/patches/00list @@ -9,0 +10,3 @@ +12-lp1266753-exception-if-no-s3.dpatch +13-lp1266763-add-concurrency-locking.dpatch +14-lp946988-fix-gnupassphrase-error.dpatch only in patch2: unchanged: --- duplicity-0.6.18.orig/debian/patches/12-lp1266753-exception-if-no-s3.dpatch +++ duplicity-0.6.18/debian/patches/12-lp1266753-exception-if-no-s3.dpatch @@ -0,0 +1,32 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 12-lp1266753-exception-if-no-s3.dpatch by Louis Bouchard > +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Fix for LP: #1266753 - Avoid silent deletion of local cache if +## DP: S3 connection is not available + +@DPATCH@ +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/duplicity/backends/_boto_multi.py duplicity-0.6.18/duplicity/backends/_boto_multi.py +--- duplicity-0.6.18~/duplicity/backends/_boto_multi.py 2012-02-29 20:24:04.000000000 +0100 ++++ duplicity-0.6.18/duplicity/backends/_boto_multi.py 2014-01-17 15:09:07.798293632 +0100 +@@ -272,7 +272,7 @@ + + def list(self): + if not self.bucket: +- return [] ++ raise BackendException("No connection to backend") + + for n in range(1, globals.num_retries+1): + if n > 1: +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/duplicity/backends/_boto_single.py duplicity-0.6.18/duplicity/backends/_boto_single.py +--- duplicity-0.6.18~/duplicity/backends/_boto_single.py 2012-02-29 20:24:04.000000000 +0100 ++++ duplicity-0.6.18/duplicity/backends/_boto_single.py 2014-01-17 15:09:07.798293632 +0100 +@@ -257,7 +257,7 @@ + + def list(self): + if not self.bucket: +- return [] ++ raise BackendException("No connection to backend") + + for n in range(1, globals.num_retries+1): + if n > 1: only in patch2: unchanged: --- duplicity-0.6.18.orig/debian/patches/14-lp946988-fix-gnupassphrase-error.dpatch +++ duplicity-0.6.18/debian/patches/14-lp946988-fix-gnupassphrase-error.dpatch @@ -0,0 +1,92 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 14-lp946988-fix-gnupassphrase-error.dpatch by Louis Bouchard > +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: No description. + +@DPATCH@ +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/bin/duplicity duplicity-0.6.18/bin/duplicity +--- duplicity-0.6.18~/bin/duplicity 2014-01-23 10:48:07.000000000 +0100 ++++ duplicity-0.6.18/bin/duplicity 2014-01-23 10:49:46.164765926 +0100 +@@ -1353,10 +1353,8 @@ + + os.umask(077) + +- # full/inc only needs a passphrase for symmetric keys +- if not action in ["full", "inc"] or not globals.gpg_profile.recipients: +- # get the passphrase if we need to based on action/options +- globals.gpg_profile.passphrase = get_passphrase(1, action) ++ # get the passphrase if we need to based on action/options ++ globals.gpg_profile.passphrase = get_passphrase(1, action) + + if action == "restore": + restore(col_stats) +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/testing/tests/restarttest.py duplicity-0.6.18/testing/tests/restarttest.py +--- duplicity-0.6.18~/testing/tests/restarttest.py 2014-01-23 10:48:07.000000000 +0100 ++++ duplicity-0.6.18/testing/tests/restarttest.py 2014-01-23 10:52:17.000770651 +0100 +@@ -59,18 +59,22 @@ + Run duplicity binary with given arguments and options + """ + options.append("--archive-dir testfiles/cache") +- cmd_list = ["duplicity"] ++ # We run under setsid and take input from /dev/null (below) because ++ # this way we force a failure if duplicity tries to read from the ++ # console (like for gpg password or such). ++ cmd_list = ["setsid", "duplicity"] + cmd_list.extend(options + ["--allow-source-mismatch"]) + if current_time: + cmd_list.append("--current-time %s" % (current_time,)) + if other_args: + cmd_list.extend(other_args) + cmd_list.extend(self.class_args) + cmd_list.extend(arglist) ++ cmd_list.extend(["<", "/dev/null"]) + cmdline = " ".join(cmd_list) + #print "Running '%s'." % cmdline +- if not os.environ.has_key('PASSPHRASE'): +- os.environ['PASSPHRASE'] = 'foobar' ++ helper.set_environ('PASSPHRASE', helper.sign_passphrase) ++ self.addCleanup(lambda: helper.set_environ("PASSPHRASE", None)) + # print "CMD: %s" % cmdline + return_val = os.system(cmdline) + if return_val: +@@ -144,6 +148,15 @@ + self.verify(dirname, + time = current_time, options = restore_options) + ++ def make_largefiles(self, count=3, size=2): ++ """ ++ Makes a number of large files in testfiles/largefiles that each are ++ the specified number of megabytes. ++ """ ++ assert not os.system("mkdir testfiles/largefiles") ++ for n in range(count): ++ assert not os.system("dd if=/dev/urandom of=testfiles/largefiles/file%d bs=1024 count=%d > /dev/null 2>&1" % (n + 1, size * 1024)) ++ + def check_same(self, filename1, filename2): + """ + Verify two filenames are the same +@@ -226,6 +230,23 @@ + self.backup("full", "/bin", options = ["--vol 1"]) + self.verify("/bin") + ++ def test_restart_sign_and_encrypt(self): ++ """ ++ Test restarting a backup using same key for sign and encrypt ++ https://bugs.launchpad.net/duplicity/+bug/946988 ++ """ ++ self.make_largefiles() ++ enc_opts = ["--sign-key " + helper.sign_key, "--encrypt-key " + helper.sign_key] ++ # Force a failure partway through ++ try: ++ self.backup("full", "testfiles/largefiles", options = ["--vols 1", "--fail 2"] + enc_opts) ++ self.fail() ++ except CmdError, e: ++ pass ++ # Now finish that backup ++ self.backup("full", "testfiles/largefiles", options = enc_opts) ++ self.verify("testfiles/largefiles") ++ + def test_last_file_missing_in_middle(self): + """ + Test restart when the last file being backed up is missing on restart. only in patch2: unchanged: --- duplicity-0.6.18.orig/debian/patches/13-lp1266763-add-concurrency-locking.dpatch +++ duplicity-0.6.18/debian/patches/13-lp1266763-add-concurrency-locking.dpatch @@ -0,0 +1,158 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 13-lp1266763-add-concurrency-locking.dpatch by Louis Bouchard > +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Add the locking mechanism to prevent concurrent execution +## DP: Fixes LP: #1266763 + +@DPATCH@ +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/bin/duplicity duplicity-0.6.18/bin/duplicity +--- duplicity-0.6.18~/bin/duplicity 2012-02-29 20:24:05.000000000 +0100 ++++ duplicity-0.6.18/bin/duplicity 2014-01-17 14:46:49.606306394 +0100 +@@ -37,6 +37,8 @@ + import gettext + gettext.install('duplicity') + ++from lockfile import FileLock ++ + from duplicity import log + log.setup() + +@@ -1234,6 +1236,25 @@ + # determine what action we're performing and process command line + action = commandline.ProcessCommandLine(sys.argv[1:]) + ++ globals.lockfile = FileLock(os.path.join(globals.archive_dir.name, "lockfile")) ++ if globals.lockfile.is_locked(): ++ log.FatalError("Another instance is already running with this archive directory\n" ++ "If you are sure that this is the only instance running you may delete\n" ++ "the following lockfile and run the command again :\n" ++ "\t%s" % os.path.join(globals.archive_dir.name, "lockfile.lock" ) ++ , log.ErrorCode.user_error) ++ log.shutdown() ++ sys.exit(2) ++ ++ globals.lockfile.acquire(timeout = 0) ++ ++ try: ++ do_backup(action) ++ ++ finally: ++ util.release_lockfile() ++ ++def do_backup(action): + # The following is for starting remote debugging in Eclipse with Pydev. + # Adjust the path to your location and version of Eclipse and Pydev. + if globals.pydevd: +@@ -1385,7 +1407,6 @@ + finally: + tempdir.default().cleanup() + +- + if __name__ == "__main__": + try: + with_tempdir(main) +@@ -1397,16 +1418,19 @@ + # goes here, if needed. + except SystemExit, e: + # No traceback, just get out ++ util.release_lockfile() + sys.exit(e) + + except KeyboardInterrupt, e: + # No traceback, just get out + log.Info(_("INT intercepted...exiting.")) ++ util.release_lockfile() + sys.exit(4) + + except gpg.GPGError, e: + # For gpg errors, don't show an ugly stack trace by + # default. But do with sufficient verbosity. ++ util.release_lockfile() + log.Info(_("GPG error detail: %s") + % (''.join(traceback.format_exception(*sys.exc_info())))) + log.FatalError("%s: %s" % (e.__class__.__name__, e.args[0]), +@@ -1414,6 +1438,7 @@ + e.__class__.__name__) + + except duplicity.errors.UserError, e: ++ util.release_lockfile() + # For user errors, don't show an ugly stack trace by + # default. But do with sufficient verbosity. + log.Info(_("User error detail: %s") +@@ -1423,6 +1448,7 @@ + e.__class__.__name__) + + except duplicity.errors.BackendException, e: ++ util.release_lockfile() + # For backend errors, don't show an ugly stack trace by + # default. But do with sufficient verbosity. + log.Info(_("Backend error detail: %s") +@@ -1432,6 +1458,7 @@ + e.__class__.__name__) + + except Exception, e: ++ util.release_lockfile() + if "Forced assertion for testing" in str(e): + log.FatalError("%s: %s" % (e.__class__.__name__, str(e)), + log.ErrorCode.exception, +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/duplicity/collections.py duplicity-0.6.18/duplicity/collections.py +--- duplicity-0.6.18~/duplicity/collections.py 2012-02-29 20:24:04.000000000 +0100 ++++ duplicity-0.6.18/duplicity/collections.py 2014-01-17 14:46:49.606306394 +0100 +@@ -24,9 +24,11 @@ + import types + import gettext + ++ + from duplicity import log + from duplicity import file_naming + from duplicity import path ++from duplicity import util + from duplicity import dup_time + from duplicity import globals + from duplicity import manifest +@@ -162,6 +164,7 @@ + except Exception: + log.Debug("BackupSet.delete: missing %s" % lfn) + pass ++ util.release_lockfile() + + def __str__(self): + """ +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/duplicity/globals.py duplicity-0.6.18/duplicity/globals.py +--- duplicity-0.6.18~/duplicity/globals.py 2012-02-29 20:24:05.000000000 +0100 ++++ duplicity-0.6.18/duplicity/globals.py 2014-01-17 14:46:49.610306394 +0100 +@@ -90,6 +90,9 @@ + # windows machines. + time_separator = ":" + ++# Global lockfile used to manage concurrency ++lockfile = None ++ + # If this is true, only warn and don't raise fatal error when backup + # source directory doesn't match previous backup source directory. + allow_source_mismatch = None +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' duplicity-0.6.18~/duplicity/util.py duplicity-0.6.18/duplicity/util.py +--- duplicity-0.6.18~/duplicity/util.py 2012-02-29 20:24:04.000000000 +0100 ++++ duplicity-0.6.18/duplicity/util.py 2014-01-17 14:46:49.610306394 +0100 +@@ -28,6 +28,8 @@ + import string + import traceback + ++from lockfile import FileLock, UnlockError ++ + from duplicity import tarfile + + import duplicity.globals as globals +@@ -120,3 +122,11 @@ + pass + else: + raise ++ ++def release_lockfile(): ++ if globals.lockfile and globals.lockfile.is_locked(): ++ log.Debug(_("Releasing lockfile %s") % globals.lockfile ) ++ try: ++ globals.lockfile.release() ++ except UnlockError: ++ pass