/* * Utilities about process handling - signal and subprocess (ex. pager) * * Copyright (c) 2011 Yuya Nishihara * * This software may be used and distributed according to the terms of the * GNU General Public License version 2 or any later version. */ #include #include #include #include #include #include #include #include "procutil.h" #include "util.h" 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); } void setupsignalhandler(pid_t pid, pid_t pgid) { if (pid <= 0) return; 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) 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; /* forward user-defined signals */ if (sigaction(SIGUSR1, &sa, NULL) < 0) goto error; if (sigaction(SIGUSR2, &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"); } void 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) 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 */ pid_t setuppager(const char *pagercmd, const char *envp[]) { assert(pagerpid == 0); 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]); pagerpid = pid; return pid; } else { dup2(pipefds[0], fileno(stdin)); close(pipefds[0]); close(pipefds[1]); int r = execle("/bin/sh", "/bin/sh", "-c", pagercmd, NULL, envp); 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; } void waitpager(void) { if (pagerpid == 0) return; /* close output streams to notify the pager its input ends */ fclose(stdout); fclose(stderr); while (1) { pid_t ret = waitpid(pagerpid, NULL, 0); if (ret == -1 && errno == EINTR) continue; break; } }