##// END OF EJS Templates
chg: use --daemon-postexec chdir:/ instead of --cwd /...
Jun Wu -
r28453:8a7110e3 default
parent child Browse files
Show More
@@ -1,579 +1,578 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 "--cwd", "/",
215 214 "--cmdserver", "chgunix",
216 215 "--address", opts->sockname,
217 "--daemon-postexec", "none",
216 "--daemon-postexec", "chdir:/",
218 217 "--pid-file", opts->pidfile,
219 218 "--config", "extensions.chgserver=",
220 219 };
221 220 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
222 221 size_t argsize = baseargvsize + opts->argsize + 1;
223 222
224 223 const char **argv = mallocx(sizeof(char *) * argsize);
225 224 memcpy(argv, baseargv, sizeof(baseargv));
226 225 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
227 226 argv[argsize - 1] = NULL;
228 227
229 228 if (putenv("CHGINTERNALMARK=") != 0)
230 229 abortmsg("failed to putenv (errno = %d)", errno);
231 230 if (execvp(hgcmd, (char **)argv) < 0)
232 231 abortmsg("failed to exec cmdserver (errno = %d)", errno);
233 232 free(argv);
234 233 }
235 234
236 235 /* Retry until we can connect to the server. Give up after some time. */
237 236 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
238 237 {
239 238 static const struct timespec sleepreq = {0, 10 * 1000000};
240 239 int pst = 0;
241 240
242 241 for (unsigned int i = 0; i < 10 * 100; i++) {
243 242 hgclient_t *hgc = hgc_open(opts->sockname);
244 243 if (hgc)
245 244 return hgc;
246 245
247 246 if (pid > 0) {
248 247 /* collect zombie if child process fails to start */
249 248 int r = waitpid(pid, &pst, WNOHANG);
250 249 if (r != 0)
251 250 goto cleanup;
252 251 }
253 252
254 253 nanosleep(&sleepreq, NULL);
255 254 }
256 255
257 256 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
258 257 return NULL;
259 258
260 259 cleanup:
261 260 if (WIFEXITED(pst)) {
262 261 abortmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
263 262 } else if (WIFSIGNALED(pst)) {
264 263 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
265 264 } else {
266 265 abortmsg("error white waiting cmdserver");
267 266 }
268 267 return NULL;
269 268 }
270 269
271 270 /* Connect to a cmdserver. Will start a new server on demand. */
272 271 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
273 272 {
274 273 const char *sockname = opts->redirectsockname[0] ?
275 274 opts->redirectsockname : opts->sockname;
276 275 hgclient_t *hgc = hgc_open(sockname);
277 276 if (hgc)
278 277 return hgc;
279 278
280 279 lockcmdserver(opts);
281 280 hgc = hgc_open(sockname);
282 281 if (hgc) {
283 282 unlockcmdserver(opts);
284 283 debugmsg("cmdserver is started by another process");
285 284 return hgc;
286 285 }
287 286
288 287 /* prevent us from being connected to an outdated server: we were
289 288 * told by a server to redirect to opts->redirectsockname and that
290 289 * address does not work. we do not want to connect to the server
291 290 * again because it will probably tell us the same thing. */
292 291 if (sockname == opts->redirectsockname)
293 292 unlink(opts->sockname);
294 293
295 294 debugmsg("start cmdserver at %s", opts->sockname);
296 295
297 296 pid_t pid = fork();
298 297 if (pid < 0)
299 298 abortmsg("failed to fork cmdserver process");
300 299 if (pid == 0) {
301 300 /* do not leak lockfd to hg */
302 301 close(opts->lockfd);
303 302 /* bypass uisetup() of pager extension */
304 303 int nullfd = open("/dev/null", O_WRONLY);
305 304 if (nullfd >= 0) {
306 305 dup2(nullfd, fileno(stdout));
307 306 close(nullfd);
308 307 }
309 308 execcmdserver(opts);
310 309 } else {
311 310 hgc = retryconnectcmdserver(opts, pid);
312 311 }
313 312
314 313 unlockcmdserver(opts);
315 314 return hgc;
316 315 }
317 316
318 317 static void killcmdserver(const struct cmdserveropts *opts, int sig)
319 318 {
320 319 FILE *fp = fopen(opts->pidfile, "r");
321 320 if (!fp)
322 321 abortmsg("cannot open %s (errno = %d)", opts->pidfile, errno);
323 322 int pid = 0;
324 323 int n = fscanf(fp, "%d", &pid);
325 324 fclose(fp);
326 325 if (n != 1 || pid <= 0)
327 326 abortmsg("cannot read pid from %s", opts->pidfile);
328 327
329 328 if (kill((pid_t)pid, sig) < 0) {
330 329 if (errno == ESRCH)
331 330 return;
332 331 abortmsg("cannot kill %d (errno = %d)", pid, errno);
333 332 }
334 333 }
335 334
336 335 static pid_t peerpid = 0;
337 336
338 337 static void forwardsignal(int sig)
339 338 {
340 339 assert(peerpid > 0);
341 340 if (kill(peerpid, sig) < 0)
342 341 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
343 342 debugmsg("forward signal %d", sig);
344 343 }
345 344
346 345 static void handlestopsignal(int sig)
347 346 {
348 347 sigset_t unblockset, oldset;
349 348 struct sigaction sa, oldsa;
350 349 if (sigemptyset(&unblockset) < 0)
351 350 goto error;
352 351 if (sigaddset(&unblockset, sig) < 0)
353 352 goto error;
354 353 memset(&sa, 0, sizeof(sa));
355 354 sa.sa_handler = SIG_DFL;
356 355 sa.sa_flags = SA_RESTART;
357 356 if (sigemptyset(&sa.sa_mask) < 0)
358 357 goto error;
359 358
360 359 forwardsignal(sig);
361 360 if (raise(sig) < 0) /* resend to self */
362 361 goto error;
363 362 if (sigaction(sig, &sa, &oldsa) < 0)
364 363 goto error;
365 364 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
366 365 goto error;
367 366 /* resent signal will be handled before sigprocmask() returns */
368 367 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
369 368 goto error;
370 369 if (sigaction(sig, &oldsa, NULL) < 0)
371 370 goto error;
372 371 return;
373 372
374 373 error:
375 374 abortmsg("failed to handle stop signal (errno = %d)", errno);
376 375 }
377 376
378 377 static void setupsignalhandler(pid_t pid)
379 378 {
380 379 if (pid <= 0)
381 380 return;
382 381 peerpid = pid;
383 382
384 383 struct sigaction sa;
385 384 memset(&sa, 0, sizeof(sa));
386 385 sa.sa_handler = forwardsignal;
387 386 sa.sa_flags = SA_RESTART;
388 387 if (sigemptyset(&sa.sa_mask) < 0)
389 388 goto error;
390 389
391 390 if (sigaction(SIGHUP, &sa, NULL) < 0)
392 391 goto error;
393 392 if (sigaction(SIGINT, &sa, NULL) < 0)
394 393 goto error;
395 394
396 395 /* terminate frontend by double SIGTERM in case of server freeze */
397 396 sa.sa_flags |= SA_RESETHAND;
398 397 if (sigaction(SIGTERM, &sa, NULL) < 0)
399 398 goto error;
400 399
401 400 /* propagate job control requests to worker */
402 401 sa.sa_handler = forwardsignal;
403 402 sa.sa_flags = SA_RESTART;
404 403 if (sigaction(SIGCONT, &sa, NULL) < 0)
405 404 goto error;
406 405 sa.sa_handler = handlestopsignal;
407 406 sa.sa_flags = SA_RESTART;
408 407 if (sigaction(SIGTSTP, &sa, NULL) < 0)
409 408 goto error;
410 409
411 410 return;
412 411
413 412 error:
414 413 abortmsg("failed to set up signal handlers (errno = %d)", errno);
415 414 }
416 415
417 416 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
418 417 static void setuppager(hgclient_t *hgc, const char *const args[],
419 418 size_t argsize)
420 419 {
421 420 const char *pagercmd = hgc_getpager(hgc, args, argsize);
422 421 if (!pagercmd)
423 422 return;
424 423
425 424 int pipefds[2];
426 425 if (pipe(pipefds) < 0)
427 426 return;
428 427 pid_t pid = fork();
429 428 if (pid < 0)
430 429 goto error;
431 430 if (pid == 0) {
432 431 close(pipefds[0]);
433 432 if (dup2(pipefds[1], fileno(stdout)) < 0)
434 433 goto error;
435 434 if (isatty(fileno(stderr))) {
436 435 if (dup2(pipefds[1], fileno(stderr)) < 0)
437 436 goto error;
438 437 }
439 438 close(pipefds[1]);
440 439 hgc_attachio(hgc); /* reattach to pager */
441 440 return;
442 441 } else {
443 442 dup2(pipefds[0], fileno(stdin));
444 443 close(pipefds[0]);
445 444 close(pipefds[1]);
446 445
447 446 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
448 447 if (r < 0) {
449 448 abortmsg("cannot start pager '%s' (errno = %d)",
450 449 pagercmd, errno);
451 450 }
452 451 return;
453 452 }
454 453
455 454 error:
456 455 close(pipefds[0]);
457 456 close(pipefds[1]);
458 457 abortmsg("failed to prepare pager (errno = %d)", errno);
459 458 }
460 459
461 460 /* Run instructions sent from the server like unlink and set redirect path */
462 461 static void runinstructions(struct cmdserveropts *opts, const char **insts)
463 462 {
464 463 assert(insts);
465 464 opts->redirectsockname[0] = '\0';
466 465 const char **pinst;
467 466 for (pinst = insts; *pinst; pinst++) {
468 467 debugmsg("instruction: %s", *pinst);
469 468 if (strncmp(*pinst, "unlink ", 7) == 0) {
470 469 unlink(*pinst + 7);
471 470 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
472 471 int r = snprintf(opts->redirectsockname,
473 472 sizeof(opts->redirectsockname),
474 473 "%s", *pinst + 9);
475 474 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
476 475 abortmsg("redirect path is too long (%d)", r);
477 476 } else {
478 477 abortmsg("unknown instruction: %s", *pinst);
479 478 }
480 479 }
481 480 }
482 481
483 482 /*
484 483 * Test whether the command is unsupported or not. This is not designed to
485 484 * cover all cases. But it's fast, does not depend on the server and does
486 485 * not return false positives.
487 486 */
488 487 static int isunsupported(int argc, const char *argv[])
489 488 {
490 489 enum {
491 490 SERVE = 1,
492 491 DAEMON = 2,
493 492 SERVEDAEMON = SERVE | DAEMON,
494 493 TIME = 4,
495 494 };
496 495 unsigned int state = 0;
497 496 int i;
498 497 for (i = 0; i < argc; ++i) {
499 498 if (strcmp(argv[i], "--") == 0)
500 499 break;
501 500 if (i == 0 && strcmp("serve", argv[i]) == 0)
502 501 state |= SERVE;
503 502 else if (strcmp("-d", argv[i]) == 0 ||
504 503 strcmp("--daemon", argv[i]) == 0)
505 504 state |= DAEMON;
506 505 else if (strcmp("--time", argv[i]) == 0)
507 506 state |= TIME;
508 507 }
509 508 return (state & TIME) == TIME ||
510 509 (state & SERVEDAEMON) == SERVEDAEMON;
511 510 }
512 511
513 512 static void execoriginalhg(const char *argv[])
514 513 {
515 514 debugmsg("execute original hg");
516 515 if (execvp(gethgcmd(), (char **)argv) < 0)
517 516 abortmsg("failed to exec original hg (errno = %d)", errno);
518 517 }
519 518
520 519 int main(int argc, const char *argv[], const char *envp[])
521 520 {
522 521 if (getenv("CHGDEBUG"))
523 522 enabledebugmsg();
524 523
525 524 if (getenv("CHGINTERNALMARK"))
526 525 abortmsg("chg started by chg detected.\n"
527 526 "Please make sure ${HG:-hg} is not a symlink or "
528 527 "wrapper to chg. Alternatively, set $CHGHG to the "
529 528 "path of real hg.");
530 529
531 530 if (isunsupported(argc - 1, argv + 1))
532 531 execoriginalhg(argv);
533 532
534 533 struct cmdserveropts opts;
535 534 initcmdserveropts(&opts);
536 535 setcmdserveropts(&opts);
537 536 setcmdserverargs(&opts, argc, argv);
538 537
539 538 if (argc == 2) {
540 539 int sig = 0;
541 540 if (strcmp(argv[1], "--kill-chg-daemon") == 0)
542 541 sig = SIGTERM;
543 542 if (strcmp(argv[1], "--reload-chg-daemon") == 0)
544 543 sig = SIGHUP;
545 544 if (sig > 0) {
546 545 killcmdserver(&opts, sig);
547 546 return 0;
548 547 }
549 548 }
550 549
551 550 hgclient_t *hgc;
552 551 size_t retry = 0;
553 552 while (1) {
554 553 hgc = connectcmdserver(&opts);
555 554 if (!hgc)
556 555 abortmsg("cannot open hg client");
557 556 hgc_setenv(hgc, envp);
558 557 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
559 558 if (insts == NULL)
560 559 break;
561 560 runinstructions(&opts, insts);
562 561 free(insts);
563 562 hgc_close(hgc);
564 563 if (++retry > 10)
565 564 abortmsg("too many redirections.\n"
566 565 "Please make sure %s is not a wrapper which "
567 566 "changes sensitive environment variables "
568 567 "before executing hg. If you have to use a "
569 568 "wrapper, wrap chg instead of hg.",
570 569 gethgcmd());
571 570 }
572 571
573 572 setupsignalhandler(hgc_peerpid(hgc));
574 573 setuppager(hgc, argv + 1, argc - 1);
575 574 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
576 575 hgc_close(hgc);
577 576 freecmdserveropts(&opts);
578 577 return exitcode;
579 578 }
General Comments 0
You need to be logged in to leave comments. Login now