##// END OF EJS Templates
chg: hold a lock file before connected to server...
chg: hold a lock file before connected to server This is a part of the one server per config series. In multiple-server setup, multiple clients may try to start different servers (on demand) at the same time. The old lock will not guarantee a client to connect to the server it just started, and is not crash friendly. This patch addressed above issues by using flock and does not release the lock until the client actually connects to the server or times out.

File last commit:

r28196:87de4a22 default
r28196:87de4a22 default
Show More
chg.c
475 lines | 11.9 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];
char lockfile[UNIX_PATH_MAX];
char pidfile[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;
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;
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;
}
/*
* 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) {
if (arg[len] == '\0') { /* --flag (value) */
return narg + 1;
} else if (arg[len] == '=' && narg > 0) { /* --flag=value */
return 1;
} else if (flags[i].name[1] != '-') { /* short flag */
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)
abortmsg("cannot create sockdir %s (errno = %d)",
sockdir, errno);
struct stat st;
r = lstat(sockdir, &st);
if (r < 0)
abortmsg("cannot stat %s (errno = %d)", sockdir, errno);
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";
const char *pidfmt = (envsockname) ? "%s.pid" : "%s/pid";
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);
r = snprintf(opts->pidfile, sizeof(opts->pidfile), pidfmt, basename);
if (r < 0 || (size_t)r >= sizeof(opts->pidfile))
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) {
opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
if (opts->lockfd == -1)
abortmsg("cannot create lock file %s", opts->lockfile);
}
int r = flock(opts->lockfd, LOCK_EX);
if (r == -1)
abortmsg("cannot acquire lock");
}
/*
* 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 }
static void execcmdserver(const struct cmdserveropts *opts)
{
const char *hgcmd = getenv("CHGHG");
if (!hgcmd || hgcmd[0] == '\0')
hgcmd = getenv("HG");
if (!hgcmd || hgcmd[0] == '\0')
hgcmd = "hg";
Jun Wu
chg: pass sensitive command line flags to server...
r28167 const char *baseargv[] = {
Yuya Nishihara
chg: import frontend sources...
r28060 hgcmd,
"serve",
"--cwd", "/",
"--cmdserver", "chgunix",
"--address", opts->sockname,
Jun Wu
chg: hold a lock file before connected to server...
r28196 "--daemon-postexec", "none",
Yuya Nishihara
chg: import frontend sources...
r28060 "--pid-file", opts->pidfile,
"--config", "extensions.chgserver=",
/* wrap root ui so that it can be disabled/enabled by config */
"--config", "progress.assume-tty=1",
};
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;
Yuya Nishihara
chg: import frontend sources...
r28060 if (execvp(hgcmd, (char **)argv) < 0)
abortmsg("failed to exec cmdserver (errno = %d)", errno);
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;
for (unsigned int i = 0; i < 10 * 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)) {
abortmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
} else if (WIFSIGNALED(pst)) {
abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
} else {
abortmsg("error white waiting cmdserver");
}
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: 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
Jun Wu
chg: hold a lock file before connected to server...
r28196 lockcmdserver(opts);
hgc = hgc_open(opts->sockname);
if (hgc) {
unlockcmdserver(opts);
debugmsg("cmdserver is started by another process");
return hgc;
Yuya Nishihara
chg: import frontend sources...
r28060 }
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) {
Jun Wu
chg: hold a lock file before connected to server...
r28196 /* do not leak lockfd to hg */
close(opts->lockfd);
Yuya Nishihara
chg: import frontend sources...
r28060 /* bypass uisetup() of pager extension */
int nullfd = open("/dev/null", O_WRONLY);
if (nullfd >= 0) {
dup2(nullfd, fileno(stdout));
close(nullfd);
}
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 }
static void killcmdserver(const struct cmdserveropts *opts, int sig)
{
FILE *fp = fopen(opts->pidfile, "r");
if (!fp)
abortmsg("cannot open %s (errno = %d)", opts->pidfile, errno);
int pid = 0;
int n = fscanf(fp, "%d", &pid);
fclose(fp);
if (n != 1 || pid <= 0)
abortmsg("cannot read pid from %s", opts->pidfile);
if (kill((pid_t)pid, sig) < 0) {
if (errno == ESRCH)
return;
abortmsg("cannot kill %d (errno = %d)", pid, errno);
}
}
static pid_t peerpid = 0;
static void forwardsignal(int sig)
{
assert(peerpid > 0);
if (kill(peerpid, sig) < 0)
abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
debugmsg("forward signal %d", sig);
}
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:
abortmsg("failed to handle stop signal (errno = %d)", errno);
}
Yuya Nishihara
chg: import frontend sources...
r28060 static void setupsignalhandler(pid_t pid)
{
if (pid <= 0)
return;
peerpid = pid;
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = forwardsignal;
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 */
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
/* 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;
Yuya Nishihara
chg: verify return value of sigaction() and sigemptyset()...
r28085 return;
error:
abortmsg("failed to set up signal handlers (errno = %d)", errno);
Yuya Nishihara
chg: import frontend sources...
r28060 }
/* This implementation is based on hgext/pager.py (pre 369741ef7253) */
static void setuppager(hgclient_t *hgc, const char *const args[],
size_t argsize)
{
const char *pagercmd = hgc_getpager(hgc, args, argsize);
if (!pagercmd)
return;
int pipefds[2];
if (pipe(pipefds) < 0)
return;
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;
} 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) {
abortmsg("cannot start pager '%s' (errno = %d)",
pagercmd, errno);
}
return;
}
error:
close(pipefds[0]);
close(pipefds[1]);
abortmsg("failed to prepare pager (errno = %d)", errno);
}
int main(int argc, const char *argv[], const char *envp[])
{
if (getenv("CHGDEBUG"))
enabledebugmsg();
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) {
int sig = 0;
if (strcmp(argv[1], "--kill-chg-daemon") == 0)
sig = SIGTERM;
if (strcmp(argv[1], "--reload-chg-daemon") == 0)
sig = SIGHUP;
if (sig > 0) {
killcmdserver(&opts, sig);
return 0;
}
}
Jun Wu
chg: hold a lock file before connected to server...
r28196 hgclient_t *hgc = connectcmdserver(&opts);
Yuya Nishihara
chg: import frontend sources...
r28060 if (!hgc)
abortmsg("cannot open hg client");
setupsignalhandler(hgc_peerpid(hgc));
hgc_setenv(hgc, envp);
setuppager(hgc, argv + 1, argc - 1);
int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
hgc_close(hgc);
Jun Wu
chg: pass sensitive command line flags to server...
r28167 freecmdserveropts(&opts);
Yuya Nishihara
chg: import frontend sources...
r28060 return exitcode;
}