chg: let hgc_open support long path
authorJun Wu <quark@fb.com>
Fri, 23 Dec 2016 16:37:00 +0000
changeset 30675 112915e9a363
parent 30674 eb69f78c48ab
child 30676 b83bddfc8048
chg: let hgc_open support long path "sizeof(sun_path)" is too small. Use the chdir trick to support long socket path, like "mercurial.util.bindunixsocket". It's useful for cases where TMPDIR is long. Modern OS X rewrites TMPDIR to a long value. And we probably want to use XDG_RUNTIME_DIR [2] for Linux. The approach is a bit different from the previous plan, where we will have hgc_openat and pass cmdserveropts.sockdirfd to it. That's because the current change is easier: chg has to pass a full path to "hg" as the "--address" parameter. There is no "--address-basename" or "--address-dirfd" flags. The next patch will remove "sockdirfd". Note: It'd be nice if we can use a native "connectat" implementation. However, that's not available everywhere. Some platform (namely FreeBSD) does support it, but the implementation has bugs so it cannot be used [2]. [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html [2]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-April/082892.html
contrib/chg/hgclient.c
--- a/contrib/chg/hgclient.c	Sat Dec 24 15:38:27 2016 -0500
+++ b/contrib/chg/hgclient.c	Fri Dec 23 16:37:00 2016 +0000
@@ -425,10 +425,43 @@
 
 	struct sockaddr_un addr;
 	addr.sun_family = AF_UNIX;
-	strncpy(addr.sun_path, sockname, sizeof(addr.sun_path));
+
+	/* use chdir to workaround small sizeof(sun_path) */
+	int bakfd = -1;
+	const char *basename = sockname;
+	{
+		const char *split = strrchr(sockname, '/');
+		if (split && split != sockname) {
+			if (split[1] == '\0')
+				abortmsg("sockname cannot end with a slash");
+			size_t len = split - sockname;
+			char sockdir[len + 1];
+			memcpy(sockdir, sockname, len);
+			sockdir[len] = '\0';
+
+			bakfd = open(".", O_DIRECTORY);
+			if (bakfd == -1)
+				abortmsgerrno("cannot open cwd");
+
+			int r = chdir(sockdir);
+			if (r != 0)
+				abortmsgerrno("cannot chdir %s", sockdir);
+
+			basename = split + 1;
+		}
+	}
+	if (strlen(basename) >= sizeof(addr.sun_path))
+		abortmsg("sockname is too long: %s", basename);
+	strncpy(addr.sun_path, basename, sizeof(addr.sun_path));
 	addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
 
+	/* real connect */
 	int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+	if (bakfd != -1) {
+		fchdirx(bakfd);
+		close(bakfd);
+	}
+
 	if (r < 0) {
 		close(fd);
 		if (errno == ENOENT || errno == ECONNREFUSED)