##// END OF EJS Templates
chg: make timeout adjustable...
Jun Wu -
r29345:62b89049 default
parent child Browse files
Show More
@@ -1,30 +1,32 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
32 connecting to a cmdserver. If it is 0, chg will wait forever. Default: 10
@@ -1,611 +1,617 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 "util.h"
26 #include "util.h"
27
27
28 #ifndef UNIX_PATH_MAX
28 #ifndef UNIX_PATH_MAX
29 #define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)NULL)->sun_path))
29 #define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)NULL)->sun_path))
30 #endif
30 #endif
31
31
32 struct cmdserveropts {
32 struct cmdserveropts {
33 char sockname[UNIX_PATH_MAX];
33 char sockname[UNIX_PATH_MAX];
34 char redirectsockname[UNIX_PATH_MAX];
34 char redirectsockname[UNIX_PATH_MAX];
35 char lockfile[UNIX_PATH_MAX];
35 char lockfile[UNIX_PATH_MAX];
36 size_t argsize;
36 size_t argsize;
37 const char **args;
37 const char **args;
38 int lockfd;
38 int lockfd;
39 int sockdirfd;
39 int sockdirfd;
40 };
40 };
41
41
42 static void initcmdserveropts(struct cmdserveropts *opts) {
42 static void initcmdserveropts(struct cmdserveropts *opts) {
43 memset(opts, 0, sizeof(struct cmdserveropts));
43 memset(opts, 0, sizeof(struct cmdserveropts));
44 opts->lockfd = -1;
44 opts->lockfd = -1;
45 opts->sockdirfd = -1;
45 opts->sockdirfd = -1;
46 }
46 }
47
47
48 static void freecmdserveropts(struct cmdserveropts *opts) {
48 static void freecmdserveropts(struct cmdserveropts *opts) {
49 free(opts->args);
49 free(opts->args);
50 opts->args = NULL;
50 opts->args = NULL;
51 opts->argsize = 0;
51 opts->argsize = 0;
52 assert(opts->lockfd == -1 && "should be closed by unlockcmdserver()");
52 assert(opts->lockfd == -1 && "should be closed by unlockcmdserver()");
53 if (opts->sockdirfd >= 0) {
53 if (opts->sockdirfd >= 0) {
54 close(opts->sockdirfd);
54 close(opts->sockdirfd);
55 opts->sockdirfd = -1;
55 opts->sockdirfd = -1;
56 }
56 }
57 }
57 }
58
58
59 /*
59 /*
60 * Test if an argument is a sensitive flag that should be passed to the server.
60 * Test if an argument is a sensitive flag that should be passed to the server.
61 * Return 0 if not, otherwise the number of arguments starting from the current
61 * Return 0 if not, otherwise the number of arguments starting from the current
62 * one that should be passed to the server.
62 * one that should be passed to the server.
63 */
63 */
64 static size_t testsensitiveflag(const char *arg)
64 static size_t testsensitiveflag(const char *arg)
65 {
65 {
66 static const struct {
66 static const struct {
67 const char *name;
67 const char *name;
68 size_t narg;
68 size_t narg;
69 } flags[] = {
69 } flags[] = {
70 {"--config", 1},
70 {"--config", 1},
71 {"--cwd", 1},
71 {"--cwd", 1},
72 {"--repo", 1},
72 {"--repo", 1},
73 {"--repository", 1},
73 {"--repository", 1},
74 {"--traceback", 0},
74 {"--traceback", 0},
75 {"-R", 1},
75 {"-R", 1},
76 };
76 };
77 size_t i;
77 size_t i;
78 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
78 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
79 size_t len = strlen(flags[i].name);
79 size_t len = strlen(flags[i].name);
80 size_t narg = flags[i].narg;
80 size_t narg = flags[i].narg;
81 if (memcmp(arg, flags[i].name, len) == 0) {
81 if (memcmp(arg, flags[i].name, len) == 0) {
82 if (arg[len] == '\0') {
82 if (arg[len] == '\0') {
83 /* --flag (value) */
83 /* --flag (value) */
84 return narg + 1;
84 return narg + 1;
85 } else if (arg[len] == '=' && narg > 0) {
85 } else if (arg[len] == '=' && narg > 0) {
86 /* --flag=value */
86 /* --flag=value */
87 return 1;
87 return 1;
88 } else if (flags[i].name[1] != '-') {
88 } else if (flags[i].name[1] != '-') {
89 /* short flag */
89 /* short flag */
90 return 1;
90 return 1;
91 }
91 }
92 }
92 }
93 }
93 }
94 return 0;
94 return 0;
95 }
95 }
96
96
97 /*
97 /*
98 * Parse argv[] and put sensitive flags to opts->args
98 * Parse argv[] and put sensitive flags to opts->args
99 */
99 */
100 static void setcmdserverargs(struct cmdserveropts *opts,
100 static void setcmdserverargs(struct cmdserveropts *opts,
101 int argc, const char *argv[])
101 int argc, const char *argv[])
102 {
102 {
103 size_t i, step;
103 size_t i, step;
104 opts->argsize = 0;
104 opts->argsize = 0;
105 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
105 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
106 if (!argv[i])
106 if (!argv[i])
107 continue; /* pass clang-analyse */
107 continue; /* pass clang-analyse */
108 if (strcmp(argv[i], "--") == 0)
108 if (strcmp(argv[i], "--") == 0)
109 break;
109 break;
110 size_t n = testsensitiveflag(argv[i]);
110 size_t n = testsensitiveflag(argv[i]);
111 if (n == 0 || i + n > (size_t)argc)
111 if (n == 0 || i + n > (size_t)argc)
112 continue;
112 continue;
113 opts->args = reallocx(opts->args,
113 opts->args = reallocx(opts->args,
114 (n + opts->argsize) * sizeof(char *));
114 (n + opts->argsize) * sizeof(char *));
115 memcpy(opts->args + opts->argsize, argv + i,
115 memcpy(opts->args + opts->argsize, argv + i,
116 sizeof(char *) * n);
116 sizeof(char *) * n);
117 opts->argsize += n;
117 opts->argsize += n;
118 step = n;
118 step = n;
119 }
119 }
120 }
120 }
121
121
122 static void preparesockdir(const char *sockdir)
122 static void preparesockdir(const char *sockdir)
123 {
123 {
124 int r;
124 int r;
125 r = mkdir(sockdir, 0700);
125 r = mkdir(sockdir, 0700);
126 if (r < 0 && errno != EEXIST)
126 if (r < 0 && errno != EEXIST)
127 abortmsgerrno("cannot create sockdir %s", sockdir);
127 abortmsgerrno("cannot create sockdir %s", sockdir);
128
128
129 struct stat st;
129 struct stat st;
130 r = lstat(sockdir, &st);
130 r = lstat(sockdir, &st);
131 if (r < 0)
131 if (r < 0)
132 abortmsgerrno("cannot stat %s", sockdir);
132 abortmsgerrno("cannot stat %s", sockdir);
133 if (!S_ISDIR(st.st_mode))
133 if (!S_ISDIR(st.st_mode))
134 abortmsg("cannot create sockdir %s (file exists)", sockdir);
134 abortmsg("cannot create sockdir %s (file exists)", sockdir);
135 if (st.st_uid != geteuid() || st.st_mode & 0077)
135 if (st.st_uid != geteuid() || st.st_mode & 0077)
136 abortmsg("insecure sockdir %s", sockdir);
136 abortmsg("insecure sockdir %s", sockdir);
137 }
137 }
138
138
139 static void setcmdserveropts(struct cmdserveropts *opts)
139 static void setcmdserveropts(struct cmdserveropts *opts)
140 {
140 {
141 int r;
141 int r;
142 char sockdir[UNIX_PATH_MAX];
142 char sockdir[UNIX_PATH_MAX];
143 const char *envsockname = getenv("CHGSOCKNAME");
143 const char *envsockname = getenv("CHGSOCKNAME");
144 if (!envsockname) {
144 if (!envsockname) {
145 /* by default, put socket file in secure directory
145 /* by default, put socket file in secure directory
146 * (permission of socket file may be ignored on some Unices) */
146 * (permission of socket file may be ignored on some Unices) */
147 const char *tmpdir = getenv("TMPDIR");
147 const char *tmpdir = getenv("TMPDIR");
148 if (!tmpdir)
148 if (!tmpdir)
149 tmpdir = "/tmp";
149 tmpdir = "/tmp";
150 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d",
150 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d",
151 tmpdir, geteuid());
151 tmpdir, geteuid());
152 if (r < 0 || (size_t)r >= sizeof(sockdir))
152 if (r < 0 || (size_t)r >= sizeof(sockdir))
153 abortmsg("too long TMPDIR (r = %d)", r);
153 abortmsg("too long TMPDIR (r = %d)", r);
154 preparesockdir(sockdir);
154 preparesockdir(sockdir);
155 }
155 }
156
156
157 const char *basename = (envsockname) ? envsockname : sockdir;
157 const char *basename = (envsockname) ? envsockname : sockdir;
158 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
158 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
159 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
159 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
160 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
160 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
161 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
161 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
162 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
162 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
163 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
163 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
164 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
164 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
165 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
165 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
166 }
166 }
167
167
168 /*
168 /*
169 * Acquire a file lock that indicates a client is trying to start and connect
169 * Acquire a file lock that indicates a client is trying to start and connect
170 * to a server, before executing a command. The lock is released upon exit or
170 * to a server, before executing a command. The lock is released upon exit or
171 * explicit unlock. Will block if the lock is held by another process.
171 * explicit unlock. Will block if the lock is held by another process.
172 */
172 */
173 static void lockcmdserver(struct cmdserveropts *opts)
173 static void lockcmdserver(struct cmdserveropts *opts)
174 {
174 {
175 if (opts->lockfd == -1) {
175 if (opts->lockfd == -1) {
176 opts->lockfd = open(opts->lockfile,
176 opts->lockfd = open(opts->lockfile,
177 O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
177 O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
178 if (opts->lockfd == -1)
178 if (opts->lockfd == -1)
179 abortmsgerrno("cannot create lock file %s",
179 abortmsgerrno("cannot create lock file %s",
180 opts->lockfile);
180 opts->lockfile);
181 fsetcloexec(opts->lockfd);
181 fsetcloexec(opts->lockfd);
182 }
182 }
183 int r = flock(opts->lockfd, LOCK_EX);
183 int r = flock(opts->lockfd, LOCK_EX);
184 if (r == -1)
184 if (r == -1)
185 abortmsgerrno("cannot acquire lock");
185 abortmsgerrno("cannot acquire lock");
186 }
186 }
187
187
188 /*
188 /*
189 * Release the file lock held by calling lockcmdserver. Will do nothing if
189 * Release the file lock held by calling lockcmdserver. Will do nothing if
190 * lockcmdserver is not called.
190 * lockcmdserver is not called.
191 */
191 */
192 static void unlockcmdserver(struct cmdserveropts *opts)
192 static void unlockcmdserver(struct cmdserveropts *opts)
193 {
193 {
194 if (opts->lockfd == -1)
194 if (opts->lockfd == -1)
195 return;
195 return;
196 flock(opts->lockfd, LOCK_UN);
196 flock(opts->lockfd, LOCK_UN);
197 close(opts->lockfd);
197 close(opts->lockfd);
198 opts->lockfd = -1;
198 opts->lockfd = -1;
199 }
199 }
200
200
201 static const char *gethgcmd(void)
201 static const char *gethgcmd(void)
202 {
202 {
203 static const char *hgcmd = NULL;
203 static const char *hgcmd = NULL;
204 if (!hgcmd) {
204 if (!hgcmd) {
205 hgcmd = getenv("CHGHG");
205 hgcmd = getenv("CHGHG");
206 if (!hgcmd || hgcmd[0] == '\0')
206 if (!hgcmd || hgcmd[0] == '\0')
207 hgcmd = getenv("HG");
207 hgcmd = getenv("HG");
208 if (!hgcmd || hgcmd[0] == '\0')
208 if (!hgcmd || hgcmd[0] == '\0')
209 #ifdef HGPATH
209 #ifdef HGPATH
210 hgcmd = (HGPATH);
210 hgcmd = (HGPATH);
211 #else
211 #else
212 hgcmd = "hg";
212 hgcmd = "hg";
213 #endif
213 #endif
214 }
214 }
215 return hgcmd;
215 return hgcmd;
216 }
216 }
217
217
218 static void execcmdserver(const struct cmdserveropts *opts)
218 static void execcmdserver(const struct cmdserveropts *opts)
219 {
219 {
220 const char *hgcmd = gethgcmd();
220 const char *hgcmd = gethgcmd();
221
221
222 const char *baseargv[] = {
222 const char *baseargv[] = {
223 hgcmd,
223 hgcmd,
224 "serve",
224 "serve",
225 "--cmdserver", "chgunix",
225 "--cmdserver", "chgunix",
226 "--address", opts->sockname,
226 "--address", opts->sockname,
227 "--daemon-postexec", "chdir:/",
227 "--daemon-postexec", "chdir:/",
228 "--config", "extensions.chgserver=",
228 "--config", "extensions.chgserver=",
229 };
229 };
230 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
230 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
231 size_t argsize = baseargvsize + opts->argsize + 1;
231 size_t argsize = baseargvsize + opts->argsize + 1;
232
232
233 const char **argv = mallocx(sizeof(char *) * argsize);
233 const char **argv = mallocx(sizeof(char *) * argsize);
234 memcpy(argv, baseargv, sizeof(baseargv));
234 memcpy(argv, baseargv, sizeof(baseargv));
235 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
235 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
236 argv[argsize - 1] = NULL;
236 argv[argsize - 1] = NULL;
237
237
238 if (putenv("CHGINTERNALMARK=") != 0)
238 if (putenv("CHGINTERNALMARK=") != 0)
239 abortmsgerrno("failed to putenv");
239 abortmsgerrno("failed to putenv");
240 if (execvp(hgcmd, (char **)argv) < 0)
240 if (execvp(hgcmd, (char **)argv) < 0)
241 abortmsgerrno("failed to exec cmdserver");
241 abortmsgerrno("failed to exec cmdserver");
242 free(argv);
242 free(argv);
243 }
243 }
244
244
245 /* Retry until we can connect to the server. Give up after some time. */
245 /* Retry until we can connect to the server. Give up after some time. */
246 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
246 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
247 {
247 {
248 static const struct timespec sleepreq = {0, 10 * 1000000};
248 static const struct timespec sleepreq = {0, 10 * 1000000};
249 int pst = 0;
249 int pst = 0;
250
250
251 debugmsg("try connect to %s repeatedly", opts->sockname);
251 debugmsg("try connect to %s repeatedly", opts->sockname);
252 for (unsigned int i = 0; i < 10 * 100; i++) {
252
253 unsigned int timeoutsec = 10; /* default: 10 seconds */
254 const char *timeoutenv = getenv("CHGTIMEOUT");
255 if (timeoutenv)
256 sscanf(timeoutenv, "%u", &timeoutsec);
257
258 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
253 hgclient_t *hgc = hgc_open(opts->sockname);
259 hgclient_t *hgc = hgc_open(opts->sockname);
254 if (hgc)
260 if (hgc)
255 return hgc;
261 return hgc;
256
262
257 if (pid > 0) {
263 if (pid > 0) {
258 /* collect zombie if child process fails to start */
264 /* collect zombie if child process fails to start */
259 int r = waitpid(pid, &pst, WNOHANG);
265 int r = waitpid(pid, &pst, WNOHANG);
260 if (r != 0)
266 if (r != 0)
261 goto cleanup;
267 goto cleanup;
262 }
268 }
263
269
264 nanosleep(&sleepreq, NULL);
270 nanosleep(&sleepreq, NULL);
265 }
271 }
266
272
267 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
273 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
268 return NULL;
274 return NULL;
269
275
270 cleanup:
276 cleanup:
271 if (WIFEXITED(pst)) {
277 if (WIFEXITED(pst)) {
272 if (WEXITSTATUS(pst) == 0)
278 if (WEXITSTATUS(pst) == 0)
273 abortmsg("could not connect to cmdserver "
279 abortmsg("could not connect to cmdserver "
274 "(exited with status 0)");
280 "(exited with status 0)");
275 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
281 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
276 exit(WEXITSTATUS(pst));
282 exit(WEXITSTATUS(pst));
277 } else if (WIFSIGNALED(pst)) {
283 } else if (WIFSIGNALED(pst)) {
278 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
284 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
279 } else {
285 } else {
280 abortmsg("error while waiting for cmdserver");
286 abortmsg("error while waiting for cmdserver");
281 }
287 }
282 return NULL;
288 return NULL;
283 }
289 }
284
290
285 /* Connect to a cmdserver. Will start a new server on demand. */
291 /* Connect to a cmdserver. Will start a new server on demand. */
286 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
292 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
287 {
293 {
288 const char *sockname = opts->redirectsockname[0] ?
294 const char *sockname = opts->redirectsockname[0] ?
289 opts->redirectsockname : opts->sockname;
295 opts->redirectsockname : opts->sockname;
290 debugmsg("try connect to %s", sockname);
296 debugmsg("try connect to %s", sockname);
291 hgclient_t *hgc = hgc_open(sockname);
297 hgclient_t *hgc = hgc_open(sockname);
292 if (hgc)
298 if (hgc)
293 return hgc;
299 return hgc;
294
300
295 lockcmdserver(opts);
301 lockcmdserver(opts);
296 hgc = hgc_open(sockname);
302 hgc = hgc_open(sockname);
297 if (hgc) {
303 if (hgc) {
298 unlockcmdserver(opts);
304 unlockcmdserver(opts);
299 debugmsg("cmdserver is started by another process");
305 debugmsg("cmdserver is started by another process");
300 return hgc;
306 return hgc;
301 }
307 }
302
308
303 /* prevent us from being connected to an outdated server: we were
309 /* prevent us from being connected to an outdated server: we were
304 * told by a server to redirect to opts->redirectsockname and that
310 * told by a server to redirect to opts->redirectsockname and that
305 * address does not work. we do not want to connect to the server
311 * address does not work. we do not want to connect to the server
306 * again because it will probably tell us the same thing. */
312 * again because it will probably tell us the same thing. */
307 if (sockname == opts->redirectsockname)
313 if (sockname == opts->redirectsockname)
308 unlink(opts->sockname);
314 unlink(opts->sockname);
309
315
310 debugmsg("start cmdserver at %s", opts->sockname);
316 debugmsg("start cmdserver at %s", opts->sockname);
311
317
312 pid_t pid = fork();
318 pid_t pid = fork();
313 if (pid < 0)
319 if (pid < 0)
314 abortmsg("failed to fork cmdserver process");
320 abortmsg("failed to fork cmdserver process");
315 if (pid == 0) {
321 if (pid == 0) {
316 execcmdserver(opts);
322 execcmdserver(opts);
317 } else {
323 } else {
318 hgc = retryconnectcmdserver(opts, pid);
324 hgc = retryconnectcmdserver(opts, pid);
319 }
325 }
320
326
321 unlockcmdserver(opts);
327 unlockcmdserver(opts);
322 return hgc;
328 return hgc;
323 }
329 }
324
330
325 static void killcmdserver(const struct cmdserveropts *opts)
331 static void killcmdserver(const struct cmdserveropts *opts)
326 {
332 {
327 /* resolve config hash */
333 /* resolve config hash */
328 char *resolvedpath = realpath(opts->sockname, NULL);
334 char *resolvedpath = realpath(opts->sockname, NULL);
329 if (resolvedpath) {
335 if (resolvedpath) {
330 unlink(resolvedpath);
336 unlink(resolvedpath);
331 free(resolvedpath);
337 free(resolvedpath);
332 }
338 }
333 }
339 }
334
340
335 static pid_t peerpid = 0;
341 static pid_t peerpid = 0;
336
342
337 static void forwardsignal(int sig)
343 static void forwardsignal(int sig)
338 {
344 {
339 assert(peerpid > 0);
345 assert(peerpid > 0);
340 if (kill(peerpid, sig) < 0)
346 if (kill(peerpid, sig) < 0)
341 abortmsgerrno("cannot kill %d", peerpid);
347 abortmsgerrno("cannot kill %d", peerpid);
342 debugmsg("forward signal %d", sig);
348 debugmsg("forward signal %d", sig);
343 }
349 }
344
350
345 static void handlestopsignal(int sig)
351 static void handlestopsignal(int sig)
346 {
352 {
347 sigset_t unblockset, oldset;
353 sigset_t unblockset, oldset;
348 struct sigaction sa, oldsa;
354 struct sigaction sa, oldsa;
349 if (sigemptyset(&unblockset) < 0)
355 if (sigemptyset(&unblockset) < 0)
350 goto error;
356 goto error;
351 if (sigaddset(&unblockset, sig) < 0)
357 if (sigaddset(&unblockset, sig) < 0)
352 goto error;
358 goto error;
353 memset(&sa, 0, sizeof(sa));
359 memset(&sa, 0, sizeof(sa));
354 sa.sa_handler = SIG_DFL;
360 sa.sa_handler = SIG_DFL;
355 sa.sa_flags = SA_RESTART;
361 sa.sa_flags = SA_RESTART;
356 if (sigemptyset(&sa.sa_mask) < 0)
362 if (sigemptyset(&sa.sa_mask) < 0)
357 goto error;
363 goto error;
358
364
359 forwardsignal(sig);
365 forwardsignal(sig);
360 if (raise(sig) < 0) /* resend to self */
366 if (raise(sig) < 0) /* resend to self */
361 goto error;
367 goto error;
362 if (sigaction(sig, &sa, &oldsa) < 0)
368 if (sigaction(sig, &sa, &oldsa) < 0)
363 goto error;
369 goto error;
364 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
370 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
365 goto error;
371 goto error;
366 /* resent signal will be handled before sigprocmask() returns */
372 /* resent signal will be handled before sigprocmask() returns */
367 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
373 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
368 goto error;
374 goto error;
369 if (sigaction(sig, &oldsa, NULL) < 0)
375 if (sigaction(sig, &oldsa, NULL) < 0)
370 goto error;
376 goto error;
371 return;
377 return;
372
378
373 error:
379 error:
374 abortmsgerrno("failed to handle stop signal");
380 abortmsgerrno("failed to handle stop signal");
375 }
381 }
376
382
377 static void setupsignalhandler(pid_t pid)
383 static void setupsignalhandler(pid_t pid)
378 {
384 {
379 if (pid <= 0)
385 if (pid <= 0)
380 return;
386 return;
381 peerpid = pid;
387 peerpid = pid;
382
388
383 struct sigaction sa;
389 struct sigaction sa;
384 memset(&sa, 0, sizeof(sa));
390 memset(&sa, 0, sizeof(sa));
385 sa.sa_handler = forwardsignal;
391 sa.sa_handler = forwardsignal;
386 sa.sa_flags = SA_RESTART;
392 sa.sa_flags = SA_RESTART;
387 if (sigemptyset(&sa.sa_mask) < 0)
393 if (sigemptyset(&sa.sa_mask) < 0)
388 goto error;
394 goto error;
389
395
390 if (sigaction(SIGHUP, &sa, NULL) < 0)
396 if (sigaction(SIGHUP, &sa, NULL) < 0)
391 goto error;
397 goto error;
392 if (sigaction(SIGINT, &sa, NULL) < 0)
398 if (sigaction(SIGINT, &sa, NULL) < 0)
393 goto error;
399 goto error;
394
400
395 /* terminate frontend by double SIGTERM in case of server freeze */
401 /* terminate frontend by double SIGTERM in case of server freeze */
396 sa.sa_flags |= SA_RESETHAND;
402 sa.sa_flags |= SA_RESETHAND;
397 if (sigaction(SIGTERM, &sa, NULL) < 0)
403 if (sigaction(SIGTERM, &sa, NULL) < 0)
398 goto error;
404 goto error;
399
405
400 /* notify the worker about window resize events */
406 /* notify the worker about window resize events */
401 sa.sa_flags = SA_RESTART;
407 sa.sa_flags = SA_RESTART;
402 if (sigaction(SIGWINCH, &sa, NULL) < 0)
408 if (sigaction(SIGWINCH, &sa, NULL) < 0)
403 goto error;
409 goto error;
404 /* propagate job control requests to worker */
410 /* propagate job control requests to worker */
405 sa.sa_handler = forwardsignal;
411 sa.sa_handler = forwardsignal;
406 sa.sa_flags = SA_RESTART;
412 sa.sa_flags = SA_RESTART;
407 if (sigaction(SIGCONT, &sa, NULL) < 0)
413 if (sigaction(SIGCONT, &sa, NULL) < 0)
408 goto error;
414 goto error;
409 sa.sa_handler = handlestopsignal;
415 sa.sa_handler = handlestopsignal;
410 sa.sa_flags = SA_RESTART;
416 sa.sa_flags = SA_RESTART;
411 if (sigaction(SIGTSTP, &sa, NULL) < 0)
417 if (sigaction(SIGTSTP, &sa, NULL) < 0)
412 goto error;
418 goto error;
413
419
414 return;
420 return;
415
421
416 error:
422 error:
417 abortmsgerrno("failed to set up signal handlers");
423 abortmsgerrno("failed to set up signal handlers");
418 }
424 }
419
425
420 /* This implementation is based on hgext/pager.py (post 369741ef7253)
426 /* This implementation is based on hgext/pager.py (post 369741ef7253)
421 * Return 0 if pager is not started, or pid of the pager */
427 * Return 0 if pager is not started, or pid of the pager */
422 static pid_t setuppager(hgclient_t *hgc, const char *const args[],
428 static pid_t setuppager(hgclient_t *hgc, const char *const args[],
423 size_t argsize)
429 size_t argsize)
424 {
430 {
425 const char *pagercmd = hgc_getpager(hgc, args, argsize);
431 const char *pagercmd = hgc_getpager(hgc, args, argsize);
426 if (!pagercmd)
432 if (!pagercmd)
427 return 0;
433 return 0;
428
434
429 int pipefds[2];
435 int pipefds[2];
430 if (pipe(pipefds) < 0)
436 if (pipe(pipefds) < 0)
431 return 0;
437 return 0;
432 pid_t pid = fork();
438 pid_t pid = fork();
433 if (pid < 0)
439 if (pid < 0)
434 goto error;
440 goto error;
435 if (pid > 0) {
441 if (pid > 0) {
436 close(pipefds[0]);
442 close(pipefds[0]);
437 if (dup2(pipefds[1], fileno(stdout)) < 0)
443 if (dup2(pipefds[1], fileno(stdout)) < 0)
438 goto error;
444 goto error;
439 if (isatty(fileno(stderr))) {
445 if (isatty(fileno(stderr))) {
440 if (dup2(pipefds[1], fileno(stderr)) < 0)
446 if (dup2(pipefds[1], fileno(stderr)) < 0)
441 goto error;
447 goto error;
442 }
448 }
443 close(pipefds[1]);
449 close(pipefds[1]);
444 hgc_attachio(hgc); /* reattach to pager */
450 hgc_attachio(hgc); /* reattach to pager */
445 return pid;
451 return pid;
446 } else {
452 } else {
447 dup2(pipefds[0], fileno(stdin));
453 dup2(pipefds[0], fileno(stdin));
448 close(pipefds[0]);
454 close(pipefds[0]);
449 close(pipefds[1]);
455 close(pipefds[1]);
450
456
451 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
457 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
452 if (r < 0) {
458 if (r < 0) {
453 abortmsgerrno("cannot start pager '%s'", pagercmd);
459 abortmsgerrno("cannot start pager '%s'", pagercmd);
454 }
460 }
455 return 0;
461 return 0;
456 }
462 }
457
463
458 error:
464 error:
459 close(pipefds[0]);
465 close(pipefds[0]);
460 close(pipefds[1]);
466 close(pipefds[1]);
461 abortmsgerrno("failed to prepare pager");
467 abortmsgerrno("failed to prepare pager");
462 return 0;
468 return 0;
463 }
469 }
464
470
465 static void waitpager(pid_t pid)
471 static void waitpager(pid_t pid)
466 {
472 {
467 /* close output streams to notify the pager its input ends */
473 /* close output streams to notify the pager its input ends */
468 fclose(stdout);
474 fclose(stdout);
469 fclose(stderr);
475 fclose(stderr);
470 while (1) {
476 while (1) {
471 pid_t ret = waitpid(pid, NULL, 0);
477 pid_t ret = waitpid(pid, NULL, 0);
472 if (ret == -1 && errno == EINTR)
478 if (ret == -1 && errno == EINTR)
473 continue;
479 continue;
474 break;
480 break;
475 }
481 }
476 }
482 }
477
483
478 /* Run instructions sent from the server like unlink and set redirect path
484 /* Run instructions sent from the server like unlink and set redirect path
479 * Return 1 if reconnect is needed, otherwise 0 */
485 * Return 1 if reconnect is needed, otherwise 0 */
480 static int runinstructions(struct cmdserveropts *opts, const char **insts)
486 static int runinstructions(struct cmdserveropts *opts, const char **insts)
481 {
487 {
482 int needreconnect = 0;
488 int needreconnect = 0;
483 if (!insts)
489 if (!insts)
484 return needreconnect;
490 return needreconnect;
485
491
486 assert(insts);
492 assert(insts);
487 opts->redirectsockname[0] = '\0';
493 opts->redirectsockname[0] = '\0';
488 const char **pinst;
494 const char **pinst;
489 for (pinst = insts; *pinst; pinst++) {
495 for (pinst = insts; *pinst; pinst++) {
490 debugmsg("instruction: %s", *pinst);
496 debugmsg("instruction: %s", *pinst);
491 if (strncmp(*pinst, "unlink ", 7) == 0) {
497 if (strncmp(*pinst, "unlink ", 7) == 0) {
492 unlink(*pinst + 7);
498 unlink(*pinst + 7);
493 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
499 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
494 int r = snprintf(opts->redirectsockname,
500 int r = snprintf(opts->redirectsockname,
495 sizeof(opts->redirectsockname),
501 sizeof(opts->redirectsockname),
496 "%s", *pinst + 9);
502 "%s", *pinst + 9);
497 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
503 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
498 abortmsg("redirect path is too long (%d)", r);
504 abortmsg("redirect path is too long (%d)", r);
499 needreconnect = 1;
505 needreconnect = 1;
500 } else if (strncmp(*pinst, "exit ", 5) == 0) {
506 } else if (strncmp(*pinst, "exit ", 5) == 0) {
501 int n = 0;
507 int n = 0;
502 if (sscanf(*pinst + 5, "%d", &n) != 1)
508 if (sscanf(*pinst + 5, "%d", &n) != 1)
503 abortmsg("cannot read the exit code");
509 abortmsg("cannot read the exit code");
504 exit(n);
510 exit(n);
505 } else if (strcmp(*pinst, "reconnect") == 0) {
511 } else if (strcmp(*pinst, "reconnect") == 0) {
506 needreconnect = 1;
512 needreconnect = 1;
507 } else {
513 } else {
508 abortmsg("unknown instruction: %s", *pinst);
514 abortmsg("unknown instruction: %s", *pinst);
509 }
515 }
510 }
516 }
511 return needreconnect;
517 return needreconnect;
512 }
518 }
513
519
514 /*
520 /*
515 * Test whether the command is unsupported or not. This is not designed to
521 * Test whether the command is unsupported or not. This is not designed to
516 * cover all cases. But it's fast, does not depend on the server and does
522 * cover all cases. But it's fast, does not depend on the server and does
517 * not return false positives.
523 * not return false positives.
518 */
524 */
519 static int isunsupported(int argc, const char *argv[])
525 static int isunsupported(int argc, const char *argv[])
520 {
526 {
521 enum {
527 enum {
522 SERVE = 1,
528 SERVE = 1,
523 DAEMON = 2,
529 DAEMON = 2,
524 SERVEDAEMON = SERVE | DAEMON,
530 SERVEDAEMON = SERVE | DAEMON,
525 TIME = 4,
531 TIME = 4,
526 };
532 };
527 unsigned int state = 0;
533 unsigned int state = 0;
528 int i;
534 int i;
529 for (i = 0; i < argc; ++i) {
535 for (i = 0; i < argc; ++i) {
530 if (strcmp(argv[i], "--") == 0)
536 if (strcmp(argv[i], "--") == 0)
531 break;
537 break;
532 if (i == 0 && strcmp("serve", argv[i]) == 0)
538 if (i == 0 && strcmp("serve", argv[i]) == 0)
533 state |= SERVE;
539 state |= SERVE;
534 else if (strcmp("-d", argv[i]) == 0 ||
540 else if (strcmp("-d", argv[i]) == 0 ||
535 strcmp("--daemon", argv[i]) == 0)
541 strcmp("--daemon", argv[i]) == 0)
536 state |= DAEMON;
542 state |= DAEMON;
537 else if (strcmp("--time", argv[i]) == 0)
543 else if (strcmp("--time", argv[i]) == 0)
538 state |= TIME;
544 state |= TIME;
539 }
545 }
540 return (state & TIME) == TIME ||
546 return (state & TIME) == TIME ||
541 (state & SERVEDAEMON) == SERVEDAEMON;
547 (state & SERVEDAEMON) == SERVEDAEMON;
542 }
548 }
543
549
544 static void execoriginalhg(const char *argv[])
550 static void execoriginalhg(const char *argv[])
545 {
551 {
546 debugmsg("execute original hg");
552 debugmsg("execute original hg");
547 if (execvp(gethgcmd(), (char **)argv) < 0)
553 if (execvp(gethgcmd(), (char **)argv) < 0)
548 abortmsgerrno("failed to exec original hg");
554 abortmsgerrno("failed to exec original hg");
549 }
555 }
550
556
551 int main(int argc, const char *argv[], const char *envp[])
557 int main(int argc, const char *argv[], const char *envp[])
552 {
558 {
553 if (getenv("CHGDEBUG"))
559 if (getenv("CHGDEBUG"))
554 enabledebugmsg();
560 enabledebugmsg();
555
561
556 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
562 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
557 enablecolor();
563 enablecolor();
558
564
559 if (getenv("CHGINTERNALMARK"))
565 if (getenv("CHGINTERNALMARK"))
560 abortmsg("chg started by chg detected.\n"
566 abortmsg("chg started by chg detected.\n"
561 "Please make sure ${HG:-hg} is not a symlink or "
567 "Please make sure ${HG:-hg} is not a symlink or "
562 "wrapper to chg. Alternatively, set $CHGHG to the "
568 "wrapper to chg. Alternatively, set $CHGHG to the "
563 "path of real hg.");
569 "path of real hg.");
564
570
565 if (isunsupported(argc - 1, argv + 1))
571 if (isunsupported(argc - 1, argv + 1))
566 execoriginalhg(argv);
572 execoriginalhg(argv);
567
573
568 struct cmdserveropts opts;
574 struct cmdserveropts opts;
569 initcmdserveropts(&opts);
575 initcmdserveropts(&opts);
570 setcmdserveropts(&opts);
576 setcmdserveropts(&opts);
571 setcmdserverargs(&opts, argc, argv);
577 setcmdserverargs(&opts, argc, argv);
572
578
573 if (argc == 2) {
579 if (argc == 2) {
574 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
580 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
575 killcmdserver(&opts);
581 killcmdserver(&opts);
576 return 0;
582 return 0;
577 }
583 }
578 }
584 }
579
585
580 hgclient_t *hgc;
586 hgclient_t *hgc;
581 size_t retry = 0;
587 size_t retry = 0;
582 while (1) {
588 while (1) {
583 hgc = connectcmdserver(&opts);
589 hgc = connectcmdserver(&opts);
584 if (!hgc)
590 if (!hgc)
585 abortmsg("cannot open hg client");
591 abortmsg("cannot open hg client");
586 hgc_setenv(hgc, envp);
592 hgc_setenv(hgc, envp);
587 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
593 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
588 int needreconnect = runinstructions(&opts, insts);
594 int needreconnect = runinstructions(&opts, insts);
589 free(insts);
595 free(insts);
590 if (!needreconnect)
596 if (!needreconnect)
591 break;
597 break;
592 hgc_close(hgc);
598 hgc_close(hgc);
593 if (++retry > 10)
599 if (++retry > 10)
594 abortmsg("too many redirections.\n"
600 abortmsg("too many redirections.\n"
595 "Please make sure %s is not a wrapper which "
601 "Please make sure %s is not a wrapper which "
596 "changes sensitive environment variables "
602 "changes sensitive environment variables "
597 "before executing hg. If you have to use a "
603 "before executing hg. If you have to use a "
598 "wrapper, wrap chg instead of hg.",
604 "wrapper, wrap chg instead of hg.",
599 gethgcmd());
605 gethgcmd());
600 }
606 }
601
607
602 setupsignalhandler(hgc_peerpid(hgc));
608 setupsignalhandler(hgc_peerpid(hgc));
603 pid_t pagerpid = setuppager(hgc, argv + 1, argc - 1);
609 pid_t pagerpid = setuppager(hgc, argv + 1, argc - 1);
604 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
610 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
605 hgc_close(hgc);
611 hgc_close(hgc);
606 freecmdserveropts(&opts);
612 freecmdserveropts(&opts);
607 if (pagerpid)
613 if (pagerpid)
608 waitpager(pagerpid);
614 waitpager(pagerpid);
609
615
610 return exitcode;
616 return exitcode;
611 }
617 }
General Comments 0
You need to be logged in to leave comments. Login now