1#!/bin/sh 2"""": # -*-python-*- 3# https://sourceware.org/bugzilla/show_bug.cgi?id=26034 4export "BUP_ARGV_0"="$0" 5arg_i=1 6for arg in "$@"; do 7 export "BUP_ARGV_${arg_i}"="$arg" 8 shift 9 arg_i=$((arg_i + 1)) 10done 11# Here to end of preamble replaced during install 12bup_python="$(dirname "$0")/../../config/bin/python" || exit $? 13exec "$bup_python" "$0" 14""" 15# end of bup preamble 16 17from __future__ import absolute_import 18from calendar import timegm 19from pipes import quote 20from subprocess import check_call 21from time import strftime, strptime 22import os 23import os.path 24import sys 25import tempfile 26 27sys.path[:0] = [os.path.dirname(os.path.realpath(__file__)) + '/..'] 28 29from bup import compat, git, helpers, options 30from bup.compat import argv_bytes, str_type 31from bup.helpers import (handle_ctrl_c, 32 log, 33 readpipe, 34 shstr, 35 saved_errors, 36 unlink) 37import bup.path 38 39optspec = """ 40bup import-duplicity [-n] <duplicity-source-url> <bup-save-name> 41-- 42n,dry-run don't do anything; just print what would be done 43""" 44 45def logcmd(cmd): 46 log(shstr(cmd).decode(errors='backslashreplace') + '\n') 47 48def exc(cmd, shell=False): 49 global opt 50 logcmd(cmd) 51 if not opt.dry_run: 52 check_call(cmd, shell=shell) 53 54def exo(cmd, shell=False, preexec_fn=None, close_fds=True): 55 global opt 56 logcmd(cmd) 57 if not opt.dry_run: 58 return helpers.exo(cmd, shell=shell, preexec_fn=preexec_fn, 59 close_fds=close_fds)[0] 60 61def redirect_dup_output(): 62 os.dup2(1, 3) 63 os.dup2(1, 2) 64 65 66handle_ctrl_c() 67 68log('\nbup: import-duplicity is EXPERIMENTAL (proceed with caution)\n\n') 69 70o = options.Options(optspec) 71opt, flags, extra = o.parse(compat.argv[1:]) 72 73if len(extra) < 1 or not extra[0]: 74 o.fatal('duplicity source URL required') 75if len(extra) < 2 or not extra[1]: 76 o.fatal('bup destination save name required') 77if len(extra) > 2: 78 o.fatal('too many arguments') 79 80source_url, save_name = extra 81source_url = argv_bytes(source_url) 82save_name = argv_bytes(save_name) 83bup = bup.path.exe() 84 85git.check_repo_or_die() 86 87tmpdir = tempfile.mkdtemp(prefix=b'bup-import-dup-') 88try: 89 dup = [b'duplicity', b'--archive-dir', tmpdir + b'/dup-cache'] 90 restoredir = tmpdir + b'/restore' 91 tmpidx = tmpdir + b'/index' 92 93 collection_status = \ 94 exo(dup + [b'collection-status', b'--log-fd=3', source_url], 95 close_fds=False, preexec_fn=redirect_dup_output) # i.e. 3>&1 1>&2 96 # Duplicity output lines of interest look like this (one leading space): 97 # full 20150222T073111Z 1 noenc 98 # inc 20150222T073233Z 1 noenc 99 dup_timestamps = [] 100 for line in collection_status.splitlines(): 101 if line.startswith(b' inc '): 102 assert(len(line) >= len(b' inc 20150222T073233Z')) 103 dup_timestamps.append(line[5:21]) 104 elif line.startswith(b' full '): 105 assert(len(line) >= len(b' full 20150222T073233Z')) 106 dup_timestamps.append(line[6:22]) 107 for i, dup_ts in enumerate(dup_timestamps): 108 tm = strptime(dup_ts.decode('ascii'), '%Y%m%dT%H%M%SZ') 109 exc([b'rm', b'-rf', restoredir]) 110 exc(dup + [b'restore', b'-t', dup_ts, source_url, restoredir]) 111 exc([bup, b'index', b'-uxf', tmpidx, restoredir]) 112 exc([bup, b'save', b'--strip', b'--date', b'%d' % timegm(tm), 113 b'-f', tmpidx, b'-n', save_name, restoredir]) 114 sys.stderr.flush() 115finally: 116 exc([b'rm', b'-rf', tmpdir]) 117 118if saved_errors: 119 log('warning: %d errors encountered\n' % len(saved_errors)) 120 sys.exit(1) 121