Line data Source code
1 : // Copyright (c) 2012-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Implementation of the Signal class.
22 : *
23 : * The Signal class listens for Unix signals to happen. This wakes us
24 : * up when the signal happens.
25 : */
26 :
27 : // self
28 : //
29 : #include "eventdispatcher/signal.h"
30 :
31 : #include "eventdispatcher/exception.h"
32 :
33 :
34 : // snaplogger
35 : //
36 : #include <snaplogger/message.h>
37 :
38 :
39 : // cppthread
40 : //
41 : #include <cppthread/thread.h>
42 :
43 :
44 : // C++
45 : //
46 : #include <iostream>
47 :
48 :
49 : // C
50 : //
51 : #include <string.h>
52 : #include <signal.h>
53 :
54 :
55 : // last include
56 : //
57 : #include <snapdev/poison.h>
58 :
59 :
60 :
61 :
62 : namespace ed
63 : {
64 :
65 :
66 : namespace
67 : {
68 :
69 :
70 :
71 : /** \brief The array of signals handled by signal objects.
72 : *
73 : * This map holds a list of signal handlers. You cannot register
74 : * the same signal more than once so this map is used to make
75 : * sure that each signal is unique.
76 : *
77 : * \todo
78 : * We may actually want to use a sigset_t object and just set
79 : * bits and remove
80 : *
81 : * \note
82 : * The pointer to the signal object is a bare pointer
83 : * for in part because we cannot use a smart pointer in
84 : * a constructor where we add the signal to this map. Also
85 : * at this time that pointer does not get used so it could
86 : * as well have been a boolean.
87 : *
88 : * \bug
89 : * Having a global list this means signal objects can't safely
90 : * be created before main() gets called.
91 : */
92 : sigset_t g_signal_handlers = sigset_t();
93 :
94 :
95 :
96 : }
97 : // no name namespace
98 :
99 :
100 :
101 :
102 : /** \brief Initializes the signal object.
103 : *
104 : * This function initializes the signal object with the specified
105 : * \p posix_signal which represents a POSIX signal such as SIGHUP,
106 : * SIGTERM, SIGUSR1, SIGUSR2, etc.
107 : *
108 : * The signal automatically gets masked out. This allows us to
109 : * unmask the signal only when we are ready to call ppoll() and
110 : * thus not have the signal break any of our normal user code.
111 : *
112 : * The ppoll() function unblocks all the signals that you listen
113 : * to (i.e. for each signal object you created.) The run()
114 : * loop ends up calling your process_signal() callback function.
115 : *
116 : * Note that the signal callback is called from the normal user
117 : * environment and not directly from the POSIX signal handler.
118 : * This means you can call any function from your callback.
119 : *
120 : * \note
121 : * IMPORTANT: Remember that POSIX signals stop your code at a 'breakable'
122 : * point which in many circumstances can create many problems unless
123 : * you make sure to mask signals while doing work. For example, you
124 : * could end up with a read() returning an error when the file you
125 : * are reading has absolutely no error but a dude decided to signal
126 : * you with a 'kill -HUP 123'...
127 : *
128 : * \code
129 : * {
130 : * // use an RAII masking mechanism
131 : * mask_posix_signal mask();
132 : *
133 : * // do your work (i.e. read/write/etc.)
134 : * ...
135 : * }
136 : * \endcode
137 : *
138 : * \par
139 : * The best way in our processes will be to block all signals except
140 : * while poll() is called (using ppoll() for the feat.)
141 : *
142 : * \note
143 : * By default the constructor masks the specified \p posix_signal and
144 : * it does not restore the signal on destruction. If you want the
145 : * signal to be unmasked on destruction (say to restore the default
146 : * functioning of the SIGINT signal,) then make sure to call the
147 : * unblock_signal() function right after you create your connection.
148 : *
149 : * \warning
150 : * The signal gets masked by this constructor. If you want to make
151 : * sure that most of your code does not get affected by said signal,
152 : * make sure to create your signal object early on or mask those
153 : * signals beforehand. Otherwise the signal could happen before it
154 : * gets masked. Initialization of your process may not require
155 : * protection anyway.
156 : *
157 : * \bug
158 : * You should not use signal() and setup a handler for the same signal.
159 : * It will not play nice to have both types of signal handlers. That
160 : * being said, with my current testing (as of Ubuntu 16.04), it seems
161 : * to work just fine..
162 : *
163 : * \bug
164 : * At the moment you can't create a signal() object if you already
165 : * started a thread. This is because the thread could end up being
166 : * the one accepting the signal and when that happens, it would most
167 : * certainly crash (i.e. the `sigprocmask()` only protects the current
168 : * thread and its spawns, not existing threads). Since you may not be
169 : * in control of other threads, this is really not easy to handle. One
170 : * possibility, though, would be to offer a "low level" function which
171 : * you can call near the beginning of your process (i.e. in main())
172 : * and call that function to "pre-block" the signals you're interested
173 : * in.
174 : *
175 : * \exception initialization_error
176 : * Create multiple signal() with the same posix_signal parameter
177 : * is not supported and this exception is raised whenever you attempt
178 : * to do that. Remember that you can have at most one communicator
179 : * object (hence the singleton.)
180 : *
181 : * \exception runtime_error
182 : * The signalfd() function is expected to create a "socket" (file
183 : * descriptor) listening for incoming signals. If it fails, this
184 : * exception is raised (which is very similar to other socket
185 : * based connections which throw whenever a connection cannot
186 : * be achieved.)
187 : *
188 : * \param[in] posix_signal The signal to be managed by this signal.
189 : */
190 1 : signal::signal(int posix_signal)
191 1 : : f_signal(posix_signal)
192 : {
193 1 : int const r(sigismember(&g_signal_handlers, f_signal));
194 1 : if(r != 0)
195 : {
196 0 : if(r == 1)
197 : {
198 : // this could be fixed, but probably not worth the trouble...
199 : //
200 0 : throw initialization_error("the same signal cannot be created more than once in your entire process.");
201 : }
202 :
203 : // f_signal is not considered valid by this OS
204 : //
205 0 : throw initialization_error("posix_signal (f_signal) is not a valid/recognized signal number.");
206 : }
207 :
208 1 : cppthread::process_ids_t const pids(cppthread::get_thread_ids());
209 1 : if(pids.size() != 1)
210 : {
211 0 : std::string const msg("an ed::signal object must be created before any threads or the signals will kill your process.");
212 0 : std::cerr << msg << std::endl;
213 0 : throw initialization_error(msg);
214 0 : }
215 :
216 : // create a mask for that signal
217 : //
218 1 : sigset_t set;
219 1 : sigemptyset(&set);
220 1 : sigaddset(&set, f_signal); // ignore returned error, we already know f_signal is valid
221 :
222 : // first we block the signal
223 : //
224 1 : if(sigprocmask(SIG_BLOCK, &set, nullptr) != 0)
225 : {
226 0 : throw runtime_error("sigprocmask() failed to block signal.");
227 : }
228 :
229 : // second we create a "socket" for the signal (really it is a file
230 : // descriptor manager by the kernel)
231 : //
232 1 : f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
233 1 : if(f_socket == -1)
234 : {
235 0 : sigprocmask(SIG_UNBLOCK, &set, nullptr);
236 :
237 0 : int const e(errno);
238 0 : std::string err("signalfd() failed to create a signal listener for signal ");
239 0 : err += std::to_string(f_signal);
240 0 : err += " (errno: ";
241 0 : err += std::to_string(e);
242 0 : err += " -- ";
243 0 : err += strerror(e);
244 0 : err += ").";
245 0 : SNAP_LOG_ERROR << err << SNAP_LOG_SEND;
246 0 : throw runtime_error(err);
247 0 : }
248 :
249 : // mark this signal as in use
250 : //
251 1 : sigaddset(&g_signal_handlers, f_signal); // ignore error, we already know f_signal is valid
252 2 : }
253 :
254 :
255 : /** \brief Restore the signal as it was before you created a signal.
256 : *
257 : * The destructor is expected to restore the signal to what it was
258 : * before you create this signal. Of course, if you created
259 : * other signal handlers in between, it will not work right since
260 : * this function will destroy your handler pointer.
261 : *
262 : * To do it right, it has to be done in order (i.e. set handler 1, set
263 : * handler 2, set handler 3, remove handler 3, remove handler 2, remove
264 : * handler 1.) We do not guarantee anything at this level!
265 : */
266 1 : signal::~signal()
267 : {
268 1 : close();
269 1 : }
270 :
271 :
272 : /** \brief Tell that this connection is listening on a Unix signal.
273 : *
274 : * The signal implements the signal listening feature. We use
275 : * a simple flag in the virtual table to avoid a more expansive
276 : * dynamic_cast<>() is a loop that goes over all the connections
277 : * you have defined.
278 : *
279 : * \return The base implementation returns false.
280 : */
281 23 : bool signal::is_signal() const
282 : {
283 23 : return true;
284 : }
285 :
286 :
287 : /** \brief Retrieve the "socket" of the signal object.
288 : *
289 : * Signal objects have a socket (file descriptor) assigned to them
290 : * using the signalfd() function.
291 : *
292 : * \note
293 : * You should not override this function since there is no other
294 : * value it can return.
295 : *
296 : * \return The signal socket to listen on with poll().
297 : */
298 36 : int signal::get_socket() const
299 : {
300 36 : return f_socket;
301 : }
302 :
303 :
304 : /** \brief Retrieve the PID of the child process that just emitted SIGCHLD.
305 : *
306 : * This function returns the process identifier (pid_t) of the child that
307 : * just sent us a SIGCHLD Unix signal.
308 : *
309 : * \exception runtime_error
310 : * This exception is raised if the function gets called before any signal
311 : * ever occurred.
312 : *
313 : * \return The process identifier (pid_t) of the child that died.
314 : */
315 0 : pid_t signal::get_child_pid() const
316 : {
317 0 : if(f_signal_info.ssi_signo == 0)
318 : {
319 0 : throw runtime_error("signal::get_child_pid() called before any signal ever occurred.");
320 : }
321 :
322 0 : return f_signal_info.ssi_pid;
323 : }
324 :
325 :
326 : /** \brief Get a copy of the current signal.
327 : *
328 : * Whenever we read a signal, the data is saved in the internal f_signal_info
329 : * structure. You can access that structure and its content using this
330 : * function.
331 : *
332 : * The structure is return as read-only. You should not modify it. If you
333 : * need to do so, make a copy.
334 : *
335 : * The structure remains valid until your process_signal() function returns.
336 : * If you need the info after, make sure to make a copy.
337 : *
338 : * \return A direct pointer to the signal info in the object.
339 : */
340 0 : signalfd_siginfo const * signal::get_signal_info() const
341 : {
342 0 : return &f_signal_info;
343 : }
344 :
345 :
346 : /** \brief Processes this signal.
347 : *
348 : * This function reads the signal "socket" for all the signal received
349 : * so far.
350 : *
351 : * For each instance found in the signal queue, the process_signal() gets
352 : * called.
353 : */
354 8 : void signal::process()
355 : {
356 : // loop any number of times as required
357 : // (or can we receive a maximum of 1 such signal at a time?)
358 : //
359 16 : while(f_socket != -1)
360 : {
361 16 : int const r(read(f_socket, &f_signal_info, sizeof(f_signal_info)));
362 16 : if(r == sizeof(f_signal_info))
363 : {
364 8 : process_signal();
365 : }
366 : else
367 : {
368 8 : if(r == -1)
369 : {
370 : // if EAGAIN then we are done as expected, any other error
371 : // is logged
372 : //
373 8 : if(errno != EAGAIN)
374 : {
375 0 : int const e(errno);
376 0 : SNAP_LOG_ERROR
377 0 : << "an error occurred while reading from the signalfd() file descriptor. (errno: "
378 : << e
379 : << " -- "
380 0 : << strerror(e)
381 : << ")."
382 : << SNAP_LOG_SEND;
383 : }
384 : }
385 : else
386 : {
387 : // what to do? what to do?
388 0 : SNAP_LOG_ERROR
389 0 : << "reading from the signalfd() file descriptor did not return the expected size. (got "
390 : << r
391 0 : << ", expected "
392 0 : << sizeof(f_signal_info)
393 : << ")"
394 : << SNAP_LOG_SEND;
395 : }
396 8 : break;
397 : }
398 : }
399 8 : }
400 :
401 :
402 : /** \brief Close the signal file descriptor.
403 : *
404 : * This function closes the file descriptor and, if you called the
405 : * unblock_signal_on_destruction() function, it also restores the
406 : * signal (unblocks it.)
407 : *
408 : * After this call, the connection is pretty much useless (although
409 : * you could still use it as a timer.) You cannot reopen the signal
410 : * file descriptor once closed. Instead, you have to create a new
411 : * connection.
412 : */
413 1 : void signal::close()
414 : {
415 1 : if(f_socket != -1)
416 : {
417 1 : ::close(f_socket);
418 1 : f_socket = -1;
419 :
420 1 : sigdelset(&g_signal_handlers, f_signal); // ignore error, we already know f_signal is valid
421 :
422 1 : if(f_unblock)
423 : {
424 : // also unblock the signal
425 : //
426 0 : sigset_t set;
427 0 : sigemptyset(&set);
428 0 : sigaddset(&set, f_signal); // ignore error, we already know f_signal is valid
429 0 : if(sigprocmask(SIG_UNBLOCK, &set, nullptr) != 0)
430 : {
431 : // we cannot throw in a destructor and in most cases this
432 : // happens in the destructor...
433 : //throw runtime_error("sigprocmask() failed to block signal.");
434 :
435 0 : int const e(errno);
436 0 : SNAP_LOG_FATAL
437 0 : << "an error occurred while unblocking signal "
438 : << f_signal
439 0 : << " with sigprocmask(). (errno: "
440 : << e
441 : << " -- "
442 0 : << strerror(e)
443 : << SNAP_LOG_SEND;
444 0 : std::cerr << "signal::close(): sigprocmask() failed to unblock signal." << std::endl;
445 :
446 0 : std::terminate();
447 : }
448 : }
449 : }
450 1 : }
451 :
452 :
453 : /** \brief Unmask a signal that was part of a connection.
454 : *
455 : * If you remove a signal connection, you may want to restore
456 : * the mask functionality. By default the signal gets masked but
457 : * it does not get unmasked.
458 : *
459 : * By calling this function just after creation, the signal gets restored
460 : * (unblocked) whenever the signal object gets destroyed.
461 : */
462 0 : void signal::unblock_signal_on_destruction()
463 : {
464 0 : f_unblock = true;
465 0 : }
466 :
467 :
468 :
469 : } // namespace ed
470 : // vim: ts=4 sw=4 et
|