From 6a6ae6fa3b96ad43238bdc81710ac284b165a882 Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Wed, 10 Jan 2018 23:27:05 +0100 Subject: [PATCH] Fix #95: Create PID file if program does not This patch adds support for creating, and removing, a PID file for programs (services) that for some reason does not create one. With Finit this is very useful since it allows us to use our condition support even for "broken" daemons. To enable this for a service, use the following syntax: service pid[:[/path/to/]file[.pid]] /path/to/daemon I.e., `pid` is a required argument if you want to enable the feature, but the rest is optional: pid --> /run/`basename $CMD`.pid pid:file --> /run/file.pid pid:file.dip --> /run/file.dip pid:/mnt/k --> /mnt/k.pid pid:/mnt/k.foo --> /mnt/k.foo There is an additional feature with this patch. For daemons that run with a different identiy one usually wants to create the PID file with that identity, e.g.: service inadyn -I dyn -- Dyn.com DDNS client service inadyn -I freedns -- FreeDNS DDNS client Here inadyn will look for /etc/dyn.conf and create /run/dyn.pid, and /etc/freedns.conf and create /run/freedns.pid, respetively. While Finit will in both cases assume the PID file is /run/inadyn.pid ... err fail! To get out of this mess the `pid` argument can be used to tell Finit what the real PID file the daemon creates is called: service pid:!dyn inadyn -I dyn -- Dyn.com DDNS client service pid:!freedns inadyn -I freedns -- FreeDNS DDNS client The same composition rules listed above applies also to the `!` variant. Signed-off-by: Joachim Nilsson --- src/service.c | 99 +++++++++++++++++++++++++++++++++++++++++++++------ src/svc.h | 1 + 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/service.c b/src/service.c index bdf5add9..5e4a4c27 100644 --- a/src/service.c +++ b/src/service.c @@ -114,6 +114,75 @@ static int is_norespawn(void) fexist("/tmp/norespawn"); } +static char *pid_file(svc_t *svc) +{ + static char fn[MAX_ARG_LEN]; + + if (svc->pidfile[0]) { + if (svc->pidfile[0] == '!') + return &svc->pidfile[1]; + return svc->pidfile; + } + + snprintf(fn, sizeof(fn), "%s%s.pid", _PATH_VARRUN, basename(svc->cmd)); + return fn; +} + +static int pid_file_create(svc_t *svc) +{ + FILE *fp; + + if (!svc->pidfile[0] || svc->pidfile[0] == '!') + return 1; + + fp = fopen(svc->pidfile, "w"); + if (!fp) + return 1; + fprintf(fp, "%d\n", svc->pid); + + return fclose(fp); +} + +static int pid_file_parse(svc_t *svc, char *arg) +{ + int len, not = 0; + + /* Always clear, called with some sort of 'pid[:..]' argument */ + svc->pidfile[0] = 0; + + /* Sanity check ... */ + if (!arg || !arg[0]) + return 1; + + /* 'pid:' imples argument following*/ + if (!strncmp(arg, "pid:", 4)) { + arg += 4; + if ((arg[0] == '!' && arg[1] == '/') || arg[0] == '/') { + strlcpy(svc->pidfile, arg, sizeof(svc->pidfile)); + return 0; + } + + if (arg[0] == '!') { + arg++; + not++; + } + len = snprintf(svc->pidfile, sizeof(svc->pidfile), "%s%s%s", + not ? "!" : "", _PATH_VARRUN, arg); + if (len > 4 && strcmp(&svc->pidfile[len - 4], ".pid")) + strlcat(svc->pidfile, ".pid", sizeof(svc->pidfile)); + + return 0; + } + + /* 'pid' arg, no argument following */ + if (!strcmp(arg, "pid")) { + strlcpy(svc->pidfile, pid_file(svc), sizeof(svc->pidfile)); + return 0; + } + + return 1; +} + /** * service_start - Start service * @svc: Service to start @@ -323,7 +392,10 @@ static int service_start(svc_t *svc) svc_set_state(svc, SVC_STOPPING_STATE); } } - + + if (svc_is_daemon(svc)) + pid_file_create(svc); + sigprocmask(SIG_SETMASK, &omask, NULL); print_result(result); @@ -540,7 +612,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) int forking = 0; #endif int log = 0, levels = 0; - char *username = NULL, *line; + char *line; + char *username = NULL, *pid = NULL; char *service = NULL, *proto = NULL, *ifaces = NULL; char *cmd, *desc, *runlevels = NULL, *cond = NULL; svc_t *svc; @@ -573,7 +646,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) } while (cmd) { - if (cmd[0] == '@') /* @username[:group] */ + if (cmd[0] == '@') /* @username[:group] */ username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; @@ -587,10 +660,12 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) else if (!strncasecmp(cmd, "wait", 4)) forking = 0; #endif - else if (cmd[0] != '/' && strchr(cmd, '/')) - service = cmd; /* inetd service/proto */ else if (!strncasecmp(cmd, "log", 3)) log = 1; + else if (!strncasecmp(cmd, "pid", 3)) + pid = cmd; + else if (cmd[0] != '/' && strchr(cmd, '/')) + service = cmd; /* inetd service/proto */ else break; @@ -679,6 +754,9 @@ recreate: if (desc) strlcpy(svc->desc, desc, sizeof(svc->desc)); + if (pid && svc_is_daemon(svc) && pid_file_parse(svc, pid)) + _e("Invalid 'pid' argument to service: %s", pid); + if (username) { char *ptr = strchr(username, ':'); @@ -759,7 +837,6 @@ void service_unregister(svc_t *svc) void service_monitor(pid_t lost) { svc_t *svc; - char pidfile[MAX_ARG_LEN]; if (fexist(SYNC_SHUTDOWN) || lost <= 1) return; @@ -778,10 +855,12 @@ void service_monitor(pid_t lost) _d("collected %s(%d)", svc->cmd, lost); /* Try removing PID file (in case service does not clean up after itself) */ - snprintf(pidfile, sizeof(pidfile), "%s%s.pid", _PATH_VARRUN, basename(svc->cmd)); - if (remove(pidfile)) { - if (errno != ENOENT) - logit(LOG_CRIT, "Failed removing service %s pidfile %s", basename(svc->cmd), pidfile); + if (svc_is_daemon(svc)) { + char *fn; + + fn = pid_file(svc); + if (remove(fn) && errno != ENOENT) + logit(LOG_CRIT, "Failed removing service %s pidfile %s", basename(svc->cmd), fn); } /* No longer running, update books. */ diff --git a/src/svc.h b/src/svc.h index f3b686c4..37fa787a 100644 --- a/src/svc.h +++ b/src/svc.h @@ -90,6 +90,7 @@ typedef struct svc { /* Service details */ pid_t pid; + char pidfile[MAX_ARG_LEN]; long start_time; /* Start time, as seconds since boot, from sysinfo() */ const svc_state_t state; /* Paused, Reloading, Restart, Running, ... */ svc_type_t type; /* Service, run, task, inetd, ... */