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