procutil.c
237 lines
| 5.6 KiB
| text/x-c
|
CLexer
Jun Wu
|
r30689 | /* | ||
* 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. | ||||
*/ | ||||
Jun Wu
|
r30693 | #include <assert.h> | ||
#include <errno.h> | ||||
#include <signal.h> | ||||
#include <stdio.h> | ||||
#include <string.h> | ||||
#include <sys/wait.h> | ||||
#include <unistd.h> | ||||
#include "procutil.h" | ||||
#include "util.h" | ||||
Jun Wu
|
r30689 | 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); | ||||
} | ||||
Jun Wu
|
r30693 | void setupsignalhandler(pid_t pid, pid_t pgid) | ||
Jun Wu
|
r30689 | { | ||
if (pid <= 0) | ||||
return; | ||||
peerpid = pid; | ||||
peerpgid = (pgid <= 1 ? 0 : pgid); | ||||
struct sigaction sa; | ||||
memset(&sa, 0, sizeof(sa)); | ||||
Jun Wu
|
r31229 | |||
/* 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 */ | ||||
Jun Wu
|
r30689 | 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; | ||||
Jun Wu
|
r31230 | /* forward user-defined signals */ | ||
if (sigaction(SIGUSR1, &sa, NULL) < 0) | ||||
goto error; | ||||
if (sigaction(SIGUSR2, &sa, NULL) < 0) | ||||
goto error; | ||||
Jun Wu
|
r30689 | /* 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"); | ||||
} | ||||
Jun Wu
|
r30693 | void restoresignalhandler(void) | ||
Jun Wu
|
r30689 | { | ||
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 */ | ||||
Jun Wu
|
r31941 | pid_t setuppager(const char *pagercmd, const char *envp[]) | ||
Jun Wu
|
r30689 | { | ||
Jun Wu
|
r30692 | assert(pagerpid == 0); | ||
Jun Wu
|
r30689 | 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]); | ||||
Jun Wu
|
r30692 | pagerpid = pid; | ||
Jun Wu
|
r30689 | return pid; | ||
} else { | ||||
dup2(pipefds[0], fileno(stdin)); | ||||
close(pipefds[0]); | ||||
close(pipefds[1]); | ||||
Jun Wu
|
r31941 | int r = execle("/bin/sh", "/bin/sh", "-c", pagercmd, NULL, | ||
envp); | ||||
Jun Wu
|
r30689 | 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; | ||||
} | ||||
Jun Wu
|
r30693 | void waitpager(void) | ||
Jun Wu
|
r30689 | { | ||
Jun Wu
|
r30692 | if (pagerpid == 0) | ||
return; | ||||
Jun Wu
|
r30689 | /* close output streams to notify the pager its input ends */ | ||
fclose(stdout); | ||||
fclose(stderr); | ||||
while (1) { | ||||
Jun Wu
|
r30692 | pid_t ret = waitpid(pagerpid, NULL, 0); | ||
Jun Wu
|
r30689 | if (ret == -1 && errno == EINTR) | ||
continue; | ||||
break; | ||||
} | ||||
} | ||||