// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
+#include <cc/data.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/daemon.h>
#include <exceptions/exceptions.h>
-#include <cc/data.h>
-#include <boost/bind.hpp>
-#include <logging.h>
#include <log/logger_name.h>
#include <log/logger_support.h>
+#include <logging.h>
+#include <util/filename.h>
+
+#include <boost/bind.hpp>
+
+#include <sstream>
#include <errno.h>
/// @brief provides default implementation for basic daemon operations
std::string Daemon::config_file_ = "";
Daemon::Daemon()
- : signal_set_(), signal_handler_() {
+ : signal_set_(), signal_handler_(), proc_name_(""),
+ pid_file_dir_(DHCP_DATA_DIR), pid_file_() {
}
Daemon::~Daemon() {
+ if (pid_file_) {
+ pid_file_->deleteFile();
+ }
}
void Daemon::init(const std::string& config_file) {
isc_throw(isc::NotImplemented, "Daemon::getVersion() called");
}
+void
+Daemon::setConfigFile(const std::string& config_file) {
+ config_file_ = config_file;
+}
+
+std::string
+Daemon::getProcName() const {
+ return (proc_name_);
+};
+
+void
+Daemon::setProcName(const std::string& proc_name) {
+ proc_name_ = proc_name;
+}
+
+std::string
+Daemon::getPIDFileDir() const {
+ return(pid_file_dir_);
+}
+
+void
+Daemon::setPIDFileDir(const std::string& pid_file_dir) {
+ pid_file_dir_ = pid_file_dir;
+}
+
+std::string
+Daemon::getPIDFileName() const {
+ if (pid_file_) {
+ return (pid_file_->getFilename());
+ }
+
+ return ("");
+};
+
+void
+Daemon::setPIDFileName(const std::string& pid_file_name) {
+ if (pid_file_) {
+ isc_throw(isc::InvalidOperation, "Daemon::setConfigFile"
+ " file name already set to:" << pid_file_->getFilename());
+ }
+
+ if (pid_file_name.empty()) {
+ isc_throw(isc::BadValue, "Daemon::setPIDFileName"
+ " file name may not be empty");
+ }
+
+ pid_file_.reset(new util::PIDFile(pid_file_name));
+};
+
+std::string
+Daemon::makePIDFileName() const {
+ if (config_file_.empty()) {
+ isc_throw(isc::InvalidOperation,
+ "Daemon::makePIDFileName config file name is not set");
+ }
+
+ if (proc_name_.empty()) {
+ isc_throw(isc::InvalidOperation,
+ "Daemon::makePIDFileName process name is not set");
+ }
+
+ // Create Filename instance from the config_file_ pathname, so we can
+ // extract the fname component.
+ isc::util::Filename file(config_file_);
+ if (file.name().empty()) {
+ isc_throw(isc::BadValue, "Daemon::makePIDFileName config file:"
+ << config_file_ << " is missing file name");
+ }
+
+ // Make the pathname for the PID file from the runtime directory,
+ // configuration name and process name.
+ std::ostringstream stream;
+ stream << pid_file_dir_ << "/" << file.name()
+ << "." << proc_name_ << ".pid";
+
+ return(stream.str());
+};
+
+void
+Daemon::createPIDFile(int pid) {
+ // If pid_file_ hasn't been instantiated explicitly, then do so
+ // using the default name.
+ if (!pid_file_) {
+ setPIDFileName(makePIDFileName());
+ }
+
+ // If we find a pre-existing file containing a live PID we bail.
+ if (pid_file_->check()) {
+ isc_throw(DaemonPIDExists, "Daemon::createPIDFile " << proc_name_
+ << " already running?, PID file: " << getPIDFileName());
+ }
+
+ if (pid == 0) {
+ // Write the PID of the current process
+ pid_file_->write();
+ } else {
+ // Write the PID we were given
+ pid_file_->write(pid);
+ }
+}
+
};
};
#include <cc/data.h>
#include <dhcpsrv/srv_config.h>
+#include <util/pid_file.h>
#include <util/signal_set.h>
#include <boost/noncopyable.hpp>
#include <string>
namespace isc {
namespace dhcp {
+/// @brief Exception thrown when a the PID file points to a live PID
+class DaemonPIDExists : public Exception {
+public:
+ DaemonPIDExists(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief Base class for all services
///
/// This is the base class that all daemons (DHCPv4, DHCPv6, D2 and possibly
/// @return text string
static std::string getVersion(bool extended);
+ /// @brief Sets the configuration file name
+ ///
+ /// @param config_file pathname of the configuration file
+ static void setConfigFile(const std::string& config_file);
+
+ /// @brief returns the process name
+ /// This value is used as when forming the default PID file name
+ /// @return text string
+ std::string getProcName() const;
+
+ /// @brief Sets the process name
+ /// @param proc_name name the process by which the process is recognized
+ void setProcName(const std::string& proc_name);
+
+ /// @brief Returns the directory used when forming default PID file name
+ /// @return text string
+ std::string getPIDFileDir() const;
+
+ /// @brief Sets the PID file directory
+ /// @param pid_file_dir path into which the PID file should be written
+ /// Note the value should not include a trailing slash, '/'
+ void setPIDFileDir(const std::string& pid_file_dir);
+
+ /// @brief Returns the current PID file name
+ /// @return text string
+ std::string getPIDFileName() const;
+
+ /// @brief Sets PID file name
+ ///
+ /// If this method is called prior to calling createPIDFile,
+ /// the value passed in will be treated as the full file name
+ /// for the PID file. This provides a means to override the
+ /// default file name with an explicit value.
+ ///
+ /// @param pid_file_name file name to be used as the PID file
+ void setPIDFileName(const std::string& pid_file_name);
+
+ /// @brief Creates the PID file
+ ///
+ /// If the PID file name has not been previously set, the method
+ /// uses manufacturePIDFileName() to set it. If the PID file
+ /// name refers to an existing file whose contents are a PID whose
+ /// process is still alive, the method will throw a DaemonPIDExists
+ /// exception. Otherwise, the file created (or truncated) and
+ /// the given pid (if not zero) is written to the file.
+ ///
+ /// @param pid PID to write to the file if not zero, otherwise the
+ /// PID of the current process is used.
+ void createPIDFile(int pid = 0);
+
protected:
/// @brief Invokes handler for the next received signal.
/// it not initialized, the signals will not be handled.
isc::util::SignalHandler signal_handler_;
-private:
+ /// @brief Manufacture the pid file name
+ std::string makePIDFileName() const;
+private:
/// @brief Config file name or empty if config file not used.
static std::string config_file_;
+ /// @brief Name of this process, used when creating its pid file
+ std::string proc_name_;
+
+ /// @brief Pointer to the directory where PID file(s) are written
+ /// It defaults to --localstatedir
+ std::string pid_file_dir_;
+
+ /// @brief Pointer to the PID file for this process
+ isc::util::PIDFilePtr pid_file_;
};
}; // end of isc::dhcp namespace
class DaemonImpl : public Daemon {
public:
static std::string getVersion(bool extended);
+
+ using Daemon::makePIDFileName;
};
std::string DaemonImpl::getVersion(bool extended) {
/// the default after each test completes.
~DaemonTest() {
isc::log::setDefaultLoggingOutput();
+ // Since it's static we need to clear it between tests
+ Daemon::setConfigFile("");
}
};
// Check that the verbose mode is not set by default.
Daemon instance2;
EXPECT_FALSE(instance2.getVerbose());
+
+ EXPECT_EQ("",instance2.getConfigFile());
+ EXPECT_EQ("",instance2.getProcName());
+ EXPECT_EQ(CfgMgr::instance().getDataDir(),instance2.getPIDFileDir());
+ EXPECT_EQ("",instance2.getPIDFileName());
+}
+
+// Verify config file accessors
+TEST_F(DaemonTest, getSetConfigFile) {
+ Daemon instance;
+
+ EXPECT_NO_THROW(instance.setConfigFile("test.txt"));
+ EXPECT_EQ("test.txt", instance.getConfigFile());
+}
+
+// Verify process name accessors
+TEST_F(DaemonTest, getSetProcName) {
+ Daemon instance;
+
+ EXPECT_NO_THROW(instance.setProcName("myproc"));
+ EXPECT_EQ("myproc", instance.getProcName());
+}
+
+// Verify PID file directory name accessors
+TEST_F(DaemonTest, getSetPIDFileDir) {
+ Daemon instance;
+
+ EXPECT_NO_THROW(instance.setPIDFileDir("/tmp"));
+ EXPECT_EQ("/tmp", instance.getPIDFileDir());
+}
+
+// Verify PID file name accessors.
+TEST_F(DaemonTest, setPIDFileName) {
+ Daemon instance;
+
+ // Verify that PID file name may not be set to empty
+ EXPECT_THROW(instance.setPIDFileName(""), BadValue);
+
+ EXPECT_NO_THROW(instance.setPIDFileName("myproc"));
+ EXPECT_EQ("myproc", instance.getPIDFileName());
+
+ // Verify that setPIDFileName cannot be called twice on the same instance.
+ EXPECT_THROW(instance.setPIDFileName("again"), InvalidOperation);
+}
+
+// Test the getVersion() redefinition
+TEST_F(DaemonTest, getVersion) {
+ EXPECT_THROW(Daemon::getVersion(false), NotImplemented);
+
+ ASSERT_NO_THROW(DaemonImpl::getVersion(false));
+
+ EXPECT_EQ(DaemonImpl::getVersion(false), "BASIC");
+
+ ASSERT_NO_THROW(DaemonImpl::getVersion(true));
+
+ EXPECT_EQ(DaemonImpl::getVersion(true), "EXTENDED");
+}
+
+// Verify makePIDFileName method
+TEST_F(DaemonTest, makePIDFileName) {
+ DaemonImpl instance;
+
+ // Verify that config file cannot be blank
+ instance.setProcName("notblank");
+ EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
+
+ // Verify that proc name cannot be blank
+ instance.setProcName("");
+ instance.setConfigFile("notblank");
+ EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
+
+ // Verify that config file must contain a file name
+ instance.setProcName("myproc");
+ instance.setConfigFile(".txt");
+ EXPECT_THROW(instance.makePIDFileName(), BadValue);
+ instance.setConfigFile("/tmp/");
+ EXPECT_THROW(instance.makePIDFileName(), BadValue);
+
+ // Given a valid config file name and proc name we should good to go
+ instance.setConfigFile("/tmp/test.conf");
+ std::string name;
+ EXPECT_NO_THROW(name = instance.makePIDFileName());
+
+ // Make sure the name is as we expect
+ std::ostringstream stream;
+ stream << CfgMgr::instance().getDataDir() << "/test.myproc.pid";
+ EXPECT_EQ(stream.str(), name);
+
+ // Verify that the default directory can be overridden
+ instance.setPIDFileDir("/tmp");
+ EXPECT_NO_THROW(name = instance.makePIDFileName());
+ EXPECT_EQ("/tmp/test.myproc.pid", name);
+}
+
+// Verifies the creation a PID file and that a pre-existing PID file
+// which points to a live PID causes a throw.
+TEST_F(DaemonTest, createPIDFile) {
+ DaemonImpl instance;
+
+ instance.setConfigFile("test.conf");
+ instance.setProcName("daemon_test");
+ instance.setPIDFileDir(TEST_DATA_BUILDDIR);
+
+ EXPECT_NO_THROW(instance.createPIDFile());
+
+ std::ostringstream stream;
+ stream << TEST_DATA_BUILDDIR << "/test.daemon_test.pid";
+ EXPECT_EQ(stream.str(), instance.getPIDFileName());
+
+ // If we try again, we should see our own PID file and fail
+ EXPECT_THROW(instance.createPIDFile(), DaemonPIDExists);
+}
+
+// Verifies that a pre-existing PID file which points to a dead PID
+// is overwritten.
+TEST_F(DaemonTest, createPIDFileOverwrite) {
+ DaemonImpl instance;
+
+ // We're going to use fork to generate a PID we KNOW is dead.
+ int pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ // This is the child, die right away. Tragic, no?
+ exit (0);
+ }
+
+ // Back in the parent test, we need to wait for the child to die
+ int stat;
+ int ret = waitpid(pid, &stat, 0);
+ ASSERT_EQ(ret, pid);
+
+ // Ok, so we should now have a PID that we know to be dead.
+ // Let's use it to create a PID file.
+ instance.setConfigFile("test.conf");
+ instance.setProcName("daemon_test");
+ instance.setPIDFileDir(TEST_DATA_BUILDDIR);
+ EXPECT_NO_THROW(instance.createPIDFile(pid));
+
+ // If we try to create the PID file again, this should work.
+ EXPECT_NO_THROW(instance.createPIDFile());
+}
+
+// Verifies that Daemon destruction deletes the PID file
+TEST_F(DaemonTest, PIDFileCleanup) {
+ boost::shared_ptr<DaemonImpl> instance;
+ instance.reset(new DaemonImpl);
+
+ instance->setConfigFile("test.conf");
+ instance->setProcName("daemon_test");
+ instance->setPIDFileDir(TEST_DATA_BUILDDIR);
+ EXPECT_NO_THROW(instance->createPIDFile());
+
+ // If we try again, we should see our own PID file
+ EXPECT_THROW(instance->createPIDFile(), DaemonPIDExists);
+
+ // Save the pid file name
+ std::string pid_file_name = instance->getPIDFileName();
+
+ // Now delete the Daemon instance. This should remove the
+ // PID file.
+ instance.reset();
+
+ struct stat stat_buf;
+ ASSERT_EQ(-1, stat(pid_file_name.c_str(), &stat_buf));
+ EXPECT_EQ(errno, ENOENT);
}
// Checks that configureLogger method is behaving properly.
EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
}
-// Test the getVersion() redefinition
-TEST_F(DaemonTest, getVersion) {
- EXPECT_THROW(Daemon::getVersion(false), NotImplemented);
-
- ASSERT_NO_THROW(DaemonImpl::getVersion(false));
-
- EXPECT_EQ(DaemonImpl::getVersion(false), "BASIC");
-
- ASSERT_NO_THROW(DaemonImpl::getVersion(true));
-
- EXPECT_EQ(DaemonImpl::getVersion(true), "EXTENDED");
-}
// More tests will appear here as we develop Daemon class.
#define PID_FILE_H
#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
#include <fstream>
#include <ostream>
#include <string>
std::string filename_;
};
+/// @brief Defines a shared pointer to a PIDFile
+typedef boost::shared_ptr<PIDFile> PIDFilePtr;
+
} // namespace isc::util
} // namespace isc