Mercurial > hg
annotate contrib/chg/hgclient.c @ 28769:222f482930c8
chg: make connect debug message less repetitive
Before this patch, "connect to" debug message is printed repeatedly because
a previous patch changed how the chg client decides the server is ready to be
connected.
This patch revises the places we print connect debug messages so they are less
repetitive without losing useful information.
author | Jun Wu <quark@fb.com> |
---|---|
date | Mon, 04 Apr 2016 02:36:05 +0100 |
parents | 8e5312f8df30 |
children | 7f6e0a15189b |
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); | |
28551
8e5312f8df30
chg: downgrade "failed to read channel" from abortmsg to debugmsg
Jun Wu <quark@fb.com>
parents:
28535
diff
changeset
|
109 if (rsize != sizeof(hgc->ctx.ch)) { |
8e5312f8df30
chg: downgrade "failed to read channel" from abortmsg to debugmsg
Jun Wu <quark@fb.com>
parents:
28535
diff
changeset
|
110 /* server would have exception and traceback would be printed */ |
8e5312f8df30
chg: downgrade "failed to read channel" from abortmsg to debugmsg
Jun Wu <quark@fb.com>
parents:
28535
diff
changeset
|
111 debugmsg("failed to read channel"); |
8e5312f8df30
chg: downgrade "failed to read channel" from abortmsg to debugmsg
Jun Wu <quark@fb.com>
parents:
28535
diff
changeset
|
112 exit(255); |
8e5312f8df30
chg: downgrade "failed to read channel" from abortmsg to debugmsg
Jun Wu <quark@fb.com>
parents:
28535
diff
changeset
|
113 } |
28060 | 114 |
115 uint32_t datasize_n; | |
116 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0); | |
117 if (rsize != sizeof(datasize_n)) | |
118 abortmsg("failed to read data size"); | |
119 | |
120 /* datasize denotes the maximum size to write if input request */ | |
121 hgc->ctx.datasize = ntohl(datasize_n); | |
122 enlargecontext(&hgc->ctx, hgc->ctx.datasize); | |
123 | |
124 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S') | |
125 return; /* assumes input request */ | |
126 | |
127 size_t cursize = 0; | |
128 while (cursize < hgc->ctx.datasize) { | |
129 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize, | |
130 hgc->ctx.datasize - cursize, 0); | |
131 if (rsize < 0) | |
132 abortmsg("failed to read data block"); | |
133 cursize += rsize; | |
134 } | |
135 } | |
136 | |
137 static void sendall(int sockfd, const void *data, size_t datasize) | |
138 { | |
139 const char *p = data; | |
140 const char *const endp = p + datasize; | |
141 while (p < endp) { | |
142 ssize_t r = send(sockfd, p, endp - p, 0); | |
143 if (r < 0) | |
144 abortmsg("cannot communicate (errno = %d)", errno); | |
145 p += r; | |
146 } | |
147 } | |
148 | |
149 /* Write lengh-data block to cmdserver */ | |
150 static void writeblock(const hgclient_t *hgc) | |
151 { | |
152 assert(hgc); | |
153 | |
154 const uint32_t datasize_n = htonl(hgc->ctx.datasize); | |
155 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n)); | |
156 | |
157 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize); | |
158 } | |
159 | |
160 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd) | |
161 { | |
162 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize); | |
163 | |
164 char buf[strlen(chcmd) + 1]; | |
165 memcpy(buf, chcmd, sizeof(buf) - 1); | |
166 buf[sizeof(buf) - 1] = '\n'; | |
167 sendall(hgc->sockfd, buf, sizeof(buf)); | |
168 | |
169 writeblock(hgc); | |
170 } | |
171 | |
172 /* Build '\0'-separated list of args. argsize < 0 denotes that args are | |
173 * terminated by NULL. */ | |
174 static void packcmdargs(context_t *ctx, const char *const args[], | |
175 ssize_t argsize) | |
176 { | |
177 ctx->datasize = 0; | |
178 const char *const *const end = (argsize >= 0) ? args + argsize : NULL; | |
179 for (const char *const *it = args; it != end && *it; ++it) { | |
180 const size_t n = strlen(*it) + 1; /* include '\0' */ | |
181 enlargecontext(ctx, ctx->datasize + n); | |
182 memcpy(ctx->data + ctx->datasize, *it, n); | |
183 ctx->datasize += n; | |
184 } | |
185 | |
186 if (ctx->datasize > 0) | |
187 --ctx->datasize; /* strip last '\0' */ | |
188 } | |
189 | |
190 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */ | |
191 static const char **unpackcmdargsnul(const context_t *ctx) | |
192 { | |
193 const char **args = NULL; | |
194 size_t nargs = 0, maxnargs = 0; | |
195 const char *s = ctx->data; | |
196 const char *e = ctx->data + ctx->datasize; | |
197 for (;;) { | |
198 if (nargs + 1 >= maxnargs) { /* including last NULL */ | |
199 maxnargs += 256; | |
28166
c6e0c2533e5f
chg: use mallocx and reallocx in hgclient
Jun Wu <quark@fb.com>
parents:
28160
diff
changeset
|
200 args = reallocx(args, maxnargs * sizeof(args[0])); |
28060 | 201 } |
202 args[nargs] = s; | |
203 nargs++; | |
204 s = memchr(s, '\0', e - s); | |
205 if (!s) | |
206 break; | |
207 s++; | |
208 } | |
209 args[nargs] = NULL; | |
210 return args; | |
211 } | |
212 | |
213 static void handlereadrequest(hgclient_t *hgc) | |
214 { | |
215 context_t *ctx = &hgc->ctx; | |
216 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin); | |
217 ctx->datasize = r; | |
218 writeblock(hgc); | |
219 } | |
220 | |
221 /* Read single-line */ | |
222 static void handlereadlinerequest(hgclient_t *hgc) | |
223 { | |
224 context_t *ctx = &hgc->ctx; | |
225 if (!fgets(ctx->data, ctx->datasize, stdin)) | |
226 ctx->data[0] = '\0'; | |
227 ctx->datasize = strlen(ctx->data); | |
228 writeblock(hgc); | |
229 } | |
230 | |
231 /* Execute the requested command and write exit code */ | |
232 static void handlesystemrequest(hgclient_t *hgc) | |
233 { | |
234 context_t *ctx = &hgc->ctx; | |
235 enlargecontext(ctx, ctx->datasize + 1); | |
236 ctx->data[ctx->datasize] = '\0'; /* terminate last string */ | |
237 | |
238 const char **args = unpackcmdargsnul(ctx); | |
239 if (!args[0] || !args[1]) | |
240 abortmsg("missing command or cwd in system request"); | |
241 debugmsg("run '%s' at '%s'", args[0], args[1]); | |
242 int32_t r = runshellcmd(args[0], args + 2, args[1]); | |
243 free(args); | |
244 | |
245 uint32_t r_n = htonl(r); | |
246 memcpy(ctx->data, &r_n, sizeof(r_n)); | |
247 ctx->datasize = sizeof(r_n); | |
248 writeblock(hgc); | |
249 } | |
250 | |
251 /* Read response of command execution until receiving 'r'-esult */ | |
252 static void handleresponse(hgclient_t *hgc) | |
253 { | |
254 for (;;) { | |
255 readchannel(hgc); | |
256 context_t *ctx = &hgc->ctx; | |
257 debugmsg("response read from channel %c, size %zu", | |
258 ctx->ch, ctx->datasize); | |
259 switch (ctx->ch) { | |
260 case 'o': | |
261 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize, | |
262 stdout); | |
263 break; | |
264 case 'e': | |
265 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize, | |
266 stderr); | |
267 break; | |
268 case 'd': | |
269 /* assumes last char is '\n' */ | |
270 ctx->data[ctx->datasize - 1] = '\0'; | |
271 debugmsg("server: %s", ctx->data); | |
272 break; | |
273 case 'r': | |
274 return; | |
275 case 'I': | |
276 handlereadrequest(hgc); | |
277 break; | |
278 case 'L': | |
279 handlereadlinerequest(hgc); | |
280 break; | |
281 case 'S': | |
282 handlesystemrequest(hgc); | |
283 break; | |
284 default: | |
285 if (isupper(ctx->ch)) | |
286 abortmsg("cannot handle response (ch = %c)", | |
287 ctx->ch); | |
288 } | |
289 } | |
290 } | |
291 | |
292 static unsigned int parsecapabilities(const char *s, const char *e) | |
293 { | |
294 unsigned int flags = 0; | |
295 while (s < e) { | |
296 const char *t = strchr(s, ' '); | |
297 if (!t || t > e) | |
298 t = e; | |
299 const cappair_t *cap; | |
300 for (cap = captable; cap->flag; ++cap) { | |
301 size_t n = t - s; | |
302 if (strncmp(s, cap->name, n) == 0 && | |
303 strlen(cap->name) == n) { | |
304 flags |= cap->flag; | |
305 break; | |
306 } | |
307 } | |
308 s = t + 1; | |
309 } | |
310 return flags; | |
311 } | |
312 | |
313 static void readhello(hgclient_t *hgc) | |
314 { | |
315 readchannel(hgc); | |
316 context_t *ctx = &hgc->ctx; | |
28512
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
317 if (ctx->ch != 'o') { |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
318 char ch = ctx->ch; |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
319 if (ch == 'e') { |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
320 /* write early error and will exit */ |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
321 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize, |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
322 stderr); |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
323 handleresponse(hgc); |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
324 } |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
325 abortmsg("unexpected channel of hello message (ch = %c)", ch); |
b957b4c6cad8
chg: provide early exception to user
Yuya Nishihara <yuya@tcha.org>
parents:
28356
diff
changeset
|
326 } |
28060 | 327 enlargecontext(ctx, ctx->datasize + 1); |
328 ctx->data[ctx->datasize] = '\0'; | |
329 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize); | |
330 | |
331 const char *s = ctx->data; | |
332 const char *const dataend = ctx->data + ctx->datasize; | |
333 while (s < dataend) { | |
334 const char *t = strchr(s, ':'); | |
335 if (!t || t[1] != ' ') | |
336 break; | |
337 const char *u = strchr(t + 2, '\n'); | |
338 if (!u) | |
339 u = dataend; | |
340 if (strncmp(s, "capabilities:", t - s + 1) == 0) { | |
341 hgc->capflags = parsecapabilities(t + 2, u); | |
342 } else if (strncmp(s, "pid:", t - s + 1) == 0) { | |
343 hgc->pid = strtol(t + 2, NULL, 10); | |
344 } | |
345 s = u + 1; | |
346 } | |
347 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid); | |
348 } | |
349 | |
350 static void attachio(hgclient_t *hgc) | |
351 { | |
352 debugmsg("request attachio"); | |
353 static const char chcmd[] = "attachio\n"; | |
354 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1); | |
355 readchannel(hgc); | |
356 context_t *ctx = &hgc->ctx; | |
357 if (ctx->ch != 'I') | |
358 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch); | |
359 | |
360 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; | |
361 struct msghdr msgh; | |
362 memset(&msgh, 0, sizeof(msgh)); | |
363 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */ | |
364 msgh.msg_iov = &iov; | |
365 msgh.msg_iovlen = 1; | |
366 char fdbuf[CMSG_SPACE(sizeof(fds))]; | |
367 msgh.msg_control = fdbuf; | |
368 msgh.msg_controllen = sizeof(fdbuf); | |
369 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); | |
370 cmsg->cmsg_level = SOL_SOCKET; | |
371 cmsg->cmsg_type = SCM_RIGHTS; | |
372 cmsg->cmsg_len = CMSG_LEN(sizeof(fds)); | |
373 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds)); | |
374 msgh.msg_controllen = cmsg->cmsg_len; | |
375 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0); | |
376 if (r < 0) | |
377 abortmsg("sendmsg failed (errno = %d)", errno); | |
378 | |
379 handleresponse(hgc); | |
380 int32_t n; | |
381 if (ctx->datasize != sizeof(n)) | |
382 abortmsg("unexpected size of attachio result"); | |
383 memcpy(&n, ctx->data, sizeof(n)); | |
384 n = ntohl(n); | |
385 if (n != sizeof(fds) / sizeof(fds[0])) | |
386 abortmsg("failed to send fds (n = %d)", n); | |
387 } | |
388 | |
389 static void chdirtocwd(hgclient_t *hgc) | |
390 { | |
391 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize)) | |
392 abortmsg("failed to getcwd (errno = %d)", errno); | |
393 hgc->ctx.datasize = strlen(hgc->ctx.data); | |
394 writeblockrequest(hgc, "chdir"); | |
395 } | |
396 | |
28160
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
397 static void forwardumask(hgclient_t *hgc) |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
398 { |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
399 mode_t mask = umask(0); |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
400 umask(mask); |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
401 |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
402 static const char command[] = "setumask\n"; |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
403 sendall(hgc->sockfd, command, sizeof(command) - 1); |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
404 uint32_t data = htonl(mask); |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
405 sendall(hgc->sockfd, &data, sizeof(data)); |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
406 } |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
407 |
28060 | 408 /*! |
409 * Open connection to per-user cmdserver | |
410 * | |
411 * If no background server running, returns NULL. | |
412 */ | |
413 hgclient_t *hgc_open(const char *sockname) | |
414 { | |
415 int fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
416 if (fd < 0) | |
417 abortmsg("cannot create socket (errno = %d)", errno); | |
418 | |
419 /* don't keep fd on fork(), so that it can be closed when the parent | |
420 * process get terminated. */ | |
421 int flags = fcntl(fd, F_GETFD); | |
422 if (flags < 0) | |
423 abortmsg("cannot get flags of socket (errno = %d)", errno); | |
424 if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) | |
425 abortmsg("cannot set flags of socket (errno = %d)", errno); | |
426 | |
427 struct sockaddr_un addr; | |
428 addr.sun_family = AF_UNIX; | |
429 strncpy(addr.sun_path, sockname, sizeof(addr.sun_path)); | |
430 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; | |
431 | |
432 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); | |
433 if (r < 0) { | |
434 close(fd); | |
435 if (errno == ENOENT || errno == ECONNREFUSED) | |
436 return NULL; | |
437 abortmsg("cannot connect to %s (errno = %d)", | |
438 addr.sun_path, errno); | |
439 } | |
28769
222f482930c8
chg: make connect debug message less repetitive
Jun Wu <quark@fb.com>
parents:
28551
diff
changeset
|
440 debugmsg("connected to %s", addr.sun_path); |
28060 | 441 |
28166
c6e0c2533e5f
chg: use mallocx and reallocx in hgclient
Jun Wu <quark@fb.com>
parents:
28160
diff
changeset
|
442 hgclient_t *hgc = mallocx(sizeof(hgclient_t)); |
28060 | 443 memset(hgc, 0, sizeof(*hgc)); |
444 hgc->sockfd = fd; | |
445 initcontext(&hgc->ctx); | |
446 | |
447 readhello(hgc); | |
448 if (!(hgc->capflags & CAP_RUNCOMMAND)) | |
449 abortmsg("insufficient capability: runcommand"); | |
450 if (hgc->capflags & CAP_ATTACHIO) | |
451 attachio(hgc); | |
452 if (hgc->capflags & CAP_CHDIR) | |
453 chdirtocwd(hgc); | |
28160
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
454 if (hgc->capflags & CAP_SETUMASK) |
098cb7bd46a7
chg: forward umask from client to server
Jun Wu <quark@fb.com>
parents:
28060
diff
changeset
|
455 forwardumask(hgc); |
28060 | 456 |
457 return hgc; | |
458 } | |
459 | |
460 /*! | |
461 * Close connection and free allocated memory | |
462 */ | |
463 void hgc_close(hgclient_t *hgc) | |
464 { | |
465 assert(hgc); | |
466 freecontext(&hgc->ctx); | |
467 close(hgc->sockfd); | |
468 free(hgc); | |
469 } | |
470 | |
471 pid_t hgc_peerpid(const hgclient_t *hgc) | |
472 { | |
473 assert(hgc); | |
474 return hgc->pid; | |
475 } | |
476 | |
477 /*! | |
28356
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
478 * 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
|
479 * whether it can process our request directly or not. |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
480 * Make sure hgc_setenv is called before calling this. |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
481 * |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
482 * @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
|
483 * support "validate" command. |
28535
aa082a8125da
chgserver: add an explicit "reconnect" instruction to validate
Jun Wu <quark@fb.com>
parents:
28512
diff
changeset
|
484 * - a list of strings, the server probably cannot handle our request |
aa082a8125da
chgserver: add an explicit "reconnect" instruction to validate
Jun Wu <quark@fb.com>
parents:
28512
diff
changeset
|
485 * and it sent instructions telling us what to do next. See |
28356
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
486 * chgserver.py for possible instruction formats. |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
487 * the list should be freed by the caller. |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
488 * the last string is guaranteed to be NULL. |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
489 */ |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
490 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
|
491 size_t argsize) |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
492 { |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
493 assert(hgc); |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
494 if (!(hgc->capflags & CAP_VALIDATE)) |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
495 return NULL; |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
496 |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
497 packcmdargs(&hgc->ctx, args, argsize); |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
498 writeblockrequest(hgc, "validate"); |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
499 handleresponse(hgc); |
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 /* 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
|
502 if (hgc->ctx.datasize <= 1) |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
503 return NULL; |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
504 |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
505 /* make sure the buffer is '\0' terminated */ |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
506 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1); |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
507 hgc->ctx.data[hgc->ctx.datasize] = '\0'; |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
508 return unpackcmdargsnul(&hgc->ctx); |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
509 } |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
510 |
a5c773acb018
chg: implement validate in hgclient
Jun Wu <quark@fb.com>
parents:
28166
diff
changeset
|
511 /*! |
28060 | 512 * Execute the specified Mercurial command |
513 * | |
514 * @return result code | |
515 */ | |
516 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize) | |
517 { | |
518 assert(hgc); | |
519 | |
520 packcmdargs(&hgc->ctx, args, argsize); | |
521 writeblockrequest(hgc, "runcommand"); | |
522 handleresponse(hgc); | |
523 | |
524 int32_t exitcode_n; | |
525 if (hgc->ctx.datasize != sizeof(exitcode_n)) { | |
526 abortmsg("unexpected size of exitcode"); | |
527 } | |
528 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n)); | |
529 return ntohl(exitcode_n); | |
530 } | |
531 | |
532 /*! | |
533 * (Re-)send client's stdio channels so that the server can access to tty | |
534 */ | |
535 void hgc_attachio(hgclient_t *hgc) | |
536 { | |
537 assert(hgc); | |
538 if (!(hgc->capflags & CAP_ATTACHIO)) | |
539 return; | |
540 attachio(hgc); | |
541 } | |
542 | |
543 /*! | |
544 * Get pager command for the given Mercurial command args | |
545 * | |
546 * If no pager enabled, returns NULL. The return value becomes invalid | |
547 * once you run another request to hgc. | |
548 */ | |
549 const char *hgc_getpager(hgclient_t *hgc, const char *const args[], | |
550 size_t argsize) | |
551 { | |
552 assert(hgc); | |
553 | |
554 if (!(hgc->capflags & CAP_GETPAGER)) | |
555 return NULL; | |
556 | |
557 packcmdargs(&hgc->ctx, args, argsize); | |
558 writeblockrequest(hgc, "getpager"); | |
559 handleresponse(hgc); | |
560 | |
561 if (hgc->ctx.datasize < 1 || hgc->ctx.data[0] == '\0') | |
562 return NULL; | |
563 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1); | |
564 hgc->ctx.data[hgc->ctx.datasize] = '\0'; | |
565 return hgc->ctx.data; | |
566 } | |
567 | |
568 /*! | |
569 * Update server's environment variables | |
570 * | |
571 * @param envp list of environment variables in "NAME=VALUE" format, | |
572 * terminated by NULL. | |
573 */ | |
574 void hgc_setenv(hgclient_t *hgc, const char *const envp[]) | |
575 { | |
576 assert(hgc && envp); | |
577 if (!(hgc->capflags & CAP_SETENV)) | |
578 return; | |
579 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1); | |
580 writeblockrequest(hgc, "setenv"); | |
581 } |