Line data Source code
1 : #include <pid/daemonize.h>
2 :
3 : // If the local compiler check passes then we have std::string_view available
4 : // and so can use the C++17 guarded overloads for pid::hashed_string
5 : #define PID_UTILS_CXX17_AVAILABLE 1
6 : #include <pid/hashed_string.h>
7 : #undef PID_UTILS_CXX17_AVAILABLE
8 :
9 : #include <string>
10 : #include <thread>
11 : #include <chrono>
12 : #include <iostream>
13 : #include <fstream>
14 : #include <cstdlib>
15 :
16 : #include <unistd.h>
17 : #include <csignal>
18 : #include <cstring>
19 : #include <sys/types.h>
20 : #include <sys/stat.h>
21 :
22 : namespace {
23 :
24 56 : std::string get_temp_file_path_for(std::string_view daemon) {
25 112 : return "/tmp/pid_daemonize_" + std::to_string(pid::hashed_string(daemon));
26 : }
27 :
28 14 : void delete_temp_file_path_for(std::string_view daemon) {
29 14 : remove(get_temp_file_path_for(daemon).c_str());
30 14 : }
31 :
32 41 : long get_pid_of(std::string_view daemon) {
33 41 : const auto temp_file_path = get_temp_file_path_for(daemon);
34 :
35 41 : auto pid_file = std::ifstream{temp_file_path};
36 41 : if (pid_file.is_open()) {
37 30 : int pid{};
38 30 : pid_file >> pid;
39 30 : return pid;
40 : } else {
41 11 : return -1;
42 : }
43 41 : }
44 :
45 25 : bool is_daemon_alive(int pid) {
46 25 : return kill(pid, 0) == 0;
47 : }
48 :
49 : } // namespace
50 :
51 : namespace pid::daemonize {
52 :
53 10 : void start(const std::string& executable_path,
54 : const std::vector<std::string>& arguments) {
55 10 : start(executable_path, executable_path, arguments);
56 5 : }
57 :
58 0 : void start(const std::string& executable_path,
59 : const std::vector<std::string>& arguments,
60 : const std::vector<std::pair<std::string, std::string>>& environment,
61 : bool keep_fd) {
62 0 : start(executable_path, executable_path, arguments, environment, keep_fd);
63 0 : }
64 :
65 10 : void start(std::string_view name, const std::string& executable_path,
66 : const std::vector<std::string>& arguments) {
67 10 : start(name, [&] {
68 : // Prepare arguments list
69 0 : std::vector<const char*> argv;
70 0 : argv.reserve(arguments.size() + 2);
71 0 : argv.push_back(executable_path.c_str());
72 0 : for (const auto& arg : arguments) {
73 0 : argv.push_back(arg.c_str());
74 : }
75 0 : argv.push_back(nullptr);
76 :
77 : // Replace current process
78 0 : if (execv(executable_path.c_str(), const_cast<char**>(argv.data())) ==
79 : -1) {
80 0 : throw std::runtime_error("unable to launch " + executable_path);
81 : }
82 0 : });
83 5 : }
84 :
85 2 : void start(std::string_view name, const std::string& executable_path,
86 : const std::vector<std::string>& arguments,
87 : const std::vector<std::pair<std::string, std::string>>& environment,
88 : bool keep_fd) {
89 2 : start(
90 : name,
91 0 : [&] {
92 : // Prepare arguments list
93 0 : std::vector<const char*> argv;
94 0 : argv.reserve(arguments.size() + 2);
95 0 : argv.push_back(executable_path.c_str());
96 0 : for (const auto& arg : arguments) {
97 0 : argv.push_back(arg.c_str());
98 : }
99 0 : argv.push_back(nullptr);
100 :
101 : // building the environment variables to give to the exec function
102 0 : std::vector<std::string> list_of_env;
103 0 : list_of_env.reserve(environment.size());
104 0 : for (const auto& var : environment) {
105 0 : list_of_env.push_back(std::string(var.first) + "=" +
106 0 : std::string(var.second));
107 : }
108 0 : std::vector<const char*> env;
109 0 : env.reserve(list_of_env.size() + 1);
110 0 : for (const auto& arg : list_of_env) {
111 0 : env.push_back(arg.c_str());
112 : }
113 0 : env.push_back(nullptr);
114 :
115 : // Replace current process
116 0 : if (execve(executable_path.c_str(), const_cast<char**>(argv.data()),
117 0 : const_cast<char**>(env.data())) == -1) {
118 0 : throw std::runtime_error("unable to launch " + executable_path);
119 : }
120 0 : },
121 : {}, keep_fd);
122 1 : }
123 13 : void start(std::string_view name, std::function<void()> function) {
124 13 : start(name, std::move(function), {});
125 6 : }
126 :
127 15 : void start(std::string_view name, std::function<void()> function,
128 : const std::vector<std::pair<std::string, std::string>>& env,
129 : bool keep_fd) {
130 : std::array<int, 2> descriptors;
131 15 : if (pipe(descriptors.data()) < 0) {
132 0 : throw std::runtime_error("pid::daemonize::start: failed to create a "
133 0 : "new pipe for the daemon");
134 : return;
135 : }
136 15 : pid_t pid = fork();
137 15 : if (pid > 0) {
138 : // Calling process
139 : // close the write descriptor, reading only
140 7 : close(descriptors[1]);
141 : int val;
142 : // blocking read
143 7 : if (read(descriptors[0], &val, sizeof(val)) < 0) {
144 : std::cerr << "pid::daemonize::start: failed to read pipe written "
145 0 : "by the daemon"
146 0 : << std::endl;
147 : }
148 7 : close(descriptors[0]);
149 7 : return;
150 8 : } else if (pid == 0) {
151 : // Child process
152 :
153 : // Create a new session
154 8 : if (setsid() < 0) {
155 : std::cerr << "pid::daemonize::start: failed to create a new "
156 0 : "session for the daemon"
157 0 : << std::endl;
158 0 : std::exit(-1);
159 : }
160 :
161 8 : signal(SIGCHLD, SIG_IGN);
162 8 : signal(SIGHUP, SIG_IGN);
163 :
164 8 : pid = fork();
165 :
166 8 : if (pid > 0) {
167 : // Exit parent process
168 : // closing all pipes
169 7 : close(descriptors[1]);
170 7 : close(descriptors[0]);
171 7 : std::exit(0);
172 1 : } else if (pid == 0) {
173 : // Daemon process
174 1 : umask(0);
175 : // write the file containing PID of daemon process
176 : {
177 1 : const auto current_pid = getpid();
178 1 : const auto temp_file_path = get_temp_file_path_for(name);
179 1 : auto pid_file = std::ofstream(temp_file_path);
180 1 : if (not pid_file.is_open()) {
181 : std::cerr << "pid::daemonize::start: failed to create "
182 : "temporary file "
183 : << temp_file_path
184 0 : << " to store the daemon's pid. Aborting launch."
185 0 : << std::endl;
186 0 : std::exit(1);
187 : }
188 1 : pid_file << current_pid;
189 1 : }
190 : // unlock calling process
191 1 : close(descriptors[0]);
192 1 : int val = 1;
193 1 : if (write(descriptors[1], &val, sizeof(val)) < 0) {
194 : std::cerr
195 0 : << "pid::daemonize::start: daemon failed to write pipe"
196 0 : << std::endl;
197 0 : std::exit(-1);
198 : }
199 1 : close(descriptors[1]);
200 : // Close all open file descriptors
201 1 : if (not keep_fd) {
202 1026 : for (auto file_descriptor = sysconf(_SC_OPEN_MAX);
203 1026 : file_descriptor >= 0; file_descriptor--) {
204 1025 : close(static_cast<int>(file_descriptor));
205 : }
206 : }
207 :
208 : // setting enviroment variables
209 1 : for (const auto& var : env) {
210 0 : if (setenv(var.first.c_str(), var.second.c_str(), 1) < 0) {
211 : std::cerr << "pid::daemonize::start: daemon failed to set "
212 0 : "environment variables"
213 0 : << std::endl;
214 0 : std::exit(-3);
215 : }
216 : }
217 : // calling the user defined function
218 1 : function();
219 :
220 : // Make sure the process exits
221 1 : std::exit(0);
222 : } else {
223 : std::cerr << "pid::daemonize::start: failed to create the "
224 0 : "daemon process"
225 0 : << std::endl;
226 0 : std::exit(-2);
227 : }
228 : } else {
229 0 : throw std::runtime_error("pid::daemonize::start: failed to create a "
230 0 : "new process for the daemon");
231 : }
232 : }
233 :
234 5 : void stop(std::string_view daemon, int signal) {
235 5 : const auto pid = get_pid_of(daemon);
236 5 : if (pid >= 0) {
237 5 : const auto ret = kill(static_cast<pid_t>(pid), signal);
238 5 : if (ret == 0) {
239 5 : ::delete_temp_file_path_for(daemon);
240 : } else {
241 0 : std::string err = strerror(errno);
242 : std::cerr << "Cannot send signal to daemon '" << daemon
243 0 : << "'. kill() returned '" << err << "'\n";
244 0 : }
245 : } else {
246 0 : std::cerr << "Cannot stop unknown daemon '" << daemon << "'\n";
247 : }
248 5 : }
249 :
250 36 : bool is_alive(std::string_view daemon) {
251 36 : const auto pid = get_pid_of(daemon);
252 36 : if (pid >= 0) {
253 25 : const bool is_alive = ::is_daemon_alive(static_cast<pid_t>(pid));
254 25 : if (not is_alive) {
255 2 : ::delete_temp_file_path_for(daemon);
256 : }
257 25 : return is_alive;
258 : } else {
259 11 : return false;
260 : }
261 : }
262 :
263 6 : void wait_started(std::string_view daemon,
264 : std::chrono::duration<double> polling_rate) {
265 : while (true) {
266 6 : if (is_alive(daemon)) {
267 6 : break;
268 : } else {
269 0 : std::this_thread::sleep_for(polling_rate);
270 : }
271 : }
272 6 : }
273 :
274 18 : void wait_stopped(std::string_view daemon,
275 : std::chrono::duration<double> polling_rate) {
276 : while (true) {
277 18 : if (not is_alive(daemon)) {
278 7 : break;
279 : } else {
280 11 : std::this_thread::sleep_for(polling_rate);
281 : }
282 : }
283 7 : ::delete_temp_file_path_for(daemon);
284 7 : }
285 :
286 : } // namespace pid::daemonize
|