summaryrefslogtreecommitdiffstats
path: root/src/lib/hooks/callout_manager.cc
blob: 0b75b1b50f81d854ca9163fd0a33b9998c98ed30 (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
// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <hooks/callout_handle.h>
#include <hooks/callout_manager.h>
#include <hooks/hooks_log.h>

#include <boost/static_assert.hpp>

#include <algorithm>
#include <climits>
#include <functional>
#include <utility>

using namespace std;

namespace isc {
namespace hooks {

// Check that the index of a library is valid.  It can range from 1 - n
// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
// (post-user library callouts).  It can also be -1 to indicate an invalid
// value.

void
CalloutManager::checkLibraryIndex(int library_index) const {
    if (((library_index >= -1) && (library_index <= num_libraries_)) ||
        (library_index == INT_MAX)) {
        return;
    }

    isc_throw(NoSuchLibrary, "library index " << library_index <<
              " is not valid for the number of loaded libraries (" <<
              num_libraries_ << ")");
}

// Set the number of libraries handled by the CalloutManager.

void
CalloutManager::setNumLibraries(int num_libraries) {
    if (num_libraries < 0) {
        isc_throw(isc::BadValue, "number of libraries passed to the "
                  "CalloutManager must be >= 0");
    }

    num_libraries_ = num_libraries;
}

// Register a callout for the current library.

void
CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
    // Note the registration.
    LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
        .arg(current_library_).arg(name);

    // Sanity check that the current library index is set to a valid value.
    checkLibraryIndex(current_library_);

    // Get the index associated with this hook (validating the name in the
    // process).
    int hook_index = ServerHooks::getServerHooks().getIndex(name);

    // Iterate through the callout vector for the hook from start to end,
    // looking for the first entry where the library index is greater than
    // the present index.
    for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
         i != hook_vector_[hook_index].end(); ++i) {
        if (i->first > current_library_) {
            // Found an element whose library index number is greater than the
            // current index, so insert the new element ahead of this one.
            hook_vector_[hook_index].insert(i, make_pair(current_library_,
                                                         callout));
            return;
        }
    }

    // Reached the end of the vector, so there is no element in the (possibly
    // empty) set of callouts with a library index greater than the current
    // library index.  Inset the callout at the end of the list.
    hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
}

// Check if callouts are present for a given hook index.

bool
CalloutManager::calloutsPresent(int hook_index) const {
    // Validate the hook index.
    if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
        isc_throw(NoSuchHook, "hook index " << hook_index <<
                  " is not valid for the list of registered hooks");
    }

    // Valid, so are there any callouts associated with that hook?
    return (!hook_vector_[hook_index].empty());
}

// Call all the callouts for a given hook.

void
CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {

    // Only initialize and iterate if there are callouts present.  This check
    // also catches the case of an invalid index.
    if (calloutsPresent(hook_index)) {

        // Clear the "skip" flag so we don't carry state from a previous call.
        callout_handle.setSkip(false);

        // Set the current hook index.  This is used should a callout wish to
        // determine to what hook it is attached.
        current_hook_ = hook_index;

        // Duplicate the callout vector for this hook and work through that.
        // This step is needed because we allow dynamic registration and
        // deregistration of callouts.  If a callout attached to a hook modified
        // the list of callouts on that hook, the underlying CalloutVector would
        // change and potentially affect the iteration through that vector.
        CalloutVector callouts(hook_vector_[hook_index]);

        // Call all the callouts.
        for (CalloutVector::const_iterator i = callouts.begin();
             i != callouts.end(); ++i) {
            // In case the callout tries to register or deregister a callout,
            // set the current library index to the index associated with the
            // library that registered the callout being called.
            current_library_ = i->first;

            // Call the callout
            try {
                int status = (*i->second)(callout_handle);
                if (status == 0) {
                    LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
                              HOOKS_CALLOUT_CALLED).arg(current_library_)
                        .arg(ServerHooks::getServerHooks()
                            .getName(current_hook_))
                        .arg(reinterpret_cast<void*>(i->second));
                } else {
                    LOG_ERROR(hooks_logger, HOOKS_CALLOUT_ERROR)
                        .arg(current_library_)
                        .arg(ServerHooks::getServerHooks()
                            .getName(current_hook_))
                        .arg(reinterpret_cast<void*>(i->second));
                }
            } catch (...) {
                // Any exception, not just ones based on isc::Exception
                LOG_ERROR(hooks_logger, HOOKS_CALLOUT_EXCEPTION)
                    .arg(current_library_)
                    .arg(ServerHooks::getServerHooks().getName(current_hook_))
                    .arg(reinterpret_cast<void*>(i->second));
            }

        }

        // Reset the current hook and library indexs to an invalid value to
        // catch any programming errors.
        current_hook_ = -1;
        current_library_ = -1;
    }
}

// Deregister a callout registered by the current library on a particular hook.

bool
CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
    // Sanity check that the current library index is set to a valid value.
    checkLibraryIndex(current_library_);

    // Get the index associated with this hook (validating the name in the
    // process).
    int hook_index = ServerHooks::getServerHooks().getIndex(name);

    /// Construct a CalloutEntry matching the current library and the callout
    /// we want to remove.
    CalloutEntry target(current_library_, callout);

    /// To decide if any entries were removed, we'll record the initial size
    /// of the callout vector for the hook, and compare it with the size after
    /// the removal.
    size_t initial_size = hook_vector_[hook_index].size();

    // The next bit is standard STL (see "Item 33" in "Effective STL" by
    // Scott Meyers).
    //
    // remove_if reorders the hook vector so that all items not matching
    // the predicate are at the start of the vector and returns a pointer
    // to the next element. (In this case, the predicate is that the item
    // is equal to the value of the passed callout.)  The erase() call
    // removes everything from that element to the end of the vector, i.e.
    // all the matching elements.
    hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
                                             hook_vector_[hook_index].end(),
                                             bind1st(equal_to<CalloutEntry>(),
                                                     target)),
                                   hook_vector_[hook_index].end());

    // Return an indication of whether anything was removed.
    bool removed = initial_size != hook_vector_[hook_index].size();
    if (removed) {
        LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
                  HOOKS_CALLOUT_DEREGISTERED).arg(current_library_).arg(name);
    }

    return (removed);
}

// Deregister all callouts on a given hook.

bool
CalloutManager::deregisterAllCallouts(const std::string& name) {

    // Get the index associated with this hook (validating the name in the
    // process).
    int hook_index = ServerHooks::getServerHooks().getIndex(name);

    /// Construct a CalloutEntry matching the current library (the callout
    /// pointer is NULL as we are not checking that).
    CalloutEntry target(current_library_, NULL);

    /// To decide if any entries were removed, we'll record the initial size
    /// of the callout vector for the hook, and compare it with the size after
    /// the removal.
    size_t initial_size = hook_vector_[hook_index].size();

    // Remove all callouts matching this library.
    hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
                                             hook_vector_[hook_index].end(),
                                             bind1st(CalloutLibraryEqual(),
                                                     target)),
                                   hook_vector_[hook_index].end());

    // Return an indication of whether anything was removed.
    bool removed = initial_size != hook_vector_[hook_index].size();
    if (removed) {
        LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
                  HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(current_library_)
                                                .arg(name);
    }

    return (removed);
}

} // namespace util
} // namespace isc