Line data Source code
1 : // Snap Websites Server -- snap websites server
2 : // Copyright (c) 2011-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // https://snapwebsites.org/
5 : // contact@m2osw.com
6 : //
7 : // This program is free software; you can redistribute it and/or modify
8 : // it under the terms of the GNU General Public License as published by
9 : // the Free Software Foundation; either version 2 of the License, or
10 : // (at your option) any later version.
11 : //
12 : // This program is distributed in the hope that it will be useful,
13 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : // GNU General Public License for more details.
16 : //
17 : // You should have received a copy of the GNU General Public License
18 : // along with this program; if not, write to the Free Software
19 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 :
21 :
22 : // self
23 : //
24 : #include "snapwebsites/snap_pid.h"
25 :
26 :
27 : // snapwebsites lib
28 : //
29 : #include "snapwebsites/log.h"
30 : #include "snapwebsites/snapwebsites.h"
31 :
32 :
33 : // C lib
34 : //
35 : #include <unistd.h>
36 : #include <fcntl.h>
37 : #include <sys/file.h>
38 :
39 :
40 : // last include
41 : //
42 : #include <snapdev/poison.h>
43 :
44 :
45 :
46 :
47 :
48 : /** \file
49 : * \brief Implementation of the snap_pid class.
50 : *
51 : * The snap_pid class is used to create a locked PID file which we keep
52 : * around while a service is running. It allows us to make sure only
53 : * one instance of a service is running. Attempting to run a second
54 : * instance will fail in the constructor.
55 : *
56 : * The destructor makes sure that the PID file gets unlocked and deleted.
57 : * It is generally not extremely useful, but it is not a bad thing to
58 : * clean up behind oneself.
59 : */
60 :
61 :
62 : namespace snap
63 : {
64 :
65 :
66 : /** \brief Create a PID file.
67 : *
68 : * This function is going to create a PID file for the currently running
69 : * process.
70 : *
71 : * The function attempts to lock the file before writing to it. If the lock
72 : * fails, then the file is already held by another process so we can't
73 : * move forward (i.e. if another instance of the same service is already
74 : * running, the second isntance has to quit.)
75 : *
76 : * \note
77 : * We do not 100% guarantee that the current file always gets deleted on exit.
78 : * The lock tells us whether a process is gone or not. We are planning to
79 : * have a stronger rule for the delete to happen, though.
80 : *
81 : * \param[in] service_name The name of this server.
82 : */
83 0 : snap_pid::snap_pid(std::string const & service_name)
84 0 : : f_service_name(service_name)
85 : {
86 0 : generate_filename(service_name);
87 0 : if(pipe2(f_pipes, O_CLOEXEC) != 0)
88 : {
89 0 : throw snapwebsites_exception_io_error("error trying to open pipe() to inform parent that the child PID file was created.");
90 : }
91 0 : }
92 :
93 :
94 : /** \brief Clean up the PID file.
95 : *
96 : * The destructor cleans up the PID file. Mainly, it attempts to delete
97 : * the file which has the side effect of removing the exclusive lock.
98 : *
99 : * If you hold a shared pointer to the snap_pid object, make sure to
100 : * reset it before calling exit() if such you do.
101 : */
102 0 : snap_pid::~snap_pid()
103 : {
104 0 : unlink_pid_file();
105 0 : close_pipes();
106 0 : }
107 :
108 :
109 : /** \brief Create the PID file.
110 : *
111 : * This function creates the PID file.
112 : *
113 : * The function returns success or failure to the parent using the pipes.
114 : *
115 : * On success, it sends true (0x01).
116 : *
117 : * On failure, it sends false (0x00).
118 : */
119 0 : void snap_pid::create_pid_file()
120 : {
121 0 : f_safe_fd.reset(open(f_pid_filename.c_str()
122 : , O_CLOEXEC | O_CREAT | O_TRUNC | O_WRONLY
123 : , S_IRUSR | S_IWUSR));
124 :
125 : // make sure the open() worked
126 : //
127 0 : if(!f_safe_fd)
128 : {
129 0 : SNAP_LOG_FATAL("Server \"")
130 0 : (f_service_name)
131 0 : ("\" could not create PID file \"")
132 0 : (f_pid_filename)
133 0 : ("\".");
134 0 : send_signal(false);
135 : throw snap_pid_exception_io_error(
136 : "Could not open PID file \""
137 0 : + f_pid_filename
138 0 : + "\".");
139 : }
140 :
141 : // attempt an exclusive lock
142 : //
143 0 : if(flock(f_safe_fd.get(), LOCK_EX) != 0)
144 : {
145 0 : SNAP_LOG_FATAL("Server \"")
146 0 : (f_service_name)
147 0 : ("\"could not lock PID file \"")
148 0 : (f_pid_filename)
149 0 : ("\". Another instance is already running?");
150 0 : send_signal(false);
151 : throw snap_pid_exception_io_error(
152 : "Could not lock PID file \""
153 0 : + f_pid_filename
154 0 : + "\". Another instance is already running?");
155 : }
156 :
157 0 : f_child_process = true;
158 :
159 0 : std::string const pid(std::to_string(getpid()) + "\n");
160 0 : if(write(f_safe_fd.get(), pid.c_str(), pid.length()) != static_cast<ssize_t>(pid.length()))
161 : {
162 0 : SNAP_LOG_FATAL("Server \"")
163 0 : (f_service_name)
164 0 : ("\"could not lock PID file \"")
165 0 : (f_pid_filename)
166 0 : ("\". Another instance is already running?");
167 0 : send_signal(false);
168 : throw snap_pid_exception_io_error(
169 : "Could not lock PID file \""
170 0 : + f_pid_filename
171 0 : + "\". Another instance is already running?");
172 : }
173 :
174 : // PID file is all good
175 : //
176 : // TBD: we may want to allow the caller to send this signal at a
177 : // later time assuming the child's initialization may not yet
178 : // be complete and we may want to return with exit(1) from
179 : // the parent if the child's initialization fails.
180 : //
181 0 : send_signal(true);
182 0 : }
183 :
184 :
185 : /** \brief Transforms the service name in a PID filename.
186 : *
187 : * This function retrieves the "run_path" parameter from the snapserver
188 : * configuration file (/etc/snapwebsites/[snapwebsites.d/]snapserver.conf)
189 : * and append the service name. It also adds a .pid extension.
190 : *
191 : * The \p service_name parameter is not expected to include a slash or
192 : * an extension.
193 : *
194 : * \param[in] service_name The name of the service getting a PID file.
195 : */
196 0 : void snap_pid::generate_filename(std::string const & service_name)
197 : {
198 0 : if(service_name.find('/') != std::string::npos)
199 : {
200 0 : SNAP_LOG_FATAL("Service name \"")
201 0 : (service_name)
202 0 : ("\"cannot include a slash (/) character.");
203 : throw snap_pid_exception_invalid_parameter(
204 : "Service name \""
205 0 : + service_name
206 0 : + "\"cannot include a slash (/) character.");
207 : }
208 :
209 : // get the "run_path=..." parameter from the snapserver.conf file
210 : //
211 0 : snap::snap_config config("snapserver");
212 0 : std::string run_path("/run/snapwebsites");
213 0 : if(config.has_parameter("run_path"))
214 : {
215 0 : run_path = config.get_parameter("run_path");
216 : }
217 :
218 : // generate the filename now
219 : //
220 0 : f_pid_filename = run_path + "/" + service_name + ".pid";
221 0 : }
222 :
223 :
224 : /** \brief Remove the PID file.
225 : *
226 : * This function removes the PID file from disk. This has the side effect
227 : * of unlocking the file too.
228 : *
229 : * \attention
230 : * Only the child process is allowed to delete the PID file. This function
231 : * will do nothing if called by the parent process (which happens in the
232 : * destructor.)
233 : */
234 0 : void snap_pid::unlink_pid_file()
235 : {
236 0 : if(f_child_process)
237 : {
238 0 : unlink(f_pid_filename.c_str());
239 : }
240 0 : }
241 :
242 :
243 : /** \brief Close the communication pipes.
244 : *
245 : * This function close the two pipes created by the constructor. This
246 : * ensures that the communication happens only once and that the
247 : * pipe resources get resolved.
248 : */
249 0 : void snap_pid::close_pipes()
250 : {
251 0 : if(f_pipes[0] != -1)
252 : {
253 0 : close(f_pipes[0]);
254 0 : f_pipes[0] = -1;
255 : }
256 0 : if(f_pipes[1] != -1)
257 : {
258 0 : close(f_pipes[1]);
259 0 : f_pipes[1] = -1;
260 : }
261 0 : }
262 :
263 :
264 : /** \brief Send the parent process a signal about the results.
265 : *
266 : * This function is used by the child to send a signal to the parent
267 : * letting it know that the PID file was properly created.
268 : *
269 : * The parent must call the wait_signal() function to make sure that
270 : * the PID gets created before it returns. In the snapserver is it
271 : * done in the server::detach() function.
272 : *
273 : * \param[in] result true if the PID file was created successfully.
274 : */
275 0 : void snap_pid::send_signal(bool result)
276 : {
277 0 : char c[1] = { static_cast<char>(result) };
278 0 : if(write(f_pipes[1], c, 1) != 1)
279 : {
280 0 : throw snapwebsites_exception_io_error("error while writing to the pipe between parent and child, letting parent know that the PID file is ready.");
281 : }
282 0 : close_pipes();
283 0 : }
284 :
285 :
286 : /** \brief Wait for the child's signal.
287 : *
288 : * This function waits for the child to send us one byte to let us know
289 : * whether the creation of the PID file succeeded or not.
290 : *
291 : * If the function returns false, we can let systemd know that the
292 : * initialization was not a success.
293 : *
294 : * \note
295 : * The function can be called multiple times. It will wait for the child
296 : * process only the first time. Further calls will just return the same
297 : * result over and over again.
298 : *
299 : * \return true if the child created the PID successfully, false otherwise.
300 : */
301 0 : bool snap_pid::wait_signal()
302 : {
303 0 : if(f_pipes[0] != -1)
304 : {
305 0 : if(read(f_pipes[0], &f_result, 1) != 1)
306 : {
307 0 : throw snapwebsites_exception_io_error("error while reading from the pipe between parent and child, parent will never know whether the PID file is ready.");
308 : }
309 :
310 0 : close_pipes();
311 : }
312 :
313 0 : return f_result == 1;
314 : }
315 :
316 :
317 6 : } // namespace snap
318 : // vim: ts=4 sw=4 et
|