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