add kill_all helper
This is similar to the sysvinit killall5 utility. It should only be used in service scripts, so it will not be installed in the path. This closes #129.
This commit is contained in:
		
							
								
								
									
										1
									
								
								src/rc/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/rc/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -62,3 +62,4 @@ openrc
 | 
			
		||||
openrc-init
 | 
			
		||||
openrc-run
 | 
			
		||||
openrc-shutdown
 | 
			
		||||
kill_all
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ SRCS+=		rc-selinux.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
ifeq (${OS},Linux)
 | 
			
		||||
SRCS+=		openrc-init.c openrc-shutdown.c
 | 
			
		||||
SRCS+=		kill_all.c openrc-init.c openrc-shutdown.c
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
CLEANFILES=	version.h rc-selinux.o
 | 
			
		||||
@@ -44,6 +44,7 @@ RC_SBINPROGS=	mark_service_starting mark_service_started \
 | 
			
		||||
		rc-abort swclock
 | 
			
		||||
 | 
			
		||||
ifeq (${OS},Linux)
 | 
			
		||||
RC_BINPROGS+= kill_all
 | 
			
		||||
SBINPROGS+= openrc-init openrc-shutdown
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
@@ -99,6 +100,9 @@ checkpath: rc-selinux.o
 | 
			
		||||
endif
 | 
			
		||||
	${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LDADD}
 | 
			
		||||
 | 
			
		||||
kill_all: kill_all.o _usage.o
 | 
			
		||||
	${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LDADD}
 | 
			
		||||
 | 
			
		||||
einfon einfo ewarnn ewarn eerrorn eerror ebegin eend ewend \
 | 
			
		||||
eindent eoutdent esyslog eval_ecolors ewaitfile \
 | 
			
		||||
veinfo vewarn vebegin veend vewend veindent veoutdent: do_e.o rc-misc.o
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										250
									
								
								src/rc/kill_all.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								src/rc/kill_all.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,250 @@
 | 
			
		||||
/*
 | 
			
		||||
 * kill_all.c
 | 
			
		||||
 * Sends a signal to all processes on the system.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2017 The OpenRC Authors.
 | 
			
		||||
 * See the Authors file at the top-level directory of this distribution and
 | 
			
		||||
 * https://github.com/OpenRC/openrc/blob/master/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/master/LICENSE
 | 
			
		||||
 * This file may not be copied, modified, propagated, or distributed
 | 
			
		||||
 *    except according to the terms contained in the LICENSE file.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <dirent.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <getopt.h>
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <syslog.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/wait.h>
 | 
			
		||||
 | 
			
		||||
#include "einfo.h"
 | 
			
		||||
#include "rc.h"
 | 
			
		||||
#include "rc-misc.h"
 | 
			
		||||
#include "_usage.h"
 | 
			
		||||
 | 
			
		||||
const char *applet = NULL;
 | 
			
		||||
const char *extraopts = "[signal number]";
 | 
			
		||||
const char *getoptstring = "do:" getoptstring_COMMON;
 | 
			
		||||
const struct option longopts[] = {
 | 
			
		||||
	{ "dry-run",        0, NULL, 'd' },
 | 
			
		||||
	{ "omit",        1, NULL, 'o' },
 | 
			
		||||
	longopts_COMMON
 | 
			
		||||
};
 | 
			
		||||
const char * const longopts_help[] = {
 | 
			
		||||
	"print what would be done",
 | 
			
		||||
	"omit this pid (can be repeated)",
 | 
			
		||||
	longopts_help_COMMON
 | 
			
		||||
};
 | 
			
		||||
const char *usagestring = NULL;
 | 
			
		||||
 | 
			
		||||
static int mount_proc(void)
 | 
			
		||||
{
 | 
			
		||||
	pid_t pid;
 | 
			
		||||
	pid_t rc;
 | 
			
		||||
	int status;
 | 
			
		||||
 | 
			
		||||
	if (exists("/proc/version"))
 | 
			
		||||
		return 0;
 | 
			
		||||
	pid = fork();
 | 
			
		||||
	switch(pid) {
 | 
			
		||||
		case -1:
 | 
			
		||||
			syslog(LOG_ERR, "Unable to fork");
 | 
			
		||||
			return -1;
 | 
			
		||||
			break;
 | 
			
		||||
		case 0:
 | 
			
		||||
			/* attempt to mount /proc */
 | 
			
		||||
			execl("mount", "mount", "-t", "proc", "proc", "/proc", NULL);
 | 
			
		||||
			syslog(LOG_ERR, "Unable to execute mount");
 | 
			
		||||
			exit(1);
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			/* wait for child process */
 | 
			
		||||
			while ((rc = wait(&status)) != pid)
 | 
			
		||||
				if (rc < 0 && errno == ECHILD)
 | 
			
		||||
					break;
 | 
			
		||||
			if (rc != pid || WEXITSTATUS(status) != 0)
 | 
			
		||||
				syslog(LOG_ERR, "mount returned non-zero exit status");
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
	if (! exists("/proc/version")) {
 | 
			
		||||
		syslog(LOG_ERR, "Could not mount /proc");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool is_user_process(pid_t pid)
 | 
			
		||||
{
 | 
			
		||||
	char buf[PATH_MAX+1];
 | 
			
		||||
	FILE *fp;
 | 
			
		||||
	char path[PATH_MAX+1];
 | 
			
		||||
	pid_t temp_pid;
 | 
			
		||||
	bool user_process = true;
 | 
			
		||||
 | 
			
		||||
	while (pid >0 && user_process) {
 | 
			
		||||
		if (pid == 2) {
 | 
			
		||||
			user_process = false;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		snprintf(path, sizeof(path), "/proc/%d/status", pid);
 | 
			
		||||
		fp = fopen(path, "r");
 | 
			
		||||
		/*
 | 
			
		||||
		 * if we could not open the file, the process disappeared, which
 | 
			
		||||
		 * leaves us no way to determine for sure whether it was a user
 | 
			
		||||
		 * process or kernel thread, so we say it is a kernel thread to
 | 
			
		||||
		 * avoid accidentally killing it.
 | 
			
		||||
		 */
 | 
			
		||||
		if (!fp) {
 | 
			
		||||
			user_process = false;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		temp_pid = -1;
 | 
			
		||||
		while (! feof(fp)) {
 | 
			
		||||
			buf[0] = 0;
 | 
			
		||||
			if (fgets(buf, sizeof(buf), fp))
 | 
			
		||||
				sscanf(buf, "PPid: %d", &temp_pid);
 | 
			
		||||
			else
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		fclose(fp);
 | 
			
		||||
		if (temp_pid == -1) {
 | 
			
		||||
			syslog(LOG_ERR, "Unable to read pid from /proc/%d/status", pid);
 | 
			
		||||
			user_process = false;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		pid = temp_pid;
 | 
			
		||||
	}
 | 
			
		||||
	return user_process;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int signal_processes(int sig, RC_STRINGLIST *omits, bool dryrun)
 | 
			
		||||
{
 | 
			
		||||
	sigset_t signals;
 | 
			
		||||
	sigset_t oldsigs;
 | 
			
		||||
	DIR *dir;
 | 
			
		||||
	struct dirent	*d;
 | 
			
		||||
	char buf[PATH_MAX+1];
 | 
			
		||||
	pid_t pid;
 | 
			
		||||
	int sendcount = 0;
 | 
			
		||||
 | 
			
		||||
	kill(-1, SIGSTOP);
 | 
			
		||||
	sigfillset(&signals);
 | 
			
		||||
	sigemptyset(&oldsigs);
 | 
			
		||||
	sigprocmask(SIG_SETMASK, &signals, &oldsigs);
 | 
			
		||||
	/*
 | 
			
		||||
	 * Open the /proc directory.
 | 
			
		||||
	 * CWD must be /proc to avoid problems if / is affected by the killing
 | 
			
		||||
	 * (i.e. depends on fuse).
 | 
			
		||||
	 */
 | 
			
		||||
	if (chdir("/proc") == -1) {
 | 
			
		||||
		syslog(LOG_ERR, "chdir /proc failed");
 | 
			
		||||
		sigprocmask(SIG_SETMASK, &oldsigs, NULL);
 | 
			
		||||
		kill(-1, SIGCONT);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	dir = opendir(".");
 | 
			
		||||
	if (!dir) {
 | 
			
		||||
		syslog(LOG_ERR, "cannot opendir(/proc)");
 | 
			
		||||
		sigprocmask(SIG_SETMASK, &oldsigs, NULL);
 | 
			
		||||
		kill(-1, SIGCONT);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Walk through the directory. */
 | 
			
		||||
	while ((d = readdir(dir)) != NULL) {
 | 
			
		||||
		/* Is this a process? */
 | 
			
		||||
		pid = (pid_t) atoi(d->d_name);
 | 
			
		||||
		if (pid == 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Is this a process we have been requested to omit? */
 | 
			
		||||
		sprintf(buf, "%d", pid);
 | 
			
		||||
		if (rc_stringlist_find(omits, buf))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Is this process in our session? */
 | 
			
		||||
		if (getsid(getpid()) == getsid(pid))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Is this a kernel thread? */
 | 
			
		||||
		if (!is_user_process(pid))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (dryrun)
 | 
			
		||||
			einfo("Would send signal %d to process %d", sig, pid);
 | 
			
		||||
		else if (kill(pid, sig) == 0)
 | 
			
		||||
			sendcount++;
 | 
			
		||||
	}
 | 
			
		||||
	closedir(dir);
 | 
			
		||||
	sigprocmask(SIG_SETMASK, &oldsigs, NULL);
 | 
			
		||||
	kill(-1, SIGCONT);
 | 
			
		||||
	return sendcount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	char *arg = NULL;
 | 
			
		||||
	int opt;
 | 
			
		||||
	bool dryrun = false;
 | 
			
		||||
	RC_STRINGLIST *omits = rc_stringlist_new();
 | 
			
		||||
	int sig = SIGKILL;
 | 
			
		||||
	char *here;
 | 
			
		||||
	char *token;
 | 
			
		||||
 | 
			
		||||
	/* Ensure that we are only quiet when explicitly told to be */
 | 
			
		||||
	unsetenv("EINFO_QUIET");
 | 
			
		||||
 | 
			
		||||
	applet = basename_c(argv[0]);
 | 
			
		||||
	rc_stringlist_addu(omits, "1");
 | 
			
		||||
	while ((opt = getopt_long(argc, argv, getoptstring,
 | 
			
		||||
		    longopts, (int *) 0)) != -1)
 | 
			
		||||
	{
 | 
			
		||||
		switch (opt) {
 | 
			
		||||
			case 'd':
 | 
			
		||||
				dryrun = true;
 | 
			
		||||
				break;
 | 
			
		||||
			case 'o':
 | 
			
		||||
				here = optarg;
 | 
			
		||||
				while ((token = strsep(&here, ",;:"))) {
 | 
			
		||||
					if ((pid_t) atoi(token) > 0)
 | 
			
		||||
						rc_stringlist_addu(omits, token);
 | 
			
		||||
					else {
 | 
			
		||||
						eerror("Invalid omit pid value %s", token);
 | 
			
		||||
						usage(EXIT_FAILURE);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case_RC_COMMON_GETOPT
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (argc > optind) {
 | 
			
		||||
	arg = argv[optind];
 | 
			
		||||
	sig = atoi(arg);
 | 
			
		||||
	if (sig <= 0 || sig > 31) {
 | 
			
		||||
		rc_stringlist_free(omits);
 | 
			
		||||
		eerror("Invalid signal %s", arg);
 | 
			
		||||
		usage(EXIT_FAILURE);
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	openlog(applet, LOG_CONS|LOG_PID, LOG_DAEMON);
 | 
			
		||||
	if (mount_proc() != 0) {
 | 
			
		||||
		rc_stringlist_free(omits);
 | 
			
		||||
		eerrorx("Unable to mount /proc file system");
 | 
			
		||||
	}
 | 
			
		||||
	signal_processes(sig, omits, dryrun);
 | 
			
		||||
	rc_stringlist_free(omits);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user