/*
 * 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/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.
 */


#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 <stdbool.h>

#include "einfo.h"
#include "rc.h"
#include "_usage.h"
#include "helpers.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 */
			execlp("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 = NULL;
	FILE *fp;
	char *path = NULL;
	pid_t temp_pid;
	size_t size;
	bool user_process = true;

	while (pid >0 && user_process) {
		if (pid == 2) {
			user_process = false;
			continue;
		}
		xasprintf(&path, "/proc/%d/status", pid);
		fp = fopen(path, "r");
		free(path);
		/*
		 * 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 = NULL;
			if (xgetline(&buf, &size, fp) != -1) {
				sscanf(buf, "PPid: %d", &temp_pid);
				free(buf);
			} else {
				free(buf);
				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 = NULL;
	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? */
		if (buf) {
			free(buf);
			buf = NULL;
		}
		xasprintf(&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;
}
