2 """": # -*-python-*- # -*-python-*-
3 bup_python="$(dirname "$0")/cmd/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 from __future__ import absolute_import, print_function
9 import errno, re, sys, os, subprocess, signal, getopt
11 if sys.version_info[0] != 2 \
12 and not os.environ.get('BUP_ALLOW_UNEXPECTED_PYTHON_VERSION') == 'true':
13 print('error: bup may crash with python versions other than 2, or eat your data',
17 from subprocess import PIPE
18 from sys import stderr, stdout
22 exe = os.path.realpath(argv[0])
23 exepath = os.path.split(exe)[0] or '.'
24 exeprefix = os.path.split(os.path.abspath(exepath))[0]
26 # fix the PYTHONPATH to include our lib dir
27 if os.path.exists("%s/lib/bup/cmd/." % exeprefix):
28 # installed binary in /.../bin.
29 # eg. /usr/bin/bup means /usr/lib/bup/... is where our libraries are.
30 cmdpath = "%s/lib/bup/cmd" % exeprefix
31 libpath = "%s/lib/bup" % exeprefix
32 resourcepath = libpath
34 # running from the src directory without being installed first
35 cmdpath = os.path.join(exepath, 'cmd')
36 libpath = os.path.join(exepath, 'lib')
37 resourcepath = libpath
38 sys.path[:0] = [libpath]
39 os.environ['PYTHONPATH'] = libpath + ':' + os.environ.get('PYTHONPATH', '')
40 os.environ['BUP_MAIN_EXE'] = os.path.abspath(exe)
41 os.environ['BUP_RESOURCE_PATH'] = resourcepath
44 from bup import compat, helpers
45 from bup.compat import add_ex_tb, add_ex_ctx, wrap_main
46 from bup.helpers import atoi, columnate, debug1, log, merge_dict, tty_width
50 log('Usage: bup [-?|--help] [-d BUP_DIR] [--debug] [--profile] '
51 '<command> [options...]\n\n')
53 ftp = 'Browse backup sets using an ftp-like client',
54 fsck = 'Check backup sets for damage and add redundancy information',
55 fuse = 'Mount your backup sets as a filesystem',
56 help = 'Print detailed help for the given command',
57 index = 'Create or display the index of files to back up',
58 on = 'Backup a remote machine to the local one',
59 restore = 'Extract files from a backup set',
60 save = 'Save files into a backup set (note: run "bup index" first)',
61 tag = 'Tag commits for easier access',
62 web = 'Launch a web server to examine backup sets',
65 log('Common commands:\n')
66 for cmd,synopsis in sorted(common.items()):
67 log(' %-10s %s\n' % (cmd, synopsis))
70 log('Other available commands:\n')
72 for c in sorted(os.listdir(cmdpath) + os.listdir(exepath)):
73 if c.startswith('bup-') and c.find('.') < 0:
75 if cname not in common:
77 log(columnate(cmds, ' '))
80 log("See 'bup help COMMAND' for more information on " +
81 "a specific command.\n")
90 # Handle global options.
92 optspec = ['help', 'version', 'debug', 'profile', 'bup-dir=']
93 global_args, subcmd = getopt.getopt(argv[1:], '?VDd:', optspec)
94 except getopt.GetoptError as ex:
95 usage('error: %s' % ex.msg)
100 for opt in global_args:
101 if opt[0] in ['-?', '--help']:
102 help_requested = True
103 elif opt[0] in ['-V', '--version']:
105 elif opt[0] in ['-D', '--debug']:
107 os.environ['BUP_DEBUG'] = str(helpers.buglvl)
108 elif opt[0] in ['--profile']:
110 elif opt[0] in ['-d', '--bup-dir']:
111 os.environ['BUP_DIR'] = opt[1]
113 usage('error: unexpected option "%s"' % opt[0])
115 # Make BUP_DIR absolute, so we aren't affected by chdir (i.e. save -C, etc.).
116 if 'BUP_DIR' in os.environ:
117 os.environ['BUP_DIR'] = os.path.abspath(os.environ['BUP_DIR'])
125 if help_requested and subcmd[0] != 'help':
126 subcmd = ['help'] + subcmd
128 if len(subcmd) > 1 and subcmd[1] == '--help' and subcmd[0] != 'help':
129 subcmd = ['help', subcmd[0]] + subcmd[2:]
131 subcmd_name = subcmd[0]
136 sp = os.path.join(exepath, 'bup-%s' % s)
137 if not os.path.exists(sp):
138 sp = os.path.join(cmdpath, 'bup-%s' % s)
141 subcmd[0] = subpath(subcmd_name)
142 if not os.path.exists(subcmd[0]):
143 usage('error: unknown command "%s"' % subcmd_name)
145 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
146 if subcmd_name in ['mux', 'ftp', 'help']:
148 fix_stdout = not already_fixed and os.isatty(1)
149 fix_stderr = not already_fixed and os.isatty(2)
151 if fix_stdout or fix_stderr:
152 tty_env = merge_dict(os.environ,
153 {'BUP_FORCE_TTY': str((fix_stdout and 1 or 0)
154 + (fix_stderr and 2 or 0))})
159 sep_rx = re.compile(br'([\r\n])')
161 def print_clean_line(dest, content, width, sep=None):
162 """Write some or all of content, followed by sep, to the dest fd after
163 padding the content with enough spaces to fill the current
164 terminal width or truncating it to the terminal width if sep is a
167 assert sep in (b'\r', b'\n', None)
173 assert not sep_rx.match(x)
174 content = b''.join(content)
175 if sep == b'\r' and len(content) > width:
176 content = content[width:]
177 os.write(dest, content)
178 if len(content) < width:
179 os.write(dest, b' ' * (width - len(content)))
183 def filter_output(src_out, src_err, dest_out, dest_err):
184 """Transfer data from src_out to dest_out and src_err to dest_err via
185 print_clean_line until src_out and src_err close."""
187 assert not isinstance(src_out, bool)
188 assert not isinstance(src_err, bool)
189 assert not isinstance(dest_out, bool)
190 assert not isinstance(dest_err, bool)
191 assert src_out is not None or src_err is not None
192 assert (src_out is None) == (dest_out is None)
193 assert (src_err is None) == (dest_err is None)
197 fds = tuple([x for x in (src_out, src_err) if x is not None])
199 ready_fds, _, _ = select.select(fds, [], [])
202 buf = os.read(fd, 4096)
203 dest = dest_out if fd == src_out else dest_err
205 fds = tuple([x for x in fds if x is not fd])
206 print_clean_line(dest, pending.pop(fd, []), width)
208 split = sep_rx.split(buf)
209 while len(split) > 1:
210 content, sep = split[:2]
212 print_clean_line(dest,
213 pending.pop(fd, []) + [content],
216 assert(len(split) == 1)
218 pending.setdefault(fd, []).extend(split)
219 except BaseException as ex:
220 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
222 # Try to finish each of the streams
223 for fd, pending_items in compat.items(pending):
224 dest = dest_out if fd == src_out else dest_err
226 print_clean_line(dest, pending_items, width)
227 except (EnvironmentError, EOFError) as ex:
228 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
229 except BaseException as ex:
230 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
234 def run_subcmd(subcmd):
236 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
237 if not (fix_stdout or fix_stderr):
242 p = subprocess.Popen(c,
243 stdout=PIPE if fix_stdout else sys.stdout,
244 stderr=PIPE if fix_stderr else sys.stderr,
245 env=tty_env, bufsize=4096, close_fds=True)
246 # Assume p will receive these signals and quit, which will
247 # then cause us to quit.
248 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
249 signal.signal(sig, signal.SIG_IGN)
251 filter_output(fix_stdout and p.stdout.fileno() or None,
252 fix_stderr and p.stderr.fileno() or None,
253 fix_stdout and sys.stdout.fileno() or None,
254 fix_stderr and sys.stderr.fileno() or None)
256 except BaseException as ex:
259 if p and p.poll() == None:
260 os.kill(p.pid, signal.SIGTERM)
262 except BaseException as kill_ex:
263 raise add_ex_ctx(add_ex_tb(kill_ex), ex)
266 wrap_main(lambda : run_subcmd(subcmd))