]> arthur.barton.de Git - bup.git/blob - main.py
Use absolute_import from the __future__ everywhere
[bup.git] / main.py
1 #!/bin/sh
2 """": # -*-python-*- # -*-python-*-
3 bup_python="$(dirname "$0")/cmd/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
5 """
6 # end of bup preamble
7
8 from __future__ import absolute_import
9 import errno, re, sys, os, subprocess, signal, getopt
10
11 from fcntl import F_GETFL, F_SETFL
12 from subprocess import PIPE
13 from sys import stderr, stdout
14 import fcntl, select
15
16 argv = sys.argv
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]
20
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
28 else:
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
37
38
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
42
43
44 def usage(msg=""):
45     log('Usage: bup [-?|--help] [-d BUP_DIR] [--debug] [--profile] '
46         '<command> [options...]\n\n')
47     common = dict(
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',
58     )
59
60     log('Common commands:\n')
61     for cmd,synopsis in sorted(common.items()):
62         log('    %-10s %s\n' % (cmd, synopsis))
63     log('\n')
64     
65     log('Other available commands:\n')
66     cmds = []
67     for c in sorted(os.listdir(cmdpath) + os.listdir(exepath)):
68         if c.startswith('bup-') and c.find('.') < 0:
69             cname = c[4:]
70             if cname not in common:
71                 cmds.append(c[4:])
72     log(columnate(cmds, '    '))
73     log('\n')
74     
75     log("See 'bup help COMMAND' for more information on " +
76         "a specific command.\n")
77     if msg:
78         log("\n%s\n" % msg)
79     sys.exit(99)
80
81
82 if len(argv) < 2:
83     usage()
84
85 # Handle global options.
86 try:
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)
91
92 help_requested = None
93 do_profile = False
94
95 for opt in global_args:
96     if opt[0] in ['-?', '--help']:
97         help_requested = True
98     elif opt[0] in ['-V', '--version']:
99         subcmd = ['version']
100     elif opt[0] in ['-D', '--debug']:
101         helpers.buglvl += 1
102         os.environ['BUP_DEBUG'] = str(helpers.buglvl)
103     elif opt[0] in ['--profile']:
104         do_profile = True
105     elif opt[0] in ['-d', '--bup-dir']:
106         os.environ['BUP_DIR'] = opt[1]
107     else:
108         usage('error: unexpected option "%s"' % opt[0])
109
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'])
113
114 if len(subcmd) == 0:
115     if help_requested:
116         subcmd = ['help']
117     else:
118         usage()
119
120 if help_requested and subcmd[0] != 'help':
121     subcmd = ['help'] + subcmd
122
123 if len(subcmd) > 1 and subcmd[1] == '--help' and subcmd[0] != 'help':
124     subcmd = ['help', subcmd[0]] + subcmd[2:]
125
126 subcmd_name = subcmd[0]
127 if not subcmd_name:
128     usage()
129
130 def subpath(s):
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)
134     return sp
135
136 subcmd[0] = subpath(subcmd_name)
137 if not os.path.exists(subcmd[0]):
138     usage('error: unknown command "%s"' % subcmd_name)
139
140 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
141 if subcmd_name in ['mux', 'ftp', 'help']:
142     already_fixed = True
143 fix_stdout = not already_fixed and os.isatty(1)
144 fix_stderr = not already_fixed and os.isatty(2)
145
146 def force_tty():
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)
150
151
152 sep_rx = re.compile(r'([\r\n])')
153
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
158     carriage return."""
159     global sep_rx
160     assert sep in ('\r', '\n', None)
161     if not content:
162         if sep:
163             os.write(dest, sep)
164         return
165     for x in content:
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)))
173     os.write(dest, sep)
174
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."""
178     global sep_rx
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)
186     pending = {}
187     pending_ex = None
188     try:
189         fds = tuple([x for x in (src_out, src_err) if x is not None])
190         for fd in fds:
191             flags = fcntl.fcntl(fd, F_GETFL)
192             assert fcntl.fcntl(fd, F_SETFL, flags | os.O_NONBLOCK) == 0
193         while fds:
194             ready_fds, _, _ = select.select(fds, [], [])
195             width = tty_width()
196             for fd in ready_fds:
197                 buf = os.read(fd, 4096)
198                 dest = dest_out if fd == src_out else dest_err
199                 if not buf:
200                     fds = tuple([x for x in fds if x is not fd])
201                     print_clean_line(dest, pending.pop(fd, []), width)
202                 else:
203                     split = sep_rx.split(buf)
204                     if len(split) > 2:
205                         while len(split) > 1:
206                             content, sep = split[:2]
207                             split = split[2:]
208                             print_clean_line(dest,
209                                              pending.pop(fd, []) + [content],
210                                              width,
211                                              sep)
212                     else:
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)
217     try:
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
221             try:
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)
227     if pending_ex:
228         raise pending_ex
229
230 def run_subcmd(subcmd):
231
232     c = (do_profile and [sys.executable, '-m', 'cProfile'] or []) + subcmd
233     if not (fix_stdout or fix_stderr):
234         os.execvp(c[0], c)
235
236     p = None
237     try:
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,
242                              bufsize=4096,
243                              close_fds=True)
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)
248
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)
253         return p.wait()
254     except BaseException as ex:
255         add_ex_tb(ex)
256         try:
257             if p and p.poll() == None:
258                 os.kill(p.pid, signal.SIGTERM)
259                 p.wait()
260         except BaseException as kill_ex:
261             raise chain_ex(add_ex_tb(kill_ex), ex)
262         raise ex
263         
264 wrap_main(lambda : run_subcmd(subcmd))