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