I flubbed up the merge on the new fixes, releasing a 0.9.8.4 with the actual fixes.
[proj/openrc.git] / src / librc / librc-daemon.c
1 /*
2 librc-daemon
3 Finds PID for given daemon criteria
4 */
5
6 /*
7 * Copyright (c) 2007-2009 Roy Marples <roy@marples.name>
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include "librc.h"
32
33 #if defined(__linux__)
34 static bool
35 pid_is_exec(pid_t pid, const char *exec)
36 {
37 char buffer[32];
38 FILE *fp;
39 int c;
40 bool retval = false;
41
42 exec = basename_c(exec);
43 snprintf(buffer, sizeof(buffer), "/proc/%d/stat", pid);
44 if ((fp = fopen(buffer, "r"))) {
45 while ((c = getc(fp)) != EOF && c != '(')
46 ;
47 if (c == '(') {
48 while ((c = getc(fp)) != EOF && c == *exec)
49 exec++;
50 if (c == ')' && *exec == '\0')
51 retval = true;
52 }
53 fclose(fp);
54 }
55 return retval;
56 }
57
58 static bool
59 pid_is_argv(pid_t pid, const char *const *argv)
60 {
61 char cmdline[32];
62 int fd;
63 char buffer[PATH_MAX];
64 char *p;
65 ssize_t bytes;
66
67 snprintf(cmdline, sizeof(cmdline), "/proc/%u/cmdline", pid);
68 if ((fd = open(cmdline, O_RDONLY)) < 0)
69 return false;
70 bytes = read(fd, buffer, sizeof(buffer));
71 close(fd);
72 if (bytes == -1)
73 return false;
74
75 buffer[bytes] = '\0';
76 p = buffer;
77 while (*argv) {
78 if (strcmp(*argv, p) != 0)
79 return false;
80 argv++;
81 p += strlen(p) + 1;
82 if ((unsigned)(p - buffer) > sizeof(buffer))
83 return false;
84 }
85 return true;
86 }
87
88 RC_PIDLIST *
89 rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid)
90 {
91 DIR *procdir;
92 struct dirent *entry;
93 pid_t p;
94 char buffer[PATH_MAX];
95 struct stat sb;
96 pid_t runscript_pid = 0;
97 char *pp;
98 RC_PIDLIST *pids = NULL;
99 RC_PID *pi;
100
101 if ((procdir = opendir("/proc")) == NULL)
102 return NULL;
103
104 /*
105 We never match RC_RUNSCRIPT_PID if present so we avoid the below
106 scenario
107
108 /etc/init.d/ntpd stop does
109 start-stop-daemon --stop --name ntpd
110 catching /etc/init.d/ntpd stop
111
112 nasty
113 */
114
115 if ((pp = getenv("RC_RUNSCRIPT_PID"))) {
116 if (sscanf(pp, "%d", &runscript_pid) != 1)
117 runscript_pid = 0;
118 }
119
120 while ((entry = readdir(procdir)) != NULL) {
121 if (sscanf(entry->d_name, "%d", &p) != 1)
122 continue;
123 if (runscript_pid != 0 && runscript_pid == p)
124 continue;
125 if (pid != 0 && pid != p)
126 continue;
127 if (uid) {
128 snprintf(buffer, sizeof(buffer), "/proc/%d", p);
129 if (stat(buffer, &sb) != 0 || sb.st_uid != uid)
130 continue;
131 }
132 if (exec && !pid_is_exec(p, exec))
133 continue;
134 if (argv &&
135 !pid_is_argv(p, (const char *const *)argv))
136 continue;
137 if (!pids) {
138 pids = xmalloc(sizeof(*pids));
139 LIST_INIT(pids);
140 }
141 pi = xmalloc(sizeof(*pi));
142 pi->pid = p;
143 LIST_INSERT_HEAD(pids, pi, entries);
144 }
145 closedir(procdir);
146 return pids;
147 }
148 librc_hidden_def(rc_find_pids)
149
150 #elif BSD
151
152 # if defined(__NetBSD__) || defined(__OpenBSD__)
153 # define _KVM_GETPROC2
154 # define _KINFO_PROC kinfo_proc2
155 # define _KVM_GETARGV kvm_getargv2
156 # define _GET_KINFO_UID(kp) (kp.p_ruid)
157 # define _GET_KINFO_COMM(kp) (kp.p_comm)
158 # define _GET_KINFO_PID(kp) (kp.p_pid)
159 # define _KVM_PATH NULL
160 # define _KVM_FLAGS KVM_NO_FILES
161 # else
162 # ifndef KERN_PROC_PROC
163 # define KERN_PROC_PROC KERN_PROC_ALL
164 # endif
165 # define _KINFO_PROC kinfo_proc
166 # define _KVM_GETARGV kvm_getargv
167 # define _GET_KINFO_UID(kp) (kp.ki_ruid)
168 # define _GET_KINFO_COMM(kp) (kp.ki_comm)
169 # define _GET_KINFO_PID(kp) (kp.ki_pid)
170 # define _KVM_PATH _PATH_DEVNULL
171 # define _KVM_FLAGS O_RDONLY
172 # endif
173
174 RC_PIDLIST *
175 rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid)
176 {
177 static kvm_t *kd = NULL;
178 char errbuf[_POSIX2_LINE_MAX];
179 struct _KINFO_PROC *kp;
180 int i;
181 int processes = 0;
182 int pargc = 0;
183 char **pargv;
184 RC_PIDLIST *pids = NULL;
185 RC_PID *pi;
186 pid_t p;
187 const char *const *arg;
188 int match;
189
190 if ((kd = kvm_openfiles(_KVM_PATH, _KVM_PATH,
191 NULL, _KVM_FLAGS, errbuf)) == NULL)
192 {
193 fprintf(stderr, "kvm_open: %s\n", errbuf);
194 return NULL;
195 }
196
197 #ifdef _KVM_GETPROC2
198 kp = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(*kp), &processes);
199 #else
200 kp = kvm_getprocs(kd, KERN_PROC_PROC, 0, &processes);
201 #endif
202 if ((kp == NULL && processes > 0) || (kp != NULL && processes < 0)) {
203 fprintf(stderr, "kvm_getprocs: %s\n", kvm_geterr(kd));
204 kvm_close(kd);
205 return NULL;
206 }
207
208 if (exec)
209 exec = basename_c(exec);
210 for (i = 0; i < processes; i++) {
211 p = _GET_KINFO_PID(kp[i]);
212 if (pid != 0 && pid != p)
213 continue;
214 if (uid != 0 && uid != _GET_KINFO_UID(kp[i]))
215 continue;
216 if (exec) {
217 if (!_GET_KINFO_COMM(kp[i]) ||
218 strcmp(exec, _GET_KINFO_COMM(kp[i])) != 0)
219 continue;
220 }
221 if (argv && *argv) {
222 pargv = _KVM_GETARGV(kd, &kp[i], pargc);
223 if (!pargv || !*pargv)
224 continue;
225 arg = argv;
226 match = 1;
227 while (*arg && *pargv)
228 if (strcmp(*arg++, *pargv++) != 0) {
229 match = 0;
230 break;
231 }
232 if (!match)
233 continue;
234 }
235 if (!pids) {
236 pids = xmalloc(sizeof(*pids));
237 LIST_INIT(pids);
238 }
239 pi = xmalloc(sizeof(*pi));
240 pi->pid = p;
241 LIST_INSERT_HEAD(pids, pi, entries);
242 }
243 kvm_close(kd);
244
245 return pids;
246 }
247 librc_hidden_def(rc_find_pids)
248
249 #else
250 # error "Platform not supported!"
251 #endif
252
253 static bool
254 _match_daemon(const char *path, const char *file, RC_STRINGLIST *match)
255 {
256 char *line = NULL;
257 size_t len = 0;
258 char ffile[PATH_MAX];
259 FILE *fp;
260 RC_STRING *m;
261
262 snprintf(ffile, sizeof(ffile), "%s/%s", path, file);
263 fp = fopen(ffile, "r");
264
265 if (!fp)
266 return false;
267
268 while ((rc_getline(&line, &len, fp))) {
269 TAILQ_FOREACH(m, match, entries)
270 if (strcmp(line, m->value) == 0) {
271 TAILQ_REMOVE(match, m, entries);
272 break;
273 }
274 if (!TAILQ_FIRST(match))
275 break;
276 }
277 fclose(fp);
278 free(line);
279 if (TAILQ_FIRST(match))
280 return false;
281 return true;
282 }
283
284 static RC_STRINGLIST *
285 _match_list(const char *exec, const char *const *argv, const char *pidfile)
286 {
287 RC_STRINGLIST *match = rc_stringlist_new();
288 int i = 0;
289 size_t l;
290 char *m;
291
292 if (exec) {
293 l = strlen(exec) + 6;
294 m = xmalloc(sizeof(char) * l);
295 snprintf(m, l, "exec=%s", exec);
296 rc_stringlist_add(match, m);
297 free(m);
298 }
299
300 while (argv && argv[i]) {
301 l = strlen(*argv) + strlen("argv_=") + 16;
302 m = xmalloc(sizeof(char) * l);
303 snprintf(m, l, "argv_0=%s", argv[i++]);
304 rc_stringlist_add(match, m);
305 free(m);
306 }
307
308 if (pidfile) {
309 l = strlen(pidfile) + 9;
310 m = xmalloc(sizeof(char) * l);
311 snprintf(m, l, "pidfile=%s", pidfile);
312 rc_stringlist_add(match, m);
313 free(m);
314 }
315
316 return match;
317 }
318
319 bool
320 rc_service_daemon_set(const char *service, const char *exec,
321 const char *const *argv,
322 const char *pidfile, bool started)
323 {
324 char dirpath[PATH_MAX];
325 char file[PATH_MAX];
326 int nfiles = 0;
327 char oldfile[PATH_MAX] = { '\0' };
328 bool retval = false;
329 DIR *dp;
330 struct dirent *d;
331 RC_STRINGLIST *match;
332 int i = 0;
333 FILE *fp;
334
335 if (!exec && !pidfile) {
336 errno = EINVAL;
337 return false;
338 }
339
340 snprintf(dirpath, sizeof(dirpath), RC_SVCDIR "/daemons/%s",
341 basename_c(service));
342
343 /* Regardless, erase any existing daemon info */
344 if ((dp = opendir(dirpath))) {
345 match = _match_list(exec, argv, pidfile);
346 while ((d = readdir(dp))) {
347 if (d->d_name[0] == '.')
348 continue;
349
350 snprintf(file, sizeof(file), "%s/%s",
351 dirpath, d->d_name);
352 nfiles++;
353
354 if (!*oldfile) {
355 if (_match_daemon(dirpath, d->d_name, match)) {
356 unlink(file);
357 strlcpy(oldfile, file, sizeof(oldfile));
358 nfiles--;
359 }
360 } else {
361 rename(file, oldfile);
362 strlcpy(oldfile, file, sizeof(oldfile));
363 }
364 }
365 closedir(dp);
366 rc_stringlist_free(match);
367 }
368
369 /* Now store our daemon info */
370 if (started) {
371 if (mkdir(dirpath, 0755) == 0 || errno == EEXIST) {
372 snprintf(file, sizeof(file), "%s/%03d",
373 dirpath, nfiles + 1);
374 if ((fp = fopen(file, "w"))) {
375 fprintf(fp, "exec=");
376 if (exec)
377 fprintf(fp, "%s", exec);
378 while (argv && argv[i]) {
379 fprintf(fp, "\nargv_%d=%s", i, argv[i]);
380 i++;
381 }
382 fprintf(fp, "\npidfile=");
383 if (pidfile)
384 fprintf(fp, "%s", pidfile);
385 fprintf(fp, "\n");
386 fclose(fp);
387 retval = true;
388 }
389 }
390 } else
391 retval = true;
392
393 return retval;
394 }
395 librc_hidden_def(rc_service_daemon_set)
396
397 bool
398 rc_service_started_daemon(const char *service,
399 const char *exec, const char *const *argv, int indx)
400 {
401 char dirpath[PATH_MAX];
402 char file[16];
403 RC_STRINGLIST *match;
404 bool retval = false;
405 DIR *dp;
406 struct dirent *d;
407
408 if (!service || !exec)
409 return false;
410
411 snprintf(dirpath, sizeof(dirpath), RC_SVCDIR "/daemons/%s",
412 basename_c(service));
413 match = _match_list(exec, argv, NULL);
414
415 if (indx > 0) {
416 snprintf(file, sizeof(file), "%03d", indx);
417 retval = _match_daemon(dirpath, file, match);
418 } else {
419 if ((dp = opendir(dirpath))) {
420 while ((d = readdir(dp))) {
421 if (d->d_name[0] == '.')
422 continue;
423 retval = _match_daemon(dirpath, d->d_name, match);
424 if (retval)
425 break;
426 }
427 closedir(dp);
428 }
429 }
430
431 rc_stringlist_free(match);
432 return retval;
433 }
434 librc_hidden_def(rc_service_started_daemon)
435
436 bool
437 rc_service_daemons_crashed(const char *service)
438 {
439 char dirpath[PATH_MAX];
440 DIR *dp;
441 struct dirent *d;
442 char *path = dirpath;
443 FILE *fp;
444 char *line = NULL;
445 size_t len = 0;
446 char **argv = NULL;
447 char *exec = NULL;
448 char *name = NULL;
449 char *pidfile = NULL;
450 pid_t pid = 0;
451 RC_PIDLIST *pids;
452 RC_PID *p1;
453 RC_PID *p2;
454 char *p;
455 char *token;
456 bool retval = false;
457 RC_STRINGLIST *list = NULL;
458 RC_STRING *s;
459 size_t i;
460
461 path += snprintf(dirpath, sizeof(dirpath), RC_SVCDIR "/daemons/%s",
462 basename_c(service));
463
464 if (!(dp = opendir(dirpath)))
465 return false;
466
467 while ((d = readdir(dp))) {
468 if (d->d_name[0] == '.')
469 continue;
470
471 snprintf(path, sizeof(dirpath) - (path - dirpath), "/%s",
472 d->d_name);
473 fp = fopen(dirpath, "r");
474 if (!fp)
475 break;
476
477 while ((rc_getline(&line, &len, fp))) {
478 p = line;
479 if ((token = strsep(&p, "=")) == NULL || !p)
480 continue;
481
482 if (!*p)
483 continue;
484
485 if (strcmp(token, "exec") == 0) {
486 if (exec)
487 free(exec);
488 exec = xstrdup(p);
489 } else if (strncmp(token, "argv_", 5) == 0) {
490 if (!list)
491 list = rc_stringlist_new();
492 rc_stringlist_add(list, p);
493 } else if (strcmp(token, "name") == 0) {
494 if (name)
495 free(name);
496 name = xstrdup(p);
497 } else if (strcmp(token, "pidfile") == 0) {
498 pidfile = xstrdup(p);
499 break;
500 }
501 }
502 fclose(fp);
503
504 pid = 0;
505 if (pidfile) {
506 retval = true;
507 if ((fp = fopen(pidfile, "r"))) {
508 if (fscanf(fp, "%d", &pid) == 1)
509 retval = false;
510 fclose(fp);
511 }
512 free(pidfile);
513 pidfile = NULL;
514
515 /* We have the pid, so no need to match
516 on exec or name */
517 free(exec);
518 exec = NULL;
519 free(name);
520 name = NULL;
521 } else {
522 if (exec) {
523 if (!list)
524 list = rc_stringlist_new();
525 if (!TAILQ_FIRST(list))
526 rc_stringlist_add(list, exec);
527
528 free(exec);
529 exec = NULL;
530 }
531
532 if (list) {
533 /* We need to flatten our linked list
534 into an array */
535 i = 0;
536 TAILQ_FOREACH(s, list, entries)
537 i++;
538 argv = xmalloc(sizeof(char *) * (i + 1));
539 i = 0;
540 TAILQ_FOREACH(s, list, entries)
541 argv[i++] = s->value;
542 argv[i] = '\0';
543 }
544 }
545
546 if (!retval) {
547 if (pid != 0) {
548 if (kill(pid, 0) == -1 && errno == ESRCH)
549 retval = true;
550 } else if ((pids = rc_find_pids(exec,
551 (const char *const *)argv,
552 0, pid)))
553 {
554 p1 = LIST_FIRST(pids);
555 while (p1) {
556 p2 = LIST_NEXT(p1, entries);
557 free(p1);
558 p1 = p2;
559 }
560 free(pids);
561 } else
562 retval = true;
563 }
564 rc_stringlist_free(list);
565 list = NULL;
566 free(argv);
567 argv = NULL;
568 free(exec);
569 exec = NULL;
570 free(name);
571 name = NULL;
572 if (retval)
573 break;
574 }
575 closedir(dp);
576 free(line);
577
578 return retval;
579 }
580 librc_hidden_def(rc_service_daemons_crashed)