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