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