Line data Source code
1 : // Copyright (c) 2012-2021 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 Handler class.
22 : *
23 : * The Signal Handler class is used to make sure that we get a log entry in
24 : * case a terminal signal happens. We can also use this class to ignore
25 : * certain signals and get callbacks called. In many cases, our services want
26 : * to do that with many signals and that code is pretty much always the same.
27 : *
28 : * The best is to add the signal_handler to your main() function like
29 : * so:
30 : *
31 : * \code
32 : * #include <eventdispatcher/signal_handler.h>
33 : *
34 : * int main(int argc, char * argv[])
35 : * {
36 : * ed::signal_handler::create_instance();
37 : * ...
38 : * }
39 : * \endcode
40 : *
41 : * This is sufficient to get all the events of interest captured and
42 : * reported with a stack trace in your logs (the eventdispatcher makes
43 : * use of the snaplogger for that purpose). Note that the function returns
44 : * a pointer to the signal_handler object, so you can save that pointer
45 : * and make tweaks immediately after (see examples of tweaks below).
46 : *
47 : * In sub-functions, you may tweak the setup by doing various calls such as:
48 : *
49 : * \code
50 : * ed::signal_handler::get_instance()->add_terminal_signals(ed::signal_handler::SIGNAL_CHILD);
51 : * \endcode
52 : *
53 : * \note
54 : * You can also add that terminal signal to the mask of the create_handler()
55 : * function. For example, you may want to add SIGNAL_TERMINATE (SIGTERM),
56 : * SIGNAL_INTERRUPT (Ctrl-C) and SIGNAL_QUIT to the list of terminal signals.
57 : * At the same time, those are expected termination signals but if you have
58 : * a TCP controller connection and can send a QUIT message, then that message
59 : * should be used and receiving those additional signals could be viewed as
60 : * an unexpected event. For that reason, we have the EXTENDED_SIGNAL_TERMINAL
61 : * which includes those additional three signals.
62 : *
63 : * Now if one of your process children dies, you will die too.
64 : *
65 : * Also, if you have an object that deals with pipes or sockets and you do
66 : * not want to receive the SIGPIPE, you can do:
67 : *
68 : * \code
69 : * ed::signal_handler::get_instance()->add_ignored_signals(ed::signal_handler::SIGNAL_PIPE);
70 : * \endcode
71 : *
72 : * \note
73 : * You can also add that ignored signal to the mask of the create_handler()
74 : * function.
75 : *
76 : * Finally, you may be interested to capture a signal such as the SIGUSR1
77 : * signal. You do that by first adding the signal as a terminal signal and
78 : * then by adding a callback which will return true (i.e. signal handled).
79 : *
80 : * \note
81 : * You may want to consider using a signal connection object instead
82 : * of a callback for such a flag. You can wait on those signals with a
83 : * poll() and you avoid the EINTR errors which are so difficult to deal
84 : * with in a very large piece of software (see eventdispatcher/signal.h).
85 : *
86 : * \code
87 : * bool handle_usr1(
88 : * callback_id_t callback_id
89 : * , int callback_sig
90 : * , siginfo_t const * info
91 : * , ucontext_t const * ucontext)
92 : * {
93 : * ...handle USR1 signal...
94 : * return true;
95 : * }
96 : *
97 : * ...
98 : * signal_handler::pointer_t sh(ed::signal_handler::get_instance());
99 : * sh->add_terminal_signals(ed::signal_handler::SIGNAL_USR1);
100 : * sh->add_callback(1, ed::signal_handler::SIGNAL_USR1, &handle_usr1);
101 : * ...
102 : * \endcode
103 : *
104 : * \note
105 : * This class is thread safe. It will lock its own mutex before running
106 : * functions dealing with this class parameters. However, this may also
107 : * results in a deadlock whenever a signal occurs.
108 : */
109 :
110 : // self
111 : //
112 : #include "eventdispatcher/signal_handler.h"
113 :
114 : #include "eventdispatcher/exception.h"
115 :
116 :
117 : // snaplogger lib
118 : //
119 : #include <snaplogger/message.h>
120 :
121 :
122 : // cppthread lib
123 : //
124 : #include <cppthread/mutex.h>
125 : #include <cppthread/guard.h>
126 :
127 :
128 : // snapdev lib
129 : //
130 : #include <snapdev/not_reached.h>
131 : #include <snapdev/not_used.h>
132 :
133 :
134 : // C++ lib
135 : //
136 : #include <iostream>
137 :
138 :
139 : // C lib
140 : //
141 : #include <string.h>
142 : #include <signal.h>
143 :
144 :
145 : // last include
146 : //
147 : #include <snapdev/poison.h>
148 :
149 :
150 :
151 :
152 : namespace ed
153 : {
154 :
155 :
156 : namespace
157 : {
158 :
159 :
160 : /** \brief Signal number to signal name table.
161 : *
162 : * This table is used to transform a signal number in a name.
163 : *
164 : * \sa signal_handler::get_signal_name()
165 : */
166 : #pragma GCC diagnostic push
167 : #pragma GCC diagnostic ignored "-Wpedantic"
168 : constexpr char const * g_signal_names[64] =
169 : {
170 : [0] = nullptr,
171 : [SIGHUP] = "SIGHUP",
172 : [SIGINT] = "SIGINT",
173 : [SIGQUIT] = "SIGQUIT",
174 : [SIGILL] = "SIGILL",
175 : [SIGTRAP] = "SIGTRAP",
176 : [SIGABRT] = "SIGABRT",
177 : [SIGBUS] = "SIGBUS",
178 : [SIGFPE] = "SIGFPE",
179 : [SIGKILL] = "SIGKILL",
180 : [SIGUSR1] = "SIGUSR1",
181 : [SIGSEGV] = "SIGSEGV",
182 : [SIGUSR2] = "SIGUSR2",
183 : [SIGPIPE] = "SIGPIPE",
184 : [SIGALRM] = "SIGALRM",
185 : [SIGTERM] = "SIGTERM",
186 : [SIGSTKFLT] = "SIGSTKFLT",
187 : [SIGCHLD] = "SIGCHLD",
188 : [SIGCONT] = "SIGCONT",
189 : [SIGSTOP] = "SIGSTOP",
190 : [SIGTSTP] = "SIGTSTP",
191 : [SIGTTIN] = "SIGTTIN",
192 : [SIGTTOU] = "SIGTTOU",
193 : [SIGURG] = "SIGURG",
194 : [SIGXCPU] = "SIGXCPU",
195 : [SIGXFSZ] = "SIGXFSZ",
196 : [SIGVTALRM] = "SIGVTALRM",
197 : [SIGPROF] = "SIGPROF",
198 : [SIGWINCH] = "SIGWINCH",
199 : [SIGPOLL] = "SIGPOLL",
200 : [SIGPWR] = "SIGPWR",
201 : [SIGSYS] = "SIGSYS",
202 : };
203 : #pragma GCC diagnostic pop
204 :
205 :
206 : /** \brief Set of signals for which we want to log a stack trace.
207 : *
208 : * Just knowing where a signal occurred is often a bit limited. Knowing
209 : * the call stack for 10 to 20 items is much more helpful. However, for
210 : * some signals, it's generally totally useless so we use a mask to know
211 : * which signals to log the stack trace for.
212 : */
213 : signal_handler::signal_mask_t g_show_stack = signal_handler::DEFAULT_SHOW_STACK;
214 :
215 :
216 : /** \brief The allocated signal_handler instance.
217 : *
218 : * The get_instance() allocates this handler.
219 : *
220 : * \warning
221 : * If you want to call the create_instance() function, then it has to be
222 : * called before get_instance().
223 : */
224 2 : signal_handler::pointer_t g_signal_handler = signal_handler::pointer_t();
225 :
226 :
227 :
228 : }
229 : // no name namespace
230 :
231 :
232 :
233 :
234 : /** \brief Initialize the signal handler class.
235 : *
236 : * This function sets all the signal action structures to a nullptr
237 : * and then it sets up the \p terminal and \p ignored signals as
238 : * defined by the corresponding masks.
239 : *
240 : * You add to the set of signals that are terminal and ignored
241 : * later with the add_terminal_signals() and the add_ignored_signals().
242 : *
243 : * You can remove from the set of signals that are terminal or ignored
244 : * by calling the remove_signals() function.
245 : *
246 : * This function is private. It gets called by the get_instance() function.
247 : * You may also want to use the create_instance() function the first time
248 : * you create an instance.
249 : */
250 0 : signal_handler::signal_handler()
251 : {
252 0 : }
253 :
254 :
255 : /** \brief Restore the signals.
256 : *
257 : * The signal handler destructor restores all the signals that it changed.
258 : *
259 : * \note
260 : * At this point, the destructor is never called since we use an instance
261 : * and we do not give a way to destroy it. Unloading the library would have
262 : * that effect, but that generally doesn't happen.
263 : */
264 0 : signal_handler::~signal_handler()
265 : {
266 0 : remove_all_signals();
267 0 : g_signal_handler = nullptr;
268 0 : }
269 :
270 :
271 : /** \brief Handy function used to create the signal handler instance.
272 : *
273 : * In many cases, you want to create the signal handler and then setup
274 : * the terminal signal, the ignored signals, and a callback. This function
275 : * does all of that for you in one go.
276 : *
277 : * \code
278 : * int main(int argc, char * argv[])
279 : * {
280 : * ed::signal_handler::create_instance(
281 : * ed::signal_mask_t terminal = DEFAULT_SIGNAL_TERMINAL
282 : * , ed::signal_mask_t ignored = DEFAULT_SIGNAL_IGNORE
283 : * , SIGFPE
284 : * , handle_floating_point_errors);
285 : * ...
286 : * }
287 : * \endcode
288 : *
289 : * This function automatically calls the add_terminal_signals()
290 : * with the specified \p terminal parameter.
291 : *
292 : * It then calls the add_ignored_signals with the \p ignored parameter.
293 : *
294 : * If the \p sig parameter is set to a non-zero value then the
295 : * add_callback() gets called. In that case, the \p callback must
296 : * be properly defined.
297 : *
298 : * \param[in] terminal Mask with the set of terminal signals.
299 : * \param[in] ignored Mask with set of signals to be ignored.
300 : * \param[in] callback_id An identifier to attach the callback with.
301 : * \param[in] callback_sig Signal for which you want a callback.
302 : * \param[in] callback The callback function to call.
303 : */
304 0 : signal_handler::pointer_t signal_handler::create_instance(
305 : signal_mask_t terminal
306 : , signal_mask_t ignored
307 : , id_t callback_id
308 : , int callback_sig
309 : , callback_t callback)
310 : {
311 0 : cppthread::guard g(*cppthread::g_system_mutex);
312 :
313 0 : if(g_signal_handler != nullptr)
314 : {
315 0 : throw std::runtime_error("signal_handler::create_instance() must be called once before signal_handler::get_instance() ever gets called.");
316 : }
317 :
318 0 : pointer_t handler(get_instance());
319 :
320 0 : handler->add_terminal_signals(terminal);
321 0 : handler->add_ignored_signals(ignored);
322 :
323 0 : if(callback_sig != 0)
324 : {
325 0 : handler->add_callback(callback_id, callback_sig, callback);
326 : }
327 :
328 0 : return handler;
329 : }
330 :
331 :
332 : /** \brief Returns the signal handler instance.
333 : *
334 : * This function creates an instance of the signal handler and returns
335 : * the pointer. The very first time, though, you probably want to call
336 : * the create_instance() function so as to automatically initialize the
337 : * class. You can also reprogram your own initialization.
338 : * This function can be called any number of times.
339 : *
340 : * \warning
341 : * If you have threads, make sure to call this function at least once
342 : * before you create a thread since it is not otherwise thread safe.
343 : * Actually, the whole class is not considered thread safe so you should
344 : * create and initialize it.
345 : *
346 : * \return A pointer to the signal handler.
347 : */
348 0 : signal_handler::pointer_t signal_handler::get_instance()
349 : {
350 0 : cppthread::guard g(*cppthread::g_system_mutex);
351 :
352 0 : if(g_signal_handler == nullptr)
353 : {
354 0 : g_signal_handler.reset(new signal_handler());
355 : }
356 0 : return g_signal_handler;
357 : }
358 :
359 :
360 : /** \brief Add a callback to the signal handler.
361 : *
362 : * This function adds a callback to the signal_handler object. Callbacks
363 : * get called whenever the specified \p sig is received.
364 : *
365 : * You can add any number of callbacks per signal.
366 : *
367 : * The \p id parameter is a number you define. It is useful really only
368 : * if you add the same callback multiple times with different identifiers
369 : * and in case you want to be able to call the remove_callback() function.
370 : * You can always use `0` in all other cases.
371 : *
372 : * If you set the \p sig parameter to 0, then it will match all the
373 : * signals received. In other words, that callback will be called whatever
374 : * the received signal is (i.e. _match any_).
375 : *
376 : * \param[in] id The callback identifier.
377 : * \param[in] sig The signal (i.e. SIGPIPE) to assign a callback to.
378 : * \param[in] callback The user callback to call on \p sig signal.
379 : */
380 0 : void signal_handler::add_callback(callback_id_t id, int sig, callback_t callback)
381 : {
382 0 : if(static_cast<std::size_t>(sig) >= sizeof(f_signal_actions) / sizeof(f_signal_actions[0]))
383 : {
384 : throw std::runtime_error(
385 : "signal_handler::add_callback() called with invalid signal number "
386 0 : + std::to_string(sig));
387 : }
388 :
389 0 : if(callback == nullptr)
390 : {
391 0 : throw std::runtime_error("signal_handler::add_callback() called with nullptr as the callback.");
392 : }
393 :
394 0 : cppthread::guard g(f_mutex);
395 :
396 0 : f_callbacks.push_back(signal_callback_t{id, sig, callback});
397 0 : }
398 :
399 :
400 : /** \brief Remove a user callback.
401 : *
402 : * This function searches for the specified callback using its identifier
403 : * and removes it from the list of callbacks of the signal_handler object.
404 : *
405 : * If more than one callback is assigned the same identifier, then all
406 : * those callbacks are removed at once.
407 : *
408 : * \note
409 : * To be able to remove your callback, you must keep a reference to it.
410 : * So if you use std::bind() to call add_callback(), you need to keep
411 : * a reference to that std::bind().
412 : *
413 : * \param[in] id The callback identifier.
414 : */
415 0 : void signal_handler::remove_callback(id_t id)
416 : {
417 0 : cppthread::guard g(f_mutex);
418 :
419 0 : for(auto it(f_callbacks.begin()); it != f_callbacks.end();)
420 : {
421 0 : if(it->f_id == id)
422 : {
423 0 : it = f_callbacks.erase(it);
424 : }
425 : else
426 : {
427 0 : ++it;
428 : }
429 : }
430 0 : }
431 :
432 :
433 : /** \brief Set signals that generate a stack trace.
434 : *
435 : * Whenever a signal happens, this class can automatically log a stack
436 : * trace of location of the event. By default the mask is set to
437 : * DEFAULT_SHOW_STACK.
438 : *
439 : * You may add signals to the list by doing:
440 : *
441 : * \code
442 : * set_show_stack(get_show_stack() | ed::signal_handler::SIGNAL_TRAP);
443 : * \endcode
444 : *
445 : * \param[in] sigs The mask of signals which will generate a stack trace.
446 : */
447 0 : void signal_handler::set_show_stack(signal_mask_t sigs)
448 : {
449 0 : cppthread::guard g(f_mutex);
450 :
451 0 : g_show_stack = sigs;
452 0 : }
453 :
454 :
455 : /** \brief Get list of signals that generate a stack trace.
456 : *
457 : * This function retrieves the current list of signal that request the
458 : * class to generate a stack trace when they happen.
459 : *
460 : * It can be used with the set_show_stack() function in order to add or
461 : * remove some signals from that list.
462 : *
463 : * \return The mask of signals that generate a stack trace.
464 : */
465 0 : signal_handler::signal_mask_t signal_handler::get_show_stack() const
466 : {
467 0 : cppthread::guard g(f_mutex);
468 :
469 0 : return g_show_stack;
470 : }
471 :
472 :
473 : /** \brief Add signals that terminate the process.
474 : *
475 : * Any signal that you consider terminal should be added using this function.
476 : * Whenever that signal is raised by the system, the process_signal() function
477 : * gets called. If the corresponding bit is set in the show stack mask, then
478 : * the function first sends the stack trace to the logs, then it terminates
479 : * the process with a log specifying which signal terminated the process.
480 : *
481 : * \note
482 : * Some signals can't be caught (i.e. SIGKILL). It is useless to add those
483 : * to this list.
484 : *
485 : * \param[in] sigs The mask of signals that are expected to terminate your
486 : * process.
487 : */
488 0 : void signal_handler::add_terminal_signals(signal_mask_t sigs)
489 : {
490 0 : cppthread::guard g(f_mutex);
491 :
492 0 : for(size_t i = 0; i < sizeof(f_signal_actions) / sizeof(f_signal_actions[0]); ++i)
493 : {
494 0 : if((sigs & (1L << i)) != 0 && f_signal_actions[i] == nullptr)
495 : {
496 0 : sigaction_t action = sigaction_t();
497 0 : action.sa_sigaction = signal_handler_func;
498 0 : action.sa_flags = SA_SIGINFO;
499 :
500 0 : f_signal_actions[i] = std::make_shared<sigaction_t>();
501 0 : sigaction(i, &action, f_signal_actions[i].get());
502 : }
503 : }
504 0 : }
505 :
506 :
507 : /** \brief The class allows you to ignore some signals.
508 : *
509 : * This function allows you to add a list of signals you want to ignore.
510 : * For example, it is often that you want to ignore SIGPIPE signals when
511 : * you deal with sockets, otherwise, reading or writing to a closed socket
512 : * generates that signal instead of just returns a -1.
513 : *
514 : * \note
515 : * Trying to ignore signals such as SIGSEGV and SIGBUS is not a good idea.
516 : *
517 : * \param[in] sigs The mask of signals you want to ignore.
518 : */
519 0 : void signal_handler::add_ignored_signals(signal_mask_t sigs)
520 : {
521 0 : cppthread::guard g(f_mutex);
522 :
523 0 : for(size_t i = 0; i < sizeof(f_signal_actions) / sizeof(f_signal_actions[0]); ++i)
524 : {
525 0 : if((sigs & (1L << i)) != 0 && f_signal_actions[i] == nullptr)
526 : {
527 0 : sigaction_t action = sigaction_t();
528 0 : action.sa_handler = SIG_IGN;
529 0 : action.sa_sigaction = signal_handler_func;
530 :
531 0 : f_signal_actions[i] = std::make_shared<sigaction_t>();
532 0 : sigaction(i, &action, f_signal_actions[i].get());
533 : }
534 : }
535 0 : }
536 :
537 :
538 : /** \brief Remove a terminal or ignored signal.
539 : *
540 : * This function removes the callback for the specified signals. The function
541 : * has no effect is you did not first add the signal with one of the
542 : * add_terminal_signals() or add_ignored_signals() functions.
543 : *
544 : * \param[in] sigs The mask of signals to remove from the list of signals
545 : * we manage through the signal handler.
546 : */
547 0 : void signal_handler::remove_signals(signal_mask_t sigs)
548 : {
549 0 : cppthread::guard g(f_mutex);
550 :
551 0 : for(size_t i = 0; i < sizeof(f_signal_actions) / sizeof(f_signal_actions[0]); ++i)
552 : {
553 0 : if((sigs & (1L << i)) != 0 && f_signal_actions[i] != nullptr)
554 : {
555 0 : sigaction(i, f_signal_actions[i].get(), nullptr);
556 0 : f_signal_actions[i].reset();
557 : }
558 : }
559 0 : }
560 :
561 :
562 : /** \brief Remove all the signals.
563 : *
564 : * Remove all the signals at once.
565 : *
566 : * This function is primarily used when the signal_handler is deleted to
567 : * restore the state to normal. It should be the very last thing you want
568 : * to do. You are welcome to call this function at any time, of course,
569 : * with the consequence that none of the signals will now be handled by
570 : * this handler.
571 : *
572 : * This is equivalent to:
573 : *
574 : * \code
575 : * remove_signals(ed::signal_handler::ALL_SIGNALS);
576 : * \endcode
577 : */
578 0 : void signal_handler::remove_all_signals()
579 : {
580 0 : remove_signals(ALL_SIGNALS);
581 0 : }
582 :
583 :
584 : /** \brief Get the name of the signal.
585 : *
586 : * This function converts the signal \p sig to a name one can use to print
587 : * the name in a log or a console.
588 : *
589 : * \param[in] sig The signal number.
590 : *
591 : * \return The name of the specified signal or nullptr if \p sig is invalid.
592 : */
593 0 : char const * signal_handler::get_signal_name(int sig)
594 : {
595 0 : if(static_cast<std::size_t>(sig) >= sizeof(g_signal_names) / sizeof(g_signal_names[0]))
596 : {
597 0 : return nullptr;
598 : }
599 0 : return g_signal_names[sig];
600 : }
601 :
602 :
603 : /** \brief This is out handler.
604 : *
605 : * This function is the handler that gets called whenever a signal is
606 : * raised.
607 : *
608 : * \param[in] sig The signal that generated this handler.
609 : * \param[in] info Information about the signal handler.
610 : * \param[in] context The context from when the interrupt was generated.
611 : */
612 0 : void signal_handler::signal_handler_func(int sig, siginfo_t * info, void * context)
613 : {
614 : // if we are called, g_signal_handler can't be nullptr
615 : //
616 0 : g_signal_handler->process_signal(sig, info, reinterpret_cast<ucontext_t *>(context));
617 0 : }
618 :
619 :
620 : /** \brief Callback to process a signal we just received.
621 : *
622 : * This function is the one called whenever a signal is received by your
623 : * process. It includes the signal number (\p sig), the signal information
624 : * as defined by the kernel (\p info) and the user context when the signal
625 : * happened (\p ucontext).
626 : *
627 : * By default, the function prints out the stack trace if requested for that
628 : * signal and then print a log message about the signal that generated
629 : * this call. Finally, it calls `std::terminate()` to terminate the process.
630 : *
631 : * However, you can add callbacks to capture the signals in your own handler.
632 : * When doing so, your callback can return true, meaning that you handled the
633 : * signal and you do not want the default process to take over. See the
634 : * add_callback() for additional details.
635 : *
636 : * \note
637 : * An `exit(1)` could be very problematic, so would raising an exception in
638 : * a thread at an impromptus moment (especially in a signal handler). So here
639 : * we use `std::terminate()`.
640 : *
641 : * \param[in] sig The signal being processed.
642 : */
643 0 : void signal_handler::process_signal(int sig, siginfo_t * info, ucontext_t * ucontext)
644 : {
645 0 : callback_list_t callbacks;
646 0 : bool show_stack(false);
647 :
648 : // here we lock as little as possible
649 : {
650 0 : cppthread::guard g(f_mutex);
651 0 : callbacks = f_callbacks;
652 0 : show_stack = (g_show_stack & (1UL << sig)) != 0;
653 : }
654 :
655 0 : bool handled(false);
656 0 : for(auto it(callbacks.begin()); it != callbacks.end(); ++it)
657 : {
658 0 : if(it->f_sig == sig)
659 : {
660 0 : if((it->f_callback)(it->f_id, sig, info, ucontext))
661 : {
662 0 : handled = true;
663 : }
664 : }
665 : }
666 0 : if(handled)
667 : {
668 : // user said it was handled, leave it to that...
669 : //
670 0 : return;
671 : }
672 :
673 0 : if(show_stack)
674 : {
675 0 : auto const trace(libexcept::collect_stack_trace());
676 0 : for(auto const & stack_line : trace)
677 : {
678 0 : SNAP_LOG_ERROR
679 0 : << "signal_handler(): backtrace="
680 : << stack_line
681 : << SNAP_LOG_SEND;
682 : }
683 : }
684 :
685 0 : char const * n(get_signal_name(sig));
686 0 : std::string signame;
687 0 : if(n == nullptr)
688 : {
689 0 : signame = "UNKNOWN";
690 : }
691 : else
692 : {
693 0 : signame = n;
694 : }
695 :
696 0 : SNAP_LOG_FATAL
697 0 : << "Fatal signal caught: "
698 : << signame
699 : << SNAP_LOG_SEND;
700 :
701 : // Abort
702 : //
703 0 : std::terminate();
704 : snap::NOT_REACHED();
705 : }
706 :
707 :
708 :
709 :
710 6 : } // namespace ed
711 : // vim: ts=4 sw=4 et
|