diff proc.c @ 19:5c6155c8e9b6

Handle signals Handled signals are generally blocked and only unblocked when doing blocking I/O, i.e. either when reading commands or printing results. A (possibly queued) signal will then interrupt I/O and can be dealt with in the main loop.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Fri, 01 Sep 2017 22:33:41 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/proc.c	Fri Sep 01 22:33:41 2017 +0200
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#ifdef	HAVE_ERR_H
+#include <err.h>
+#endif /* HAVE_ERR_H */
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "proc.h"
+#include "pwm.h"
+
+#define	PIPE_R	0
+#define	PIPE_W	1
+
+static sigjmp_buf	signal_env;
+
+static void
+signal_handler(int signal_no)
+{
+	siglongjmp(signal_env, signal_no);
+}
+
+enum io_status
+proc_open(struct proc *proc, const char *command, const char *type)
+{
+	int	pipe_fds[2];
+	pid_t	pid;
+
+	if ((strlen(type) != 1) || ((*type != 'r') && (*type != 'w'))) {
+		return (IO_ERROR);
+	}
+
+	if (pipe(pipe_fds) < 0) {
+		return (IO_ERROR);
+	}
+
+	switch (pid = fork()) {
+	case -1:
+		return (IO_ERROR);
+	case 0:
+		if (*type == 'r') {
+			close(pipe_fds[PIPE_R]);
+			dup2(pipe_fds[PIPE_W], STDOUT_FILENO);
+		} else {
+			close(pipe_fds[PIPE_W]);
+			dup2(pipe_fds[PIPE_R], STDIN_FILENO);
+		}
+		closefrom(STDERR_FILENO + 1);
+		execlp("sh", "sh", "-c", command, (char *)0);
+		err(1, "execlp");
+	default:
+		if (*type == 'r') {
+			close(pipe_fds[PIPE_W]);
+			proc->fd = pipe_fds[PIPE_R];
+		} else {
+			close(pipe_fds[PIPE_R]);
+			proc->fd = pipe_fds[PIPE_W];
+		}
+		proc->pid = pid;
+		return (IO_OK);
+	}
+}
+
+enum io_status
+proc_close(struct proc *proc)
+{
+	struct sigaction action;
+	struct sigaction oaction;
+	int		signal_no = 0;
+	pid_t		wpid;
+	int		status;
+
+	close(proc->fd);
+
+	/* install signal handlers */
+	action.sa_handler = signal_handler;
+	action.sa_flags = 0;
+	sigemptyset(&action.sa_mask);
+	sigaddset(&action.sa_mask, SIGINT);
+	sigaddset(&action.sa_mask, SIGTERM);
+	sigaddset(&action.sa_mask, SIGHUP);
+	sigaddset(&action.sa_mask, SIGQUIT);
+	if ((sigaction(SIGINT, &action, &oaction) != 0) ||
+	    (sigaction(SIGTERM, &action, &oaction) != 0) ||
+	    (sigaction(SIGHUP, &action, &oaction) != 0) ||
+	    (sigaction(SIGQUIT, &action, &oaction) != 0)) {
+		err(1, "sigaction");
+	}
+
+	if ((signal_no = sigsetjmp(signal_env, 1)) != 0) {
+		/*
+		 * signal received, signal mask has been restored, send SIGTERM
+		 * to the child process, wait 500 ms and send a SIGKILL if the
+		 * child still exists
+		 */
+		kill(proc->pid, SIGTERM);
+		nanosleep(&(struct timespec){ .tv_nsec = 500 * 1000 * 1000 },
+		    NULL);
+		do {
+			wpid = waitpid(proc->pid, &status, WNOHANG);
+		} while ((wpid == -1) && (errno == EINTR));
+		if (wpid == proc->pid) {
+			goto out;
+		}
+
+		kill(proc->pid, SIGKILL);
+		do {
+			wpid = waitpid(proc->pid, &status, 0);
+		} while ((wpid == -1) && (errno == EINTR));
+
+		goto out;
+	}
+
+	pwm_unblock_signals();
+	do {
+		wpid = waitpid(proc->pid, &status, 0);
+	} while ((wpid == -1) && (errno == EINTR));
+	pwm_block_signals();
+
+out:
+	/* restore signal handlers */
+	if ((sigaction(SIGINT, &oaction, NULL) != 0) ||
+	    (sigaction(SIGTERM, &oaction, NULL) != 0) ||
+	    (sigaction(SIGHUP, &oaction, NULL) != 0) ||
+	    (sigaction(SIGQUIT, &oaction, NULL) != 0)) {
+		err(1, "sigaction");
+	}
+
+	proc->fd = -1;
+	proc->pid = (pid_t)-1;
+
+	return ((signal_no == 0) ? IO_OK : IO_SIGNAL);
+}