##// END OF EJS Templates
chg: fallback to original hg for some unsupported commands or flags...
Jun Wu -
r28260:0a17cfbe default
parent child Browse files
Show More
@@ -1,484 +1,524
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 lockfile[UNIX_PATH_MAX];
35 35 char pidfile[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 const char *pidfmt = (envsockname) ? "%s.pid" : "%s/pid";
152 152 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
153 153 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
154 154 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
155 155 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
156 156 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
157 157 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
158 158 r = snprintf(opts->pidfile, sizeof(opts->pidfile), pidfmt, basename);
159 159 if (r < 0 || (size_t)r >= sizeof(opts->pidfile))
160 160 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
161 161 }
162 162
163 163 /*
164 164 * Acquire a file lock that indicates a client is trying to start and connect
165 165 * to a server, before executing a command. The lock is released upon exit or
166 166 * explicit unlock. Will block if the lock is held by another process.
167 167 */
168 168 static void lockcmdserver(struct cmdserveropts *opts)
169 169 {
170 170 if (opts->lockfd == -1) {
171 171 opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
172 172 if (opts->lockfd == -1)
173 173 abortmsg("cannot create lock file %s", opts->lockfile);
174 174 }
175 175 int r = flock(opts->lockfd, LOCK_EX);
176 176 if (r == -1)
177 177 abortmsg("cannot acquire lock");
178 178 }
179 179
180 180 /*
181 181 * Release the file lock held by calling lockcmdserver. Will do nothing if
182 182 * lockcmdserver is not called.
183 183 */
184 184 static void unlockcmdserver(struct cmdserveropts *opts)
185 185 {
186 186 if (opts->lockfd == -1)
187 187 return;
188 188 flock(opts->lockfd, LOCK_UN);
189 189 close(opts->lockfd);
190 190 opts->lockfd = -1;
191 191 }
192 192
193 193 static const char *gethgcmd(void)
194 194 {
195 195 static const char *hgcmd = NULL;
196 196 if (!hgcmd) {
197 197 hgcmd = getenv("CHGHG");
198 198 if (!hgcmd || hgcmd[0] == '\0')
199 199 hgcmd = getenv("HG");
200 200 if (!hgcmd || hgcmd[0] == '\0')
201 201 hgcmd = "hg";
202 202 }
203 203 return hgcmd;
204 204 }
205 205
206 206 static void execcmdserver(const struct cmdserveropts *opts)
207 207 {
208 208 const char *hgcmd = gethgcmd();
209 209
210 210 const char *baseargv[] = {
211 211 hgcmd,
212 212 "serve",
213 213 "--cwd", "/",
214 214 "--cmdserver", "chgunix",
215 215 "--address", opts->sockname,
216 216 "--daemon-postexec", "none",
217 217 "--pid-file", opts->pidfile,
218 218 "--config", "extensions.chgserver=",
219 219 /* wrap root ui so that it can be disabled/enabled by config */
220 220 "--config", "progress.assume-tty=1",
221 221 };
222 222 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
223 223 size_t argsize = baseargvsize + opts->argsize + 1;
224 224
225 225 const char **argv = mallocx(sizeof(char *) * argsize);
226 226 memcpy(argv, baseargv, sizeof(baseargv));
227 227 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
228 228 argv[argsize - 1] = NULL;
229 229
230 230 if (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 hgclient_t *hgc = hgc_open(opts->sockname);
274 274 if (hgc)
275 275 return hgc;
276 276
277 277 lockcmdserver(opts);
278 278 hgc = hgc_open(opts->sockname);
279 279 if (hgc) {
280 280 unlockcmdserver(opts);
281 281 debugmsg("cmdserver is started by another process");
282 282 return hgc;
283 283 }
284 284
285 285 debugmsg("start cmdserver at %s", opts->sockname);
286 286
287 287 pid_t pid = fork();
288 288 if (pid < 0)
289 289 abortmsg("failed to fork cmdserver process");
290 290 if (pid == 0) {
291 291 /* do not leak lockfd to hg */
292 292 close(opts->lockfd);
293 293 /* bypass uisetup() of pager extension */
294 294 int nullfd = open("/dev/null", O_WRONLY);
295 295 if (nullfd >= 0) {
296 296 dup2(nullfd, fileno(stdout));
297 297 close(nullfd);
298 298 }
299 299 execcmdserver(opts);
300 300 } else {
301 301 hgc = retryconnectcmdserver(opts, pid);
302 302 }
303 303
304 304 unlockcmdserver(opts);
305 305 return hgc;
306 306 }
307 307
308 308 static void killcmdserver(const struct cmdserveropts *opts, int sig)
309 309 {
310 310 FILE *fp = fopen(opts->pidfile, "r");
311 311 if (!fp)
312 312 abortmsg("cannot open %s (errno = %d)", opts->pidfile, errno);
313 313 int pid = 0;
314 314 int n = fscanf(fp, "%d", &pid);
315 315 fclose(fp);
316 316 if (n != 1 || pid <= 0)
317 317 abortmsg("cannot read pid from %s", opts->pidfile);
318 318
319 319 if (kill((pid_t)pid, sig) < 0) {
320 320 if (errno == ESRCH)
321 321 return;
322 322 abortmsg("cannot kill %d (errno = %d)", pid, errno);
323 323 }
324 324 }
325 325
326 326 static pid_t peerpid = 0;
327 327
328 328 static void forwardsignal(int sig)
329 329 {
330 330 assert(peerpid > 0);
331 331 if (kill(peerpid, sig) < 0)
332 332 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
333 333 debugmsg("forward signal %d", sig);
334 334 }
335 335
336 336 static void handlestopsignal(int sig)
337 337 {
338 338 sigset_t unblockset, oldset;
339 339 struct sigaction sa, oldsa;
340 340 if (sigemptyset(&unblockset) < 0)
341 341 goto error;
342 342 if (sigaddset(&unblockset, sig) < 0)
343 343 goto error;
344 344 memset(&sa, 0, sizeof(sa));
345 345 sa.sa_handler = SIG_DFL;
346 346 sa.sa_flags = SA_RESTART;
347 347 if (sigemptyset(&sa.sa_mask) < 0)
348 348 goto error;
349 349
350 350 forwardsignal(sig);
351 351 if (raise(sig) < 0) /* resend to self */
352 352 goto error;
353 353 if (sigaction(sig, &sa, &oldsa) < 0)
354 354 goto error;
355 355 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
356 356 goto error;
357 357 /* resent signal will be handled before sigprocmask() returns */
358 358 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
359 359 goto error;
360 360 if (sigaction(sig, &oldsa, NULL) < 0)
361 361 goto error;
362 362 return;
363 363
364 364 error:
365 365 abortmsg("failed to handle stop signal (errno = %d)", errno);
366 366 }
367 367
368 368 static void setupsignalhandler(pid_t pid)
369 369 {
370 370 if (pid <= 0)
371 371 return;
372 372 peerpid = pid;
373 373
374 374 struct sigaction sa;
375 375 memset(&sa, 0, sizeof(sa));
376 376 sa.sa_handler = forwardsignal;
377 377 sa.sa_flags = SA_RESTART;
378 378 if (sigemptyset(&sa.sa_mask) < 0)
379 379 goto error;
380 380
381 381 if (sigaction(SIGHUP, &sa, NULL) < 0)
382 382 goto error;
383 383 if (sigaction(SIGINT, &sa, NULL) < 0)
384 384 goto error;
385 385
386 386 /* terminate frontend by double SIGTERM in case of server freeze */
387 387 sa.sa_flags |= SA_RESETHAND;
388 388 if (sigaction(SIGTERM, &sa, NULL) < 0)
389 389 goto error;
390 390
391 391 /* propagate job control requests to worker */
392 392 sa.sa_handler = forwardsignal;
393 393 sa.sa_flags = SA_RESTART;
394 394 if (sigaction(SIGCONT, &sa, NULL) < 0)
395 395 goto error;
396 396 sa.sa_handler = handlestopsignal;
397 397 sa.sa_flags = SA_RESTART;
398 398 if (sigaction(SIGTSTP, &sa, NULL) < 0)
399 399 goto error;
400 400
401 401 return;
402 402
403 403 error:
404 404 abortmsg("failed to set up signal handlers (errno = %d)", errno);
405 405 }
406 406
407 407 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
408 408 static void setuppager(hgclient_t *hgc, const char *const args[],
409 409 size_t argsize)
410 410 {
411 411 const char *pagercmd = hgc_getpager(hgc, args, argsize);
412 412 if (!pagercmd)
413 413 return;
414 414
415 415 int pipefds[2];
416 416 if (pipe(pipefds) < 0)
417 417 return;
418 418 pid_t pid = fork();
419 419 if (pid < 0)
420 420 goto error;
421 421 if (pid == 0) {
422 422 close(pipefds[0]);
423 423 if (dup2(pipefds[1], fileno(stdout)) < 0)
424 424 goto error;
425 425 if (isatty(fileno(stderr))) {
426 426 if (dup2(pipefds[1], fileno(stderr)) < 0)
427 427 goto error;
428 428 }
429 429 close(pipefds[1]);
430 430 hgc_attachio(hgc); /* reattach to pager */
431 431 return;
432 432 } else {
433 433 dup2(pipefds[0], fileno(stdin));
434 434 close(pipefds[0]);
435 435 close(pipefds[1]);
436 436
437 437 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
438 438 if (r < 0) {
439 439 abortmsg("cannot start pager '%s' (errno = %d)",
440 440 pagercmd, errno);
441 441 }
442 442 return;
443 443 }
444 444
445 445 error:
446 446 close(pipefds[0]);
447 447 close(pipefds[1]);
448 448 abortmsg("failed to prepare pager (errno = %d)", errno);
449 449 }
450 450
451 /*
452 * Test whether the command is unsupported or not. This is not designed to
453 * cover all cases. But it's fast, does not depend on the server and does
454 * not return false positives.
455 */
456 static int isunsupported(int argc, const char *argv[])
457 {
458 enum {
459 SERVE = 1,
460 DAEMON = 2,
461 SERVEDAEMON = SERVE | DAEMON,
462 TIME = 4,
463 };
464 unsigned int state = 0;
465 int i;
466 for (i = 0; i < argc; ++i) {
467 if (strcmp(argv[i], "--") == 0)
468 break;
469 if (i == 0 && strcmp("serve", argv[i]) == 0)
470 state |= SERVE;
471 else if (strcmp("-d", argv[i]) == 0 ||
472 strcmp("--daemon", argv[i]) == 0)
473 state |= DAEMON;
474 else if (strcmp("--time", argv[i]) == 0)
475 state |= TIME;
476 }
477 return (state & TIME) == TIME ||
478 (state & SERVEDAEMON) == SERVEDAEMON;
479 }
480
481 static void execoriginalhg(const char *argv[])
482 {
483 debugmsg("execute original hg");
484 if (execvp(gethgcmd(), (char **)argv) < 0)
485 abortmsg("failed to exec original hg (errno = %d)", errno);
486 }
487
451 488 int main(int argc, const char *argv[], const char *envp[])
452 489 {
453 490 if (getenv("CHGDEBUG"))
454 491 enabledebugmsg();
455 492
493 if (isunsupported(argc - 1, argv + 1))
494 execoriginalhg(argv);
495
456 496 struct cmdserveropts opts;
457 497 initcmdserveropts(&opts);
458 498 setcmdserveropts(&opts);
459 499 setcmdserverargs(&opts, argc, argv);
460 500
461 501 if (argc == 2) {
462 502 int sig = 0;
463 503 if (strcmp(argv[1], "--kill-chg-daemon") == 0)
464 504 sig = SIGTERM;
465 505 if (strcmp(argv[1], "--reload-chg-daemon") == 0)
466 506 sig = SIGHUP;
467 507 if (sig > 0) {
468 508 killcmdserver(&opts, sig);
469 509 return 0;
470 510 }
471 511 }
472 512
473 513 hgclient_t *hgc = connectcmdserver(&opts);
474 514 if (!hgc)
475 515 abortmsg("cannot open hg client");
476 516
477 517 setupsignalhandler(hgc_peerpid(hgc));
478 518 hgc_setenv(hgc, envp);
479 519 setuppager(hgc, argv + 1, argc - 1);
480 520 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
481 521 hgc_close(hgc);
482 522 freecmdserveropts(&opts);
483 523 return exitcode;
484 524 }
General Comments 0
You need to be logged in to leave comments. Login now