1 /*------------------------------------------------------------------------------
2  *
3  * Copyright (c) 2011-2021, EURid vzw. All rights reserved.
4  * The YADIFA TM software product is provided under the BSD 3-clause license:
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  *        * Redistributions of source code must retain the above copyright
11  *          notice, this list of conditions and the following disclaimer.
12  *        * Redistributions in binary form must reproduce the above copyright
13  *          notice, this list of conditions and the following disclaimer in the
14  *          documentation and/or other materials provided with the distribution.
15  *        * Neither the name of EURid nor the names of its contributors may be
16  *          used to endorse or promote products derived from this software
17  *          without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  *------------------------------------------------------------------------------
32  *
33  */
34 
35 /** @defgroup
36  *  @ingroup
37  *  @brief
38  *
39  *
40  *
41  * @{
42  *
43  *----------------------------------------------------------------------------*/
44 
45 #include "dnscore/dnscore-config.h"
46 
47 #include <sys/file.h>
48 #include <fcntl.h>
49 #include <ctype.h>
50 #include <signal.h>
51 
52 #include "dnscore/pid.h"
53 
54 #include "dnscore/sys_types.h"
55 #include "dnscore/logger.h"
56 #include "dnscore/parser.h"
57 #include "dnscore/fdtools.h"
58 #include "dnscore/process.h"
59 
60 
61 /*------------------------------------------------------------------------------
62  * GLOBAL VARIABLES */
63 
64 extern logger_handle *g_system_logger;
65 #define MODULE_MSG_HANDLE g_system_logger
66 
67 /*------------------------------------------------------------------------------
68  * STATIC PROTOTYPES */
69 
70 /*------------------------------------------------------------------------------
71  * FUNCTIONS */
72 
73 /** \brief Read \b pid \b file, program quits on log_quit
74  *
75  *  @param[in] path
76  *  @param[in] file_name
77  *
78  *  @retval pid
79  *  @retval NOK (negative number),
80  *  @return otherwise log_quit will stop the program with correct exit code
81  */
82 pid_t
pid_file_read(const char * pid_file_path)83 pid_file_read(const char *pid_file_path)
84 {
85     ssize_t                                                        received;
86     int                                                                  fd;
87     char                                                                 *p;
88     u32                                                                 pid;
89     ya_result                                                           ret;
90     char                                                      buffer[8 + 1];
91 
92     /*    ------------------------------------------------------------    */
93 
94     yassert(pid_file_path != NULL);
95 
96     if(strlen(pid_file_path) > PATH_MAX)
97     {
98         log_err("pid file '%s': path is bigger than %i", pid_file_path, PATH_MAX);
99 
100         return INVALID_PATH;
101     }
102 
103     if(FAIL(fd = open_ex(pid_file_path, O_RDONLY|O_CLOEXEC)))
104     {
105         ret = ERRNO_ERROR;
106 
107         log_debug("pid file '%s': cannot open: %r", pid_file_path, ret);
108 
109         return ret; /* no file found : not running assumed */
110     }
111 
112     received = readfully(fd, buffer, sizeof(buffer) - 1);
113 
114     if(0 >= received)
115     {
116         if(received < 0)
117         {
118             ret = received;
119             log_err("pid file '%s': cannot read pid: %r", pid_file_path, ret);
120         }
121         else
122         {
123             ret = UNEXPECTED_EOF;
124         }
125 
126         return ret;
127     }
128 
129     buffer[received] = '\0';    /* Append a terminator for strlen */
130 
131     p = buffer;
132     while(isdigit(*p)!=0) p++;  /* Cut after the first character that is not a digit (ie: CR LF ...) */
133     *p = '\0';
134 
135     if(FAIL(ret = parse_u32_check_range(buffer, &pid, 0, MAX_S32, BASE_10)))
136     {
137         log_err("pid file '%s': invalid pid number: %r", pid_file_path, ret);
138 
139         return ret;
140     }
141 
142     close_ex(fd);      /* close the pid file */
143 
144     return (pid_t)pid;
145 }
146 
147 /** \brief Create or overwrite the \b pid \b file with its new process id
148  *
149  *  @param[in] config is a config_data structure
150  *
151  *  @retval OK
152  *  @retval YDF_ERROR_CHOWN if can not "chown"
153  *  @return otherwise log_quit will stop the program with correct exit code
154  */
155 ya_result
pid_file_create(pid_t * pid,const char * pid_file_path,uid_t new_uid,gid_t new_gid)156 pid_file_create(pid_t *pid, const char *pid_file_path, uid_t new_uid, gid_t new_gid)
157 {
158     ya_result ret;
159     int                                                                  fd;
160     mode_t                                               permissions = 0644;
161 #ifndef WIN32
162     uid_t                                                    uid = getuid();
163 #endif
164     char                                                         buffer[16];
165     pid_t pid_tmp;
166 
167     if(pid == NULL)
168     {
169         pid = &pid_tmp;
170     }
171 
172     /*    ------------------------------------------------------------    */
173 
174     yassert(pid_file_path != NULL);
175 
176     if(strlen(pid_file_path) > PATH_MAX)
177     {
178         log_err("pid file '%s': path is bigger than %i", pid_file_path, PATH_MAX);
179 
180         return INVALID_PATH;
181     }
182 
183     *pid           = getpid_ex();
184     int buffer_len = snprintf(buffer, sizeof(buffer), "%d\n", *pid); // VS complains for something that's Windows specific and wrong at the moment anyhow.
185 
186     yassert(buffer_len > 0);
187 
188     fd = open_create_ex(pid_file_path, O_WRONLY | O_CREAT | O_TRUNC, permissions);
189 
190     if(fd >= 0)
191     {
192         for(;;)
193         {
194 #ifndef WIN32
195             if(flock(fd, LOCK_EX|LOCK_NB) < 0)
196             {
197                 ret = errno;
198                 if(ret == EINTR)
199                 {
200                     continue;
201                 }
202 
203                 if(ret == EWOULDBLOCK)
204                 {
205                     // already locked
206                     close_ex(fd);
207                     return PID_LOCKED;
208                 }
209 
210                 abort();
211             }
212 #endif
213 
214             break;
215         }
216 
217         if(ISOK(ret = pid_check_running_program(pid_file_path, NULL)))
218         {
219             // got the lock
220 
221             if(writefully(fd, buffer, buffer_len) > 0)
222             {
223                 ret = SUCCESS;
224 #ifndef WIN32
225                 if(uid == 0)  // only applicable if you are root
226                 {
227                     if(chown(pid_file_path, new_uid, new_gid) >= 0)
228                     {
229                         log_debug("pid file '%s': created", pid_file_path);
230                     }
231                     else
232                     {
233                         ret = ERRNO_ERROR;
234                         log_err("pid file '%s': cannot change owner.group to %i.%i: %r", pid_file_path, new_uid, new_gid, ret);
235                         pid_file_destroy(pid_file_path);
236                     }
237                 }
238 #endif
239             }
240             else
241             {
242                 ret = ERRNO_ERROR;
243                 log_err("pid file '%s': cannot write pid: %r", pid_file_path, ret);
244                 pid_file_destroy(pid_file_path);
245             }
246         }
247     }
248     else
249     {
250         ret = ERRNO_ERROR;
251         log_err("pid file '%s': cannot create: %r", pid_file_path, ret);
252     }
253 
254     close_ex(fd);
255 
256     return ret;
257 }
258 
259 /** \brief Check if program is already running
260  *
261  *  @param[in] config is a config_data structure
262  *
263  *  @return NONE
264  *  @return otherwise log_quit will stop the program with correct exit code
265  */
266 ya_result
pid_check_running_program(const char * pid_file_path,pid_t * out_pid)267 pid_check_running_program(const char *pid_file_path, pid_t *out_pid)
268 {
269 #ifndef WIN32
270     yassert(pid_file_path != NULL);
271     pid_t                                                               pid;
272 
273     /*    ------------------------------------------------------------    */
274 
275     yassert(pid_file_path != NULL);
276 
277     if(strlen(pid_file_path) > PATH_MAX)
278     {
279         log_err("pid file '%s': path is bigger than %i", pid_file_path, PATH_MAX);
280 
281         return INVALID_PATH;
282     }
283 
284     if(ISOK(pid = pid_file_read(pid_file_path)))
285     {
286         if((pid != getpid_ex()) && ((kill(pid, 0) == 0) || (errno == EPERM)))
287         {
288             if(out_pid != NULL)
289             {
290                 *out_pid = pid;
291             }
292             return PID_LOCKED;
293         }
294     }
295 #endif
296     return SUCCESS;
297 }
298 
299 void
pid_file_destroy(const char * pid_file_path)300 pid_file_destroy(const char *pid_file_path)
301 {
302     yassert(pid_file_path != NULL);
303 
304     if(strlen(pid_file_path) > PATH_MAX)
305     {
306         log_err("pid file '%s': path is bigger than %i", pid_file_path, PATH_MAX);
307         return;
308     }
309 
310     if(FAIL(unlink(pid_file_path)))
311     {
312         int ret = ERRNO_ERROR;
313 
314         // don't complain if the file has already been destroyed
315         if(ret != ENOENT)
316         {
317             log_err("pid file '%s': cannot delete: %r", pid_file_path, ret);
318         }
319     }
320 }
321 
322 /** @} */
323