1
2from __future__ import absolute_import, print_function
3from array import array
4from binascii import hexlify
5from traceback import print_exception
6import os, sys
7
8# Please see CODINGSTYLE for important exception handling guidelines
9# and the rationale behind add_ex_tb(), add_ex_ctx(), etc.
10
11py_maj = sys.version_info[0]
12py3 = py_maj >= 3
13
14if py3:
15
16    from os import environb as environ
17    from os import fsdecode, fsencode
18    from shlex import quote
19    ModuleNotFoundError = ModuleNotFoundError
20    input = input
21    range = range
22    str_type = str
23    int_types = (int,)
24
25    def hexstr(b):
26        """Return hex string (not bytes as with hexlify) representation of b."""
27        return b.hex()
28
29    def reraise(ex):
30        raise ex.with_traceback(sys.exc_info()[2])
31
32    def add_ex_tb(ex):
33        """Do nothing (already handled by Python 3 infrastructure)."""
34        return ex
35
36    def add_ex_ctx(ex, context_ex):
37        """Do nothing (already handled by Python 3 infrastructure)."""
38        return ex
39
40    def items(x):
41        return x.items()
42
43    def argv_bytes(x):
44        """Return the original bytes passed to main() for an argv argument."""
45        return fsencode(x)
46
47    def bytes_from_uint(i):
48        return bytes((i,))
49
50    def bytes_from_byte(b):  # python > 2: b[3] returns ord('x'), not b'x'
51        return bytes((b,))
52
53    byte_int = lambda x: x
54
55    def buffer(object, offset=None, size=None):
56        if size:
57            assert offset is not None
58            return memoryview(object)[offset:offset + size]
59        if offset:
60            return memoryview(object)[offset:]
61        return memoryview(object)
62
63    def getcwd():
64        return fsencode(os.getcwd())
65
66else:  # Python 2
67
68    ModuleNotFoundError = ImportError
69
70    def fsdecode(x):
71        return x
72
73    def fsencode(x):
74        return x
75
76    from pipes import quote
77    from os import environ, getcwd
78
79    from bup.py2raise import reraise
80
81    input = raw_input
82    range = xrange
83    str_type = basestring
84    int_types = (int, long)
85
86    hexstr = hexlify
87
88    def add_ex_tb(ex):
89        """Add a traceback to ex if it doesn't already have one.  Return ex.
90
91        """
92        if not getattr(ex, '__traceback__', None):
93            ex.__traceback__ = sys.exc_info()[2]
94        return ex
95
96    def add_ex_ctx(ex, context_ex):
97        """Make context_ex the __context__ of ex (unless it already has one).
98        Return ex.
99
100        """
101        if context_ex:
102            if not getattr(ex, '__context__', None):
103                ex.__context__ = context_ex
104        return ex
105
106    def dump_traceback(ex):
107        stack = [ex]
108        next_ex = getattr(ex, '__context__', None)
109        while next_ex:
110            stack.append(next_ex)
111            next_ex = getattr(next_ex, '__context__', None)
112        stack = reversed(stack)
113        ex = next(stack)
114        tb = getattr(ex, '__traceback__', None)
115        print_exception(type(ex), ex, tb)
116        for ex in stack:
117            print('\nDuring handling of the above exception, another exception occurred:\n',
118                  file=sys.stderr)
119            tb = getattr(ex, '__traceback__', None)
120            print_exception(type(ex), ex, tb)
121
122    def items(x):
123        return x.iteritems()
124
125    def argv_bytes(x):
126        """Return the original bytes passed to main() for an argv argument."""
127        return x
128
129    bytes_from_uint = chr
130
131    def bytes_from_byte(b):
132        return b
133
134    byte_int = ord
135
136    buffer = buffer
137
138
139argv = None
140argvb = None
141
142def _configure_argv():
143    global argv, argvb
144    assert not argv
145    assert not argvb
146    if len(sys.argv) > 1:
147        if environ.get(b'BUP_ARGV_0'):
148            print('error: BUP_ARGV* set and sys.argv not empty', file=sys.stderr)
149            sys.exit(2)
150        argv = sys.argv
151        argvb = [argv_bytes(x) for x in argv]
152        return
153    args = []
154    i = 0
155    arg = environ.get(b'BUP_ARGV_%d' % i)
156    while arg is not None:
157        args.append(arg)
158        i += 1
159        arg = environ.get(b'BUP_ARGV_%d' % i)
160    i -= 1
161    while i >= 0:
162        del environ[b'BUP_ARGV_%d' % i]
163        i -= 1
164    argvb = args
165    # System encoding?
166    if py3:
167        argv = [x.decode(errors='surrogateescape') for x in args]
168    else:
169        argv = argvb
170
171_configure_argv()
172
173
174def wrap_main(main):
175    """Run main() and raise a SystemExit with the return value if it
176    returns, pass along any SystemExit it raises, convert
177    KeyboardInterrupts into exit(130), and print a Python 3 style
178    contextual backtrace for other exceptions in both Python 2 and
179    3)."""
180    try:
181        sys.exit(main())
182    except KeyboardInterrupt as ex:
183        sys.exit(130)
184    except SystemExit as ex:
185        raise
186    except BaseException as ex:
187        if py3:
188            raise
189        add_ex_tb(ex)
190        dump_traceback(ex)
191        sys.exit(1)
192
193
194# Excepting wrap_main() in the traceback, these should produce similar output:
195#   python2 lib/bup/compat.py
196#   python3 lib/bup/compat.py
197# i.e.:
198#   diff -u <(python2 lib/bup/compat.py 2>&1) <(python3 lib/bup/compat.py 2>&1)
199#
200# Though the python3 output for 'second' will include a stacktrace
201# starting from wrap_main, rather than from outer().
202
203if __name__ == '__main__':
204
205    def inner():
206        raise Exception('first')
207
208    def outer():
209        try:
210            inner()
211        except Exception as ex:
212            add_ex_tb(ex)
213            try:
214                raise Exception('second')
215            except Exception as ex2:
216                raise add_ex_ctx(add_ex_tb(ex2), ex)
217
218    wrap_main(outer)
219