chg: move signal and pager handling to a separate file
In the future hgclient will deal with pager directly inside runcommand, so
related signal handling stuff needs to be decoupled from chg.c.
The signal handling and pager logic are coupled because we need to forward
SIGPIPE when pager exits. So they are moved together, otherwise a global
variable (pagerpid) is inevitable.
This patch moves related functions from chg.c to procutil.c, which was
marked as copied to maintain annotate history.
The move is done without code modification for easy review, therefore
`#include "procutil.c"` was introduced temporarily.
--- a/contrib/chg/chg.c Wed Oct 07 16:02:45 2015 -0700
+++ b/contrib/chg/chg.c Mon Jan 02 14:02:47 2017 +0000
@@ -303,214 +303,7 @@
}
}
-static pid_t pagerpid = 0;
-static pid_t peerpgid = 0;
-static pid_t peerpid = 0;
-
-static void forwardsignal(int sig)
-{
- assert(peerpid > 0);
- if (kill(peerpid, sig) < 0)
- abortmsgerrno("cannot kill %d", peerpid);
- debugmsg("forward signal %d", sig);
-}
-
-static void forwardsignaltogroup(int sig)
-{
- /* prefer kill(-pgid, sig), fallback to pid if pgid is invalid */
- pid_t killpid = peerpgid > 1 ? -peerpgid : peerpid;
- if (kill(killpid, sig) < 0)
- abortmsgerrno("cannot kill %d", killpid);
- debugmsg("forward signal %d to %d", sig, killpid);
-}
-
-static void handlestopsignal(int sig)
-{
- sigset_t unblockset, oldset;
- struct sigaction sa, oldsa;
- if (sigemptyset(&unblockset) < 0)
- goto error;
- if (sigaddset(&unblockset, sig) < 0)
- goto error;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = SIG_DFL;
- sa.sa_flags = SA_RESTART;
- if (sigemptyset(&sa.sa_mask) < 0)
- goto error;
-
- forwardsignal(sig);
- if (raise(sig) < 0) /* resend to self */
- goto error;
- if (sigaction(sig, &sa, &oldsa) < 0)
- goto error;
- if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
- goto error;
- /* resent signal will be handled before sigprocmask() returns */
- if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
- goto error;
- if (sigaction(sig, &oldsa, NULL) < 0)
- goto error;
- return;
-
-error:
- abortmsgerrno("failed to handle stop signal");
-}
-
-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);
-}
-
-static void setupsignalhandler(const hgclient_t *hgc)
-{
- pid_t pid = hgc_peerpid(hgc);
- if (pid <= 0)
- return;
- peerpid = pid;
-
- pid_t pgid = hgc_peerpgid(hgc);
- peerpgid = (pgid <= 1 ? 0 : pgid);
-
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = forwardsignaltogroup;
- sa.sa_flags = SA_RESTART;
- if (sigemptyset(&sa.sa_mask) < 0)
- goto error;
-
- if (sigaction(SIGHUP, &sa, NULL) < 0)
- goto error;
- if (sigaction(SIGINT, &sa, NULL) < 0)
- goto error;
-
- /* 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)
- goto error;
-
- /* notify the worker about window resize events */
- sa.sa_flags = SA_RESTART;
- if (sigaction(SIGWINCH, &sa, NULL) < 0)
- goto error;
- /* propagate job control requests to worker */
- sa.sa_handler = forwardsignal;
- sa.sa_flags = SA_RESTART;
- if (sigaction(SIGCONT, &sa, NULL) < 0)
- goto error;
- sa.sa_handler = handlestopsignal;
- sa.sa_flags = SA_RESTART;
- if (sigaction(SIGTSTP, &sa, NULL) < 0)
- goto error;
- /* get notified when pager exits */
- sa.sa_handler = handlechildsignal;
- sa.sa_flags = SA_RESTART;
- if (sigaction(SIGCHLD, &sa, NULL) < 0)
- goto error;
-
- return;
-
-error:
- abortmsgerrno("failed to set up signal handlers");
-}
-
-static void restoresignalhandler()
-{
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = SIG_DFL;
- sa.sa_flags = SA_RESTART;
- if (sigemptyset(&sa.sa_mask) < 0)
- goto error;
-
- if (sigaction(SIGHUP, &sa, NULL) < 0)
- goto error;
- if (sigaction(SIGTERM, &sa, NULL) < 0)
- goto error;
- if (sigaction(SIGWINCH, &sa, NULL) < 0)
- goto error;
- if (sigaction(SIGCONT, &sa, NULL) < 0)
- goto error;
- if (sigaction(SIGTSTP, &sa, NULL) < 0)
- goto error;
- if (sigaction(SIGCHLD, &sa, NULL) < 0)
- goto error;
-
- /* ignore Ctrl+C while shutting down to make pager exits cleanly */
- sa.sa_handler = SIG_IGN;
- if (sigaction(SIGINT, &sa, NULL) < 0)
- goto error;
-
- peerpid = 0;
- return;
-
-error:
- abortmsgerrno("failed to restore signal handlers");
-}
-
-/* This implementation is based on hgext/pager.py (post 369741ef7253)
- * Return 0 if pager is not started, or pid of the pager */
-static pid_t setuppager(hgclient_t *hgc, const char *const args[],
- size_t argsize)
-{
- const char *pagercmd = hgc_getpager(hgc, args, argsize);
- if (!pagercmd)
- return 0;
-
- int pipefds[2];
- if (pipe(pipefds) < 0)
- return 0;
- pid_t pid = fork();
- if (pid < 0)
- goto error;
- if (pid > 0) {
- close(pipefds[0]);
- if (dup2(pipefds[1], fileno(stdout)) < 0)
- goto error;
- if (isatty(fileno(stderr))) {
- if (dup2(pipefds[1], fileno(stderr)) < 0)
- goto error;
- }
- close(pipefds[1]);
- hgc_attachio(hgc); /* reattach to pager */
- return pid;
- } else {
- dup2(pipefds[0], fileno(stdin));
- close(pipefds[0]);
- close(pipefds[1]);
-
- int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
- if (r < 0) {
- abortmsgerrno("cannot start pager '%s'", pagercmd);
- }
- return 0;
- }
-
-error:
- close(pipefds[0]);
- close(pipefds[1]);
- abortmsgerrno("failed to prepare pager");
- return 0;
-}
-
-static void waitpager(pid_t pid)
-{
- /* close output streams to notify the pager its input ends */
- fclose(stdout);
- fclose(stderr);
- while (1) {
- pid_t ret = waitpid(pid, NULL, 0);
- if (ret == -1 && errno == EINTR)
- continue;
- break;
- }
-}
+#include "procutil.c"
/* Run instructions sent from the server like unlink and set redirect path
* Return 1 if reconnect is needed, otherwise 0 */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/chg/procutil.c Mon Jan 02 14:02:47 2017 +0000
@@ -0,0 +1,217 @@
+/*
+ * Utilities about process handling - signal and subprocess (ex. pager)
+ *
+ * Copyright (c) 2011 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.
+ */
+
+static pid_t pagerpid = 0;
+static pid_t peerpgid = 0;
+static pid_t peerpid = 0;
+
+static void forwardsignal(int sig)
+{
+ assert(peerpid > 0);
+ if (kill(peerpid, sig) < 0)
+ abortmsgerrno("cannot kill %d", peerpid);
+ debugmsg("forward signal %d", sig);
+}
+
+static void forwardsignaltogroup(int sig)
+{
+ /* prefer kill(-pgid, sig), fallback to pid if pgid is invalid */
+ pid_t killpid = peerpgid > 1 ? -peerpgid : peerpid;
+ if (kill(killpid, sig) < 0)
+ abortmsgerrno("cannot kill %d", killpid);
+ debugmsg("forward signal %d to %d", sig, killpid);
+}
+
+static void handlestopsignal(int sig)
+{
+ sigset_t unblockset, oldset;
+ struct sigaction sa, oldsa;
+ if (sigemptyset(&unblockset) < 0)
+ goto error;
+ if (sigaddset(&unblockset, sig) < 0)
+ goto error;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = SA_RESTART;
+ if (sigemptyset(&sa.sa_mask) < 0)
+ goto error;
+
+ forwardsignal(sig);
+ if (raise(sig) < 0) /* resend to self */
+ goto error;
+ if (sigaction(sig, &sa, &oldsa) < 0)
+ goto error;
+ if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
+ goto error;
+ /* resent signal will be handled before sigprocmask() returns */
+ if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
+ goto error;
+ if (sigaction(sig, &oldsa, NULL) < 0)
+ goto error;
+ return;
+
+error:
+ abortmsgerrno("failed to handle stop signal");
+}
+
+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);
+}
+
+static void setupsignalhandler(const hgclient_t *hgc)
+{
+ pid_t pid = hgc_peerpid(hgc);
+ if (pid <= 0)
+ return;
+ peerpid = pid;
+
+ pid_t pgid = hgc_peerpgid(hgc);
+ peerpgid = (pgid <= 1 ? 0 : pgid);
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = forwardsignaltogroup;
+ sa.sa_flags = SA_RESTART;
+ if (sigemptyset(&sa.sa_mask) < 0)
+ goto error;
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ goto error;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ goto error;
+
+ /* 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)
+ goto error;
+
+ /* notify the worker about window resize events */
+ sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGWINCH, &sa, NULL) < 0)
+ goto error;
+ /* propagate job control requests to worker */
+ sa.sa_handler = forwardsignal;
+ sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGCONT, &sa, NULL) < 0)
+ goto error;
+ sa.sa_handler = handlestopsignal;
+ sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGTSTP, &sa, NULL) < 0)
+ goto error;
+ /* get notified when pager exits */
+ sa.sa_handler = handlechildsignal;
+ sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGCHLD, &sa, NULL) < 0)
+ goto error;
+
+ return;
+
+error:
+ abortmsgerrno("failed to set up signal handlers");
+}
+
+static void restoresignalhandler()
+{
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = SA_RESTART;
+ if (sigemptyset(&sa.sa_mask) < 0)
+ goto error;
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ goto error;
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ goto error;
+ if (sigaction(SIGWINCH, &sa, NULL) < 0)
+ goto error;
+ if (sigaction(SIGCONT, &sa, NULL) < 0)
+ goto error;
+ if (sigaction(SIGTSTP, &sa, NULL) < 0)
+ goto error;
+ if (sigaction(SIGCHLD, &sa, NULL) < 0)
+ goto error;
+
+ /* ignore Ctrl+C while shutting down to make pager exits cleanly */
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ goto error;
+
+ peerpid = 0;
+ return;
+
+error:
+ abortmsgerrno("failed to restore signal handlers");
+}
+
+/* This implementation is based on hgext/pager.py (post 369741ef7253)
+ * Return 0 if pager is not started, or pid of the pager */
+static pid_t setuppager(hgclient_t *hgc, const char *const args[],
+ size_t argsize)
+{
+ const char *pagercmd = hgc_getpager(hgc, args, argsize);
+ if (!pagercmd)
+ return 0;
+
+ int pipefds[2];
+ if (pipe(pipefds) < 0)
+ return 0;
+ pid_t pid = fork();
+ if (pid < 0)
+ goto error;
+ if (pid > 0) {
+ close(pipefds[0]);
+ if (dup2(pipefds[1], fileno(stdout)) < 0)
+ goto error;
+ if (isatty(fileno(stderr))) {
+ if (dup2(pipefds[1], fileno(stderr)) < 0)
+ goto error;
+ }
+ close(pipefds[1]);
+ hgc_attachio(hgc); /* reattach to pager */
+ return pid;
+ } else {
+ dup2(pipefds[0], fileno(stdin));
+ close(pipefds[0]);
+ close(pipefds[1]);
+
+ int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
+ if (r < 0) {
+ abortmsgerrno("cannot start pager '%s'", pagercmd);
+ }
+ return 0;
+ }
+
+error:
+ close(pipefds[0]);
+ close(pipefds[1]);
+ abortmsgerrno("failed to prepare pager");
+ return 0;
+}
+
+static void waitpager(pid_t pid)
+{
+ /* close output streams to notify the pager its input ends */
+ fclose(stdout);
+ fclose(stderr);
+ while (1) {
+ pid_t ret = waitpid(pid, NULL, 0);
+ if (ret == -1 && errno == EINTR)
+ continue;
+ break;
+ }
+}