##// END OF EJS Templates
chgserver: add an explicit "reconnect" instruction to validate...
Jun Wu -
r28535:aa082a81 default
parent child Browse files
Show More
@@ -1,565 +1,574
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 255 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
256 256 exit(WEXITSTATUS(pst));
257 257 } else if (WIFSIGNALED(pst)) {
258 258 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
259 259 } else {
260 260 abortmsg("error white waiting cmdserver");
261 261 }
262 262 return NULL;
263 263 }
264 264
265 265 /* Connect to a cmdserver. Will start a new server on demand. */
266 266 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
267 267 {
268 268 const char *sockname = opts->redirectsockname[0] ?
269 269 opts->redirectsockname : opts->sockname;
270 270 hgclient_t *hgc = hgc_open(sockname);
271 271 if (hgc)
272 272 return hgc;
273 273
274 274 lockcmdserver(opts);
275 275 hgc = hgc_open(sockname);
276 276 if (hgc) {
277 277 unlockcmdserver(opts);
278 278 debugmsg("cmdserver is started by another process");
279 279 return hgc;
280 280 }
281 281
282 282 /* prevent us from being connected to an outdated server: we were
283 283 * told by a server to redirect to opts->redirectsockname and that
284 284 * address does not work. we do not want to connect to the server
285 285 * again because it will probably tell us the same thing. */
286 286 if (sockname == opts->redirectsockname)
287 287 unlink(opts->sockname);
288 288
289 289 debugmsg("start cmdserver at %s", opts->sockname);
290 290
291 291 pid_t pid = fork();
292 292 if (pid < 0)
293 293 abortmsg("failed to fork cmdserver process");
294 294 if (pid == 0) {
295 295 /* do not leak lockfd to hg */
296 296 close(opts->lockfd);
297 297 /* bypass uisetup() of pager extension */
298 298 int nullfd = open("/dev/null", O_WRONLY);
299 299 if (nullfd >= 0) {
300 300 dup2(nullfd, fileno(stdout));
301 301 close(nullfd);
302 302 }
303 303 execcmdserver(opts);
304 304 } else {
305 305 hgc = retryconnectcmdserver(opts, pid);
306 306 }
307 307
308 308 unlockcmdserver(opts);
309 309 return hgc;
310 310 }
311 311
312 312 static void killcmdserver(const struct cmdserveropts *opts)
313 313 {
314 314 /* resolve config hash */
315 315 char *resolvedpath = realpath(opts->sockname, NULL);
316 316 if (resolvedpath) {
317 317 unlink(resolvedpath);
318 318 free(resolvedpath);
319 319 }
320 320 }
321 321
322 322 static pid_t peerpid = 0;
323 323
324 324 static void forwardsignal(int sig)
325 325 {
326 326 assert(peerpid > 0);
327 327 if (kill(peerpid, sig) < 0)
328 328 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
329 329 debugmsg("forward signal %d", sig);
330 330 }
331 331
332 332 static void handlestopsignal(int sig)
333 333 {
334 334 sigset_t unblockset, oldset;
335 335 struct sigaction sa, oldsa;
336 336 if (sigemptyset(&unblockset) < 0)
337 337 goto error;
338 338 if (sigaddset(&unblockset, sig) < 0)
339 339 goto error;
340 340 memset(&sa, 0, sizeof(sa));
341 341 sa.sa_handler = SIG_DFL;
342 342 sa.sa_flags = SA_RESTART;
343 343 if (sigemptyset(&sa.sa_mask) < 0)
344 344 goto error;
345 345
346 346 forwardsignal(sig);
347 347 if (raise(sig) < 0) /* resend to self */
348 348 goto error;
349 349 if (sigaction(sig, &sa, &oldsa) < 0)
350 350 goto error;
351 351 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
352 352 goto error;
353 353 /* resent signal will be handled before sigprocmask() returns */
354 354 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
355 355 goto error;
356 356 if (sigaction(sig, &oldsa, NULL) < 0)
357 357 goto error;
358 358 return;
359 359
360 360 error:
361 361 abortmsg("failed to handle stop signal (errno = %d)", errno);
362 362 }
363 363
364 364 static void setupsignalhandler(pid_t pid)
365 365 {
366 366 if (pid <= 0)
367 367 return;
368 368 peerpid = pid;
369 369
370 370 struct sigaction sa;
371 371 memset(&sa, 0, sizeof(sa));
372 372 sa.sa_handler = forwardsignal;
373 373 sa.sa_flags = SA_RESTART;
374 374 if (sigemptyset(&sa.sa_mask) < 0)
375 375 goto error;
376 376
377 377 if (sigaction(SIGHUP, &sa, NULL) < 0)
378 378 goto error;
379 379 if (sigaction(SIGINT, &sa, NULL) < 0)
380 380 goto error;
381 381
382 382 /* terminate frontend by double SIGTERM in case of server freeze */
383 383 sa.sa_flags |= SA_RESETHAND;
384 384 if (sigaction(SIGTERM, &sa, NULL) < 0)
385 385 goto error;
386 386
387 387 /* propagate job control requests to worker */
388 388 sa.sa_handler = forwardsignal;
389 389 sa.sa_flags = SA_RESTART;
390 390 if (sigaction(SIGCONT, &sa, NULL) < 0)
391 391 goto error;
392 392 sa.sa_handler = handlestopsignal;
393 393 sa.sa_flags = SA_RESTART;
394 394 if (sigaction(SIGTSTP, &sa, NULL) < 0)
395 395 goto error;
396 396
397 397 return;
398 398
399 399 error:
400 400 abortmsg("failed to set up signal handlers (errno = %d)", errno);
401 401 }
402 402
403 403 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
404 404 static void setuppager(hgclient_t *hgc, const char *const args[],
405 405 size_t argsize)
406 406 {
407 407 const char *pagercmd = hgc_getpager(hgc, args, argsize);
408 408 if (!pagercmd)
409 409 return;
410 410
411 411 int pipefds[2];
412 412 if (pipe(pipefds) < 0)
413 413 return;
414 414 pid_t pid = fork();
415 415 if (pid < 0)
416 416 goto error;
417 417 if (pid == 0) {
418 418 close(pipefds[0]);
419 419 if (dup2(pipefds[1], fileno(stdout)) < 0)
420 420 goto error;
421 421 if (isatty(fileno(stderr))) {
422 422 if (dup2(pipefds[1], fileno(stderr)) < 0)
423 423 goto error;
424 424 }
425 425 close(pipefds[1]);
426 426 hgc_attachio(hgc); /* reattach to pager */
427 427 return;
428 428 } else {
429 429 dup2(pipefds[0], fileno(stdin));
430 430 close(pipefds[0]);
431 431 close(pipefds[1]);
432 432
433 433 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
434 434 if (r < 0) {
435 435 abortmsg("cannot start pager '%s' (errno = %d)",
436 436 pagercmd, errno);
437 437 }
438 438 return;
439 439 }
440 440
441 441 error:
442 442 close(pipefds[0]);
443 443 close(pipefds[1]);
444 444 abortmsg("failed to prepare pager (errno = %d)", errno);
445 445 }
446 446
447 /* Run instructions sent from the server like unlink and set redirect path */
448 static void runinstructions(struct cmdserveropts *opts, const char **insts)
447 /* Run instructions sent from the server like unlink and set redirect path
448 * Return 1 if reconnect is needed, otherwise 0 */
449 static int runinstructions(struct cmdserveropts *opts, const char **insts)
449 450 {
451 int needreconnect = 0;
452 if (!insts)
453 return needreconnect;
454
450 455 assert(insts);
451 456 opts->redirectsockname[0] = '\0';
452 457 const char **pinst;
453 458 for (pinst = insts; *pinst; pinst++) {
454 459 debugmsg("instruction: %s", *pinst);
455 460 if (strncmp(*pinst, "unlink ", 7) == 0) {
456 461 unlink(*pinst + 7);
457 462 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
458 463 int r = snprintf(opts->redirectsockname,
459 464 sizeof(opts->redirectsockname),
460 465 "%s", *pinst + 9);
461 466 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
462 467 abortmsg("redirect path is too long (%d)", r);
468 needreconnect = 1;
463 469 } else if (strncmp(*pinst, "exit ", 5) == 0) {
464 470 int n = 0;
465 471 if (sscanf(*pinst + 5, "%d", &n) != 1)
466 472 abortmsg("cannot read the exit code");
467 473 exit(n);
474 } else if (strcmp(*pinst, "reconnect") == 0) {
475 needreconnect = 1;
468 476 } else {
469 477 abortmsg("unknown instruction: %s", *pinst);
470 478 }
471 479 }
480 return needreconnect;
472 481 }
473 482
474 483 /*
475 484 * Test whether the command is unsupported or not. This is not designed to
476 485 * cover all cases. But it's fast, does not depend on the server and does
477 486 * not return false positives.
478 487 */
479 488 static int isunsupported(int argc, const char *argv[])
480 489 {
481 490 enum {
482 491 SERVE = 1,
483 492 DAEMON = 2,
484 493 SERVEDAEMON = SERVE | DAEMON,
485 494 TIME = 4,
486 495 };
487 496 unsigned int state = 0;
488 497 int i;
489 498 for (i = 0; i < argc; ++i) {
490 499 if (strcmp(argv[i], "--") == 0)
491 500 break;
492 501 if (i == 0 && strcmp("serve", argv[i]) == 0)
493 502 state |= SERVE;
494 503 else if (strcmp("-d", argv[i]) == 0 ||
495 504 strcmp("--daemon", argv[i]) == 0)
496 505 state |= DAEMON;
497 506 else if (strcmp("--time", argv[i]) == 0)
498 507 state |= TIME;
499 508 }
500 509 return (state & TIME) == TIME ||
501 510 (state & SERVEDAEMON) == SERVEDAEMON;
502 511 }
503 512
504 513 static void execoriginalhg(const char *argv[])
505 514 {
506 515 debugmsg("execute original hg");
507 516 if (execvp(gethgcmd(), (char **)argv) < 0)
508 517 abortmsg("failed to exec original hg (errno = %d)", errno);
509 518 }
510 519
511 520 int main(int argc, const char *argv[], const char *envp[])
512 521 {
513 522 if (getenv("CHGDEBUG"))
514 523 enabledebugmsg();
515 524
516 525 if (getenv("CHGINTERNALMARK"))
517 526 abortmsg("chg started by chg detected.\n"
518 527 "Please make sure ${HG:-hg} is not a symlink or "
519 528 "wrapper to chg. Alternatively, set $CHGHG to the "
520 529 "path of real hg.");
521 530
522 531 if (isunsupported(argc - 1, argv + 1))
523 532 execoriginalhg(argv);
524 533
525 534 struct cmdserveropts opts;
526 535 initcmdserveropts(&opts);
527 536 setcmdserveropts(&opts);
528 537 setcmdserverargs(&opts, argc, argv);
529 538
530 539 if (argc == 2) {
531 540 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
532 541 killcmdserver(&opts);
533 542 return 0;
534 543 }
535 544 }
536 545
537 546 hgclient_t *hgc;
538 547 size_t retry = 0;
539 548 while (1) {
540 549 hgc = connectcmdserver(&opts);
541 550 if (!hgc)
542 551 abortmsg("cannot open hg client");
543 552 hgc_setenv(hgc, envp);
544 553 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
545 if (insts == NULL)
554 int needreconnect = runinstructions(&opts, insts);
555 free(insts);
556 if (!needreconnect)
546 557 break;
547 runinstructions(&opts, insts);
548 free(insts);
549 558 hgc_close(hgc);
550 559 if (++retry > 10)
551 560 abortmsg("too many redirections.\n"
552 561 "Please make sure %s is not a wrapper which "
553 562 "changes sensitive environment variables "
554 563 "before executing hg. If you have to use a "
555 564 "wrapper, wrap chg instead of hg.",
556 565 gethgcmd());
557 566 }
558 567
559 568 setupsignalhandler(hgc_peerpid(hgc));
560 569 setuppager(hgc, argv + 1, argc - 1);
561 570 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
562 571 hgc_close(hgc);
563 572 freecmdserveropts(&opts);
564 573 return exitcode;
565 574 }
@@ -1,578 +1,578
1 1 /*
2 2 * A command server client that uses Unix domain socket
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 <arpa/inet.h> /* for ntohl(), htonl() */
11 11 #include <assert.h>
12 12 #include <ctype.h>
13 13 #include <errno.h>
14 14 #include <fcntl.h>
15 15 #include <signal.h>
16 16 #include <stdint.h>
17 17 #include <stdio.h>
18 18 #include <stdlib.h>
19 19 #include <string.h>
20 20 #include <sys/socket.h>
21 21 #include <sys/stat.h>
22 22 #include <sys/un.h>
23 23 #include <unistd.h>
24 24
25 25 #include "hgclient.h"
26 26 #include "util.h"
27 27
28 28 enum {
29 29 CAP_GETENCODING = 0x0001,
30 30 CAP_RUNCOMMAND = 0x0002,
31 31 /* cHg extension: */
32 32 CAP_ATTACHIO = 0x0100,
33 33 CAP_CHDIR = 0x0200,
34 34 CAP_GETPAGER = 0x0400,
35 35 CAP_SETENV = 0x0800,
36 36 CAP_SETUMASK = 0x1000,
37 37 CAP_VALIDATE = 0x2000,
38 38 };
39 39
40 40 typedef struct {
41 41 const char *name;
42 42 unsigned int flag;
43 43 } cappair_t;
44 44
45 45 static const cappair_t captable[] = {
46 46 {"getencoding", CAP_GETENCODING},
47 47 {"runcommand", CAP_RUNCOMMAND},
48 48 {"attachio", CAP_ATTACHIO},
49 49 {"chdir", CAP_CHDIR},
50 50 {"getpager", CAP_GETPAGER},
51 51 {"setenv", CAP_SETENV},
52 52 {"setumask", CAP_SETUMASK},
53 53 {"validate", CAP_VALIDATE},
54 54 {NULL, 0}, /* terminator */
55 55 };
56 56
57 57 typedef struct {
58 58 char ch;
59 59 char *data;
60 60 size_t maxdatasize;
61 61 size_t datasize;
62 62 } context_t;
63 63
64 64 struct hgclient_tag_ {
65 65 int sockfd;
66 66 pid_t pid;
67 67 context_t ctx;
68 68 unsigned int capflags;
69 69 };
70 70
71 71 static const size_t defaultdatasize = 4096;
72 72
73 73 static void initcontext(context_t *ctx)
74 74 {
75 75 ctx->ch = '\0';
76 76 ctx->data = malloc(defaultdatasize);
77 77 ctx->maxdatasize = (ctx->data) ? defaultdatasize : 0;
78 78 ctx->datasize = 0;
79 79 debugmsg("initialize context buffer with size %zu", ctx->maxdatasize);
80 80 }
81 81
82 82 static void enlargecontext(context_t *ctx, size_t newsize)
83 83 {
84 84 if (newsize <= ctx->maxdatasize)
85 85 return;
86 86
87 87 newsize = defaultdatasize
88 88 * ((newsize + defaultdatasize - 1) / defaultdatasize);
89 89 ctx->data = reallocx(ctx->data, newsize);
90 90 ctx->maxdatasize = newsize;
91 91 debugmsg("enlarge context buffer to %zu", ctx->maxdatasize);
92 92 }
93 93
94 94 static void freecontext(context_t *ctx)
95 95 {
96 96 debugmsg("free context buffer");
97 97 free(ctx->data);
98 98 ctx->data = NULL;
99 99 ctx->maxdatasize = 0;
100 100 ctx->datasize = 0;
101 101 }
102 102
103 103 /* Read channeled response from cmdserver */
104 104 static void readchannel(hgclient_t *hgc)
105 105 {
106 106 assert(hgc);
107 107
108 108 ssize_t rsize = recv(hgc->sockfd, &hgc->ctx.ch, sizeof(hgc->ctx.ch), 0);
109 109 if (rsize != sizeof(hgc->ctx.ch))
110 110 abortmsg("failed to read channel");
111 111
112 112 uint32_t datasize_n;
113 113 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0);
114 114 if (rsize != sizeof(datasize_n))
115 115 abortmsg("failed to read data size");
116 116
117 117 /* datasize denotes the maximum size to write if input request */
118 118 hgc->ctx.datasize = ntohl(datasize_n);
119 119 enlargecontext(&hgc->ctx, hgc->ctx.datasize);
120 120
121 121 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S')
122 122 return; /* assumes input request */
123 123
124 124 size_t cursize = 0;
125 125 while (cursize < hgc->ctx.datasize) {
126 126 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
127 127 hgc->ctx.datasize - cursize, 0);
128 128 if (rsize < 0)
129 129 abortmsg("failed to read data block");
130 130 cursize += rsize;
131 131 }
132 132 }
133 133
134 134 static void sendall(int sockfd, const void *data, size_t datasize)
135 135 {
136 136 const char *p = data;
137 137 const char *const endp = p + datasize;
138 138 while (p < endp) {
139 139 ssize_t r = send(sockfd, p, endp - p, 0);
140 140 if (r < 0)
141 141 abortmsg("cannot communicate (errno = %d)", errno);
142 142 p += r;
143 143 }
144 144 }
145 145
146 146 /* Write lengh-data block to cmdserver */
147 147 static void writeblock(const hgclient_t *hgc)
148 148 {
149 149 assert(hgc);
150 150
151 151 const uint32_t datasize_n = htonl(hgc->ctx.datasize);
152 152 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n));
153 153
154 154 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize);
155 155 }
156 156
157 157 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd)
158 158 {
159 159 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize);
160 160
161 161 char buf[strlen(chcmd) + 1];
162 162 memcpy(buf, chcmd, sizeof(buf) - 1);
163 163 buf[sizeof(buf) - 1] = '\n';
164 164 sendall(hgc->sockfd, buf, sizeof(buf));
165 165
166 166 writeblock(hgc);
167 167 }
168 168
169 169 /* Build '\0'-separated list of args. argsize < 0 denotes that args are
170 170 * terminated by NULL. */
171 171 static void packcmdargs(context_t *ctx, const char *const args[],
172 172 ssize_t argsize)
173 173 {
174 174 ctx->datasize = 0;
175 175 const char *const *const end = (argsize >= 0) ? args + argsize : NULL;
176 176 for (const char *const *it = args; it != end && *it; ++it) {
177 177 const size_t n = strlen(*it) + 1; /* include '\0' */
178 178 enlargecontext(ctx, ctx->datasize + n);
179 179 memcpy(ctx->data + ctx->datasize, *it, n);
180 180 ctx->datasize += n;
181 181 }
182 182
183 183 if (ctx->datasize > 0)
184 184 --ctx->datasize; /* strip last '\0' */
185 185 }
186 186
187 187 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */
188 188 static const char **unpackcmdargsnul(const context_t *ctx)
189 189 {
190 190 const char **args = NULL;
191 191 size_t nargs = 0, maxnargs = 0;
192 192 const char *s = ctx->data;
193 193 const char *e = ctx->data + ctx->datasize;
194 194 for (;;) {
195 195 if (nargs + 1 >= maxnargs) { /* including last NULL */
196 196 maxnargs += 256;
197 197 args = reallocx(args, maxnargs * sizeof(args[0]));
198 198 }
199 199 args[nargs] = s;
200 200 nargs++;
201 201 s = memchr(s, '\0', e - s);
202 202 if (!s)
203 203 break;
204 204 s++;
205 205 }
206 206 args[nargs] = NULL;
207 207 return args;
208 208 }
209 209
210 210 static void handlereadrequest(hgclient_t *hgc)
211 211 {
212 212 context_t *ctx = &hgc->ctx;
213 213 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin);
214 214 ctx->datasize = r;
215 215 writeblock(hgc);
216 216 }
217 217
218 218 /* Read single-line */
219 219 static void handlereadlinerequest(hgclient_t *hgc)
220 220 {
221 221 context_t *ctx = &hgc->ctx;
222 222 if (!fgets(ctx->data, ctx->datasize, stdin))
223 223 ctx->data[0] = '\0';
224 224 ctx->datasize = strlen(ctx->data);
225 225 writeblock(hgc);
226 226 }
227 227
228 228 /* Execute the requested command and write exit code */
229 229 static void handlesystemrequest(hgclient_t *hgc)
230 230 {
231 231 context_t *ctx = &hgc->ctx;
232 232 enlargecontext(ctx, ctx->datasize + 1);
233 233 ctx->data[ctx->datasize] = '\0'; /* terminate last string */
234 234
235 235 const char **args = unpackcmdargsnul(ctx);
236 236 if (!args[0] || !args[1])
237 237 abortmsg("missing command or cwd in system request");
238 238 debugmsg("run '%s' at '%s'", args[0], args[1]);
239 239 int32_t r = runshellcmd(args[0], args + 2, args[1]);
240 240 free(args);
241 241
242 242 uint32_t r_n = htonl(r);
243 243 memcpy(ctx->data, &r_n, sizeof(r_n));
244 244 ctx->datasize = sizeof(r_n);
245 245 writeblock(hgc);
246 246 }
247 247
248 248 /* Read response of command execution until receiving 'r'-esult */
249 249 static void handleresponse(hgclient_t *hgc)
250 250 {
251 251 for (;;) {
252 252 readchannel(hgc);
253 253 context_t *ctx = &hgc->ctx;
254 254 debugmsg("response read from channel %c, size %zu",
255 255 ctx->ch, ctx->datasize);
256 256 switch (ctx->ch) {
257 257 case 'o':
258 258 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
259 259 stdout);
260 260 break;
261 261 case 'e':
262 262 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
263 263 stderr);
264 264 break;
265 265 case 'd':
266 266 /* assumes last char is '\n' */
267 267 ctx->data[ctx->datasize - 1] = '\0';
268 268 debugmsg("server: %s", ctx->data);
269 269 break;
270 270 case 'r':
271 271 return;
272 272 case 'I':
273 273 handlereadrequest(hgc);
274 274 break;
275 275 case 'L':
276 276 handlereadlinerequest(hgc);
277 277 break;
278 278 case 'S':
279 279 handlesystemrequest(hgc);
280 280 break;
281 281 default:
282 282 if (isupper(ctx->ch))
283 283 abortmsg("cannot handle response (ch = %c)",
284 284 ctx->ch);
285 285 }
286 286 }
287 287 }
288 288
289 289 static unsigned int parsecapabilities(const char *s, const char *e)
290 290 {
291 291 unsigned int flags = 0;
292 292 while (s < e) {
293 293 const char *t = strchr(s, ' ');
294 294 if (!t || t > e)
295 295 t = e;
296 296 const cappair_t *cap;
297 297 for (cap = captable; cap->flag; ++cap) {
298 298 size_t n = t - s;
299 299 if (strncmp(s, cap->name, n) == 0 &&
300 300 strlen(cap->name) == n) {
301 301 flags |= cap->flag;
302 302 break;
303 303 }
304 304 }
305 305 s = t + 1;
306 306 }
307 307 return flags;
308 308 }
309 309
310 310 static void readhello(hgclient_t *hgc)
311 311 {
312 312 readchannel(hgc);
313 313 context_t *ctx = &hgc->ctx;
314 314 if (ctx->ch != 'o') {
315 315 char ch = ctx->ch;
316 316 if (ch == 'e') {
317 317 /* write early error and will exit */
318 318 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
319 319 stderr);
320 320 handleresponse(hgc);
321 321 }
322 322 abortmsg("unexpected channel of hello message (ch = %c)", ch);
323 323 }
324 324 enlargecontext(ctx, ctx->datasize + 1);
325 325 ctx->data[ctx->datasize] = '\0';
326 326 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize);
327 327
328 328 const char *s = ctx->data;
329 329 const char *const dataend = ctx->data + ctx->datasize;
330 330 while (s < dataend) {
331 331 const char *t = strchr(s, ':');
332 332 if (!t || t[1] != ' ')
333 333 break;
334 334 const char *u = strchr(t + 2, '\n');
335 335 if (!u)
336 336 u = dataend;
337 337 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
338 338 hgc->capflags = parsecapabilities(t + 2, u);
339 339 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
340 340 hgc->pid = strtol(t + 2, NULL, 10);
341 341 }
342 342 s = u + 1;
343 343 }
344 344 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid);
345 345 }
346 346
347 347 static void attachio(hgclient_t *hgc)
348 348 {
349 349 debugmsg("request attachio");
350 350 static const char chcmd[] = "attachio\n";
351 351 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1);
352 352 readchannel(hgc);
353 353 context_t *ctx = &hgc->ctx;
354 354 if (ctx->ch != 'I')
355 355 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch);
356 356
357 357 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
358 358 struct msghdr msgh;
359 359 memset(&msgh, 0, sizeof(msgh));
360 360 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */
361 361 msgh.msg_iov = &iov;
362 362 msgh.msg_iovlen = 1;
363 363 char fdbuf[CMSG_SPACE(sizeof(fds))];
364 364 msgh.msg_control = fdbuf;
365 365 msgh.msg_controllen = sizeof(fdbuf);
366 366 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
367 367 cmsg->cmsg_level = SOL_SOCKET;
368 368 cmsg->cmsg_type = SCM_RIGHTS;
369 369 cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
370 370 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
371 371 msgh.msg_controllen = cmsg->cmsg_len;
372 372 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0);
373 373 if (r < 0)
374 374 abortmsg("sendmsg failed (errno = %d)", errno);
375 375
376 376 handleresponse(hgc);
377 377 int32_t n;
378 378 if (ctx->datasize != sizeof(n))
379 379 abortmsg("unexpected size of attachio result");
380 380 memcpy(&n, ctx->data, sizeof(n));
381 381 n = ntohl(n);
382 382 if (n != sizeof(fds) / sizeof(fds[0]))
383 383 abortmsg("failed to send fds (n = %d)", n);
384 384 }
385 385
386 386 static void chdirtocwd(hgclient_t *hgc)
387 387 {
388 388 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize))
389 389 abortmsg("failed to getcwd (errno = %d)", errno);
390 390 hgc->ctx.datasize = strlen(hgc->ctx.data);
391 391 writeblockrequest(hgc, "chdir");
392 392 }
393 393
394 394 static void forwardumask(hgclient_t *hgc)
395 395 {
396 396 mode_t mask = umask(0);
397 397 umask(mask);
398 398
399 399 static const char command[] = "setumask\n";
400 400 sendall(hgc->sockfd, command, sizeof(command) - 1);
401 401 uint32_t data = htonl(mask);
402 402 sendall(hgc->sockfd, &data, sizeof(data));
403 403 }
404 404
405 405 /*!
406 406 * Open connection to per-user cmdserver
407 407 *
408 408 * If no background server running, returns NULL.
409 409 */
410 410 hgclient_t *hgc_open(const char *sockname)
411 411 {
412 412 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
413 413 if (fd < 0)
414 414 abortmsg("cannot create socket (errno = %d)", errno);
415 415
416 416 /* don't keep fd on fork(), so that it can be closed when the parent
417 417 * process get terminated. */
418 418 int flags = fcntl(fd, F_GETFD);
419 419 if (flags < 0)
420 420 abortmsg("cannot get flags of socket (errno = %d)", errno);
421 421 if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
422 422 abortmsg("cannot set flags of socket (errno = %d)", errno);
423 423
424 424 struct sockaddr_un addr;
425 425 addr.sun_family = AF_UNIX;
426 426 strncpy(addr.sun_path, sockname, sizeof(addr.sun_path));
427 427 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
428 428
429 429 debugmsg("connect to %s", addr.sun_path);
430 430 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
431 431 if (r < 0) {
432 432 close(fd);
433 433 if (errno == ENOENT || errno == ECONNREFUSED)
434 434 return NULL;
435 435 abortmsg("cannot connect to %s (errno = %d)",
436 436 addr.sun_path, errno);
437 437 }
438 438
439 439 hgclient_t *hgc = mallocx(sizeof(hgclient_t));
440 440 memset(hgc, 0, sizeof(*hgc));
441 441 hgc->sockfd = fd;
442 442 initcontext(&hgc->ctx);
443 443
444 444 readhello(hgc);
445 445 if (!(hgc->capflags & CAP_RUNCOMMAND))
446 446 abortmsg("insufficient capability: runcommand");
447 447 if (hgc->capflags & CAP_ATTACHIO)
448 448 attachio(hgc);
449 449 if (hgc->capflags & CAP_CHDIR)
450 450 chdirtocwd(hgc);
451 451 if (hgc->capflags & CAP_SETUMASK)
452 452 forwardumask(hgc);
453 453
454 454 return hgc;
455 455 }
456 456
457 457 /*!
458 458 * Close connection and free allocated memory
459 459 */
460 460 void hgc_close(hgclient_t *hgc)
461 461 {
462 462 assert(hgc);
463 463 freecontext(&hgc->ctx);
464 464 close(hgc->sockfd);
465 465 free(hgc);
466 466 }
467 467
468 468 pid_t hgc_peerpid(const hgclient_t *hgc)
469 469 {
470 470 assert(hgc);
471 471 return hgc->pid;
472 472 }
473 473
474 474 /*!
475 475 * Send command line arguments to let the server load the repo config and check
476 476 * whether it can process our request directly or not.
477 477 * Make sure hgc_setenv is called before calling this.
478 478 *
479 479 * @return - NULL, the server believes it can handle our request, or does not
480 480 * support "validate" command.
481 * - a list of strings, the server cannot handle our request and it
482 * sent instructions telling us how to fix the issue. See
481 * - a list of strings, the server probably cannot handle our request
482 * and it sent instructions telling us what to do next. See
483 483 * chgserver.py for possible instruction formats.
484 484 * the list should be freed by the caller.
485 485 * the last string is guaranteed to be NULL.
486 486 */
487 487 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
488 488 size_t argsize)
489 489 {
490 490 assert(hgc);
491 491 if (!(hgc->capflags & CAP_VALIDATE))
492 492 return NULL;
493 493
494 494 packcmdargs(&hgc->ctx, args, argsize);
495 495 writeblockrequest(hgc, "validate");
496 496 handleresponse(hgc);
497 497
498 498 /* the server returns '\0' if it can handle our request */
499 499 if (hgc->ctx.datasize <= 1)
500 500 return NULL;
501 501
502 502 /* make sure the buffer is '\0' terminated */
503 503 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
504 504 hgc->ctx.data[hgc->ctx.datasize] = '\0';
505 505 return unpackcmdargsnul(&hgc->ctx);
506 506 }
507 507
508 508 /*!
509 509 * Execute the specified Mercurial command
510 510 *
511 511 * @return result code
512 512 */
513 513 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize)
514 514 {
515 515 assert(hgc);
516 516
517 517 packcmdargs(&hgc->ctx, args, argsize);
518 518 writeblockrequest(hgc, "runcommand");
519 519 handleresponse(hgc);
520 520
521 521 int32_t exitcode_n;
522 522 if (hgc->ctx.datasize != sizeof(exitcode_n)) {
523 523 abortmsg("unexpected size of exitcode");
524 524 }
525 525 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n));
526 526 return ntohl(exitcode_n);
527 527 }
528 528
529 529 /*!
530 530 * (Re-)send client's stdio channels so that the server can access to tty
531 531 */
532 532 void hgc_attachio(hgclient_t *hgc)
533 533 {
534 534 assert(hgc);
535 535 if (!(hgc->capflags & CAP_ATTACHIO))
536 536 return;
537 537 attachio(hgc);
538 538 }
539 539
540 540 /*!
541 541 * Get pager command for the given Mercurial command args
542 542 *
543 543 * If no pager enabled, returns NULL. The return value becomes invalid
544 544 * once you run another request to hgc.
545 545 */
546 546 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
547 547 size_t argsize)
548 548 {
549 549 assert(hgc);
550 550
551 551 if (!(hgc->capflags & CAP_GETPAGER))
552 552 return NULL;
553 553
554 554 packcmdargs(&hgc->ctx, args, argsize);
555 555 writeblockrequest(hgc, "getpager");
556 556 handleresponse(hgc);
557 557
558 558 if (hgc->ctx.datasize < 1 || hgc->ctx.data[0] == '\0')
559 559 return NULL;
560 560 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
561 561 hgc->ctx.data[hgc->ctx.datasize] = '\0';
562 562 return hgc->ctx.data;
563 563 }
564 564
565 565 /*!
566 566 * Update server's environment variables
567 567 *
568 568 * @param envp list of environment variables in "NAME=VALUE" format,
569 569 * terminated by NULL.
570 570 */
571 571 void hgc_setenv(hgclient_t *hgc, const char *const envp[])
572 572 {
573 573 assert(hgc && envp);
574 574 if (!(hgc->capflags & CAP_SETENV))
575 575 return;
576 576 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1);
577 577 writeblockrequest(hgc, "setenv");
578 578 }
@@ -1,671 +1,678
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """command server extension for cHg (EXPERIMENTAL)
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'getpager' command
20 20 checks if pager is enabled and which pager should be executed
21 21
22 22 'setenv' command
23 23 replace os.environ completely
24 24
25 25 'setumask' command
26 26 set umask
27 27
28 28 'validate' command
29 29 reload the config and check if the server is up to date
30 30
31 31 Config
32 32 ------
33 33
34 34 ::
35 35
36 36 [chgserver]
37 37 idletimeout = 3600 # seconds, after which an idle server will exit
38 38 skiphash = False # whether to skip config or env change checks
39 39 """
40 40
41 41 from __future__ import absolute_import
42 42
43 43 import SocketServer
44 44 import errno
45 45 import inspect
46 46 import os
47 47 import re
48 48 import struct
49 49 import sys
50 50 import threading
51 51 import time
52 52 import traceback
53 53
54 54 from mercurial.i18n import _
55 55
56 56 from mercurial import (
57 57 cmdutil,
58 58 commands,
59 59 commandserver,
60 60 dispatch,
61 61 error,
62 62 extensions,
63 63 osutil,
64 64 util,
65 65 )
66 66
67 67 # Note for extension authors: ONLY specify testedwith = 'internal' for
68 68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
69 69 # be specifying the version(s) of Mercurial they are tested with, or
70 70 # leave the attribute unspecified.
71 71 testedwith = 'internal'
72 72
73 73 _log = commandserver.log
74 74
75 75 def _hashlist(items):
76 76 """return sha1 hexdigest for a list"""
77 77 return util.sha1(str(items)).hexdigest()
78 78
79 79 # sensitive config sections affecting confighash
80 80 _configsections = [
81 81 'extdiff', # uisetup will register new commands
82 82 'extensions',
83 83 ]
84 84
85 85 # sensitive environment variables affecting confighash
86 86 _envre = re.compile(r'''\A(?:
87 87 CHGHG
88 88 |HG.*
89 89 |LANG(?:UAGE)?
90 90 |LC_.*
91 91 |LD_.*
92 92 |PATH
93 93 |PYTHON.*
94 94 |TERM(?:INFO)?
95 95 |TZ
96 96 )\Z''', re.X)
97 97
98 98 def _confighash(ui):
99 99 """return a quick hash for detecting config/env changes
100 100
101 101 confighash is the hash of sensitive config items and environment variables.
102 102
103 103 for chgserver, it is designed that once confighash changes, the server is
104 104 not qualified to serve its client and should redirect the client to a new
105 105 server. different from mtimehash, confighash change will not mark the
106 106 server outdated and exit since the user can have different configs at the
107 107 same time.
108 108 """
109 109 sectionitems = []
110 110 for section in _configsections:
111 111 sectionitems.append(ui.configitems(section))
112 112 sectionhash = _hashlist(sectionitems)
113 113 envitems = [(k, v) for k, v in os.environ.iteritems() if _envre.match(k)]
114 114 envhash = _hashlist(sorted(envitems))
115 115 return sectionhash[:6] + envhash[:6]
116 116
117 117 def _getmtimepaths(ui):
118 118 """get a list of paths that should be checked to detect change
119 119
120 120 The list will include:
121 121 - extensions (will not cover all files for complex extensions)
122 122 - mercurial/__version__.py
123 123 - python binary
124 124 """
125 125 modules = [m for n, m in extensions.extensions(ui)]
126 126 try:
127 127 from mercurial import __version__
128 128 modules.append(__version__)
129 129 except ImportError:
130 130 pass
131 131 files = [sys.executable]
132 132 for m in modules:
133 133 try:
134 134 files.append(inspect.getabsfile(m))
135 135 except TypeError:
136 136 pass
137 137 return sorted(set(files))
138 138
139 139 def _mtimehash(paths):
140 140 """return a quick hash for detecting file changes
141 141
142 142 mtimehash calls stat on given paths and calculate a hash based on size and
143 143 mtime of each file. mtimehash does not read file content because reading is
144 144 expensive. therefore it's not 100% reliable for detecting content changes.
145 145 it's possible to return different hashes for same file contents.
146 146 it's also possible to return a same hash for different file contents for
147 147 some carefully crafted situation.
148 148
149 149 for chgserver, it is designed that once mtimehash changes, the server is
150 150 considered outdated immediately and should no longer provide service.
151 151 """
152 152 def trystat(path):
153 153 try:
154 154 st = os.stat(path)
155 155 return (st.st_mtime, st.st_size)
156 156 except OSError:
157 157 # could be ENOENT, EPERM etc. not fatal in any case
158 158 pass
159 159 return _hashlist(map(trystat, paths))[:12]
160 160
161 161 class hashstate(object):
162 162 """a structure storing confighash, mtimehash, paths used for mtimehash"""
163 163 def __init__(self, confighash, mtimehash, mtimepaths):
164 164 self.confighash = confighash
165 165 self.mtimehash = mtimehash
166 166 self.mtimepaths = mtimepaths
167 167
168 168 @staticmethod
169 169 def fromui(ui, mtimepaths=None):
170 170 if mtimepaths is None:
171 171 mtimepaths = _getmtimepaths(ui)
172 172 confighash = _confighash(ui)
173 173 mtimehash = _mtimehash(mtimepaths)
174 174 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
175 175 return hashstate(confighash, mtimehash, mtimepaths)
176 176
177 177 # copied from hgext/pager.py:uisetup()
178 178 def _setuppagercmd(ui, options, cmd):
179 179 if not ui.formatted():
180 180 return
181 181
182 182 p = ui.config("pager", "pager", os.environ.get("PAGER"))
183 183 usepager = False
184 184 always = util.parsebool(options['pager'])
185 185 auto = options['pager'] == 'auto'
186 186
187 187 if not p:
188 188 pass
189 189 elif always:
190 190 usepager = True
191 191 elif not auto:
192 192 usepager = False
193 193 else:
194 194 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
195 195 attend = ui.configlist('pager', 'attend', attended)
196 196 ignore = ui.configlist('pager', 'ignore')
197 197 cmds, _ = cmdutil.findcmd(cmd, commands.table)
198 198
199 199 for cmd in cmds:
200 200 var = 'attend-%s' % cmd
201 201 if ui.config('pager', var):
202 202 usepager = ui.configbool('pager', var)
203 203 break
204 204 if (cmd in attend or
205 205 (cmd not in ignore and not attend)):
206 206 usepager = True
207 207 break
208 208
209 209 if usepager:
210 210 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
211 211 ui.setconfig('ui', 'interactive', False, 'pager')
212 212 return p
213 213
214 214 _envvarre = re.compile(r'\$[a-zA-Z_]+')
215 215
216 216 def _clearenvaliases(cmdtable):
217 217 """Remove stale command aliases referencing env vars; variable expansion
218 218 is done at dispatch.addaliases()"""
219 219 for name, tab in cmdtable.items():
220 220 cmddef = tab[0]
221 221 if (isinstance(cmddef, dispatch.cmdalias) and
222 222 not cmddef.definition.startswith('!') and # shell alias
223 223 _envvarre.search(cmddef.definition)):
224 224 del cmdtable[name]
225 225
226 226 def _newchgui(srcui, csystem):
227 227 class chgui(srcui.__class__):
228 228 def __init__(self, src=None):
229 229 super(chgui, self).__init__(src)
230 230 if src:
231 231 self._csystem = getattr(src, '_csystem', csystem)
232 232 else:
233 233 self._csystem = csystem
234 234
235 235 def system(self, cmd, environ=None, cwd=None, onerr=None,
236 236 errprefix=None):
237 237 # copied from mercurial/util.py:system()
238 238 self.flush()
239 239 def py2shell(val):
240 240 if val is None or val is False:
241 241 return '0'
242 242 if val is True:
243 243 return '1'
244 244 return str(val)
245 245 env = os.environ.copy()
246 246 if environ:
247 247 env.update((k, py2shell(v)) for k, v in environ.iteritems())
248 248 env['HG'] = util.hgexecutable()
249 249 rc = self._csystem(cmd, env, cwd)
250 250 if rc and onerr:
251 251 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
252 252 util.explainexit(rc)[0])
253 253 if errprefix:
254 254 errmsg = '%s: %s' % (errprefix, errmsg)
255 255 raise onerr(errmsg)
256 256 return rc
257 257
258 258 return chgui(srcui)
259 259
260 260 def _renewui(srcui, args=None):
261 261 if not args:
262 262 args = []
263 263
264 264 newui = srcui.__class__()
265 265 for a in ['fin', 'fout', 'ferr', 'environ']:
266 266 setattr(newui, a, getattr(srcui, a))
267 267 if util.safehasattr(srcui, '_csystem'):
268 268 newui._csystem = srcui._csystem
269 269
270 270 # load wd and repo config, copied from dispatch.py
271 271 cwds = dispatch._earlygetopt(['--cwd'], args)
272 272 cwd = cwds and os.path.realpath(cwds[-1]) or None
273 273 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
274 274 path, newui = dispatch._getlocal(newui, rpath, wd=cwd)
275 275
276 276 # internal config: extensions.chgserver
277 277 # copy it. it can only be overrided from command line.
278 278 newui.setconfig('extensions', 'chgserver',
279 279 srcui.config('extensions', 'chgserver'), '--config')
280 280
281 281 # command line args
282 282 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
283 283
284 284 # stolen from tortoisehg.util.copydynamicconfig()
285 285 for section, name, value in srcui.walkconfig():
286 286 source = srcui.configsource(section, name)
287 287 if ':' in source or source == '--config':
288 288 # path:line or command line
289 289 continue
290 290 if source == 'none':
291 291 # ui.configsource returns 'none' by default
292 292 source = ''
293 293 newui.setconfig(section, name, value, source)
294 294 return newui
295 295
296 296 class channeledsystem(object):
297 297 """Propagate ui.system() request in the following format:
298 298
299 299 payload length (unsigned int),
300 300 cmd, '\0',
301 301 cwd, '\0',
302 302 envkey, '=', val, '\0',
303 303 ...
304 304 envkey, '=', val
305 305
306 306 and waits:
307 307
308 308 exitcode length (unsigned int),
309 309 exitcode (int)
310 310 """
311 311 def __init__(self, in_, out, channel):
312 312 self.in_ = in_
313 313 self.out = out
314 314 self.channel = channel
315 315
316 316 def __call__(self, cmd, environ, cwd):
317 317 args = [util.quotecommand(cmd), os.path.abspath(cwd or '.')]
318 318 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
319 319 data = '\0'.join(args)
320 320 self.out.write(struct.pack('>cI', self.channel, len(data)))
321 321 self.out.write(data)
322 322 self.out.flush()
323 323
324 324 length = self.in_.read(4)
325 325 length, = struct.unpack('>I', length)
326 326 if length != 4:
327 327 raise error.Abort(_('invalid response'))
328 328 rc, = struct.unpack('>i', self.in_.read(4))
329 329 return rc
330 330
331 331 _iochannels = [
332 332 # server.ch, ui.fp, mode
333 333 ('cin', 'fin', 'rb'),
334 334 ('cout', 'fout', 'wb'),
335 335 ('cerr', 'ferr', 'wb'),
336 336 ]
337 337
338 338 class chgcmdserver(commandserver.server):
339 339 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
340 340 super(chgcmdserver, self).__init__(
341 341 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
342 342 self.clientsock = sock
343 343 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
344 344 self.hashstate = hashstate
345 345 self.baseaddress = baseaddress
346 346 if hashstate is not None:
347 347 self.capabilities = self.capabilities.copy()
348 348 self.capabilities['validate'] = chgcmdserver.validate
349 349
350 350 def cleanup(self):
351 351 # dispatch._runcatch() does not flush outputs if exception is not
352 352 # handled by dispatch._dispatch()
353 353 self.ui.flush()
354 354 self._restoreio()
355 355
356 356 def attachio(self):
357 357 """Attach to client's stdio passed via unix domain socket; all
358 358 channels except cresult will no longer be used
359 359 """
360 360 # tell client to sendmsg() with 1-byte payload, which makes it
361 361 # distinctive from "attachio\n" command consumed by client.read()
362 362 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
363 363 clientfds = osutil.recvfds(self.clientsock.fileno())
364 364 _log('received fds: %r\n' % clientfds)
365 365
366 366 ui = self.ui
367 367 ui.flush()
368 368 first = self._saveio()
369 369 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
370 370 assert fd > 0
371 371 fp = getattr(ui, fn)
372 372 os.dup2(fd, fp.fileno())
373 373 os.close(fd)
374 374 if not first:
375 375 continue
376 376 # reset buffering mode when client is first attached. as we want
377 377 # to see output immediately on pager, the mode stays unchanged
378 378 # when client re-attached. ferr is unchanged because it should
379 379 # be unbuffered no matter if it is a tty or not.
380 380 if fn == 'ferr':
381 381 newfp = fp
382 382 else:
383 383 # make it line buffered explicitly because the default is
384 384 # decided on first write(), where fout could be a pager.
385 385 if fp.isatty():
386 386 bufsize = 1 # line buffered
387 387 else:
388 388 bufsize = -1 # system default
389 389 newfp = os.fdopen(fp.fileno(), mode, bufsize)
390 390 setattr(ui, fn, newfp)
391 391 setattr(self, cn, newfp)
392 392
393 393 self.cresult.write(struct.pack('>i', len(clientfds)))
394 394
395 395 def _saveio(self):
396 396 if self._oldios:
397 397 return False
398 398 ui = self.ui
399 399 for cn, fn, _mode in _iochannels:
400 400 ch = getattr(self, cn)
401 401 fp = getattr(ui, fn)
402 402 fd = os.dup(fp.fileno())
403 403 self._oldios.append((ch, fp, fd))
404 404 return True
405 405
406 406 def _restoreio(self):
407 407 ui = self.ui
408 408 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
409 409 newfp = getattr(ui, fn)
410 410 # close newfp while it's associated with client; otherwise it
411 411 # would be closed when newfp is deleted
412 412 if newfp is not fp:
413 413 newfp.close()
414 414 # restore original fd: fp is open again
415 415 os.dup2(fd, fp.fileno())
416 416 os.close(fd)
417 417 setattr(self, cn, ch)
418 418 setattr(ui, fn, fp)
419 419 del self._oldios[:]
420 420
421 421 def validate(self):
422 422 """Reload the config and check if the server is up to date
423 423
424 424 Read a list of '\0' separated arguments.
425 425 Write a non-empty list of '\0' separated instruction strings or '\0'
426 426 if the list is empty.
427 427 An instruction string could be either:
428 428 - "unlink $path", the client should unlink the path to stop the
429 429 outdated server.
430 - "redirect $path", the client should try to connect to another
431 server instead.
430 - "redirect $path", the client should attempt to connect to $path
431 first. If it does not work, start a new server. It implies
432 "reconnect".
432 433 - "exit $n", the client should exit directly with code n.
433 434 This may happen if we cannot parse the config.
435 - "reconnect", the client should close the connection and
436 reconnect.
437 If neither "reconnect" nor "redirect" is included in the instruction
438 list, the client can continue with this server after completing all
439 the instructions.
434 440 """
435 441 args = self._readlist()
436 442 try:
437 443 self.ui = _renewui(self.ui, args)
438 444 except error.ParseError as inst:
439 445 dispatch._formatparse(self.ui.warn, inst)
440 446 self.ui.flush()
441 447 self.cresult.write('exit 255')
442 448 return
443 449 newhash = hashstate.fromui(self.ui, self.hashstate.mtimepaths)
444 450 insts = []
445 451 if newhash.mtimehash != self.hashstate.mtimehash:
446 452 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
447 453 insts.append('unlink %s' % addr)
454 insts.append('reconnect')
448 455 if newhash.confighash != self.hashstate.confighash:
449 456 addr = _hashaddress(self.baseaddress, newhash.confighash)
450 457 insts.append('redirect %s' % addr)
451 458 _log('validate: %s\n' % insts)
452 459 self.cresult.write('\0'.join(insts) or '\0')
453 460
454 461 def chdir(self):
455 462 """Change current directory
456 463
457 464 Note that the behavior of --cwd option is bit different from this.
458 465 It does not affect --config parameter.
459 466 """
460 467 path = self._readstr()
461 468 if not path:
462 469 return
463 470 _log('chdir to %r\n' % path)
464 471 os.chdir(path)
465 472
466 473 def setumask(self):
467 474 """Change umask"""
468 475 mask = struct.unpack('>I', self._read(4))[0]
469 476 _log('setumask %r\n' % mask)
470 477 os.umask(mask)
471 478
472 479 def getpager(self):
473 480 """Read cmdargs and write pager command to r-channel if enabled
474 481
475 482 If pager isn't enabled, this writes '\0' because channeledoutput
476 483 does not allow to write empty data.
477 484 """
478 485 args = self._readlist()
479 486 try:
480 487 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
481 488 args)
482 489 except (error.Abort, error.AmbiguousCommand, error.CommandError,
483 490 error.UnknownCommand):
484 491 cmd = None
485 492 options = {}
486 493 if not cmd or 'pager' not in options:
487 494 self.cresult.write('\0')
488 495 return
489 496
490 497 pagercmd = _setuppagercmd(self.ui, options, cmd)
491 498 if pagercmd:
492 499 self.cresult.write(pagercmd)
493 500 else:
494 501 self.cresult.write('\0')
495 502
496 503 def setenv(self):
497 504 """Clear and update os.environ
498 505
499 506 Note that not all variables can make an effect on the running process.
500 507 """
501 508 l = self._readlist()
502 509 try:
503 510 newenv = dict(s.split('=', 1) for s in l)
504 511 except ValueError:
505 512 raise ValueError('unexpected value in setenv request')
506 513
507 514 diffkeys = set(k for k in set(os.environ.keys() + newenv.keys())
508 515 if os.environ.get(k) != newenv.get(k))
509 516 _log('change env: %r\n' % sorted(diffkeys))
510 517
511 518 os.environ.clear()
512 519 os.environ.update(newenv)
513 520
514 521 if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys:
515 522 # reload config so that ui.plain() takes effect
516 523 self.ui = _renewui(self.ui)
517 524
518 525 _clearenvaliases(commands.table)
519 526
520 527 capabilities = commandserver.server.capabilities.copy()
521 528 capabilities.update({'attachio': attachio,
522 529 'chdir': chdir,
523 530 'getpager': getpager,
524 531 'setenv': setenv,
525 532 'setumask': setumask})
526 533
527 534 # copied from mercurial/commandserver.py
528 535 class _requesthandler(SocketServer.StreamRequestHandler):
529 536 def handle(self):
530 537 # use a different process group from the master process, making this
531 538 # process pass kernel "is_current_pgrp_orphaned" check so signals like
532 539 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
533 540 os.setpgid(0, 0)
534 541 ui = self.server.ui
535 542 repo = self.server.repo
536 543 sv = None
537 544 try:
538 545 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection,
539 546 self.server.hashstate, self.server.baseaddress)
540 547 try:
541 548 sv.serve()
542 549 # handle exceptions that may be raised by command server. most of
543 550 # known exceptions are caught by dispatch.
544 551 except error.Abort as inst:
545 552 ui.warn(_('abort: %s\n') % inst)
546 553 except IOError as inst:
547 554 if inst.errno != errno.EPIPE:
548 555 raise
549 556 except KeyboardInterrupt:
550 557 pass
551 558 finally:
552 559 sv.cleanup()
553 560 except: # re-raises
554 561 # also write traceback to error channel. otherwise client cannot
555 562 # see it because it is written to server's stderr by default.
556 563 if sv:
557 564 cerr = sv.cerr
558 565 else:
559 566 cerr = commandserver.channeledoutput(self.wfile, 'e')
560 567 traceback.print_exc(file=cerr)
561 568 raise
562 569
563 570 def _tempaddress(address):
564 571 return '%s.%d.tmp' % (address, os.getpid())
565 572
566 573 def _hashaddress(address, hashstr):
567 574 return '%s-%s' % (address, hashstr)
568 575
569 576 class AutoExitMixIn: # use old-style to comply with SocketServer design
570 577 lastactive = time.time()
571 578 idletimeout = 3600 # default 1 hour
572 579
573 580 def startautoexitthread(self):
574 581 # note: the auto-exit check here is cheap enough to not use a thread,
575 582 # be done in serve_forever. however SocketServer is hook-unfriendly,
576 583 # you simply cannot hook serve_forever without copying a lot of code.
577 584 # besides, serve_forever's docstring suggests using thread.
578 585 thread = threading.Thread(target=self._autoexitloop)
579 586 thread.daemon = True
580 587 thread.start()
581 588
582 589 def _autoexitloop(self, interval=1):
583 590 while True:
584 591 time.sleep(interval)
585 592 if not self.issocketowner():
586 593 _log('%s is not owned, exiting.\n' % self.server_address)
587 594 break
588 595 if time.time() - self.lastactive > self.idletimeout:
589 596 _log('being idle too long. exiting.\n')
590 597 break
591 598 self.shutdown()
592 599
593 600 def process_request(self, request, address):
594 601 self.lastactive = time.time()
595 602 return SocketServer.ForkingMixIn.process_request(
596 603 self, request, address)
597 604
598 605 def server_bind(self):
599 606 # use a unique temp address so we can stat the file and do ownership
600 607 # check later
601 608 tempaddress = _tempaddress(self.server_address)
602 609 self.socket.bind(tempaddress)
603 610 self._socketstat = os.stat(tempaddress)
604 611 # rename will replace the old socket file if exists atomically. the
605 612 # old server will detect ownership change and exit.
606 613 util.rename(tempaddress, self.server_address)
607 614
608 615 def issocketowner(self):
609 616 try:
610 617 stat = os.stat(self.server_address)
611 618 return (stat.st_ino == self._socketstat.st_ino and
612 619 stat.st_mtime == self._socketstat.st_mtime)
613 620 except OSError:
614 621 return False
615 622
616 623 def unlinksocketfile(self):
617 624 if not self.issocketowner():
618 625 return
619 626 # it is possible to have a race condition here that we may
620 627 # remove another server's socket file. but that's okay
621 628 # since that server will detect and exit automatically and
622 629 # the client will start a new server on demand.
623 630 try:
624 631 os.unlink(self.server_address)
625 632 except OSError as exc:
626 633 if exc.errno != errno.ENOENT:
627 634 raise
628 635
629 636 class chgunixservice(commandserver.unixservice):
630 637 def init(self):
631 638 self._inithashstate()
632 639 class cls(AutoExitMixIn, SocketServer.ForkingMixIn,
633 640 SocketServer.UnixStreamServer):
634 641 ui = self.ui
635 642 repo = self.repo
636 643 hashstate = self.hashstate
637 644 baseaddress = self.baseaddress
638 645 self.server = cls(self.address, _requesthandler)
639 646 self.server.idletimeout = self.ui.configint(
640 647 'chgserver', 'idletimeout', self.server.idletimeout)
641 648 self.server.startautoexitthread()
642 649 self._createsymlink()
643 650
644 651 def _inithashstate(self):
645 652 self.baseaddress = self.address
646 653 if self.ui.configbool('chgserver', 'skiphash', False):
647 654 self.hashstate = None
648 655 return
649 656 self.hashstate = hashstate.fromui(self.ui)
650 657 self.address = _hashaddress(self.address, self.hashstate.confighash)
651 658
652 659 def _createsymlink(self):
653 660 if self.baseaddress == self.address:
654 661 return
655 662 tempaddress = _tempaddress(self.baseaddress)
656 663 os.symlink(os.path.basename(self.address), tempaddress)
657 664 util.rename(tempaddress, self.baseaddress)
658 665
659 666 def run(self):
660 667 try:
661 668 self.server.serve_forever()
662 669 finally:
663 670 self.server.unlinksocketfile()
664 671
665 672 def uisetup(ui):
666 673 commandserver._servicemap['chgunix'] = chgunixservice
667 674
668 675 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
669 676 # start another chg. drop it to avoid possible side effects.
670 677 if 'CHGINTERNALMARK' in os.environ:
671 678 del os.environ['CHGINTERNALMARK']
General Comments 0
You need to be logged in to leave comments. Login now