chg.c
644 lines
| 16.1 KiB
| text/x-c
|
CLexer
Yuya Nishihara
|
r28060 | /* | ||
* A fast client for Mercurial command server | ||||
* | ||||
* 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. | ||||
*/ | ||||
#include <assert.h> | ||||
#include <errno.h> | ||||
#include <fcntl.h> | ||||
#include <signal.h> | ||||
#include <stdio.h> | ||||
#include <stdlib.h> | ||||
#include <string.h> | ||||
Jun Wu
|
r28196 | #include <sys/file.h> | ||
Yuya Nishihara
|
r28060 | #include <sys/stat.h> | ||
#include <sys/types.h> | ||||
#include <sys/un.h> | ||||
#include <sys/wait.h> | ||||
#include <time.h> | ||||
#include <unistd.h> | ||||
#include "hgclient.h" | ||||
#include "util.h" | ||||
#ifndef UNIX_PATH_MAX | ||||
#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)NULL)->sun_path)) | ||||
#endif | ||||
struct cmdserveropts { | ||||
char sockname[UNIX_PATH_MAX]; | ||||
Jun Wu
|
r30620 | char initsockname[UNIX_PATH_MAX]; | ||
Jun Wu
|
r28357 | char redirectsockname[UNIX_PATH_MAX]; | ||
Jun Wu
|
r28167 | size_t argsize; | ||
const char **args; | ||||
Jun Wu
|
r28852 | int sockdirfd; | ||
Yuya Nishihara
|
r28060 | }; | ||
Jun Wu
|
r28167 | static void initcmdserveropts(struct cmdserveropts *opts) { | ||
memset(opts, 0, sizeof(struct cmdserveropts)); | ||||
Yuya Nishihara
|
r29016 | opts->sockdirfd = -1; | ||
Jun Wu
|
r28167 | } | ||
static void freecmdserveropts(struct cmdserveropts *opts) { | ||||
free(opts->args); | ||||
opts->args = NULL; | ||||
opts->argsize = 0; | ||||
Yuya Nishihara
|
r29016 | if (opts->sockdirfd >= 0) { | ||
Jun Wu
|
r28852 | close(opts->sockdirfd); | ||
Yuya Nishihara
|
r29016 | opts->sockdirfd = -1; | ||
Jun Wu
|
r28852 | } | ||
Jun Wu
|
r28167 | } | ||
/* | ||||
* Test if an argument is a sensitive flag that should be passed to the server. | ||||
* Return 0 if not, otherwise the number of arguments starting from the current | ||||
* one that should be passed to the server. | ||||
*/ | ||||
static size_t testsensitiveflag(const char *arg) | ||||
{ | ||||
static const struct { | ||||
const char *name; | ||||
size_t narg; | ||||
} flags[] = { | ||||
{"--config", 1}, | ||||
{"--cwd", 1}, | ||||
{"--repo", 1}, | ||||
{"--repository", 1}, | ||||
{"--traceback", 0}, | ||||
{"-R", 1}, | ||||
}; | ||||
size_t i; | ||||
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) { | ||||
size_t len = strlen(flags[i].name); | ||||
size_t narg = flags[i].narg; | ||||
if (memcmp(arg, flags[i].name, len) == 0) { | ||||
Jun Wu
|
r28790 | if (arg[len] == '\0') { | ||
/* --flag (value) */ | ||||
Jun Wu
|
r28167 | return narg + 1; | ||
Jun Wu
|
r28790 | } else if (arg[len] == '=' && narg > 0) { | ||
/* --flag=value */ | ||||
Jun Wu
|
r28167 | return 1; | ||
Jun Wu
|
r28790 | } else if (flags[i].name[1] != '-') { | ||
/* short flag */ | ||||
Jun Wu
|
r28167 | return 1; | ||
} | ||||
} | ||||
} | ||||
return 0; | ||||
} | ||||
/* | ||||
* Parse argv[] and put sensitive flags to opts->args | ||||
*/ | ||||
static void setcmdserverargs(struct cmdserveropts *opts, | ||||
int argc, const char *argv[]) | ||||
{ | ||||
size_t i, step; | ||||
opts->argsize = 0; | ||||
for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) { | ||||
if (!argv[i]) | ||||
continue; /* pass clang-analyse */ | ||||
if (strcmp(argv[i], "--") == 0) | ||||
break; | ||||
size_t n = testsensitiveflag(argv[i]); | ||||
if (n == 0 || i + n > (size_t)argc) | ||||
continue; | ||||
opts->args = reallocx(opts->args, | ||||
(n + opts->argsize) * sizeof(char *)); | ||||
memcpy(opts->args + opts->argsize, argv + i, | ||||
sizeof(char *) * n); | ||||
opts->argsize += n; | ||||
step = n; | ||||
} | ||||
} | ||||
Yuya Nishihara
|
r28060 | static void preparesockdir(const char *sockdir) | ||
{ | ||||
int r; | ||||
r = mkdir(sockdir, 0700); | ||||
if (r < 0 && errno != EEXIST) | ||||
Jun Wu
|
r28789 | abortmsgerrno("cannot create sockdir %s", sockdir); | ||
Yuya Nishihara
|
r28060 | |||
struct stat st; | ||||
r = lstat(sockdir, &st); | ||||
if (r < 0) | ||||
Jun Wu
|
r28789 | abortmsgerrno("cannot stat %s", sockdir); | ||
Yuya Nishihara
|
r28060 | if (!S_ISDIR(st.st_mode)) | ||
abortmsg("cannot create sockdir %s (file exists)", sockdir); | ||||
if (st.st_uid != geteuid() || st.st_mode & 0077) | ||||
abortmsg("insecure sockdir %s", sockdir); | ||||
} | ||||
static void setcmdserveropts(struct cmdserveropts *opts) | ||||
{ | ||||
int r; | ||||
char sockdir[UNIX_PATH_MAX]; | ||||
const char *envsockname = getenv("CHGSOCKNAME"); | ||||
if (!envsockname) { | ||||
/* by default, put socket file in secure directory | ||||
* (permission of socket file may be ignored on some Unices) */ | ||||
const char *tmpdir = getenv("TMPDIR"); | ||||
if (!tmpdir) | ||||
tmpdir = "/tmp"; | ||||
r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d", | ||||
tmpdir, geteuid()); | ||||
if (r < 0 || (size_t)r >= sizeof(sockdir)) | ||||
abortmsg("too long TMPDIR (r = %d)", r); | ||||
preparesockdir(sockdir); | ||||
} | ||||
const char *basename = (envsockname) ? envsockname : sockdir; | ||||
const char *sockfmt = (envsockname) ? "%s" : "%s/server"; | ||||
r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename); | ||||
if (r < 0 || (size_t)r >= sizeof(opts->sockname)) | ||||
abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); | ||||
Jun Wu
|
r30620 | r = snprintf(opts->initsockname, sizeof(opts->initsockname), | ||
"%s.%u", opts->sockname, (unsigned)getpid()); | ||||
if (r < 0 || (size_t)r >= sizeof(opts->initsockname)) | ||||
abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); | ||||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28237 | static const char *gethgcmd(void) | ||
{ | ||||
static const char *hgcmd = NULL; | ||||
if (!hgcmd) { | ||||
hgcmd = getenv("CHGHG"); | ||||
if (!hgcmd || hgcmd[0] == '\0') | ||||
hgcmd = getenv("HG"); | ||||
if (!hgcmd || hgcmd[0] == '\0') | ||||
Jun Wu
|
r28605 | #ifdef HGPATH | ||
hgcmd = (HGPATH); | ||||
#else | ||||
Jun Wu
|
r28237 | hgcmd = "hg"; | ||
Jun Wu
|
r28605 | #endif | ||
Jun Wu
|
r28237 | } | ||
return hgcmd; | ||||
} | ||||
Yuya Nishihara
|
r28060 | static void execcmdserver(const struct cmdserveropts *opts) | ||
{ | ||||
Jun Wu
|
r28237 | const char *hgcmd = gethgcmd(); | ||
Yuya Nishihara
|
r28060 | |||
Jun Wu
|
r28167 | const char *baseargv[] = { | ||
Yuya Nishihara
|
r28060 | hgcmd, | ||
"serve", | ||||
"--cmdserver", "chgunix", | ||||
Jun Wu
|
r30620 | "--address", opts->initsockname, | ||
Jun Wu
|
r28453 | "--daemon-postexec", "chdir:/", | ||
Yuya Nishihara
|
r28060 | }; | ||
Jun Wu
|
r28167 | size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]); | ||
size_t argsize = baseargvsize + opts->argsize + 1; | ||||
const char **argv = mallocx(sizeof(char *) * argsize); | ||||
memcpy(argv, baseargv, sizeof(baseargv)); | ||||
memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize); | ||||
argv[argsize - 1] = NULL; | ||||
Jun Wu
|
r28261 | if (putenv("CHGINTERNALMARK=") != 0) | ||
Jun Wu
|
r28789 | abortmsgerrno("failed to putenv"); | ||
Yuya Nishihara
|
r28060 | if (execvp(hgcmd, (char **)argv) < 0) | ||
Jun Wu
|
r28789 | abortmsgerrno("failed to exec cmdserver"); | ||
Jun Wu
|
r28167 | free(argv); | ||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28196 | /* Retry until we can connect to the server. Give up after some time. */ | ||
static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid) | ||||
Yuya Nishihara
|
r28060 | { | ||
static const struct timespec sleepreq = {0, 10 * 1000000}; | ||||
int pst = 0; | ||||
Jun Wu
|
r30620 | debugmsg("try connect to %s repeatedly", opts->initsockname); | ||
Jun Wu
|
r29345 | |||
Jun Wu
|
r29357 | unsigned int timeoutsec = 60; /* default: 60 seconds */ | ||
Jun Wu
|
r29345 | const char *timeoutenv = getenv("CHGTIMEOUT"); | ||
if (timeoutenv) | ||||
sscanf(timeoutenv, "%u", &timeoutsec); | ||||
for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) { | ||||
Jun Wu
|
r30620 | hgclient_t *hgc = hgc_open(opts->initsockname); | ||
if (hgc) { | ||||
debugmsg("rename %s to %s", opts->initsockname, | ||||
opts->sockname); | ||||
int r = rename(opts->initsockname, opts->sockname); | ||||
if (r != 0) | ||||
abortmsgerrno("cannot rename"); | ||||
Jun Wu
|
r28196 | return hgc; | ||
Jun Wu
|
r30620 | } | ||
Yuya Nishihara
|
r28060 | |||
if (pid > 0) { | ||||
/* collect zombie if child process fails to start */ | ||||
Jun Wu
|
r28196 | int r = waitpid(pid, &pst, WNOHANG); | ||
Yuya Nishihara
|
r28060 | if (r != 0) | ||
goto cleanup; | ||||
} | ||||
nanosleep(&sleepreq, NULL); | ||||
} | ||||
Jun Wu
|
r30620 | abortmsg("timed out waiting for cmdserver %s", opts->initsockname); | ||
Jun Wu
|
r28196 | return NULL; | ||
Yuya Nishihara
|
r28060 | |||
cleanup: | ||||
if (WIFEXITED(pst)) { | ||||
Jun Wu
|
r28863 | if (WEXITSTATUS(pst) == 0) | ||
abortmsg("could not connect to cmdserver " | ||||
"(exited with status 0)"); | ||||
Jun Wu
|
r28477 | debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst)); | ||
exit(WEXITSTATUS(pst)); | ||||
Yuya Nishihara
|
r28060 | } else if (WIFSIGNALED(pst)) { | ||
abortmsg("cmdserver killed by signal %d", WTERMSIG(pst)); | ||||
} else { | ||||
Jun Wu
|
r28851 | abortmsg("error while waiting for cmdserver"); | ||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28196 | return NULL; | ||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28196 | /* Connect to a cmdserver. Will start a new server on demand. */ | ||
static hgclient_t *connectcmdserver(struct cmdserveropts *opts) | ||||
Yuya Nishihara
|
r28060 | { | ||
Jun Wu
|
r28357 | const char *sockname = opts->redirectsockname[0] ? | ||
opts->redirectsockname : opts->sockname; | ||||
Jun Wu
|
r28769 | debugmsg("try connect to %s", sockname); | ||
Jun Wu
|
r28357 | hgclient_t *hgc = hgc_open(sockname); | ||
Jun Wu
|
r28196 | if (hgc) | ||
return hgc; | ||||
Yuya Nishihara
|
r28060 | |||
Jun Wu
|
r28357 | /* prevent us from being connected to an outdated server: we were | ||
* told by a server to redirect to opts->redirectsockname and that | ||||
* address does not work. we do not want to connect to the server | ||||
* again because it will probably tell us the same thing. */ | ||||
if (sockname == opts->redirectsockname) | ||||
unlink(opts->sockname); | ||||
Jun Wu
|
r30620 | debugmsg("start cmdserver at %s", opts->initsockname); | ||
Yuya Nishihara
|
r28060 | |||
pid_t pid = fork(); | ||||
if (pid < 0) | ||||
abortmsg("failed to fork cmdserver process"); | ||||
if (pid == 0) { | ||||
execcmdserver(opts); | ||||
} else { | ||||
Jun Wu
|
r28196 | hgc = retryconnectcmdserver(opts, pid); | ||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28196 | |||
return hgc; | ||||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28455 | static void killcmdserver(const struct cmdserveropts *opts) | ||
Yuya Nishihara
|
r28060 | { | ||
Jun Wu
|
r28455 | /* resolve config hash */ | ||
char *resolvedpath = realpath(opts->sockname, NULL); | ||||
if (resolvedpath) { | ||||
unlink(resolvedpath); | ||||
free(resolvedpath); | ||||
Yuya Nishihara
|
r28060 | } | ||
} | ||||
Jun Wu
|
r29429 | static pid_t pagerpid = 0; | ||
Jun Wu
|
r29608 | static pid_t peerpgid = 0; | ||
Yuya Nishihara
|
r28060 | static pid_t peerpid = 0; | ||
static void forwardsignal(int sig) | ||||
{ | ||||
assert(peerpid > 0); | ||||
if (kill(peerpid, sig) < 0) | ||||
Jun Wu
|
r28789 | abortmsgerrno("cannot kill %d", peerpid); | ||
Yuya Nishihara
|
r28060 | debugmsg("forward signal %d", sig); | ||
} | ||||
Jun Wu
|
r29608 | 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); | ||||
} | ||||
Yuya Nishihara
|
r28086 | 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: | ||||
Jun Wu
|
r28789 | abortmsgerrno("failed to handle stop signal"); | ||
Yuya Nishihara
|
r28086 | } | ||
Yuya Nishihara
|
r29440 | static void handlechildsignal(int sig UNUSED_) | ||
Jun Wu
|
r29429 | { | ||
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
|
r29608 | static void setupsignalhandler(const hgclient_t *hgc) | ||
Yuya Nishihara
|
r28060 | { | ||
Jun Wu
|
r29608 | pid_t pid = hgc_peerpid(hgc); | ||
Yuya Nishihara
|
r28060 | if (pid <= 0) | ||
return; | ||||
peerpid = pid; | ||||
Jun Wu
|
r29608 | pid_t pgid = hgc_peerpgid(hgc); | ||
peerpgid = (pgid <= 1 ? 0 : pgid); | ||||
Yuya Nishihara
|
r28060 | struct sigaction sa; | ||
memset(&sa, 0, sizeof(sa)); | ||||
Jun Wu
|
r29608 | sa.sa_handler = forwardsignaltogroup; | ||
Yuya Nishihara
|
r28060 | sa.sa_flags = SA_RESTART; | ||
Yuya Nishihara
|
r28085 | if (sigemptyset(&sa.sa_mask) < 0) | ||
goto error; | ||||
Yuya Nishihara
|
r28060 | |||
Yuya Nishihara
|
r28085 | if (sigaction(SIGHUP, &sa, NULL) < 0) | ||
goto error; | ||||
if (sigaction(SIGINT, &sa, NULL) < 0) | ||||
goto error; | ||||
Yuya Nishihara
|
r28060 | |||
/* terminate frontend by double SIGTERM in case of server freeze */ | ||||
Jun Wu
|
r29608 | sa.sa_handler = forwardsignal; | ||
Yuya Nishihara
|
r28060 | sa.sa_flags |= SA_RESETHAND; | ||
Yuya Nishihara
|
r28085 | if (sigaction(SIGTERM, &sa, NULL) < 0) | ||
goto error; | ||||
Yuya Nishihara
|
r28086 | |||
Jun Wu
|
r28980 | /* notify the worker about window resize events */ | ||
sa.sa_flags = SA_RESTART; | ||||
if (sigaction(SIGWINCH, &sa, NULL) < 0) | ||||
goto error; | ||||
Yuya Nishihara
|
r28086 | /* 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; | ||||
Jun Wu
|
r29429 | /* get notified when pager exits */ | ||
sa.sa_handler = handlechildsignal; | ||||
sa.sa_flags = SA_RESTART; | ||||
if (sigaction(SIGCHLD, &sa, NULL) < 0) | ||||
goto error; | ||||
Yuya Nishihara
|
r28086 | |||
Yuya Nishihara
|
r28085 | return; | ||
error: | ||||
Jun Wu
|
r28789 | abortmsgerrno("failed to set up signal handlers"); | ||
Yuya Nishihara
|
r28060 | } | ||
Yuya Nishihara
|
r29369 | 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; | ||||
Jun Wu
|
r29429 | if (sigaction(SIGCHLD, &sa, NULL) < 0) | ||
goto error; | ||||
Yuya Nishihara
|
r29369 | |||
Yuya Nishihara
|
r29370 | /* ignore Ctrl+C while shutting down to make pager exits cleanly */ | ||
sa.sa_handler = SIG_IGN; | ||||
if (sigaction(SIGINT, &sa, NULL) < 0) | ||||
goto error; | ||||
Yuya Nishihara
|
r29369 | peerpid = 0; | ||
return; | ||||
error: | ||||
abortmsgerrno("failed to restore signal handlers"); | ||||
} | ||||
Jun Wu
|
r29344 | /* 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[], | ||||
Yuya Nishihara
|
r28060 | size_t argsize) | ||
{ | ||||
const char *pagercmd = hgc_getpager(hgc, args, argsize); | ||||
if (!pagercmd) | ||||
Jun Wu
|
r29344 | return 0; | ||
Yuya Nishihara
|
r28060 | |||
int pipefds[2]; | ||||
if (pipe(pipefds) < 0) | ||||
Jun Wu
|
r29344 | return 0; | ||
Yuya Nishihara
|
r28060 | pid_t pid = fork(); | ||
if (pid < 0) | ||||
goto error; | ||||
Jun Wu
|
r29344 | if (pid > 0) { | ||
Yuya Nishihara
|
r28060 | 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 */ | ||||
Jun Wu
|
r29344 | return pid; | ||
Yuya Nishihara
|
r28060 | } 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) { | ||||
Jun Wu
|
r28789 | abortmsgerrno("cannot start pager '%s'", pagercmd); | ||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r29344 | return 0; | ||
Yuya Nishihara
|
r28060 | } | ||
error: | ||||
close(pipefds[0]); | ||||
close(pipefds[1]); | ||||
Jun Wu
|
r28789 | abortmsgerrno("failed to prepare pager"); | ||
Jun Wu
|
r29344 | 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; | ||||
} | ||||
Yuya Nishihara
|
r28060 | } | ||
Jun Wu
|
r28535 | /* Run instructions sent from the server like unlink and set redirect path | ||
* Return 1 if reconnect is needed, otherwise 0 */ | ||||
static int runinstructions(struct cmdserveropts *opts, const char **insts) | ||||
Jun Wu
|
r28357 | { | ||
Jun Wu
|
r28535 | int needreconnect = 0; | ||
if (!insts) | ||||
return needreconnect; | ||||
Jun Wu
|
r28357 | assert(insts); | ||
opts->redirectsockname[0] = '\0'; | ||||
const char **pinst; | ||||
for (pinst = insts; *pinst; pinst++) { | ||||
debugmsg("instruction: %s", *pinst); | ||||
if (strncmp(*pinst, "unlink ", 7) == 0) { | ||||
unlink(*pinst + 7); | ||||
} else if (strncmp(*pinst, "redirect ", 9) == 0) { | ||||
int r = snprintf(opts->redirectsockname, | ||||
sizeof(opts->redirectsockname), | ||||
"%s", *pinst + 9); | ||||
if (r < 0 || r >= (int)sizeof(opts->redirectsockname)) | ||||
abortmsg("redirect path is too long (%d)", r); | ||||
Jun Wu
|
r28535 | needreconnect = 1; | ||
Jun Wu
|
r28516 | } else if (strncmp(*pinst, "exit ", 5) == 0) { | ||
int n = 0; | ||||
if (sscanf(*pinst + 5, "%d", &n) != 1) | ||||
abortmsg("cannot read the exit code"); | ||||
exit(n); | ||||
Jun Wu
|
r28535 | } else if (strcmp(*pinst, "reconnect") == 0) { | ||
needreconnect = 1; | ||||
Jun Wu
|
r28357 | } else { | ||
abortmsg("unknown instruction: %s", *pinst); | ||||
} | ||||
} | ||||
Jun Wu
|
r28535 | return needreconnect; | ||
Jun Wu
|
r28357 | } | ||
Jun Wu
|
r28260 | /* | ||
* Test whether the command is unsupported or not. This is not designed to | ||||
* cover all cases. But it's fast, does not depend on the server and does | ||||
* not return false positives. | ||||
*/ | ||||
static int isunsupported(int argc, const char *argv[]) | ||||
{ | ||||
enum { | ||||
SERVE = 1, | ||||
DAEMON = 2, | ||||
SERVEDAEMON = SERVE | DAEMON, | ||||
TIME = 4, | ||||
}; | ||||
unsigned int state = 0; | ||||
int i; | ||||
for (i = 0; i < argc; ++i) { | ||||
if (strcmp(argv[i], "--") == 0) | ||||
break; | ||||
if (i == 0 && strcmp("serve", argv[i]) == 0) | ||||
state |= SERVE; | ||||
else if (strcmp("-d", argv[i]) == 0 || | ||||
strcmp("--daemon", argv[i]) == 0) | ||||
state |= DAEMON; | ||||
else if (strcmp("--time", argv[i]) == 0) | ||||
state |= TIME; | ||||
} | ||||
return (state & TIME) == TIME || | ||||
(state & SERVEDAEMON) == SERVEDAEMON; | ||||
} | ||||
static void execoriginalhg(const char *argv[]) | ||||
{ | ||||
debugmsg("execute original hg"); | ||||
if (execvp(gethgcmd(), (char **)argv) < 0) | ||||
Jun Wu
|
r28789 | abortmsgerrno("failed to exec original hg"); | ||
Jun Wu
|
r28260 | } | ||
Yuya Nishihara
|
r28060 | int main(int argc, const char *argv[], const char *envp[]) | ||
{ | ||||
if (getenv("CHGDEBUG")) | ||||
enabledebugmsg(); | ||||
Jun Wu
|
r28787 | if (!getenv("HGPLAIN") && isatty(fileno(stderr))) | ||
enablecolor(); | ||||
Jun Wu
|
r28261 | if (getenv("CHGINTERNALMARK")) | ||
abortmsg("chg started by chg detected.\n" | ||||
"Please make sure ${HG:-hg} is not a symlink or " | ||||
"wrapper to chg. Alternatively, set $CHGHG to the " | ||||
"path of real hg."); | ||||
Jun Wu
|
r28260 | if (isunsupported(argc - 1, argv + 1)) | ||
execoriginalhg(argv); | ||||
Yuya Nishihara
|
r28060 | struct cmdserveropts opts; | ||
Jun Wu
|
r28167 | initcmdserveropts(&opts); | ||
Yuya Nishihara
|
r28060 | setcmdserveropts(&opts); | ||
Jun Wu
|
r28167 | setcmdserverargs(&opts, argc, argv); | ||
Yuya Nishihara
|
r28060 | |||
if (argc == 2) { | ||||
Jun Wu
|
r28455 | if (strcmp(argv[1], "--kill-chg-daemon") == 0) { | ||
killcmdserver(&opts); | ||||
Yuya Nishihara
|
r28060 | return 0; | ||
} | ||||
} | ||||
Jun Wu
|
r28357 | hgclient_t *hgc; | ||
Jun Wu
|
r28358 | size_t retry = 0; | ||
Jun Wu
|
r28357 | while (1) { | ||
hgc = connectcmdserver(&opts); | ||||
if (!hgc) | ||||
abortmsg("cannot open hg client"); | ||||
hgc_setenv(hgc, envp); | ||||
const char **insts = hgc_validate(hgc, argv + 1, argc - 1); | ||||
Jun Wu
|
r28535 | int needreconnect = runinstructions(&opts, insts); | ||
free(insts); | ||||
if (!needreconnect) | ||||
Jun Wu
|
r28357 | break; | ||
hgc_close(hgc); | ||||
Jun Wu
|
r28358 | if (++retry > 10) | ||
abortmsg("too many redirections.\n" | ||||
"Please make sure %s is not a wrapper which " | ||||
"changes sensitive environment variables " | ||||
"before executing hg. If you have to use a " | ||||
"wrapper, wrap chg instead of hg.", | ||||
gethgcmd()); | ||||
Jun Wu
|
r28357 | } | ||
Yuya Nishihara
|
r28060 | |||
Jun Wu
|
r29608 | setupsignalhandler(hgc); | ||
Jun Wu
|
r29429 | pagerpid = setuppager(hgc, argv + 1, argc - 1); | ||
Yuya Nishihara
|
r28060 | int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1); | ||
Yuya Nishihara
|
r29369 | restoresignalhandler(); | ||
Yuya Nishihara
|
r28060 | hgc_close(hgc); | ||
Jun Wu
|
r28167 | freecmdserveropts(&opts); | ||
Jun Wu
|
r29344 | if (pagerpid) | ||
waitpager(pagerpid); | ||||
Yuya Nishihara
|
r28060 | return exitcode; | ||
} | ||||