1# Copyright (c) 2010, 2011 Nicira, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at:
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import atexit
16import os
17import signal
18import sys
19
20import ovs.vlog
21
22_hooks = []
23vlog = ovs.vlog.Vlog("fatal-signal")
24
25
26def add_hook(hook, cancel, run_at_exit):
27    _init()
28    _hooks.append((hook, cancel, run_at_exit))
29
30
31def fork():
32    """Clears all of the fatal signal hooks without executing them.  If any of
33    the hooks passed a 'cancel' function to add_hook(), then those functions
34    will be called, allowing them to free resources, etc.
35
36    Following a fork, one of the resulting processes can call this function to
37    allow it to terminate without calling the hooks registered before calling
38    this function.  New hooks registered after calling this function will take
39    effect normally."""
40    global _hooks
41    for hook, cancel, run_at_exit in _hooks:
42        if cancel:
43            cancel()
44
45    _hooks = []
46
47
48_added_hook = False
49_files = {}
50
51
52def add_file_to_unlink(file):
53    """Registers 'file' to be unlinked when the program terminates via
54    sys.exit() or a fatal signal."""
55    global _added_hook
56    if not _added_hook:
57        _added_hook = True
58        add_hook(_unlink_files, _cancel_files, True)
59    _files[file] = None
60
61
62def add_file_to_close_and_unlink(file, fd=None):
63    """Registers 'file' to be unlinked when the program terminates via
64    sys.exit() or a fatal signal and the 'fd' to be closed. On Windows a file
65    cannot be removed while it is open for writing."""
66    global _added_hook
67    if not _added_hook:
68        _added_hook = True
69        add_hook(_unlink_files, _cancel_files, True)
70    _files[file] = fd
71
72
73def remove_file_to_unlink(file):
74    """Unregisters 'file' from being unlinked when the program terminates via
75    sys.exit() or a fatal signal."""
76    if file in _files:
77        del _files[file]
78
79
80def unlink_file_now(file):
81    """Like fatal_signal_remove_file_to_unlink(), but also unlinks 'file'.
82    Returns 0 if successful, otherwise a positive errno value."""
83    error = _unlink(file)
84    if error:
85        vlog.warn("could not unlink \"%s\" (%s)" % (file, os.strerror(error)))
86    remove_file_to_unlink(file)
87    return error
88
89
90def _unlink_files():
91    for file_ in _files:
92        if sys.platform == "win32" and _files[file_]:
93            _files[file_].close()
94        _unlink(file_)
95
96
97def _cancel_files():
98    global _added_hook
99    global _files
100    _added_hook = False
101    _files = {}
102
103
104def _unlink(file_):
105    try:
106        os.unlink(file_)
107        return 0
108    except OSError as e:
109        return e.errno
110
111
112def _signal_handler(signr, _):
113    _call_hooks(signr)
114
115    # Re-raise the signal with the default handling so that the program
116    # termination status reflects that we were killed by this signal.
117    signal.signal(signr, signal.SIG_DFL)
118    os.kill(os.getpid(), signr)
119
120
121def _atexit_handler():
122    _call_hooks(0)
123
124
125recurse = False
126
127
128def _call_hooks(signr):
129    global recurse
130    if recurse:
131        return
132    recurse = True
133
134    for hook, cancel, run_at_exit in _hooks:
135        if signr != 0 or run_at_exit:
136            hook()
137
138
139_inited = False
140
141
142def _init():
143    global _inited
144    if not _inited:
145        _inited = True
146        if sys.platform == "win32":
147            signals = [signal.SIGTERM, signal.SIGINT]
148        else:
149            signals = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP,
150                       signal.SIGALRM]
151
152        for signr in signals:
153            if signal.getsignal(signr) == signal.SIG_DFL:
154                signal.signal(signr, _signal_handler)
155        atexit.register(_atexit_handler)
156
157
158def signal_alarm(timeout):
159    if sys.platform == "win32":
160        import os
161        import time
162        import threading
163
164        class Alarm (threading.Thread):
165            def __init__(self, timeout):
166                super(Alarm, self).__init__()
167                self.timeout = timeout
168                self.setDaemon(True)
169
170            def run(self):
171                time.sleep(self.timeout)
172                os._exit(1)
173
174        alarm = Alarm(timeout)
175        alarm.start()
176    else:
177        signal.alarm(timeout)
178