##// END OF EJS Templates
chg: provide early exception to user...
Yuya Nishihara -
r28512:b957b4c6 default
parent child Browse files
Show More
@@ -1,571 +1,578 b''
1 1 /*
2 2 * A command server client that uses Unix domain socket
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #include <arpa/inet.h> /* for ntohl(), htonl() */
11 11 #include <assert.h>
12 12 #include <ctype.h>
13 13 #include <errno.h>
14 14 #include <fcntl.h>
15 15 #include <signal.h>
16 16 #include <stdint.h>
17 17 #include <stdio.h>
18 18 #include <stdlib.h>
19 19 #include <string.h>
20 20 #include <sys/socket.h>
21 21 #include <sys/stat.h>
22 22 #include <sys/un.h>
23 23 #include <unistd.h>
24 24
25 25 #include "hgclient.h"
26 26 #include "util.h"
27 27
28 28 enum {
29 29 CAP_GETENCODING = 0x0001,
30 30 CAP_RUNCOMMAND = 0x0002,
31 31 /* cHg extension: */
32 32 CAP_ATTACHIO = 0x0100,
33 33 CAP_CHDIR = 0x0200,
34 34 CAP_GETPAGER = 0x0400,
35 35 CAP_SETENV = 0x0800,
36 36 CAP_SETUMASK = 0x1000,
37 37 CAP_VALIDATE = 0x2000,
38 38 };
39 39
40 40 typedef struct {
41 41 const char *name;
42 42 unsigned int flag;
43 43 } cappair_t;
44 44
45 45 static const cappair_t captable[] = {
46 46 {"getencoding", CAP_GETENCODING},
47 47 {"runcommand", CAP_RUNCOMMAND},
48 48 {"attachio", CAP_ATTACHIO},
49 49 {"chdir", CAP_CHDIR},
50 50 {"getpager", CAP_GETPAGER},
51 51 {"setenv", CAP_SETENV},
52 52 {"setumask", CAP_SETUMASK},
53 53 {"validate", CAP_VALIDATE},
54 54 {NULL, 0}, /* terminator */
55 55 };
56 56
57 57 typedef struct {
58 58 char ch;
59 59 char *data;
60 60 size_t maxdatasize;
61 61 size_t datasize;
62 62 } context_t;
63 63
64 64 struct hgclient_tag_ {
65 65 int sockfd;
66 66 pid_t pid;
67 67 context_t ctx;
68 68 unsigned int capflags;
69 69 };
70 70
71 71 static const size_t defaultdatasize = 4096;
72 72
73 73 static void initcontext(context_t *ctx)
74 74 {
75 75 ctx->ch = '\0';
76 76 ctx->data = malloc(defaultdatasize);
77 77 ctx->maxdatasize = (ctx->data) ? defaultdatasize : 0;
78 78 ctx->datasize = 0;
79 79 debugmsg("initialize context buffer with size %zu", ctx->maxdatasize);
80 80 }
81 81
82 82 static void enlargecontext(context_t *ctx, size_t newsize)
83 83 {
84 84 if (newsize <= ctx->maxdatasize)
85 85 return;
86 86
87 87 newsize = defaultdatasize
88 88 * ((newsize + defaultdatasize - 1) / defaultdatasize);
89 89 ctx->data = reallocx(ctx->data, newsize);
90 90 ctx->maxdatasize = newsize;
91 91 debugmsg("enlarge context buffer to %zu", ctx->maxdatasize);
92 92 }
93 93
94 94 static void freecontext(context_t *ctx)
95 95 {
96 96 debugmsg("free context buffer");
97 97 free(ctx->data);
98 98 ctx->data = NULL;
99 99 ctx->maxdatasize = 0;
100 100 ctx->datasize = 0;
101 101 }
102 102
103 103 /* Read channeled response from cmdserver */
104 104 static void readchannel(hgclient_t *hgc)
105 105 {
106 106 assert(hgc);
107 107
108 108 ssize_t rsize = recv(hgc->sockfd, &hgc->ctx.ch, sizeof(hgc->ctx.ch), 0);
109 109 if (rsize != sizeof(hgc->ctx.ch))
110 110 abortmsg("failed to read channel");
111 111
112 112 uint32_t datasize_n;
113 113 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0);
114 114 if (rsize != sizeof(datasize_n))
115 115 abortmsg("failed to read data size");
116 116
117 117 /* datasize denotes the maximum size to write if input request */
118 118 hgc->ctx.datasize = ntohl(datasize_n);
119 119 enlargecontext(&hgc->ctx, hgc->ctx.datasize);
120 120
121 121 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S')
122 122 return; /* assumes input request */
123 123
124 124 size_t cursize = 0;
125 125 while (cursize < hgc->ctx.datasize) {
126 126 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
127 127 hgc->ctx.datasize - cursize, 0);
128 128 if (rsize < 0)
129 129 abortmsg("failed to read data block");
130 130 cursize += rsize;
131 131 }
132 132 }
133 133
134 134 static void sendall(int sockfd, const void *data, size_t datasize)
135 135 {
136 136 const char *p = data;
137 137 const char *const endp = p + datasize;
138 138 while (p < endp) {
139 139 ssize_t r = send(sockfd, p, endp - p, 0);
140 140 if (r < 0)
141 141 abortmsg("cannot communicate (errno = %d)", errno);
142 142 p += r;
143 143 }
144 144 }
145 145
146 146 /* Write lengh-data block to cmdserver */
147 147 static void writeblock(const hgclient_t *hgc)
148 148 {
149 149 assert(hgc);
150 150
151 151 const uint32_t datasize_n = htonl(hgc->ctx.datasize);
152 152 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n));
153 153
154 154 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize);
155 155 }
156 156
157 157 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd)
158 158 {
159 159 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize);
160 160
161 161 char buf[strlen(chcmd) + 1];
162 162 memcpy(buf, chcmd, sizeof(buf) - 1);
163 163 buf[sizeof(buf) - 1] = '\n';
164 164 sendall(hgc->sockfd, buf, sizeof(buf));
165 165
166 166 writeblock(hgc);
167 167 }
168 168
169 169 /* Build '\0'-separated list of args. argsize < 0 denotes that args are
170 170 * terminated by NULL. */
171 171 static void packcmdargs(context_t *ctx, const char *const args[],
172 172 ssize_t argsize)
173 173 {
174 174 ctx->datasize = 0;
175 175 const char *const *const end = (argsize >= 0) ? args + argsize : NULL;
176 176 for (const char *const *it = args; it != end && *it; ++it) {
177 177 const size_t n = strlen(*it) + 1; /* include '\0' */
178 178 enlargecontext(ctx, ctx->datasize + n);
179 179 memcpy(ctx->data + ctx->datasize, *it, n);
180 180 ctx->datasize += n;
181 181 }
182 182
183 183 if (ctx->datasize > 0)
184 184 --ctx->datasize; /* strip last '\0' */
185 185 }
186 186
187 187 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */
188 188 static const char **unpackcmdargsnul(const context_t *ctx)
189 189 {
190 190 const char **args = NULL;
191 191 size_t nargs = 0, maxnargs = 0;
192 192 const char *s = ctx->data;
193 193 const char *e = ctx->data + ctx->datasize;
194 194 for (;;) {
195 195 if (nargs + 1 >= maxnargs) { /* including last NULL */
196 196 maxnargs += 256;
197 197 args = reallocx(args, maxnargs * sizeof(args[0]));
198 198 }
199 199 args[nargs] = s;
200 200 nargs++;
201 201 s = memchr(s, '\0', e - s);
202 202 if (!s)
203 203 break;
204 204 s++;
205 205 }
206 206 args[nargs] = NULL;
207 207 return args;
208 208 }
209 209
210 210 static void handlereadrequest(hgclient_t *hgc)
211 211 {
212 212 context_t *ctx = &hgc->ctx;
213 213 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin);
214 214 ctx->datasize = r;
215 215 writeblock(hgc);
216 216 }
217 217
218 218 /* Read single-line */
219 219 static void handlereadlinerequest(hgclient_t *hgc)
220 220 {
221 221 context_t *ctx = &hgc->ctx;
222 222 if (!fgets(ctx->data, ctx->datasize, stdin))
223 223 ctx->data[0] = '\0';
224 224 ctx->datasize = strlen(ctx->data);
225 225 writeblock(hgc);
226 226 }
227 227
228 228 /* Execute the requested command and write exit code */
229 229 static void handlesystemrequest(hgclient_t *hgc)
230 230 {
231 231 context_t *ctx = &hgc->ctx;
232 232 enlargecontext(ctx, ctx->datasize + 1);
233 233 ctx->data[ctx->datasize] = '\0'; /* terminate last string */
234 234
235 235 const char **args = unpackcmdargsnul(ctx);
236 236 if (!args[0] || !args[1])
237 237 abortmsg("missing command or cwd in system request");
238 238 debugmsg("run '%s' at '%s'", args[0], args[1]);
239 239 int32_t r = runshellcmd(args[0], args + 2, args[1]);
240 240 free(args);
241 241
242 242 uint32_t r_n = htonl(r);
243 243 memcpy(ctx->data, &r_n, sizeof(r_n));
244 244 ctx->datasize = sizeof(r_n);
245 245 writeblock(hgc);
246 246 }
247 247
248 248 /* Read response of command execution until receiving 'r'-esult */
249 249 static void handleresponse(hgclient_t *hgc)
250 250 {
251 251 for (;;) {
252 252 readchannel(hgc);
253 253 context_t *ctx = &hgc->ctx;
254 254 debugmsg("response read from channel %c, size %zu",
255 255 ctx->ch, ctx->datasize);
256 256 switch (ctx->ch) {
257 257 case 'o':
258 258 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
259 259 stdout);
260 260 break;
261 261 case 'e':
262 262 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
263 263 stderr);
264 264 break;
265 265 case 'd':
266 266 /* assumes last char is '\n' */
267 267 ctx->data[ctx->datasize - 1] = '\0';
268 268 debugmsg("server: %s", ctx->data);
269 269 break;
270 270 case 'r':
271 271 return;
272 272 case 'I':
273 273 handlereadrequest(hgc);
274 274 break;
275 275 case 'L':
276 276 handlereadlinerequest(hgc);
277 277 break;
278 278 case 'S':
279 279 handlesystemrequest(hgc);
280 280 break;
281 281 default:
282 282 if (isupper(ctx->ch))
283 283 abortmsg("cannot handle response (ch = %c)",
284 284 ctx->ch);
285 285 }
286 286 }
287 287 }
288 288
289 289 static unsigned int parsecapabilities(const char *s, const char *e)
290 290 {
291 291 unsigned int flags = 0;
292 292 while (s < e) {
293 293 const char *t = strchr(s, ' ');
294 294 if (!t || t > e)
295 295 t = e;
296 296 const cappair_t *cap;
297 297 for (cap = captable; cap->flag; ++cap) {
298 298 size_t n = t - s;
299 299 if (strncmp(s, cap->name, n) == 0 &&
300 300 strlen(cap->name) == n) {
301 301 flags |= cap->flag;
302 302 break;
303 303 }
304 304 }
305 305 s = t + 1;
306 306 }
307 307 return flags;
308 308 }
309 309
310 310 static void readhello(hgclient_t *hgc)
311 311 {
312 312 readchannel(hgc);
313 313 context_t *ctx = &hgc->ctx;
314 if (ctx->ch != 'o')
315 abortmsg("unexpected channel of hello message (ch = %c)",
316 ctx->ch);
314 if (ctx->ch != 'o') {
315 char ch = ctx->ch;
316 if (ch == 'e') {
317 /* write early error and will exit */
318 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
319 stderr);
320 handleresponse(hgc);
321 }
322 abortmsg("unexpected channel of hello message (ch = %c)", ch);
323 }
317 324 enlargecontext(ctx, ctx->datasize + 1);
318 325 ctx->data[ctx->datasize] = '\0';
319 326 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize);
320 327
321 328 const char *s = ctx->data;
322 329 const char *const dataend = ctx->data + ctx->datasize;
323 330 while (s < dataend) {
324 331 const char *t = strchr(s, ':');
325 332 if (!t || t[1] != ' ')
326 333 break;
327 334 const char *u = strchr(t + 2, '\n');
328 335 if (!u)
329 336 u = dataend;
330 337 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
331 338 hgc->capflags = parsecapabilities(t + 2, u);
332 339 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
333 340 hgc->pid = strtol(t + 2, NULL, 10);
334 341 }
335 342 s = u + 1;
336 343 }
337 344 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid);
338 345 }
339 346
340 347 static void attachio(hgclient_t *hgc)
341 348 {
342 349 debugmsg("request attachio");
343 350 static const char chcmd[] = "attachio\n";
344 351 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1);
345 352 readchannel(hgc);
346 353 context_t *ctx = &hgc->ctx;
347 354 if (ctx->ch != 'I')
348 355 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch);
349 356
350 357 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
351 358 struct msghdr msgh;
352 359 memset(&msgh, 0, sizeof(msgh));
353 360 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */
354 361 msgh.msg_iov = &iov;
355 362 msgh.msg_iovlen = 1;
356 363 char fdbuf[CMSG_SPACE(sizeof(fds))];
357 364 msgh.msg_control = fdbuf;
358 365 msgh.msg_controllen = sizeof(fdbuf);
359 366 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
360 367 cmsg->cmsg_level = SOL_SOCKET;
361 368 cmsg->cmsg_type = SCM_RIGHTS;
362 369 cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
363 370 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
364 371 msgh.msg_controllen = cmsg->cmsg_len;
365 372 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0);
366 373 if (r < 0)
367 374 abortmsg("sendmsg failed (errno = %d)", errno);
368 375
369 376 handleresponse(hgc);
370 377 int32_t n;
371 378 if (ctx->datasize != sizeof(n))
372 379 abortmsg("unexpected size of attachio result");
373 380 memcpy(&n, ctx->data, sizeof(n));
374 381 n = ntohl(n);
375 382 if (n != sizeof(fds) / sizeof(fds[0]))
376 383 abortmsg("failed to send fds (n = %d)", n);
377 384 }
378 385
379 386 static void chdirtocwd(hgclient_t *hgc)
380 387 {
381 388 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize))
382 389 abortmsg("failed to getcwd (errno = %d)", errno);
383 390 hgc->ctx.datasize = strlen(hgc->ctx.data);
384 391 writeblockrequest(hgc, "chdir");
385 392 }
386 393
387 394 static void forwardumask(hgclient_t *hgc)
388 395 {
389 396 mode_t mask = umask(0);
390 397 umask(mask);
391 398
392 399 static const char command[] = "setumask\n";
393 400 sendall(hgc->sockfd, command, sizeof(command) - 1);
394 401 uint32_t data = htonl(mask);
395 402 sendall(hgc->sockfd, &data, sizeof(data));
396 403 }
397 404
398 405 /*!
399 406 * Open connection to per-user cmdserver
400 407 *
401 408 * If no background server running, returns NULL.
402 409 */
403 410 hgclient_t *hgc_open(const char *sockname)
404 411 {
405 412 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
406 413 if (fd < 0)
407 414 abortmsg("cannot create socket (errno = %d)", errno);
408 415
409 416 /* don't keep fd on fork(), so that it can be closed when the parent
410 417 * process get terminated. */
411 418 int flags = fcntl(fd, F_GETFD);
412 419 if (flags < 0)
413 420 abortmsg("cannot get flags of socket (errno = %d)", errno);
414 421 if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
415 422 abortmsg("cannot set flags of socket (errno = %d)", errno);
416 423
417 424 struct sockaddr_un addr;
418 425 addr.sun_family = AF_UNIX;
419 426 strncpy(addr.sun_path, sockname, sizeof(addr.sun_path));
420 427 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
421 428
422 429 debugmsg("connect to %s", addr.sun_path);
423 430 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
424 431 if (r < 0) {
425 432 close(fd);
426 433 if (errno == ENOENT || errno == ECONNREFUSED)
427 434 return NULL;
428 435 abortmsg("cannot connect to %s (errno = %d)",
429 436 addr.sun_path, errno);
430 437 }
431 438
432 439 hgclient_t *hgc = mallocx(sizeof(hgclient_t));
433 440 memset(hgc, 0, sizeof(*hgc));
434 441 hgc->sockfd = fd;
435 442 initcontext(&hgc->ctx);
436 443
437 444 readhello(hgc);
438 445 if (!(hgc->capflags & CAP_RUNCOMMAND))
439 446 abortmsg("insufficient capability: runcommand");
440 447 if (hgc->capflags & CAP_ATTACHIO)
441 448 attachio(hgc);
442 449 if (hgc->capflags & CAP_CHDIR)
443 450 chdirtocwd(hgc);
444 451 if (hgc->capflags & CAP_SETUMASK)
445 452 forwardumask(hgc);
446 453
447 454 return hgc;
448 455 }
449 456
450 457 /*!
451 458 * Close connection and free allocated memory
452 459 */
453 460 void hgc_close(hgclient_t *hgc)
454 461 {
455 462 assert(hgc);
456 463 freecontext(&hgc->ctx);
457 464 close(hgc->sockfd);
458 465 free(hgc);
459 466 }
460 467
461 468 pid_t hgc_peerpid(const hgclient_t *hgc)
462 469 {
463 470 assert(hgc);
464 471 return hgc->pid;
465 472 }
466 473
467 474 /*!
468 475 * Send command line arguments to let the server load the repo config and check
469 476 * whether it can process our request directly or not.
470 477 * Make sure hgc_setenv is called before calling this.
471 478 *
472 479 * @return - NULL, the server believes it can handle our request, or does not
473 480 * support "validate" command.
474 481 * - a list of strings, the server cannot handle our request and it
475 482 * sent instructions telling us how to fix the issue. See
476 483 * chgserver.py for possible instruction formats.
477 484 * the list should be freed by the caller.
478 485 * the last string is guaranteed to be NULL.
479 486 */
480 487 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
481 488 size_t argsize)
482 489 {
483 490 assert(hgc);
484 491 if (!(hgc->capflags & CAP_VALIDATE))
485 492 return NULL;
486 493
487 494 packcmdargs(&hgc->ctx, args, argsize);
488 495 writeblockrequest(hgc, "validate");
489 496 handleresponse(hgc);
490 497
491 498 /* the server returns '\0' if it can handle our request */
492 499 if (hgc->ctx.datasize <= 1)
493 500 return NULL;
494 501
495 502 /* make sure the buffer is '\0' terminated */
496 503 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
497 504 hgc->ctx.data[hgc->ctx.datasize] = '\0';
498 505 return unpackcmdargsnul(&hgc->ctx);
499 506 }
500 507
501 508 /*!
502 509 * Execute the specified Mercurial command
503 510 *
504 511 * @return result code
505 512 */
506 513 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize)
507 514 {
508 515 assert(hgc);
509 516
510 517 packcmdargs(&hgc->ctx, args, argsize);
511 518 writeblockrequest(hgc, "runcommand");
512 519 handleresponse(hgc);
513 520
514 521 int32_t exitcode_n;
515 522 if (hgc->ctx.datasize != sizeof(exitcode_n)) {
516 523 abortmsg("unexpected size of exitcode");
517 524 }
518 525 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n));
519 526 return ntohl(exitcode_n);
520 527 }
521 528
522 529 /*!
523 530 * (Re-)send client's stdio channels so that the server can access to tty
524 531 */
525 532 void hgc_attachio(hgclient_t *hgc)
526 533 {
527 534 assert(hgc);
528 535 if (!(hgc->capflags & CAP_ATTACHIO))
529 536 return;
530 537 attachio(hgc);
531 538 }
532 539
533 540 /*!
534 541 * Get pager command for the given Mercurial command args
535 542 *
536 543 * If no pager enabled, returns NULL. The return value becomes invalid
537 544 * once you run another request to hgc.
538 545 */
539 546 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
540 547 size_t argsize)
541 548 {
542 549 assert(hgc);
543 550
544 551 if (!(hgc->capflags & CAP_GETPAGER))
545 552 return NULL;
546 553
547 554 packcmdargs(&hgc->ctx, args, argsize);
548 555 writeblockrequest(hgc, "getpager");
549 556 handleresponse(hgc);
550 557
551 558 if (hgc->ctx.datasize < 1 || hgc->ctx.data[0] == '\0')
552 559 return NULL;
553 560 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
554 561 hgc->ctx.data[hgc->ctx.datasize] = '\0';
555 562 return hgc->ctx.data;
556 563 }
557 564
558 565 /*!
559 566 * Update server's environment variables
560 567 *
561 568 * @param envp list of environment variables in "NAME=VALUE" format,
562 569 * terminated by NULL.
563 570 */
564 571 void hgc_setenv(hgclient_t *hgc, const char *const envp[])
565 572 {
566 573 assert(hgc && envp);
567 574 if (!(hgc->capflags & CAP_SETENV))
568 575 return;
569 576 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1);
570 577 writeblockrequest(hgc, "setenv");
571 578 }
General Comments 0
You need to be logged in to leave comments. Login now