1 // Copyright (C) 2015-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 <util/pid_file.h>
10 #include <gtest/gtest.h>
11 #include <fstream>
12 #include <signal.h>
13 #include <stdint.h>
14 
15 namespace {
16 using namespace isc::util;
17 
18 // Filenames used for testing.
19 const char* TESTNAME = "pid_file.test";
20 
21 class PIDFileTest : public ::testing::Test {
22 public:
23 
24     /// @brief Constructor
25     PIDFileTest() = default;
26 
27     /// @brief Destructor
28     virtual ~PIDFileTest() = default;
29 
30     /// @brief Prepends the absolute path to the file specified
31     /// as an argument.
32     ///
33     /// @param filename Name of the file.
34     /// @return Absolute path to the test file.
35     static std::string absolutePath(const std::string& filename);
36 
37     /// @brief Generate a random number for use as a PID
38     ///
39     /// @param start - the start of the range we want the PID in
40     /// @param range - the size of the range for our PID
41     ///
42     /// @return returns a random value between start and start + range
randomizePID(const uint32_t start,const uint32_t range)43     int randomizePID(const uint32_t start, const uint32_t range) {
44         int pid;
45 
46         for (pid = (random() % range) + start;
47              kill(pid, 0) == 0;
48              ++pid)
49             ;
50 
51         return (pid);
52     }
53 
54 protected:
55     /// @brief Removes any old test files before the test
SetUp()56     virtual void SetUp() {
57         removeTestFile();
58     }
59 
60     /// @brief Removes any remaining test files after the test
TearDown()61     virtual void TearDown() {
62         removeTestFile();
63     }
64 
65 private:
66     /// @brief Removes any remaining test files
removeTestFile() const67     void removeTestFile() const {
68         static_cast<void>(remove(absolutePath(TESTNAME).c_str()));
69     }
70 
71 };
72 
73 std::string
absolutePath(const std::string & filename)74 PIDFileTest::absolutePath(const std::string& filename) {
75     std::ostringstream s;
76     s << TEST_DATA_BUILDDIR << "/" << filename;
77 
78     return (s.str());
79 }
80 
81 /// @brief Test file writing and deletion. Start by removing
82 /// any leftover file. Then write a known PID to the file and
83 /// attempt to read the file and verify the PID. Next write
84 /// a second and verify a second PID to verify that an existing
85 /// file is properly overwritten.
86 
TEST_F(PIDFileTest,writeAndDelete)87 TEST_F(PIDFileTest, writeAndDelete) {
88     PIDFile pid_file(absolutePath(TESTNAME));
89     std::ifstream fs;
90     int pid(0);
91 
92     // Write a known process id
93     pid_file.write(10);
94 
95     // Read the file and compare the pid
96     fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
97     fs >> pid;
98     EXPECT_TRUE(fs.good());
99     EXPECT_EQ(pid, 10);
100     fs.close();
101 
102     // Write a second known process id
103     pid_file.write(20);
104 
105     // And compare the second pid
106     fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
107     fs >> pid;
108     EXPECT_TRUE(fs.good());
109     EXPECT_EQ(pid, 20);
110     fs.close();
111 
112     // Delete the file
113     pid_file.deleteFile();
114 
115     // And verify that it's gone
116     fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
117     EXPECT_FALSE(fs.good());
118     fs.close();
119 }
120 
121 /// @brief Test checking a PID. Write the PID of the current
122 /// process to the PID file then verify that check indicates
123 /// the process is running.
TEST_F(PIDFileTest,pidInUse)124 TEST_F(PIDFileTest, pidInUse) {
125     PIDFile pid_file(absolutePath(TESTNAME));
126 
127     // Write the current PID
128     pid_file.write();
129 
130     // Check if we think the process is running
131     EXPECT_EQ(getpid(), pid_file.check());
132 }
133 
134 /// @brief Test checking a PID. Write a PID that isn't in use
135 /// to the PID file and verify that check indicates the process
136 /// isn't running. The PID may get used between when we select it
137 /// and write the file and when we check it. To minimize false
138 /// errors if the first call to check fails we try again with a
139 /// different range of values and only if both attempts fail do
140 /// we declare the test to have failed.
TEST_F(PIDFileTest,pidNotInUse)141 TEST_F(PIDFileTest, pidNotInUse) {
142     PIDFile pid_file(absolutePath(TESTNAME));
143     int pid;
144 
145     // get a pid between 10000 and 20000
146     pid = randomizePID(10000, 10000);
147 
148     // write it
149     pid_file.write(pid);
150 
151     // Check to see if we think the process is running
152     if (pid_file.check() == 0) {
153         return;
154     }
155 
156     // get a pid between 40000 and 50000
157     pid = randomizePID(10000, 40000);
158 
159     // write it
160     pid_file.write(pid);
161 
162     // Check to see if we think the process is running
163     EXPECT_EQ(0, pid_file.check());
164 }
165 
166 /// @brief Test checking a PID.  Write garbage to the PID file
167 /// and verify that check throws an error. In this situation
168 /// the caller should probably log an error and may decide to
169 /// continue or not depending on the requirements.
TEST_F(PIDFileTest,pidGarbage)170 TEST_F(PIDFileTest, pidGarbage) {
171     PIDFile pid_file(absolutePath(TESTNAME));
172     std::ofstream fs;
173 
174     // Open the file and write garbage to it
175     fs.open(absolutePath(TESTNAME).c_str(), std::ofstream::out);
176     fs << "text" << std::endl;
177     fs.close();
178 
179     // Run the check, we expect to get an exception
180     EXPECT_THROW(pid_file.check(), PIDCantReadPID);
181 }
182 
183 /// @brief Test failing to write a file.
TEST_F(PIDFileTest,pidWriteFail)184 TEST_F(PIDFileTest, pidWriteFail) {
185     PIDFile pid_file(absolutePath(TESTNAME));
186 
187     // Create the test file and change it's permission bits
188     // so we can't write to it.
189     pid_file.write(10);
190     chmod(absolutePath(TESTNAME).c_str(), S_IRUSR);
191 
192     // Now try a write to the file, expecting an exception
193     EXPECT_THROW(pid_file.write(10), PIDFileError);
194 
195     // Don't forget to restore the write right for the next test
196     chmod(absolutePath(TESTNAME).c_str(), S_IRUSR | S_IWUSR);
197 }
198 
199 /// @brief Test deleting a file that doesn't exist
TEST_F(PIDFileTest,noDeleteFile)200 TEST_F(PIDFileTest, noDeleteFile) {
201     PIDFile pid_file(absolutePath(TESTNAME));
202 
203     // Delete a file we haven't created
204     pid_file.deleteFile();
205 }
206 } // end of anonymous namespace
207