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)))
175 def filter_output(src_out, src_err, dest_out, dest_err):
176 """Transfer data from src_out to dest_out and src_err to dest_err via
177 print_clean_line until src_out and src_err close."""
179 assert not isinstance(src_out, bool)
180 assert not isinstance(src_err, bool)
181 assert not isinstance(dest_out, bool)
182 assert not isinstance(dest_err, bool)
183 assert src_out is not None or src_err is not None
184 assert (src_out is None) == (dest_out is None)
185 assert (src_err is None) == (dest_err is None)
189 fds = tuple([x for x in (src_out, src_err) if x is not None])
191 ready_fds, _, _ = select.select(fds, [], [])
194 buf = os.read(fd, 4096)
195 dest = dest_out if fd == src_out else dest_err
197 fds = tuple([x for x in fds if x is not fd])
198 print_clean_line(dest, pending.pop(fd, []), width)
200 split = sep_rx.split(buf)
202 while len(split) > 1:
203 content, sep = split[:2]
205 print_clean_line(dest,
206 pending.pop(fd, []) + [content],
210 assert(len(split) == 1)
211 pending.setdefault(fd, []).extend(split)
212 except BaseException as ex:
213 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
215 # Try to finish each of the streams
216 for fd, pending_items in compat.items(pending):
217 dest = dest_out if fd == src_out else dest_err
219 print_clean_line(dest, pending_items, width)
220 except (EnvironmentError, EOFError) as ex:
221 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
222 except BaseException as ex:
223 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
227 def run_subcmd(subcmd):
229 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
230 if not (fix_stdout or fix_stderr):
235 p = subprocess.Popen(c,
236 stdout=PIPE if fix_stdout else sys.stdout,
237 stderr=PIPE if fix_stderr else sys.stderr,
238 preexec_fn=force_tty,
241 # Assume p will receive these signals and quit, which will
242 # then cause us to quit.
243 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
244 signal.signal(sig, signal.SIG_IGN)
246 filter_output(fix_stdout and p.stdout.fileno() or None,
247 fix_stderr and p.stderr.fileno() or None,
248 fix_stdout and sys.stdout.fileno() or None,
249 fix_stderr and sys.stderr.fileno() or None)
251 except BaseException as ex:
254 if p and p.poll() == None:
255 os.kill(p.pid, signal.SIGTERM)
257 except BaseException as kill_ex:
258 raise chain_ex(add_ex_tb(kill_ex), ex)
261 wrap_main(lambda : run_subcmd(subcmd))