rust-chg: extract signal handlers from chg/procutil.c
authorYuya Nishihara <yuya@tcha.org>
Mon, 24 Sep 2018 22:04:57 +0900
changeset 40119 cd490ac908c0
parent 40118 dd23eb81a3a3
child 40120 e70b616a077b
rust-chg: extract signal handlers from chg/procutil.c abortmsgerrno() and debugmsg() are removed, and the public interface instead returns success/error status. Since signal handlers can't propagate errors, the result of kill() is just ignored.
rust/chg/build.rs
rust/chg/src/sighandlers.c
--- a/rust/chg/build.rs	Sun Oct 07 23:19:49 2018 +0900
+++ b/rust/chg/build.rs	Mon Sep 24 22:04:57 2018 +0900
@@ -4,5 +4,6 @@
     cc::Build::new()
         .warnings(true)
         .file("src/sendfds.c")
+        .file("src/sighandlers.c")
         .compile("procutil");
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chg/src/sighandlers.c	Mon Sep 24 22:04:57 2018 +0900
@@ -0,0 +1,177 @@
+/*
+ * Signal handlers for cHg
+ *
+ * Copyright 2011, 2018 Yuya Nishihara <yuya@tcha.org>
+ *
+ * This software may be used and distributed according to the terms of the
+ * GNU General Public License version 2 or any later version.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#ifdef __GNUC__
+#define UNUSED_ __attribute__((unused))
+#else
+#define UNUSED_
+#endif
+
+static pid_t pagerpid = 0;
+static pid_t peerpgid = 0;
+static pid_t peerpid = 0;
+
+static void forwardsignal(int sig)
+{
+	assert(peerpid > 0);
+	(void)kill(peerpid, sig);
+}
+
+static void forwardsignaltogroup(int sig)
+{
+	/* prefer kill(-pgid, sig), fallback to pid if pgid is invalid */
+	pid_t killpid = peerpgid > 1 ? -peerpgid : peerpid;
+	(void)kill(killpid, sig);
+}
+
+static void handlestopsignal(int sig)
+{
+	sigset_t unblockset, oldset;
+	struct sigaction sa, oldsa;
+	if (sigemptyset(&unblockset) < 0)
+		return;
+	if (sigaddset(&unblockset, sig) < 0)
+		return;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = SIG_DFL;
+	sa.sa_flags = SA_RESTART;
+	if (sigemptyset(&sa.sa_mask) < 0)
+		return;
+
+	forwardsignal(sig);
+	if (raise(sig) < 0) /* resend to self */
+		return;
+	if (sigaction(sig, &sa, &oldsa) < 0)
+		return;
+	if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
+		return;
+	/* resent signal will be handled before sigprocmask() returns */
+	if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
+		return;
+	if (sigaction(sig, &oldsa, NULL) < 0)
+		return;
+}
+
+static void handlechildsignal(int sig UNUSED_)
+{
+	if (peerpid == 0 || pagerpid == 0)
+		return;
+	/* if pager exits, notify the server with SIGPIPE immediately.
+	 * otherwise the server won't get SIGPIPE if it does not write
+	 * anything. (issue5278) */
+	if (waitpid(pagerpid, NULL, WNOHANG) == pagerpid)
+		kill(peerpid, SIGPIPE);
+}
+
+/*
+ * Installs signal handlers.
+ *
+ * Returns 0 on success, -1 on error and errno is set appropriately.
+ * Installed handlers wouldn't be cleaned up on error.
+ */
+int setupsignalhandler(pid_t pid, pid_t pgid)
+{
+	if (pid <= 0) {
+		errno = EINVAL;
+		return -1;
+	}
+	peerpid = pid;
+	peerpgid = (pgid <= 1 ? 0 : pgid);
+
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+
+	/* deadly signals meant to be sent to a process group:
+	 * - SIGHUP: usually generated by the kernel, when termination of a
+	 *   process causes that process group to become orphaned
+	 * - SIGINT: usually generated by the terminal */
+	sa.sa_handler = forwardsignaltogroup;
+	sa.sa_flags = SA_RESTART;
+	if (sigemptyset(&sa.sa_mask) < 0)
+		return -1;
+	if (sigaction(SIGHUP, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGINT, &sa, NULL) < 0)
+		return -1;
+
+	/* terminate frontend by double SIGTERM in case of server freeze */
+	sa.sa_handler = forwardsignal;
+	sa.sa_flags |= SA_RESETHAND;
+	if (sigaction(SIGTERM, &sa, NULL) < 0)
+		return -1;
+
+	/* notify the worker about window resize events */
+	sa.sa_flags = SA_RESTART;
+	if (sigaction(SIGWINCH, &sa, NULL) < 0)
+		return -1;
+	/* forward user-defined signals */
+	if (sigaction(SIGUSR1, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGUSR2, &sa, NULL) < 0)
+		return -1;
+	/* propagate job control requests to worker */
+	sa.sa_handler = forwardsignal;
+	sa.sa_flags = SA_RESTART;
+	if (sigaction(SIGCONT, &sa, NULL) < 0)
+		return -1;
+	sa.sa_handler = handlestopsignal;
+	sa.sa_flags = SA_RESTART;
+	if (sigaction(SIGTSTP, &sa, NULL) < 0)
+		return -1;
+	/* get notified when pager exits */
+	sa.sa_handler = handlechildsignal;
+	sa.sa_flags = SA_RESTART;
+	if (sigaction(SIGCHLD, &sa, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Restores signal handlers to the default, and masks SIGINT.
+ *
+ * Returns 0 on success, -1 on error and errno is set appropriately.
+ */
+int restoresignalhandler(void)
+{
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = SIG_DFL;
+	sa.sa_flags = SA_RESTART;
+	if (sigemptyset(&sa.sa_mask) < 0)
+		return -1;
+
+	if (sigaction(SIGHUP, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGTERM, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGWINCH, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGCONT, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGTSTP, &sa, NULL) < 0)
+		return -1;
+	if (sigaction(SIGCHLD, &sa, NULL) < 0)
+		return -1;
+
+	/* ignore Ctrl+C while shutting down to make pager exits cleanly */
+	sa.sa_handler = SIG_IGN;
+	if (sigaction(SIGINT, &sa, NULL) < 0)
+		return -1;
+
+	peerpid = 0;
+	return 0;
+}