Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

"""Registers functions to be called if an exception or signal occurs.""" 

import functools 

import logging 

import os 

import signal 

import traceback 

 

# pylint: disable=unused-import, no-name-in-module 

from acme.magic_typing import Any, Callable, Dict, List, Union 

# pylint: enable=unused-import, no-name-in-module 

 

from certbot import errors 

 

logger = logging.getLogger(__name__) 

 

 

# _SIGNALS stores the signals that will be handled by the ErrorHandler. These 

# signals were chosen as their default handler terminates the process and could 

# potentially occur from inside Python. Signals such as SIGILL were not 

# included as they could be a sign of something devious and we should terminate 

# immediately. 

_SIGNALS = [signal.SIGTERM] 

if os.name != "nt": 

for signal_code in [signal.SIGHUP, signal.SIGQUIT, 

signal.SIGXCPU, signal.SIGXFSZ]: 

# Adding only those signals that their default action is not Ignore. 

# This is platform-dependent, so we check it dynamically. 

if signal.getsignal(signal_code) != signal.SIG_IGN: 

_SIGNALS.append(signal_code) 

 

class ErrorHandler(object): 

"""Context manager for running code that must be cleaned up on failure. 

 

The context manager allows you to register functions that will be called 

when an exception (excluding SystemExit) or signal is encountered. 

Usage:: 

 

handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs) 

handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs) 

 

with handler: 

do_something() 

 

Or for one cleanup function:: 

 

with ErrorHandler(func, args, kwargs): 

do_something() 

 

If an exception is raised out of do_something, the cleanup functions will 

be called in last in first out order. Then the exception is raised. 

Similarly, if a signal is encountered, the cleanup functions are called 

followed by the previously received signal handler. 

 

Each registered cleanup function is called exactly once. If a registered 

function raises an exception, it is logged and the next function is called. 

Signals received while the registered functions are executing are 

deferred until they finish. 

 

""" 

def __init__(self, func=None, *args, **kwargs): 

self.call_on_regular_exit = False 

self.body_executed = False 

self.funcs = [] # type: List[Callable[[], Any]] 

self.prev_handlers = {} # type: Dict[int, Union[int, None, Callable]] 

self.received_signals = [] # type: List[int] 

if func is not None: 

self.register(func, *args, **kwargs) 

 

def __enter__(self): 

self.body_executed = False 

self._set_signal_handlers() 

 

def __exit__(self, exec_type, exec_value, trace): 

self.body_executed = True 

retval = False 

# SystemExit is ignored to properly handle forks that don't exec 

if exec_type is SystemExit: 

return retval 

elif exec_type is None: 

if not self.call_on_regular_exit: 

return retval 

elif exec_type is errors.SignalExit: 

logger.debug("Encountered signals: %s", self.received_signals) 

retval = True 

else: 

logger.debug("Encountered exception:\n%s", "".join( 

traceback.format_exception(exec_type, exec_value, trace))) 

 

self._call_registered() 

self._reset_signal_handlers() 

self._call_signals() 

return retval 

 

def register(self, func, *args, **kwargs): 

# type: (Callable, *Any, **Any) -> None 

"""Sets func to be run with the given arguments during cleanup. 

 

:param function func: function to be called in case of an error 

 

""" 

self.funcs.append(functools.partial(func, *args, **kwargs)) 

 

def _call_registered(self): 

"""Calls all registered functions""" 

logger.debug("Calling registered functions") 

while self.funcs: 

try: 

self.funcs[-1]() 

except Exception: # pylint: disable=broad-except 

logger.error("Encountered exception during recovery: ", exc_info=True) 

self.funcs.pop() 

 

def _set_signal_handlers(self): 

"""Sets signal handlers for signals in _SIGNALS.""" 

for signum in _SIGNALS: 

prev_handler = signal.getsignal(signum) 

# If prev_handler is None, the handler was set outside of Python 

if prev_handler is not None: 

self.prev_handlers[signum] = prev_handler 

signal.signal(signum, self._signal_handler) 

 

def _reset_signal_handlers(self): 

"""Resets signal handlers for signals in _SIGNALS.""" 

for signum in self.prev_handlers: 

signal.signal(signum, self.prev_handlers[signum]) 

self.prev_handlers.clear() 

 

def _signal_handler(self, signum, unused_frame): 

"""Replacement function for handling received signals. 

 

Store the received signal. If we are executing the code block in 

the body of the context manager, stop by raising signal exit. 

 

:param int signum: number of current signal 

 

""" 

self.received_signals.append(signum) 

if not self.body_executed: 

raise errors.SignalExit 

 

def _call_signals(self): 

"""Finally call the deferred signals.""" 

for signum in self.received_signals: 

logger.debug("Calling signal %s", signum) 

os.kill(os.getpid(), signum) 

 

class ExitHandler(ErrorHandler): 

"""Context manager for running code that must be cleaned up. 

 

Subclass of ErrorHandler, with the same usage and parameters. 

In addition to cleaning up on all signals, also cleans up on 

regular exit. 

""" 

def __init__(self, func=None, *args, **kwargs): 

ErrorHandler.__init__(self, func, *args, **kwargs) 

self.call_on_regular_exit = True