Browse Source

Merge branch 'trac3687' Add PIDFile class

Conflicts:
	ChangeLog

Add PIDFile class to write, delete and check PID files.

Use the PIDFile class in the LFC process to ensure that
only one LFC is running at a time.
Shawn Routhier 10 years ago
parent
commit
1e92382aaa

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+882.	[func]		sar
+	A utility class has been added which handles writing and
+	deleting pid files as well as checking if the process with
+	the given pid is running.
+
 881.	[func]		kalmus
 881.	[func]		kalmus
 	Extracting hardware/MAC address from the DHCPv6 remote-id
 	Extracting hardware/MAC address from the DHCPv6 remote-id
 	option is now implemented.
 	option is now implemented.

+ 15 - 3
src/bin/lfc/kea-lfc.xml

@@ -46,7 +46,8 @@
       <command>kea-lfc</command>
       <command>kea-lfc</command>
       <arg><option>-4|-6</option></arg>
       <arg><option>-4|-6</option></arg>
       <arg><option>-c <replaceable class="parameter">config-file</replaceable></option></arg>
       <arg><option>-c <replaceable class="parameter">config-file</replaceable></option></arg>
-      <arg><option>-p <replaceable class="parameter">previous-file</replaceable></option></arg>
+      <arg><option>-p <replaceable class="parameter">pid-file</replaceable></option></arg>
+      <arg><option>-x <replaceable class="parameter">previous-file</replaceable></option></arg>
       <arg><option>-i <replaceable class="parameter">copy-file</replaceable></option></arg>
       <arg><option>-i <replaceable class="parameter">copy-file</replaceable></option></arg>
       <arg><option>-o <replaceable class="parameter">output-file</replaceable></option></arg>
       <arg><option>-o <replaceable class="parameter">output-file</replaceable></option></arg>
       <arg><option>-f <replaceable class="parameter">finish-file</replaceable></option></arg>
       <arg><option>-f <replaceable class="parameter">finish-file</replaceable></option></arg>
@@ -117,7 +118,7 @@
           Configuration file including the configuration for
           Configuration file including the configuration for
           <command>kea-lfc</command> process.  It may also
           <command>kea-lfc</command> process.  It may also
           contain configuration entries for other Kea services.
           contain configuration entries for other Kea services.
-          Currently <command>kea-lfc</command> gets all of its arguments from 
+          Currently <command>kea-lfc</command> gets all of its arguments from
           the comamnd line, in the future it will be extended to get some arguments
           the comamnd line, in the future it will be extended to get some arguments
           from the config file.
           from the config file.
         </para></listitem>
         </para></listitem>
@@ -126,7 +127,18 @@
       <varlistentry>
       <varlistentry>
         <term><option>-p</option></term>
         <term><option>-p</option></term>
         <listitem><para>
         <listitem><para>
-          Previous lease file - When <command>kea-lfc</command> starts this
+          PID file - When the <command>kea-lfc</command> process starts
+          it attempts to determine if another instance of the process is
+          already running by examining the pid file.  If one is running
+          it aborts the new process.  If one isn't running it writes its
+          pid into the pid file.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-x</option></term>
+        <listitem><para>
+          Previous or ex lease file - When <command>kea-lfc</command> starts this
           is the result of any previous run of <command>kea-lfc</command>.
           is the result of any previous run of <command>kea-lfc</command>.
           When <command>kea-lfc</command> finishes it is the result of this run.
           When <command>kea-lfc</command> finishes it is the result of this run.
           If <command>kea-lfc</command> is interrupted before compelting
           If <command>kea-lfc</command> is interrupted before compelting

+ 71 - 19
src/bin/lfc/lfc_controller.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <lfc/lfc_controller.h>
 #include <lfc/lfc_controller.h>
+#include <util/pid_file.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <config.h>
 #include <config.h>
 #include <iostream>
 #include <iostream>
@@ -21,6 +22,7 @@
 #include <stdlib.h>
 #include <stdlib.h>
 
 
 using namespace std;
 using namespace std;
+using namespace isc::util;
 
 
 namespace isc {
 namespace isc {
 namespace lfc {
 namespace lfc {
@@ -42,12 +44,49 @@ LFCController::~LFCController() {
 
 
 void
 void
 LFCController::launch(int argc, char* argv[]) {
 LFCController::launch(int argc, char* argv[]) {
-  try {
-    parseArgs(argc, argv);
-  } catch (const InvalidUsage& ex) {
-    usage(ex.what());
-    throw;  // rethrow it
-  }
+    try {
+        parseArgs(argc, argv);
+    } catch (const InvalidUsage& ex) {
+        usage(ex.what());
+        throw;  // rethrow it
+    }
+
+    std::cerr << "Starting lease file cleanup" << std::endl;
+
+    // verify we are the only instance
+    PIDFile pid_file(pid_file_);
+
+    try {
+        if (pid_file.check() == true) {
+            // Already running instance, bail out
+            std::cerr << "LFC instance already running" <<  std::endl;
+            return;
+        }
+    } catch (const PIDFileError& pid_ex) {
+        std::cerr << pid_ex.what() << std::endl;
+        return;
+    }
+
+    // create the pid file for this instance
+    try {
+        pid_file.write();
+    } catch (const PIDFileError& pid_ex) {
+        std::cerr << pid_ex.what() << std::endl;
+        return;
+    }
+
+    // do other work (TBD)
+    std::cerr << "Add code to perform lease cleanup" << std::endl;
+
+    // delete the pid file for this instance
+    try {
+        pid_file.deleteFile();
+    } catch (const PIDFileError& pid_ex) {
+        std::cerr << pid_ex.what() << std::endl;
+        return;
+    }
+
+    std::cerr << "LFC complete" << std::endl;
 }
 }
 
 
 void
 void
@@ -56,7 +95,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
 
 
     opterr = 0;
     opterr = 0;
     optind = 1;
     optind = 1;
-    while ((ch = getopt(argc, argv, ":46dvVp:i:o:c:f:")) != -1) {
+    while ((ch = getopt(argc, argv, ":46dvVp:x:i:o:c:f:")) != -1) {
         switch (ch) {
         switch (ch) {
         case '4':
         case '4':
             // Process DHCPv4 lease files.
             // Process DHCPv4 lease files.
@@ -84,9 +123,17 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
             break;
 
 
         case 'p':
         case 'p':
-            // Previous file name.
+            // PID file name.
             if (optarg == NULL) {
             if (optarg == NULL) {
-                isc_throw(InvalidUsage, "Previous file name missing");
+                isc_throw(InvalidUsage, "PID file name missing");
+            }
+            pid_file_ = optarg;
+            break;
+
+        case 'x':
+            // Previous (or ex) file name.
+            if (optarg == NULL) {
+                isc_throw(InvalidUsage, "Previous (ex) file name missing");
             }
             }
             previous_file_ = optarg;
             previous_file_ = optarg;
             break;
             break;
@@ -108,7 +155,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
             break;
 
 
         case 'f':
         case 'f':
-            // Output file name.
+            // Finish file name.
             if (optarg == NULL) {
             if (optarg == NULL) {
                 isc_throw(InvalidUsage, "Finish file name missing");
                 isc_throw(InvalidUsage, "Finish file name missing");
             }
             }
@@ -116,7 +163,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
             break;
 
 
         case 'c':
         case 'c':
-            // Previous file name.
+            // Configuration file name
             if (optarg == NULL) {
             if (optarg == NULL) {
                 isc_throw(InvalidUsage, "Configuration file name missing");
                 isc_throw(InvalidUsage, "Configuration file name missing");
             }
             }
@@ -151,6 +198,10 @@ LFCController::parseArgs(int argc, char* argv[]) {
         isc_throw(InvalidUsage, "DHCP version required");
         isc_throw(InvalidUsage, "DHCP version required");
     }
     }
 
 
+    if (pid_file_.empty()) {
+        isc_throw(InvalidUsage, "PID file not specified");
+    }
+
     if (previous_file_.empty()) {
     if (previous_file_.empty()) {
         isc_throw(InvalidUsage, "Previous file not specified");
         isc_throw(InvalidUsage, "Previous file not specified");
     }
     }
@@ -174,12 +225,12 @@ LFCController::parseArgs(int argc, char* argv[]) {
     // If verbose is set echo the input information
     // If verbose is set echo the input information
     if (verbose_ == true) {
     if (verbose_ == true) {
       std::cerr << "Protocol version:    DHCPv" << protocol_version_ << std::endl
       std::cerr << "Protocol version:    DHCPv" << protocol_version_ << std::endl
-                << "Previous lease file: " << previous_file_ << std::endl
-                << "Copy lease file:     " << copy_file_ << std::endl
-                << "Output lease file:   " << output_file_ << std::endl
-                << "Finishn file:        " << finish_file_ << std::endl
-                << "Config file:         " << config_file_ << std::endl
-                << "PID file:            " << pid_file_ << std::endl;
+                << "Previous or ex lease file: " << previous_file_ << std::endl
+                << "Copy lease file:           " << copy_file_ << std::endl
+                << "Output lease file:         " << output_file_ << std::endl
+                << "Finishn file:              " << finish_file_ << std::endl
+                << "Config file:               " << config_file_ << std::endl
+                << "PID file:                  " << pid_file_ << std::endl;
     }
     }
 }
 }
 
 
@@ -190,9 +241,10 @@ LFCController::usage(const std::string& text) {
     }
     }
 
 
     std::cerr << "Usage: " << lfc_bin_name_ << std::endl
     std::cerr << "Usage: " << lfc_bin_name_ << std::endl
-              << " [-4|-6] -p file -i file -o file -f file -c file" << std::endl
+              << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
               << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
               << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
-              << "   -p <file>: previous lease file" << std::endl
+              << "   -p <file>: PID file" << std::endl
+              << "   -x <file>: previous or ex lease file" << std::endl
               << "   -i <file>: copy of lease file" << std::endl
               << "   -i <file>: copy of lease file" << std::endl
               << "   -o <file>: output lease file" << std::endl
               << "   -o <file>: output lease file" << std::endl
               << "   -f <file>: finish file" << std::endl
               << "   -f <file>: finish file" << std::endl

+ 4 - 4
src/bin/lfc/lfc_controller.h

@@ -59,10 +59,10 @@ public:
     /// of the process.  Provides the control logic:
     /// of the process.  Provides the control logic:
     ///
     ///
     /// -# parse command line arguments
     /// -# parse command line arguments
-    /// -# verifies that it is the only instance
-    /// -# creates pid file (TBD)
+    /// -# verify that it is the only instance
+    /// -# create pid file
     /// -# .... TBD
     /// -# .... TBD
-    /// -# remove pid file (TBD)
+    /// -# remove pid file
     /// -# exit to the caller
     /// -# exit to the caller
     ///
     ///
     /// @param argc Number of strings in the @c argv array.
     /// @param argc Number of strings in the @c argv array.
@@ -111,7 +111,7 @@ public:
         return (config_file_);
         return (config_file_);
     }
     }
 
 
-    /// @brief Gets the prevous file name
+    /// @brief Gets the previous file name
     ///
     ///
     /// @return Returns the path to the previous file
     /// @return Returns the path to the previous file
     std::string getPreviousFile() const {
     std::string getPreviousFile() const {

+ 15 - 8
src/bin/lfc/tests/lfc_controller_unittests.cc

@@ -45,7 +45,7 @@ TEST(LFCControllerTest, fullCommandLine) {
     // Verify that standard options can be parsed without error
     // Verify that standard options can be parsed without error
     char* argv[] = { const_cast<char*>("progName"),
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
                      const_cast<char*>("-4"),
-                     const_cast<char*>("-p"),
+                     const_cast<char*>("-x"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("copy"),
                      const_cast<char*>("copy"),
@@ -54,8 +54,10 @@ TEST(LFCControllerTest, fullCommandLine) {
                      const_cast<char*>("-c"),
                      const_cast<char*>("-c"),
                      const_cast<char*>("config"),
                      const_cast<char*>("config"),
                      const_cast<char*>("-f"),
                      const_cast<char*>("-f"),
-                     const_cast<char*>("finish") };
-    int argc = 12;
+                     const_cast<char*>("finish"),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>("pid") };
+    int argc = 14;
 
 
     ASSERT_NO_THROW(lfc_controller.parseArgs(argc, argv));
     ASSERT_NO_THROW(lfc_controller.parseArgs(argc, argv));
 
 
@@ -66,6 +68,7 @@ TEST(LFCControllerTest, fullCommandLine) {
     EXPECT_EQ(lfc_controller.getCopyFile(), "copy");
     EXPECT_EQ(lfc_controller.getCopyFile(), "copy");
     EXPECT_EQ(lfc_controller.getOutputFile(), "output");
     EXPECT_EQ(lfc_controller.getOutputFile(), "output");
     EXPECT_EQ(lfc_controller.getFinishFile(), "finish");
     EXPECT_EQ(lfc_controller.getFinishFile(), "finish");
+    EXPECT_EQ(lfc_controller.getPidFile(), "pid");
 }
 }
 
 
 /// @brief Verify that parsing a correct but incomplete line fails.
 /// @brief Verify that parsing a correct but incomplete line fails.
@@ -80,7 +83,7 @@ TEST(LFCControllerTest, notEnoughData) {
     // to the parse routine via the argc variable.
     // to the parse routine via the argc variable.
     char* argv[] = { const_cast<char*>("progName"),
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
                      const_cast<char*>("-4"),
-                     const_cast<char*>("-p"),
+                     const_cast<char*>("-x"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("copy"),
                      const_cast<char*>("copy"),
@@ -89,11 +92,13 @@ TEST(LFCControllerTest, notEnoughData) {
                      const_cast<char*>("-c"),
                      const_cast<char*>("-c"),
                      const_cast<char*>("config"),
                      const_cast<char*>("config"),
                      const_cast<char*>("-f"),
                      const_cast<char*>("-f"),
-                     const_cast<char*>("finish") };
+                     const_cast<char*>("finish"),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>("pid") };
 
 
     int argc = 1;
     int argc = 1;
 
 
-    for (; argc < 12; ++argc) {
+    for (; argc < 14; ++argc) {
         EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage)
         EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage)
             << "test failed for argc = " << argc;
             << "test failed for argc = " << argc;
     }
     }
@@ -114,7 +119,7 @@ TEST(LFCControllerTest, tooMuchData) {
 
 
     char* argv[] = { const_cast<char*>("progName"),
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
                      const_cast<char*>("-4"),
-                     const_cast<char*>("-p"),
+                     const_cast<char*>("-x"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("copy"),
                      const_cast<char*>("copy"),
@@ -124,11 +129,13 @@ TEST(LFCControllerTest, tooMuchData) {
                      const_cast<char*>("config"),
                      const_cast<char*>("config"),
                      const_cast<char*>("-f"),
                      const_cast<char*>("-f"),
                      const_cast<char*>("finish"),
                      const_cast<char*>("finish"),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>("pid"),
                      const_cast<char*>("some"),
                      const_cast<char*>("some"),
                      const_cast<char*>("other"),
                      const_cast<char*>("other"),
                      const_cast<char*>("args"),
                      const_cast<char*>("args"),
     };
     };
-    int argc = 15;
+    int argc = 17;
 
 
     // We expect an error as we have arguments that aren't valid
     // We expect an error as we have arguments that aren't valid
     EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage);
     EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage);

+ 1 - 0
src/lib/util/Makefile.am

@@ -18,6 +18,7 @@ libkea_util_la_SOURCES += time_utilities.h time_utilities.cc
 libkea_util_la_SOURCES += memory_segment.h
 libkea_util_la_SOURCES += memory_segment.h
 libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
 libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
 libkea_util_la_SOURCES += optional_value.h
 libkea_util_la_SOURCES += optional_value.h
+libkea_util_la_SOURCES += pid_file.h pid_file.cc
 libkea_util_la_SOURCES += range_utilities.h
 libkea_util_la_SOURCES += range_utilities.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h

+ 97 - 0
src/lib/util/pid_file.cc

@@ -0,0 +1,97 @@
+// Copyright (C) 2015 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 <util/pid_file.h>
+#include <cstdio>
+#include <signal.h>
+#include <unistd.h>
+
+namespace isc {
+namespace util {
+
+PIDFile::PIDFile(const std::string& filename)
+    : filename_(filename) {
+}
+
+PIDFile::~PIDFile() {
+}
+
+bool
+PIDFile::check() const {
+    std::ifstream fs(filename_.c_str());
+    int pid;
+    bool good;
+
+    // If we weren't able to open the file treat
+    // it as if the process wasn't running
+    if (!fs.is_open()) {
+        return (false);
+    }
+
+    // Try to get the pid, get the status and get rid of the file
+    fs >> pid;
+    good = fs.good();
+    fs.close();
+
+    // If we weren't able to read a pid send back an execption
+    if (!good) {
+        isc_throw(PIDCantReadPID, "Unable to read PID from file '"
+                  << filename_ << "'");
+    }
+
+    // If the process is still running return true
+    if (kill(pid, 0) == 0) {
+        return (true);
+    }
+
+    // No process
+    return (false);
+}
+
+void
+PIDFile::write() const {
+    write(getpid());
+}
+
+void
+PIDFile::write(int pid) const {
+  std::ofstream fs(filename_.c_str(), std::ofstream::trunc);
+
+    if (!fs.is_open()) {
+        isc_throw(PIDFileError, "Unable to open PID file '"
+                  << filename_ << "' for write");
+    }
+
+    // File is open, write the pid.
+    fs << pid << std::endl;
+
+    bool good = fs.good();
+    fs.close();
+
+    if (!good) {
+        isc_throw(PIDFileError, "Unable to write to PID file '"
+                  << filename_ << "'");
+    }
+}
+
+void
+PIDFile::deleteFile() const {
+    if (remove(filename_.c_str()) != 0) {
+        isc_throw(PIDFileError, "Unable to delete PID file '"
+                  << filename_ << "'");
+    }
+}
+
+} // namespace isc::util
+} // namespace isc

+ 101 - 0
src/lib/util/pid_file.h

@@ -0,0 +1,101 @@
+// Copyright (C) 2015 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.
+
+#ifndef PID_FILE_H
+#define PID_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <fstream>
+#include <ostream>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during PID file processing.
+class PIDFileError : public Exception {
+public:
+    PIDFileError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when an error occurs trying to read a PID
+/// from an opened file.
+class PIDCantReadPID : public Exception {
+public:
+    PIDCantReadPID(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Class to help with processing PID files
+///
+/// This is a utility class to manipulate PID file. It provides
+/// functions for writing and deleting a file containing a PID as
+/// well as for extracting a PID from a file and checking if the
+/// process is still running.
+class PIDFile {
+public:
+    /// @brief Constructor
+    ///
+    /// @param filename PID filename.
+    PIDFile(const std::string& filename);
+
+    /// @brief Destructor
+    ~PIDFile();
+
+    /// @brief Read the PID in from the file and check it.
+    ///
+    /// Read the PID in from the file then use it to see if
+    /// a process with that PID exists. If the file doesn't
+    /// exist treat it as the process not being there.
+    /// If the file exists but can't be read or it doesn't have
+    /// the proper format treat it as the process existing.
+    ///
+    /// @return true if the PID is in use, false otherwise
+    ///
+    /// @throw throws PIDCantReadPID if it was able to open the file
+    /// but was unable to read the PID from it.
+    bool check() const;
+
+    /// @brief Write the PID to the file.
+    ///
+    /// @param pid the pid to write to the file.
+    ///
+    /// @throw throws PIDFileError if it can't open or write to the PID file.
+    void write(int) const;
+
+    /// @brief Get PID of the current process and write it to the file.
+    ///
+    /// @throw throws PIDFileError if it can't open or write to the PID file.
+    void write() const;
+
+    /// @brief Delete the PID file.
+    ///
+    /// @throw throws PIDFileError if it can't delete the PID file
+    void deleteFile() const;
+
+    /// @brief Returns the path to the PID file.
+    std::string getFilename() const {
+        return (filename_);
+    }
+
+private:
+    /// @brief PID filename
+    std::string filename_;
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // PID_FILE_H

+ 1 - 0
src/lib/util/tests/Makefile.am

@@ -38,6 +38,7 @@ run_unittests_SOURCES += memory_segment_local_unittest.cc
 run_unittests_SOURCES += memory_segment_common_unittest.h
 run_unittests_SOURCES += memory_segment_common_unittest.h
 run_unittests_SOURCES += memory_segment_common_unittest.cc
 run_unittests_SOURCES += memory_segment_common_unittest.cc
 run_unittests_SOURCES += optional_value_unittest.cc
 run_unittests_SOURCES += optional_value_unittest.cc
+run_unittests_SOURCES += pid_file_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += socketsession_unittest.cc
 run_unittests_SOURCES += socketsession_unittest.cc

+ 195 - 0
src/lib/util/tests/pid_file_unittest.cc

@@ -0,0 +1,195 @@
+// Copyright (C) 2015 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 <util/pid_file.h>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <signal.h>
+#include <stdint.h>
+
+namespace {
+using namespace isc::util;
+
+// Filenames used for testing.
+const char* TESTNAME = "pid_file.test";
+
+class PIDFileTest : public ::testing::Test {
+public:
+    /// @brief Prepends the absolute path to the file specified
+    /// as an argument.
+    ///
+    /// @param filename Name of the file.
+    /// @return Absolute path to the test file.
+    static std::string absolutePath(const std::string& filename);
+
+    /// @brief Generate a random number for use as a PID
+    ///
+    /// @param start - the start of the range we want the PID in
+    /// @param range - the size of the range for our PID
+    ///
+    /// @return returns a random value between start and start + range
+    int randomizePID(const uint32_t start, const uint32_t range) {
+        int pid;
+
+        for (pid = (random() % range) + start;
+             kill(pid, 0) == 0;
+             ++pid)
+            ;
+
+        return (pid);
+    }
+
+protected:
+    /// @brief Removes any old test files before the test
+    virtual void SetUp() {
+        removeTestFile();
+    }
+
+    /// @brief Removes any remaining test files after the test
+    virtual void TearDown() {
+        removeTestFile();
+    }
+
+private:
+    /// @brief Removes any remaining test files
+    void removeTestFile() const {
+        remove(TESTNAME);
+    }
+
+};
+
+std::string
+PIDFileTest::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << TEST_DATA_BUILDDIR << "/" << filename;
+
+    return (s.str());
+}
+
+/// @brief Test file writing and deletion. Start by removing
+/// any leftover file. Then write a known PID to the file and
+/// attempt to read the file and verify the PID. Next write
+/// a second and verify a second PID to verify that an existing
+/// file is properly overwritten.
+
+TEST_F(PIDFileTest, writeAndDelete) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+    std::ifstream fs;
+    int pid(0);
+
+    // Write a known process id
+    pid_file.write(10);
+
+    // Read the file and compare the pid
+    fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+    fs >> pid;
+    EXPECT_TRUE(fs.good());
+    EXPECT_EQ(pid, 10);
+    fs.close();
+
+    // Write a second known process id
+    pid_file.write(20);
+
+    // And comapre the second pid
+    fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+    fs >> pid;
+    EXPECT_TRUE(fs.good());
+    EXPECT_EQ(pid, 20);
+    fs.close();
+
+    // Delete the file
+    pid_file.deleteFile();
+
+    // And verify that it's gone
+    fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+    EXPECT_FALSE(fs.good());
+    fs.close();
+}
+
+/// @brief Test checking a PID. Write the PID of the current
+/// process to the PID file then verify that check indicates
+/// the process is running.
+TEST_F(PIDFileTest, pidInUse) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+
+    // Write the current PID
+    pid_file.write();
+
+    // Check if we think the process is running
+    EXPECT_TRUE(pid_file.check());
+}
+
+/// @brief Test checking a PID. Write a PID that isn't in use
+/// to the PID file and verify that check indicates the process
+/// isn't running. The PID may get used between when we select it
+/// and write the file and when we check it. To minimize false
+/// errors if the first call to check fails we try again with a
+/// different range of values and only if both attempts fail do
+/// we declare the test to have failed.
+TEST_F(PIDFileTest, pidNotInUse) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+    int pid;
+
+    // get a pid betwen 10000 and 20000
+    pid = randomizePID(10000, 10000);
+
+    // write it
+    pid_file.write(pid);
+
+    // Check to see if we think the process is running
+    if (!pid_file.check()) {
+        return;
+    }
+
+    // get a pid betwen 40000 and 50000
+    pid = randomizePID(10000, 40000);
+
+    // write it
+    pid_file.write(pid);
+
+    // Check to see if we think the process is running
+    EXPECT_FALSE(pid_file.check());
+}
+
+/// @brief Test checking a PID.  Write garbage to the PID file
+/// and verify that check throws an error. In this situation
+/// the caller should probably log an error and may decide to
+/// continue or not depending on the requirements.
+TEST_F(PIDFileTest, pidGarbage) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+    std::ofstream fs;
+
+    // Open the file and write garbage to it
+    fs.open(absolutePath(TESTNAME).c_str(), std::ofstream::out);
+    fs << "text" << std::endl;
+    fs.close();
+
+    // Run the check, we expect to get an execption
+    EXPECT_THROW(pid_file.check(), PIDCantReadPID);
+}
+
+/// @brief Test failing to write a file.
+TEST_F(PIDFileTest, pidWriteFail) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+
+    // Create the test file and change it's permission bits
+    // so we can't write to it.
+    pid_file.write(10);
+    chmod(absolutePath(TESTNAME).c_str(), S_IRUSR);
+
+    // Now try a write to the file, expecting an exception
+    EXPECT_THROW(pid_file.write(10), PIDFileError);
+}
+
+} // end of anonymous namespace