2 """": # -*-python-*- # -*-python-*-
3 bup_python="$(dirname "$0")/cmd/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 from __future__ import absolute_import
9 import errno, re, sys, os, subprocess, signal, getopt
11 from subprocess import PIPE
12 from sys import stderr, stdout
16 exe = os.path.realpath(argv[0])
17 exepath = os.path.split(exe)[0] or '.'
18 exeprefix = os.path.split(os.path.abspath(exepath))[0]
20 # fix the PYTHONPATH to include our lib dir
21 if os.path.exists("%s/lib/bup/cmd/." % exeprefix):
22 # installed binary in /.../bin.
23 # eg. /usr/bin/bup means /usr/lib/bup/... is where our libraries are.
24 cmdpath = "%s/lib/bup/cmd" % exeprefix
25 libpath = "%s/lib/bup" % exeprefix
26 resourcepath = libpath
28 # running from the src directory without being installed first
29 cmdpath = os.path.join(exepath, 'cmd')
30 libpath = os.path.join(exepath, 'lib')
31 resourcepath = libpath
32 sys.path[:0] = [libpath]
33 os.environ['PYTHONPATH'] = libpath + ':' + os.environ.get('PYTHONPATH', '')
34 os.environ['BUP_MAIN_EXE'] = os.path.abspath(exe)
35 os.environ['BUP_RESOURCE_PATH'] = resourcepath
38 from bup import compat, helpers
39 from bup.compat import add_ex_tb, chain_ex, wrap_main
40 from bup.helpers import atoi, columnate, debug1, log, tty_width
44 log('Usage: bup [-?|--help] [-d BUP_DIR] [--debug] [--profile] '
45 '<command> [options...]\n\n')
47 ftp = 'Browse backup sets using an ftp-like client',
48 fsck = 'Check backup sets for damage and add redundancy information',
49 fuse = 'Mount your backup sets as a filesystem',
50 help = 'Print detailed help for the given command',
51 index = 'Create or display the index of files to back up',
52 on = 'Backup a remote machine to the local one',
53 restore = 'Extract files from a backup set',
54 save = 'Save files into a backup set (note: run "bup index" first)',
55 tag = 'Tag commits for easier access',
56 web = 'Launch a web server to examine backup sets',
59 log('Common commands:\n')
60 for cmd,synopsis in sorted(common.items()):
61 log(' %-10s %s\n' % (cmd, synopsis))
64 log('Other available commands:\n')
66 for c in sorted(os.listdir(cmdpath) + os.listdir(exepath)):
67 if c.startswith('bup-') and c.find('.') < 0:
69 if cname not in common:
71 log(columnate(cmds, ' '))
74 log("See 'bup help COMMAND' for more information on " +
75 "a specific command.\n")
84 # Handle global options.
86 optspec = ['help', 'version', 'debug', 'profile', 'bup-dir=']
87 global_args, subcmd = getopt.getopt(argv[1:], '?VDd:', optspec)
88 except getopt.GetoptError as ex:
89 usage('error: %s' % ex.msg)
94 for opt in global_args:
95 if opt[0] in ['-?', '--help']:
97 elif opt[0] in ['-V', '--version']:
99 elif opt[0] in ['-D', '--debug']:
101 os.environ['BUP_DEBUG'] = str(helpers.buglvl)
102 elif opt[0] in ['--profile']:
104 elif opt[0] in ['-d', '--bup-dir']:
105 os.environ['BUP_DIR'] = opt[1]
107 usage('error: unexpected option "%s"' % opt[0])
109 # Make BUP_DIR absolute, so we aren't affected by chdir (i.e. save -C, etc.).
110 if 'BUP_DIR' in os.environ:
111 os.environ['BUP_DIR'] = os.path.abspath(os.environ['BUP_DIR'])
119 if help_requested and subcmd[0] != 'help':
120 subcmd = ['help'] + subcmd
122 if len(subcmd) > 1 and subcmd[1] == '--help' and subcmd[0] != 'help':
123 subcmd = ['help', subcmd[0]] + subcmd[2:]
125 subcmd_name = subcmd[0]
130 sp = os.path.join(exepath, 'bup-%s' % s)
131 if not os.path.exists(sp):
132 sp = os.path.join(cmdpath, 'bup-%s' % s)
135 subcmd[0] = subpath(subcmd_name)
136 if not os.path.exists(subcmd[0]):
137 usage('error: unknown command "%s"' % subcmd_name)
139 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
140 if subcmd_name in ['mux', 'ftp', 'help']:
142 fix_stdout = not already_fixed and os.isatty(1)
143 fix_stderr = not already_fixed and os.isatty(2)
146 if fix_stdout or fix_stderr:
147 amt = (fix_stdout and 1 or 0) + (fix_stderr and 2 or 0)
148 os.environ['BUP_FORCE_TTY'] = str(amt)
151 sep_rx = re.compile(r'([\r\n])')
153 def print_clean_line(dest, content, width, sep=None):
154 """Write some or all of content, followed by sep, to the dest fd after
155 padding the content with enough spaces to fill the current
156 terminal width or truncating it to the terminal width if sep is a
159 assert sep in ('\r', '\n', None)
165 assert not sep_rx.match(x)
166 content = ''.join(content)
167 if sep == '\r' and len(content) > width:
168 content = content[width:]
169 os.write(dest, content)
170 if len(content) < width:
171 os.write(dest, ' ' * (width - len(content)))
174 def filter_output(src_out, src_err, dest_out, dest_err):
175 """Transfer data from src_out to dest_out and src_err to dest_err via
176 print_clean_line until src_out and src_err close."""
178 assert not isinstance(src_out, bool)
179 assert not isinstance(src_err, bool)
180 assert not isinstance(dest_out, bool)
181 assert not isinstance(dest_err, bool)
182 assert src_out is not None or src_err is not None
183 assert (src_out is None) == (dest_out is None)
184 assert (src_err is None) == (dest_err is None)
188 fds = tuple([x for x in (src_out, src_err) if x is not None])
190 ready_fds, _, _ = select.select(fds, [], [])
193 buf = os.read(fd, 4096)
194 dest = dest_out if fd == src_out else dest_err
196 fds = tuple([x for x in fds if x is not fd])
197 print_clean_line(dest, pending.pop(fd, []), width)
199 split = sep_rx.split(buf)
201 while len(split) > 1:
202 content, sep = split[:2]
204 print_clean_line(dest,
205 pending.pop(fd, []) + [content],
209 assert(len(split) == 1)
210 pending.setdefault(fd, []).extend(split)
211 except BaseException as ex:
212 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
214 # Try to finish each of the streams
215 for fd, pending_items in compat.items(pending):
216 dest = dest_out if fd == src_out else dest_err
218 print_clean_line(dest, pending_items, width)
219 except (EnvironmentError, EOFError) as ex:
220 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
221 except BaseException as ex:
222 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
226 def run_subcmd(subcmd):
228 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
229 if not (fix_stdout or fix_stderr):
234 p = subprocess.Popen(c,
235 stdout=PIPE if fix_stdout else sys.stdout,
236 stderr=PIPE if fix_stderr else sys.stderr,
237 preexec_fn=force_tty,
240 # Assume p will receive these signals and quit, which will
241 # then cause us to quit.
242 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
243 signal.signal(sig, signal.SIG_IGN)
245 filter_output(fix_stdout and p.stdout.fileno() or None,
246 fix_stderr and p.stderr.fileno() or None,
247 fix_stdout and sys.stdout.fileno() or None,
248 fix_stderr and sys.stderr.fileno() or None)
250 except BaseException as ex:
253 if p and p.poll() == None:
254 os.kill(p.pid, signal.SIGTERM)
256 except BaseException as kill_ex:
257 raise chain_ex(add_ex_tb(kill_ex), ex)
260 wrap_main(lambda : run_subcmd(subcmd))