X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fcore%2Fkillall.c;h=5a50ae6f045e5d024ec8434ee047a689abc97e03;hp=6fcaf847cfdb75b2126667d40ffdeff22939e5ee;hb=615938651d3a4fd9253b08da00db22d451a8cef8;hpb=cee530bb23b78c0dfd18b0c2718cfe41286396df diff --git a/src/core/killall.c b/src/core/killall.c index 6fcaf847c..5a50ae6f0 100644 --- a/src/core/killall.c +++ b/src/core/killall.c @@ -22,17 +22,19 @@ #include #include #include +#include #include "util.h" #include "def.h" #include "killall.h" +#include "set.h" -#define TIMEOUT_USEC (5 * USEC_PER_SEC) +#define TIMEOUT_USEC (10 * USEC_PER_SEC) static bool ignore_proc(pid_t pid) { - char buf[PATH_MAX]; - FILE *f; + _cleanup_fclose_ FILE *f = NULL; char c; + const char *p; size_t count; uid_t uid; int r; @@ -49,15 +51,12 @@ static bool ignore_proc(pid_t pid) { if (uid != 0) return false; - snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long) pid); - char_array_0(buf); - - f = fopen(buf, "re"); + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); if (!f) return true; /* not really, but has the desired effect */ count = fread(&c, 1, 1, f); - fclose(f); /* Kernel threads have an empty cmdline */ if (count <= 0) @@ -73,41 +72,71 @@ static bool ignore_proc(pid_t pid) { return false; } -static void wait_for_children(int n_processes, sigset_t *mask) { +static void wait_for_children(Set *pids, sigset_t *mask) { usec_t until; assert(mask); + if (set_isempty(pids)) + return; + until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC; for (;;) { struct timespec ts; int k; usec_t n; + void *p; + Iterator i; + /* First, let the kernel inform us about killed + * children. Most processes will probably be our + * children, but some are not (might be our + * grandchildren instead...). */ for (;;) { - pid_t pid = waitpid(-1, NULL, WNOHANG); + pid_t pid; + pid = waitpid(-1, NULL, WNOHANG); if (pid == 0) break; + if (pid < 0) { + if (errno == ECHILD) + break; - if (pid < 0 && errno == ECHILD) + log_error_errno(errno, "waitpid() failed: %m"); return; + } - if (n_processes > 0) - if (--n_processes == 0) - return; + set_remove(pids, ULONG_TO_PTR(pid)); } + /* Now explicitly check who might be remaining, who + * might not be our child. */ + SET_FOREACH(p, pids, i) { + + /* We misuse getpgid as a check whether a + * process still exists. */ + if (getpgid((pid_t) PTR_TO_ULONG(p)) >= 0) + continue; + + if (errno != ESRCH) + continue; + + set_remove(pids, p); + } + + if (set_isempty(pids)) + return; + n = now(CLOCK_MONOTONIC); if (n >= until) return; timespec_store(&ts, until - n); - - if ((k = sigtimedwait(mask, NULL, &ts)) != SIGCHLD) { + k = sigtimedwait(mask, NULL, &ts); + if (k != SIGCHLD) { if (k < 0 && errno != EAGAIN) { - log_error("sigtimedwait() failed: %m"); + log_error_errno(errno, "sigtimedwait() failed: %m"); return; } @@ -117,10 +146,9 @@ static void wait_for_children(int n_processes, sigset_t *mask) { } } -static int killall(int sig) { - DIR *dir; +static int killall(int sig, Set *pids, bool send_sighup) { + _cleanup_closedir_ DIR *dir = NULL; struct dirent *d; - unsigned int n_processes = 0; dir = opendir("/proc"); if (!dir) @@ -139,39 +167,60 @@ static int killall(int sig) { if (ignore_proc(pid)) continue; - if (kill(pid, sig) >= 0) - n_processes++; - else if (errno != ENOENT) - log_warning("Could not kill %d: %m", pid); - } + if (sig == SIGKILL) { + _cleanup_free_ char *s = NULL; - closedir(dir); + get_process_comm(pid, &s); + log_notice("Sending SIGKILL to PID "PID_FMT" (%s).", pid, strna(s)); + } - return n_processes; + if (kill(pid, sig) >= 0) { + if (pids) + set_put(pids, ULONG_TO_PTR(pid)); + } else if (errno != ENOENT) + log_warning_errno(errno, "Could not kill %d: %m", pid); + + if (send_sighup) { + /* Optionally, also send a SIGHUP signal, but + only if the process has a controlling + tty. This is useful to allow handling of + shells which ignore SIGTERM but react to + SIGHUP. We do not send this to processes that + have no controlling TTY since we don't want to + trigger reloads of daemon processes. Also we + make sure to only send this after SIGTERM so + that SIGTERM is always first in the queue. */ + + + if (get_ctty_devnr(pid, NULL) >= 0) + kill(pid, SIGHUP); + } + } + + return set_size(pids); } -void broadcast_signal(int sig, bool wait) { +void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup) { sigset_t mask, oldmask; - int n_processes; + _cleanup_set_free_ Set *pids = NULL; + + if (wait_for_exit) + pids = set_new(NULL); assert_se(sigemptyset(&mask) == 0); assert_se(sigaddset(&mask, SIGCHLD) == 0); assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) - log_warning("kill(-1, SIGSTOP) failed: %m"); + log_warning_errno(errno, "kill(-1, SIGSTOP) failed: %m"); - n_processes = killall(sig); + killall(sig, pids, send_sighup); if (kill(-1, SIGCONT) < 0 && errno != ESRCH) - log_warning("kill(-1, SIGCONT) failed: %m"); - - if (n_processes <= 0) - goto finish; + log_warning_errno(errno, "kill(-1, SIGCONT) failed: %m"); - if (wait) - wait_for_children(n_processes, &mask); + if (wait_for_exit) + wait_for_children(pids, &mask); -finish: - sigprocmask(SIG_SETMASK, &oldmask, NULL); + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); }