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