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