2 """": # -*-python-*- # -*-python-*-
3 bup_python="$(dirname "$0")/cmd/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 import errno, re, sys, os, subprocess, signal, getopt
10 from fcntl import F_GETFL, F_SETFL
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 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 flags = fcntl.fcntl(fd, F_GETFL)
191 assert fcntl.fcntl(fd, F_SETFL, flags | os.O_NONBLOCK) == 0
193 ready_fds, _, _ = select.select(fds, [], [])
196 buf = os.read(fd, 4096)
197 dest = dest_out if fd == src_out else dest_err
199 fds = tuple([x for x in fds if x is not fd])
200 print_clean_line(dest, pending.pop(fd, []), width)
202 split = sep_rx.split(buf)
204 while len(split) > 1:
205 content, sep = split[:2]
207 print_clean_line(dest,
208 pending.pop(fd, []) + [content],
212 assert(len(split) == 1)
213 pending.setdefault(fd, []).extend(split)
214 except BaseException as ex:
215 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
217 # Try to finish each of the streams
218 for fd, pending_items in compat.items(pending):
219 dest = dest_out if fd == src_out else dest_err
221 print_clean_line(dest, pending_items, width)
222 except (EnvironmentError, EOFError) as ex:
223 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
224 except BaseException as ex:
225 pending_ex = chain_ex(add_ex_tb(ex), pending_ex)
229 def run_subcmd(subcmd):
231 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
232 if not (fix_stdout or fix_stderr):
237 p = subprocess.Popen(c,
238 stdout=PIPE if fix_stdout else sys.stdout,
239 stderr=PIPE if fix_stderr else sys.stderr,
240 preexec_fn=force_tty,
243 # Assume p will receive these signals and quit, which will
244 # then cause us to quit.
245 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
246 signal.signal(sig, signal.SIG_IGN)
248 filter_output(fix_stdout and p.stdout.fileno() or None,
249 fix_stderr and p.stderr.fileno() or None,
250 fix_stdout and sys.stdout.fileno() or None,
251 fix_stderr and sys.stderr.fileno() or None)
253 except BaseException as ex:
256 if p and p.poll() == None:
257 os.kill(p.pid, signal.SIGTERM)
259 except BaseException as kill_ex:
260 raise chain_ex(add_ex_tb(kill_ex), ex)
263 wrap_main(lambda : run_subcmd(subcmd))