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