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, chain_ex, wrap_main
46 from bup.helpers import atoi, columnate, debug1, log, 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)
152 if fix_stdout or fix_stderr:
153 amt = (fix_stdout and 1 or 0) + (fix_stderr and 2 or 0)
154 os.environ['BUP_FORCE_TTY'] = str(amt)
157 sep_rx = re.compile(r'([\r\n])')
159 def print_clean_line(dest, content, width, sep=None):
160 """Write some or all of content, followed by sep, to the dest fd after
161 padding the content with enough spaces to fill the current
162 terminal width or truncating it to the terminal width if sep is a
165 assert sep in ('\r', '\n', None)
171 assert not sep_rx.match(x)
172 content = ''.join(content)
173 if sep == '\r' and len(content) > width:
174 content = content[width:]
175 os.write(dest, content)
176 if len(content) < width:
177 os.write(dest, ' ' * (width - len(content)))
181 def filter_output(src_out, src_err, dest_out, dest_err):
182 """Transfer data from src_out to dest_out and src_err to dest_err via
183 print_clean_line until src_out and src_err close."""
185 assert not isinstance(src_out, bool)
186 assert not isinstance(src_err, bool)
187 assert not isinstance(dest_out, bool)
188 assert not isinstance(dest_err, bool)
189 assert src_out is not None or src_err is not None
190 assert (src_out is None) == (dest_out is None)
191 assert (src_err is None) == (dest_err is None)
195 fds = tuple([x for x in (src_out, src_err) if x is not None])
197 ready_fds, _, _ = select.select(fds, [], [])
200 buf = os.read(fd, 4096)
201 dest = dest_out if fd == src_out else dest_err
203 fds = tuple([x for x in fds if x is not fd])
204 print_clean_line(dest, pending.pop(fd, []), width)
206 split = sep_rx.split(buf)
208 while len(split) > 1:
209 content, sep = split[:2]
211 print_clean_line(dest,
212 pending.pop(fd, []) + [content],
216 assert(len(split) == 1)
217 pending.setdefault(fd, []).extend(split)
218 except BaseException as ex:
219 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
221 # Try to finish each of the streams
222 for fd, pending_items in compat.items(pending):
223 dest = dest_out if fd == src_out else dest_err
225 print_clean_line(dest, pending_items, width)
226 except (EnvironmentError, EOFError) as ex:
227 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
228 except BaseException as ex:
229 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
233 def run_subcmd(subcmd):
235 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
236 if not (fix_stdout or fix_stderr):
241 p = subprocess.Popen(c,
242 stdout=PIPE if fix_stdout else sys.stdout,
243 stderr=PIPE if fix_stderr else sys.stderr,
244 preexec_fn=force_tty,
247 # Assume p will receive these signals and quit, which will
248 # then cause us to quit.
249 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
250 signal.signal(sig, signal.SIG_IGN)
252 filter_output(fix_stdout and p.stdout.fileno() or None,
253 fix_stderr and p.stderr.fileno() or None,
254 fix_stdout and sys.stdout.fileno() or None,
255 fix_stderr and sys.stderr.fileno() or None)
257 except BaseException as ex:
260 if p and p.poll() == None:
261 os.kill(p.pid, signal.SIGTERM)
263 except BaseException as kill_ex:
264 raise chain_ex(add_ex_tb(kill_ex), ex)
267 wrap_main(lambda : run_subcmd(subcmd))