1 // Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <hooks/callout_handle.h>
10 #include <hooks/callout_manager.h>
11 #include <hooks/hooks_log.h>
12 #include <hooks/pointer_converter.h>
13 #include <util/stopwatch.h>
14
15 #include <boost/static_assert.hpp>
16
17 #include <algorithm>
18 #include <climits>
19 #include <functional>
20 #include <utility>
21
22 using namespace std;
23
24 namespace isc {
25 namespace hooks {
26
27 // Constructor
CalloutManager(int num_libraries)28 CalloutManager::CalloutManager(int num_libraries)
29 : server_hooks_(ServerHooks::getServerHooks()), current_library_(-1),
30 hook_vector_(ServerHooks::getServerHooks().getCount()),
31 library_handle_(*this), pre_library_handle_(*this, 0),
32 post_library_handle_(*this, INT_MAX), num_libraries_(num_libraries) {
33 if (num_libraries < 0) {
34 isc_throw(isc::BadValue, "number of libraries passed to the "
35 "CalloutManager must be >= 0");
36 }
37 }
38
39 // Check that the index of a library is valid. It can range from 1 - n
40 // (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
41 // (post-user library callouts). It can also be -1 to indicate an invalid
42 // value.
43
44 void
checkLibraryIndex(int library_index) const45 CalloutManager::checkLibraryIndex(int library_index) const {
46 if (((library_index >= -1) && (library_index <= num_libraries_)) ||
47 (library_index == INT_MAX)) {
48 return;
49 }
50
51 isc_throw(NoSuchLibrary, "library index " << library_index <<
52 " is not valid for the number of loaded libraries (" <<
53 num_libraries_ << ")");
54 }
55
56 // Register a callout for the current library.
57
58 void
registerCallout(const std::string & name,CalloutPtr callout,int library_index)59 CalloutManager::registerCallout(const std::string& name,
60 CalloutPtr callout,
61 int library_index) {
62 // Note the registration.
63 LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
64 .arg(library_index).arg(name);
65
66 // Sanity check that the current library index is set to a valid value.
67 checkLibraryIndex(library_index);
68
69 // New hooks could have been registered since the manager was constructed.
70 ensureHookLibsVectorSize();
71
72 // Get the index associated with this hook (validating the name in the
73 // process).
74 int hook_index = server_hooks_.getIndex(name);
75
76 // Iterate through the callout vector for the hook from start to end,
77 // looking for the first entry where the library index is greater than
78 // the present index.
79 for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
80 i != hook_vector_[hook_index].end(); ++i) {
81 if (i->first > library_index) {
82 // Found an element whose library index number is greater than the
83 // current index, so insert the new element ahead of this one.
84 hook_vector_[hook_index].insert(i, make_pair(library_index,
85 callout));
86 return;
87 }
88 }
89
90 // Reached the end of the vector, so there is no element in the (possibly
91 // empty) set of callouts with a library index greater than the current
92 // library index. Inset the callout at the end of the list.
93 hook_vector_[hook_index].push_back(make_pair(library_index, callout));
94 }
95
96 // Check if callouts are present for a given hook index.
97
98 bool
calloutsPresent(int hook_index) const99 CalloutManager::calloutsPresent(int hook_index) const {
100 // Validate the hook index.
101 if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
102 isc_throw(NoSuchHook, "hook index " << hook_index <<
103 " is not valid for the list of registered hooks");
104 }
105
106 // Valid, so are there any callouts associated with that hook?
107 return (!hook_vector_[hook_index].empty());
108 }
109
110 bool
commandHandlersPresent(const std::string & command_name) const111 CalloutManager::commandHandlersPresent(const std::string& command_name) const {
112 // Check if the hook point for the specified command exists.
113 int index = ServerHooks::getServerHooks().findIndex(
114 ServerHooks::commandToHookName(command_name));
115 if (index >= 0) {
116 // The hook point exits but it is possible that there are no
117 // callouts/command handlers. This is possible if there was a
118 // hook library supporting this command attached, but it was
119 // later unloaded. The hook points are not deregistered in
120 // this case. Only callouts are deregistered.
121 // Let's check if callouts are present for this hook point.
122 return (calloutsPresent(index));
123 }
124
125 // Hook point not created, so we don't support this command in
126 // any of the hooks libraries.
127 return (false);
128 }
129
130
131 // Call all the callouts for a given hook.
132
133 void
callCallouts(int hook_index,CalloutHandle & callout_handle)134 CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
135 // Clear the "skip" flag so we don't carry state from a previous call.
136 // This is done regardless of whether callouts are present to avoid passing
137 // any state from the previous call of callCallouts().
138 callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
139
140 // Only initialize and iterate if there are callouts present. This check
141 // also catches the case of an invalid index.
142 if (calloutsPresent(hook_index)) {
143
144 // Set the current hook index. This is used should a callout wish to
145 // determine to what hook it is attached.
146 callout_handle.setCurrentHook(hook_index);
147
148 // This object will be used to measure execution time of each callout
149 // and the total time spent in callouts for this hook point.
150 util::Stopwatch stopwatch;
151
152 // Mark that the callouts begin for the hook.
153 LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN)
154 .arg(server_hooks_.getName(callout_handle.getCurrentHook()));
155
156 // Call all the callouts.
157 for (CalloutVector::const_iterator i = hook_vector_[hook_index].begin();
158 i != hook_vector_[hook_index].end(); ++i) {
159 // In case the callout requires access to the context associated
160 // with the library, set the current library index to the index
161 // associated with the library that registered the callout being
162 // called.
163 callout_handle.setCurrentLibrary(i->first);
164
165 // Call the callout
166 try {
167 stopwatch.start();
168 int status = (*i->second)(callout_handle);
169 stopwatch.stop();
170 if (status == 0) {
171 LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
172 HOOKS_CALLOUT_CALLED)
173 .arg(callout_handle.getCurrentLibrary())
174 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
175 .arg(PointerConverter(i->second).dlsymPtr())
176 .arg(stopwatch.logFormatLastDuration());
177 } else {
178 LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR)
179 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
180 .arg(callout_handle.getCurrentLibrary())
181 .arg(PointerConverter(i->second).dlsymPtr())
182 .arg(stopwatch.logFormatLastDuration());
183 }
184 } catch (const std::exception& e) {
185 // If an exception occurred, the stopwatch.stop() hasn't been
186 // called, so we have to call it here.
187 stopwatch.stop();
188 // Any exception, not just ones based on isc::Exception
189 LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
190 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
191 .arg(callout_handle.getCurrentLibrary())
192 .arg(PointerConverter(i->second).dlsymPtr())
193 .arg(e.what())
194 .arg(stopwatch.logFormatLastDuration());
195 }
196
197 }
198
199 // Mark end of callout execution. Include the total execution
200 // time for callouts.
201 LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE)
202 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
203 .arg(stopwatch.logFormatTotalDuration());
204
205 // Reset the current hook and library indexes to an invalid value to
206 // catch any programming errors.
207 callout_handle.setCurrentHook(-1);
208 callout_handle.setCurrentLibrary(-1);
209 }
210 }
211
212 void
callCommandHandlers(const std::string & command_name,CalloutHandle & callout_handle)213 CalloutManager::callCommandHandlers(const std::string& command_name,
214 CalloutHandle& callout_handle) {
215 // Get the index of the hook point for the specified command.
216 // This will throw an exception if the hook point doesn't exist.
217 // The caller should check if the hook point exists by calling
218 // commandHandlersPresent.
219 int index = ServerHooks::getServerHooks().getIndex(
220 ServerHooks::commandToHookName(command_name));
221 // Call the handlers for this command.
222 callCallouts(index, callout_handle);
223 }
224
225
226 // Deregister a callout registered by the current library on a particular hook.
227
228 bool
deregisterCallout(const std::string & name,CalloutPtr callout,int library_index)229 CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout,
230 int library_index) {
231 // Sanity check that the current library index is set to a valid value.
232 checkLibraryIndex(library_index);
233
234 // New hooks could have been registered since the manager was constructed.
235 ensureHookLibsVectorSize();
236
237 // Get the index associated with this hook (validating the name in the
238 // process).
239 int hook_index = server_hooks_.getIndex(name);
240
241 // New hooks can have been registered since the manager was constructed.
242 if (hook_index >= hook_vector_.size()) {
243 return (false);
244 }
245
246 /// Construct a CalloutEntry matching the current library and the callout
247 /// we want to remove.
248 CalloutEntry target(library_index, callout);
249
250 /// To decide if any entries were removed, we'll record the initial size
251 /// of the callout vector for the hook, and compare it with the size after
252 /// the removal.
253 size_t initial_size = hook_vector_[hook_index].size();
254
255 // The next bit is standard STL (see "Item 33" in "Effective STL" by
256 // Scott Meyers).
257 //
258 // remove_if reorders the hook vector so that all items not matching
259 // the predicate are at the start of the vector and returns a pointer
260 // to the next element. (In this case, the predicate is that the item
261 // is equal to the value of the passed callout.) The erase() call
262 // removes everything from that element to the end of the vector, i.e.
263 // all the matching elements.
264 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
265 hook_vector_[hook_index].end(),
266 [&target] (CalloutEntry x) {
267 return (x == target); }),
268 hook_vector_[hook_index].end());
269
270 // Return an indication of whether anything was removed.
271 bool removed = initial_size != hook_vector_[hook_index].size();
272 if (removed) {
273 LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
274 HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name);
275 }
276
277 return (removed);
278 }
279
280 // Deregister all callouts on a given hook.
281
282 bool
deregisterAllCallouts(const std::string & name,int library_index)283 CalloutManager::deregisterAllCallouts(const std::string& name,
284 int library_index) {
285 // New hooks could have been registered since the manager was constructed.
286 ensureHookLibsVectorSize();
287
288 // Get the index associated with this hook (validating the name in the
289 // process).
290 int hook_index = server_hooks_.getIndex(name);
291
292 /// Construct a CalloutEntry matching the current library (the callout
293 /// pointer is NULL as we are not checking that).
294 CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
295
296 /// To decide if any entries were removed, we'll record the initial size
297 /// of the callout vector for the hook, and compare it with the size after
298 /// the removal.
299 size_t initial_size = hook_vector_[hook_index].size();
300
301 // Remove all callouts matching this library.
302 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
303 hook_vector_[hook_index].end(),
304 [&target] (CalloutEntry x) {
305 return (x.first == target.first);
306 }),
307 hook_vector_[hook_index].end());
308
309 // Return an indication of whether anything was removed.
310 bool removed = initial_size != hook_vector_[hook_index].size();
311 if (removed) {
312 LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
313 HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
314 }
315
316 return (removed);
317 }
318
319 void
registerCommandHook(const std::string & command_name)320 CalloutManager::registerCommandHook(const std::string& command_name) {
321 // New hooks could have been registered since the manager was constructed.
322 ensureHookLibsVectorSize();
323
324 ServerHooks& hooks = ServerHooks::getServerHooks();
325 int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
326 if (hook_index < 0) {
327 // Hook for this command doesn't exist. Let's create one.
328 hooks.registerHook(ServerHooks::commandToHookName(command_name));
329 // Callout Manager's vector of hooks have to be resized to hold the
330 // information about callouts for this new hook point. This should
331 // add new element at the end of the hook_vector_. The index of this
332 // element will match the index of the hook point in the ServerHooks
333 // because ServerHooks allocates indexes incrementally.
334 hook_vector_.resize(server_hooks_.getCount());
335 }
336 }
337
338 void
ensureHookLibsVectorSize()339 CalloutManager::ensureHookLibsVectorSize() {
340 ServerHooks& hooks = ServerHooks::getServerHooks();
341 if (hooks.getCount() > hook_vector_.size()) {
342 // Uh oh, there are more hook points that our vector allows.
343 hook_vector_.resize(hooks.getCount());
344 }
345 }
346
347 } // namespace util
348 } // namespace isc
349