##// END OF EJS Templates
chg: remove manual reload logic...
Jun Wu -
r28454:80628698 default
parent child Browse files
Show More
@@ -1,32 +1,30 b''
1 1 cHg
2 2 ===
3 3
4 4 A fast client for Mercurial command server running on Unix.
5 5
6 6 Install:
7 7
8 8 $ make
9 9 $ make install
10 10
11 11 Usage:
12 12
13 13 $ chg help # show help of Mercurial
14 14 $ alias hg=chg # replace hg command
15 15 $ chg --kill-chg-daemon # terminate background server
16 $ chg --reload-chg-daemon # reload configuration files
17 16
18 17 Environment variables:
19 18
20 19 Although cHg tries to update environment variables, some of them cannot be
21 20 changed after spawning the server. The following variables are specially
22 21 handled:
23 22
24 * configuration files are reloaded if HGPLAIN or HGPLAINEXCEPT changed, but
25 some behaviors won't change correctly.
23 * configuration files are reloaded automatically by default.
26 24 * CHGHG or HG specifies the path to the hg executable spawned as the
27 25 background command server.
28 26
29 27 The following variables are available for testing:
30 28
31 29 * CHGDEBUG enables debug messages.
32 30 * CHGSOCKNAME specifies the socket path of the background cmdserver.
@@ -1,44 +1,41 b''
1 1 .\" Hey, EMACS: -*- nroff -*-
2 2 .\" First parameter, NAME, should be all caps
3 3 .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
4 4 .\" other parameters are allowed: see man(7), man(1)
5 5 .TH CHG 1 "March 3, 2013"
6 6 .\" Please adjust this date whenever revising the manpage.
7 7 .\"
8 8 .\" Some roff macros, for reference:
9 9 .\" .nh disable hyphenation
10 10 .\" .hy enable hyphenation
11 11 .\" .ad l left justify
12 12 .\" .ad b justify to both left and right margins
13 13 .\" .nf disable filling
14 14 .\" .fi enable filling
15 15 .\" .br insert line break
16 16 .\" .sp <n> insert n+1 empty lines
17 17 .\" for manpage-specific macros, see man(7)
18 18 .SH NAME
19 19 chg \- a fast client for Mercurial command server
20 20 .SH SYNOPSIS
21 21 .B chg
22 22 .IR command " [" options "] [" arguments "]..."
23 23 .br
24 24 .SH DESCRIPTION
25 25 The
26 26 .B chg
27 27 command is the wrapper for
28 28 .B hg
29 29 command.
30 30 It uses the Mercurial command server to reduce start-up overhead.
31 31 .SH OPTIONS
32 32 This program accepts the same command line syntax as the
33 33 .B hg
34 34 command. Additionally it accepts the following options.
35 35 .TP
36 36 .B \-\-kill\-chg\-daemon
37 37 Terminate the background command servers.
38 .TP
39 .B \-\-reload\-chg\-daemon
40 Reload configuration files.
41 38 .SH SEE ALSO
42 39 .BR hg (1),
43 40 .SH AUTHOR
44 41 Written by Yuya Nishihara <yuya@tcha.org>.
@@ -1,578 +1,576 b''
1 1 /*
2 2 * A fast client for Mercurial command server
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #include <assert.h>
11 11 #include <errno.h>
12 12 #include <fcntl.h>
13 13 #include <signal.h>
14 14 #include <stdio.h>
15 15 #include <stdlib.h>
16 16 #include <string.h>
17 17 #include <sys/file.h>
18 18 #include <sys/stat.h>
19 19 #include <sys/types.h>
20 20 #include <sys/un.h>
21 21 #include <sys/wait.h>
22 22 #include <time.h>
23 23 #include <unistd.h>
24 24
25 25 #include "hgclient.h"
26 26 #include "util.h"
27 27
28 28 #ifndef UNIX_PATH_MAX
29 29 #define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)NULL)->sun_path))
30 30 #endif
31 31
32 32 struct cmdserveropts {
33 33 char sockname[UNIX_PATH_MAX];
34 34 char redirectsockname[UNIX_PATH_MAX];
35 35 char lockfile[UNIX_PATH_MAX];
36 36 char pidfile[UNIX_PATH_MAX];
37 37 size_t argsize;
38 38 const char **args;
39 39 int lockfd;
40 40 };
41 41
42 42 static void initcmdserveropts(struct cmdserveropts *opts) {
43 43 memset(opts, 0, sizeof(struct cmdserveropts));
44 44 opts->lockfd = -1;
45 45 }
46 46
47 47 static void freecmdserveropts(struct cmdserveropts *opts) {
48 48 free(opts->args);
49 49 opts->args = NULL;
50 50 opts->argsize = 0;
51 51 }
52 52
53 53 /*
54 54 * Test if an argument is a sensitive flag that should be passed to the server.
55 55 * Return 0 if not, otherwise the number of arguments starting from the current
56 56 * one that should be passed to the server.
57 57 */
58 58 static size_t testsensitiveflag(const char *arg)
59 59 {
60 60 static const struct {
61 61 const char *name;
62 62 size_t narg;
63 63 } flags[] = {
64 64 {"--config", 1},
65 65 {"--cwd", 1},
66 66 {"--repo", 1},
67 67 {"--repository", 1},
68 68 {"--traceback", 0},
69 69 {"-R", 1},
70 70 };
71 71 size_t i;
72 72 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
73 73 size_t len = strlen(flags[i].name);
74 74 size_t narg = flags[i].narg;
75 75 if (memcmp(arg, flags[i].name, len) == 0) {
76 76 if (arg[len] == '\0') { /* --flag (value) */
77 77 return narg + 1;
78 78 } else if (arg[len] == '=' && narg > 0) { /* --flag=value */
79 79 return 1;
80 80 } else if (flags[i].name[1] != '-') { /* short flag */
81 81 return 1;
82 82 }
83 83 }
84 84 }
85 85 return 0;
86 86 }
87 87
88 88 /*
89 89 * Parse argv[] and put sensitive flags to opts->args
90 90 */
91 91 static void setcmdserverargs(struct cmdserveropts *opts,
92 92 int argc, const char *argv[])
93 93 {
94 94 size_t i, step;
95 95 opts->argsize = 0;
96 96 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
97 97 if (!argv[i])
98 98 continue; /* pass clang-analyse */
99 99 if (strcmp(argv[i], "--") == 0)
100 100 break;
101 101 size_t n = testsensitiveflag(argv[i]);
102 102 if (n == 0 || i + n > (size_t)argc)
103 103 continue;
104 104 opts->args = reallocx(opts->args,
105 105 (n + opts->argsize) * sizeof(char *));
106 106 memcpy(opts->args + opts->argsize, argv + i,
107 107 sizeof(char *) * n);
108 108 opts->argsize += n;
109 109 step = n;
110 110 }
111 111 }
112 112
113 113 static void preparesockdir(const char *sockdir)
114 114 {
115 115 int r;
116 116 r = mkdir(sockdir, 0700);
117 117 if (r < 0 && errno != EEXIST)
118 118 abortmsg("cannot create sockdir %s (errno = %d)",
119 119 sockdir, errno);
120 120
121 121 struct stat st;
122 122 r = lstat(sockdir, &st);
123 123 if (r < 0)
124 124 abortmsg("cannot stat %s (errno = %d)", sockdir, errno);
125 125 if (!S_ISDIR(st.st_mode))
126 126 abortmsg("cannot create sockdir %s (file exists)", sockdir);
127 127 if (st.st_uid != geteuid() || st.st_mode & 0077)
128 128 abortmsg("insecure sockdir %s", sockdir);
129 129 }
130 130
131 131 static void setcmdserveropts(struct cmdserveropts *opts)
132 132 {
133 133 int r;
134 134 char sockdir[UNIX_PATH_MAX];
135 135 const char *envsockname = getenv("CHGSOCKNAME");
136 136 if (!envsockname) {
137 137 /* by default, put socket file in secure directory
138 138 * (permission of socket file may be ignored on some Unices) */
139 139 const char *tmpdir = getenv("TMPDIR");
140 140 if (!tmpdir)
141 141 tmpdir = "/tmp";
142 142 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d",
143 143 tmpdir, geteuid());
144 144 if (r < 0 || (size_t)r >= sizeof(sockdir))
145 145 abortmsg("too long TMPDIR (r = %d)", r);
146 146 preparesockdir(sockdir);
147 147 }
148 148
149 149 const char *basename = (envsockname) ? envsockname : sockdir;
150 150 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
151 151 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
152 152 const char *pidfmt = (envsockname) ? "%s.pid" : "%s/pid";
153 153 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
154 154 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
155 155 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
156 156 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
157 157 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
158 158 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
159 159 r = snprintf(opts->pidfile, sizeof(opts->pidfile), pidfmt, basename);
160 160 if (r < 0 || (size_t)r >= sizeof(opts->pidfile))
161 161 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
162 162 }
163 163
164 164 /*
165 165 * Acquire a file lock that indicates a client is trying to start and connect
166 166 * to a server, before executing a command. The lock is released upon exit or
167 167 * explicit unlock. Will block if the lock is held by another process.
168 168 */
169 169 static void lockcmdserver(struct cmdserveropts *opts)
170 170 {
171 171 if (opts->lockfd == -1) {
172 172 opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
173 173 if (opts->lockfd == -1)
174 174 abortmsg("cannot create lock file %s", opts->lockfile);
175 175 }
176 176 int r = flock(opts->lockfd, LOCK_EX);
177 177 if (r == -1)
178 178 abortmsg("cannot acquire lock");
179 179 }
180 180
181 181 /*
182 182 * Release the file lock held by calling lockcmdserver. Will do nothing if
183 183 * lockcmdserver is not called.
184 184 */
185 185 static void unlockcmdserver(struct cmdserveropts *opts)
186 186 {
187 187 if (opts->lockfd == -1)
188 188 return;
189 189 flock(opts->lockfd, LOCK_UN);
190 190 close(opts->lockfd);
191 191 opts->lockfd = -1;
192 192 }
193 193
194 194 static const char *gethgcmd(void)
195 195 {
196 196 static const char *hgcmd = NULL;
197 197 if (!hgcmd) {
198 198 hgcmd = getenv("CHGHG");
199 199 if (!hgcmd || hgcmd[0] == '\0')
200 200 hgcmd = getenv("HG");
201 201 if (!hgcmd || hgcmd[0] == '\0')
202 202 hgcmd = "hg";
203 203 }
204 204 return hgcmd;
205 205 }
206 206
207 207 static void execcmdserver(const struct cmdserveropts *opts)
208 208 {
209 209 const char *hgcmd = gethgcmd();
210 210
211 211 const char *baseargv[] = {
212 212 hgcmd,
213 213 "serve",
214 214 "--cmdserver", "chgunix",
215 215 "--address", opts->sockname,
216 216 "--daemon-postexec", "chdir:/",
217 217 "--pid-file", opts->pidfile,
218 218 "--config", "extensions.chgserver=",
219 219 };
220 220 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
221 221 size_t argsize = baseargvsize + opts->argsize + 1;
222 222
223 223 const char **argv = mallocx(sizeof(char *) * argsize);
224 224 memcpy(argv, baseargv, sizeof(baseargv));
225 225 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
226 226 argv[argsize - 1] = NULL;
227 227
228 228 if (putenv("CHGINTERNALMARK=") != 0)
229 229 abortmsg("failed to putenv (errno = %d)", errno);
230 230 if (execvp(hgcmd, (char **)argv) < 0)
231 231 abortmsg("failed to exec cmdserver (errno = %d)", errno);
232 232 free(argv);
233 233 }
234 234
235 235 /* Retry until we can connect to the server. Give up after some time. */
236 236 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
237 237 {
238 238 static const struct timespec sleepreq = {0, 10 * 1000000};
239 239 int pst = 0;
240 240
241 241 for (unsigned int i = 0; i < 10 * 100; i++) {
242 242 hgclient_t *hgc = hgc_open(opts->sockname);
243 243 if (hgc)
244 244 return hgc;
245 245
246 246 if (pid > 0) {
247 247 /* collect zombie if child process fails to start */
248 248 int r = waitpid(pid, &pst, WNOHANG);
249 249 if (r != 0)
250 250 goto cleanup;
251 251 }
252 252
253 253 nanosleep(&sleepreq, NULL);
254 254 }
255 255
256 256 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
257 257 return NULL;
258 258
259 259 cleanup:
260 260 if (WIFEXITED(pst)) {
261 261 abortmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
262 262 } else if (WIFSIGNALED(pst)) {
263 263 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
264 264 } else {
265 265 abortmsg("error white waiting cmdserver");
266 266 }
267 267 return NULL;
268 268 }
269 269
270 270 /* Connect to a cmdserver. Will start a new server on demand. */
271 271 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
272 272 {
273 273 const char *sockname = opts->redirectsockname[0] ?
274 274 opts->redirectsockname : opts->sockname;
275 275 hgclient_t *hgc = hgc_open(sockname);
276 276 if (hgc)
277 277 return hgc;
278 278
279 279 lockcmdserver(opts);
280 280 hgc = hgc_open(sockname);
281 281 if (hgc) {
282 282 unlockcmdserver(opts);
283 283 debugmsg("cmdserver is started by another process");
284 284 return hgc;
285 285 }
286 286
287 287 /* prevent us from being connected to an outdated server: we were
288 288 * told by a server to redirect to opts->redirectsockname and that
289 289 * address does not work. we do not want to connect to the server
290 290 * again because it will probably tell us the same thing. */
291 291 if (sockname == opts->redirectsockname)
292 292 unlink(opts->sockname);
293 293
294 294 debugmsg("start cmdserver at %s", opts->sockname);
295 295
296 296 pid_t pid = fork();
297 297 if (pid < 0)
298 298 abortmsg("failed to fork cmdserver process");
299 299 if (pid == 0) {
300 300 /* do not leak lockfd to hg */
301 301 close(opts->lockfd);
302 302 /* bypass uisetup() of pager extension */
303 303 int nullfd = open("/dev/null", O_WRONLY);
304 304 if (nullfd >= 0) {
305 305 dup2(nullfd, fileno(stdout));
306 306 close(nullfd);
307 307 }
308 308 execcmdserver(opts);
309 309 } else {
310 310 hgc = retryconnectcmdserver(opts, pid);
311 311 }
312 312
313 313 unlockcmdserver(opts);
314 314 return hgc;
315 315 }
316 316
317 317 static void killcmdserver(const struct cmdserveropts *opts, int sig)
318 318 {
319 319 FILE *fp = fopen(opts->pidfile, "r");
320 320 if (!fp)
321 321 abortmsg("cannot open %s (errno = %d)", opts->pidfile, errno);
322 322 int pid = 0;
323 323 int n = fscanf(fp, "%d", &pid);
324 324 fclose(fp);
325 325 if (n != 1 || pid <= 0)
326 326 abortmsg("cannot read pid from %s", opts->pidfile);
327 327
328 328 if (kill((pid_t)pid, sig) < 0) {
329 329 if (errno == ESRCH)
330 330 return;
331 331 abortmsg("cannot kill %d (errno = %d)", pid, errno);
332 332 }
333 333 }
334 334
335 335 static pid_t peerpid = 0;
336 336
337 337 static void forwardsignal(int sig)
338 338 {
339 339 assert(peerpid > 0);
340 340 if (kill(peerpid, sig) < 0)
341 341 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
342 342 debugmsg("forward signal %d", sig);
343 343 }
344 344
345 345 static void handlestopsignal(int sig)
346 346 {
347 347 sigset_t unblockset, oldset;
348 348 struct sigaction sa, oldsa;
349 349 if (sigemptyset(&unblockset) < 0)
350 350 goto error;
351 351 if (sigaddset(&unblockset, sig) < 0)
352 352 goto error;
353 353 memset(&sa, 0, sizeof(sa));
354 354 sa.sa_handler = SIG_DFL;
355 355 sa.sa_flags = SA_RESTART;
356 356 if (sigemptyset(&sa.sa_mask) < 0)
357 357 goto error;
358 358
359 359 forwardsignal(sig);
360 360 if (raise(sig) < 0) /* resend to self */
361 361 goto error;
362 362 if (sigaction(sig, &sa, &oldsa) < 0)
363 363 goto error;
364 364 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
365 365 goto error;
366 366 /* resent signal will be handled before sigprocmask() returns */
367 367 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
368 368 goto error;
369 369 if (sigaction(sig, &oldsa, NULL) < 0)
370 370 goto error;
371 371 return;
372 372
373 373 error:
374 374 abortmsg("failed to handle stop signal (errno = %d)", errno);
375 375 }
376 376
377 377 static void setupsignalhandler(pid_t pid)
378 378 {
379 379 if (pid <= 0)
380 380 return;
381 381 peerpid = pid;
382 382
383 383 struct sigaction sa;
384 384 memset(&sa, 0, sizeof(sa));
385 385 sa.sa_handler = forwardsignal;
386 386 sa.sa_flags = SA_RESTART;
387 387 if (sigemptyset(&sa.sa_mask) < 0)
388 388 goto error;
389 389
390 390 if (sigaction(SIGHUP, &sa, NULL) < 0)
391 391 goto error;
392 392 if (sigaction(SIGINT, &sa, NULL) < 0)
393 393 goto error;
394 394
395 395 /* terminate frontend by double SIGTERM in case of server freeze */
396 396 sa.sa_flags |= SA_RESETHAND;
397 397 if (sigaction(SIGTERM, &sa, NULL) < 0)
398 398 goto error;
399 399
400 400 /* propagate job control requests to worker */
401 401 sa.sa_handler = forwardsignal;
402 402 sa.sa_flags = SA_RESTART;
403 403 if (sigaction(SIGCONT, &sa, NULL) < 0)
404 404 goto error;
405 405 sa.sa_handler = handlestopsignal;
406 406 sa.sa_flags = SA_RESTART;
407 407 if (sigaction(SIGTSTP, &sa, NULL) < 0)
408 408 goto error;
409 409
410 410 return;
411 411
412 412 error:
413 413 abortmsg("failed to set up signal handlers (errno = %d)", errno);
414 414 }
415 415
416 416 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
417 417 static void setuppager(hgclient_t *hgc, const char *const args[],
418 418 size_t argsize)
419 419 {
420 420 const char *pagercmd = hgc_getpager(hgc, args, argsize);
421 421 if (!pagercmd)
422 422 return;
423 423
424 424 int pipefds[2];
425 425 if (pipe(pipefds) < 0)
426 426 return;
427 427 pid_t pid = fork();
428 428 if (pid < 0)
429 429 goto error;
430 430 if (pid == 0) {
431 431 close(pipefds[0]);
432 432 if (dup2(pipefds[1], fileno(stdout)) < 0)
433 433 goto error;
434 434 if (isatty(fileno(stderr))) {
435 435 if (dup2(pipefds[1], fileno(stderr)) < 0)
436 436 goto error;
437 437 }
438 438 close(pipefds[1]);
439 439 hgc_attachio(hgc); /* reattach to pager */
440 440 return;
441 441 } else {
442 442 dup2(pipefds[0], fileno(stdin));
443 443 close(pipefds[0]);
444 444 close(pipefds[1]);
445 445
446 446 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
447 447 if (r < 0) {
448 448 abortmsg("cannot start pager '%s' (errno = %d)",
449 449 pagercmd, errno);
450 450 }
451 451 return;
452 452 }
453 453
454 454 error:
455 455 close(pipefds[0]);
456 456 close(pipefds[1]);
457 457 abortmsg("failed to prepare pager (errno = %d)", errno);
458 458 }
459 459
460 460 /* Run instructions sent from the server like unlink and set redirect path */
461 461 static void runinstructions(struct cmdserveropts *opts, const char **insts)
462 462 {
463 463 assert(insts);
464 464 opts->redirectsockname[0] = '\0';
465 465 const char **pinst;
466 466 for (pinst = insts; *pinst; pinst++) {
467 467 debugmsg("instruction: %s", *pinst);
468 468 if (strncmp(*pinst, "unlink ", 7) == 0) {
469 469 unlink(*pinst + 7);
470 470 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
471 471 int r = snprintf(opts->redirectsockname,
472 472 sizeof(opts->redirectsockname),
473 473 "%s", *pinst + 9);
474 474 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
475 475 abortmsg("redirect path is too long (%d)", r);
476 476 } else {
477 477 abortmsg("unknown instruction: %s", *pinst);
478 478 }
479 479 }
480 480 }
481 481
482 482 /*
483 483 * Test whether the command is unsupported or not. This is not designed to
484 484 * cover all cases. But it's fast, does not depend on the server and does
485 485 * not return false positives.
486 486 */
487 487 static int isunsupported(int argc, const char *argv[])
488 488 {
489 489 enum {
490 490 SERVE = 1,
491 491 DAEMON = 2,
492 492 SERVEDAEMON = SERVE | DAEMON,
493 493 TIME = 4,
494 494 };
495 495 unsigned int state = 0;
496 496 int i;
497 497 for (i = 0; i < argc; ++i) {
498 498 if (strcmp(argv[i], "--") == 0)
499 499 break;
500 500 if (i == 0 && strcmp("serve", argv[i]) == 0)
501 501 state |= SERVE;
502 502 else if (strcmp("-d", argv[i]) == 0 ||
503 503 strcmp("--daemon", argv[i]) == 0)
504 504 state |= DAEMON;
505 505 else if (strcmp("--time", argv[i]) == 0)
506 506 state |= TIME;
507 507 }
508 508 return (state & TIME) == TIME ||
509 509 (state & SERVEDAEMON) == SERVEDAEMON;
510 510 }
511 511
512 512 static void execoriginalhg(const char *argv[])
513 513 {
514 514 debugmsg("execute original hg");
515 515 if (execvp(gethgcmd(), (char **)argv) < 0)
516 516 abortmsg("failed to exec original hg (errno = %d)", errno);
517 517 }
518 518
519 519 int main(int argc, const char *argv[], const char *envp[])
520 520 {
521 521 if (getenv("CHGDEBUG"))
522 522 enabledebugmsg();
523 523
524 524 if (getenv("CHGINTERNALMARK"))
525 525 abortmsg("chg started by chg detected.\n"
526 526 "Please make sure ${HG:-hg} is not a symlink or "
527 527 "wrapper to chg. Alternatively, set $CHGHG to the "
528 528 "path of real hg.");
529 529
530 530 if (isunsupported(argc - 1, argv + 1))
531 531 execoriginalhg(argv);
532 532
533 533 struct cmdserveropts opts;
534 534 initcmdserveropts(&opts);
535 535 setcmdserveropts(&opts);
536 536 setcmdserverargs(&opts, argc, argv);
537 537
538 538 if (argc == 2) {
539 539 int sig = 0;
540 540 if (strcmp(argv[1], "--kill-chg-daemon") == 0)
541 541 sig = SIGTERM;
542 if (strcmp(argv[1], "--reload-chg-daemon") == 0)
543 sig = SIGHUP;
544 542 if (sig > 0) {
545 543 killcmdserver(&opts, sig);
546 544 return 0;
547 545 }
548 546 }
549 547
550 548 hgclient_t *hgc;
551 549 size_t retry = 0;
552 550 while (1) {
553 551 hgc = connectcmdserver(&opts);
554 552 if (!hgc)
555 553 abortmsg("cannot open hg client");
556 554 hgc_setenv(hgc, envp);
557 555 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
558 556 if (insts == NULL)
559 557 break;
560 558 runinstructions(&opts, insts);
561 559 free(insts);
562 560 hgc_close(hgc);
563 561 if (++retry > 10)
564 562 abortmsg("too many redirections.\n"
565 563 "Please make sure %s is not a wrapper which "
566 564 "changes sensitive environment variables "
567 565 "before executing hg. If you have to use a "
568 566 "wrapper, wrap chg instead of hg.",
569 567 gethgcmd());
570 568 }
571 569
572 570 setupsignalhandler(hgc_peerpid(hgc));
573 571 setuppager(hgc, argv + 1, argc - 1);
574 572 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
575 573 hgc_close(hgc);
576 574 freecmdserveropts(&opts);
577 575 return exitcode;
578 576 }
@@ -1,665 +1,657 b''
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 'SIGHUP' signal
32 reload configuration files
33
34 31 Config
35 32 ------
36 33
37 34 ::
38 35
39 36 [chgserver]
40 37 idletimeout = 3600 # seconds, after which an idle server will exit
41 38 skiphash = False # whether to skip config or env change checks
42 39 """
43 40
44 41 from __future__ import absolute_import
45 42
46 43 import SocketServer
47 44 import errno
48 45 import inspect
49 46 import os
50 47 import re
51 import signal
52 48 import struct
53 49 import sys
54 50 import threading
55 51 import time
56 52 import traceback
57 53
58 54 from mercurial.i18n import _
59 55
60 56 from mercurial import (
61 57 cmdutil,
62 58 commands,
63 59 commandserver,
64 60 dispatch,
65 61 error,
66 62 extensions,
67 63 osutil,
68 64 util,
69 65 )
70 66
71 67 # Note for extension authors: ONLY specify testedwith = 'internal' for
72 68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 69 # be specifying the version(s) of Mercurial they are tested with, or
74 70 # leave the attribute unspecified.
75 71 testedwith = 'internal'
76 72
77 73 _log = commandserver.log
78 74
79 75 def _hashlist(items):
80 76 """return sha1 hexdigest for a list"""
81 77 return util.sha1(str(items)).hexdigest()
82 78
83 79 # sensitive config sections affecting confighash
84 80 _configsections = ['extensions']
85 81
86 82 # sensitive environment variables affecting confighash
87 83 _envre = re.compile(r'''\A(?:
88 84 CHGHG
89 85 |HG.*
90 86 |LANG(?:UAGE)?
91 87 |LC_.*
92 88 |LD_.*
93 89 |PATH
94 90 |PYTHON.*
95 91 |TERM(?:INFO)?
96 92 |TZ
97 93 )\Z''', re.X)
98 94
99 95 def _confighash(ui):
100 96 """return a quick hash for detecting config/env changes
101 97
102 98 confighash is the hash of sensitive config items and environment variables.
103 99
104 100 for chgserver, it is designed that once confighash changes, the server is
105 101 not qualified to serve its client and should redirect the client to a new
106 102 server. different from mtimehash, confighash change will not mark the
107 103 server outdated and exit since the user can have different configs at the
108 104 same time.
109 105 """
110 106 sectionitems = []
111 107 for section in _configsections:
112 108 sectionitems.append(ui.configitems(section))
113 109 sectionhash = _hashlist(sectionitems)
114 110 envitems = [(k, v) for k, v in os.environ.iteritems() if _envre.match(k)]
115 111 envhash = _hashlist(sorted(envitems))
116 112 return sectionhash[:6] + envhash[:6]
117 113
118 114 def _getmtimepaths(ui):
119 115 """get a list of paths that should be checked to detect change
120 116
121 117 The list will include:
122 118 - extensions (will not cover all files for complex extensions)
123 119 - mercurial/__version__.py
124 120 - python binary
125 121 """
126 122 modules = [m for n, m in extensions.extensions(ui)]
127 123 try:
128 124 from mercurial import __version__
129 125 modules.append(__version__)
130 126 except ImportError:
131 127 pass
132 128 files = [sys.executable]
133 129 for m in modules:
134 130 try:
135 131 files.append(inspect.getabsfile(m))
136 132 except TypeError:
137 133 pass
138 134 return sorted(set(files))
139 135
140 136 def _mtimehash(paths):
141 137 """return a quick hash for detecting file changes
142 138
143 139 mtimehash calls stat on given paths and calculate a hash based on size and
144 140 mtime of each file. mtimehash does not read file content because reading is
145 141 expensive. therefore it's not 100% reliable for detecting content changes.
146 142 it's possible to return different hashes for same file contents.
147 143 it's also possible to return a same hash for different file contents for
148 144 some carefully crafted situation.
149 145
150 146 for chgserver, it is designed that once mtimehash changes, the server is
151 147 considered outdated immediately and should no longer provide service.
152 148 """
153 149 def trystat(path):
154 150 try:
155 151 st = os.stat(path)
156 152 return (st.st_mtime, st.st_size)
157 153 except OSError:
158 154 # could be ENOENT, EPERM etc. not fatal in any case
159 155 pass
160 156 return _hashlist(map(trystat, paths))[:12]
161 157
162 158 class hashstate(object):
163 159 """a structure storing confighash, mtimehash, paths used for mtimehash"""
164 160 def __init__(self, confighash, mtimehash, mtimepaths):
165 161 self.confighash = confighash
166 162 self.mtimehash = mtimehash
167 163 self.mtimepaths = mtimepaths
168 164
169 165 @staticmethod
170 166 def fromui(ui, mtimepaths=None):
171 167 if mtimepaths is None:
172 168 mtimepaths = _getmtimepaths(ui)
173 169 confighash = _confighash(ui)
174 170 mtimehash = _mtimehash(mtimepaths)
175 171 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
176 172 return hashstate(confighash, mtimehash, mtimepaths)
177 173
178 174 # copied from hgext/pager.py:uisetup()
179 175 def _setuppagercmd(ui, options, cmd):
180 176 if not ui.formatted():
181 177 return
182 178
183 179 p = ui.config("pager", "pager", os.environ.get("PAGER"))
184 180 usepager = False
185 181 always = util.parsebool(options['pager'])
186 182 auto = options['pager'] == 'auto'
187 183
188 184 if not p:
189 185 pass
190 186 elif always:
191 187 usepager = True
192 188 elif not auto:
193 189 usepager = False
194 190 else:
195 191 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
196 192 attend = ui.configlist('pager', 'attend', attended)
197 193 ignore = ui.configlist('pager', 'ignore')
198 194 cmds, _ = cmdutil.findcmd(cmd, commands.table)
199 195
200 196 for cmd in cmds:
201 197 var = 'attend-%s' % cmd
202 198 if ui.config('pager', var):
203 199 usepager = ui.configbool('pager', var)
204 200 break
205 201 if (cmd in attend or
206 202 (cmd not in ignore and not attend)):
207 203 usepager = True
208 204 break
209 205
210 206 if usepager:
211 207 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
212 208 ui.setconfig('ui', 'interactive', False, 'pager')
213 209 return p
214 210
215 211 _envvarre = re.compile(r'\$[a-zA-Z_]+')
216 212
217 213 def _clearenvaliases(cmdtable):
218 214 """Remove stale command aliases referencing env vars; variable expansion
219 215 is done at dispatch.addaliases()"""
220 216 for name, tab in cmdtable.items():
221 217 cmddef = tab[0]
222 218 if (isinstance(cmddef, dispatch.cmdalias) and
223 219 not cmddef.definition.startswith('!') and # shell alias
224 220 _envvarre.search(cmddef.definition)):
225 221 del cmdtable[name]
226 222
227 223 def _newchgui(srcui, csystem):
228 224 class chgui(srcui.__class__):
229 225 def __init__(self, src=None):
230 226 super(chgui, self).__init__(src)
231 227 if src:
232 228 self._csystem = getattr(src, '_csystem', csystem)
233 229 else:
234 230 self._csystem = csystem
235 231
236 232 def system(self, cmd, environ=None, cwd=None, onerr=None,
237 233 errprefix=None):
238 234 # copied from mercurial/util.py:system()
239 235 self.flush()
240 236 def py2shell(val):
241 237 if val is None or val is False:
242 238 return '0'
243 239 if val is True:
244 240 return '1'
245 241 return str(val)
246 242 env = os.environ.copy()
247 243 if environ:
248 244 env.update((k, py2shell(v)) for k, v in environ.iteritems())
249 245 env['HG'] = util.hgexecutable()
250 246 rc = self._csystem(cmd, env, cwd)
251 247 if rc and onerr:
252 248 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
253 249 util.explainexit(rc)[0])
254 250 if errprefix:
255 251 errmsg = '%s: %s' % (errprefix, errmsg)
256 252 raise onerr(errmsg)
257 253 return rc
258 254
259 255 return chgui(srcui)
260 256
261 257 def _renewui(srcui, args=None):
262 258 if not args:
263 259 args = []
264 260
265 261 newui = srcui.__class__()
266 262 for a in ['fin', 'fout', 'ferr', 'environ']:
267 263 setattr(newui, a, getattr(srcui, a))
268 264 if util.safehasattr(srcui, '_csystem'):
269 265 newui._csystem = srcui._csystem
270 266
271 267 # load wd and repo config, copied from dispatch.py
272 268 cwds = dispatch._earlygetopt(['--cwd'], args)
273 269 cwd = cwds and os.path.realpath(cwds[-1]) or None
274 270 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
275 271 path, newui = dispatch._getlocal(newui, rpath, wd=cwd)
276 272
277 273 # internal config: extensions.chgserver
278 274 # copy it. it can only be overrided from command line.
279 275 newui.setconfig('extensions', 'chgserver',
280 276 srcui.config('extensions', 'chgserver'), '--config')
281 277
282 278 # command line args
283 279 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
284 280
285 281 # stolen from tortoisehg.util.copydynamicconfig()
286 282 for section, name, value in srcui.walkconfig():
287 283 source = srcui.configsource(section, name)
288 284 if ':' in source or source == '--config':
289 285 # path:line or command line
290 286 continue
291 287 if source == 'none':
292 288 # ui.configsource returns 'none' by default
293 289 source = ''
294 290 newui.setconfig(section, name, value, source)
295 291 return newui
296 292
297 293 class channeledsystem(object):
298 294 """Propagate ui.system() request in the following format:
299 295
300 296 payload length (unsigned int),
301 297 cmd, '\0',
302 298 cwd, '\0',
303 299 envkey, '=', val, '\0',
304 300 ...
305 301 envkey, '=', val
306 302
307 303 and waits:
308 304
309 305 exitcode length (unsigned int),
310 306 exitcode (int)
311 307 """
312 308 def __init__(self, in_, out, channel):
313 309 self.in_ = in_
314 310 self.out = out
315 311 self.channel = channel
316 312
317 313 def __call__(self, cmd, environ, cwd):
318 314 args = [util.quotecommand(cmd), cwd or '.']
319 315 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
320 316 data = '\0'.join(args)
321 317 self.out.write(struct.pack('>cI', self.channel, len(data)))
322 318 self.out.write(data)
323 319 self.out.flush()
324 320
325 321 length = self.in_.read(4)
326 322 length, = struct.unpack('>I', length)
327 323 if length != 4:
328 324 raise error.Abort(_('invalid response'))
329 325 rc, = struct.unpack('>i', self.in_.read(4))
330 326 return rc
331 327
332 328 _iochannels = [
333 329 # server.ch, ui.fp, mode
334 330 ('cin', 'fin', 'rb'),
335 331 ('cout', 'fout', 'wb'),
336 332 ('cerr', 'ferr', 'wb'),
337 333 ]
338 334
339 335 class chgcmdserver(commandserver.server):
340 336 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
341 337 super(chgcmdserver, self).__init__(
342 338 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
343 339 self.clientsock = sock
344 340 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
345 341 self.hashstate = hashstate
346 342 self.baseaddress = baseaddress
347 343 if hashstate is not None:
348 344 self.capabilities = self.capabilities.copy()
349 345 self.capabilities['validate'] = chgcmdserver.validate
350 346
351 347 def cleanup(self):
352 348 # dispatch._runcatch() does not flush outputs if exception is not
353 349 # handled by dispatch._dispatch()
354 350 self.ui.flush()
355 351 self._restoreio()
356 352
357 353 def attachio(self):
358 354 """Attach to client's stdio passed via unix domain socket; all
359 355 channels except cresult will no longer be used
360 356 """
361 357 # tell client to sendmsg() with 1-byte payload, which makes it
362 358 # distinctive from "attachio\n" command consumed by client.read()
363 359 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
364 360 clientfds = osutil.recvfds(self.clientsock.fileno())
365 361 _log('received fds: %r\n' % clientfds)
366 362
367 363 ui = self.ui
368 364 ui.flush()
369 365 first = self._saveio()
370 366 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
371 367 assert fd > 0
372 368 fp = getattr(ui, fn)
373 369 os.dup2(fd, fp.fileno())
374 370 os.close(fd)
375 371 if not first:
376 372 continue
377 373 # reset buffering mode when client is first attached. as we want
378 374 # to see output immediately on pager, the mode stays unchanged
379 375 # when client re-attached. ferr is unchanged because it should
380 376 # be unbuffered no matter if it is a tty or not.
381 377 if fn == 'ferr':
382 378 newfp = fp
383 379 else:
384 380 # make it line buffered explicitly because the default is
385 381 # decided on first write(), where fout could be a pager.
386 382 if fp.isatty():
387 383 bufsize = 1 # line buffered
388 384 else:
389 385 bufsize = -1 # system default
390 386 newfp = os.fdopen(fp.fileno(), mode, bufsize)
391 387 setattr(ui, fn, newfp)
392 388 setattr(self, cn, newfp)
393 389
394 390 self.cresult.write(struct.pack('>i', len(clientfds)))
395 391
396 392 def _saveio(self):
397 393 if self._oldios:
398 394 return False
399 395 ui = self.ui
400 396 for cn, fn, _mode in _iochannels:
401 397 ch = getattr(self, cn)
402 398 fp = getattr(ui, fn)
403 399 fd = os.dup(fp.fileno())
404 400 self._oldios.append((ch, fp, fd))
405 401 return True
406 402
407 403 def _restoreio(self):
408 404 ui = self.ui
409 405 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
410 406 newfp = getattr(ui, fn)
411 407 # close newfp while it's associated with client; otherwise it
412 408 # would be closed when newfp is deleted
413 409 if newfp is not fp:
414 410 newfp.close()
415 411 # restore original fd: fp is open again
416 412 os.dup2(fd, fp.fileno())
417 413 os.close(fd)
418 414 setattr(self, cn, ch)
419 415 setattr(ui, fn, fp)
420 416 del self._oldios[:]
421 417
422 418 def validate(self):
423 419 """Reload the config and check if the server is up to date
424 420
425 421 Read a list of '\0' separated arguments.
426 422 Write a non-empty list of '\0' separated instruction strings or '\0'
427 423 if the list is empty.
428 424 An instruction string could be either:
429 425 - "unlink $path", the client should unlink the path to stop the
430 426 outdated server.
431 427 - "redirect $path", the client should try to connect to another
432 428 server instead.
433 429 """
434 430 args = self._readlist()
435 431 self.ui = _renewui(self.ui, args)
436 432 newhash = hashstate.fromui(self.ui, self.hashstate.mtimepaths)
437 433 insts = []
438 434 if newhash.mtimehash != self.hashstate.mtimehash:
439 435 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
440 436 insts.append('unlink %s' % addr)
441 437 if newhash.confighash != self.hashstate.confighash:
442 438 addr = _hashaddress(self.baseaddress, newhash.confighash)
443 439 insts.append('redirect %s' % addr)
444 440 _log('validate: %s\n' % insts)
445 441 self.cresult.write('\0'.join(insts) or '\0')
446 442
447 443 def chdir(self):
448 444 """Change current directory
449 445
450 446 Note that the behavior of --cwd option is bit different from this.
451 447 It does not affect --config parameter.
452 448 """
453 449 path = self._readstr()
454 450 if not path:
455 451 return
456 452 _log('chdir to %r\n' % path)
457 453 os.chdir(path)
458 454
459 455 def setumask(self):
460 456 """Change umask"""
461 457 mask = struct.unpack('>I', self._read(4))[0]
462 458 _log('setumask %r\n' % mask)
463 459 os.umask(mask)
464 460
465 461 def getpager(self):
466 462 """Read cmdargs and write pager command to r-channel if enabled
467 463
468 464 If pager isn't enabled, this writes '\0' because channeledoutput
469 465 does not allow to write empty data.
470 466 """
471 467 args = self._readlist()
472 468 try:
473 469 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
474 470 args)
475 471 except (error.Abort, error.AmbiguousCommand, error.CommandError,
476 472 error.UnknownCommand):
477 473 cmd = None
478 474 options = {}
479 475 if not cmd or 'pager' not in options:
480 476 self.cresult.write('\0')
481 477 return
482 478
483 479 pagercmd = _setuppagercmd(self.ui, options, cmd)
484 480 if pagercmd:
485 481 self.cresult.write(pagercmd)
486 482 else:
487 483 self.cresult.write('\0')
488 484
489 485 def setenv(self):
490 486 """Clear and update os.environ
491 487
492 488 Note that not all variables can make an effect on the running process.
493 489 """
494 490 l = self._readlist()
495 491 try:
496 492 newenv = dict(s.split('=', 1) for s in l)
497 493 except ValueError:
498 494 raise ValueError('unexpected value in setenv request')
499 495
500 496 diffkeys = set(k for k in set(os.environ.keys() + newenv.keys())
501 497 if os.environ.get(k) != newenv.get(k))
502 498 _log('change env: %r\n' % sorted(diffkeys))
503 499
504 500 os.environ.clear()
505 501 os.environ.update(newenv)
506 502
507 503 if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys:
508 504 # reload config so that ui.plain() takes effect
509 505 self.ui = _renewui(self.ui)
510 506
511 507 _clearenvaliases(commands.table)
512 508
513 509 capabilities = commandserver.server.capabilities.copy()
514 510 capabilities.update({'attachio': attachio,
515 511 'chdir': chdir,
516 512 'getpager': getpager,
517 513 'setenv': setenv,
518 514 'setumask': setumask})
519 515
520 516 # copied from mercurial/commandserver.py
521 517 class _requesthandler(SocketServer.StreamRequestHandler):
522 518 def handle(self):
523 519 # use a different process group from the master process, making this
524 520 # process pass kernel "is_current_pgrp_orphaned" check so signals like
525 521 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
526 522 os.setpgid(0, 0)
527 523 ui = self.server.ui
528 524 repo = self.server.repo
529 525 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection,
530 526 self.server.hashstate, self.server.baseaddress)
531 527 try:
532 528 try:
533 529 sv.serve()
534 530 # handle exceptions that may be raised by command server. most of
535 531 # known exceptions are caught by dispatch.
536 532 except error.Abort as inst:
537 533 ui.warn(_('abort: %s\n') % inst)
538 534 except IOError as inst:
539 535 if inst.errno != errno.EPIPE:
540 536 raise
541 537 except KeyboardInterrupt:
542 538 pass
543 539 finally:
544 540 sv.cleanup()
545 541 except: # re-raises
546 542 # also write traceback to error channel. otherwise client cannot
547 543 # see it because it is written to server's stderr by default.
548 544 traceback.print_exc(file=sv.cerr)
549 545 raise
550 546
551 547 def _tempaddress(address):
552 548 return '%s.%d.tmp' % (address, os.getpid())
553 549
554 550 def _hashaddress(address, hashstr):
555 551 return '%s-%s' % (address, hashstr)
556 552
557 553 class AutoExitMixIn: # use old-style to comply with SocketServer design
558 554 lastactive = time.time()
559 555 idletimeout = 3600 # default 1 hour
560 556
561 557 def startautoexitthread(self):
562 558 # note: the auto-exit check here is cheap enough to not use a thread,
563 559 # be done in serve_forever. however SocketServer is hook-unfriendly,
564 560 # you simply cannot hook serve_forever without copying a lot of code.
565 561 # besides, serve_forever's docstring suggests using thread.
566 562 thread = threading.Thread(target=self._autoexitloop)
567 563 thread.daemon = True
568 564 thread.start()
569 565
570 566 def _autoexitloop(self, interval=1):
571 567 while True:
572 568 time.sleep(interval)
573 569 if not self.issocketowner():
574 570 _log('%s is not owned, exiting.\n' % self.server_address)
575 571 break
576 572 if time.time() - self.lastactive > self.idletimeout:
577 573 _log('being idle too long. exiting.\n')
578 574 break
579 575 self.shutdown()
580 576
581 577 def process_request(self, request, address):
582 578 self.lastactive = time.time()
583 579 return SocketServer.ForkingMixIn.process_request(
584 580 self, request, address)
585 581
586 582 def server_bind(self):
587 583 # use a unique temp address so we can stat the file and do ownership
588 584 # check later
589 585 tempaddress = _tempaddress(self.server_address)
590 586 self.socket.bind(tempaddress)
591 587 self._socketstat = os.stat(tempaddress)
592 588 # rename will replace the old socket file if exists atomically. the
593 589 # old server will detect ownership change and exit.
594 590 util.rename(tempaddress, self.server_address)
595 591
596 592 def issocketowner(self):
597 593 try:
598 594 stat = os.stat(self.server_address)
599 595 return (stat.st_ino == self._socketstat.st_ino and
600 596 stat.st_mtime == self._socketstat.st_mtime)
601 597 except OSError:
602 598 return False
603 599
604 600 def unlinksocketfile(self):
605 601 if not self.issocketowner():
606 602 return
607 603 # it is possible to have a race condition here that we may
608 604 # remove another server's socket file. but that's okay
609 605 # since that server will detect and exit automatically and
610 606 # the client will start a new server on demand.
611 607 try:
612 608 os.unlink(self.server_address)
613 609 except OSError as exc:
614 610 if exc.errno != errno.ENOENT:
615 611 raise
616 612
617 613 class chgunixservice(commandserver.unixservice):
618 614 def init(self):
619 signal.signal(signal.SIGHUP, self._reloadconfig)
620 615 self._inithashstate()
621 616 class cls(AutoExitMixIn, SocketServer.ForkingMixIn,
622 617 SocketServer.UnixStreamServer):
623 618 ui = self.ui
624 619 repo = self.repo
625 620 hashstate = self.hashstate
626 621 baseaddress = self.baseaddress
627 622 self.server = cls(self.address, _requesthandler)
628 623 self.server.idletimeout = self.ui.configint(
629 624 'chgserver', 'idletimeout', self.server.idletimeout)
630 625 self.server.startautoexitthread()
631 626 self._createsymlink()
632 627 # avoid writing "listening at" message to stdout before attachio
633 628 # request, which calls setvbuf()
634 629
635 630 def _inithashstate(self):
636 631 self.baseaddress = self.address
637 632 if self.ui.configbool('chgserver', 'skiphash', False):
638 633 self.hashstate = None
639 634 return
640 635 self.hashstate = hashstate.fromui(self.ui)
641 636 self.address = _hashaddress(self.address, self.hashstate.confighash)
642 637
643 638 def _createsymlink(self):
644 639 if self.baseaddress == self.address:
645 640 return
646 641 tempaddress = _tempaddress(self.baseaddress)
647 642 os.symlink(os.path.basename(self.address), tempaddress)
648 643 util.rename(tempaddress, self.baseaddress)
649 644
650 def _reloadconfig(self, signum, frame):
651 self.ui = self.server.ui = _renewui(self.ui)
652
653 645 def run(self):
654 646 try:
655 647 self.server.serve_forever()
656 648 finally:
657 649 self.server.unlinksocketfile()
658 650
659 651 def uisetup(ui):
660 652 commandserver._servicemap['chgunix'] = chgunixservice
661 653
662 654 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
663 655 # start another chg. drop it to avoid possible side effects.
664 656 if 'CHGINTERNALMARK' in os.environ:
665 657 del os.environ['CHGINTERNALMARK']
General Comments 0
You need to be logged in to leave comments. Login now