diff --git a/src/hooks/dhcp/Makefile.am b/src/hooks/dhcp/Makefile.am
index 090bde7fd3..b40e08c3fd 100644
--- a/src/hooks/dhcp/Makefile.am
+++ b/src/hooks/dhcp/Makefile.am
@@ -4,4 +4,4 @@ if HAVE_MYSQL
SUBDIRS += mysql_cb
endif
-SUBDIRS += stat_cmds user_chk
+SUBDIRS += run_script stat_cmds user_chk
diff --git a/src/hooks/dhcp/run_script/.gitattributes b/src/hooks/dhcp/run_script/.gitattributes
new file mode 100644
index 0000000000..31e5a07093
--- /dev/null
+++ b/src/hooks/dhcp/run_script/.gitattributes
@@ -0,0 +1,2 @@
+/run_script_messages.cc -diff merge=ours
+/run_script_messages.h -diff merge=ours
diff --git a/src/hooks/dhcp/run_script/.gitignore b/src/hooks/dhcp/run_script/.gitignore
new file mode 100644
index 0000000000..35b5e99aee
--- /dev/null
+++ b/src/hooks/dhcp/run_script/.gitignore
@@ -0,0 +1 @@
+/html
diff --git a/src/hooks/dhcp/run_script/Makefile.am b/src/hooks/dhcp/run_script/Makefile.am
new file mode 100644
index 0000000000..0fa924c9bb
--- /dev/null
+++ b/src/hooks/dhcp/run_script/Makefile.am
@@ -0,0 +1,74 @@
+SUBDIRS = . libloadtests tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file and doxygen file is included in the distribution
+EXTRA_DIST = run_script_messages.mes
+EXTRA_DIST += run_script.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# convenience archive
+
+noinst_LTLIBRARIES = librun_script.la
+
+librun_script_la_SOURCES = run_script_callouts.cc
+librun_script_la_SOURCES += run_script_log.cc run_script_log.h
+librun_script_la_SOURCES += run_script_messages.cc run_script_messages.h
+librun_script_la_SOURCES += version.cc
+
+librun_script_la_CXXFLAGS = $(AM_CXXFLAGS)
+librun_script_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+# install the shared object into $(libdir)/kea/hooks
+lib_hooksdir = $(libdir)/kea/hooks
+lib_hooks_LTLIBRARIES = libdhcp_run_script.la
+
+libdhcp_run_script_la_SOURCES =
+libdhcp_run_script_la_LDFLAGS = $(AM_LDFLAGS)
+libdhcp_run_script_la_LDFLAGS += -avoid-version -export-dynamic -module
+libdhcp_run_script_la_LIBADD = librun_script.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp_run_script_la_LIBADD += $(LOG4CPLUS_LIBS)
+libdhcp_run_script_la_LIBADD += $(CRYPTO_LIBS)
+libdhcp_run_script_la_LIBADD += $(BOOST_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f run_script_messages.h run_script_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: run_script_messages.h run_script_messages.cc
+ @echo Message files regenerated
+
+run_script_messages.h run_script_messages.cc: run_script_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/hooks/dhcp/run_script/run_script_messages.mes
+
+else
+
+messages run_script_messages.h run_script_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
diff --git a/src/hooks/dhcp/run_script/run_script.dox b/src/hooks/dhcp/run_script/run_script.dox
new file mode 100644
index 0000000000..45ecf765b6
--- /dev/null
+++ b/src/hooks/dhcp/run_script/run_script.dox
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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/.
+
+/**
+
+@page libdhcp_run_script Kea Run Script Hooks Library
+
+@section libdhcp_run_scriptIntro Introduction
+
+Welcome to Kea Run Script Hooks Library. This documentation is addressed to
+developers who are interested in the internal operation of the Run Script
+library. This file provides information needed to understand and
+perhaps extend this library.
+
+This documentation is stand-alone: you should have read and understood
+the Kea
+Developer's Guide and in particular its section about hooks.
+
+@section libdhcp_run_scriptUser Now To Use libdhcp_run_script
+## Introduction
+libdhcp_run_script is a hooks library which allows an external script to
+be run on specific hook points.
+
+## Configuring the DHCPv4 Module
+
+It must be configured as a hook library for the desired DHCP server
+modules. Note that the bootp library is installed alongside the
+Kea libraries in "/lib" where is determined
+by the --prefix option of the configure script. It defaults to
+"/usr/local". Assuming the default value then, configuring kea-dhcp4
+to load the bootp library could be done with the following Kea4
+configuration:
+
+@code
+"Dhcp4": {
+ "hook_libraries": [
+ { "library": "/usr/local/lib/libdhcp_run_script.so",
+ "parameters": { "name": "/path_to/script_name.sh",
+ "sync": false }},
+ ...
+ ]
+}
+@endcode
+
+The parameters contain the 'name' which indicates the path and name of the
+external script to be called on each hookpoint, and also the 'sync' option
+to be able to wait synchronously for the script to finish execution.
+If the 'sync' parameter is false, then the script will be launched and all
+the OUT parameters of the script will be ignored.
+
+## Internal operation
+
+The first function called in @ref load() located in the
+bootp_callouts.cc. It checks if the necessary parameter is passed and
+decodes the option configurations. @ref unload() free the configuration.
+
+Kea engine checks if the library has functions that match known hook
+point names. This library has several such functions: @ref pkt4_receive,
+@ref pkt4_send, @ref pkt6_receive, @ref pkt6_send, etc...
+located in run_script_callouts.cc.
+
+*/
diff --git a/src/hooks/dhcp/run_script/run_script_callouts.cc b/src/hooks/dhcp/run_script/run_script_callouts.cc
new file mode 100644
index 0000000000..5478509d3f
--- /dev/null
+++ b/src/hooks/dhcp/run_script/run_script_callouts.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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
+
+#include
+#include
+
+namespace isc {
+namespace run_script {
+
+RunScriptPtr impl;
+
+} // end of namespace flex_option
+} // end of namespace isc
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::run_script;
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+/// @brief This function is called when the library is loaded.
+///
+/// @param handle library handle
+/// @return 0 when initialization is successful, 1 otherwise
+int load(LibraryHandle& handle) {
+ try {
+ impl.reset(new RunScriptImpl());
+ } catch (const std::exception& ex) {
+ LOG_ERROR(run_script_logger, RUN_SCRIPT_LOAD_ERROR)
+ .arg(ex.what());
+ return (1);
+ }
+
+ LOG_INFO(run_script_logger, RUN_SCRIPT_LOAD);
+ return (0);
+}
+
+/// @brief This function is called when the library is unloaded.
+///
+/// @return always 0.
+int unload() {
+ impl.reset();
+ LOG_INFO(run_script_logger, RUN_SCRIPT_UNLOAD);
+ return (0);
+}
+
+/// @brief This function is called to retrieve the multi-threading compatibility.
+///
+/// @return 1 which means compatible with multi-threading.
+int multi_threading_compatible() {
+ return (1);
+}
+
+} // end extern "C"
+
diff --git a/src/hooks/dhcp/run_script/run_script_log.cc b/src/hooks/dhcp/run_script/run_script_log.cc
new file mode 100644
index 0000000000..62af75b466
--- /dev/null
+++ b/src/hooks/dhcp/run_script/run_script_log.cc
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 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
+
+#include
+
+namespace isc {
+namespace run_script {
+
+isc::log::Logger run_script_logger("run-script-hooks");
+
+} // namespace bootp
+} // namespace isc
diff --git a/src/hooks/dhcp/run_script/run_script_log.h b/src/hooks/dhcp/run_script/run_script_log.h
new file mode 100644
index 0000000000..10a23d92a6
--- /dev/null
+++ b/src/hooks/dhcp/run_script/run_script_log.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 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/.
+
+#ifndef RUN_SCRIPT_LOG_H
+#define RUN_SCRIPT_LOG_H
+
+#include
+#include
+#include
+#include
+
+namespace isc {
+namespace run_script {
+
+extern isc::log::Logger run_script_logger;
+
+} // end of namespace run_script
+} // end of namespace isc
+#endif
diff --git a/src/hooks/dhcp/run_script/run_script_messages.mes b/src/hooks/dhcp/run_script/run_script_messages.mes
new file mode 100644
index 0000000000..348b4ae73a
--- /dev/null
+++ b/src/hooks/dhcp/run_script/run_script_messages.mes
@@ -0,0 +1,11 @@
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+
+% RUN_SCRIPT_LOAD Run Script hooks library has been loaded
+This info message indicates that the Run Script hooks library has been loaded.
+
+% RUN_SCRIPT_LOAD_ERROR Run Script hooks library failed: %1
+This error message indicates an error during loading the Run Script hooks
+library. The details of the error are provided as argument of the log message.
+
+% RUN_SCRIPT_UNLOAD Run Script hooks library has been unloaded
+This info message indicates that the RunScript hooks library has been unloaded.
diff --git a/src/hooks/dhcp/run_script/run_script_wrapper.sh b/src/hooks/dhcp/run_script/run_script_wrapper.sh
new file mode 100755
index 0000000000..5687af8530
--- /dev/null
+++ b/src/hooks/dhcp/run_script/run_script_wrapper.sh
@@ -0,0 +1,17 @@
+if test ${#} -lt 2; then
+ echo "Usage: ${0} script_name function_name"
+ echo " All variables used by the script must be available"
+ echo " as environment variables."
+ echo " All variables specified in the 'hook_parameters'"
+ echo " variable will be printed to stdout."
+ exit 1
+fi
+
+script_name=${1}
+function_name=${2}
+
+source ${script_name} ${function_name}
+
+for parameter in ${hook_parameters}; do
+ echo "${parameter}=${!parameter}"
+done
diff --git a/src/hooks/dhcp/run_script/tests/run_script_test.sh b/src/hooks/dhcp/run_script/tests/run_script_test.sh
new file mode 100755
index 0000000000..eac4366264
--- /dev/null
+++ b/src/hooks/dhcp/run_script/tests/run_script_test.sh
@@ -0,0 +1,7 @@
+echo ${@}
+
+SKIP="false"
+if test ! -z ${ADDRESS}; then
+ echo "${ADDRESS}"
+ SKIP="false"
+fi
diff --git a/src/lib/util/process_spawn.cc b/src/lib/util/process_spawn.cc
index 1cc3717356..249bc96862 100644
--- a/src/lib/util/process_spawn.cc
+++ b/src/lib/util/process_spawn.cc
@@ -59,8 +59,10 @@ public:
///
/// @param executable A path to the program to be executed.
/// @param args Arguments for the program to be executed.
+ /// @param vars Environment variables for the program to be executed.
ProcessSpawnImpl(const std::string& executable,
- const ProcessArgs& args);
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars);
/// @brief Destructor.
~ProcessSpawnImpl();
@@ -146,13 +148,18 @@ private:
std::string executable_;
/// @brief An array holding arguments for the executable.
- char** args_;
+ boost::shared_ptr args_;
+
+ /// @brief An array holding environment variables for the executable.
+ boost::shared_ptr vars_;
};
ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
- const ProcessArgs& args)
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars)
: signals_(new SignalSet(SIGCHLD)), process_state_(),
- executable_(executable), args_(new char*[args.size() + 2]) {
+ executable_(executable), args_(new char*[args.size() + 2]),
+ vars_(new char*[vars.size() + 1]) {
// Set the handler which is invoked immediately when the signal
// is received.
signals_->setOnReceiptHandler(std::bind(&ProcessSpawnImpl::waitForProcess,
@@ -160,12 +167,17 @@ ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
// Conversion of the arguments to the C-style array we start by setting
// all pointers within an array to NULL to indicate that they haven't
// been allocated yet.
- memset(args_, 0, (args.size() + 2) * sizeof(char*));
+ memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
+ memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
// By convention, the first argument points to an executable name.
args_[0] = allocateArg(executable_);
// Copy arguments to the array.
for (int i = 1; i <= args.size(); ++i) {
- args_[i] = allocateArg(args[i-1]);
+ args_[i] = allocateArg(args[i - 1]);
+ }
+ // Copy environment variables to the array.
+ for (int i = 0; i < vars.size(); ++i) {
+ vars_[i] = allocateArg(vars[i]);
}
}
@@ -176,8 +188,13 @@ ProcessSpawnImpl::~ProcessSpawnImpl() {
delete[] args_[i];
++i;
}
- // Deallocate the array.
- delete[] args_;
+
+ i = 0;
+ // Deallocate strings in the array of environment variables.
+ while (vars_[i] != NULL) {
+ delete[] vars_[i];
+ ++i;
+ }
}
std::string
@@ -217,8 +234,8 @@ ProcessSpawnImpl::spawn() {
// We're in the child process.
sigprocmask(SIG_SETMASK, &osset, 0);
// Run the executable.
- if (execvp(executable_.c_str(), args_) != 0) {
- // We may end up here if the execvp failed, e.g. as a result
+ if (execvpe(executable_.c_str(), args_.get(), vars_.get()) != 0) {
+ // We may end up here if the execvpe failed, e.g. as a result
// of issue with permissions or invalid executable name.
_exit(EXIT_FAILURE);
}
@@ -332,12 +349,12 @@ ProcessSpawnImpl::clearState(const pid_t pid) {
}
ProcessSpawn::ProcessSpawn(const std::string& executable,
- const ProcessArgs& args)
- : impl_(new ProcessSpawnImpl(executable, args)) {
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars)
+ : impl_(new ProcessSpawnImpl(executable, args, vars)) {
}
ProcessSpawn::~ProcessSpawn() {
- delete impl_;
}
std::string
diff --git a/src/lib/util/process_spawn.h b/src/lib/util/process_spawn.h
index dffd365e9b..c4e931047d 100644
--- a/src/lib/util/process_spawn.h
+++ b/src/lib/util/process_spawn.h
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
namespace isc {
namespace util {
@@ -27,10 +28,17 @@ public:
/// class.
class ProcessSpawnImpl;
+/// @brief Pointer to a ProcessSpawnImpl class.
+typedef boost::shared_ptr ProcessSpawnImplPtr;
+
/// @brief Type of the container holding arguments of the executable
/// being run as a background process.
typedef std::vector ProcessArgs;
+/// @brief Type of the container holding environment variables of the executable
+/// being run as a background process.
+typedef std::vector ProcessEnvVars;
+
/// @brief Utility class for spawning new processes.
///
/// This class is used to spawn new process by Kea. It forks the current
@@ -64,8 +72,10 @@ public:
///
/// @param executable A path to the program to be executed.
/// @param args Arguments for the program to be executed.
+ /// @param vars Environment variables for the program to be executed.
ProcessSpawn(const std::string& executable,
- const ProcessArgs& args = ProcessArgs());
+ const ProcessArgs& args = ProcessArgs(),
+ const ProcessEnvVars& = ProcessEnvVars());
/// @brief Destructor.
~ProcessSpawn();
@@ -132,9 +142,8 @@ public:
private:
- /// @brief A pointer to the implementation of this class.
- ProcessSpawnImpl* impl_;
-
+ /// @brief A smart pointer to the implementation of this class.
+ ProcessSpawnImplPtr impl_;
};
}
diff --git a/src/lib/util/tests/process_spawn_app.sh.in b/src/lib/util/tests/process_spawn_app.sh.in
index c0b4a54b4c..806e7bd702 100644
--- a/src/lib/util/tests/process_spawn_app.sh.in
+++ b/src/lib/util/tests/process_spawn_app.sh.in
@@ -45,6 +45,16 @@ do
shift
sleep "${1}"
;;
+ -v)
+ shift
+ VAR_NAME=${1}
+ shift
+ VAR_VALUE=${1}
+ EXPECTED=$(env | grep ${VAR_NAME})
+ if ! test "${VAR_NAME}=${VAR_VALUE}" = "${EXPECTED}"; then
+ exit 123
+ fi
+ ;;
*)
exit 123
;;
diff --git a/src/lib/util/tests/process_spawn_unittest.cc b/src/lib/util/tests/process_spawn_unittest.cc
index 016fbbebcb..6137af9d7f 100644
--- a/src/lib/util/tests/process_spawn_unittest.cc
+++ b/src/lib/util/tests/process_spawn_unittest.cc
@@ -122,6 +122,25 @@ TEST(ProcessSpawn, spawnWithArgs) {
EXPECT_EQ(64, process.getExitStatus(pid));
}
+// This test verifies that the external application can be ran with
+// arguments and environment variables that the exit code is gathered.
+TEST(ProcessSpawn, spawnWithArgsAndEnvVars) {
+ std::vector args;
+ std::vector vars;
+ args.push_back("-v");
+ args.push_back("TEST_VARIABLE_NAME");
+ args.push_back("TEST_VARIABLE_VALUE");
+ vars.push_back("TEST_VARIABLE_NAME=TEST_VARIABLE_VALUE");
+
+ ProcessSpawn process(getApp(), args, vars);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ ASSERT_TRUE(waitForProcess(process, pid, 2));
+
+ EXPECT_EQ(32, process.getExitStatus(pid));
+}
+
// This test verifies that the single ProcessSpawn object can be used
// to start two processes and that their status codes can be gathered.
// It also checks that it is possible to clear the status of the
@@ -164,7 +183,6 @@ TEST(ProcessSpawn, spawnNoArgs) {
EXPECT_EQ(32, process.getExitStatus(pid));
}
-
// This test verifies that the EXIT_FAILURE code is returned when
// application can't be executed.
TEST(ProcessSpawn, invalidExecutable) {
@@ -247,5 +265,4 @@ TEST(ProcessSpawn, errnoInvariance) {
EXPECT_EQ(123, errno);
}
-
} // end of anonymous namespace