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