1 /*******************************************************************************
2 
3     Epoll-based signal handlers.
4 
5     Note that the signals will be handled with a delay of up to single epoll
6     cycle. This is because the signal extension is synced with the
7     EpollSelectDispatcher. This makes it unsuitable to handle critical signals
8     (like `SIGABRT` or `SIGSEGV`) where the application shouldn't be allowed to
9     proceed in the general case; for these cases setup an asynchronous signal
10     handler using `sigaction` instead.
11 
12     Copyright:
13         Copyright (c) 2018 dunnhumby Germany GmbH.
14         All rights reserved.
15 
16     License:
17         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
18         Alternatively, this file may be distributed under the terms of the Tango
19         3-Clause BSD License (see LICENSE_BSD.txt for details).
20 
21 *******************************************************************************/
22 
23 module ocean.application.components.Signals;
24 
25 import ocean.meta.types.Qualifiers;
26 
27 /// ditto
28 public class Signals
29 {
30     import ocean.core.Verify;
31     import ocean.io.device.Conduit: ISelectable;
32     import ocean.io.device.Device;
33     import ocean.io.device.IODevice;
34     import ocean.io.select.client.model.ISelectClient;
35     import ocean.io.select.protocol.SelectReader;
36     import ocean.sys.Pipe;
37     import ocean.sys.SignalFD;
38 
39     import core.sys.posix.signal;
40 
41     /***************************************************************************
42 
43         SignalErrnoException.
44 
45     ***************************************************************************/
46 
47     alias SignalFD.SignalErrnoException SignalErrnoException;
48 
49     /***************************************************************************
50 
51         SelectReader instance used to read the data from pipe written by
52         signal handler.
53 
54     ***************************************************************************/
55 
56     private SelectReader reader;
57 
58     /***************************************************************************
59 
60         Associative array of old signal handlers, addressed by the signal
61         number.
62 
63     ***************************************************************************/
64 
65     private sigaction_t[int] old_signals;
66 
67     /***************************************************************************
68 
69         Helper class wrapping Device to InputDevice. Used to read from Pipe
70         via SelectReader.
71 
72     ***************************************************************************/
73 
74     private static class InputDeviceWrapper: InputDevice, ISelectable
75     {
76         /***********************************************************************
77 
78             Device instance to read from.
79 
80         ***********************************************************************/
81 
82         private Device device;
83 
84         /***********************************************************************
85 
86             Constructor.
87 
88             Params:
89                 device = device instance to read from.
90 
91         ***********************************************************************/
92 
93         public this (Device device)
94         {
95             this.device = device;
96         }
97 
98         /***********************************************************************
99 
100             Returns:
101                 file handle of the underlying device.
102 
103         ***********************************************************************/
104 
105         override Handle fileHandle()
106         {
107             return this.device.fileHandle();
108         }
109     }
110 
111     /***************************************************************************
112 
113         InputDevice reading data from the file.
114 
115     ***************************************************************************/
116 
117     private InputDeviceWrapper pipe_source;
118 
119     /***************************************************************************
120 
121         Pipe used to tranfser the data from the signal handler back to the
122         application. Static as used from the static signal handler
123 
124     ***************************************************************************/
125 
126     private static Pipe signal_pipe;
127 
128     /// Signal handler delegate type.
129     private alias void delegate ( int[] ) HandlerDg;
130 
131     /// Delegate to call when the signal handler fires in epoll.
132     private HandlerDg handler_dg;
133 
134     /***************************************************************************
135 
136         Signal handler. Needs to be static method as it is registered as
137         C callback.
138 
139         Params:
140             signum = signal being handled
141 
142     ***************************************************************************/
143 
144     private static extern(C) void signalHandler (int signum)
145     {
146         typeof(this).signal_pipe.sink.write(cast(ubyte[])(&signum)[0..1]);
147     }
148 
149     /***************************************************************************
150 
151         Static constructor. Initialises signal_pipe static member.
152 
153     ***************************************************************************/
154 
155     static this ( )
156     {
157         // Setup a pipe for transferring the signal info. Unbuffered,
158         // as we want these to be available as soon as possible.
159         typeof(this).signal_pipe = new Pipe(0);
160     }
161 
162     /***************************************************************************
163 
164         Constructor. Creates the internal signal event. The event (accessible
165         via the selectClient() method) must be registered with epoll.
166 
167         Params:
168             handler_dg = delegate to call when the signal handler fires in epoll
169 
170     ***************************************************************************/
171 
172     public this ( scope HandlerDg handler_dg )
173     {
174         verify(handler_dg !is null);
175         this.handler_dg = handler_dg;
176 
177         typeof(this).signal_pipe.source.setNonBlock();
178         this.pipe_source =
179             new InputDeviceWrapper(typeof(this).signal_pipe.source);
180         this.reader = new SelectReader(this.pipe_source, int.sizeof);
181 
182         // Make the intention to read 4 bytes (the signal number). This will
183         // read it from the pipe in one of the epoll cycles, after the data
184         // is written into the pipe from signal handler.
185         this.reader.read(&this.handleSignals);
186     }
187 
188     /***************************************************************************
189 
190         Ignores the specified signals.
191 
192         Params:
193             signals = list of signals to ignore
194 
195         Throws:
196             SignalErrnoException if setting up the signal fails
197 
198     ***************************************************************************/
199 
200     public void ignore ( in int[] signals )
201     {
202         this.installSignalHandlers(signals, SIG_IGN);
203     }
204 
205     /***************************************************************************
206 
207         Handles the specified signals.
208 
209         Params:
210             signals = list of signals to handle
211 
212         Throws:
213             SignalErrnoException if setting up the signal fails
214 
215     ***************************************************************************/
216 
217     public void handle ( in int[] signals )
218     {
219         this.installSignalHandlers(signals);
220     }
221 
222     /***************************************************************************
223 
224         Restores the original signal handlers.
225 
226         Throws:
227             SignalErrnoException if resetting up the signal fails
228 
229     ***************************************************************************/
230 
231     public void clear ( )
232     {
233         foreach (signal, sa; this.old_signals)
234         {
235             if (sigaction(signal, &sa, null) == -1)
236             {
237                 throw (new SignalErrnoException).useGlobalErrno("sigaction");
238             }
239         }
240     }
241 
242     /***************************************************************************
243 
244         Returns:
245             ISelectClient interface to register with epoll
246 
247     ***************************************************************************/
248 
249     public ISelectClient selectClient ( )
250     {
251         return this.reader;
252     }
253 
254     /***************************************************************************
255 
256         Signal handler delegate, called from epoll when a signal has fired. In
257         turn notifies all registered extensions about the signal.
258 
259         Params:
260             signals = info about signals which have fired
261 
262     ***************************************************************************/
263 
264     private void handleSignals ( void[] signals_read )
265     {
266         auto signals = cast(int[])signals_read;
267         this.handler_dg(signals);
268     }
269 
270     /***************************************************************************
271 
272         Installs the signal handlers.
273 
274         Params:
275             signals = list of signals to handle
276             signal_handler = signal handler to install
277 
278         Throws:
279             SignalErrnoException if setting up the signal fails
280 
281     ***************************************************************************/
282 
283     private void installSignalHandlers ( in int[] signals,
284             in typeof(sigaction_t.sa_handler) signal_handler = &this.signalHandler)
285     {
286         sigaction_t sa;
287 
288         sa.sa_handler = signal_handler;
289 
290         if (sigemptyset(&sa.sa_mask) == -1)
291         {
292             throw (new SignalErrnoException).useGlobalErrno("sigemptyset");
293         }
294 
295         foreach (signal; signals)
296         {
297             this.old_signals[signal] = sigaction_t.init;
298             sigaction_t* old_handler = signal in this.old_signals;
299             verify(old_handler !is null);
300 
301             if (sigaction(signal, &sa, old_handler) == -1)
302             {
303                 throw (new SignalErrnoException).useGlobalErrno("sigaction");
304             }
305         }
306     }
307 }