##// END OF EJS Templates
chg: remove getpager support...
Jun Wu -
r30741:fde9692a default
parent child Browse files
Show More
@@ -1,443 +1,439
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 "procutil.h"
27 27 #include "util.h"
28 28
29 29 #ifndef PATH_MAX
30 30 #define PATH_MAX 4096
31 31 #endif
32 32
33 33 struct cmdserveropts {
34 34 char sockname[PATH_MAX];
35 35 char initsockname[PATH_MAX];
36 36 char redirectsockname[PATH_MAX];
37 37 size_t argsize;
38 38 const char **args;
39 39 };
40 40
41 41 static void initcmdserveropts(struct cmdserveropts *opts) {
42 42 memset(opts, 0, sizeof(struct cmdserveropts));
43 43 }
44 44
45 45 static void freecmdserveropts(struct cmdserveropts *opts) {
46 46 free(opts->args);
47 47 opts->args = NULL;
48 48 opts->argsize = 0;
49 49 }
50 50
51 51 /*
52 52 * Test if an argument is a sensitive flag that should be passed to the server.
53 53 * Return 0 if not, otherwise the number of arguments starting from the current
54 54 * one that should be passed to the server.
55 55 */
56 56 static size_t testsensitiveflag(const char *arg)
57 57 {
58 58 static const struct {
59 59 const char *name;
60 60 size_t narg;
61 61 } flags[] = {
62 62 {"--config", 1},
63 63 {"--cwd", 1},
64 64 {"--repo", 1},
65 65 {"--repository", 1},
66 66 {"--traceback", 0},
67 67 {"-R", 1},
68 68 };
69 69 size_t i;
70 70 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
71 71 size_t len = strlen(flags[i].name);
72 72 size_t narg = flags[i].narg;
73 73 if (memcmp(arg, flags[i].name, len) == 0) {
74 74 if (arg[len] == '\0') {
75 75 /* --flag (value) */
76 76 return narg + 1;
77 77 } else if (arg[len] == '=' && narg > 0) {
78 78 /* --flag=value */
79 79 return 1;
80 80 } else if (flags[i].name[1] != '-') {
81 81 /* short flag */
82 82 return 1;
83 83 }
84 84 }
85 85 }
86 86 return 0;
87 87 }
88 88
89 89 /*
90 90 * Parse argv[] and put sensitive flags to opts->args
91 91 */
92 92 static void setcmdserverargs(struct cmdserveropts *opts,
93 93 int argc, const char *argv[])
94 94 {
95 95 size_t i, step;
96 96 opts->argsize = 0;
97 97 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
98 98 if (!argv[i])
99 99 continue; /* pass clang-analyse */
100 100 if (strcmp(argv[i], "--") == 0)
101 101 break;
102 102 size_t n = testsensitiveflag(argv[i]);
103 103 if (n == 0 || i + n > (size_t)argc)
104 104 continue;
105 105 opts->args = reallocx(opts->args,
106 106 (n + opts->argsize) * sizeof(char *));
107 107 memcpy(opts->args + opts->argsize, argv + i,
108 108 sizeof(char *) * n);
109 109 opts->argsize += n;
110 110 step = n;
111 111 }
112 112 }
113 113
114 114 static void preparesockdir(const char *sockdir)
115 115 {
116 116 int r;
117 117 r = mkdir(sockdir, 0700);
118 118 if (r < 0 && errno != EEXIST)
119 119 abortmsgerrno("cannot create sockdir %s", sockdir);
120 120
121 121 struct stat st;
122 122 r = lstat(sockdir, &st);
123 123 if (r < 0)
124 124 abortmsgerrno("cannot stat %s", sockdir);
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 getdefaultsockdir(char sockdir[], size_t size)
132 132 {
133 133 /* by default, put socket file in secure directory
134 134 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
135 135 * (permission of socket file may be ignored on some Unices) */
136 136 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
137 137 int r;
138 138 if (runtimedir) {
139 139 r = snprintf(sockdir, size, "%s/chg", runtimedir);
140 140 } else {
141 141 const char *tmpdir = getenv("TMPDIR");
142 142 if (!tmpdir)
143 143 tmpdir = "/tmp";
144 144 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
145 145 }
146 146 if (r < 0 || (size_t)r >= size)
147 147 abortmsg("too long TMPDIR (r = %d)", r);
148 148 }
149 149
150 150 static void setcmdserveropts(struct cmdserveropts *opts)
151 151 {
152 152 int r;
153 153 char sockdir[PATH_MAX];
154 154 const char *envsockname = getenv("CHGSOCKNAME");
155 155 if (!envsockname) {
156 156 getdefaultsockdir(sockdir, sizeof(sockdir));
157 157 preparesockdir(sockdir);
158 158 }
159 159
160 160 const char *basename = (envsockname) ? envsockname : sockdir;
161 161 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
162 162 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
163 163 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
164 164 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
165 165 r = snprintf(opts->initsockname, sizeof(opts->initsockname),
166 166 "%s.%u", opts->sockname, (unsigned)getpid());
167 167 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
168 168 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
169 169 }
170 170
171 171 static const char *gethgcmd(void)
172 172 {
173 173 static const char *hgcmd = NULL;
174 174 if (!hgcmd) {
175 175 hgcmd = getenv("CHGHG");
176 176 if (!hgcmd || hgcmd[0] == '\0')
177 177 hgcmd = getenv("HG");
178 178 if (!hgcmd || hgcmd[0] == '\0')
179 179 #ifdef HGPATH
180 180 hgcmd = (HGPATH);
181 181 #else
182 182 hgcmd = "hg";
183 183 #endif
184 184 }
185 185 return hgcmd;
186 186 }
187 187
188 188 static void execcmdserver(const struct cmdserveropts *opts)
189 189 {
190 190 const char *hgcmd = gethgcmd();
191 191
192 192 const char *baseargv[] = {
193 193 hgcmd,
194 194 "serve",
195 195 "--cmdserver", "chgunix",
196 196 "--address", opts->initsockname,
197 197 "--daemon-postexec", "chdir:/",
198 198 };
199 199 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
200 200 size_t argsize = baseargvsize + opts->argsize + 1;
201 201
202 202 const char **argv = mallocx(sizeof(char *) * argsize);
203 203 memcpy(argv, baseargv, sizeof(baseargv));
204 204 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
205 205 argv[argsize - 1] = NULL;
206 206
207 207 if (putenv("CHGINTERNALMARK=") != 0)
208 208 abortmsgerrno("failed to putenv");
209 209 if (execvp(hgcmd, (char **)argv) < 0)
210 210 abortmsgerrno("failed to exec cmdserver");
211 211 free(argv);
212 212 }
213 213
214 214 /* Retry until we can connect to the server. Give up after some time. */
215 215 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
216 216 {
217 217 static const struct timespec sleepreq = {0, 10 * 1000000};
218 218 int pst = 0;
219 219
220 220 debugmsg("try connect to %s repeatedly", opts->initsockname);
221 221
222 222 unsigned int timeoutsec = 60; /* default: 60 seconds */
223 223 const char *timeoutenv = getenv("CHGTIMEOUT");
224 224 if (timeoutenv)
225 225 sscanf(timeoutenv, "%u", &timeoutsec);
226 226
227 227 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
228 228 hgclient_t *hgc = hgc_open(opts->initsockname);
229 229 if (hgc) {
230 230 debugmsg("rename %s to %s", opts->initsockname,
231 231 opts->sockname);
232 232 int r = rename(opts->initsockname, opts->sockname);
233 233 if (r != 0)
234 234 abortmsgerrno("cannot rename");
235 235 return hgc;
236 236 }
237 237
238 238 if (pid > 0) {
239 239 /* collect zombie if child process fails to start */
240 240 int r = waitpid(pid, &pst, WNOHANG);
241 241 if (r != 0)
242 242 goto cleanup;
243 243 }
244 244
245 245 nanosleep(&sleepreq, NULL);
246 246 }
247 247
248 248 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
249 249 return NULL;
250 250
251 251 cleanup:
252 252 if (WIFEXITED(pst)) {
253 253 if (WEXITSTATUS(pst) == 0)
254 254 abortmsg("could not connect to cmdserver "
255 255 "(exited with status 0)");
256 256 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
257 257 exit(WEXITSTATUS(pst));
258 258 } else if (WIFSIGNALED(pst)) {
259 259 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
260 260 } else {
261 261 abortmsg("error while waiting for cmdserver");
262 262 }
263 263 return NULL;
264 264 }
265 265
266 266 /* Connect to a cmdserver. Will start a new server on demand. */
267 267 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
268 268 {
269 269 const char *sockname = opts->redirectsockname[0] ?
270 270 opts->redirectsockname : opts->sockname;
271 271 debugmsg("try connect to %s", sockname);
272 272 hgclient_t *hgc = hgc_open(sockname);
273 273 if (hgc)
274 274 return hgc;
275 275
276 276 /* prevent us from being connected to an outdated server: we were
277 277 * told by a server to redirect to opts->redirectsockname and that
278 278 * address does not work. we do not want to connect to the server
279 279 * again because it will probably tell us the same thing. */
280 280 if (sockname == opts->redirectsockname)
281 281 unlink(opts->sockname);
282 282
283 283 debugmsg("start cmdserver at %s", opts->initsockname);
284 284
285 285 pid_t pid = fork();
286 286 if (pid < 0)
287 287 abortmsg("failed to fork cmdserver process");
288 288 if (pid == 0) {
289 289 execcmdserver(opts);
290 290 } else {
291 291 hgc = retryconnectcmdserver(opts, pid);
292 292 }
293 293
294 294 return hgc;
295 295 }
296 296
297 297 static void killcmdserver(const struct cmdserveropts *opts)
298 298 {
299 299 /* resolve config hash */
300 300 char *resolvedpath = realpath(opts->sockname, NULL);
301 301 if (resolvedpath) {
302 302 unlink(resolvedpath);
303 303 free(resolvedpath);
304 304 }
305 305 }
306 306
307 307 /* Run instructions sent from the server like unlink and set redirect path
308 308 * Return 1 if reconnect is needed, otherwise 0 */
309 309 static int runinstructions(struct cmdserveropts *opts, const char **insts)
310 310 {
311 311 int needreconnect = 0;
312 312 if (!insts)
313 313 return needreconnect;
314 314
315 315 assert(insts);
316 316 opts->redirectsockname[0] = '\0';
317 317 const char **pinst;
318 318 for (pinst = insts; *pinst; pinst++) {
319 319 debugmsg("instruction: %s", *pinst);
320 320 if (strncmp(*pinst, "unlink ", 7) == 0) {
321 321 unlink(*pinst + 7);
322 322 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
323 323 int r = snprintf(opts->redirectsockname,
324 324 sizeof(opts->redirectsockname),
325 325 "%s", *pinst + 9);
326 326 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
327 327 abortmsg("redirect path is too long (%d)", r);
328 328 needreconnect = 1;
329 329 } else if (strncmp(*pinst, "exit ", 5) == 0) {
330 330 int n = 0;
331 331 if (sscanf(*pinst + 5, "%d", &n) != 1)
332 332 abortmsg("cannot read the exit code");
333 333 exit(n);
334 334 } else if (strcmp(*pinst, "reconnect") == 0) {
335 335 needreconnect = 1;
336 336 } else {
337 337 abortmsg("unknown instruction: %s", *pinst);
338 338 }
339 339 }
340 340 return needreconnect;
341 341 }
342 342
343 343 /*
344 344 * Test whether the command is unsupported or not. This is not designed to
345 345 * cover all cases. But it's fast, does not depend on the server and does
346 346 * not return false positives.
347 347 */
348 348 static int isunsupported(int argc, const char *argv[])
349 349 {
350 350 enum {
351 351 SERVE = 1,
352 352 DAEMON = 2,
353 353 SERVEDAEMON = SERVE | DAEMON,
354 354 TIME = 4,
355 355 };
356 356 unsigned int state = 0;
357 357 int i;
358 358 for (i = 0; i < argc; ++i) {
359 359 if (strcmp(argv[i], "--") == 0)
360 360 break;
361 361 if (i == 0 && strcmp("serve", argv[i]) == 0)
362 362 state |= SERVE;
363 363 else if (strcmp("-d", argv[i]) == 0 ||
364 364 strcmp("--daemon", argv[i]) == 0)
365 365 state |= DAEMON;
366 366 else if (strcmp("--time", argv[i]) == 0)
367 367 state |= TIME;
368 368 }
369 369 return (state & TIME) == TIME ||
370 370 (state & SERVEDAEMON) == SERVEDAEMON;
371 371 }
372 372
373 373 static void execoriginalhg(const char *argv[])
374 374 {
375 375 debugmsg("execute original hg");
376 376 if (execvp(gethgcmd(), (char **)argv) < 0)
377 377 abortmsgerrno("failed to exec original hg");
378 378 }
379 379
380 380 int main(int argc, const char *argv[], const char *envp[])
381 381 {
382 382 if (getenv("CHGDEBUG"))
383 383 enabledebugmsg();
384 384
385 385 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
386 386 enablecolor();
387 387
388 388 if (getenv("CHGINTERNALMARK"))
389 389 abortmsg("chg started by chg detected.\n"
390 390 "Please make sure ${HG:-hg} is not a symlink or "
391 391 "wrapper to chg. Alternatively, set $CHGHG to the "
392 392 "path of real hg.");
393 393
394 394 if (isunsupported(argc - 1, argv + 1))
395 395 execoriginalhg(argv);
396 396
397 397 struct cmdserveropts opts;
398 398 initcmdserveropts(&opts);
399 399 setcmdserveropts(&opts);
400 400 setcmdserverargs(&opts, argc, argv);
401 401
402 402 if (argc == 2) {
403 403 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
404 404 killcmdserver(&opts);
405 405 return 0;
406 406 }
407 407 }
408 408
409 409 hgclient_t *hgc;
410 410 size_t retry = 0;
411 411 while (1) {
412 412 hgc = connectcmdserver(&opts);
413 413 if (!hgc)
414 414 abortmsg("cannot open hg client");
415 415 hgc_setenv(hgc, envp);
416 416 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
417 417 int needreconnect = runinstructions(&opts, insts);
418 418 free(insts);
419 419 if (!needreconnect)
420 420 break;
421 421 hgc_close(hgc);
422 422 if (++retry > 10)
423 423 abortmsg("too many redirections.\n"
424 424 "Please make sure %s is not a wrapper which "
425 425 "changes sensitive environment variables "
426 426 "before executing hg. If you have to use a "
427 427 "wrapper, wrap chg instead of hg.",
428 428 gethgcmd());
429 429 }
430 430
431 431 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
432 const char *pagercmd = hgc_getpager(hgc, argv + 1, argc - 1);
433 pid_t pagerpid = setuppager(pagercmd);
434 if (pagerpid)
435 hgc_attachio(hgc); /* reattach to pager */
436 432 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
437 433 restoresignalhandler();
438 434 hgc_close(hgc);
439 435 freecmdserveropts(&opts);
440 436 waitpager();
441 437
442 438 return exitcode;
443 439 }
@@ -1,633 +1,606
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 "procutil.h"
27 27 #include "util.h"
28 28
29 29 enum {
30 30 CAP_GETENCODING = 0x0001,
31 31 CAP_RUNCOMMAND = 0x0002,
32 32 /* cHg extension: */
33 33 CAP_ATTACHIO = 0x0100,
34 34 CAP_CHDIR = 0x0200,
35 CAP_GETPAGER = 0x0400,
36 35 CAP_SETENV = 0x0800,
37 36 CAP_SETUMASK = 0x1000,
38 37 CAP_VALIDATE = 0x2000,
39 38 };
40 39
41 40 typedef struct {
42 41 const char *name;
43 42 unsigned int flag;
44 43 } cappair_t;
45 44
46 45 static const cappair_t captable[] = {
47 46 {"getencoding", CAP_GETENCODING},
48 47 {"runcommand", CAP_RUNCOMMAND},
49 48 {"attachio", CAP_ATTACHIO},
50 49 {"chdir", CAP_CHDIR},
51 {"getpager", CAP_GETPAGER},
52 50 {"setenv", CAP_SETENV},
53 51 {"setumask", CAP_SETUMASK},
54 52 {"validate", CAP_VALIDATE},
55 53 {NULL, 0}, /* terminator */
56 54 };
57 55
58 56 typedef struct {
59 57 char ch;
60 58 char *data;
61 59 size_t maxdatasize;
62 60 size_t datasize;
63 61 } context_t;
64 62
65 63 struct hgclient_tag_ {
66 64 int sockfd;
67 65 pid_t pgid;
68 66 pid_t pid;
69 67 context_t ctx;
70 68 unsigned int capflags;
71 69 };
72 70
73 71 static const size_t defaultdatasize = 4096;
74 72
75 73 static void attachio(hgclient_t *hgc);
76 74
77 75 static void initcontext(context_t *ctx)
78 76 {
79 77 ctx->ch = '\0';
80 78 ctx->data = malloc(defaultdatasize);
81 79 ctx->maxdatasize = (ctx->data) ? defaultdatasize : 0;
82 80 ctx->datasize = 0;
83 81 debugmsg("initialize context buffer with size %zu", ctx->maxdatasize);
84 82 }
85 83
86 84 static void enlargecontext(context_t *ctx, size_t newsize)
87 85 {
88 86 if (newsize <= ctx->maxdatasize)
89 87 return;
90 88
91 89 newsize = defaultdatasize
92 90 * ((newsize + defaultdatasize - 1) / defaultdatasize);
93 91 ctx->data = reallocx(ctx->data, newsize);
94 92 ctx->maxdatasize = newsize;
95 93 debugmsg("enlarge context buffer to %zu", ctx->maxdatasize);
96 94 }
97 95
98 96 static void freecontext(context_t *ctx)
99 97 {
100 98 debugmsg("free context buffer");
101 99 free(ctx->data);
102 100 ctx->data = NULL;
103 101 ctx->maxdatasize = 0;
104 102 ctx->datasize = 0;
105 103 }
106 104
107 105 /* Read channeled response from cmdserver */
108 106 static void readchannel(hgclient_t *hgc)
109 107 {
110 108 assert(hgc);
111 109
112 110 ssize_t rsize = recv(hgc->sockfd, &hgc->ctx.ch, sizeof(hgc->ctx.ch), 0);
113 111 if (rsize != sizeof(hgc->ctx.ch)) {
114 112 /* server would have exception and traceback would be printed */
115 113 debugmsg("failed to read channel");
116 114 exit(255);
117 115 }
118 116
119 117 uint32_t datasize_n;
120 118 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0);
121 119 if (rsize != sizeof(datasize_n))
122 120 abortmsg("failed to read data size");
123 121
124 122 /* datasize denotes the maximum size to write if input request */
125 123 hgc->ctx.datasize = ntohl(datasize_n);
126 124 enlargecontext(&hgc->ctx, hgc->ctx.datasize);
127 125
128 126 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S')
129 127 return; /* assumes input request */
130 128
131 129 size_t cursize = 0;
132 130 while (cursize < hgc->ctx.datasize) {
133 131 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
134 132 hgc->ctx.datasize - cursize, 0);
135 133 if (rsize < 1)
136 134 abortmsg("failed to read data block");
137 135 cursize += rsize;
138 136 }
139 137 }
140 138
141 139 static void sendall(int sockfd, const void *data, size_t datasize)
142 140 {
143 141 const char *p = data;
144 142 const char *const endp = p + datasize;
145 143 while (p < endp) {
146 144 ssize_t r = send(sockfd, p, endp - p, 0);
147 145 if (r < 0)
148 146 abortmsgerrno("cannot communicate");
149 147 p += r;
150 148 }
151 149 }
152 150
153 151 /* Write lengh-data block to cmdserver */
154 152 static void writeblock(const hgclient_t *hgc)
155 153 {
156 154 assert(hgc);
157 155
158 156 const uint32_t datasize_n = htonl(hgc->ctx.datasize);
159 157 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n));
160 158
161 159 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize);
162 160 }
163 161
164 162 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd)
165 163 {
166 164 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize);
167 165
168 166 char buf[strlen(chcmd) + 1];
169 167 memcpy(buf, chcmd, sizeof(buf) - 1);
170 168 buf[sizeof(buf) - 1] = '\n';
171 169 sendall(hgc->sockfd, buf, sizeof(buf));
172 170
173 171 writeblock(hgc);
174 172 }
175 173
176 174 /* Build '\0'-separated list of args. argsize < 0 denotes that args are
177 175 * terminated by NULL. */
178 176 static void packcmdargs(context_t *ctx, const char *const args[],
179 177 ssize_t argsize)
180 178 {
181 179 ctx->datasize = 0;
182 180 const char *const *const end = (argsize >= 0) ? args + argsize : NULL;
183 181 for (const char *const *it = args; it != end && *it; ++it) {
184 182 const size_t n = strlen(*it) + 1; /* include '\0' */
185 183 enlargecontext(ctx, ctx->datasize + n);
186 184 memcpy(ctx->data + ctx->datasize, *it, n);
187 185 ctx->datasize += n;
188 186 }
189 187
190 188 if (ctx->datasize > 0)
191 189 --ctx->datasize; /* strip last '\0' */
192 190 }
193 191
194 192 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */
195 193 static const char **unpackcmdargsnul(const context_t *ctx)
196 194 {
197 195 const char **args = NULL;
198 196 size_t nargs = 0, maxnargs = 0;
199 197 const char *s = ctx->data;
200 198 const char *e = ctx->data + ctx->datasize;
201 199 for (;;) {
202 200 if (nargs + 1 >= maxnargs) { /* including last NULL */
203 201 maxnargs += 256;
204 202 args = reallocx(args, maxnargs * sizeof(args[0]));
205 203 }
206 204 args[nargs] = s;
207 205 nargs++;
208 206 s = memchr(s, '\0', e - s);
209 207 if (!s)
210 208 break;
211 209 s++;
212 210 }
213 211 args[nargs] = NULL;
214 212 return args;
215 213 }
216 214
217 215 static void handlereadrequest(hgclient_t *hgc)
218 216 {
219 217 context_t *ctx = &hgc->ctx;
220 218 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin);
221 219 ctx->datasize = r;
222 220 writeblock(hgc);
223 221 }
224 222
225 223 /* Read single-line */
226 224 static void handlereadlinerequest(hgclient_t *hgc)
227 225 {
228 226 context_t *ctx = &hgc->ctx;
229 227 if (!fgets(ctx->data, ctx->datasize, stdin))
230 228 ctx->data[0] = '\0';
231 229 ctx->datasize = strlen(ctx->data);
232 230 writeblock(hgc);
233 231 }
234 232
235 233 /* Execute the requested command and write exit code */
236 234 static void handlesystemrequest(hgclient_t *hgc)
237 235 {
238 236 context_t *ctx = &hgc->ctx;
239 237 enlargecontext(ctx, ctx->datasize + 1);
240 238 ctx->data[ctx->datasize] = '\0'; /* terminate last string */
241 239
242 240 const char **args = unpackcmdargsnul(ctx);
243 241 if (!args[0] || !args[1] || !args[2])
244 242 abortmsg("missing type or command or cwd in system request");
245 243 if (strcmp(args[0], "system") == 0) {
246 244 debugmsg("run '%s' at '%s'", args[1], args[2]);
247 245 int32_t r = runshellcmd(args[1], args + 3, args[2]);
248 246 free(args);
249 247
250 248 uint32_t r_n = htonl(r);
251 249 memcpy(ctx->data, &r_n, sizeof(r_n));
252 250 ctx->datasize = sizeof(r_n);
253 251 writeblock(hgc);
254 252 } else if (strcmp(args[0], "pager") == 0) {
255 253 setuppager(args[1]);
256 254 if (hgc->capflags & CAP_ATTACHIO)
257 255 attachio(hgc);
258 256 /* unblock the server */
259 257 static const char emptycmd[] = "\n";
260 258 sendall(hgc->sockfd, emptycmd, sizeof(emptycmd) - 1);
261 259 } else {
262 260 abortmsg("unknown type in system request: %s", args[0]);
263 261 }
264 262 }
265 263
266 264 /* Read response of command execution until receiving 'r'-esult */
267 265 static void handleresponse(hgclient_t *hgc)
268 266 {
269 267 for (;;) {
270 268 readchannel(hgc);
271 269 context_t *ctx = &hgc->ctx;
272 270 debugmsg("response read from channel %c, size %zu",
273 271 ctx->ch, ctx->datasize);
274 272 switch (ctx->ch) {
275 273 case 'o':
276 274 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
277 275 stdout);
278 276 break;
279 277 case 'e':
280 278 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
281 279 stderr);
282 280 break;
283 281 case 'd':
284 282 /* assumes last char is '\n' */
285 283 ctx->data[ctx->datasize - 1] = '\0';
286 284 debugmsg("server: %s", ctx->data);
287 285 break;
288 286 case 'r':
289 287 return;
290 288 case 'I':
291 289 handlereadrequest(hgc);
292 290 break;
293 291 case 'L':
294 292 handlereadlinerequest(hgc);
295 293 break;
296 294 case 'S':
297 295 handlesystemrequest(hgc);
298 296 break;
299 297 default:
300 298 if (isupper(ctx->ch))
301 299 abortmsg("cannot handle response (ch = %c)",
302 300 ctx->ch);
303 301 }
304 302 }
305 303 }
306 304
307 305 static unsigned int parsecapabilities(const char *s, const char *e)
308 306 {
309 307 unsigned int flags = 0;
310 308 while (s < e) {
311 309 const char *t = strchr(s, ' ');
312 310 if (!t || t > e)
313 311 t = e;
314 312 const cappair_t *cap;
315 313 for (cap = captable; cap->flag; ++cap) {
316 314 size_t n = t - s;
317 315 if (strncmp(s, cap->name, n) == 0 &&
318 316 strlen(cap->name) == n) {
319 317 flags |= cap->flag;
320 318 break;
321 319 }
322 320 }
323 321 s = t + 1;
324 322 }
325 323 return flags;
326 324 }
327 325
328 326 static void readhello(hgclient_t *hgc)
329 327 {
330 328 readchannel(hgc);
331 329 context_t *ctx = &hgc->ctx;
332 330 if (ctx->ch != 'o') {
333 331 char ch = ctx->ch;
334 332 if (ch == 'e') {
335 333 /* write early error and will exit */
336 334 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
337 335 stderr);
338 336 handleresponse(hgc);
339 337 }
340 338 abortmsg("unexpected channel of hello message (ch = %c)", ch);
341 339 }
342 340 enlargecontext(ctx, ctx->datasize + 1);
343 341 ctx->data[ctx->datasize] = '\0';
344 342 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize);
345 343
346 344 const char *s = ctx->data;
347 345 const char *const dataend = ctx->data + ctx->datasize;
348 346 while (s < dataend) {
349 347 const char *t = strchr(s, ':');
350 348 if (!t || t[1] != ' ')
351 349 break;
352 350 const char *u = strchr(t + 2, '\n');
353 351 if (!u)
354 352 u = dataend;
355 353 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
356 354 hgc->capflags = parsecapabilities(t + 2, u);
357 355 } else if (strncmp(s, "pgid:", t - s + 1) == 0) {
358 356 hgc->pgid = strtol(t + 2, NULL, 10);
359 357 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
360 358 hgc->pid = strtol(t + 2, NULL, 10);
361 359 }
362 360 s = u + 1;
363 361 }
364 362 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid);
365 363 }
366 364
367 365 static void attachio(hgclient_t *hgc)
368 366 {
369 367 debugmsg("request attachio");
370 368 static const char chcmd[] = "attachio\n";
371 369 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1);
372 370 readchannel(hgc);
373 371 context_t *ctx = &hgc->ctx;
374 372 if (ctx->ch != 'I')
375 373 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch);
376 374
377 375 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
378 376 struct msghdr msgh;
379 377 memset(&msgh, 0, sizeof(msgh));
380 378 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */
381 379 msgh.msg_iov = &iov;
382 380 msgh.msg_iovlen = 1;
383 381 char fdbuf[CMSG_SPACE(sizeof(fds))];
384 382 msgh.msg_control = fdbuf;
385 383 msgh.msg_controllen = sizeof(fdbuf);
386 384 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
387 385 cmsg->cmsg_level = SOL_SOCKET;
388 386 cmsg->cmsg_type = SCM_RIGHTS;
389 387 cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
390 388 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
391 389 msgh.msg_controllen = cmsg->cmsg_len;
392 390 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0);
393 391 if (r < 0)
394 392 abortmsgerrno("sendmsg failed");
395 393
396 394 handleresponse(hgc);
397 395 int32_t n;
398 396 if (ctx->datasize != sizeof(n))
399 397 abortmsg("unexpected size of attachio result");
400 398 memcpy(&n, ctx->data, sizeof(n));
401 399 n = ntohl(n);
402 400 if (n != sizeof(fds) / sizeof(fds[0]))
403 401 abortmsg("failed to send fds (n = %d)", n);
404 402 }
405 403
406 404 static void chdirtocwd(hgclient_t *hgc)
407 405 {
408 406 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize))
409 407 abortmsgerrno("failed to getcwd");
410 408 hgc->ctx.datasize = strlen(hgc->ctx.data);
411 409 writeblockrequest(hgc, "chdir");
412 410 }
413 411
414 412 static void forwardumask(hgclient_t *hgc)
415 413 {
416 414 mode_t mask = umask(0);
417 415 umask(mask);
418 416
419 417 static const char command[] = "setumask\n";
420 418 sendall(hgc->sockfd, command, sizeof(command) - 1);
421 419 uint32_t data = htonl(mask);
422 420 sendall(hgc->sockfd, &data, sizeof(data));
423 421 }
424 422
425 423 /*!
426 424 * Open connection to per-user cmdserver
427 425 *
428 426 * If no background server running, returns NULL.
429 427 */
430 428 hgclient_t *hgc_open(const char *sockname)
431 429 {
432 430 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
433 431 if (fd < 0)
434 432 abortmsgerrno("cannot create socket");
435 433
436 434 /* don't keep fd on fork(), so that it can be closed when the parent
437 435 * process get terminated. */
438 436 fsetcloexec(fd);
439 437
440 438 struct sockaddr_un addr;
441 439 addr.sun_family = AF_UNIX;
442 440
443 441 /* use chdir to workaround small sizeof(sun_path) */
444 442 int bakfd = -1;
445 443 const char *basename = sockname;
446 444 {
447 445 const char *split = strrchr(sockname, '/');
448 446 if (split && split != sockname) {
449 447 if (split[1] == '\0')
450 448 abortmsg("sockname cannot end with a slash");
451 449 size_t len = split - sockname;
452 450 char sockdir[len + 1];
453 451 memcpy(sockdir, sockname, len);
454 452 sockdir[len] = '\0';
455 453
456 454 bakfd = open(".", O_DIRECTORY);
457 455 if (bakfd == -1)
458 456 abortmsgerrno("cannot open cwd");
459 457
460 458 int r = chdir(sockdir);
461 459 if (r != 0)
462 460 abortmsgerrno("cannot chdir %s", sockdir);
463 461
464 462 basename = split + 1;
465 463 }
466 464 }
467 465 if (strlen(basename) >= sizeof(addr.sun_path))
468 466 abortmsg("sockname is too long: %s", basename);
469 467 strncpy(addr.sun_path, basename, sizeof(addr.sun_path));
470 468 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
471 469
472 470 /* real connect */
473 471 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
474 472 if (r < 0) {
475 473 if (errno != ENOENT && errno != ECONNREFUSED)
476 474 abortmsgerrno("cannot connect to %s", sockname);
477 475 }
478 476 if (bakfd != -1) {
479 477 fchdirx(bakfd);
480 478 close(bakfd);
481 479 }
482 480 if (r < 0) {
483 481 close(fd);
484 482 return NULL;
485 483 }
486 484 debugmsg("connected to %s", addr.sun_path);
487 485
488 486 hgclient_t *hgc = mallocx(sizeof(hgclient_t));
489 487 memset(hgc, 0, sizeof(*hgc));
490 488 hgc->sockfd = fd;
491 489 initcontext(&hgc->ctx);
492 490
493 491 readhello(hgc);
494 492 if (!(hgc->capflags & CAP_RUNCOMMAND))
495 493 abortmsg("insufficient capability: runcommand");
496 494 if (hgc->capflags & CAP_ATTACHIO)
497 495 attachio(hgc);
498 496 if (hgc->capflags & CAP_CHDIR)
499 497 chdirtocwd(hgc);
500 498 if (hgc->capflags & CAP_SETUMASK)
501 499 forwardumask(hgc);
502 500
503 501 return hgc;
504 502 }
505 503
506 504 /*!
507 505 * Close connection and free allocated memory
508 506 */
509 507 void hgc_close(hgclient_t *hgc)
510 508 {
511 509 assert(hgc);
512 510 freecontext(&hgc->ctx);
513 511 close(hgc->sockfd);
514 512 free(hgc);
515 513 }
516 514
517 515 pid_t hgc_peerpgid(const hgclient_t *hgc)
518 516 {
519 517 assert(hgc);
520 518 return hgc->pgid;
521 519 }
522 520
523 521 pid_t hgc_peerpid(const hgclient_t *hgc)
524 522 {
525 523 assert(hgc);
526 524 return hgc->pid;
527 525 }
528 526
529 527 /*!
530 528 * Send command line arguments to let the server load the repo config and check
531 529 * whether it can process our request directly or not.
532 530 * Make sure hgc_setenv is called before calling this.
533 531 *
534 532 * @return - NULL, the server believes it can handle our request, or does not
535 533 * support "validate" command.
536 534 * - a list of strings, the server probably cannot handle our request
537 535 * and it sent instructions telling us what to do next. See
538 536 * chgserver.py for possible instruction formats.
539 537 * the list should be freed by the caller.
540 538 * the last string is guaranteed to be NULL.
541 539 */
542 540 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
543 541 size_t argsize)
544 542 {
545 543 assert(hgc);
546 544 if (!(hgc->capflags & CAP_VALIDATE))
547 545 return NULL;
548 546
549 547 packcmdargs(&hgc->ctx, args, argsize);
550 548 writeblockrequest(hgc, "validate");
551 549 handleresponse(hgc);
552 550
553 551 /* the server returns '\0' if it can handle our request */
554 552 if (hgc->ctx.datasize <= 1)
555 553 return NULL;
556 554
557 555 /* make sure the buffer is '\0' terminated */
558 556 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
559 557 hgc->ctx.data[hgc->ctx.datasize] = '\0';
560 558 return unpackcmdargsnul(&hgc->ctx);
561 559 }
562 560
563 561 /*!
564 562 * Execute the specified Mercurial command
565 563 *
566 564 * @return result code
567 565 */
568 566 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize)
569 567 {
570 568 assert(hgc);
571 569
572 570 packcmdargs(&hgc->ctx, args, argsize);
573 571 writeblockrequest(hgc, "runcommand");
574 572 handleresponse(hgc);
575 573
576 574 int32_t exitcode_n;
577 575 if (hgc->ctx.datasize != sizeof(exitcode_n)) {
578 576 abortmsg("unexpected size of exitcode");
579 577 }
580 578 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n));
581 579 return ntohl(exitcode_n);
582 580 }
583 581
584 582 /*!
585 583 * (Re-)send client's stdio channels so that the server can access to tty
586 584 */
587 585 void hgc_attachio(hgclient_t *hgc)
588 586 {
589 587 assert(hgc);
590 588 if (!(hgc->capflags & CAP_ATTACHIO))
591 589 return;
592 590 attachio(hgc);
593 591 }
594 592
595 593 /*!
596 * Get pager command for the given Mercurial command args
597 *
598 * If no pager enabled, returns NULL. The return value becomes invalid
599 * once you run another request to hgc.
600 */
601 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
602 size_t argsize)
603 {
604 assert(hgc);
605
606 if (!(hgc->capflags & CAP_GETPAGER))
607 return NULL;
608
609 packcmdargs(&hgc->ctx, args, argsize);
610 writeblockrequest(hgc, "getpager");
611 handleresponse(hgc);
612
613 if (hgc->ctx.datasize < 1 || hgc->ctx.data[0] == '\0')
614 return NULL;
615 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
616 hgc->ctx.data[hgc->ctx.datasize] = '\0';
617 return hgc->ctx.data;
618 }
619
620 /*!
621 594 * Update server's environment variables
622 595 *
623 596 * @param envp list of environment variables in "NAME=VALUE" format,
624 597 * terminated by NULL.
625 598 */
626 599 void hgc_setenv(hgclient_t *hgc, const char *const envp[])
627 600 {
628 601 assert(hgc && envp);
629 602 if (!(hgc->capflags & CAP_SETENV))
630 603 return;
631 604 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1);
632 605 writeblockrequest(hgc, "setenv");
633 606 }
@@ -1,32 +1,30
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 #ifndef HGCLIENT_H_
11 11 #define HGCLIENT_H_
12 12
13 13 #include <sys/types.h>
14 14
15 15 struct hgclient_tag_;
16 16 typedef struct hgclient_tag_ hgclient_t;
17 17
18 18 hgclient_t *hgc_open(const char *sockname);
19 19 void hgc_close(hgclient_t *hgc);
20 20
21 21 pid_t hgc_peerpgid(const hgclient_t *hgc);
22 22 pid_t hgc_peerpid(const hgclient_t *hgc);
23 23
24 24 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
25 25 size_t argsize);
26 26 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize);
27 27 void hgc_attachio(hgclient_t *hgc);
28 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
29 size_t argsize);
30 28 void hgc_setenv(hgclient_t *hgc, const char *const envp[]);
31 29
32 30 #endif /* HGCLIENT_H_ */
@@ -1,656 +1,580
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
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 'getpager' command
20 checks if pager is enabled and which pager should be executed
21
22 19 'setenv' command
23 20 replace os.environ completely
24 21
25 22 'setumask' command
26 23 set umask
27 24
28 25 'validate' command
29 26 reload the config and check if the server is up to date
30 27
31 28 Config
32 29 ------
33 30
34 31 ::
35 32
36 33 [chgserver]
37 34 idletimeout = 3600 # seconds, after which an idle server will exit
38 35 skiphash = False # whether to skip config or env change checks
39 36 """
40 37
41 38 from __future__ import absolute_import
42 39
43 40 import errno
44 41 import hashlib
45 42 import inspect
46 43 import os
47 44 import re
48 import signal
49 45 import struct
50 46 import time
51 47
52 48 from .i18n import _
53 49
54 50 from . import (
55 cmdutil,
56 51 commandserver,
57 52 encoding,
58 53 error,
59 54 extensions,
60 55 osutil,
61 56 pycompat,
62 57 util,
63 58 )
64 59
65 60 _log = commandserver.log
66 61
67 62 def _hashlist(items):
68 63 """return sha1 hexdigest for a list"""
69 64 return hashlib.sha1(str(items)).hexdigest()
70 65
71 66 # sensitive config sections affecting confighash
72 67 _configsections = [
73 68 'alias', # affects global state commands.table
74 69 'extdiff', # uisetup will register new commands
75 70 'extensions',
76 71 ]
77 72
78 73 # sensitive environment variables affecting confighash
79 74 _envre = re.compile(r'''\A(?:
80 75 CHGHG
81 76 |HG(?:[A-Z].*)?
82 77 |LANG(?:UAGE)?
83 78 |LC_.*
84 79 |LD_.*
85 80 |PATH
86 81 |PYTHON.*
87 82 |TERM(?:INFO)?
88 83 |TZ
89 84 )\Z''', re.X)
90 85
91 86 def _confighash(ui):
92 87 """return a quick hash for detecting config/env changes
93 88
94 89 confighash is the hash of sensitive config items and environment variables.
95 90
96 91 for chgserver, it is designed that once confighash changes, the server is
97 92 not qualified to serve its client and should redirect the client to a new
98 93 server. different from mtimehash, confighash change will not mark the
99 94 server outdated and exit since the user can have different configs at the
100 95 same time.
101 96 """
102 97 sectionitems = []
103 98 for section in _configsections:
104 99 sectionitems.append(ui.configitems(section))
105 100 sectionhash = _hashlist(sectionitems)
106 101 envitems = [(k, v) for k, v in encoding.environ.iteritems()
107 102 if _envre.match(k)]
108 103 envhash = _hashlist(sorted(envitems))
109 104 return sectionhash[:6] + envhash[:6]
110 105
111 106 def _getmtimepaths(ui):
112 107 """get a list of paths that should be checked to detect change
113 108
114 109 The list will include:
115 110 - extensions (will not cover all files for complex extensions)
116 111 - mercurial/__version__.py
117 112 - python binary
118 113 """
119 114 modules = [m for n, m in extensions.extensions(ui)]
120 115 try:
121 116 from . import __version__
122 117 modules.append(__version__)
123 118 except ImportError:
124 119 pass
125 120 files = [pycompat.sysexecutable]
126 121 for m in modules:
127 122 try:
128 123 files.append(inspect.getabsfile(m))
129 124 except TypeError:
130 125 pass
131 126 return sorted(set(files))
132 127
133 128 def _mtimehash(paths):
134 129 """return a quick hash for detecting file changes
135 130
136 131 mtimehash calls stat on given paths and calculate a hash based on size and
137 132 mtime of each file. mtimehash does not read file content because reading is
138 133 expensive. therefore it's not 100% reliable for detecting content changes.
139 134 it's possible to return different hashes for same file contents.
140 135 it's also possible to return a same hash for different file contents for
141 136 some carefully crafted situation.
142 137
143 138 for chgserver, it is designed that once mtimehash changes, the server is
144 139 considered outdated immediately and should no longer provide service.
145 140
146 141 mtimehash is not included in confighash because we only know the paths of
147 142 extensions after importing them (there is imp.find_module but that faces
148 143 race conditions). We need to calculate confighash without importing.
149 144 """
150 145 def trystat(path):
151 146 try:
152 147 st = os.stat(path)
153 148 return (st.st_mtime, st.st_size)
154 149 except OSError:
155 150 # could be ENOENT, EPERM etc. not fatal in any case
156 151 pass
157 152 return _hashlist(map(trystat, paths))[:12]
158 153
159 154 class hashstate(object):
160 155 """a structure storing confighash, mtimehash, paths used for mtimehash"""
161 156 def __init__(self, confighash, mtimehash, mtimepaths):
162 157 self.confighash = confighash
163 158 self.mtimehash = mtimehash
164 159 self.mtimepaths = mtimepaths
165 160
166 161 @staticmethod
167 162 def fromui(ui, mtimepaths=None):
168 163 if mtimepaths is None:
169 164 mtimepaths = _getmtimepaths(ui)
170 165 confighash = _confighash(ui)
171 166 mtimehash = _mtimehash(mtimepaths)
172 167 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
173 168 return hashstate(confighash, mtimehash, mtimepaths)
174 169
175 # copied from hgext/pager.py:uisetup()
176 def _setuppagercmd(ui, options, cmd):
177 from . import commands # avoid cycle
178
179 if not ui.formatted():
180 return
181
182 p = ui.config("pager", "pager", encoding.environ.get("PAGER"))
183 usepager = False
184 always = util.parsebool(options['pager'])
185 auto = options['pager'] == 'auto'
186
187 if not p:
188 pass
189 elif always:
190 usepager = True
191 elif not auto:
192 usepager = False
193 else:
194 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
195 attend = ui.configlist('pager', 'attend', attended)
196 ignore = ui.configlist('pager', 'ignore')
197 cmds, _ = cmdutil.findcmd(cmd, commands.table)
198
199 for cmd in cmds:
200 var = 'attend-%s' % cmd
201 if ui.config('pager', var):
202 usepager = ui.configbool('pager', var)
203 break
204 if (cmd in attend or
205 (cmd not in ignore and not attend)):
206 usepager = True
207 break
208
209 if usepager:
210 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
211 ui.setconfig('ui', 'interactive', False, 'pager')
212 return p
213
214 170 def _newchgui(srcui, csystem, attachio):
215 171 class chgui(srcui.__class__):
216 172 def __init__(self, src=None):
217 173 super(chgui, self).__init__(src)
218 174 if src:
219 175 self._csystem = getattr(src, '_csystem', csystem)
220 176 else:
221 177 self._csystem = csystem
222 178
223 179 def system(self, cmd, environ=None, cwd=None, onerr=None,
224 180 errprefix=None):
225 181 # fallback to the original system method if the output needs to be
226 182 # captured (to self._buffers), or the output stream is not stdout
227 183 # (e.g. stderr, cStringIO), because the chg client is not aware of
228 184 # these situations and will behave differently (write to stdout).
229 185 if (any(s[1] for s in self._bufferstates)
230 186 or not util.safehasattr(self.fout, 'fileno')
231 187 or self.fout.fileno() != util.stdout.fileno()):
232 188 return super(chgui, self).system(cmd, environ, cwd, onerr,
233 189 errprefix)
234 190 self.flush()
235 191 rc = self._csystem(cmd, util.shellenviron(environ), cwd)
236 192 if rc and onerr:
237 193 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
238 194 util.explainexit(rc)[0])
239 195 if errprefix:
240 196 errmsg = '%s: %s' % (errprefix, errmsg)
241 197 raise onerr(errmsg)
242 198 return rc
243 199
244 200 def _runpager(self, cmd):
245 201 self._csystem(cmd, util.shellenviron(), type='pager',
246 202 cmdtable={'attachio': attachio})
247 203
248 204 return chgui(srcui)
249 205
250 206 def _loadnewui(srcui, args):
251 207 from . import dispatch # avoid cycle
252 208
253 209 newui = srcui.__class__.load()
254 210 for a in ['fin', 'fout', 'ferr', 'environ']:
255 211 setattr(newui, a, getattr(srcui, a))
256 212 if util.safehasattr(srcui, '_csystem'):
257 213 newui._csystem = srcui._csystem
258 214
259 215 # command line args
260 216 args = args[:]
261 217 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
262 218
263 219 # stolen from tortoisehg.util.copydynamicconfig()
264 220 for section, name, value in srcui.walkconfig():
265 221 source = srcui.configsource(section, name)
266 222 if ':' in source or source == '--config':
267 223 # path:line or command line
268 224 continue
269 225 newui.setconfig(section, name, value, source)
270 226
271 227 # load wd and repo config, copied from dispatch.py
272 228 cwds = dispatch._earlygetopt(['--cwd'], args)
273 229 cwd = cwds and os.path.realpath(cwds[-1]) or None
274 230 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
275 231 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
276 232
277 233 return (newui, newlui)
278 234
279 235 class channeledsystem(object):
280 236 """Propagate ui.system() request in the following format:
281 237
282 238 payload length (unsigned int),
283 239 type, '\0',
284 240 cmd, '\0',
285 241 cwd, '\0',
286 242 envkey, '=', val, '\0',
287 243 ...
288 244 envkey, '=', val
289 245
290 246 if type == 'system', waits for:
291 247
292 248 exitcode length (unsigned int),
293 249 exitcode (int)
294 250
295 251 if type == 'pager', repetitively waits for a command name ending with '\n'
296 252 and executes it defined by cmdtable, or exits the loop if the command name
297 253 is empty.
298 254 """
299 255 def __init__(self, in_, out, channel):
300 256 self.in_ = in_
301 257 self.out = out
302 258 self.channel = channel
303 259
304 260 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
305 261 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
306 262 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
307 263 data = '\0'.join(args)
308 264 self.out.write(struct.pack('>cI', self.channel, len(data)))
309 265 self.out.write(data)
310 266 self.out.flush()
311 267
312 268 if type == 'system':
313 269 length = self.in_.read(4)
314 270 length, = struct.unpack('>I', length)
315 271 if length != 4:
316 272 raise error.Abort(_('invalid response'))
317 273 rc, = struct.unpack('>i', self.in_.read(4))
318 274 return rc
319 275 elif type == 'pager':
320 276 while True:
321 277 cmd = self.in_.readline()[:-1]
322 278 if not cmd:
323 279 break
324 280 if cmdtable and cmd in cmdtable:
325 281 _log('pager subcommand: %s' % cmd)
326 282 cmdtable[cmd]()
327 283 else:
328 284 raise error.Abort(_('unexpected command: %s') % cmd)
329 285 else:
330 286 raise error.ProgrammingError('invalid S channel type: %s' % type)
331 287
332 288 _iochannels = [
333 289 # server.ch, ui.fp, mode
334 290 ('cin', 'fin', 'rb'),
335 291 ('cout', 'fout', 'wb'),
336 292 ('cerr', 'ferr', 'wb'),
337 293 ]
338 294
339 295 class chgcmdserver(commandserver.server):
340 296 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
341 297 super(chgcmdserver, self).__init__(
342 298 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
343 299 repo, fin, fout)
344 300 self.clientsock = sock
345 301 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
346 302 self.hashstate = hashstate
347 303 self.baseaddress = baseaddress
348 304 if hashstate is not None:
349 305 self.capabilities = self.capabilities.copy()
350 306 self.capabilities['validate'] = chgcmdserver.validate
351 307
352 308 def cleanup(self):
353 309 super(chgcmdserver, self).cleanup()
354 310 # dispatch._runcatch() does not flush outputs if exception is not
355 311 # handled by dispatch._dispatch()
356 312 self.ui.flush()
357 313 self._restoreio()
358 314
359 315 def attachio(self):
360 316 """Attach to client's stdio passed via unix domain socket; all
361 317 channels except cresult will no longer be used
362 318 """
363 319 # tell client to sendmsg() with 1-byte payload, which makes it
364 320 # distinctive from "attachio\n" command consumed by client.read()
365 321 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
366 322 clientfds = osutil.recvfds(self.clientsock.fileno())
367 323 _log('received fds: %r\n' % clientfds)
368 324
369 325 ui = self.ui
370 326 ui.flush()
371 327 first = self._saveio()
372 328 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
373 329 assert fd > 0
374 330 fp = getattr(ui, fn)
375 331 os.dup2(fd, fp.fileno())
376 332 os.close(fd)
377 333 if not first:
378 334 continue
379 335 # reset buffering mode when client is first attached. as we want
380 336 # to see output immediately on pager, the mode stays unchanged
381 337 # when client re-attached. ferr is unchanged because it should
382 338 # be unbuffered no matter if it is a tty or not.
383 339 if fn == 'ferr':
384 340 newfp = fp
385 341 else:
386 342 # make it line buffered explicitly because the default is
387 343 # decided on first write(), where fout could be a pager.
388 344 if fp.isatty():
389 345 bufsize = 1 # line buffered
390 346 else:
391 347 bufsize = -1 # system default
392 348 newfp = os.fdopen(fp.fileno(), mode, bufsize)
393 349 setattr(ui, fn, newfp)
394 350 setattr(self, cn, newfp)
395 351
396 352 self.cresult.write(struct.pack('>i', len(clientfds)))
397 353
398 354 def _saveio(self):
399 355 if self._oldios:
400 356 return False
401 357 ui = self.ui
402 358 for cn, fn, _mode in _iochannels:
403 359 ch = getattr(self, cn)
404 360 fp = getattr(ui, fn)
405 361 fd = os.dup(fp.fileno())
406 362 self._oldios.append((ch, fp, fd))
407 363 return True
408 364
409 365 def _restoreio(self):
410 366 ui = self.ui
411 367 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
412 368 newfp = getattr(ui, fn)
413 369 # close newfp while it's associated with client; otherwise it
414 370 # would be closed when newfp is deleted
415 371 if newfp is not fp:
416 372 newfp.close()
417 373 # restore original fd: fp is open again
418 374 os.dup2(fd, fp.fileno())
419 375 os.close(fd)
420 376 setattr(self, cn, ch)
421 377 setattr(ui, fn, fp)
422 378 del self._oldios[:]
423 379
424 380 def validate(self):
425 381 """Reload the config and check if the server is up to date
426 382
427 383 Read a list of '\0' separated arguments.
428 384 Write a non-empty list of '\0' separated instruction strings or '\0'
429 385 if the list is empty.
430 386 An instruction string could be either:
431 387 - "unlink $path", the client should unlink the path to stop the
432 388 outdated server.
433 389 - "redirect $path", the client should attempt to connect to $path
434 390 first. If it does not work, start a new server. It implies
435 391 "reconnect".
436 392 - "exit $n", the client should exit directly with code n.
437 393 This may happen if we cannot parse the config.
438 394 - "reconnect", the client should close the connection and
439 395 reconnect.
440 396 If neither "reconnect" nor "redirect" is included in the instruction
441 397 list, the client can continue with this server after completing all
442 398 the instructions.
443 399 """
444 400 from . import dispatch # avoid cycle
445 401
446 402 args = self._readlist()
447 403 try:
448 404 self.ui, lui = _loadnewui(self.ui, args)
449 405 except error.ParseError as inst:
450 406 dispatch._formatparse(self.ui.warn, inst)
451 407 self.ui.flush()
452 408 self.cresult.write('exit 255')
453 409 return
454 410 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
455 411 insts = []
456 412 if newhash.mtimehash != self.hashstate.mtimehash:
457 413 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
458 414 insts.append('unlink %s' % addr)
459 415 # mtimehash is empty if one or more extensions fail to load.
460 416 # to be compatible with hg, still serve the client this time.
461 417 if self.hashstate.mtimehash:
462 418 insts.append('reconnect')
463 419 if newhash.confighash != self.hashstate.confighash:
464 420 addr = _hashaddress(self.baseaddress, newhash.confighash)
465 421 insts.append('redirect %s' % addr)
466 422 _log('validate: %s\n' % insts)
467 423 self.cresult.write('\0'.join(insts) or '\0')
468 424
469 425 def chdir(self):
470 426 """Change current directory
471 427
472 428 Note that the behavior of --cwd option is bit different from this.
473 429 It does not affect --config parameter.
474 430 """
475 431 path = self._readstr()
476 432 if not path:
477 433 return
478 434 _log('chdir to %r\n' % path)
479 435 os.chdir(path)
480 436
481 437 def setumask(self):
482 438 """Change umask"""
483 439 mask = struct.unpack('>I', self._read(4))[0]
484 440 _log('setumask %r\n' % mask)
485 441 os.umask(mask)
486 442
487 def getpager(self):
488 """Read cmdargs and write pager command to r-channel if enabled
489
490 If pager isn't enabled, this writes '\0' because channeledoutput
491 does not allow to write empty data.
492 """
493 from . import dispatch # avoid cycle
494
495 args = self._readlist()
496 try:
497 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
498 args)
499 except (error.Abort, error.AmbiguousCommand, error.CommandError,
500 error.UnknownCommand):
501 cmd = None
502 options = {}
503 if not cmd or 'pager' not in options:
504 self.cresult.write('\0')
505 return
506
507 pagercmd = _setuppagercmd(self.ui, options, cmd)
508 if pagercmd:
509 # Python's SIGPIPE is SIG_IGN by default. change to SIG_DFL so
510 # we can exit if the pipe to the pager is closed
511 if util.safehasattr(signal, 'SIGPIPE') and \
512 signal.getsignal(signal.SIGPIPE) == signal.SIG_IGN:
513 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
514 self.cresult.write(pagercmd)
515 else:
516 self.cresult.write('\0')
517
518 443 def runcommand(self):
519 444 return super(chgcmdserver, self).runcommand()
520 445
521 446 def setenv(self):
522 447 """Clear and update os.environ
523 448
524 449 Note that not all variables can make an effect on the running process.
525 450 """
526 451 l = self._readlist()
527 452 try:
528 453 newenv = dict(s.split('=', 1) for s in l)
529 454 except ValueError:
530 455 raise ValueError('unexpected value in setenv request')
531 456 _log('setenv: %r\n' % sorted(newenv.keys()))
532 457 encoding.environ.clear()
533 458 encoding.environ.update(newenv)
534 459
535 460 capabilities = commandserver.server.capabilities.copy()
536 461 capabilities.update({'attachio': attachio,
537 462 'chdir': chdir,
538 'getpager': getpager,
539 463 'runcommand': runcommand,
540 464 'setenv': setenv,
541 465 'setumask': setumask})
542 466
543 467 def _tempaddress(address):
544 468 return '%s.%d.tmp' % (address, os.getpid())
545 469
546 470 def _hashaddress(address, hashstr):
547 471 # if the basename of address contains '.', use only the left part. this
548 472 # makes it possible for the client to pass 'server.tmp$PID' and follow by
549 473 # an atomic rename to avoid locking when spawning new servers.
550 474 dirname, basename = os.path.split(address)
551 475 basename = basename.split('.', 1)[0]
552 476 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
553 477
554 478 class chgunixservicehandler(object):
555 479 """Set of operations for chg services"""
556 480
557 481 pollinterval = 1 # [sec]
558 482
559 483 def __init__(self, ui):
560 484 self.ui = ui
561 485 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
562 486 self._lastactive = time.time()
563 487
564 488 def bindsocket(self, sock, address):
565 489 self._inithashstate(address)
566 490 self._checkextensions()
567 491 self._bind(sock)
568 492 self._createsymlink()
569 493
570 494 def _inithashstate(self, address):
571 495 self._baseaddress = address
572 496 if self.ui.configbool('chgserver', 'skiphash', False):
573 497 self._hashstate = None
574 498 self._realaddress = address
575 499 return
576 500 self._hashstate = hashstate.fromui(self.ui)
577 501 self._realaddress = _hashaddress(address, self._hashstate.confighash)
578 502
579 503 def _checkextensions(self):
580 504 if not self._hashstate:
581 505 return
582 506 if extensions.notloaded():
583 507 # one or more extensions failed to load. mtimehash becomes
584 508 # meaningless because we do not know the paths of those extensions.
585 509 # set mtimehash to an illegal hash value to invalidate the server.
586 510 self._hashstate.mtimehash = ''
587 511
588 512 def _bind(self, sock):
589 513 # use a unique temp address so we can stat the file and do ownership
590 514 # check later
591 515 tempaddress = _tempaddress(self._realaddress)
592 516 util.bindunixsocket(sock, tempaddress)
593 517 self._socketstat = os.stat(tempaddress)
594 518 # rename will replace the old socket file if exists atomically. the
595 519 # old server will detect ownership change and exit.
596 520 util.rename(tempaddress, self._realaddress)
597 521
598 522 def _createsymlink(self):
599 523 if self._baseaddress == self._realaddress:
600 524 return
601 525 tempaddress = _tempaddress(self._baseaddress)
602 526 os.symlink(os.path.basename(self._realaddress), tempaddress)
603 527 util.rename(tempaddress, self._baseaddress)
604 528
605 529 def _issocketowner(self):
606 530 try:
607 531 stat = os.stat(self._realaddress)
608 532 return (stat.st_ino == self._socketstat.st_ino and
609 533 stat.st_mtime == self._socketstat.st_mtime)
610 534 except OSError:
611 535 return False
612 536
613 537 def unlinksocket(self, address):
614 538 if not self._issocketowner():
615 539 return
616 540 # it is possible to have a race condition here that we may
617 541 # remove another server's socket file. but that's okay
618 542 # since that server will detect and exit automatically and
619 543 # the client will start a new server on demand.
620 544 try:
621 545 os.unlink(self._realaddress)
622 546 except OSError as exc:
623 547 if exc.errno != errno.ENOENT:
624 548 raise
625 549
626 550 def printbanner(self, address):
627 551 # no "listening at" message should be printed to simulate hg behavior
628 552 pass
629 553
630 554 def shouldexit(self):
631 555 if not self._issocketowner():
632 556 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
633 557 return True
634 558 if time.time() - self._lastactive > self._idletimeout:
635 559 self.ui.debug('being idle too long. exiting.\n')
636 560 return True
637 561 return False
638 562
639 563 def newconnection(self):
640 564 self._lastactive = time.time()
641 565
642 566 def createcmdserver(self, repo, conn, fin, fout):
643 567 return chgcmdserver(self.ui, repo, fin, fout, conn,
644 568 self._hashstate, self._baseaddress)
645 569
646 570 def chgunixservice(ui, repo, opts):
647 571 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
648 572 # start another chg. drop it to avoid possible side effects.
649 573 if 'CHGINTERNALMARK' in encoding.environ:
650 574 del encoding.environ['CHGINTERNALMARK']
651 575
652 576 if repo:
653 577 # one chgserver can serve multiple repos. drop repo information
654 578 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
655 579 h = chgunixservicehandler(ui)
656 580 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
General Comments 0
You need to be logged in to leave comments. Login now