summaryrefslogtreecommitdiffstats
path: root/src/lib/util/signal_set.cc
blob: f0695ea13e545a8143c319719d8c4a8887ffaccf (plain)
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <util/signal_set.h>

#include <array>
#include <cerrno>
#include <cstring>
#include <list>

using namespace isc;
using namespace isc::util;

namespace {

/// @brief Fixed size storage for the codes of received signals.
///
/// It is initialized with all zeros, which means that no signals
/// have been received.
std::array<int, 1024> received_signals;

/// @brief Returns a pointer to the global set of registered signals.
///
/// Multiple instances of @c SignalSet may use this pointer to access
/// and update the set. Note we use a smart pointer so callers can ensure
/// the static object doesn't go out of scope before they are done with it
/// during process exit.
///
/// @return Pointer to the global set of registered signals. This pointer
/// is always initialized and points to a valid object.
SigIntSetPtr getRegisteredSignals() {
    static SigIntSetPtr registered_signals(new SigIntSet());
    return (registered_signals);
}

/// @brief Returns a pointer to static collection of signals received.
///
/// Multiple instances of @c SignalSet may use this pointer to access
/// and update the queue of signals received.  Note we use a smart pointer
/// so callers can ensure the static object doesn't go out of scope before
/// they are done with it during process exit.
///
/// @return Static collection of signals received. This pointer is always
/// initialized and points to a valid object.
SigIntListPtr getSignalStates() {
    static SigIntListPtr signal_states(new SigIntList());
    return (signal_states);
}

/// @brief Internal signal handler for @c isc::util::SignalSet class.
///
/// This handler catches all registered signals. When a signal arrives it
/// passes the signal to invokeOnReceiptHandler for "on-receipt" processing.
/// If this processing returns true if exists without further action,
/// otherwise it adds the signal number to the queue of received signals.
/// It prevents adding duplicated signals. All duplicated signals are dropped.
/// This prevents hammering a process to invoke handlers (e.g. DHCP server
/// reconfiguration), when many of the same signals are received one after
/// another.
///
/// @param sig Signal number.
void internalHandler(int sig) {
    if (SignalSet::invokeOnReceiptHandler(sig)) {
        // Signal has been handled by the on-receipt handler.
        return;
    }

    // Signal is using post-receipt handling, see if we've
    // already received it.
    SigIntListPtr states = getSignalStates();
    for (std::list<int>::const_iterator it = states->begin();
        it != states->end(); ++it) {
            if (sig == *it) {
                return;
        }
    }

    // We haven't yet received this signal so we have to record its
    // reception. We're using the fixed size array to temporarily
    // store the received signal codes. We can't directly insert
    // the signal to the 'states' list because this would cause a
    // new allocation (malloc) which is not async-signal-safe.
    for (int i = 0; i < received_signals.size(); ++i) {
        // We have already recorded this signal.
        if (received_signals[i] == sig) {
            return;

        } else if (received_signals[i] == 0) {
            // We reached the end of the useful data in the storage.
            // Put the signal code into the storage.
            received_signals[i] = sig;
            break;
        }
    }
}

/// @brief Optional handler to execute at the time of signal receipt
BoolSignalHandler onreceipt_handler_ = BoolSignalHandler();

}; // end anon namespace

namespace isc {
namespace util {

bool
SignalSet::invokeOnReceiptHandler(int sig) {
    if (!onreceipt_handler_) {
        return (false);
    }

    // First we set the signal to SIG_IGN.  This causes any repeat occurrences
    // to be discarded, not deferred as they would be if blocked.   Note that
    // we save the current sig action so we can restore it later.
    struct sigaction sa;
    struct sigaction prev_sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = SIG_IGN;
    if (sigaction(sig, &sa, &prev_sa) < 0) {
        // Highly unlikely we can get here.
        const char* errmsg = strerror(errno);
        isc_throw(SignalSetError, "failed to set SIG_IGN for signal "
                  << sig << ": " << errmsg);
    }

    // Call the registered handler.
    bool signal_processed = false;
    try {
        signal_processed = onreceipt_handler_(sig);
    } catch (const std::exception& ex) {
        // Restore the handler.  We might fail to restore it, but we likely
        // have bigger issues anyway: for that reason, the return value is
        // ignored.  To avoid complaints from static code checkers that notice
        // that the return values from other calls to sigaction() have been
        // used, the call to sigaction is explicitly cast to void to indicate
        // that the return value is intentionally being ignored.
        static_cast<void>(sigaction(sig, &prev_sa, 0));
        isc_throw(SignalSetError, "onreceipt_handler failed for signal "
                  << sig << ": " << ex.what());
    }

    // Restore the sig action to re-enable handling this signal.
    if (sigaction(sig, &prev_sa, 0) < 0) {
        // Highly unlikely we can get here.
        const char* errmsg = strerror(errno);
        isc_throw(SignalSetError, "failed to restore handler for signal "
                  << sig << ": " << errmsg);
    }

    return (signal_processed);
}

SignalSet::SignalSet(const int sig0)
    : blocked_(false) {
    // Copy static pointers to ensure they don't lose scope before we do.
    registered_signals_ = getRegisteredSignals();
    signal_states_ = getSignalStates();
    add(sig0);
}

SignalSet::SignalSet(const int sig0, const int sig1) :
    blocked_(false) {
    registered_signals_ = getRegisteredSignals();
    signal_states_ = getSignalStates();
    add(sig0);
    add(sig1);
}

SignalSet::SignalSet(const int sig0, const int sig1, const int sig2) :
    blocked_(false) {
    registered_signals_ = getRegisteredSignals();
    signal_states_ = getSignalStates();
    add(sig0);
    add(sig1);
    add(sig2);
}

SignalSet::~SignalSet() {
    // Set default signal handlers.
    try {
        clear();
    } catch (...) {
        // Not a good thing to throw from a destructor. in fact this should
        // not throw an exception because we just unregister the signals
        // that we have previously registered. So the signal codes are fine.
    }
}

void
SignalSet::add(const int sig) {
    insert(sig);
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = internalHandler;
    sigfillset(&sa.sa_mask);
    if (sigaction(sig, &sa, 0) < 0) {
        const char* errmsg = strerror(errno);
        erase(sig);
        isc_throw(SignalSetError, "failed to register a signal handler for"
                  " signal " << sig << ": " << errmsg);
    }
}

void
SignalSet::block() {
    maskSignals(SIG_BLOCK);
    blocked_ = true;
}

void
SignalSet::clear() {
    // Iterate over a copy of the registered signal set because the
    // remove function is erasing the elements and we don't want to
    // erase the elements we are iterating over. This would cause
    // a segfault.
    std::set<int> all_signals = local_signals_;
    for (std::set<int>::const_iterator it = all_signals.begin();
         it != all_signals.end(); ++it) {
        remove(*it);
    }
}

int
SignalSet::getNext() {
    // Since we're operating on the global values that the signal
    // handler has access to, we have to temporarily block the
    // signals to not interfere with unsafe operations performed
    // by this method. Remember if the signals were blocked or
    // not to be able to determine whether they should be unblocked
    // when the function returns.
    bool was_blocked = blocked_;
    if (!was_blocked) {
        block();
    }

    // We may have some signals received. We need to iterate over those
    // and move them to the signal states structure.
    for (int i = 0; i < received_signals.size(); ++i) {
        // If we hit the end of useful data, break.
        if (received_signals[i] == 0) {
            break;
        }
        // Check if such signal has been already recorded.
        if (std::find(signal_states_->begin(), signal_states_->end(), received_signals[i])
            == signal_states_->end()) {
            signal_states_->push_back(received_signals[i]);
        }
        // Set the current element to 0 to indicate the end of
        // the useful data and process the next element.
        received_signals[i] = 0;
    }

    int sig = -1;
    for (std::list<int>::iterator it = signal_states_->begin();
         it != signal_states_->end(); ++it) {
        if (local_signals_.find(*it) != local_signals_.end()) {
            sig = *it;
            break;
        }
    }

    // If we blocked signals here locally, we have to unblock them.
    if (!was_blocked) {
        unblock();
    }
    return (sig);
}

void
SignalSet::erase(const int sig) {
    if (local_signals_.find(sig) == local_signals_.end()) {
        isc_throw(SignalSetError, "failed to unregister signal " << sig
                  << " from a signal set: signal is not owned by the"
                  " signal set");
    }

    // Since we're operating on the global values that the signal
    // handler has access to, we have to temporarily block the
    // signals to not interfere with unsafe operations performed
    // by this method. Remember if the signals were blocked or
    // not to be able to determine whether they should be unblocked
    // when the function returns.
    bool was_blocked = blocked_;
    if (!was_blocked) {
        block();
    }

    // Remove globally registered signal.
    registered_signals_->erase(sig);
    // Remove unhandled signals from the queue.
    for (std::list<int>::iterator it = signal_states_->begin();
         it != signal_states_->end(); ++it) {
        if (*it == sig) {
            it = signal_states_->erase(it);
        }
    }

    // Remove locally registered signal.
    local_signals_.erase(sig);

    // If the signal is no longer supported by this signal set,
    // we have to unblock it, because other signal set instance
    // may be using it. We won't process it anyway after it is
    // removed from this set.
    unblock(sig);

    // Unblock all remaining signals if we have blocked them in
    // this function.
    if (!was_blocked) {
        unblock();
    }
}

void
SignalSet::handleNext(SignalHandler signal_handler) {
    block();
    int signum = getNext();
    if (signum >= 0) {
        popNext();
        try {
            signal_handler(signum);
        } catch (...) {
            unblock();
            throw;
        }
    }
    unblock();
}

void
SignalSet::insert(const int sig) {
    if ((registered_signals_->find(sig) != registered_signals_->end()) ||
        (local_signals_.find(sig) != local_signals_.end())) {
        isc_throw(SignalSetError, "attempt to register a duplicate signal "
                  << sig);
    }
    registered_signals_->insert(sig);
    local_signals_.insert(sig);
}

void
SignalSet::maskSignals(const int mask, const int sig) const {
    sigset_t new_set;
    sigemptyset(&new_set);
    if (sig < 0) {
        for (std::set<int>::const_iterator it = registered_signals_->begin();
             it != registered_signals_->end(); ++it) {
            sigaddset(&new_set, *it);
        }
    } else {
        sigaddset(&new_set, sig);
    }
    pthread_sigmask(mask, &new_set, 0);
}

void
SignalSet::popNext() {
    for (std::list<int>::iterator it = signal_states_->begin();
         it != signal_states_->end(); ++it) {
        if (local_signals_.find(*it) != local_signals_.end()) {
            signal_states_->erase(it);
            return;
        }
    }
}

void
SignalSet::remove(const int sig) {
    // Unregister only if we own this signal.
    if (local_signals_.find(sig) != local_signals_.end()) {
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = SIG_DFL;
        sigfillset(&sa.sa_mask);
        if (sigaction(sig, &sa, 0) < 0) {
            isc_throw(SignalSetError, "unable to restore original signal"
                      " handler for signal: " << sig);
        }
        erase(sig);
    } else {
        isc_throw(SignalSetError, "failed to unregister signal " << sig
                  << ": this signal is not owned by the signal set");
    }
}

void
SignalSet::unblock(int sig) {
    // There are two cases supported by this function. One is to
    // unblock some specific signal. Another one is to unblock all
    // signals supported by this function. The maskSignals function
    // will deal wih this internally but we marked signals as unblocked
    // only if we unblocked all of them (sig is negative).
    maskSignals(SIG_UNBLOCK, sig);
    if (sig < 0) {
        blocked_ = false;
    }
}


void
SignalSet::setOnReceiptHandler(BoolSignalHandler handler) {
    onreceipt_handler_ = handler;
}

void
SignalSet::clearOnReceiptHandler() {
    onreceipt_handler_ = BoolSignalHandler();
}

} // end of isc::util
} // end of isc