##// END OF EJS Templates
chg: make is possible to call by default an hg binary located next to chg...
Valentin Gatien-Baron -
r46125:eb443f7c default draft
parent child Browse files
Show More
@@ -1,40 +1,43 b''
1 TARGET = chg
1 TARGET = chg
2 SRCS = chg.c hgclient.c procutil.c util.c
2 SRCS = chg.c hgclient.c procutil.c util.c
3 OBJS = $(SRCS:.c=.o)
3 OBJS = $(SRCS:.c=.o)
4
4
5 CFLAGS ?= -O2 -Wall -Wextra -pedantic -g
5 CFLAGS ?= -O2 -Wall -Wextra -pedantic -g
6 CPPFLAGS ?= -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE
6 CPPFLAGS ?= -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE
7 override CFLAGS += -std=gnu99
7 override CFLAGS += -std=gnu99
8 ifdef HGPATH
8 ifdef HGPATH
9 override CPPFLAGS += -DHGPATH=\"$(HGPATH)\"
9 override CPPFLAGS += -DHGPATH=\"$(HGPATH)\"
10 endif
10 endif
11 ifdef HGPATHREL
12 override CPPFLAGS += -DHGPATHREL=\"$(HGPATHREL)\"
13 endif
11
14
12 DESTDIR =
15 DESTDIR =
13 PREFIX = /usr/local
16 PREFIX = /usr/local
14 MANDIR = $(PREFIX)/share/man/man1
17 MANDIR = $(PREFIX)/share/man/man1
15
18
16 .PHONY: all
19 .PHONY: all
17 all: $(TARGET)
20 all: $(TARGET)
18
21
19 $(TARGET): $(OBJS)
22 $(TARGET): $(OBJS)
20 $(CC) $(LDFLAGS) -o $@ $(OBJS)
23 $(CC) $(LDFLAGS) -o $@ $(OBJS)
21
24
22 chg.o: hgclient.h procutil.h util.h
25 chg.o: hgclient.h procutil.h util.h
23 hgclient.o: hgclient.h procutil.h util.h
26 hgclient.o: hgclient.h procutil.h util.h
24 procutil.o: procutil.h util.h
27 procutil.o: procutil.h util.h
25 util.o: util.h
28 util.o: util.h
26
29
27 .PHONY: install
30 .PHONY: install
28 install: $(TARGET)
31 install: $(TARGET)
29 install -d "$(DESTDIR)$(PREFIX)"/bin
32 install -d "$(DESTDIR)$(PREFIX)"/bin
30 install -m 755 "$(TARGET)" "$(DESTDIR)$(PREFIX)"/bin
33 install -m 755 "$(TARGET)" "$(DESTDIR)$(PREFIX)"/bin
31 install -d "$(DESTDIR)$(MANDIR)"
34 install -d "$(DESTDIR)$(MANDIR)"
32 install -m 644 chg.1 "$(DESTDIR)$(MANDIR)"
35 install -m 644 chg.1 "$(DESTDIR)$(MANDIR)"
33
36
34 .PHONY: clean
37 .PHONY: clean
35 clean:
38 clean:
36 $(RM) $(OBJS)
39 $(RM) $(OBJS)
37
40
38 .PHONY: distclean
41 .PHONY: distclean
39 distclean:
42 distclean:
40 $(RM) $(OBJS) $(TARGET)
43 $(RM) $(OBJS) $(TARGET)
@@ -1,32 +1,40 b''
1 cHg
1 cHg
2 ===
2 ===
3
3
4 A fast client for Mercurial command server running on Unix.
4 A fast client for Mercurial command server running on Unix.
5
5
6 Install:
6 Install:
7
7
8 $ make
8 $ make
9 $ make install
9 $ make install
10
10
11 Usage:
11 Usage:
12
12
13 $ chg help # show help of Mercurial
13 $ chg help # show help of Mercurial
14 $ alias hg=chg # replace hg command
14 $ alias hg=chg # replace hg command
15 $ chg --kill-chg-daemon # terminate background server
15 $ chg --kill-chg-daemon # terminate background server
16
16
17 Environment variables:
17 Environment variables:
18
18
19 Although cHg tries to update environment variables, some of them cannot be
19 Although cHg tries to update environment variables, some of them cannot be
20 changed after spawning the server. The following variables are specially
20 changed after spawning the server. The following variables are specially
21 handled:
21 handled:
22
22
23 * configuration files are reloaded automatically by default.
23 * configuration files are reloaded automatically by default.
24 * CHGHG or HG specifies the path to the hg executable spawned as the
24 * CHGHG or HG specifies the path to the hg executable spawned as the
25 background command server.
25 background command server.
26
26
27 The following variables are available for testing:
27 The following variables are available for testing:
28
28
29 * CHGDEBUG enables debug messages.
29 * CHGDEBUG enables debug messages.
30 * CHGSOCKNAME specifies the socket path of the background cmdserver.
30 * CHGSOCKNAME specifies the socket path of the background cmdserver.
31 * CHGTIMEOUT specifies how many seconds chg will wait before giving up
31 * CHGTIMEOUT specifies how many seconds chg will wait before giving up
32 connecting to a cmdserver. If it is 0, chg will wait forever. Default: 60
32 connecting to a cmdserver. If it is 0, chg will wait forever. Default: 60
33
34 Build environment variables:
35
36 * HGPATH: the path to the hg executable to call when CHGHG and HG are not set,
37 instead of "hg"
38 * HGPATHREL=1: when CHGHG and HG are not set, the hg executable will be ./hg
39 relative to the chg executable. Only works on linux, falls back to "hg"
40 otherwise.
@@ -1,484 +1,517 b''
1 /*
1 /*
2 * A fast client for Mercurial command server
2 * A fast client for Mercurial command server
3 *
3 *
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 *
5 *
6 * This software may be used and distributed according to the terms of the
6 * This software may be used and distributed according to the terms of the
7 * GNU General Public License version 2 or any later version.
7 * GNU General Public License version 2 or any later version.
8 */
8 */
9
9
10 #include <assert.h>
10 #include <assert.h>
11 #include <errno.h>
11 #include <errno.h>
12 #include <fcntl.h>
12 #include <fcntl.h>
13 #include <signal.h>
13 #include <signal.h>
14 #include <stdio.h>
14 #include <stdio.h>
15 #include <stdlib.h>
15 #include <stdlib.h>
16 #include <string.h>
16 #include <string.h>
17 #include <sys/file.h>
17 #include <sys/file.h>
18 #include <sys/stat.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
19 #include <sys/types.h>
20 #include <sys/un.h>
20 #include <sys/un.h>
21 #include <sys/wait.h>
21 #include <sys/wait.h>
22 #include <time.h>
22 #include <time.h>
23 #include <unistd.h>
23 #include <unistd.h>
24
24
25 #include "hgclient.h"
25 #include "hgclient.h"
26 #include "procutil.h"
26 #include "procutil.h"
27 #include "util.h"
27 #include "util.h"
28
28
29 #ifndef PATH_MAX
29 #ifndef PATH_MAX
30 #define PATH_MAX 4096
30 #define PATH_MAX 4096
31 #endif
31 #endif
32
32
33 struct cmdserveropts {
33 struct cmdserveropts {
34 char sockname[PATH_MAX];
34 char sockname[PATH_MAX];
35 char initsockname[PATH_MAX];
35 char initsockname[PATH_MAX];
36 char redirectsockname[PATH_MAX];
36 char redirectsockname[PATH_MAX];
37 size_t argsize;
37 size_t argsize;
38 const char **args;
38 const char **args;
39 };
39 };
40
40
41 static void initcmdserveropts(struct cmdserveropts *opts)
41 static void initcmdserveropts(struct cmdserveropts *opts)
42 {
42 {
43 memset(opts, 0, sizeof(struct cmdserveropts));
43 memset(opts, 0, sizeof(struct cmdserveropts));
44 }
44 }
45
45
46 static void freecmdserveropts(struct cmdserveropts *opts)
46 static void freecmdserveropts(struct cmdserveropts *opts)
47 {
47 {
48 free(opts->args);
48 free(opts->args);
49 opts->args = NULL;
49 opts->args = NULL;
50 opts->argsize = 0;
50 opts->argsize = 0;
51 }
51 }
52
52
53 /*
53 /*
54 * Test if an argument is a sensitive flag that should be passed to the server.
54 * Test if an argument is a sensitive flag that should be passed to the server.
55 * Return 0 if not, otherwise the number of arguments starting from the current
55 * Return 0 if not, otherwise the number of arguments starting from the current
56 * one that should be passed to the server.
56 * one that should be passed to the server.
57 */
57 */
58 static size_t testsensitiveflag(const char *arg)
58 static size_t testsensitiveflag(const char *arg)
59 {
59 {
60 static const struct {
60 static const struct {
61 const char *name;
61 const char *name;
62 size_t narg;
62 size_t narg;
63 } flags[] = {
63 } flags[] = {
64 {"--config", 1}, {"--cwd", 1}, {"--repo", 1},
64 {"--config", 1}, {"--cwd", 1}, {"--repo", 1},
65 {"--repository", 1}, {"--traceback", 0}, {"-R", 1},
65 {"--repository", 1}, {"--traceback", 0}, {"-R", 1},
66 };
66 };
67 size_t i;
67 size_t i;
68 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
68 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
69 size_t len = strlen(flags[i].name);
69 size_t len = strlen(flags[i].name);
70 size_t narg = flags[i].narg;
70 size_t narg = flags[i].narg;
71 if (memcmp(arg, flags[i].name, len) == 0) {
71 if (memcmp(arg, flags[i].name, len) == 0) {
72 if (arg[len] == '\0') {
72 if (arg[len] == '\0') {
73 /* --flag (value) */
73 /* --flag (value) */
74 return narg + 1;
74 return narg + 1;
75 } else if (arg[len] == '=' && narg > 0) {
75 } else if (arg[len] == '=' && narg > 0) {
76 /* --flag=value */
76 /* --flag=value */
77 return 1;
77 return 1;
78 } else if (flags[i].name[1] != '-') {
78 } else if (flags[i].name[1] != '-') {
79 /* short flag */
79 /* short flag */
80 return 1;
80 return 1;
81 }
81 }
82 }
82 }
83 }
83 }
84 return 0;
84 return 0;
85 }
85 }
86
86
87 /*
87 /*
88 * Parse argv[] and put sensitive flags to opts->args
88 * Parse argv[] and put sensitive flags to opts->args
89 */
89 */
90 static void setcmdserverargs(struct cmdserveropts *opts, int argc,
90 static void setcmdserverargs(struct cmdserveropts *opts, int argc,
91 const char *argv[])
91 const char *argv[])
92 {
92 {
93 size_t i, step;
93 size_t i, step;
94 opts->argsize = 0;
94 opts->argsize = 0;
95 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
95 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
96 if (!argv[i])
96 if (!argv[i])
97 continue; /* pass clang-analyse */
97 continue; /* pass clang-analyse */
98 if (strcmp(argv[i], "--") == 0)
98 if (strcmp(argv[i], "--") == 0)
99 break;
99 break;
100 size_t n = testsensitiveflag(argv[i]);
100 size_t n = testsensitiveflag(argv[i]);
101 if (n == 0 || i + n > (size_t)argc)
101 if (n == 0 || i + n > (size_t)argc)
102 continue;
102 continue;
103 opts->args =
103 opts->args =
104 reallocx(opts->args, (n + opts->argsize) * sizeof(char *));
104 reallocx(opts->args, (n + opts->argsize) * sizeof(char *));
105 memcpy(opts->args + opts->argsize, argv + i,
105 memcpy(opts->args + opts->argsize, argv + i,
106 sizeof(char *) * n);
106 sizeof(char *) * n);
107 opts->argsize += n;
107 opts->argsize += n;
108 step = n;
108 step = n;
109 }
109 }
110 }
110 }
111
111
112 static void preparesockdir(const char *sockdir)
112 static void preparesockdir(const char *sockdir)
113 {
113 {
114 int r;
114 int r;
115 r = mkdir(sockdir, 0700);
115 r = mkdir(sockdir, 0700);
116 if (r < 0 && errno != EEXIST)
116 if (r < 0 && errno != EEXIST)
117 abortmsgerrno("cannot create sockdir %s", sockdir);
117 abortmsgerrno("cannot create sockdir %s", sockdir);
118
118
119 struct stat st;
119 struct stat st;
120 r = lstat(sockdir, &st);
120 r = lstat(sockdir, &st);
121 if (r < 0)
121 if (r < 0)
122 abortmsgerrno("cannot stat %s", sockdir);
122 abortmsgerrno("cannot stat %s", sockdir);
123 if (!S_ISDIR(st.st_mode))
123 if (!S_ISDIR(st.st_mode))
124 abortmsg("cannot create sockdir %s (file exists)", sockdir);
124 abortmsg("cannot create sockdir %s (file exists)", sockdir);
125 if (st.st_uid != geteuid() || st.st_mode & 0077)
125 if (st.st_uid != geteuid() || st.st_mode & 0077)
126 abortmsg("insecure sockdir %s", sockdir);
126 abortmsg("insecure sockdir %s", sockdir);
127 }
127 }
128
128
129 /*
129 /*
130 * Check if a socket directory exists and is only owned by the current user.
130 * Check if a socket directory exists and is only owned by the current user.
131 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
131 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
132 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
132 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
133 * ignored if the directory is not owned by the user with mode 0700.
133 * ignored if the directory is not owned by the user with mode 0700.
134 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
134 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
135 */
135 */
136 static int checkruntimedir(const char *sockdir)
136 static int checkruntimedir(const char *sockdir)
137 {
137 {
138 struct stat st;
138 struct stat st;
139 int r = lstat(sockdir, &st);
139 int r = lstat(sockdir, &st);
140 if (r < 0) /* ex. does not exist */
140 if (r < 0) /* ex. does not exist */
141 return 0;
141 return 0;
142 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
142 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
143 return 0;
143 return 0;
144 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
144 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
145 }
145 }
146
146
147 static void getdefaultsockdir(char sockdir[], size_t size)
147 static void getdefaultsockdir(char sockdir[], size_t size)
148 {
148 {
149 /* by default, put socket file in secure directory
149 /* by default, put socket file in secure directory
150 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
150 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
151 * (permission of socket file may be ignored on some Unices) */
151 * (permission of socket file may be ignored on some Unices) */
152 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
152 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
153 int r;
153 int r;
154 if (runtimedir && checkruntimedir(runtimedir)) {
154 if (runtimedir && checkruntimedir(runtimedir)) {
155 r = snprintf(sockdir, size, "%s/chg", runtimedir);
155 r = snprintf(sockdir, size, "%s/chg", runtimedir);
156 } else {
156 } else {
157 const char *tmpdir = getenv("TMPDIR");
157 const char *tmpdir = getenv("TMPDIR");
158 if (!tmpdir)
158 if (!tmpdir)
159 tmpdir = "/tmp";
159 tmpdir = "/tmp";
160 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
160 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
161 }
161 }
162 if (r < 0 || (size_t)r >= size)
162 if (r < 0 || (size_t)r >= size)
163 abortmsg("too long TMPDIR (r = %d)", r);
163 abortmsg("too long TMPDIR (r = %d)", r);
164 }
164 }
165
165
166 static void setcmdserveropts(struct cmdserveropts *opts)
166 static void setcmdserveropts(struct cmdserveropts *opts)
167 {
167 {
168 int r;
168 int r;
169 char sockdir[PATH_MAX];
169 char sockdir[PATH_MAX];
170 const char *envsockname = getenv("CHGSOCKNAME");
170 const char *envsockname = getenv("CHGSOCKNAME");
171 if (!envsockname) {
171 if (!envsockname) {
172 getdefaultsockdir(sockdir, sizeof(sockdir));
172 getdefaultsockdir(sockdir, sizeof(sockdir));
173 preparesockdir(sockdir);
173 preparesockdir(sockdir);
174 }
174 }
175
175
176 const char *basename = (envsockname) ? envsockname : sockdir;
176 const char *basename = (envsockname) ? envsockname : sockdir;
177 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
177 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
178 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
178 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
179 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
179 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
180 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
180 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
181 r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u",
181 r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u",
182 opts->sockname, (unsigned)getpid());
182 opts->sockname, (unsigned)getpid());
183 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
183 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
184 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
184 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
185 }
185 }
186
186
187 /* If the current program is, say, /a/b/c/chg, returns /a/b/c/hg. */
188 static char *getrelhgcmd(void)
189 {
190 ssize_t n;
191 char *res, *slash;
192 int maxsize = 4096;
193 res = malloc(maxsize);
194 if (res == NULL)
195 goto cleanup;
196 n = readlink("/proc/self/exe", res, maxsize);
197 if (n < 0 || n >= maxsize)
198 goto cleanup;
199 res[n] = '\0';
200 slash = strrchr(res, '/');
201 if (slash == NULL)
202 goto cleanup;
203 /* 4 is strlen("/hg") + nul byte */
204 if (slash + 4 >= res + maxsize)
205 goto cleanup;
206 memcpy(slash, "/hg", 4);
207 return res;
208 cleanup:
209 free(res);
210 return NULL;
211 }
212
187 static const char *gethgcmd(void)
213 static const char *gethgcmd(void)
188 {
214 {
189 static const char *hgcmd = NULL;
215 static const char *hgcmd = NULL;
216 #ifdef HGPATHREL
217 int tryrelhgcmd = 1;
218 #else
219 int tryrelhgcmd = 0;
220 #endif
190 if (!hgcmd) {
221 if (!hgcmd) {
191 hgcmd = getenv("CHGHG");
222 hgcmd = getenv("CHGHG");
192 if (!hgcmd || hgcmd[0] == '\0')
223 if (!hgcmd || hgcmd[0] == '\0')
193 hgcmd = getenv("HG");
224 hgcmd = getenv("HG");
225 if (tryrelhgcmd && (!hgcmd || hgcmd[0] == '\0'))
226 hgcmd = getrelhgcmd();
194 if (!hgcmd || hgcmd[0] == '\0')
227 if (!hgcmd || hgcmd[0] == '\0')
195 #ifdef HGPATH
228 #ifdef HGPATH
196 hgcmd = (HGPATH);
229 hgcmd = (HGPATH);
197 #else
230 #else
198 hgcmd = "hg";
231 hgcmd = "hg";
199 #endif
232 #endif
200 }
233 }
201 return hgcmd;
234 return hgcmd;
202 }
235 }
203
236
204 static void execcmdserver(const struct cmdserveropts *opts)
237 static void execcmdserver(const struct cmdserveropts *opts)
205 {
238 {
206 const char *hgcmd = gethgcmd();
239 const char *hgcmd = gethgcmd();
207
240
208 const char *baseargv[] = {
241 const char *baseargv[] = {
209 hgcmd,
242 hgcmd,
210 "serve",
243 "serve",
211 "--cmdserver",
244 "--cmdserver",
212 "chgunix",
245 "chgunix",
213 "--address",
246 "--address",
214 opts->initsockname,
247 opts->initsockname,
215 "--daemon-postexec",
248 "--daemon-postexec",
216 "chdir:/",
249 "chdir:/",
217 };
250 };
218 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
251 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
219 size_t argsize = baseargvsize + opts->argsize + 1;
252 size_t argsize = baseargvsize + opts->argsize + 1;
220
253
221 const char **argv = mallocx(sizeof(char *) * argsize);
254 const char **argv = mallocx(sizeof(char *) * argsize);
222 memcpy(argv, baseargv, sizeof(baseargv));
255 memcpy(argv, baseargv, sizeof(baseargv));
223 if (opts->args) {
256 if (opts->args) {
224 size_t size = sizeof(char *) * opts->argsize;
257 size_t size = sizeof(char *) * opts->argsize;
225 memcpy(argv + baseargvsize, opts->args, size);
258 memcpy(argv + baseargvsize, opts->args, size);
226 }
259 }
227 argv[argsize - 1] = NULL;
260 argv[argsize - 1] = NULL;
228
261
229 const char *lc_ctype_env = getenv("LC_CTYPE");
262 const char *lc_ctype_env = getenv("LC_CTYPE");
230 if (lc_ctype_env == NULL) {
263 if (lc_ctype_env == NULL) {
231 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
264 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
232 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
265 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
233 } else {
266 } else {
234 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
267 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
235 abortmsgerrno("failed to setenv CHGORIG_LC_CTYPE");
268 abortmsgerrno("failed to setenv CHGORIG_LC_CTYPE");
236 }
269 }
237 }
270 }
238
271
239 if (putenv("CHGINTERNALMARK=") != 0)
272 if (putenv("CHGINTERNALMARK=") != 0)
240 abortmsgerrno("failed to putenv");
273 abortmsgerrno("failed to putenv");
241 if (execvp(hgcmd, (char **)argv) < 0)
274 if (execvp(hgcmd, (char **)argv) < 0)
242 abortmsgerrno("failed to exec cmdserver");
275 abortmsgerrno("failed to exec cmdserver");
243 free(argv);
276 free(argv);
244 }
277 }
245
278
246 /* Retry until we can connect to the server. Give up after some time. */
279 /* Retry until we can connect to the server. Give up after some time. */
247 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
280 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
248 {
281 {
249 static const struct timespec sleepreq = {0, 10 * 1000000};
282 static const struct timespec sleepreq = {0, 10 * 1000000};
250 int pst = 0;
283 int pst = 0;
251
284
252 debugmsg("try connect to %s repeatedly", opts->initsockname);
285 debugmsg("try connect to %s repeatedly", opts->initsockname);
253
286
254 unsigned int timeoutsec = 60; /* default: 60 seconds */
287 unsigned int timeoutsec = 60; /* default: 60 seconds */
255 const char *timeoutenv = getenv("CHGTIMEOUT");
288 const char *timeoutenv = getenv("CHGTIMEOUT");
256 if (timeoutenv)
289 if (timeoutenv)
257 sscanf(timeoutenv, "%u", &timeoutsec);
290 sscanf(timeoutenv, "%u", &timeoutsec);
258
291
259 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
292 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
260 hgclient_t *hgc = hgc_open(opts->initsockname);
293 hgclient_t *hgc = hgc_open(opts->initsockname);
261 if (hgc) {
294 if (hgc) {
262 debugmsg("rename %s to %s", opts->initsockname,
295 debugmsg("rename %s to %s", opts->initsockname,
263 opts->sockname);
296 opts->sockname);
264 int r = rename(opts->initsockname, opts->sockname);
297 int r = rename(opts->initsockname, opts->sockname);
265 if (r != 0)
298 if (r != 0)
266 abortmsgerrno("cannot rename");
299 abortmsgerrno("cannot rename");
267 return hgc;
300 return hgc;
268 }
301 }
269
302
270 if (pid > 0) {
303 if (pid > 0) {
271 /* collect zombie if child process fails to start */
304 /* collect zombie if child process fails to start */
272 int r = waitpid(pid, &pst, WNOHANG);
305 int r = waitpid(pid, &pst, WNOHANG);
273 if (r != 0)
306 if (r != 0)
274 goto cleanup;
307 goto cleanup;
275 }
308 }
276
309
277 nanosleep(&sleepreq, NULL);
310 nanosleep(&sleepreq, NULL);
278 }
311 }
279
312
280 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
313 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
281 return NULL;
314 return NULL;
282
315
283 cleanup:
316 cleanup:
284 if (WIFEXITED(pst)) {
317 if (WIFEXITED(pst)) {
285 if (WEXITSTATUS(pst) == 0)
318 if (WEXITSTATUS(pst) == 0)
286 abortmsg("could not connect to cmdserver "
319 abortmsg("could not connect to cmdserver "
287 "(exited with status 0)");
320 "(exited with status 0)");
288 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
321 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
289 exit(WEXITSTATUS(pst));
322 exit(WEXITSTATUS(pst));
290 } else if (WIFSIGNALED(pst)) {
323 } else if (WIFSIGNALED(pst)) {
291 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
324 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
292 } else {
325 } else {
293 abortmsg("error while waiting for cmdserver");
326 abortmsg("error while waiting for cmdserver");
294 }
327 }
295 return NULL;
328 return NULL;
296 }
329 }
297
330
298 /* Connect to a cmdserver. Will start a new server on demand. */
331 /* Connect to a cmdserver. Will start a new server on demand. */
299 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
332 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
300 {
333 {
301 const char *sockname =
334 const char *sockname =
302 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
335 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
303 debugmsg("try connect to %s", sockname);
336 debugmsg("try connect to %s", sockname);
304 hgclient_t *hgc = hgc_open(sockname);
337 hgclient_t *hgc = hgc_open(sockname);
305 if (hgc)
338 if (hgc)
306 return hgc;
339 return hgc;
307
340
308 /* prevent us from being connected to an outdated server: we were
341 /* prevent us from being connected to an outdated server: we were
309 * told by a server to redirect to opts->redirectsockname and that
342 * told by a server to redirect to opts->redirectsockname and that
310 * address does not work. we do not want to connect to the server
343 * address does not work. we do not want to connect to the server
311 * again because it will probably tell us the same thing. */
344 * again because it will probably tell us the same thing. */
312 if (sockname == opts->redirectsockname)
345 if (sockname == opts->redirectsockname)
313 unlink(opts->sockname);
346 unlink(opts->sockname);
314
347
315 debugmsg("start cmdserver at %s", opts->initsockname);
348 debugmsg("start cmdserver at %s", opts->initsockname);
316
349
317 pid_t pid = fork();
350 pid_t pid = fork();
318 if (pid < 0)
351 if (pid < 0)
319 abortmsg("failed to fork cmdserver process");
352 abortmsg("failed to fork cmdserver process");
320 if (pid == 0) {
353 if (pid == 0) {
321 execcmdserver(opts);
354 execcmdserver(opts);
322 } else {
355 } else {
323 hgc = retryconnectcmdserver(opts, pid);
356 hgc = retryconnectcmdserver(opts, pid);
324 }
357 }
325
358
326 return hgc;
359 return hgc;
327 }
360 }
328
361
329 static void killcmdserver(const struct cmdserveropts *opts)
362 static void killcmdserver(const struct cmdserveropts *opts)
330 {
363 {
331 /* resolve config hash */
364 /* resolve config hash */
332 char *resolvedpath = realpath(opts->sockname, NULL);
365 char *resolvedpath = realpath(opts->sockname, NULL);
333 if (resolvedpath) {
366 if (resolvedpath) {
334 unlink(resolvedpath);
367 unlink(resolvedpath);
335 free(resolvedpath);
368 free(resolvedpath);
336 }
369 }
337 }
370 }
338
371
339 /* Run instructions sent from the server like unlink and set redirect path
372 /* Run instructions sent from the server like unlink and set redirect path
340 * Return 1 if reconnect is needed, otherwise 0 */
373 * Return 1 if reconnect is needed, otherwise 0 */
341 static int runinstructions(struct cmdserveropts *opts, const char **insts)
374 static int runinstructions(struct cmdserveropts *opts, const char **insts)
342 {
375 {
343 int needreconnect = 0;
376 int needreconnect = 0;
344 if (!insts)
377 if (!insts)
345 return needreconnect;
378 return needreconnect;
346
379
347 assert(insts);
380 assert(insts);
348 opts->redirectsockname[0] = '\0';
381 opts->redirectsockname[0] = '\0';
349 const char **pinst;
382 const char **pinst;
350 for (pinst = insts; *pinst; pinst++) {
383 for (pinst = insts; *pinst; pinst++) {
351 debugmsg("instruction: %s", *pinst);
384 debugmsg("instruction: %s", *pinst);
352 if (strncmp(*pinst, "unlink ", 7) == 0) {
385 if (strncmp(*pinst, "unlink ", 7) == 0) {
353 unlink(*pinst + 7);
386 unlink(*pinst + 7);
354 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
387 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
355 int r = snprintf(opts->redirectsockname,
388 int r = snprintf(opts->redirectsockname,
356 sizeof(opts->redirectsockname), "%s",
389 sizeof(opts->redirectsockname), "%s",
357 *pinst + 9);
390 *pinst + 9);
358 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
391 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
359 abortmsg("redirect path is too long (%d)", r);
392 abortmsg("redirect path is too long (%d)", r);
360 needreconnect = 1;
393 needreconnect = 1;
361 } else if (strncmp(*pinst, "exit ", 5) == 0) {
394 } else if (strncmp(*pinst, "exit ", 5) == 0) {
362 int n = 0;
395 int n = 0;
363 if (sscanf(*pinst + 5, "%d", &n) != 1)
396 if (sscanf(*pinst + 5, "%d", &n) != 1)
364 abortmsg("cannot read the exit code");
397 abortmsg("cannot read the exit code");
365 exit(n);
398 exit(n);
366 } else if (strcmp(*pinst, "reconnect") == 0) {
399 } else if (strcmp(*pinst, "reconnect") == 0) {
367 needreconnect = 1;
400 needreconnect = 1;
368 } else {
401 } else {
369 abortmsg("unknown instruction: %s", *pinst);
402 abortmsg("unknown instruction: %s", *pinst);
370 }
403 }
371 }
404 }
372 return needreconnect;
405 return needreconnect;
373 }
406 }
374
407
375 /*
408 /*
376 * Test whether the command and the environment is unsupported or not.
409 * Test whether the command and the environment is unsupported or not.
377 *
410 *
378 * If any of the stdio file descriptors are not present (rare, but some tools
411 * If any of the stdio file descriptors are not present (rare, but some tools
379 * might spawn new processes without stdio instead of redirecting them to the
412 * might spawn new processes without stdio instead of redirecting them to the
380 * null device), then mark it as not supported because attachio won't work
413 * null device), then mark it as not supported because attachio won't work
381 * correctly.
414 * correctly.
382 *
415 *
383 * The command list is not designed to cover all cases. But it's fast, and does
416 * The command list is not designed to cover all cases. But it's fast, and does
384 * not depend on the server.
417 * not depend on the server.
385 */
418 */
386 static int isunsupported(int argc, const char *argv[])
419 static int isunsupported(int argc, const char *argv[])
387 {
420 {
388 enum { SERVE = 1,
421 enum { SERVE = 1,
389 DAEMON = 2,
422 DAEMON = 2,
390 SERVEDAEMON = SERVE | DAEMON,
423 SERVEDAEMON = SERVE | DAEMON,
391 };
424 };
392 unsigned int state = 0;
425 unsigned int state = 0;
393 int i;
426 int i;
394 /* use fcntl to test missing stdio fds */
427 /* use fcntl to test missing stdio fds */
395 if (fcntl(STDIN_FILENO, F_GETFD) == -1 ||
428 if (fcntl(STDIN_FILENO, F_GETFD) == -1 ||
396 fcntl(STDOUT_FILENO, F_GETFD) == -1 ||
429 fcntl(STDOUT_FILENO, F_GETFD) == -1 ||
397 fcntl(STDERR_FILENO, F_GETFD) == -1) {
430 fcntl(STDERR_FILENO, F_GETFD) == -1) {
398 debugmsg("stdio fds are missing");
431 debugmsg("stdio fds are missing");
399 return 1;
432 return 1;
400 }
433 }
401 for (i = 0; i < argc; ++i) {
434 for (i = 0; i < argc; ++i) {
402 if (strcmp(argv[i], "--") == 0)
435 if (strcmp(argv[i], "--") == 0)
403 break;
436 break;
404 /*
437 /*
405 * there can be false positives but no false negative
438 * there can be false positives but no false negative
406 * we cannot assume `serve` will always be first argument
439 * we cannot assume `serve` will always be first argument
407 * because global options can be passed before the command name
440 * because global options can be passed before the command name
408 */
441 */
409 if (strcmp("serve", argv[i]) == 0)
442 if (strcmp("serve", argv[i]) == 0)
410 state |= SERVE;
443 state |= SERVE;
411 else if (strcmp("-d", argv[i]) == 0 ||
444 else if (strcmp("-d", argv[i]) == 0 ||
412 strcmp("--daemon", argv[i]) == 0)
445 strcmp("--daemon", argv[i]) == 0)
413 state |= DAEMON;
446 state |= DAEMON;
414 }
447 }
415 return (state & SERVEDAEMON) == SERVEDAEMON;
448 return (state & SERVEDAEMON) == SERVEDAEMON;
416 }
449 }
417
450
418 static void execoriginalhg(const char *argv[])
451 static void execoriginalhg(const char *argv[])
419 {
452 {
420 debugmsg("execute original hg");
453 debugmsg("execute original hg");
421 if (execvp(gethgcmd(), (char **)argv) < 0)
454 if (execvp(gethgcmd(), (char **)argv) < 0)
422 abortmsgerrno("failed to exec original hg");
455 abortmsgerrno("failed to exec original hg");
423 }
456 }
424
457
425 int main(int argc, const char *argv[], const char *envp[])
458 int main(int argc, const char *argv[], const char *envp[])
426 {
459 {
427 if (getenv("CHGDEBUG"))
460 if (getenv("CHGDEBUG"))
428 enabledebugmsg();
461 enabledebugmsg();
429
462
430 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
463 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
431 enablecolor();
464 enablecolor();
432
465
433 if (getenv("CHGINTERNALMARK"))
466 if (getenv("CHGINTERNALMARK"))
434 abortmsg("chg started by chg detected.\n"
467 abortmsg("chg started by chg detected.\n"
435 "Please make sure ${HG:-hg} is not a symlink or "
468 "Please make sure ${HG:-hg} is not a symlink or "
436 "wrapper to chg. Alternatively, set $CHGHG to the "
469 "wrapper to chg. Alternatively, set $CHGHG to the "
437 "path of real hg.");
470 "path of real hg.");
438
471
439 if (isunsupported(argc - 1, argv + 1))
472 if (isunsupported(argc - 1, argv + 1))
440 execoriginalhg(argv);
473 execoriginalhg(argv);
441
474
442 struct cmdserveropts opts;
475 struct cmdserveropts opts;
443 initcmdserveropts(&opts);
476 initcmdserveropts(&opts);
444 setcmdserveropts(&opts);
477 setcmdserveropts(&opts);
445 setcmdserverargs(&opts, argc, argv);
478 setcmdserverargs(&opts, argc, argv);
446
479
447 if (argc == 2) {
480 if (argc == 2) {
448 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
481 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
449 killcmdserver(&opts);
482 killcmdserver(&opts);
450 return 0;
483 return 0;
451 }
484 }
452 }
485 }
453
486
454 hgclient_t *hgc;
487 hgclient_t *hgc;
455 size_t retry = 0;
488 size_t retry = 0;
456 while (1) {
489 while (1) {
457 hgc = connectcmdserver(&opts);
490 hgc = connectcmdserver(&opts);
458 if (!hgc)
491 if (!hgc)
459 abortmsg("cannot open hg client");
492 abortmsg("cannot open hg client");
460 hgc_setenv(hgc, envp);
493 hgc_setenv(hgc, envp);
461 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
494 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
462 int needreconnect = runinstructions(&opts, insts);
495 int needreconnect = runinstructions(&opts, insts);
463 free(insts);
496 free(insts);
464 if (!needreconnect)
497 if (!needreconnect)
465 break;
498 break;
466 hgc_close(hgc);
499 hgc_close(hgc);
467 if (++retry > 10)
500 if (++retry > 10)
468 abortmsg("too many redirections.\n"
501 abortmsg("too many redirections.\n"
469 "Please make sure %s is not a wrapper which "
502 "Please make sure %s is not a wrapper which "
470 "changes sensitive environment variables "
503 "changes sensitive environment variables "
471 "before executing hg. If you have to use a "
504 "before executing hg. If you have to use a "
472 "wrapper, wrap chg instead of hg.",
505 "wrapper, wrap chg instead of hg.",
473 gethgcmd());
506 gethgcmd());
474 }
507 }
475
508
476 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
509 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
477 atexit(waitpager);
510 atexit(waitpager);
478 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
511 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
479 restoresignalhandler();
512 restoresignalhandler();
480 hgc_close(hgc);
513 hgc_close(hgc);
481 freecmdserveropts(&opts);
514 freecmdserveropts(&opts);
482
515
483 return exitcode;
516 return exitcode;
484 }
517 }
General Comments 0
You need to be logged in to leave comments. Login now