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