]> arthur.barton.de Git - bup.git/blob - lib/cmd/bup
ftp: fix ls arguments for python3
[bup.git] / lib / cmd / bup
1 #!/bin/sh
2 """": # -*-python-*-
3 set -e
4 # https://sourceware.org/bugzilla/show_bug.cgi?id=26034
5 export "BUP_ARGV_0"="$0"
6 arg_i=1
7 for arg in "$@"; do
8     export "BUP_ARGV_${arg_i}"="$arg"
9     shift
10     arg_i=$((arg_i + 1))
11 done
12 # Here to end of preamble replaced during install
13 # Find our directory
14 top="$(pwd)"
15 cmdpath="$0"
16 # loop because macos doesn't have recursive readlink/realpath utils
17 while test -L "$cmdpath"; do
18     link="$(readlink "$cmdpath")"
19     cd "$(dirname "$cmdpath")"
20     cmdpath="$link"
21 done
22 script_home="$(cd "$(dirname "$cmdpath")" && pwd -P)"
23 cd "$top"
24 exec "$script_home/../../config/bin/python" "$0"
25 """
26 # end of bup preamble
27
28 from __future__ import absolute_import, print_function
29
30 import os, sys
31 sys.path[:0] = [os.path.dirname(os.path.realpath(__file__)) + '/..']
32
33 import errno, getopt, os, re, select, signal, subprocess, sys
34 from subprocess import PIPE
35
36 from bup.compat import environ, fsdecode
37 from bup.io import path_msg
38 from bup import compat, path, helpers
39 from bup.compat import add_ex_tb, add_ex_ctx, argv_bytes, wrap_main
40 from bup.helpers import atoi, columnate, debug1, log, merge_dict, tty_width
41 from bup.io import byte_stream, path_msg
42
43 cmdpath = path.cmddir()
44
45 # We manipulate the subcmds here as strings, but they must be ASCII
46 # compatible, since we're going to be looking for exactly
47 # b'bup-SUBCMD' to exec.
48
49 def usage(msg=""):
50     log('Usage: bup [-?|--help] [-d BUP_DIR] [--debug] [--profile] '
51         '<command> [options...]\n\n')
52     common = dict(
53         ftp = 'Browse backup sets using an ftp-like client',
54         fsck = 'Check backup sets for damage and add redundancy information',
55         fuse = 'Mount your backup sets as a filesystem',
56         help = 'Print detailed help for the given command',
57         index = 'Create or display the index of files to back up',
58         on = 'Backup a remote machine to the local one',
59         restore = 'Extract files from a backup set',
60         save = 'Save files into a backup set (note: run "bup index" first)',
61         tag = 'Tag commits for easier access',
62         web = 'Launch a web server to examine backup sets',
63     )
64
65     log('Common commands:\n')
66     for cmd,synopsis in sorted(common.items()):
67         log('    %-10s %s\n' % (cmd, synopsis))
68     log('\n')
69     
70     log('Other available commands:\n')
71     cmds = []
72     for c in sorted(os.listdir(cmdpath)):
73         if c.startswith(b'bup-') and c.find(b'.') < 0:
74             cname = fsdecode(c[4:])
75             if cname not in common:
76                 cmds.append(c[4:].decode(errors='backslashreplace'))
77     log(columnate(cmds, '    '))
78     log('\n')
79     
80     log("See 'bup help COMMAND' for more information on " +
81         "a specific command.\n")
82     if msg:
83         log("\n%s\n" % msg)
84     sys.exit(99)
85
86 argv = compat.argv
87 if len(argv) < 2:
88     usage()
89
90 # Handle global options.
91 try:
92     optspec = ['help', 'version', 'debug', 'profile', 'bup-dir=']
93     global_args, subcmd = getopt.getopt(argv[1:], '?VDd:', optspec)
94 except getopt.GetoptError as ex:
95     usage('error: %s' % ex.msg)
96
97 subcmd = [argv_bytes(x) for x in subcmd]
98 help_requested = None
99 do_profile = False
100 bup_dir = None
101
102 for opt in global_args:
103     if opt[0] in ['-?', '--help']:
104         help_requested = True
105     elif opt[0] in ['-V', '--version']:
106         subcmd = [b'version']
107     elif opt[0] in ['-D', '--debug']:
108         helpers.buglvl += 1
109         environ[b'BUP_DEBUG'] = b'%d' % helpers.buglvl
110     elif opt[0] in ['--profile']:
111         do_profile = True
112     elif opt[0] in ['-d', '--bup-dir']:
113         bup_dir = argv_bytes(opt[1])
114     else:
115         usage('error: unexpected option "%s"' % opt[0])
116
117 if bup_dir:
118     bup_dir = argv_bytes(bup_dir)
119
120 # Make BUP_DIR absolute, so we aren't affected by chdir (i.e. save -C, etc.).
121 if bup_dir:
122     environ[b'BUP_DIR'] = os.path.abspath(bup_dir)
123
124 if len(subcmd) == 0:
125     if help_requested:
126         subcmd = [b'help']
127     else:
128         usage()
129
130 if help_requested and subcmd[0] != b'help':
131     subcmd = [b'help'] + subcmd
132
133 if len(subcmd) > 1 and subcmd[1] == b'--help' and subcmd[0] != b'help':
134     subcmd = [b'help', subcmd[0]] + subcmd[2:]
135
136 subcmd_name = subcmd[0]
137 if not subcmd_name:
138     usage()
139
140 def subpath(subcmd):
141     return os.path.join(cmdpath, b'bup-' + subcmd)
142
143 subcmd[0] = subpath(subcmd_name)
144 if not os.path.exists(subcmd[0]):
145     usage('error: unknown command "%s"' % path_msg(subcmd_name))
146
147 already_fixed = atoi(environ.get(b'BUP_FORCE_TTY'))
148 if subcmd_name in [b'mux', b'ftp', b'help']:
149     already_fixed = True
150 fix_stdout = not already_fixed and os.isatty(1)
151 fix_stderr = not already_fixed and os.isatty(2)
152
153 if fix_stdout or fix_stderr:
154     tty_env = merge_dict(environ,
155                          {b'BUP_FORCE_TTY': (b'%d'
156                                              % ((fix_stdout and 1 or 0)
157                                                 + (fix_stderr and 2 or 0)))})
158 else:
159     tty_env = environ
160
161
162 sep_rx = re.compile(br'([\r\n])')
163
164 def print_clean_line(dest, content, width, sep=None):
165     """Write some or all of content, followed by sep, to the dest fd after
166     padding the content with enough spaces to fill the current
167     terminal width or truncating it to the terminal width if sep is a
168     carriage return."""
169     global sep_rx
170     assert sep in (b'\r', b'\n', None)
171     if not content:
172         if sep:
173             os.write(dest, sep)
174         return
175     for x in content:
176         assert not sep_rx.match(x)
177     content = b''.join(content)
178     if sep == b'\r' and len(content) > width:
179         content = content[width:]
180     os.write(dest, content)
181     if len(content) < width:
182         os.write(dest, b' ' * (width - len(content)))
183     if sep:
184         os.write(dest, sep)
185
186 def filter_output(src_out, src_err, dest_out, dest_err):
187     """Transfer data from src_out to dest_out and src_err to dest_err via
188     print_clean_line until src_out and src_err close."""
189     global sep_rx
190     assert not isinstance(src_out, bool)
191     assert not isinstance(src_err, bool)
192     assert not isinstance(dest_out, bool)
193     assert not isinstance(dest_err, bool)
194     assert src_out is not None or src_err is not None
195     assert (src_out is None) == (dest_out is None)
196     assert (src_err is None) == (dest_err is None)
197     pending = {}
198     pending_ex = None
199     try:
200         fds = tuple([x for x in (src_out, src_err) if x is not None])
201         while fds:
202             ready_fds, _, _ = select.select(fds, [], [])
203             width = tty_width()
204             for fd in ready_fds:
205                 buf = os.read(fd, 4096)
206                 dest = dest_out if fd == src_out else dest_err
207                 if not buf:
208                     fds = tuple([x for x in fds if x is not fd])
209                     print_clean_line(dest, pending.pop(fd, []), width)
210                 else:
211                     split = sep_rx.split(buf)
212                     while len(split) > 1:
213                         content, sep = split[:2]
214                         split = split[2:]
215                         print_clean_line(dest,
216                                          pending.pop(fd, []) + [content],
217                                          width,
218                                          sep)
219                     assert(len(split) == 1)
220                     if split[0]:
221                         pending.setdefault(fd, []).extend(split)
222     except BaseException as ex:
223         pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
224     try:
225         # Try to finish each of the streams
226         for fd, pending_items in compat.items(pending):
227             dest = dest_out if fd == src_out else dest_err
228             try:
229                 print_clean_line(dest, pending_items, width)
230             except (EnvironmentError, EOFError) as ex:
231                 pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
232     except BaseException as ex:
233         pending_ex = add_ex_ctx(add_ex_tb(ex), pending_ex)
234     if pending_ex:
235         raise pending_ex
236
237 def run_subcmd(subcmd):
238
239     c = (do_profile and [sys.executable, b'-m', b'cProfile'] or []) + subcmd
240     if not (fix_stdout or fix_stderr):
241         os.execvp(c[0], c)
242
243     sys.stdout.flush()
244     sys.stderr.flush()
245     out = byte_stream(sys.stdout)
246     err = byte_stream(sys.stderr)
247     p = None
248     try:
249         p = subprocess.Popen(c,
250                              stdout=PIPE if fix_stdout else out,
251                              stderr=PIPE if fix_stderr else err,
252                              env=tty_env, bufsize=4096, close_fds=True)
253         # Assume p will receive these signals and quit, which will
254         # then cause us to quit.
255         for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
256             signal.signal(sig, signal.SIG_IGN)
257
258         filter_output(fix_stdout and p.stdout.fileno() or None,
259                       fix_stderr and p.stderr.fileno() or None,
260                       fix_stdout and out.fileno() or None,
261                       fix_stderr and err.fileno() or None)
262         return p.wait()
263     except BaseException as ex:
264         add_ex_tb(ex)
265         try:
266             if p and p.poll() == None:
267                 os.kill(p.pid, signal.SIGTERM)
268                 p.wait()
269         except BaseException as kill_ex:
270             raise add_ex_ctx(add_ex_tb(kill_ex), ex)
271         raise ex
272         
273 wrap_main(lambda : run_subcmd(subcmd))