##// END OF EJS Templates
chg: change default connect timeout to 60 seconds...
Jun Wu -
r29357:66d41c9e default
parent child Browse files
Show More
@@ -1,32 +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
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
32 connecting to a cmdserver. If it is 0, chg will wait forever. Default: 60
@@ -1,617 +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
252
253 unsigned int timeoutsec = 10; /* default: 10 seconds */
253 unsigned int timeoutsec = 60; /* default: 60 seconds */
254 const char *timeoutenv = getenv("CHGTIMEOUT");
254 const char *timeoutenv = getenv("CHGTIMEOUT");
255 if (timeoutenv)
255 if (timeoutenv)
256 sscanf(timeoutenv, "%u", &timeoutsec);
256 sscanf(timeoutenv, "%u", &timeoutsec);
257
257
258 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
258 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
259 hgclient_t *hgc = hgc_open(opts->sockname);
259 hgclient_t *hgc = hgc_open(opts->sockname);
260 if (hgc)
260 if (hgc)
261 return hgc;
261 return hgc;
262
262
263 if (pid > 0) {
263 if (pid > 0) {
264 /* collect zombie if child process fails to start */
264 /* collect zombie if child process fails to start */
265 int r = waitpid(pid, &pst, WNOHANG);
265 int r = waitpid(pid, &pst, WNOHANG);
266 if (r != 0)
266 if (r != 0)
267 goto cleanup;
267 goto cleanup;
268 }
268 }
269
269
270 nanosleep(&sleepreq, NULL);
270 nanosleep(&sleepreq, NULL);
271 }
271 }
272
272
273 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
273 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
274 return NULL;
274 return NULL;
275
275
276 cleanup:
276 cleanup:
277 if (WIFEXITED(pst)) {
277 if (WIFEXITED(pst)) {
278 if (WEXITSTATUS(pst) == 0)
278 if (WEXITSTATUS(pst) == 0)
279 abortmsg("could not connect to cmdserver "
279 abortmsg("could not connect to cmdserver "
280 "(exited with status 0)");
280 "(exited with status 0)");
281 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
281 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
282 exit(WEXITSTATUS(pst));
282 exit(WEXITSTATUS(pst));
283 } else if (WIFSIGNALED(pst)) {
283 } else if (WIFSIGNALED(pst)) {
284 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
284 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
285 } else {
285 } else {
286 abortmsg("error while waiting for cmdserver");
286 abortmsg("error while waiting for cmdserver");
287 }
287 }
288 return NULL;
288 return NULL;
289 }
289 }
290
290
291 /* Connect to a cmdserver. Will start a new server on demand. */
291 /* Connect to a cmdserver. Will start a new server on demand. */
292 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
292 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
293 {
293 {
294 const char *sockname = opts->redirectsockname[0] ?
294 const char *sockname = opts->redirectsockname[0] ?
295 opts->redirectsockname : opts->sockname;
295 opts->redirectsockname : opts->sockname;
296 debugmsg("try connect to %s", sockname);
296 debugmsg("try connect to %s", sockname);
297 hgclient_t *hgc = hgc_open(sockname);
297 hgclient_t *hgc = hgc_open(sockname);
298 if (hgc)
298 if (hgc)
299 return hgc;
299 return hgc;
300
300
301 lockcmdserver(opts);
301 lockcmdserver(opts);
302 hgc = hgc_open(sockname);
302 hgc = hgc_open(sockname);
303 if (hgc) {
303 if (hgc) {
304 unlockcmdserver(opts);
304 unlockcmdserver(opts);
305 debugmsg("cmdserver is started by another process");
305 debugmsg("cmdserver is started by another process");
306 return hgc;
306 return hgc;
307 }
307 }
308
308
309 /* prevent us from being connected to an outdated server: we were
309 /* prevent us from being connected to an outdated server: we were
310 * told by a server to redirect to opts->redirectsockname and that
310 * told by a server to redirect to opts->redirectsockname and that
311 * 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
312 * again because it will probably tell us the same thing. */
312 * again because it will probably tell us the same thing. */
313 if (sockname == opts->redirectsockname)
313 if (sockname == opts->redirectsockname)
314 unlink(opts->sockname);
314 unlink(opts->sockname);
315
315
316 debugmsg("start cmdserver at %s", opts->sockname);
316 debugmsg("start cmdserver at %s", opts->sockname);
317
317
318 pid_t pid = fork();
318 pid_t pid = fork();
319 if (pid < 0)
319 if (pid < 0)
320 abortmsg("failed to fork cmdserver process");
320 abortmsg("failed to fork cmdserver process");
321 if (pid == 0) {
321 if (pid == 0) {
322 execcmdserver(opts);
322 execcmdserver(opts);
323 } else {
323 } else {
324 hgc = retryconnectcmdserver(opts, pid);
324 hgc = retryconnectcmdserver(opts, pid);
325 }
325 }
326
326
327 unlockcmdserver(opts);
327 unlockcmdserver(opts);
328 return hgc;
328 return hgc;
329 }
329 }
330
330
331 static void killcmdserver(const struct cmdserveropts *opts)
331 static void killcmdserver(const struct cmdserveropts *opts)
332 {
332 {
333 /* resolve config hash */
333 /* resolve config hash */
334 char *resolvedpath = realpath(opts->sockname, NULL);
334 char *resolvedpath = realpath(opts->sockname, NULL);
335 if (resolvedpath) {
335 if (resolvedpath) {
336 unlink(resolvedpath);
336 unlink(resolvedpath);
337 free(resolvedpath);
337 free(resolvedpath);
338 }
338 }
339 }
339 }
340
340
341 static pid_t peerpid = 0;
341 static pid_t peerpid = 0;
342
342
343 static void forwardsignal(int sig)
343 static void forwardsignal(int sig)
344 {
344 {
345 assert(peerpid > 0);
345 assert(peerpid > 0);
346 if (kill(peerpid, sig) < 0)
346 if (kill(peerpid, sig) < 0)
347 abortmsgerrno("cannot kill %d", peerpid);
347 abortmsgerrno("cannot kill %d", peerpid);
348 debugmsg("forward signal %d", sig);
348 debugmsg("forward signal %d", sig);
349 }
349 }
350
350
351 static void handlestopsignal(int sig)
351 static void handlestopsignal(int sig)
352 {
352 {
353 sigset_t unblockset, oldset;
353 sigset_t unblockset, oldset;
354 struct sigaction sa, oldsa;
354 struct sigaction sa, oldsa;
355 if (sigemptyset(&unblockset) < 0)
355 if (sigemptyset(&unblockset) < 0)
356 goto error;
356 goto error;
357 if (sigaddset(&unblockset, sig) < 0)
357 if (sigaddset(&unblockset, sig) < 0)
358 goto error;
358 goto error;
359 memset(&sa, 0, sizeof(sa));
359 memset(&sa, 0, sizeof(sa));
360 sa.sa_handler = SIG_DFL;
360 sa.sa_handler = SIG_DFL;
361 sa.sa_flags = SA_RESTART;
361 sa.sa_flags = SA_RESTART;
362 if (sigemptyset(&sa.sa_mask) < 0)
362 if (sigemptyset(&sa.sa_mask) < 0)
363 goto error;
363 goto error;
364
364
365 forwardsignal(sig);
365 forwardsignal(sig);
366 if (raise(sig) < 0) /* resend to self */
366 if (raise(sig) < 0) /* resend to self */
367 goto error;
367 goto error;
368 if (sigaction(sig, &sa, &oldsa) < 0)
368 if (sigaction(sig, &sa, &oldsa) < 0)
369 goto error;
369 goto error;
370 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
370 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
371 goto error;
371 goto error;
372 /* resent signal will be handled before sigprocmask() returns */
372 /* resent signal will be handled before sigprocmask() returns */
373 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
373 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
374 goto error;
374 goto error;
375 if (sigaction(sig, &oldsa, NULL) < 0)
375 if (sigaction(sig, &oldsa, NULL) < 0)
376 goto error;
376 goto error;
377 return;
377 return;
378
378
379 error:
379 error:
380 abortmsgerrno("failed to handle stop signal");
380 abortmsgerrno("failed to handle stop signal");
381 }
381 }
382
382
383 static void setupsignalhandler(pid_t pid)
383 static void setupsignalhandler(pid_t pid)
384 {
384 {
385 if (pid <= 0)
385 if (pid <= 0)
386 return;
386 return;
387 peerpid = pid;
387 peerpid = pid;
388
388
389 struct sigaction sa;
389 struct sigaction sa;
390 memset(&sa, 0, sizeof(sa));
390 memset(&sa, 0, sizeof(sa));
391 sa.sa_handler = forwardsignal;
391 sa.sa_handler = forwardsignal;
392 sa.sa_flags = SA_RESTART;
392 sa.sa_flags = SA_RESTART;
393 if (sigemptyset(&sa.sa_mask) < 0)
393 if (sigemptyset(&sa.sa_mask) < 0)
394 goto error;
394 goto error;
395
395
396 if (sigaction(SIGHUP, &sa, NULL) < 0)
396 if (sigaction(SIGHUP, &sa, NULL) < 0)
397 goto error;
397 goto error;
398 if (sigaction(SIGINT, &sa, NULL) < 0)
398 if (sigaction(SIGINT, &sa, NULL) < 0)
399 goto error;
399 goto error;
400
400
401 /* terminate frontend by double SIGTERM in case of server freeze */
401 /* terminate frontend by double SIGTERM in case of server freeze */
402 sa.sa_flags |= SA_RESETHAND;
402 sa.sa_flags |= SA_RESETHAND;
403 if (sigaction(SIGTERM, &sa, NULL) < 0)
403 if (sigaction(SIGTERM, &sa, NULL) < 0)
404 goto error;
404 goto error;
405
405
406 /* notify the worker about window resize events */
406 /* notify the worker about window resize events */
407 sa.sa_flags = SA_RESTART;
407 sa.sa_flags = SA_RESTART;
408 if (sigaction(SIGWINCH, &sa, NULL) < 0)
408 if (sigaction(SIGWINCH, &sa, NULL) < 0)
409 goto error;
409 goto error;
410 /* propagate job control requests to worker */
410 /* propagate job control requests to worker */
411 sa.sa_handler = forwardsignal;
411 sa.sa_handler = forwardsignal;
412 sa.sa_flags = SA_RESTART;
412 sa.sa_flags = SA_RESTART;
413 if (sigaction(SIGCONT, &sa, NULL) < 0)
413 if (sigaction(SIGCONT, &sa, NULL) < 0)
414 goto error;
414 goto error;
415 sa.sa_handler = handlestopsignal;
415 sa.sa_handler = handlestopsignal;
416 sa.sa_flags = SA_RESTART;
416 sa.sa_flags = SA_RESTART;
417 if (sigaction(SIGTSTP, &sa, NULL) < 0)
417 if (sigaction(SIGTSTP, &sa, NULL) < 0)
418 goto error;
418 goto error;
419
419
420 return;
420 return;
421
421
422 error:
422 error:
423 abortmsgerrno("failed to set up signal handlers");
423 abortmsgerrno("failed to set up signal handlers");
424 }
424 }
425
425
426 /* This implementation is based on hgext/pager.py (post 369741ef7253)
426 /* This implementation is based on hgext/pager.py (post 369741ef7253)
427 * 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 */
428 static pid_t setuppager(hgclient_t *hgc, const char *const args[],
428 static pid_t setuppager(hgclient_t *hgc, const char *const args[],
429 size_t argsize)
429 size_t argsize)
430 {
430 {
431 const char *pagercmd = hgc_getpager(hgc, args, argsize);
431 const char *pagercmd = hgc_getpager(hgc, args, argsize);
432 if (!pagercmd)
432 if (!pagercmd)
433 return 0;
433 return 0;
434
434
435 int pipefds[2];
435 int pipefds[2];
436 if (pipe(pipefds) < 0)
436 if (pipe(pipefds) < 0)
437 return 0;
437 return 0;
438 pid_t pid = fork();
438 pid_t pid = fork();
439 if (pid < 0)
439 if (pid < 0)
440 goto error;
440 goto error;
441 if (pid > 0) {
441 if (pid > 0) {
442 close(pipefds[0]);
442 close(pipefds[0]);
443 if (dup2(pipefds[1], fileno(stdout)) < 0)
443 if (dup2(pipefds[1], fileno(stdout)) < 0)
444 goto error;
444 goto error;
445 if (isatty(fileno(stderr))) {
445 if (isatty(fileno(stderr))) {
446 if (dup2(pipefds[1], fileno(stderr)) < 0)
446 if (dup2(pipefds[1], fileno(stderr)) < 0)
447 goto error;
447 goto error;
448 }
448 }
449 close(pipefds[1]);
449 close(pipefds[1]);
450 hgc_attachio(hgc); /* reattach to pager */
450 hgc_attachio(hgc); /* reattach to pager */
451 return pid;
451 return pid;
452 } else {
452 } else {
453 dup2(pipefds[0], fileno(stdin));
453 dup2(pipefds[0], fileno(stdin));
454 close(pipefds[0]);
454 close(pipefds[0]);
455 close(pipefds[1]);
455 close(pipefds[1]);
456
456
457 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
457 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
458 if (r < 0) {
458 if (r < 0) {
459 abortmsgerrno("cannot start pager '%s'", pagercmd);
459 abortmsgerrno("cannot start pager '%s'", pagercmd);
460 }
460 }
461 return 0;
461 return 0;
462 }
462 }
463
463
464 error:
464 error:
465 close(pipefds[0]);
465 close(pipefds[0]);
466 close(pipefds[1]);
466 close(pipefds[1]);
467 abortmsgerrno("failed to prepare pager");
467 abortmsgerrno("failed to prepare pager");
468 return 0;
468 return 0;
469 }
469 }
470
470
471 static void waitpager(pid_t pid)
471 static void waitpager(pid_t pid)
472 {
472 {
473 /* close output streams to notify the pager its input ends */
473 /* close output streams to notify the pager its input ends */
474 fclose(stdout);
474 fclose(stdout);
475 fclose(stderr);
475 fclose(stderr);
476 while (1) {
476 while (1) {
477 pid_t ret = waitpid(pid, NULL, 0);
477 pid_t ret = waitpid(pid, NULL, 0);
478 if (ret == -1 && errno == EINTR)
478 if (ret == -1 && errno == EINTR)
479 continue;
479 continue;
480 break;
480 break;
481 }
481 }
482 }
482 }
483
483
484 /* 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
485 * Return 1 if reconnect is needed, otherwise 0 */
485 * Return 1 if reconnect is needed, otherwise 0 */
486 static int runinstructions(struct cmdserveropts *opts, const char **insts)
486 static int runinstructions(struct cmdserveropts *opts, const char **insts)
487 {
487 {
488 int needreconnect = 0;
488 int needreconnect = 0;
489 if (!insts)
489 if (!insts)
490 return needreconnect;
490 return needreconnect;
491
491
492 assert(insts);
492 assert(insts);
493 opts->redirectsockname[0] = '\0';
493 opts->redirectsockname[0] = '\0';
494 const char **pinst;
494 const char **pinst;
495 for (pinst = insts; *pinst; pinst++) {
495 for (pinst = insts; *pinst; pinst++) {
496 debugmsg("instruction: %s", *pinst);
496 debugmsg("instruction: %s", *pinst);
497 if (strncmp(*pinst, "unlink ", 7) == 0) {
497 if (strncmp(*pinst, "unlink ", 7) == 0) {
498 unlink(*pinst + 7);
498 unlink(*pinst + 7);
499 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
499 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
500 int r = snprintf(opts->redirectsockname,
500 int r = snprintf(opts->redirectsockname,
501 sizeof(opts->redirectsockname),
501 sizeof(opts->redirectsockname),
502 "%s", *pinst + 9);
502 "%s", *pinst + 9);
503 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
503 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
504 abortmsg("redirect path is too long (%d)", r);
504 abortmsg("redirect path is too long (%d)", r);
505 needreconnect = 1;
505 needreconnect = 1;
506 } else if (strncmp(*pinst, "exit ", 5) == 0) {
506 } else if (strncmp(*pinst, "exit ", 5) == 0) {
507 int n = 0;
507 int n = 0;
508 if (sscanf(*pinst + 5, "%d", &n) != 1)
508 if (sscanf(*pinst + 5, "%d", &n) != 1)
509 abortmsg("cannot read the exit code");
509 abortmsg("cannot read the exit code");
510 exit(n);
510 exit(n);
511 } else if (strcmp(*pinst, "reconnect") == 0) {
511 } else if (strcmp(*pinst, "reconnect") == 0) {
512 needreconnect = 1;
512 needreconnect = 1;
513 } else {
513 } else {
514 abortmsg("unknown instruction: %s", *pinst);
514 abortmsg("unknown instruction: %s", *pinst);
515 }
515 }
516 }
516 }
517 return needreconnect;
517 return needreconnect;
518 }
518 }
519
519
520 /*
520 /*
521 * 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
522 * 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
523 * not return false positives.
523 * not return false positives.
524 */
524 */
525 static int isunsupported(int argc, const char *argv[])
525 static int isunsupported(int argc, const char *argv[])
526 {
526 {
527 enum {
527 enum {
528 SERVE = 1,
528 SERVE = 1,
529 DAEMON = 2,
529 DAEMON = 2,
530 SERVEDAEMON = SERVE | DAEMON,
530 SERVEDAEMON = SERVE | DAEMON,
531 TIME = 4,
531 TIME = 4,
532 };
532 };
533 unsigned int state = 0;
533 unsigned int state = 0;
534 int i;
534 int i;
535 for (i = 0; i < argc; ++i) {
535 for (i = 0; i < argc; ++i) {
536 if (strcmp(argv[i], "--") == 0)
536 if (strcmp(argv[i], "--") == 0)
537 break;
537 break;
538 if (i == 0 && strcmp("serve", argv[i]) == 0)
538 if (i == 0 && strcmp("serve", argv[i]) == 0)
539 state |= SERVE;
539 state |= SERVE;
540 else if (strcmp("-d", argv[i]) == 0 ||
540 else if (strcmp("-d", argv[i]) == 0 ||
541 strcmp("--daemon", argv[i]) == 0)
541 strcmp("--daemon", argv[i]) == 0)
542 state |= DAEMON;
542 state |= DAEMON;
543 else if (strcmp("--time", argv[i]) == 0)
543 else if (strcmp("--time", argv[i]) == 0)
544 state |= TIME;
544 state |= TIME;
545 }
545 }
546 return (state & TIME) == TIME ||
546 return (state & TIME) == TIME ||
547 (state & SERVEDAEMON) == SERVEDAEMON;
547 (state & SERVEDAEMON) == SERVEDAEMON;
548 }
548 }
549
549
550 static void execoriginalhg(const char *argv[])
550 static void execoriginalhg(const char *argv[])
551 {
551 {
552 debugmsg("execute original hg");
552 debugmsg("execute original hg");
553 if (execvp(gethgcmd(), (char **)argv) < 0)
553 if (execvp(gethgcmd(), (char **)argv) < 0)
554 abortmsgerrno("failed to exec original hg");
554 abortmsgerrno("failed to exec original hg");
555 }
555 }
556
556
557 int main(int argc, const char *argv[], const char *envp[])
557 int main(int argc, const char *argv[], const char *envp[])
558 {
558 {
559 if (getenv("CHGDEBUG"))
559 if (getenv("CHGDEBUG"))
560 enabledebugmsg();
560 enabledebugmsg();
561
561
562 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
562 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
563 enablecolor();
563 enablecolor();
564
564
565 if (getenv("CHGINTERNALMARK"))
565 if (getenv("CHGINTERNALMARK"))
566 abortmsg("chg started by chg detected.\n"
566 abortmsg("chg started by chg detected.\n"
567 "Please make sure ${HG:-hg} is not a symlink or "
567 "Please make sure ${HG:-hg} is not a symlink or "
568 "wrapper to chg. Alternatively, set $CHGHG to the "
568 "wrapper to chg. Alternatively, set $CHGHG to the "
569 "path of real hg.");
569 "path of real hg.");
570
570
571 if (isunsupported(argc - 1, argv + 1))
571 if (isunsupported(argc - 1, argv + 1))
572 execoriginalhg(argv);
572 execoriginalhg(argv);
573
573
574 struct cmdserveropts opts;
574 struct cmdserveropts opts;
575 initcmdserveropts(&opts);
575 initcmdserveropts(&opts);
576 setcmdserveropts(&opts);
576 setcmdserveropts(&opts);
577 setcmdserverargs(&opts, argc, argv);
577 setcmdserverargs(&opts, argc, argv);
578
578
579 if (argc == 2) {
579 if (argc == 2) {
580 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
580 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
581 killcmdserver(&opts);
581 killcmdserver(&opts);
582 return 0;
582 return 0;
583 }
583 }
584 }
584 }
585
585
586 hgclient_t *hgc;
586 hgclient_t *hgc;
587 size_t retry = 0;
587 size_t retry = 0;
588 while (1) {
588 while (1) {
589 hgc = connectcmdserver(&opts);
589 hgc = connectcmdserver(&opts);
590 if (!hgc)
590 if (!hgc)
591 abortmsg("cannot open hg client");
591 abortmsg("cannot open hg client");
592 hgc_setenv(hgc, envp);
592 hgc_setenv(hgc, envp);
593 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
593 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
594 int needreconnect = runinstructions(&opts, insts);
594 int needreconnect = runinstructions(&opts, insts);
595 free(insts);
595 free(insts);
596 if (!needreconnect)
596 if (!needreconnect)
597 break;
597 break;
598 hgc_close(hgc);
598 hgc_close(hgc);
599 if (++retry > 10)
599 if (++retry > 10)
600 abortmsg("too many redirections.\n"
600 abortmsg("too many redirections.\n"
601 "Please make sure %s is not a wrapper which "
601 "Please make sure %s is not a wrapper which "
602 "changes sensitive environment variables "
602 "changes sensitive environment variables "
603 "before executing hg. If you have to use a "
603 "before executing hg. If you have to use a "
604 "wrapper, wrap chg instead of hg.",
604 "wrapper, wrap chg instead of hg.",
605 gethgcmd());
605 gethgcmd());
606 }
606 }
607
607
608 setupsignalhandler(hgc_peerpid(hgc));
608 setupsignalhandler(hgc_peerpid(hgc));
609 pid_t pagerpid = setuppager(hgc, argv + 1, argc - 1);
609 pid_t pagerpid = setuppager(hgc, argv + 1, argc - 1);
610 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
610 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
611 hgc_close(hgc);
611 hgc_close(hgc);
612 freecmdserveropts(&opts);
612 freecmdserveropts(&opts);
613 if (pagerpid)
613 if (pagerpid)
614 waitpager(pagerpid);
614 waitpager(pagerpid);
615
615
616 return exitcode;
616 return exitcode;
617 }
617 }
General Comments 0
You need to be logged in to leave comments. Login now