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 fcntl import F_GETFL, F_SETFL
12 from subprocess import PIPE
13 from sys import stderr, stdout
17 exe = os.path.realpath(argv[0])
18 exepath = os.path.split(exe)[0] or '.'
19 exeprefix = os.path.split(os.path.abspath(exepath))[0]
21 # fix the PYTHONPATH to include our lib dir
22 if os.path.exists("%s/lib/bup/cmd/." % exeprefix):
23 # installed binary in /.../bin.
24 # eg. /usr/bin/bup means /usr/lib/bup/... is where our libraries are.
25 cmdpath = "%s/lib/bup/cmd" % exeprefix
26 libpath = "%s/lib/bup" % exeprefix
27 resourcepath = libpath
29 # running from the src directory without being installed first
30 cmdpath = os.path.join(exepath, 'cmd')
31 libpath = os.path.join(exepath, 'lib')
32 resourcepath = libpath
33 sys.path[:0] = [libpath]
34 os.environ['PYTHONPATH'] = libpath + ':' + os.environ.get('PYTHONPATH', '')
35 os.environ['BUP_MAIN_EXE'] = os.path.abspath(exe)
36 os.environ['BUP_RESOURCE_PATH'] = resourcepath
39 from bup import compat, helpers
40 from bup.compat import add_ex_tb, chain_ex, wrap_main
41 from bup.helpers import atoi, columnate, debug1, log, tty_width
45 log('Usage: bup [-?|--help] [-d BUP_DIR] [--debug] [--profile] '
46 '<command> [options...]\n\n')
48 ftp = 'Browse backup sets using an ftp-like client',
49 fsck = 'Check backup sets for damage and add redundancy information',
50 fuse = 'Mount your backup sets as a filesystem',
51 help = 'Print detailed help for the given command',
52 index = 'Create or display the index of files to back up',
53 on = 'Backup a remote machine to the local one',
54 restore = 'Extract files from a backup set',
55 save = 'Save files into a backup set (note: run "bup index" first)',
56 tag = 'Tag commits for easier access',
57 web = 'Launch a web server to examine backup sets',
60 log('Common commands:\n')
61 for cmd,synopsis in sorted(common.items()):
62 log(' %-10s %s\n' % (cmd, synopsis))
65 log('Other available commands:\n')
67 for c in sorted(os.listdir(cmdpath) + os.listdir(exepath)):
68 if c.startswith('bup-') and c.find('.') < 0:
70 if cname not in common:
72 log(columnate(cmds, ' '))
75 log("See 'bup help COMMAND' for more information on " +
76 "a specific command.\n")
85 # Handle global options.
87 optspec = ['help', 'version', 'debug', 'profile', 'bup-dir=']
88 global_args, subcmd = getopt.getopt(argv[1:], '?VDd:', optspec)
89 except getopt.GetoptError as ex:
90 usage('error: %s' % ex.msg)
95 for opt in global_args:
96 if opt[0] in ['-?', '--help']:
98 elif opt[0] in ['-V', '--version']:
100 elif opt[0] in ['-D', '--debug']:
102 os.environ['BUP_DEBUG'] = str(helpers.buglvl)
103 elif opt[0] in ['--profile']:
105 elif opt[0] in ['-d', '--bup-dir']:
106 os.environ['BUP_DIR'] = opt[1]
108 usage('error: unexpected option "%s"' % opt[0])
110 # Make BUP_DIR absolute, so we aren't affected by chdir (i.e. save -C, etc.).
111 if 'BUP_DIR' in os.environ:
112 os.environ['BUP_DIR'] = os.path.abspath(os.environ['BUP_DIR'])
120 if help_requested and subcmd[0] != 'help':
121 subcmd = ['help'] + subcmd
123 if len(subcmd) > 1 and subcmd[1] == '--help' and subcmd[0] != 'help':
124 subcmd = ['help', subcmd[0]] + subcmd[2:]
126 subcmd_name = subcmd[0]
131 sp = os.path.join(exepath, 'bup-%s' % s)
132 if not os.path.exists(sp):
133 sp = os.path.join(cmdpath, 'bup-%s' % s)
136 subcmd[0] = subpath(subcmd_name)
137 if not os.path.exists(subcmd[0]):
138 usage('error: unknown command "%s"' % subcmd_name)
140 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
141 if subcmd_name in ['mux', 'ftp', 'help']:
143 fix_stdout = not already_fixed and os.isatty(1)
144 fix_stderr = not already_fixed and os.isatty(2)
147 if fix_stdout or fix_stderr:
148 amt = (fix_stdout and 1 or 0) + (fix_stderr and 2 or 0)
149 os.environ['BUP_FORCE_TTY'] = str(amt)
152 sep_rx = re.compile(r'([\r\n])')
154 def print_clean_line(dest, content, width, sep=None):
155 """Write some or all of content, followed by sep, to the dest fd after
156 padding the content with enough spaces to fill the current
157 terminal width or truncating it to the terminal width if sep is a
160 assert sep in ('\r', '\n', None)
166 assert not sep_rx.match(x)
167 content = ''.join(content)
168 if sep == '\r' and len(content) > width:
169 content = content[width:]
170 os.write(dest, content)
171 if len(content) < width:
172 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 flags = fcntl.fcntl(fd, F_GETFL)
192 assert fcntl.fcntl(fd, F_SETFL, flags | os.O_NONBLOCK) == 0
194 ready_fds, _, _ = select.select(fds, [], [])
197 buf = os.read(fd, 4096)
198 dest = dest_out if fd == src_out else dest_err
200 fds = tuple([x for x in fds if x is not fd])
201 print_clean_line(dest, pending.pop(fd, []), width)
203 split = sep_rx.split(buf)
205 while len(split) > 1:
206 content, sep = split[:2]
208 print_clean_line(dest,
209 pending.pop(fd, []) + [content],
213 assert(len(split) == 1)
214 pending.setdefault(fd, []).extend(split)
215 except BaseException as ex:
216 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
218 # Try to finish each of the streams
219 for fd, pending_items in compat.items(pending):
220 dest = dest_out if fd == src_out else dest_err
222 print_clean_line(dest, pending_items, width)
223 except (EnvironmentError, EOFError) as ex:
224 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
225 except BaseException as ex:
226 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
230 def run_subcmd(subcmd):
232 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
233 if not (fix_stdout or fix_stderr):
238 p = subprocess.Popen(c,
239 stdout=PIPE if fix_stdout else sys.stdout,
240 stderr=PIPE if fix_stderr else sys.stderr,
241 preexec_fn=force_tty,
244 # Assume p will receive these signals and quit, which will
245 # then cause us to quit.
246 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
247 signal.signal(sig, signal.SIG_IGN)
249 filter_output(fix_stdout and p.stdout.fileno() or None,
250 fix_stderr and p.stderr.fileno() or None,
251 fix_stdout and sys.stdout.fileno() or None,
252 fix_stderr and sys.stderr.fileno() or None)
254 except BaseException as ex:
257 if p and p.poll() == None:
258 os.kill(p.pid, signal.SIGTERM)
260 except BaseException as kill_ex:
261 raise chain_ex(add_ex_tb(kill_ex), ex)
264 wrap_main(lambda : run_subcmd(subcmd))