contrib/chg/chg.c
changeset 28196 87de4a22e8c2
parent 28194 7623ba92af72
child 28237 a3d73e069f8d
--- a/contrib/chg/chg.c	Mon Feb 22 17:30:02 2016 +0000
+++ b/contrib/chg/chg.c	Tue Feb 16 11:08:52 2016 +0000
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
@@ -34,10 +35,12 @@
 	char pidfile[UNIX_PATH_MAX];
 	size_t argsize;
 	const char **args;
+	int lockfd;
 };
 
 static void initcmdserveropts(struct cmdserveropts *opts) {
 	memset(opts, 0, sizeof(struct cmdserveropts));
+	opts->lockfd = -1;
 }
 
 static void freecmdserveropts(struct cmdserveropts *opts) {
@@ -158,21 +161,33 @@
 }
 
 /*
- * Make lock file that indicates cmdserver process is about to start. Created
- * lock file will be deleted by server. (0: success, -1: lock exists)
+ * 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.
  */
-static int lockcmdserver(const struct cmdserveropts *opts)
+static void lockcmdserver(struct cmdserveropts *opts)
 {
-	int r;
-	char info[32];
-	r = snprintf(info, sizeof(info), "%d", getpid());
-	if (r < 0 || (size_t)r >= sizeof(info))
-		abortmsg("failed to format lock info");
-	r = symlink(info, opts->lockfile);
-	if (r < 0 && errno != EEXIST)
-		abortmsg("failed to make lock %s (errno = %d)",
-			 opts->lockfile, errno);
-	return r;
+	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;
 }
 
 static void execcmdserver(const struct cmdserveropts *opts)
@@ -189,7 +204,7 @@
 		"--cwd", "/",
 		"--cmdserver", "chgunix",
 		"--address", opts->sockname,
-		"--daemon-postexec", opts->lockfile,
+		"--daemon-postexec", "none",
 		"--pid-file", opts->pidfile,
 		"--config", "extensions.chgserver=",
 		/* wrap root ui so that it can be disabled/enabled by config */
@@ -208,28 +223,20 @@
 	free(argv);
 }
 
-/*
- * Sleep until lock file is deleted, i.e. cmdserver process starts listening.
- * If pid is given, it also checks if the child process fails to start.
- */
-static void waitcmdserver(const struct cmdserveropts *opts, pid_t pid)
+/* Retry until we can connect to the server. Give up after some time. */
+static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
 {
 	static const struct timespec sleepreq = {0, 10 * 1000000};
 	int pst = 0;
 
 	for (unsigned int i = 0; i < 10 * 100; i++) {
-		int r;
-		struct stat lst;
-
-		r = lstat(opts->lockfile, &lst);
-		if (r < 0 && errno == ENOENT)
-			return;  /* lock file deleted by server */
-		if (r < 0)
-			goto cleanup;
+		hgclient_t *hgc = hgc_open(opts->sockname);
+		if (hgc)
+			return hgc;
 
 		if (pid > 0) {
 			/* collect zombie if child process fails to start */
-			r = waitpid(pid, &pst, WNOHANG);
+			int r = waitpid(pid, &pst, WNOHANG);
 			if (r != 0)
 				goto cleanup;
 		}
@@ -237,13 +244,10 @@
 		nanosleep(&sleepreq, NULL);
 	}
 
-	abortmsg("timed out waiting for cmdserver %s", opts->lockfile);
-	return;
+	abortmsg("timed out waiting for cmdserver %s", opts->sockname);
+	return NULL;
 
 cleanup:
-	if (pid > 0)
-		/* lockfile should be made by this process */
-		unlink(opts->lockfile);
 	if (WIFEXITED(pst)) {
 		abortmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
 	} else if (WIFSIGNALED(pst)) {
@@ -251,26 +255,32 @@
 	} else {
 		abortmsg("error white waiting cmdserver");
 	}
+	return NULL;
 }
 
-/* Spawn new background cmdserver */
-static void startcmdserver(const struct cmdserveropts *opts)
+/* Connect to a cmdserver. Will start a new server on demand. */
+static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
 {
-	debugmsg("start cmdserver at %s", opts->sockname);
+	hgclient_t *hgc = hgc_open(opts->sockname);
+	if (hgc)
+		return hgc;
 
-	if (lockcmdserver(opts) < 0) {
-		debugmsg("lock file exists, waiting...");
-		waitcmdserver(opts, 0);
-		return;
+	lockcmdserver(opts);
+	hgc = hgc_open(opts->sockname);
+	if (hgc) {
+		unlockcmdserver(opts);
+		debugmsg("cmdserver is started by another process");
+		return hgc;
 	}
 
-	/* remove dead cmdserver socket if any */
-	unlink(opts->sockname);
+	debugmsg("start cmdserver at %s", opts->sockname);
 
 	pid_t pid = fork();
 	if (pid < 0)
 		abortmsg("failed to fork cmdserver process");
 	if (pid == 0) {
+		/* do not leak lockfd to hg */
+		close(opts->lockfd);
 		/* bypass uisetup() of pager extension */
 		int nullfd = open("/dev/null", O_WRONLY);
 		if (nullfd >= 0) {
@@ -279,8 +289,11 @@
 		}
 		execcmdserver(opts);
 	} else {
-		waitcmdserver(opts, pid);
+		hgc = retryconnectcmdserver(opts, pid);
 	}
+
+	unlockcmdserver(opts);
+	return hgc;
 }
 
 static void killcmdserver(const struct cmdserveropts *opts, int sig)
@@ -448,11 +461,7 @@
 		}
 	}
 
-	hgclient_t *hgc = hgc_open(opts.sockname);
-	if (!hgc) {
-		startcmdserver(&opts);
-		hgc = hgc_open(opts.sockname);
-	}
+	hgclient_t *hgc = connectcmdserver(&opts);
 	if (!hgc)
 		abortmsg("cannot open hg client");