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