/* * The functions in this file control the stopping of daemons by * start-stop-daemon and supervise-daemon. */ /* * Copyright (c) 2015 The OpenRC Authors. * See the Authors file at the top-level directory of this distribution and * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS * * This file is part of OpenRC. It is subject to the license terms in * the LICENSE file found in the top-level directory of this * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE * This file may not be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ /* nano seconds */ #define POLL_INTERVAL 20000000 #define WAIT_PIDFILE 500000000 #define ONE_SECOND 1000000000 #define ONE_MS 1000000 #include #include #include #include #include #include #include #include #include #include #include "einfo.h" #include "queue.h" #include "rc.h" #include "schedules.h" #include "helpers.h" typedef struct scheduleitem { enum { SC_TIMEOUT, SC_SIGNAL, SC_GOTO, SC_FOREVER, } type; int value; struct scheduleitem *gotoitem; TAILQ_ENTRY(scheduleitem) entries; } SCHEDULEITEM; static TAILQ_HEAD(, scheduleitem) schedule; void free_schedulelist(void) { SCHEDULEITEM *s1 = TAILQ_FIRST(&schedule); SCHEDULEITEM *s2; while (s1) { s2 = TAILQ_NEXT(s1, entries); free(s1); s1 = s2; } TAILQ_INIT(&schedule); } int parse_signal(const char *applet, const char *sig) { typedef struct signalpair { const char *name; int signal; } SIGNALPAIR; #define signalpair_item(name) { #name, SIG##name }, static const SIGNALPAIR signallist[] = { signalpair_item(HUP) signalpair_item(INT) signalpair_item(QUIT) signalpair_item(ILL) signalpair_item(TRAP) signalpair_item(ABRT) signalpair_item(BUS) signalpair_item(FPE) signalpair_item(KILL) signalpair_item(USR1) signalpair_item(SEGV) signalpair_item(USR2) signalpair_item(PIPE) signalpair_item(ALRM) signalpair_item(TERM) signalpair_item(CHLD) signalpair_item(CONT) signalpair_item(STOP) signalpair_item(TSTP) signalpair_item(TTIN) signalpair_item(TTOU) signalpair_item(URG) signalpair_item(XCPU) signalpair_item(XFSZ) signalpair_item(VTALRM) signalpair_item(PROF) #ifdef SIGWINCH signalpair_item(WINCH) #endif #ifdef SIGIO signalpair_item(IO) #endif #ifdef SIGPWR signalpair_item(PWR) #endif signalpair_item(SYS) { "NULL", 0 }, }; unsigned int i = 0; const char *s; if (!sig || *sig == '\0') return -1; if (sscanf(sig, "%u", &i) == 1) { if (i < NSIG) return i; eerrorx("%s: `%s' is not a valid signal", applet, sig); } if (strncmp(sig, "SIG", 3) == 0) s = sig + 3; else s = NULL; for (i = 0; i < ARRAY_SIZE(signallist); ++i) if (strcmp(sig, signallist[i].name) == 0 || (s && strcmp(s, signallist[i].name) == 0)) return signallist[i].signal; eerrorx("%s: `%s' is not a valid signal", applet, sig); /* NOTREACHED */ } static SCHEDULEITEM *parse_schedule_item(const char *applet, const char *string) { const char *after_hyph; int sig; SCHEDULEITEM *item = xmalloc(sizeof(*item)); item->value = 0; item->gotoitem = NULL; if (strcmp(string,"forever") == 0) item->type = SC_FOREVER; else if (isdigit((unsigned char)string[0])) { item->type = SC_TIMEOUT; errno = 0; if (sscanf(string, "%d", &item->value) != 1) eerrorx("%s: invalid timeout value in schedule `%s'", applet, string); } else if ((after_hyph = string + (string[0] == '-')) && ((sig = parse_signal(applet, after_hyph)) != -1)) { item->type = SC_SIGNAL; item->value = (int)sig; } else eerrorx("%s: invalid schedule item `%s'", applet, string); return item; } void parse_schedule(const char *applet, const char *string, int timeout) { char buffer[20]; const char *slash; int count = 0; SCHEDULEITEM *repeatat = NULL; size_t len; SCHEDULEITEM *item; TAILQ_INIT(&schedule); if (string) for (slash = string; *slash; slash++) if (*slash == '/') count++; free_schedulelist(); if (count == 0) { item = xmalloc(sizeof(*item)); item->type = SC_SIGNAL; item->value = timeout; item->gotoitem = NULL; TAILQ_INSERT_TAIL(&schedule, item, entries); item = xmalloc(sizeof(*item)); item->type = SC_TIMEOUT; item->gotoitem = NULL; TAILQ_INSERT_TAIL(&schedule, item, entries); if (string) { if (sscanf(string, "%d", &item->value) != 1) eerrorx("%s: invalid timeout in schedule", applet); } else item->value = 5; return; } while (string != NULL) { if ((slash = strchr(string, '/'))) len = slash - string; else len = strlen(string); if (len >= (ptrdiff_t)sizeof(buffer)) eerrorx("%s: invalid schedule item, far too long", applet); memcpy(buffer, string, len); buffer[len] = 0; string = slash ? slash + 1 : NULL; item = parse_schedule_item(applet, buffer); TAILQ_INSERT_TAIL(&schedule, item, entries); if (item->type == SC_FOREVER) { if (repeatat) eerrorx("%s: invalid schedule, `forever' " "appears more than once", applet); repeatat = item; continue; } } if (repeatat) { item = xmalloc(sizeof(*item)); item->type = SC_GOTO; item->value = 0; item->gotoitem = repeatat; TAILQ_INSERT_TAIL(&schedule, item, entries); } return; } /* return number of processes killed, -1 on error */ int do_stop(const char *applet, const char *exec, const char *const *argv, pid_t pid, uid_t uid,int sig, bool test, bool quiet) { RC_PIDLIST *pids; RC_PID *pi; RC_PID *np; bool killed; int nkilled = 0; if (pid > 0) pids = rc_find_pids(NULL, NULL, 0, pid); else pids = rc_find_pids(exec, argv, uid, 0); if (!pids) return 0; LIST_FOREACH_SAFE(pi, pids, entries, np) { if (test) { einfo("Would send signal %d to PID %d", sig, pi->pid); nkilled++; } else { if (sig) { syslog(LOG_DEBUG, "Sending signal %d to PID %d", sig, pi->pid); if (!quiet) ebeginv("Sending signal %d to PID %d", sig, pi->pid); } errno = 0; killed = (kill(pi->pid, sig) == 0 || errno == ESRCH ? true : false); if (!quiet) eendv(killed ? 0 : 1, "%s: failed to send signal %d to PID %d: %s", applet, sig, pi->pid, strerror(errno)); else if (!killed) syslog(LOG_ERR, "Failed to send signal %d to PID %d: %s", sig, pi->pid, strerror(errno)); if (!killed) { nkilled = -1; } else { if (nkilled != -1) nkilled++; } } free(pi); } free(pids); return nkilled; } int run_stop_schedule(const char *applet, const char *exec, const char *const *argv, pid_t pid, uid_t uid, bool test, bool progress, bool quiet) { SCHEDULEITEM *item = TAILQ_FIRST(&schedule); int nkilled = 0; int tkilled = 0; int nrunning = 0; long nloops, nsecs; struct timespec ts; const char *const *p; bool progressed = false; if (!(pid > 0 || exec || uid || (argv && *argv))) return 0; if (exec) { einfov("Will stop %s", exec); syslog(LOG_DEBUG, "Will stop %s", exec); } if (pid > 0) { einfov("Will stop PID %d", pid); syslog(LOG_DEBUG, "Will stop PID %d", pid); } if (uid) { einfov("Will stop processes owned by UID %d", uid); syslog(LOG_DEBUG, "Will stop processes owned by UID %d", uid); } if (argv && *argv) { einfovn("Will stop processes of `"); if (rc_yesno(getenv("EINFO_VERBOSE"))) { for (p = argv; p && *p; p++) { if (p != argv) printf(" "); printf("%s", *p); } printf("'\n"); } } while (item) { switch (item->type) { case SC_GOTO: item = item->gotoitem; continue; case SC_SIGNAL: nrunning = 0; nkilled = do_stop(applet, exec, argv, pid, uid, item->value, test, quiet); if (nkilled == 0) { if (tkilled == 0) { if (progressed) printf("\n"); eerror("%s: no matching processes found", applet); } return tkilled; } else if (nkilled == -1) return 0; tkilled += nkilled; break; case SC_FOREVER: case SC_TIMEOUT: if (item->type == SC_TIMEOUT && item->value < 1) { item = NULL; break; } ts.tv_sec = 0; ts.tv_nsec = POLL_INTERVAL; for (nsecs = 0; item->type == SC_FOREVER || nsecs < item->value; nsecs++) { for (nloops = 0; nloops < ONE_SECOND / POLL_INTERVAL; nloops++) { if ((nrunning = do_stop(applet, exec, argv, pid, uid, 0, test, quiet)) == 0) return 0; if (nanosleep(&ts, NULL) == -1) { if (progressed) { printf("\n"); progressed = false; } if (errno != EINTR) { eerror("%s: nanosleep: %s", applet, strerror(errno)); return 0; } } } if (progress) { printf("."); fflush(stdout); progressed = true; } } break; default: if (progressed) { printf("\n"); progressed = false; } eerror("%s: invalid schedule item `%d'", applet, item->type); return 0; } if (item) item = TAILQ_NEXT(item, entries); } if (test || (tkilled > 0 && nrunning == 0)) return nkilled; if (progressed) printf("\n"); if (!quiet) { if (nrunning == 1) eerror("%s: %d process refused to stop", applet, nrunning); else eerror("%s: %d process(es) refused to stop", applet, nrunning); } return -nrunning; }