]> arthur.barton.de Git - bup.git/blob - lib/bup/compat.py
Detect failures to explicitly close mmaps in py3 too
[bup.git] / lib / bup / compat.py
1
2 from __future__ import absolute_import, print_function
3 from binascii import hexlify
4 from traceback import print_exception
5 import os, sys
6
7 # Please see CODINGSTYLE for important exception handling guidelines
8 # and the rationale behind add_ex_tb(), add_ex_ctx(), etc.
9
10 py_maj = sys.version_info[0]
11 py3 = py_maj >= 3
12
13 if py3:
14
15     # pylint: disable=unused-import
16     from contextlib import ExitStack, nullcontext
17     from os import environb as environ
18     from os import fsdecode, fsencode
19     from shlex import quote
20     # pylint: disable=undefined-variable
21     # (for python2 looking here)
22     ModuleNotFoundError = ModuleNotFoundError
23     input = input
24     range = range
25     str_type = str
26     int_types = (int,)
27
28     def hexstr(b):
29         """Return hex string (not bytes as with hexlify) representation of b."""
30         return b.hex()
31
32     def reraise(ex):
33         raise ex.with_traceback(sys.exc_info()[2])
34
35     def add_ex_tb(ex):
36         """Do nothing (already handled by Python 3 infrastructure)."""
37         return ex
38
39     def add_ex_ctx(ex, context_ex):
40         """Do nothing (already handled by Python 3 infrastructure)."""
41         return ex
42
43     class pending_raise:
44         """If rethrow is true, rethrow ex (if any), unless the body throws.
45
46         (Supports Python 2 compatibility.)
47
48         """
49         def __init__(self, ex, rethrow=True):
50             self.closed = False
51             self.ex = ex
52             self.rethrow = rethrow
53         def __enter__(self):
54             return None
55         def __exit__(self, exc_type, exc_value, traceback):
56             self.closed = True
57             if not exc_type and self.ex and self.rethrow:
58                 raise self.ex
59         def __del__(self):
60             assert self.closed
61
62     def items(x):
63         return x.items()
64
65     def argv_bytes(x):
66         """Return the original bytes passed to main() for an argv argument."""
67         return fsencode(x)
68
69     def bytes_from_uint(i):
70         return bytes((i,))
71
72     def bytes_from_byte(b):  # python > 2: b[3] returns ord('x'), not b'x'
73         return bytes((b,))
74
75     byte_int = lambda x: x
76
77     def buffer(object, offset=None, size=None):
78         if size:
79             assert offset is not None
80             return memoryview(object)[offset:offset + size]
81         if offset:
82             return memoryview(object)[offset:]
83         return memoryview(object)
84
85     def getcwd():
86         return fsencode(os.getcwd())
87
88 else:  # Python 2
89
90     from contextlib import contextmanager
91
92     ModuleNotFoundError = ImportError
93
94     def fsdecode(x):
95         return x
96
97     def fsencode(x):
98         return x
99
100     from pipes import quote
101     # pylint: disable=unused-import
102     from os import environ, getcwd
103
104     # pylint: disable=unused-import
105     from bup.py2raise import reraise
106
107     @contextmanager
108     def nullcontext(enter_result=None):
109         yield enter_result
110
111     # on py3 this causes errors, obviously
112     # pylint: disable=undefined-variable
113     input = raw_input
114     # pylint: disable=undefined-variable
115     range = xrange
116     # pylint: disable=undefined-variable
117     str_type = basestring
118     # pylint: disable=undefined-variable
119     int_types = (int, long)
120
121     hexstr = hexlify
122
123     def add_ex_tb(ex):
124         """Add a traceback to ex if it doesn't already have one.  Return ex.
125
126         """
127         if not getattr(ex, '__traceback__', None):
128             ex.__traceback__ = sys.exc_info()[2]
129         return ex
130
131     def add_ex_ctx(ex, context_ex):
132         """Make context_ex the __context__ of ex (unless it already has one).
133         Return ex.
134
135         """
136         if context_ex:
137             if not getattr(ex, '__context__', None):
138                 ex.__context__ = context_ex
139         return ex
140
141     class pending_raise:
142         """If rethrow is true, rethrow ex (if any), unless the body throws.
143
144         If the body does throw, make any provided ex the __context__
145         of the newer exception (assuming there's no existing
146         __context__).  Ensure the exceptions have __tracebacks__.
147         (Supports Python 2 compatibility.)
148
149         """
150         def __init__(self, ex, rethrow=True):
151             self.closed = False
152             self.ex = ex
153             self.rethrow = rethrow
154         def __enter__(self):
155             if self.ex:
156                 add_ex_tb(self.ex)
157         def __exit__(self, exc_type, exc_value, traceback):
158             self.closed = True
159             if exc_value:
160                 if self.ex:
161                     add_ex_tb(exc_value)
162                     add_ex_ctx(exc_value, self.ex)
163                 return
164             if self.rethrow and self.ex:
165                 raise self.ex
166         def __del__(self):
167             assert self.closed
168
169     def dump_traceback(ex):
170         stack = [ex]
171         next_ex = getattr(ex, '__context__', None)
172         while next_ex:
173             stack.append(next_ex)
174             next_ex = getattr(next_ex, '__context__', None)
175         stack = reversed(stack)
176         ex = next(stack)
177         tb = getattr(ex, '__traceback__', None)
178         print_exception(type(ex), ex, tb)
179         for ex in stack:
180             print('\nDuring handling of the above exception, another exception occurred:\n',
181                   file=sys.stderr)
182             tb = getattr(ex, '__traceback__', None)
183             print_exception(type(ex), ex, tb)
184
185     class ExitStack:
186         def __init__(self):
187             self.contexts = []
188
189         def __enter__(self):
190             return self
191
192         def __exit__(self, value_type, value, traceback):
193             init_value = value
194             for ctx in reversed(self.contexts):
195                 try:
196                     ctx.__exit__(value_type, value, traceback)
197                 except BaseException as ex:
198                     add_ex_tb(ex)
199                     if value:
200                         add_ex_ctx(ex, value)
201                     value_type = type(ex)
202                     value = ex
203                     traceback = ex.__traceback__
204             if value is not init_value:
205                 raise value
206
207         def enter_context(self, x):
208             self.contexts.append(x)
209
210     def items(x):
211         return x.iteritems()
212
213     def argv_bytes(x):
214         """Return the original bytes passed to main() for an argv argument."""
215         return x
216
217     bytes_from_uint = chr
218
219     def bytes_from_byte(b):
220         return b
221
222     byte_int = ord
223
224     buffer = buffer
225
226 try:
227     import bup_main
228 except ModuleNotFoundError:
229     bup_main = None
230
231 if bup_main:
232     def get_argvb():
233         "Return a new list containing the current process argv bytes."
234         return bup_main.argv()
235     if py3:
236         def get_argv():
237             "Return a new list containing the current process argv strings."
238             return [x.decode(errors='surrogateescape') for x in bup_main.argv()]
239     else:
240         def get_argv():
241             return bup_main.argv()
242 else:
243     def get_argvb():
244         raise Exception('get_argvb requires the bup_main module');
245     def get_argv():
246         raise Exception('get_argv requires the bup_main module');
247
248 def wrap_main(main):
249     """Run main() and raise a SystemExit with the return value if it
250     returns, pass along any SystemExit it raises, convert
251     KeyboardInterrupts into exit(130), and print a Python 3 style
252     contextual backtrace for other exceptions in both Python 2 and
253     3)."""
254     try:
255         sys.exit(main())
256     except KeyboardInterrupt as ex:
257         sys.exit(130)
258     except SystemExit as ex:
259         raise
260     except BaseException as ex:
261         if py3:
262             raise
263         add_ex_tb(ex)
264         dump_traceback(ex)
265         sys.exit(1)
266
267
268 # Excepting wrap_main() in the traceback, these should produce similar output:
269 #   python2 lib/bup/compat.py
270 #   python3 lib/bup/compat.py
271 # i.e.:
272 #   diff -u <(python2 lib/bup/compat.py 2>&1) <(python3 lib/bup/compat.py 2>&1)
273 #
274 # Though the python3 output for 'second' will include a stacktrace
275 # starting from wrap_main, rather than from outer().
276
277 if __name__ == '__main__':
278
279     def inner():
280         raise Exception('first')
281
282     def outer():
283         try:
284             inner()
285         except Exception as ex:
286             add_ex_tb(ex)
287             try:
288                 raise Exception('second')
289             except Exception as ex2:
290                 raise add_ex_ctx(add_ex_tb(ex2), ex)
291
292     wrap_main(outer)