##// END OF EJS Templates
util: implement zstd compression engine...
util: implement zstd compression engine Now that zstd is vendored and being built (in some configurations), we can implement a compression engine for zstd! The zstd engine is a little different from existing engines. Because it may not always be present, we have to defer load the module in case importing it fails. We facilitate this via a cached property that holds a reference to the module or None. The "available" method is implemented to reflect reality. The zstd engine declares its ability to handle bundles using the "zstd" human name and the "ZS" internal name. The latter was chosen because internal names are 2 characters (by only convention I think) and "ZS" seems reasonable. The engine, like others, supports specifying the compression level. However, there are no consumers of this API that yet pass in that argument. I have plans to change that, so stay tuned. Since all we need to do to support bundle generation with a new compression engine is implement and register the compression engine, bundle generation with zstd "just works!" Tests demonstrating this have been added. How does performance of zstd for bundle generation compare? On the mozilla-unified repo, `hg bundle --all -t <engine>-v2` yields the following on my i7-6700K on Linux: engine CPU time bundle size vs orig size throughput none 97.0s 4,054,405,584 100.0% 41.8 MB/s bzip2 (l=9) 393.6s 975,343,098 24.0% 10.3 MB/s gzip (l=6) 184.0s 1,140,533,074 28.1% 22.0 MB/s zstd (l=1) 108.2s 1,119,434,718 27.6% 37.5 MB/s zstd (l=2) 111.3s 1,078,328,002 26.6% 36.4 MB/s zstd (l=3) 113.7s 1,011,823,727 25.0% 35.7 MB/s zstd (l=4) 116.0s 1,008,965,888 24.9% 35.0 MB/s zstd (l=5) 121.0s 977,203,148 24.1% 33.5 MB/s zstd (l=6) 131.7s 927,360,198 22.9% 30.8 MB/s zstd (l=7) 139.0s 912,808,505 22.5% 29.2 MB/s zstd (l=12) 198.1s 854,527,714 21.1% 20.5 MB/s zstd (l=18) 681.6s 789,750,690 19.5% 5.9 MB/s On compression, zstd for bundle generation delivers: * better compression than gzip with significantly less CPU utilization * better than bzip2 compression ratios while still being significantly faster than gzip * ability to aggressively tune compression level to achieve significantly smaller bundles That last point is important. With clone bundles, a server can pre-generate a bundle file, upload it to a static file server, and redirect clients to transparently download it during clone. The server could choose to produce a zstd bundle with the highest compression settings possible. This would take a very long time - a magnitude longer than a typical zstd bundle generation - but the result would be hundreds of megabytes smaller! For the clone volume we do at Mozilla, this could translate to petabytes of bandwidth savings per year and faster clones (due to smaller transfer size). I don't have detailed numbers to report on decompression. However, zstd decompression is fast: >1 GB/s output throughput on this machine, even through the Python bindings. And it can do that regardless of the compression level of the input. By the time you have enough data to worry about overhead of decompression, you have plenty of other things to worry about performance wise. zstd is wins all around. I can't wait to implement support for it on the wire protocol and in revlogs.

File last commit:

r29608:681fe090 stable
r30442:41a81067 default
Show More
chg.c
684 lines | 17.1 KiB | text/x-c | CLexer
Yuya Nishihara
chg: import frontend sources...
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
chg: hold a lock file before connected to server...
r28196 #include <sys/file.h>
Yuya Nishihara
chg: import frontend sources...
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
chg: use validate to make sure the server is up to date...
r28357 char redirectsockname[UNIX_PATH_MAX];
Yuya Nishihara
chg: import frontend sources...
r28060 char lockfile[UNIX_PATH_MAX];
Jun Wu
chg: pass sensitive command line flags to server...
r28167 size_t argsize;
const char **args;
Jun Wu
chg: hold a lock file before connected to server...
r28196 int lockfd;
Jun Wu
chg: add sockdirfd to cmdserveropts...
r28852 int sockdirfd;
Yuya Nishihara
chg: import frontend sources...
r28060 };
Jun Wu
chg: pass sensitive command line flags to server...
r28167 static void initcmdserveropts(struct cmdserveropts *opts) {
memset(opts, 0, sizeof(struct cmdserveropts));
Jun Wu
chg: hold a lock file before connected to server...
r28196 opts->lockfd = -1;
Yuya Nishihara
chg: initialize sockdirfd to -1 instead of AT_FDCWD...
r29016 opts->sockdirfd = -1;
Jun Wu
chg: pass sensitive command line flags to server...
r28167 }
static void freecmdserveropts(struct cmdserveropts *opts) {
free(opts->args);
opts->args = NULL;
opts->argsize = 0;
Jun Wu
chg: check lockfd at freecmdserveropts...
r28853 assert(opts->lockfd == -1 && "should be closed by unlockcmdserver()");
Yuya Nishihara
chg: initialize sockdirfd to -1 instead of AT_FDCWD...
r29016 if (opts->sockdirfd >= 0) {
Jun Wu
chg: add sockdirfd to cmdserveropts...
r28852 close(opts->sockdirfd);
Yuya Nishihara
chg: initialize sockdirfd to -1 instead of AT_FDCWD...
r29016 opts->sockdirfd = -1;
Jun Wu
chg: add sockdirfd to cmdserveropts...
r28852 }
Jun Wu
chg: pass sensitive command line flags to server...
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
chg: wrap line at 80 chars...
r28790 if (arg[len] == '\0') {
/* --flag (value) */
Jun Wu
chg: pass sensitive command line flags to server...
r28167 return narg + 1;
Jun Wu
chg: wrap line at 80 chars...
r28790 } else if (arg[len] == '=' && narg > 0) {
/* --flag=value */
Jun Wu
chg: pass sensitive command line flags to server...
r28167 return 1;
Jun Wu
chg: wrap line at 80 chars...
r28790 } else if (flags[i].name[1] != '-') {
/* short flag */
Jun Wu
chg: pass sensitive command line flags to server...
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
chg: import frontend sources...
r28060 static void preparesockdir(const char *sockdir)
{
int r;
r = mkdir(sockdir, 0700);
if (r < 0 && errno != EEXIST)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("cannot create sockdir %s", sockdir);
Yuya Nishihara
chg: import frontend sources...
r28060
struct stat st;
r = lstat(sockdir, &st);
if (r < 0)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("cannot stat %s", sockdir);
Yuya Nishihara
chg: import frontend sources...
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";
const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
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);
r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
}
/*
Jun Wu
chg: hold a lock file before connected to server...
r28196 * Acquire a file lock that indicates a client is trying to start and connect
* to a server, before executing a command. The lock is released upon exit or
* explicit unlock. Will block if the lock is held by another process.
Yuya Nishihara
chg: import frontend sources...
r28060 */
Jun Wu
chg: hold a lock file before connected to server...
r28196 static void lockcmdserver(struct cmdserveropts *opts)
Yuya Nishihara
chg: import frontend sources...
r28060 {
Jun Wu
chg: hold a lock file before connected to server...
r28196 if (opts->lockfd == -1) {
Jun Wu
chg: wrap line at 80 chars...
r28790 opts->lockfd = open(opts->lockfile,
O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
Jun Wu
chg: hold a lock file before connected to server...
r28196 if (opts->lockfd == -1)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("cannot create lock file %s",
opts->lockfile);
Jun Wu
chg: use fsetcloexec instead of closing lockfd manually...
r28856 fsetcloexec(opts->lockfd);
Jun Wu
chg: hold a lock file before connected to server...
r28196 }
int r = flock(opts->lockfd, LOCK_EX);
if (r == -1)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("cannot acquire lock");
Jun Wu
chg: hold a lock file before connected to server...
r28196 }
/*
* Release the file lock held by calling lockcmdserver. Will do nothing if
* lockcmdserver is not called.
*/
static void unlockcmdserver(struct cmdserveropts *opts)
{
if (opts->lockfd == -1)
return;
flock(opts->lockfd, LOCK_UN);
close(opts->lockfd);
opts->lockfd = -1;
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: extract gethgcmd logic to a function...
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
chg: allows default hg path to be overridden...
r28605 #ifdef HGPATH
hgcmd = (HGPATH);
#else
Jun Wu
chg: extract gethgcmd logic to a function...
r28237 hgcmd = "hg";
Jun Wu
chg: allows default hg path to be overridden...
r28605 #endif
Jun Wu
chg: extract gethgcmd logic to a function...
r28237 }
return hgcmd;
}
Yuya Nishihara
chg: import frontend sources...
r28060 static void execcmdserver(const struct cmdserveropts *opts)
{
Jun Wu
chg: extract gethgcmd logic to a function...
r28237 const char *hgcmd = gethgcmd();
Yuya Nishihara
chg: import frontend sources...
r28060
Jun Wu
chg: pass sensitive command line flags to server...
r28167 const char *baseargv[] = {
Yuya Nishihara
chg: import frontend sources...
r28060 hgcmd,
"serve",
"--cmdserver", "chgunix",
"--address", opts->sockname,
Jun Wu
chg: use --daemon-postexec chdir:/ instead of --cwd /...
r28453 "--daemon-postexec", "chdir:/",
Yuya Nishihara
chg: import frontend sources...
r28060 "--config", "extensions.chgserver=",
};
Jun Wu
chg: pass sensitive command line flags to server...
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
chg: detect chg started by chg...
r28261 if (putenv("CHGINTERNALMARK=") != 0)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("failed to putenv");
Yuya Nishihara
chg: import frontend sources...
r28060 if (execvp(hgcmd, (char **)argv) < 0)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("failed to exec cmdserver");
Jun Wu
chg: pass sensitive command line flags to server...
r28167 free(argv);
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: hold a lock file before connected to server...
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
chg: import frontend sources...
r28060 {
static const struct timespec sleepreq = {0, 10 * 1000000};
int pst = 0;
Jun Wu
chg: make connect debug message less repetitive...
r28769 debugmsg("try connect to %s repeatedly", opts->sockname);
Jun Wu
chg: make timeout adjustable...
r29345
Jun Wu
chg: change default connect timeout to 60 seconds...
r29357 unsigned int timeoutsec = 60; /* default: 60 seconds */
Jun Wu
chg: make timeout adjustable...
r29345 const char *timeoutenv = getenv("CHGTIMEOUT");
if (timeoutenv)
sscanf(timeoutenv, "%u", &timeoutsec);
for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
Jun Wu
chg: hold a lock file before connected to server...
r28196 hgclient_t *hgc = hgc_open(opts->sockname);
if (hgc)
return hgc;
Yuya Nishihara
chg: import frontend sources...
r28060
if (pid > 0) {
/* collect zombie if child process fails to start */
Jun Wu
chg: hold a lock file before connected to server...
r28196 int r = waitpid(pid, &pst, WNOHANG);
Yuya Nishihara
chg: import frontend sources...
r28060 if (r != 0)
goto cleanup;
}
nanosleep(&sleepreq, NULL);
}
Jun Wu
chg: hold a lock file before connected to server...
r28196 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
return NULL;
Yuya Nishihara
chg: import frontend sources...
r28060
cleanup:
if (WIFEXITED(pst)) {
Jun Wu
chg: server exited with code 0 without being connectable is an error...
r28863 if (WEXITSTATUS(pst) == 0)
abortmsg("could not connect to cmdserver "
"(exited with status 0)");
Jun Wu
chg: silently inherit server exit code...
r28477 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
exit(WEXITSTATUS(pst));
Yuya Nishihara
chg: import frontend sources...
r28060 } else if (WIFSIGNALED(pst)) {
abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
} else {
Jun Wu
chg: fix spelling in the error message about error waiting for cmdserver...
r28851 abortmsg("error while waiting for cmdserver");
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: hold a lock file before connected to server...
r28196 return NULL;
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: hold a lock file before connected to server...
r28196 /* Connect to a cmdserver. Will start a new server on demand. */
static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
Yuya Nishihara
chg: import frontend sources...
r28060 {
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 const char *sockname = opts->redirectsockname[0] ?
opts->redirectsockname : opts->sockname;
Jun Wu
chg: make connect debug message less repetitive...
r28769 debugmsg("try connect to %s", sockname);
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 hgclient_t *hgc = hgc_open(sockname);
Jun Wu
chg: hold a lock file before connected to server...
r28196 if (hgc)
return hgc;
Yuya Nishihara
chg: import frontend sources...
r28060
Jun Wu
chg: hold a lock file before connected to server...
r28196 lockcmdserver(opts);
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 hgc = hgc_open(sockname);
Jun Wu
chg: hold a lock file before connected to server...
r28196 if (hgc) {
unlockcmdserver(opts);
debugmsg("cmdserver is started by another process");
return hgc;
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: use validate to make sure the server is up to date...
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
chg: hold a lock file before connected to server...
r28196 debugmsg("start cmdserver at %s", opts->sockname);
Yuya Nishihara
chg: import frontend sources...
r28060
pid_t pid = fork();
if (pid < 0)
abortmsg("failed to fork cmdserver process");
if (pid == 0) {
execcmdserver(opts);
} else {
Jun Wu
chg: hold a lock file before connected to server...
r28196 hgc = retryconnectcmdserver(opts, pid);
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: hold a lock file before connected to server...
r28196
unlockcmdserver(opts);
return hgc;
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: do not write pidfile...
r28455 static void killcmdserver(const struct cmdserveropts *opts)
Yuya Nishihara
chg: import frontend sources...
r28060 {
Jun Wu
chg: do not write pidfile...
r28455 /* resolve config hash */
char *resolvedpath = realpath(opts->sockname, NULL);
if (resolvedpath) {
unlink(resolvedpath);
free(resolvedpath);
Yuya Nishihara
chg: import frontend sources...
r28060 }
}
Jun Wu
chg: send SIGPIPE to server immediately when pager exits (issue5278)...
r29429 static pid_t pagerpid = 0;
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
r29608 static pid_t peerpgid = 0;
Yuya Nishihara
chg: import frontend sources...
r28060 static pid_t peerpid = 0;
static void forwardsignal(int sig)
{
assert(peerpid > 0);
if (kill(peerpid, sig) < 0)
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("cannot kill %d", peerpid);
Yuya Nishihara
chg: import frontend sources...
r28060 debugmsg("forward signal %d", sig);
}
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
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
chg: forward job control signals to worker process (issue5051)...
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
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("failed to handle stop signal");
Yuya Nishihara
chg: forward job control signals to worker process (issue5051)...
r28086 }
Yuya Nishihara
chg: silence warning of unused parameter 'sig'
r29440 static void handlechildsignal(int sig UNUSED_)
Jun Wu
chg: send SIGPIPE to server immediately when pager exits (issue5278)...
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
chg: forward SIGINT, SIGHUP to process group...
r29608 static void setupsignalhandler(const hgclient_t *hgc)
Yuya Nishihara
chg: import frontend sources...
r28060 {
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
r29608 pid_t pid = hgc_peerpid(hgc);
Yuya Nishihara
chg: import frontend sources...
r28060 if (pid <= 0)
return;
peerpid = pid;
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
r29608 pid_t pgid = hgc_peerpgid(hgc);
peerpgid = (pgid <= 1 ? 0 : pgid);
Yuya Nishihara
chg: import frontend sources...
r28060 struct sigaction sa;
memset(&sa, 0, sizeof(sa));
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
r29608 sa.sa_handler = forwardsignaltogroup;
Yuya Nishihara
chg: import frontend sources...
r28060 sa.sa_flags = SA_RESTART;
Yuya Nishihara
chg: verify return value of sigaction() and sigemptyset()...
r28085 if (sigemptyset(&sa.sa_mask) < 0)
goto error;
Yuya Nishihara
chg: import frontend sources...
r28060
Yuya Nishihara
chg: verify return value of sigaction() and sigemptyset()...
r28085 if (sigaction(SIGHUP, &sa, NULL) < 0)
goto error;
if (sigaction(SIGINT, &sa, NULL) < 0)
goto error;
Yuya Nishihara
chg: import frontend sources...
r28060
/* terminate frontend by double SIGTERM in case of server freeze */
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
r29608 sa.sa_handler = forwardsignal;
Yuya Nishihara
chg: import frontend sources...
r28060 sa.sa_flags |= SA_RESETHAND;
Yuya Nishihara
chg: verify return value of sigaction() and sigemptyset()...
r28085 if (sigaction(SIGTERM, &sa, NULL) < 0)
goto error;
Yuya Nishihara
chg: forward job control signals to worker process (issue5051)...
r28086
Jun Wu
chg: forward SIGWINCH to worker...
r28980 /* notify the worker about window resize events */
sa.sa_flags = SA_RESTART;
if (sigaction(SIGWINCH, &sa, NULL) < 0)
goto error;
Yuya Nishihara
chg: forward job control signals to worker process (issue5051)...
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
chg: send SIGPIPE to server immediately when pager exits (issue5278)...
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
chg: forward job control signals to worker process (issue5051)...
r28086
Yuya Nishihara
chg: verify return value of sigaction() and sigemptyset()...
r28085 return;
error:
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("failed to set up signal handlers");
Yuya Nishihara
chg: import frontend sources...
r28060 }
Yuya Nishihara
chg: reset signal handlers to default before waiting pager...
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
chg: send SIGPIPE to server immediately when pager exits (issue5278)...
r29429 if (sigaction(SIGCHLD, &sa, NULL) < 0)
goto error;
Yuya Nishihara
chg: reset signal handlers to default before waiting pager...
r29369
Yuya Nishihara
chg: ignore SIGINT while waiting pager termination...
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
chg: reset signal handlers to default before waiting pager...
r29369 peerpid = 0;
return;
error:
abortmsgerrno("failed to restore signal handlers");
}
Jun Wu
chg: exec pager in child process...
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
chg: import frontend sources...
r28060 size_t argsize)
{
const char *pagercmd = hgc_getpager(hgc, args, argsize);
if (!pagercmd)
Jun Wu
chg: exec pager in child process...
r29344 return 0;
Yuya Nishihara
chg: import frontend sources...
r28060
int pipefds[2];
if (pipe(pipefds) < 0)
Jun Wu
chg: exec pager in child process...
r29344 return 0;
Yuya Nishihara
chg: import frontend sources...
r28060 pid_t pid = fork();
if (pid < 0)
goto error;
Jun Wu
chg: exec pager in child process...
r29344 if (pid > 0) {
Yuya Nishihara
chg: import frontend sources...
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
chg: exec pager in child process...
r29344 return pid;
Yuya Nishihara
chg: import frontend sources...
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
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("cannot start pager '%s'", pagercmd);
Yuya Nishihara
chg: import frontend sources...
r28060 }
Jun Wu
chg: exec pager in child process...
r29344 return 0;
Yuya Nishihara
chg: import frontend sources...
r28060 }
error:
close(pipefds[0]);
close(pipefds[1]);
Jun Wu
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("failed to prepare pager");
Jun Wu
chg: exec pager in child process...
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
chg: import frontend sources...
r28060 }
Jun Wu
chgserver: add an explicit "reconnect" instruction to validate...
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
chg: use validate to make sure the server is up to date...
r28357 {
Jun Wu
chgserver: add an explicit "reconnect" instruction to validate...
r28535 int needreconnect = 0;
if (!insts)
return needreconnect;
Jun Wu
chg: use validate to make sure the server is up to date...
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
chgserver: add an explicit "reconnect" instruction to validate...
r28535 needreconnect = 1;
Jun Wu
chgserver: handle ParseError during validate...
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
chgserver: add an explicit "reconnect" instruction to validate...
r28535 } else if (strcmp(*pinst, "reconnect") == 0) {
needreconnect = 1;
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 } else {
abortmsg("unknown instruction: %s", *pinst);
}
}
Jun Wu
chgserver: add an explicit "reconnect" instruction to validate...
r28535 return needreconnect;
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 }
Jun Wu
chg: fallback to original hg for some unsupported commands or flags...
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
chg: replace abortmsg showing errno with abortmsgerrno...
r28789 abortmsgerrno("failed to exec original hg");
Jun Wu
chg: fallback to original hg for some unsupported commands or flags...
r28260 }
Yuya Nishihara
chg: import frontend sources...
r28060 int main(int argc, const char *argv[], const char *envp[])
{
if (getenv("CHGDEBUG"))
enabledebugmsg();
Jun Wu
chg: use color in debug/error messages conditionally...
r28787 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
enablecolor();
Jun Wu
chg: detect chg started by chg...
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
chg: fallback to original hg for some unsupported commands or flags...
r28260 if (isunsupported(argc - 1, argv + 1))
execoriginalhg(argv);
Yuya Nishihara
chg: import frontend sources...
r28060 struct cmdserveropts opts;
Jun Wu
chg: pass sensitive command line flags to server...
r28167 initcmdserveropts(&opts);
Yuya Nishihara
chg: import frontend sources...
r28060 setcmdserveropts(&opts);
Jun Wu
chg: pass sensitive command line flags to server...
r28167 setcmdserverargs(&opts, argc, argv);
Yuya Nishihara
chg: import frontend sources...
r28060
if (argc == 2) {
Jun Wu
chg: do not write pidfile...
r28455 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
killcmdserver(&opts);
Yuya Nishihara
chg: import frontend sources...
r28060 return 0;
}
}
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 hgclient_t *hgc;
Jun Wu
chg: limit reconnect attempts...
r28358 size_t retry = 0;
Jun Wu
chg: use validate to make sure the server is up to date...
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
chgserver: add an explicit "reconnect" instruction to validate...
r28535 int needreconnect = runinstructions(&opts, insts);
free(insts);
if (!needreconnect)
Jun Wu
chg: use validate to make sure the server is up to date...
r28357 break;
hgc_close(hgc);
Jun Wu
chg: limit reconnect attempts...
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
chg: use validate to make sure the server is up to date...
r28357 }
Yuya Nishihara
chg: import frontend sources...
r28060
Jun Wu
chg: forward SIGINT, SIGHUP to process group...
r29608 setupsignalhandler(hgc);
Jun Wu
chg: send SIGPIPE to server immediately when pager exits (issue5278)...
r29429 pagerpid = setuppager(hgc, argv + 1, argc - 1);
Yuya Nishihara
chg: import frontend sources...
r28060 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
Yuya Nishihara
chg: reset signal handlers to default before waiting pager...
r29369 restoresignalhandler();
Yuya Nishihara
chg: import frontend sources...
r28060 hgc_close(hgc);
Jun Wu
chg: pass sensitive command line flags to server...
r28167 freecmdserveropts(&opts);
Jun Wu
chg: exec pager in child process...
r29344 if (pagerpid)
waitpager(pagerpid);
Yuya Nishihara
chg: import frontend sources...
r28060 return exitcode;
}