2 """": # -*-python-*- # -*-python-*-
6 # loop because macos doesn't have recursive readlink/realpath utils
7 while test -L "$cmdpath"; do
8 link="$(readlink "$cmdpath")"
9 cd "$(dirname "$cmdpath")"
12 script_home="$(cd "$(dirname "$cmdpath")" && pwd -P)"
14 exec "$script_home/bup-python" "$0" ${1+"$@"}
18 from __future__ import absolute_import, print_function
19 import errno, re, sys, os, subprocess, signal, getopt
21 if sys.version_info[0] != 2 \
22 and not os.environ.get('BUP_ALLOW_UNEXPECTED_PYTHON_VERSION') == 'true':
23 print('error: bup may crash with python versions other than 2, or eat your data',
27 from subprocess import PIPE
28 from sys import stderr, stdout
31 from bup import compat, path, helpers
32 from bup.compat import add_ex_tb, add_ex_ctx, wrap_main
33 from bup.helpers import atoi, columnate, debug1, log, merge_dict, tty_width
35 cmdpath = path.cmddir()
38 log('Usage: bup [-?|--help] [-d BUP_DIR] [--debug] [--profile] '
39 '<command> [options...]\n\n')
41 ftp = 'Browse backup sets using an ftp-like client',
42 fsck = 'Check backup sets for damage and add redundancy information',
43 fuse = 'Mount your backup sets as a filesystem',
44 help = 'Print detailed help for the given command',
45 index = 'Create or display the index of files to back up',
46 on = 'Backup a remote machine to the local one',
47 restore = 'Extract files from a backup set',
48 save = 'Save files into a backup set (note: run "bup index" first)',
49 tag = 'Tag commits for easier access',
50 web = 'Launch a web server to examine backup sets',
53 log('Common commands:\n')
54 for cmd,synopsis in sorted(common.items()):
55 log(' %-10s %s\n' % (cmd, synopsis))
58 log('Other available commands:\n')
60 for c in sorted(os.listdir(cmdpath)):
61 if c.startswith('bup-') and c.find('.') < 0:
63 if cname not in common:
65 log(columnate(cmds, ' '))
68 log("See 'bup help COMMAND' for more information on " +
69 "a specific command.\n")
78 # Handle global options.
80 optspec = ['help', 'version', 'debug', 'profile', 'bup-dir=']
81 global_args, subcmd = getopt.getopt(sys.argv[1:], '?VDd:', optspec)
82 except getopt.GetoptError as ex:
83 usage('error: %s' % ex.msg)
88 for opt in global_args:
89 if opt[0] in ['-?', '--help']:
91 elif opt[0] in ['-V', '--version']:
93 elif opt[0] in ['-D', '--debug']:
95 os.environ['BUP_DEBUG'] = str(helpers.buglvl)
96 elif opt[0] in ['--profile']:
98 elif opt[0] in ['-d', '--bup-dir']:
99 os.environ['BUP_DIR'] = opt[1]
101 usage('error: unexpected option "%s"' % opt[0])
103 # Make BUP_DIR absolute, so we aren't affected by chdir (i.e. save -C, etc.).
104 if 'BUP_DIR' in os.environ:
105 os.environ['BUP_DIR'] = os.path.abspath(os.environ['BUP_DIR'])
113 if help_requested and subcmd[0] != 'help':
114 subcmd = ['help'] + subcmd
116 if len(subcmd) > 1 and subcmd[1] == '--help' and subcmd[0] != 'help':
117 subcmd = ['help', subcmd[0]] + subcmd[2:]
119 subcmd_name = subcmd[0]
124 return os.path.join(cmdpath, 'bup-%s' % s)
126 subcmd[0] = subpath(subcmd_name)
127 if not os.path.exists(subcmd[0]):
128 usage('error: unknown command "%s"' % subcmd_name)
130 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
131 if subcmd_name in ['mux', 'ftp', 'help']:
133 fix_stdout = not already_fixed and os.isatty(1)
134 fix_stderr = not already_fixed and os.isatty(2)
136 if fix_stdout or fix_stderr:
137 tty_env = merge_dict(os.environ,
138 {'BUP_FORCE_TTY': str((fix_stdout and 1 or 0)
139 + (fix_stderr and 2 or 0))})
144 sep_rx = re.compile(br'([\r\n])')
146 def print_clean_line(dest, content, width, sep=None):
147 """Write some or all of content, followed by sep, to the dest fd after
148 padding the content with enough spaces to fill the current
149 terminal width or truncating it to the terminal width if sep is a
152 assert sep in (b'\r', b'\n', None)
158 assert not sep_rx.match(x)
159 content = b''.join(content)
160 if sep == b'\r' and len(content) > width:
161 content = content[width:]
162 os.write(dest, content)
163 if len(content) < width:
164 os.write(dest, b' ' * (width - len(content)))
168 def filter_output(src_out, src_err, dest_out, dest_err):
169 """Transfer data from src_out to dest_out and src_err to dest_err via
170 print_clean_line until src_out and src_err close."""
172 assert not isinstance(src_out, bool)
173 assert not isinstance(src_err, bool)
174 assert not isinstance(dest_out, bool)
175 assert not isinstance(dest_err, bool)
176 assert src_out is not None or src_err is not None
177 assert (src_out is None) == (dest_out is None)
178 assert (src_err is None) == (dest_err is None)
182 fds = tuple([x for x in (src_out, src_err) if x is not None])
184 ready_fds, _, _ = select.select(fds, [], [])
187 buf = os.read(fd, 4096)
188 dest = dest_out if fd == src_out else dest_err
190 fds = tuple([x for x in fds if x is not fd])
191 print_clean_line(dest, pending.pop(fd, []), width)
193 split = sep_rx.split(buf)
194 while len(split) > 1:
195 content, sep = split[:2]
197 print_clean_line(dest,
198 pending.pop(fd, []) + [content],
201 assert(len(split) == 1)
203 pending.setdefault(fd, []).extend(split)
204 except BaseException as ex:
205 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
207 # Try to finish each of the streams
208 for fd, pending_items in compat.items(pending):
209 dest = dest_out if fd == src_out else dest_err
211 print_clean_line(dest, pending_items, width)
212 except (EnvironmentError, EOFError) as ex:
213 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
214 except BaseException as ex:
215 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
219 def run_subcmd(subcmd):
221 c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
222 if not (fix_stdout or fix_stderr):
227 p = subprocess.Popen(c,
228 stdout=PIPE if fix_stdout else sys.stdout,
229 stderr=PIPE if fix_stderr else sys.stderr,
230 env=tty_env, bufsize=4096, close_fds=True)
231 # Assume p will receive these signals and quit, which will
232 # then cause us to quit.
233 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
234 signal.signal(sig, signal.SIG_IGN)
236 filter_output(fix_stdout and p.stdout.fileno() or None,
237 fix_stderr and p.stderr.fileno() or None,
238 fix_stdout and sys.stdout.fileno() or None,
239 fix_stderr and sys.stderr.fileno() or None)
241 except BaseException as ex:
244 if p and p.poll() == None:
245 os.kill(p.pid, signal.SIGTERM)
247 except BaseException as kill_ex:
248 raise add_ex_ctx(add_ex_tb(kill_ex), ex)
251 wrap_main(lambda : run_subcmd(subcmd))