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)
207 while len(split) > 1:
208 content, sep = split[:2]
210 print_clean_line(dest,
211 pending.pop(fd, []) + [content],
214 assert(len(split) == 1)
216 pending.setdefault(fd, []).extend(split)
217 except BaseException as ex:
218 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
220 # Try to finish each of the streams
221 for fd, pending_items in compat.items(pending):
222 dest = dest_out if fd == src_out else dest_err
224 print_clean_line(dest, pending_items, width)
225 except (EnvironmentError, EOFError) as ex:
226 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
227 except BaseException as ex:
228 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
232 def run_subcmd(subcmd):
234 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
235 if not (fix_stdout or fix_stderr):
240 p = subprocess.Popen(c,
241 stdout=PIPE if fix_stdout else sys.stdout,
242 stderr=PIPE if fix_stderr else sys.stderr,
243 preexec_fn=force_tty,
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 chain_ex(add_ex_tb(kill_ex), ex)
266 wrap_main(lambda : run_subcmd(subcmd))