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