##// END OF EJS Templates
chg: remove getpager support...
Jun Wu -
r30741:fde9692a default
parent child Browse files
Show More
@@ -1,443 +1,439 b''
1 /*
1 /*
2 * A fast client for Mercurial command server
2 * A fast client for Mercurial command server
3 *
3 *
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 *
5 *
6 * This software may be used and distributed according to the terms of the
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.
7 * GNU General Public License version 2 or any later version.
8 */
8 */
9
9
10 #include <assert.h>
10 #include <assert.h>
11 #include <errno.h>
11 #include <errno.h>
12 #include <fcntl.h>
12 #include <fcntl.h>
13 #include <signal.h>
13 #include <signal.h>
14 #include <stdio.h>
14 #include <stdio.h>
15 #include <stdlib.h>
15 #include <stdlib.h>
16 #include <string.h>
16 #include <string.h>
17 #include <sys/file.h>
17 #include <sys/file.h>
18 #include <sys/stat.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
19 #include <sys/types.h>
20 #include <sys/un.h>
20 #include <sys/un.h>
21 #include <sys/wait.h>
21 #include <sys/wait.h>
22 #include <time.h>
22 #include <time.h>
23 #include <unistd.h>
23 #include <unistd.h>
24
24
25 #include "hgclient.h"
25 #include "hgclient.h"
26 #include "procutil.h"
26 #include "procutil.h"
27 #include "util.h"
27 #include "util.h"
28
28
29 #ifndef PATH_MAX
29 #ifndef PATH_MAX
30 #define PATH_MAX 4096
30 #define PATH_MAX 4096
31 #endif
31 #endif
32
32
33 struct cmdserveropts {
33 struct cmdserveropts {
34 char sockname[PATH_MAX];
34 char sockname[PATH_MAX];
35 char initsockname[PATH_MAX];
35 char initsockname[PATH_MAX];
36 char redirectsockname[PATH_MAX];
36 char redirectsockname[PATH_MAX];
37 size_t argsize;
37 size_t argsize;
38 const char **args;
38 const char **args;
39 };
39 };
40
40
41 static void initcmdserveropts(struct cmdserveropts *opts) {
41 static void initcmdserveropts(struct cmdserveropts *opts) {
42 memset(opts, 0, sizeof(struct cmdserveropts));
42 memset(opts, 0, sizeof(struct cmdserveropts));
43 }
43 }
44
44
45 static void freecmdserveropts(struct cmdserveropts *opts) {
45 static void freecmdserveropts(struct cmdserveropts *opts) {
46 free(opts->args);
46 free(opts->args);
47 opts->args = NULL;
47 opts->args = NULL;
48 opts->argsize = 0;
48 opts->argsize = 0;
49 }
49 }
50
50
51 /*
51 /*
52 * Test if an argument is a sensitive flag that should be passed to the server.
52 * Test if an argument is a sensitive flag that should be passed to the server.
53 * Return 0 if not, otherwise the number of arguments starting from the current
53 * Return 0 if not, otherwise the number of arguments starting from the current
54 * one that should be passed to the server.
54 * one that should be passed to the server.
55 */
55 */
56 static size_t testsensitiveflag(const char *arg)
56 static size_t testsensitiveflag(const char *arg)
57 {
57 {
58 static const struct {
58 static const struct {
59 const char *name;
59 const char *name;
60 size_t narg;
60 size_t narg;
61 } flags[] = {
61 } flags[] = {
62 {"--config", 1},
62 {"--config", 1},
63 {"--cwd", 1},
63 {"--cwd", 1},
64 {"--repo", 1},
64 {"--repo", 1},
65 {"--repository", 1},
65 {"--repository", 1},
66 {"--traceback", 0},
66 {"--traceback", 0},
67 {"-R", 1},
67 {"-R", 1},
68 };
68 };
69 size_t i;
69 size_t i;
70 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
70 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
71 size_t len = strlen(flags[i].name);
71 size_t len = strlen(flags[i].name);
72 size_t narg = flags[i].narg;
72 size_t narg = flags[i].narg;
73 if (memcmp(arg, flags[i].name, len) == 0) {
73 if (memcmp(arg, flags[i].name, len) == 0) {
74 if (arg[len] == '\0') {
74 if (arg[len] == '\0') {
75 /* --flag (value) */
75 /* --flag (value) */
76 return narg + 1;
76 return narg + 1;
77 } else if (arg[len] == '=' && narg > 0) {
77 } else if (arg[len] == '=' && narg > 0) {
78 /* --flag=value */
78 /* --flag=value */
79 return 1;
79 return 1;
80 } else if (flags[i].name[1] != '-') {
80 } else if (flags[i].name[1] != '-') {
81 /* short flag */
81 /* short flag */
82 return 1;
82 return 1;
83 }
83 }
84 }
84 }
85 }
85 }
86 return 0;
86 return 0;
87 }
87 }
88
88
89 /*
89 /*
90 * Parse argv[] and put sensitive flags to opts->args
90 * Parse argv[] and put sensitive flags to opts->args
91 */
91 */
92 static void setcmdserverargs(struct cmdserveropts *opts,
92 static void setcmdserverargs(struct cmdserveropts *opts,
93 int argc, const char *argv[])
93 int argc, const char *argv[])
94 {
94 {
95 size_t i, step;
95 size_t i, step;
96 opts->argsize = 0;
96 opts->argsize = 0;
97 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
97 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
98 if (!argv[i])
98 if (!argv[i])
99 continue; /* pass clang-analyse */
99 continue; /* pass clang-analyse */
100 if (strcmp(argv[i], "--") == 0)
100 if (strcmp(argv[i], "--") == 0)
101 break;
101 break;
102 size_t n = testsensitiveflag(argv[i]);
102 size_t n = testsensitiveflag(argv[i]);
103 if (n == 0 || i + n > (size_t)argc)
103 if (n == 0 || i + n > (size_t)argc)
104 continue;
104 continue;
105 opts->args = reallocx(opts->args,
105 opts->args = reallocx(opts->args,
106 (n + opts->argsize) * sizeof(char *));
106 (n + opts->argsize) * sizeof(char *));
107 memcpy(opts->args + opts->argsize, argv + i,
107 memcpy(opts->args + opts->argsize, argv + i,
108 sizeof(char *) * n);
108 sizeof(char *) * n);
109 opts->argsize += n;
109 opts->argsize += n;
110 step = n;
110 step = n;
111 }
111 }
112 }
112 }
113
113
114 static void preparesockdir(const char *sockdir)
114 static void preparesockdir(const char *sockdir)
115 {
115 {
116 int r;
116 int r;
117 r = mkdir(sockdir, 0700);
117 r = mkdir(sockdir, 0700);
118 if (r < 0 && errno != EEXIST)
118 if (r < 0 && errno != EEXIST)
119 abortmsgerrno("cannot create sockdir %s", sockdir);
119 abortmsgerrno("cannot create sockdir %s", sockdir);
120
120
121 struct stat st;
121 struct stat st;
122 r = lstat(sockdir, &st);
122 r = lstat(sockdir, &st);
123 if (r < 0)
123 if (r < 0)
124 abortmsgerrno("cannot stat %s", sockdir);
124 abortmsgerrno("cannot stat %s", sockdir);
125 if (!S_ISDIR(st.st_mode))
125 if (!S_ISDIR(st.st_mode))
126 abortmsg("cannot create sockdir %s (file exists)", sockdir);
126 abortmsg("cannot create sockdir %s (file exists)", sockdir);
127 if (st.st_uid != geteuid() || st.st_mode & 0077)
127 if (st.st_uid != geteuid() || st.st_mode & 0077)
128 abortmsg("insecure sockdir %s", sockdir);
128 abortmsg("insecure sockdir %s", sockdir);
129 }
129 }
130
130
131 static void getdefaultsockdir(char sockdir[], size_t size)
131 static void getdefaultsockdir(char sockdir[], size_t size)
132 {
132 {
133 /* by default, put socket file in secure directory
133 /* by default, put socket file in secure directory
134 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
134 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
135 * (permission of socket file may be ignored on some Unices) */
135 * (permission of socket file may be ignored on some Unices) */
136 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
136 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
137 int r;
137 int r;
138 if (runtimedir) {
138 if (runtimedir) {
139 r = snprintf(sockdir, size, "%s/chg", runtimedir);
139 r = snprintf(sockdir, size, "%s/chg", runtimedir);
140 } else {
140 } else {
141 const char *tmpdir = getenv("TMPDIR");
141 const char *tmpdir = getenv("TMPDIR");
142 if (!tmpdir)
142 if (!tmpdir)
143 tmpdir = "/tmp";
143 tmpdir = "/tmp";
144 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
144 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
145 }
145 }
146 if (r < 0 || (size_t)r >= size)
146 if (r < 0 || (size_t)r >= size)
147 abortmsg("too long TMPDIR (r = %d)", r);
147 abortmsg("too long TMPDIR (r = %d)", r);
148 }
148 }
149
149
150 static void setcmdserveropts(struct cmdserveropts *opts)
150 static void setcmdserveropts(struct cmdserveropts *opts)
151 {
151 {
152 int r;
152 int r;
153 char sockdir[PATH_MAX];
153 char sockdir[PATH_MAX];
154 const char *envsockname = getenv("CHGSOCKNAME");
154 const char *envsockname = getenv("CHGSOCKNAME");
155 if (!envsockname) {
155 if (!envsockname) {
156 getdefaultsockdir(sockdir, sizeof(sockdir));
156 getdefaultsockdir(sockdir, sizeof(sockdir));
157 preparesockdir(sockdir);
157 preparesockdir(sockdir);
158 }
158 }
159
159
160 const char *basename = (envsockname) ? envsockname : sockdir;
160 const char *basename = (envsockname) ? envsockname : sockdir;
161 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
161 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
162 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
162 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
163 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
163 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
164 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
164 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
165 r = snprintf(opts->initsockname, sizeof(opts->initsockname),
165 r = snprintf(opts->initsockname, sizeof(opts->initsockname),
166 "%s.%u", opts->sockname, (unsigned)getpid());
166 "%s.%u", opts->sockname, (unsigned)getpid());
167 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
167 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
168 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
168 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
169 }
169 }
170
170
171 static const char *gethgcmd(void)
171 static const char *gethgcmd(void)
172 {
172 {
173 static const char *hgcmd = NULL;
173 static const char *hgcmd = NULL;
174 if (!hgcmd) {
174 if (!hgcmd) {
175 hgcmd = getenv("CHGHG");
175 hgcmd = getenv("CHGHG");
176 if (!hgcmd || hgcmd[0] == '\0')
176 if (!hgcmd || hgcmd[0] == '\0')
177 hgcmd = getenv("HG");
177 hgcmd = getenv("HG");
178 if (!hgcmd || hgcmd[0] == '\0')
178 if (!hgcmd || hgcmd[0] == '\0')
179 #ifdef HGPATH
179 #ifdef HGPATH
180 hgcmd = (HGPATH);
180 hgcmd = (HGPATH);
181 #else
181 #else
182 hgcmd = "hg";
182 hgcmd = "hg";
183 #endif
183 #endif
184 }
184 }
185 return hgcmd;
185 return hgcmd;
186 }
186 }
187
187
188 static void execcmdserver(const struct cmdserveropts *opts)
188 static void execcmdserver(const struct cmdserveropts *opts)
189 {
189 {
190 const char *hgcmd = gethgcmd();
190 const char *hgcmd = gethgcmd();
191
191
192 const char *baseargv[] = {
192 const char *baseargv[] = {
193 hgcmd,
193 hgcmd,
194 "serve",
194 "serve",
195 "--cmdserver", "chgunix",
195 "--cmdserver", "chgunix",
196 "--address", opts->initsockname,
196 "--address", opts->initsockname,
197 "--daemon-postexec", "chdir:/",
197 "--daemon-postexec", "chdir:/",
198 };
198 };
199 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
199 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
200 size_t argsize = baseargvsize + opts->argsize + 1;
200 size_t argsize = baseargvsize + opts->argsize + 1;
201
201
202 const char **argv = mallocx(sizeof(char *) * argsize);
202 const char **argv = mallocx(sizeof(char *) * argsize);
203 memcpy(argv, baseargv, sizeof(baseargv));
203 memcpy(argv, baseargv, sizeof(baseargv));
204 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
204 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
205 argv[argsize - 1] = NULL;
205 argv[argsize - 1] = NULL;
206
206
207 if (putenv("CHGINTERNALMARK=") != 0)
207 if (putenv("CHGINTERNALMARK=") != 0)
208 abortmsgerrno("failed to putenv");
208 abortmsgerrno("failed to putenv");
209 if (execvp(hgcmd, (char **)argv) < 0)
209 if (execvp(hgcmd, (char **)argv) < 0)
210 abortmsgerrno("failed to exec cmdserver");
210 abortmsgerrno("failed to exec cmdserver");
211 free(argv);
211 free(argv);
212 }
212 }
213
213
214 /* Retry until we can connect to the server. Give up after some time. */
214 /* Retry until we can connect to the server. Give up after some time. */
215 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
215 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
216 {
216 {
217 static const struct timespec sleepreq = {0, 10 * 1000000};
217 static const struct timespec sleepreq = {0, 10 * 1000000};
218 int pst = 0;
218 int pst = 0;
219
219
220 debugmsg("try connect to %s repeatedly", opts->initsockname);
220 debugmsg("try connect to %s repeatedly", opts->initsockname);
221
221
222 unsigned int timeoutsec = 60; /* default: 60 seconds */
222 unsigned int timeoutsec = 60; /* default: 60 seconds */
223 const char *timeoutenv = getenv("CHGTIMEOUT");
223 const char *timeoutenv = getenv("CHGTIMEOUT");
224 if (timeoutenv)
224 if (timeoutenv)
225 sscanf(timeoutenv, "%u", &timeoutsec);
225 sscanf(timeoutenv, "%u", &timeoutsec);
226
226
227 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
227 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
228 hgclient_t *hgc = hgc_open(opts->initsockname);
228 hgclient_t *hgc = hgc_open(opts->initsockname);
229 if (hgc) {
229 if (hgc) {
230 debugmsg("rename %s to %s", opts->initsockname,
230 debugmsg("rename %s to %s", opts->initsockname,
231 opts->sockname);
231 opts->sockname);
232 int r = rename(opts->initsockname, opts->sockname);
232 int r = rename(opts->initsockname, opts->sockname);
233 if (r != 0)
233 if (r != 0)
234 abortmsgerrno("cannot rename");
234 abortmsgerrno("cannot rename");
235 return hgc;
235 return hgc;
236 }
236 }
237
237
238 if (pid > 0) {
238 if (pid > 0) {
239 /* collect zombie if child process fails to start */
239 /* collect zombie if child process fails to start */
240 int r = waitpid(pid, &pst, WNOHANG);
240 int r = waitpid(pid, &pst, WNOHANG);
241 if (r != 0)
241 if (r != 0)
242 goto cleanup;
242 goto cleanup;
243 }
243 }
244
244
245 nanosleep(&sleepreq, NULL);
245 nanosleep(&sleepreq, NULL);
246 }
246 }
247
247
248 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
248 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
249 return NULL;
249 return NULL;
250
250
251 cleanup:
251 cleanup:
252 if (WIFEXITED(pst)) {
252 if (WIFEXITED(pst)) {
253 if (WEXITSTATUS(pst) == 0)
253 if (WEXITSTATUS(pst) == 0)
254 abortmsg("could not connect to cmdserver "
254 abortmsg("could not connect to cmdserver "
255 "(exited with status 0)");
255 "(exited with status 0)");
256 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
256 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
257 exit(WEXITSTATUS(pst));
257 exit(WEXITSTATUS(pst));
258 } else if (WIFSIGNALED(pst)) {
258 } else if (WIFSIGNALED(pst)) {
259 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
259 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
260 } else {
260 } else {
261 abortmsg("error while waiting for cmdserver");
261 abortmsg("error while waiting for cmdserver");
262 }
262 }
263 return NULL;
263 return NULL;
264 }
264 }
265
265
266 /* Connect to a cmdserver. Will start a new server on demand. */
266 /* Connect to a cmdserver. Will start a new server on demand. */
267 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
267 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
268 {
268 {
269 const char *sockname = opts->redirectsockname[0] ?
269 const char *sockname = opts->redirectsockname[0] ?
270 opts->redirectsockname : opts->sockname;
270 opts->redirectsockname : opts->sockname;
271 debugmsg("try connect to %s", sockname);
271 debugmsg("try connect to %s", sockname);
272 hgclient_t *hgc = hgc_open(sockname);
272 hgclient_t *hgc = hgc_open(sockname);
273 if (hgc)
273 if (hgc)
274 return hgc;
274 return hgc;
275
275
276 /* prevent us from being connected to an outdated server: we were
276 /* prevent us from being connected to an outdated server: we were
277 * told by a server to redirect to opts->redirectsockname and that
277 * told by a server to redirect to opts->redirectsockname and that
278 * address does not work. we do not want to connect to the server
278 * address does not work. we do not want to connect to the server
279 * again because it will probably tell us the same thing. */
279 * again because it will probably tell us the same thing. */
280 if (sockname == opts->redirectsockname)
280 if (sockname == opts->redirectsockname)
281 unlink(opts->sockname);
281 unlink(opts->sockname);
282
282
283 debugmsg("start cmdserver at %s", opts->initsockname);
283 debugmsg("start cmdserver at %s", opts->initsockname);
284
284
285 pid_t pid = fork();
285 pid_t pid = fork();
286 if (pid < 0)
286 if (pid < 0)
287 abortmsg("failed to fork cmdserver process");
287 abortmsg("failed to fork cmdserver process");
288 if (pid == 0) {
288 if (pid == 0) {
289 execcmdserver(opts);
289 execcmdserver(opts);
290 } else {
290 } else {
291 hgc = retryconnectcmdserver(opts, pid);
291 hgc = retryconnectcmdserver(opts, pid);
292 }
292 }
293
293
294 return hgc;
294 return hgc;
295 }
295 }
296
296
297 static void killcmdserver(const struct cmdserveropts *opts)
297 static void killcmdserver(const struct cmdserveropts *opts)
298 {
298 {
299 /* resolve config hash */
299 /* resolve config hash */
300 char *resolvedpath = realpath(opts->sockname, NULL);
300 char *resolvedpath = realpath(opts->sockname, NULL);
301 if (resolvedpath) {
301 if (resolvedpath) {
302 unlink(resolvedpath);
302 unlink(resolvedpath);
303 free(resolvedpath);
303 free(resolvedpath);
304 }
304 }
305 }
305 }
306
306
307 /* Run instructions sent from the server like unlink and set redirect path
307 /* Run instructions sent from the server like unlink and set redirect path
308 * Return 1 if reconnect is needed, otherwise 0 */
308 * Return 1 if reconnect is needed, otherwise 0 */
309 static int runinstructions(struct cmdserveropts *opts, const char **insts)
309 static int runinstructions(struct cmdserveropts *opts, const char **insts)
310 {
310 {
311 int needreconnect = 0;
311 int needreconnect = 0;
312 if (!insts)
312 if (!insts)
313 return needreconnect;
313 return needreconnect;
314
314
315 assert(insts);
315 assert(insts);
316 opts->redirectsockname[0] = '\0';
316 opts->redirectsockname[0] = '\0';
317 const char **pinst;
317 const char **pinst;
318 for (pinst = insts; *pinst; pinst++) {
318 for (pinst = insts; *pinst; pinst++) {
319 debugmsg("instruction: %s", *pinst);
319 debugmsg("instruction: %s", *pinst);
320 if (strncmp(*pinst, "unlink ", 7) == 0) {
320 if (strncmp(*pinst, "unlink ", 7) == 0) {
321 unlink(*pinst + 7);
321 unlink(*pinst + 7);
322 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
322 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
323 int r = snprintf(opts->redirectsockname,
323 int r = snprintf(opts->redirectsockname,
324 sizeof(opts->redirectsockname),
324 sizeof(opts->redirectsockname),
325 "%s", *pinst + 9);
325 "%s", *pinst + 9);
326 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
326 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
327 abortmsg("redirect path is too long (%d)", r);
327 abortmsg("redirect path is too long (%d)", r);
328 needreconnect = 1;
328 needreconnect = 1;
329 } else if (strncmp(*pinst, "exit ", 5) == 0) {
329 } else if (strncmp(*pinst, "exit ", 5) == 0) {
330 int n = 0;
330 int n = 0;
331 if (sscanf(*pinst + 5, "%d", &n) != 1)
331 if (sscanf(*pinst + 5, "%d", &n) != 1)
332 abortmsg("cannot read the exit code");
332 abortmsg("cannot read the exit code");
333 exit(n);
333 exit(n);
334 } else if (strcmp(*pinst, "reconnect") == 0) {
334 } else if (strcmp(*pinst, "reconnect") == 0) {
335 needreconnect = 1;
335 needreconnect = 1;
336 } else {
336 } else {
337 abortmsg("unknown instruction: %s", *pinst);
337 abortmsg("unknown instruction: %s", *pinst);
338 }
338 }
339 }
339 }
340 return needreconnect;
340 return needreconnect;
341 }
341 }
342
342
343 /*
343 /*
344 * Test whether the command is unsupported or not. This is not designed to
344 * Test whether the command is unsupported or not. This is not designed to
345 * cover all cases. But it's fast, does not depend on the server and does
345 * cover all cases. But it's fast, does not depend on the server and does
346 * not return false positives.
346 * not return false positives.
347 */
347 */
348 static int isunsupported(int argc, const char *argv[])
348 static int isunsupported(int argc, const char *argv[])
349 {
349 {
350 enum {
350 enum {
351 SERVE = 1,
351 SERVE = 1,
352 DAEMON = 2,
352 DAEMON = 2,
353 SERVEDAEMON = SERVE | DAEMON,
353 SERVEDAEMON = SERVE | DAEMON,
354 TIME = 4,
354 TIME = 4,
355 };
355 };
356 unsigned int state = 0;
356 unsigned int state = 0;
357 int i;
357 int i;
358 for (i = 0; i < argc; ++i) {
358 for (i = 0; i < argc; ++i) {
359 if (strcmp(argv[i], "--") == 0)
359 if (strcmp(argv[i], "--") == 0)
360 break;
360 break;
361 if (i == 0 && strcmp("serve", argv[i]) == 0)
361 if (i == 0 && strcmp("serve", argv[i]) == 0)
362 state |= SERVE;
362 state |= SERVE;
363 else if (strcmp("-d", argv[i]) == 0 ||
363 else if (strcmp("-d", argv[i]) == 0 ||
364 strcmp("--daemon", argv[i]) == 0)
364 strcmp("--daemon", argv[i]) == 0)
365 state |= DAEMON;
365 state |= DAEMON;
366 else if (strcmp("--time", argv[i]) == 0)
366 else if (strcmp("--time", argv[i]) == 0)
367 state |= TIME;
367 state |= TIME;
368 }
368 }
369 return (state & TIME) == TIME ||
369 return (state & TIME) == TIME ||
370 (state & SERVEDAEMON) == SERVEDAEMON;
370 (state & SERVEDAEMON) == SERVEDAEMON;
371 }
371 }
372
372
373 static void execoriginalhg(const char *argv[])
373 static void execoriginalhg(const char *argv[])
374 {
374 {
375 debugmsg("execute original hg");
375 debugmsg("execute original hg");
376 if (execvp(gethgcmd(), (char **)argv) < 0)
376 if (execvp(gethgcmd(), (char **)argv) < 0)
377 abortmsgerrno("failed to exec original hg");
377 abortmsgerrno("failed to exec original hg");
378 }
378 }
379
379
380 int main(int argc, const char *argv[], const char *envp[])
380 int main(int argc, const char *argv[], const char *envp[])
381 {
381 {
382 if (getenv("CHGDEBUG"))
382 if (getenv("CHGDEBUG"))
383 enabledebugmsg();
383 enabledebugmsg();
384
384
385 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
385 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
386 enablecolor();
386 enablecolor();
387
387
388 if (getenv("CHGINTERNALMARK"))
388 if (getenv("CHGINTERNALMARK"))
389 abortmsg("chg started by chg detected.\n"
389 abortmsg("chg started by chg detected.\n"
390 "Please make sure ${HG:-hg} is not a symlink or "
390 "Please make sure ${HG:-hg} is not a symlink or "
391 "wrapper to chg. Alternatively, set $CHGHG to the "
391 "wrapper to chg. Alternatively, set $CHGHG to the "
392 "path of real hg.");
392 "path of real hg.");
393
393
394 if (isunsupported(argc - 1, argv + 1))
394 if (isunsupported(argc - 1, argv + 1))
395 execoriginalhg(argv);
395 execoriginalhg(argv);
396
396
397 struct cmdserveropts opts;
397 struct cmdserveropts opts;
398 initcmdserveropts(&opts);
398 initcmdserveropts(&opts);
399 setcmdserveropts(&opts);
399 setcmdserveropts(&opts);
400 setcmdserverargs(&opts, argc, argv);
400 setcmdserverargs(&opts, argc, argv);
401
401
402 if (argc == 2) {
402 if (argc == 2) {
403 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
403 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
404 killcmdserver(&opts);
404 killcmdserver(&opts);
405 return 0;
405 return 0;
406 }
406 }
407 }
407 }
408
408
409 hgclient_t *hgc;
409 hgclient_t *hgc;
410 size_t retry = 0;
410 size_t retry = 0;
411 while (1) {
411 while (1) {
412 hgc = connectcmdserver(&opts);
412 hgc = connectcmdserver(&opts);
413 if (!hgc)
413 if (!hgc)
414 abortmsg("cannot open hg client");
414 abortmsg("cannot open hg client");
415 hgc_setenv(hgc, envp);
415 hgc_setenv(hgc, envp);
416 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
416 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
417 int needreconnect = runinstructions(&opts, insts);
417 int needreconnect = runinstructions(&opts, insts);
418 free(insts);
418 free(insts);
419 if (!needreconnect)
419 if (!needreconnect)
420 break;
420 break;
421 hgc_close(hgc);
421 hgc_close(hgc);
422 if (++retry > 10)
422 if (++retry > 10)
423 abortmsg("too many redirections.\n"
423 abortmsg("too many redirections.\n"
424 "Please make sure %s is not a wrapper which "
424 "Please make sure %s is not a wrapper which "
425 "changes sensitive environment variables "
425 "changes sensitive environment variables "
426 "before executing hg. If you have to use a "
426 "before executing hg. If you have to use a "
427 "wrapper, wrap chg instead of hg.",
427 "wrapper, wrap chg instead of hg.",
428 gethgcmd());
428 gethgcmd());
429 }
429 }
430
430
431 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
431 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
432 const char *pagercmd = hgc_getpager(hgc, argv + 1, argc - 1);
433 pid_t pagerpid = setuppager(pagercmd);
434 if (pagerpid)
435 hgc_attachio(hgc); /* reattach to pager */
436 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
432 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
437 restoresignalhandler();
433 restoresignalhandler();
438 hgc_close(hgc);
434 hgc_close(hgc);
439 freecmdserveropts(&opts);
435 freecmdserveropts(&opts);
440 waitpager();
436 waitpager();
441
437
442 return exitcode;
438 return exitcode;
443 }
439 }
@@ -1,633 +1,606 b''
1 /*
1 /*
2 * A command server client that uses Unix domain socket
2 * A command server client that uses Unix domain socket
3 *
3 *
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 *
5 *
6 * This software may be used and distributed according to the terms of the
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.
7 * GNU General Public License version 2 or any later version.
8 */
8 */
9
9
10 #include <arpa/inet.h> /* for ntohl(), htonl() */
10 #include <arpa/inet.h> /* for ntohl(), htonl() */
11 #include <assert.h>
11 #include <assert.h>
12 #include <ctype.h>
12 #include <ctype.h>
13 #include <errno.h>
13 #include <errno.h>
14 #include <fcntl.h>
14 #include <fcntl.h>
15 #include <signal.h>
15 #include <signal.h>
16 #include <stdint.h>
16 #include <stdint.h>
17 #include <stdio.h>
17 #include <stdio.h>
18 #include <stdlib.h>
18 #include <stdlib.h>
19 #include <string.h>
19 #include <string.h>
20 #include <sys/socket.h>
20 #include <sys/socket.h>
21 #include <sys/stat.h>
21 #include <sys/stat.h>
22 #include <sys/un.h>
22 #include <sys/un.h>
23 #include <unistd.h>
23 #include <unistd.h>
24
24
25 #include "hgclient.h"
25 #include "hgclient.h"
26 #include "procutil.h"
26 #include "procutil.h"
27 #include "util.h"
27 #include "util.h"
28
28
29 enum {
29 enum {
30 CAP_GETENCODING = 0x0001,
30 CAP_GETENCODING = 0x0001,
31 CAP_RUNCOMMAND = 0x0002,
31 CAP_RUNCOMMAND = 0x0002,
32 /* cHg extension: */
32 /* cHg extension: */
33 CAP_ATTACHIO = 0x0100,
33 CAP_ATTACHIO = 0x0100,
34 CAP_CHDIR = 0x0200,
34 CAP_CHDIR = 0x0200,
35 CAP_GETPAGER = 0x0400,
36 CAP_SETENV = 0x0800,
35 CAP_SETENV = 0x0800,
37 CAP_SETUMASK = 0x1000,
36 CAP_SETUMASK = 0x1000,
38 CAP_VALIDATE = 0x2000,
37 CAP_VALIDATE = 0x2000,
39 };
38 };
40
39
41 typedef struct {
40 typedef struct {
42 const char *name;
41 const char *name;
43 unsigned int flag;
42 unsigned int flag;
44 } cappair_t;
43 } cappair_t;
45
44
46 static const cappair_t captable[] = {
45 static const cappair_t captable[] = {
47 {"getencoding", CAP_GETENCODING},
46 {"getencoding", CAP_GETENCODING},
48 {"runcommand", CAP_RUNCOMMAND},
47 {"runcommand", CAP_RUNCOMMAND},
49 {"attachio", CAP_ATTACHIO},
48 {"attachio", CAP_ATTACHIO},
50 {"chdir", CAP_CHDIR},
49 {"chdir", CAP_CHDIR},
51 {"getpager", CAP_GETPAGER},
52 {"setenv", CAP_SETENV},
50 {"setenv", CAP_SETENV},
53 {"setumask", CAP_SETUMASK},
51 {"setumask", CAP_SETUMASK},
54 {"validate", CAP_VALIDATE},
52 {"validate", CAP_VALIDATE},
55 {NULL, 0}, /* terminator */
53 {NULL, 0}, /* terminator */
56 };
54 };
57
55
58 typedef struct {
56 typedef struct {
59 char ch;
57 char ch;
60 char *data;
58 char *data;
61 size_t maxdatasize;
59 size_t maxdatasize;
62 size_t datasize;
60 size_t datasize;
63 } context_t;
61 } context_t;
64
62
65 struct hgclient_tag_ {
63 struct hgclient_tag_ {
66 int sockfd;
64 int sockfd;
67 pid_t pgid;
65 pid_t pgid;
68 pid_t pid;
66 pid_t pid;
69 context_t ctx;
67 context_t ctx;
70 unsigned int capflags;
68 unsigned int capflags;
71 };
69 };
72
70
73 static const size_t defaultdatasize = 4096;
71 static const size_t defaultdatasize = 4096;
74
72
75 static void attachio(hgclient_t *hgc);
73 static void attachio(hgclient_t *hgc);
76
74
77 static void initcontext(context_t *ctx)
75 static void initcontext(context_t *ctx)
78 {
76 {
79 ctx->ch = '\0';
77 ctx->ch = '\0';
80 ctx->data = malloc(defaultdatasize);
78 ctx->data = malloc(defaultdatasize);
81 ctx->maxdatasize = (ctx->data) ? defaultdatasize : 0;
79 ctx->maxdatasize = (ctx->data) ? defaultdatasize : 0;
82 ctx->datasize = 0;
80 ctx->datasize = 0;
83 debugmsg("initialize context buffer with size %zu", ctx->maxdatasize);
81 debugmsg("initialize context buffer with size %zu", ctx->maxdatasize);
84 }
82 }
85
83
86 static void enlargecontext(context_t *ctx, size_t newsize)
84 static void enlargecontext(context_t *ctx, size_t newsize)
87 {
85 {
88 if (newsize <= ctx->maxdatasize)
86 if (newsize <= ctx->maxdatasize)
89 return;
87 return;
90
88
91 newsize = defaultdatasize
89 newsize = defaultdatasize
92 * ((newsize + defaultdatasize - 1) / defaultdatasize);
90 * ((newsize + defaultdatasize - 1) / defaultdatasize);
93 ctx->data = reallocx(ctx->data, newsize);
91 ctx->data = reallocx(ctx->data, newsize);
94 ctx->maxdatasize = newsize;
92 ctx->maxdatasize = newsize;
95 debugmsg("enlarge context buffer to %zu", ctx->maxdatasize);
93 debugmsg("enlarge context buffer to %zu", ctx->maxdatasize);
96 }
94 }
97
95
98 static void freecontext(context_t *ctx)
96 static void freecontext(context_t *ctx)
99 {
97 {
100 debugmsg("free context buffer");
98 debugmsg("free context buffer");
101 free(ctx->data);
99 free(ctx->data);
102 ctx->data = NULL;
100 ctx->data = NULL;
103 ctx->maxdatasize = 0;
101 ctx->maxdatasize = 0;
104 ctx->datasize = 0;
102 ctx->datasize = 0;
105 }
103 }
106
104
107 /* Read channeled response from cmdserver */
105 /* Read channeled response from cmdserver */
108 static void readchannel(hgclient_t *hgc)
106 static void readchannel(hgclient_t *hgc)
109 {
107 {
110 assert(hgc);
108 assert(hgc);
111
109
112 ssize_t rsize = recv(hgc->sockfd, &hgc->ctx.ch, sizeof(hgc->ctx.ch), 0);
110 ssize_t rsize = recv(hgc->sockfd, &hgc->ctx.ch, sizeof(hgc->ctx.ch), 0);
113 if (rsize != sizeof(hgc->ctx.ch)) {
111 if (rsize != sizeof(hgc->ctx.ch)) {
114 /* server would have exception and traceback would be printed */
112 /* server would have exception and traceback would be printed */
115 debugmsg("failed to read channel");
113 debugmsg("failed to read channel");
116 exit(255);
114 exit(255);
117 }
115 }
118
116
119 uint32_t datasize_n;
117 uint32_t datasize_n;
120 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0);
118 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0);
121 if (rsize != sizeof(datasize_n))
119 if (rsize != sizeof(datasize_n))
122 abortmsg("failed to read data size");
120 abortmsg("failed to read data size");
123
121
124 /* datasize denotes the maximum size to write if input request */
122 /* datasize denotes the maximum size to write if input request */
125 hgc->ctx.datasize = ntohl(datasize_n);
123 hgc->ctx.datasize = ntohl(datasize_n);
126 enlargecontext(&hgc->ctx, hgc->ctx.datasize);
124 enlargecontext(&hgc->ctx, hgc->ctx.datasize);
127
125
128 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S')
126 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S')
129 return; /* assumes input request */
127 return; /* assumes input request */
130
128
131 size_t cursize = 0;
129 size_t cursize = 0;
132 while (cursize < hgc->ctx.datasize) {
130 while (cursize < hgc->ctx.datasize) {
133 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
131 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
134 hgc->ctx.datasize - cursize, 0);
132 hgc->ctx.datasize - cursize, 0);
135 if (rsize < 1)
133 if (rsize < 1)
136 abortmsg("failed to read data block");
134 abortmsg("failed to read data block");
137 cursize += rsize;
135 cursize += rsize;
138 }
136 }
139 }
137 }
140
138
141 static void sendall(int sockfd, const void *data, size_t datasize)
139 static void sendall(int sockfd, const void *data, size_t datasize)
142 {
140 {
143 const char *p = data;
141 const char *p = data;
144 const char *const endp = p + datasize;
142 const char *const endp = p + datasize;
145 while (p < endp) {
143 while (p < endp) {
146 ssize_t r = send(sockfd, p, endp - p, 0);
144 ssize_t r = send(sockfd, p, endp - p, 0);
147 if (r < 0)
145 if (r < 0)
148 abortmsgerrno("cannot communicate");
146 abortmsgerrno("cannot communicate");
149 p += r;
147 p += r;
150 }
148 }
151 }
149 }
152
150
153 /* Write lengh-data block to cmdserver */
151 /* Write lengh-data block to cmdserver */
154 static void writeblock(const hgclient_t *hgc)
152 static void writeblock(const hgclient_t *hgc)
155 {
153 {
156 assert(hgc);
154 assert(hgc);
157
155
158 const uint32_t datasize_n = htonl(hgc->ctx.datasize);
156 const uint32_t datasize_n = htonl(hgc->ctx.datasize);
159 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n));
157 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n));
160
158
161 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize);
159 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize);
162 }
160 }
163
161
164 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd)
162 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd)
165 {
163 {
166 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize);
164 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize);
167
165
168 char buf[strlen(chcmd) + 1];
166 char buf[strlen(chcmd) + 1];
169 memcpy(buf, chcmd, sizeof(buf) - 1);
167 memcpy(buf, chcmd, sizeof(buf) - 1);
170 buf[sizeof(buf) - 1] = '\n';
168 buf[sizeof(buf) - 1] = '\n';
171 sendall(hgc->sockfd, buf, sizeof(buf));
169 sendall(hgc->sockfd, buf, sizeof(buf));
172
170
173 writeblock(hgc);
171 writeblock(hgc);
174 }
172 }
175
173
176 /* Build '\0'-separated list of args. argsize < 0 denotes that args are
174 /* Build '\0'-separated list of args. argsize < 0 denotes that args are
177 * terminated by NULL. */
175 * terminated by NULL. */
178 static void packcmdargs(context_t *ctx, const char *const args[],
176 static void packcmdargs(context_t *ctx, const char *const args[],
179 ssize_t argsize)
177 ssize_t argsize)
180 {
178 {
181 ctx->datasize = 0;
179 ctx->datasize = 0;
182 const char *const *const end = (argsize >= 0) ? args + argsize : NULL;
180 const char *const *const end = (argsize >= 0) ? args + argsize : NULL;
183 for (const char *const *it = args; it != end && *it; ++it) {
181 for (const char *const *it = args; it != end && *it; ++it) {
184 const size_t n = strlen(*it) + 1; /* include '\0' */
182 const size_t n = strlen(*it) + 1; /* include '\0' */
185 enlargecontext(ctx, ctx->datasize + n);
183 enlargecontext(ctx, ctx->datasize + n);
186 memcpy(ctx->data + ctx->datasize, *it, n);
184 memcpy(ctx->data + ctx->datasize, *it, n);
187 ctx->datasize += n;
185 ctx->datasize += n;
188 }
186 }
189
187
190 if (ctx->datasize > 0)
188 if (ctx->datasize > 0)
191 --ctx->datasize; /* strip last '\0' */
189 --ctx->datasize; /* strip last '\0' */
192 }
190 }
193
191
194 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */
192 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */
195 static const char **unpackcmdargsnul(const context_t *ctx)
193 static const char **unpackcmdargsnul(const context_t *ctx)
196 {
194 {
197 const char **args = NULL;
195 const char **args = NULL;
198 size_t nargs = 0, maxnargs = 0;
196 size_t nargs = 0, maxnargs = 0;
199 const char *s = ctx->data;
197 const char *s = ctx->data;
200 const char *e = ctx->data + ctx->datasize;
198 const char *e = ctx->data + ctx->datasize;
201 for (;;) {
199 for (;;) {
202 if (nargs + 1 >= maxnargs) { /* including last NULL */
200 if (nargs + 1 >= maxnargs) { /* including last NULL */
203 maxnargs += 256;
201 maxnargs += 256;
204 args = reallocx(args, maxnargs * sizeof(args[0]));
202 args = reallocx(args, maxnargs * sizeof(args[0]));
205 }
203 }
206 args[nargs] = s;
204 args[nargs] = s;
207 nargs++;
205 nargs++;
208 s = memchr(s, '\0', e - s);
206 s = memchr(s, '\0', e - s);
209 if (!s)
207 if (!s)
210 break;
208 break;
211 s++;
209 s++;
212 }
210 }
213 args[nargs] = NULL;
211 args[nargs] = NULL;
214 return args;
212 return args;
215 }
213 }
216
214
217 static void handlereadrequest(hgclient_t *hgc)
215 static void handlereadrequest(hgclient_t *hgc)
218 {
216 {
219 context_t *ctx = &hgc->ctx;
217 context_t *ctx = &hgc->ctx;
220 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin);
218 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin);
221 ctx->datasize = r;
219 ctx->datasize = r;
222 writeblock(hgc);
220 writeblock(hgc);
223 }
221 }
224
222
225 /* Read single-line */
223 /* Read single-line */
226 static void handlereadlinerequest(hgclient_t *hgc)
224 static void handlereadlinerequest(hgclient_t *hgc)
227 {
225 {
228 context_t *ctx = &hgc->ctx;
226 context_t *ctx = &hgc->ctx;
229 if (!fgets(ctx->data, ctx->datasize, stdin))
227 if (!fgets(ctx->data, ctx->datasize, stdin))
230 ctx->data[0] = '\0';
228 ctx->data[0] = '\0';
231 ctx->datasize = strlen(ctx->data);
229 ctx->datasize = strlen(ctx->data);
232 writeblock(hgc);
230 writeblock(hgc);
233 }
231 }
234
232
235 /* Execute the requested command and write exit code */
233 /* Execute the requested command and write exit code */
236 static void handlesystemrequest(hgclient_t *hgc)
234 static void handlesystemrequest(hgclient_t *hgc)
237 {
235 {
238 context_t *ctx = &hgc->ctx;
236 context_t *ctx = &hgc->ctx;
239 enlargecontext(ctx, ctx->datasize + 1);
237 enlargecontext(ctx, ctx->datasize + 1);
240 ctx->data[ctx->datasize] = '\0'; /* terminate last string */
238 ctx->data[ctx->datasize] = '\0'; /* terminate last string */
241
239
242 const char **args = unpackcmdargsnul(ctx);
240 const char **args = unpackcmdargsnul(ctx);
243 if (!args[0] || !args[1] || !args[2])
241 if (!args[0] || !args[1] || !args[2])
244 abortmsg("missing type or command or cwd in system request");
242 abortmsg("missing type or command or cwd in system request");
245 if (strcmp(args[0], "system") == 0) {
243 if (strcmp(args[0], "system") == 0) {
246 debugmsg("run '%s' at '%s'", args[1], args[2]);
244 debugmsg("run '%s' at '%s'", args[1], args[2]);
247 int32_t r = runshellcmd(args[1], args + 3, args[2]);
245 int32_t r = runshellcmd(args[1], args + 3, args[2]);
248 free(args);
246 free(args);
249
247
250 uint32_t r_n = htonl(r);
248 uint32_t r_n = htonl(r);
251 memcpy(ctx->data, &r_n, sizeof(r_n));
249 memcpy(ctx->data, &r_n, sizeof(r_n));
252 ctx->datasize = sizeof(r_n);
250 ctx->datasize = sizeof(r_n);
253 writeblock(hgc);
251 writeblock(hgc);
254 } else if (strcmp(args[0], "pager") == 0) {
252 } else if (strcmp(args[0], "pager") == 0) {
255 setuppager(args[1]);
253 setuppager(args[1]);
256 if (hgc->capflags & CAP_ATTACHIO)
254 if (hgc->capflags & CAP_ATTACHIO)
257 attachio(hgc);
255 attachio(hgc);
258 /* unblock the server */
256 /* unblock the server */
259 static const char emptycmd[] = "\n";
257 static const char emptycmd[] = "\n";
260 sendall(hgc->sockfd, emptycmd, sizeof(emptycmd) - 1);
258 sendall(hgc->sockfd, emptycmd, sizeof(emptycmd) - 1);
261 } else {
259 } else {
262 abortmsg("unknown type in system request: %s", args[0]);
260 abortmsg("unknown type in system request: %s", args[0]);
263 }
261 }
264 }
262 }
265
263
266 /* Read response of command execution until receiving 'r'-esult */
264 /* Read response of command execution until receiving 'r'-esult */
267 static void handleresponse(hgclient_t *hgc)
265 static void handleresponse(hgclient_t *hgc)
268 {
266 {
269 for (;;) {
267 for (;;) {
270 readchannel(hgc);
268 readchannel(hgc);
271 context_t *ctx = &hgc->ctx;
269 context_t *ctx = &hgc->ctx;
272 debugmsg("response read from channel %c, size %zu",
270 debugmsg("response read from channel %c, size %zu",
273 ctx->ch, ctx->datasize);
271 ctx->ch, ctx->datasize);
274 switch (ctx->ch) {
272 switch (ctx->ch) {
275 case 'o':
273 case 'o':
276 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
274 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
277 stdout);
275 stdout);
278 break;
276 break;
279 case 'e':
277 case 'e':
280 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
278 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
281 stderr);
279 stderr);
282 break;
280 break;
283 case 'd':
281 case 'd':
284 /* assumes last char is '\n' */
282 /* assumes last char is '\n' */
285 ctx->data[ctx->datasize - 1] = '\0';
283 ctx->data[ctx->datasize - 1] = '\0';
286 debugmsg("server: %s", ctx->data);
284 debugmsg("server: %s", ctx->data);
287 break;
285 break;
288 case 'r':
286 case 'r':
289 return;
287 return;
290 case 'I':
288 case 'I':
291 handlereadrequest(hgc);
289 handlereadrequest(hgc);
292 break;
290 break;
293 case 'L':
291 case 'L':
294 handlereadlinerequest(hgc);
292 handlereadlinerequest(hgc);
295 break;
293 break;
296 case 'S':
294 case 'S':
297 handlesystemrequest(hgc);
295 handlesystemrequest(hgc);
298 break;
296 break;
299 default:
297 default:
300 if (isupper(ctx->ch))
298 if (isupper(ctx->ch))
301 abortmsg("cannot handle response (ch = %c)",
299 abortmsg("cannot handle response (ch = %c)",
302 ctx->ch);
300 ctx->ch);
303 }
301 }
304 }
302 }
305 }
303 }
306
304
307 static unsigned int parsecapabilities(const char *s, const char *e)
305 static unsigned int parsecapabilities(const char *s, const char *e)
308 {
306 {
309 unsigned int flags = 0;
307 unsigned int flags = 0;
310 while (s < e) {
308 while (s < e) {
311 const char *t = strchr(s, ' ');
309 const char *t = strchr(s, ' ');
312 if (!t || t > e)
310 if (!t || t > e)
313 t = e;
311 t = e;
314 const cappair_t *cap;
312 const cappair_t *cap;
315 for (cap = captable; cap->flag; ++cap) {
313 for (cap = captable; cap->flag; ++cap) {
316 size_t n = t - s;
314 size_t n = t - s;
317 if (strncmp(s, cap->name, n) == 0 &&
315 if (strncmp(s, cap->name, n) == 0 &&
318 strlen(cap->name) == n) {
316 strlen(cap->name) == n) {
319 flags |= cap->flag;
317 flags |= cap->flag;
320 break;
318 break;
321 }
319 }
322 }
320 }
323 s = t + 1;
321 s = t + 1;
324 }
322 }
325 return flags;
323 return flags;
326 }
324 }
327
325
328 static void readhello(hgclient_t *hgc)
326 static void readhello(hgclient_t *hgc)
329 {
327 {
330 readchannel(hgc);
328 readchannel(hgc);
331 context_t *ctx = &hgc->ctx;
329 context_t *ctx = &hgc->ctx;
332 if (ctx->ch != 'o') {
330 if (ctx->ch != 'o') {
333 char ch = ctx->ch;
331 char ch = ctx->ch;
334 if (ch == 'e') {
332 if (ch == 'e') {
335 /* write early error and will exit */
333 /* write early error and will exit */
336 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
334 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
337 stderr);
335 stderr);
338 handleresponse(hgc);
336 handleresponse(hgc);
339 }
337 }
340 abortmsg("unexpected channel of hello message (ch = %c)", ch);
338 abortmsg("unexpected channel of hello message (ch = %c)", ch);
341 }
339 }
342 enlargecontext(ctx, ctx->datasize + 1);
340 enlargecontext(ctx, ctx->datasize + 1);
343 ctx->data[ctx->datasize] = '\0';
341 ctx->data[ctx->datasize] = '\0';
344 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize);
342 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize);
345
343
346 const char *s = ctx->data;
344 const char *s = ctx->data;
347 const char *const dataend = ctx->data + ctx->datasize;
345 const char *const dataend = ctx->data + ctx->datasize;
348 while (s < dataend) {
346 while (s < dataend) {
349 const char *t = strchr(s, ':');
347 const char *t = strchr(s, ':');
350 if (!t || t[1] != ' ')
348 if (!t || t[1] != ' ')
351 break;
349 break;
352 const char *u = strchr(t + 2, '\n');
350 const char *u = strchr(t + 2, '\n');
353 if (!u)
351 if (!u)
354 u = dataend;
352 u = dataend;
355 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
353 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
356 hgc->capflags = parsecapabilities(t + 2, u);
354 hgc->capflags = parsecapabilities(t + 2, u);
357 } else if (strncmp(s, "pgid:", t - s + 1) == 0) {
355 } else if (strncmp(s, "pgid:", t - s + 1) == 0) {
358 hgc->pgid = strtol(t + 2, NULL, 10);
356 hgc->pgid = strtol(t + 2, NULL, 10);
359 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
357 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
360 hgc->pid = strtol(t + 2, NULL, 10);
358 hgc->pid = strtol(t + 2, NULL, 10);
361 }
359 }
362 s = u + 1;
360 s = u + 1;
363 }
361 }
364 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid);
362 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid);
365 }
363 }
366
364
367 static void attachio(hgclient_t *hgc)
365 static void attachio(hgclient_t *hgc)
368 {
366 {
369 debugmsg("request attachio");
367 debugmsg("request attachio");
370 static const char chcmd[] = "attachio\n";
368 static const char chcmd[] = "attachio\n";
371 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1);
369 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1);
372 readchannel(hgc);
370 readchannel(hgc);
373 context_t *ctx = &hgc->ctx;
371 context_t *ctx = &hgc->ctx;
374 if (ctx->ch != 'I')
372 if (ctx->ch != 'I')
375 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch);
373 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch);
376
374
377 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
375 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
378 struct msghdr msgh;
376 struct msghdr msgh;
379 memset(&msgh, 0, sizeof(msgh));
377 memset(&msgh, 0, sizeof(msgh));
380 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */
378 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */
381 msgh.msg_iov = &iov;
379 msgh.msg_iov = &iov;
382 msgh.msg_iovlen = 1;
380 msgh.msg_iovlen = 1;
383 char fdbuf[CMSG_SPACE(sizeof(fds))];
381 char fdbuf[CMSG_SPACE(sizeof(fds))];
384 msgh.msg_control = fdbuf;
382 msgh.msg_control = fdbuf;
385 msgh.msg_controllen = sizeof(fdbuf);
383 msgh.msg_controllen = sizeof(fdbuf);
386 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
384 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
387 cmsg->cmsg_level = SOL_SOCKET;
385 cmsg->cmsg_level = SOL_SOCKET;
388 cmsg->cmsg_type = SCM_RIGHTS;
386 cmsg->cmsg_type = SCM_RIGHTS;
389 cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
387 cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
390 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
388 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
391 msgh.msg_controllen = cmsg->cmsg_len;
389 msgh.msg_controllen = cmsg->cmsg_len;
392 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0);
390 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0);
393 if (r < 0)
391 if (r < 0)
394 abortmsgerrno("sendmsg failed");
392 abortmsgerrno("sendmsg failed");
395
393
396 handleresponse(hgc);
394 handleresponse(hgc);
397 int32_t n;
395 int32_t n;
398 if (ctx->datasize != sizeof(n))
396 if (ctx->datasize != sizeof(n))
399 abortmsg("unexpected size of attachio result");
397 abortmsg("unexpected size of attachio result");
400 memcpy(&n, ctx->data, sizeof(n));
398 memcpy(&n, ctx->data, sizeof(n));
401 n = ntohl(n);
399 n = ntohl(n);
402 if (n != sizeof(fds) / sizeof(fds[0]))
400 if (n != sizeof(fds) / sizeof(fds[0]))
403 abortmsg("failed to send fds (n = %d)", n);
401 abortmsg("failed to send fds (n = %d)", n);
404 }
402 }
405
403
406 static void chdirtocwd(hgclient_t *hgc)
404 static void chdirtocwd(hgclient_t *hgc)
407 {
405 {
408 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize))
406 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize))
409 abortmsgerrno("failed to getcwd");
407 abortmsgerrno("failed to getcwd");
410 hgc->ctx.datasize = strlen(hgc->ctx.data);
408 hgc->ctx.datasize = strlen(hgc->ctx.data);
411 writeblockrequest(hgc, "chdir");
409 writeblockrequest(hgc, "chdir");
412 }
410 }
413
411
414 static void forwardumask(hgclient_t *hgc)
412 static void forwardumask(hgclient_t *hgc)
415 {
413 {
416 mode_t mask = umask(0);
414 mode_t mask = umask(0);
417 umask(mask);
415 umask(mask);
418
416
419 static const char command[] = "setumask\n";
417 static const char command[] = "setumask\n";
420 sendall(hgc->sockfd, command, sizeof(command) - 1);
418 sendall(hgc->sockfd, command, sizeof(command) - 1);
421 uint32_t data = htonl(mask);
419 uint32_t data = htonl(mask);
422 sendall(hgc->sockfd, &data, sizeof(data));
420 sendall(hgc->sockfd, &data, sizeof(data));
423 }
421 }
424
422
425 /*!
423 /*!
426 * Open connection to per-user cmdserver
424 * Open connection to per-user cmdserver
427 *
425 *
428 * If no background server running, returns NULL.
426 * If no background server running, returns NULL.
429 */
427 */
430 hgclient_t *hgc_open(const char *sockname)
428 hgclient_t *hgc_open(const char *sockname)
431 {
429 {
432 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
430 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
433 if (fd < 0)
431 if (fd < 0)
434 abortmsgerrno("cannot create socket");
432 abortmsgerrno("cannot create socket");
435
433
436 /* don't keep fd on fork(), so that it can be closed when the parent
434 /* don't keep fd on fork(), so that it can be closed when the parent
437 * process get terminated. */
435 * process get terminated. */
438 fsetcloexec(fd);
436 fsetcloexec(fd);
439
437
440 struct sockaddr_un addr;
438 struct sockaddr_un addr;
441 addr.sun_family = AF_UNIX;
439 addr.sun_family = AF_UNIX;
442
440
443 /* use chdir to workaround small sizeof(sun_path) */
441 /* use chdir to workaround small sizeof(sun_path) */
444 int bakfd = -1;
442 int bakfd = -1;
445 const char *basename = sockname;
443 const char *basename = sockname;
446 {
444 {
447 const char *split = strrchr(sockname, '/');
445 const char *split = strrchr(sockname, '/');
448 if (split && split != sockname) {
446 if (split && split != sockname) {
449 if (split[1] == '\0')
447 if (split[1] == '\0')
450 abortmsg("sockname cannot end with a slash");
448 abortmsg("sockname cannot end with a slash");
451 size_t len = split - sockname;
449 size_t len = split - sockname;
452 char sockdir[len + 1];
450 char sockdir[len + 1];
453 memcpy(sockdir, sockname, len);
451 memcpy(sockdir, sockname, len);
454 sockdir[len] = '\0';
452 sockdir[len] = '\0';
455
453
456 bakfd = open(".", O_DIRECTORY);
454 bakfd = open(".", O_DIRECTORY);
457 if (bakfd == -1)
455 if (bakfd == -1)
458 abortmsgerrno("cannot open cwd");
456 abortmsgerrno("cannot open cwd");
459
457
460 int r = chdir(sockdir);
458 int r = chdir(sockdir);
461 if (r != 0)
459 if (r != 0)
462 abortmsgerrno("cannot chdir %s", sockdir);
460 abortmsgerrno("cannot chdir %s", sockdir);
463
461
464 basename = split + 1;
462 basename = split + 1;
465 }
463 }
466 }
464 }
467 if (strlen(basename) >= sizeof(addr.sun_path))
465 if (strlen(basename) >= sizeof(addr.sun_path))
468 abortmsg("sockname is too long: %s", basename);
466 abortmsg("sockname is too long: %s", basename);
469 strncpy(addr.sun_path, basename, sizeof(addr.sun_path));
467 strncpy(addr.sun_path, basename, sizeof(addr.sun_path));
470 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
468 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
471
469
472 /* real connect */
470 /* real connect */
473 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
471 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
474 if (r < 0) {
472 if (r < 0) {
475 if (errno != ENOENT && errno != ECONNREFUSED)
473 if (errno != ENOENT && errno != ECONNREFUSED)
476 abortmsgerrno("cannot connect to %s", sockname);
474 abortmsgerrno("cannot connect to %s", sockname);
477 }
475 }
478 if (bakfd != -1) {
476 if (bakfd != -1) {
479 fchdirx(bakfd);
477 fchdirx(bakfd);
480 close(bakfd);
478 close(bakfd);
481 }
479 }
482 if (r < 0) {
480 if (r < 0) {
483 close(fd);
481 close(fd);
484 return NULL;
482 return NULL;
485 }
483 }
486 debugmsg("connected to %s", addr.sun_path);
484 debugmsg("connected to %s", addr.sun_path);
487
485
488 hgclient_t *hgc = mallocx(sizeof(hgclient_t));
486 hgclient_t *hgc = mallocx(sizeof(hgclient_t));
489 memset(hgc, 0, sizeof(*hgc));
487 memset(hgc, 0, sizeof(*hgc));
490 hgc->sockfd = fd;
488 hgc->sockfd = fd;
491 initcontext(&hgc->ctx);
489 initcontext(&hgc->ctx);
492
490
493 readhello(hgc);
491 readhello(hgc);
494 if (!(hgc->capflags & CAP_RUNCOMMAND))
492 if (!(hgc->capflags & CAP_RUNCOMMAND))
495 abortmsg("insufficient capability: runcommand");
493 abortmsg("insufficient capability: runcommand");
496 if (hgc->capflags & CAP_ATTACHIO)
494 if (hgc->capflags & CAP_ATTACHIO)
497 attachio(hgc);
495 attachio(hgc);
498 if (hgc->capflags & CAP_CHDIR)
496 if (hgc->capflags & CAP_CHDIR)
499 chdirtocwd(hgc);
497 chdirtocwd(hgc);
500 if (hgc->capflags & CAP_SETUMASK)
498 if (hgc->capflags & CAP_SETUMASK)
501 forwardumask(hgc);
499 forwardumask(hgc);
502
500
503 return hgc;
501 return hgc;
504 }
502 }
505
503
506 /*!
504 /*!
507 * Close connection and free allocated memory
505 * Close connection and free allocated memory
508 */
506 */
509 void hgc_close(hgclient_t *hgc)
507 void hgc_close(hgclient_t *hgc)
510 {
508 {
511 assert(hgc);
509 assert(hgc);
512 freecontext(&hgc->ctx);
510 freecontext(&hgc->ctx);
513 close(hgc->sockfd);
511 close(hgc->sockfd);
514 free(hgc);
512 free(hgc);
515 }
513 }
516
514
517 pid_t hgc_peerpgid(const hgclient_t *hgc)
515 pid_t hgc_peerpgid(const hgclient_t *hgc)
518 {
516 {
519 assert(hgc);
517 assert(hgc);
520 return hgc->pgid;
518 return hgc->pgid;
521 }
519 }
522
520
523 pid_t hgc_peerpid(const hgclient_t *hgc)
521 pid_t hgc_peerpid(const hgclient_t *hgc)
524 {
522 {
525 assert(hgc);
523 assert(hgc);
526 return hgc->pid;
524 return hgc->pid;
527 }
525 }
528
526
529 /*!
527 /*!
530 * Send command line arguments to let the server load the repo config and check
528 * Send command line arguments to let the server load the repo config and check
531 * whether it can process our request directly or not.
529 * whether it can process our request directly or not.
532 * Make sure hgc_setenv is called before calling this.
530 * Make sure hgc_setenv is called before calling this.
533 *
531 *
534 * @return - NULL, the server believes it can handle our request, or does not
532 * @return - NULL, the server believes it can handle our request, or does not
535 * support "validate" command.
533 * support "validate" command.
536 * - a list of strings, the server probably cannot handle our request
534 * - a list of strings, the server probably cannot handle our request
537 * and it sent instructions telling us what to do next. See
535 * and it sent instructions telling us what to do next. See
538 * chgserver.py for possible instruction formats.
536 * chgserver.py for possible instruction formats.
539 * the list should be freed by the caller.
537 * the list should be freed by the caller.
540 * the last string is guaranteed to be NULL.
538 * the last string is guaranteed to be NULL.
541 */
539 */
542 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
540 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
543 size_t argsize)
541 size_t argsize)
544 {
542 {
545 assert(hgc);
543 assert(hgc);
546 if (!(hgc->capflags & CAP_VALIDATE))
544 if (!(hgc->capflags & CAP_VALIDATE))
547 return NULL;
545 return NULL;
548
546
549 packcmdargs(&hgc->ctx, args, argsize);
547 packcmdargs(&hgc->ctx, args, argsize);
550 writeblockrequest(hgc, "validate");
548 writeblockrequest(hgc, "validate");
551 handleresponse(hgc);
549 handleresponse(hgc);
552
550
553 /* the server returns '\0' if it can handle our request */
551 /* the server returns '\0' if it can handle our request */
554 if (hgc->ctx.datasize <= 1)
552 if (hgc->ctx.datasize <= 1)
555 return NULL;
553 return NULL;
556
554
557 /* make sure the buffer is '\0' terminated */
555 /* make sure the buffer is '\0' terminated */
558 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
556 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
559 hgc->ctx.data[hgc->ctx.datasize] = '\0';
557 hgc->ctx.data[hgc->ctx.datasize] = '\0';
560 return unpackcmdargsnul(&hgc->ctx);
558 return unpackcmdargsnul(&hgc->ctx);
561 }
559 }
562
560
563 /*!
561 /*!
564 * Execute the specified Mercurial command
562 * Execute the specified Mercurial command
565 *
563 *
566 * @return result code
564 * @return result code
567 */
565 */
568 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize)
566 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize)
569 {
567 {
570 assert(hgc);
568 assert(hgc);
571
569
572 packcmdargs(&hgc->ctx, args, argsize);
570 packcmdargs(&hgc->ctx, args, argsize);
573 writeblockrequest(hgc, "runcommand");
571 writeblockrequest(hgc, "runcommand");
574 handleresponse(hgc);
572 handleresponse(hgc);
575
573
576 int32_t exitcode_n;
574 int32_t exitcode_n;
577 if (hgc->ctx.datasize != sizeof(exitcode_n)) {
575 if (hgc->ctx.datasize != sizeof(exitcode_n)) {
578 abortmsg("unexpected size of exitcode");
576 abortmsg("unexpected size of exitcode");
579 }
577 }
580 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n));
578 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n));
581 return ntohl(exitcode_n);
579 return ntohl(exitcode_n);
582 }
580 }
583
581
584 /*!
582 /*!
585 * (Re-)send client's stdio channels so that the server can access to tty
583 * (Re-)send client's stdio channels so that the server can access to tty
586 */
584 */
587 void hgc_attachio(hgclient_t *hgc)
585 void hgc_attachio(hgclient_t *hgc)
588 {
586 {
589 assert(hgc);
587 assert(hgc);
590 if (!(hgc->capflags & CAP_ATTACHIO))
588 if (!(hgc->capflags & CAP_ATTACHIO))
591 return;
589 return;
592 attachio(hgc);
590 attachio(hgc);
593 }
591 }
594
592
595 /*!
593 /*!
596 * Get pager command for the given Mercurial command args
597 *
598 * If no pager enabled, returns NULL. The return value becomes invalid
599 * once you run another request to hgc.
600 */
601 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
602 size_t argsize)
603 {
604 assert(hgc);
605
606 if (!(hgc->capflags & CAP_GETPAGER))
607 return NULL;
608
609 packcmdargs(&hgc->ctx, args, argsize);
610 writeblockrequest(hgc, "getpager");
611 handleresponse(hgc);
612
613 if (hgc->ctx.datasize < 1 || hgc->ctx.data[0] == '\0')
614 return NULL;
615 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
616 hgc->ctx.data[hgc->ctx.datasize] = '\0';
617 return hgc->ctx.data;
618 }
619
620 /*!
621 * Update server's environment variables
594 * Update server's environment variables
622 *
595 *
623 * @param envp list of environment variables in "NAME=VALUE" format,
596 * @param envp list of environment variables in "NAME=VALUE" format,
624 * terminated by NULL.
597 * terminated by NULL.
625 */
598 */
626 void hgc_setenv(hgclient_t *hgc, const char *const envp[])
599 void hgc_setenv(hgclient_t *hgc, const char *const envp[])
627 {
600 {
628 assert(hgc && envp);
601 assert(hgc && envp);
629 if (!(hgc->capflags & CAP_SETENV))
602 if (!(hgc->capflags & CAP_SETENV))
630 return;
603 return;
631 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1);
604 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1);
632 writeblockrequest(hgc, "setenv");
605 writeblockrequest(hgc, "setenv");
633 }
606 }
@@ -1,32 +1,30 b''
1 /*
1 /*
2 * A command server client that uses Unix domain socket
2 * A command server client that uses Unix domain socket
3 *
3 *
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 *
5 *
6 * This software may be used and distributed according to the terms of the
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.
7 * GNU General Public License version 2 or any later version.
8 */
8 */
9
9
10 #ifndef HGCLIENT_H_
10 #ifndef HGCLIENT_H_
11 #define HGCLIENT_H_
11 #define HGCLIENT_H_
12
12
13 #include <sys/types.h>
13 #include <sys/types.h>
14
14
15 struct hgclient_tag_;
15 struct hgclient_tag_;
16 typedef struct hgclient_tag_ hgclient_t;
16 typedef struct hgclient_tag_ hgclient_t;
17
17
18 hgclient_t *hgc_open(const char *sockname);
18 hgclient_t *hgc_open(const char *sockname);
19 void hgc_close(hgclient_t *hgc);
19 void hgc_close(hgclient_t *hgc);
20
20
21 pid_t hgc_peerpgid(const hgclient_t *hgc);
21 pid_t hgc_peerpgid(const hgclient_t *hgc);
22 pid_t hgc_peerpid(const hgclient_t *hgc);
22 pid_t hgc_peerpid(const hgclient_t *hgc);
23
23
24 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
24 const char **hgc_validate(hgclient_t *hgc, const char *const args[],
25 size_t argsize);
25 size_t argsize);
26 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize);
26 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize);
27 void hgc_attachio(hgclient_t *hgc);
27 void hgc_attachio(hgclient_t *hgc);
28 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
29 size_t argsize);
30 void hgc_setenv(hgclient_t *hgc, const char *const envp[]);
28 void hgc_setenv(hgclient_t *hgc, const char *const envp[]);
31
29
32 #endif /* HGCLIENT_H_ */
30 #endif /* HGCLIENT_H_ */
@@ -1,656 +1,580 b''
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """command server extension for cHg
8 """command server extension for cHg
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'getpager' command
20 checks if pager is enabled and which pager should be executed
21
22 'setenv' command
19 'setenv' command
23 replace os.environ completely
20 replace os.environ completely
24
21
25 'setumask' command
22 'setumask' command
26 set umask
23 set umask
27
24
28 'validate' command
25 'validate' command
29 reload the config and check if the server is up to date
26 reload the config and check if the server is up to date
30
27
31 Config
28 Config
32 ------
29 ------
33
30
34 ::
31 ::
35
32
36 [chgserver]
33 [chgserver]
37 idletimeout = 3600 # seconds, after which an idle server will exit
34 idletimeout = 3600 # seconds, after which an idle server will exit
38 skiphash = False # whether to skip config or env change checks
35 skiphash = False # whether to skip config or env change checks
39 """
36 """
40
37
41 from __future__ import absolute_import
38 from __future__ import absolute_import
42
39
43 import errno
40 import errno
44 import hashlib
41 import hashlib
45 import inspect
42 import inspect
46 import os
43 import os
47 import re
44 import re
48 import signal
49 import struct
45 import struct
50 import time
46 import time
51
47
52 from .i18n import _
48 from .i18n import _
53
49
54 from . import (
50 from . import (
55 cmdutil,
56 commandserver,
51 commandserver,
57 encoding,
52 encoding,
58 error,
53 error,
59 extensions,
54 extensions,
60 osutil,
55 osutil,
61 pycompat,
56 pycompat,
62 util,
57 util,
63 )
58 )
64
59
65 _log = commandserver.log
60 _log = commandserver.log
66
61
67 def _hashlist(items):
62 def _hashlist(items):
68 """return sha1 hexdigest for a list"""
63 """return sha1 hexdigest for a list"""
69 return hashlib.sha1(str(items)).hexdigest()
64 return hashlib.sha1(str(items)).hexdigest()
70
65
71 # sensitive config sections affecting confighash
66 # sensitive config sections affecting confighash
72 _configsections = [
67 _configsections = [
73 'alias', # affects global state commands.table
68 'alias', # affects global state commands.table
74 'extdiff', # uisetup will register new commands
69 'extdiff', # uisetup will register new commands
75 'extensions',
70 'extensions',
76 ]
71 ]
77
72
78 # sensitive environment variables affecting confighash
73 # sensitive environment variables affecting confighash
79 _envre = re.compile(r'''\A(?:
74 _envre = re.compile(r'''\A(?:
80 CHGHG
75 CHGHG
81 |HG(?:[A-Z].*)?
76 |HG(?:[A-Z].*)?
82 |LANG(?:UAGE)?
77 |LANG(?:UAGE)?
83 |LC_.*
78 |LC_.*
84 |LD_.*
79 |LD_.*
85 |PATH
80 |PATH
86 |PYTHON.*
81 |PYTHON.*
87 |TERM(?:INFO)?
82 |TERM(?:INFO)?
88 |TZ
83 |TZ
89 )\Z''', re.X)
84 )\Z''', re.X)
90
85
91 def _confighash(ui):
86 def _confighash(ui):
92 """return a quick hash for detecting config/env changes
87 """return a quick hash for detecting config/env changes
93
88
94 confighash is the hash of sensitive config items and environment variables.
89 confighash is the hash of sensitive config items and environment variables.
95
90
96 for chgserver, it is designed that once confighash changes, the server is
91 for chgserver, it is designed that once confighash changes, the server is
97 not qualified to serve its client and should redirect the client to a new
92 not qualified to serve its client and should redirect the client to a new
98 server. different from mtimehash, confighash change will not mark the
93 server. different from mtimehash, confighash change will not mark the
99 server outdated and exit since the user can have different configs at the
94 server outdated and exit since the user can have different configs at the
100 same time.
95 same time.
101 """
96 """
102 sectionitems = []
97 sectionitems = []
103 for section in _configsections:
98 for section in _configsections:
104 sectionitems.append(ui.configitems(section))
99 sectionitems.append(ui.configitems(section))
105 sectionhash = _hashlist(sectionitems)
100 sectionhash = _hashlist(sectionitems)
106 envitems = [(k, v) for k, v in encoding.environ.iteritems()
101 envitems = [(k, v) for k, v in encoding.environ.iteritems()
107 if _envre.match(k)]
102 if _envre.match(k)]
108 envhash = _hashlist(sorted(envitems))
103 envhash = _hashlist(sorted(envitems))
109 return sectionhash[:6] + envhash[:6]
104 return sectionhash[:6] + envhash[:6]
110
105
111 def _getmtimepaths(ui):
106 def _getmtimepaths(ui):
112 """get a list of paths that should be checked to detect change
107 """get a list of paths that should be checked to detect change
113
108
114 The list will include:
109 The list will include:
115 - extensions (will not cover all files for complex extensions)
110 - extensions (will not cover all files for complex extensions)
116 - mercurial/__version__.py
111 - mercurial/__version__.py
117 - python binary
112 - python binary
118 """
113 """
119 modules = [m for n, m in extensions.extensions(ui)]
114 modules = [m for n, m in extensions.extensions(ui)]
120 try:
115 try:
121 from . import __version__
116 from . import __version__
122 modules.append(__version__)
117 modules.append(__version__)
123 except ImportError:
118 except ImportError:
124 pass
119 pass
125 files = [pycompat.sysexecutable]
120 files = [pycompat.sysexecutable]
126 for m in modules:
121 for m in modules:
127 try:
122 try:
128 files.append(inspect.getabsfile(m))
123 files.append(inspect.getabsfile(m))
129 except TypeError:
124 except TypeError:
130 pass
125 pass
131 return sorted(set(files))
126 return sorted(set(files))
132
127
133 def _mtimehash(paths):
128 def _mtimehash(paths):
134 """return a quick hash for detecting file changes
129 """return a quick hash for detecting file changes
135
130
136 mtimehash calls stat on given paths and calculate a hash based on size and
131 mtimehash calls stat on given paths and calculate a hash based on size and
137 mtime of each file. mtimehash does not read file content because reading is
132 mtime of each file. mtimehash does not read file content because reading is
138 expensive. therefore it's not 100% reliable for detecting content changes.
133 expensive. therefore it's not 100% reliable for detecting content changes.
139 it's possible to return different hashes for same file contents.
134 it's possible to return different hashes for same file contents.
140 it's also possible to return a same hash for different file contents for
135 it's also possible to return a same hash for different file contents for
141 some carefully crafted situation.
136 some carefully crafted situation.
142
137
143 for chgserver, it is designed that once mtimehash changes, the server is
138 for chgserver, it is designed that once mtimehash changes, the server is
144 considered outdated immediately and should no longer provide service.
139 considered outdated immediately and should no longer provide service.
145
140
146 mtimehash is not included in confighash because we only know the paths of
141 mtimehash is not included in confighash because we only know the paths of
147 extensions after importing them (there is imp.find_module but that faces
142 extensions after importing them (there is imp.find_module but that faces
148 race conditions). We need to calculate confighash without importing.
143 race conditions). We need to calculate confighash without importing.
149 """
144 """
150 def trystat(path):
145 def trystat(path):
151 try:
146 try:
152 st = os.stat(path)
147 st = os.stat(path)
153 return (st.st_mtime, st.st_size)
148 return (st.st_mtime, st.st_size)
154 except OSError:
149 except OSError:
155 # could be ENOENT, EPERM etc. not fatal in any case
150 # could be ENOENT, EPERM etc. not fatal in any case
156 pass
151 pass
157 return _hashlist(map(trystat, paths))[:12]
152 return _hashlist(map(trystat, paths))[:12]
158
153
159 class hashstate(object):
154 class hashstate(object):
160 """a structure storing confighash, mtimehash, paths used for mtimehash"""
155 """a structure storing confighash, mtimehash, paths used for mtimehash"""
161 def __init__(self, confighash, mtimehash, mtimepaths):
156 def __init__(self, confighash, mtimehash, mtimepaths):
162 self.confighash = confighash
157 self.confighash = confighash
163 self.mtimehash = mtimehash
158 self.mtimehash = mtimehash
164 self.mtimepaths = mtimepaths
159 self.mtimepaths = mtimepaths
165
160
166 @staticmethod
161 @staticmethod
167 def fromui(ui, mtimepaths=None):
162 def fromui(ui, mtimepaths=None):
168 if mtimepaths is None:
163 if mtimepaths is None:
169 mtimepaths = _getmtimepaths(ui)
164 mtimepaths = _getmtimepaths(ui)
170 confighash = _confighash(ui)
165 confighash = _confighash(ui)
171 mtimehash = _mtimehash(mtimepaths)
166 mtimehash = _mtimehash(mtimepaths)
172 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
167 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
173 return hashstate(confighash, mtimehash, mtimepaths)
168 return hashstate(confighash, mtimehash, mtimepaths)
174
169
175 # copied from hgext/pager.py:uisetup()
176 def _setuppagercmd(ui, options, cmd):
177 from . import commands # avoid cycle
178
179 if not ui.formatted():
180 return
181
182 p = ui.config("pager", "pager", encoding.environ.get("PAGER"))
183 usepager = False
184 always = util.parsebool(options['pager'])
185 auto = options['pager'] == 'auto'
186
187 if not p:
188 pass
189 elif always:
190 usepager = True
191 elif not auto:
192 usepager = False
193 else:
194 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
195 attend = ui.configlist('pager', 'attend', attended)
196 ignore = ui.configlist('pager', 'ignore')
197 cmds, _ = cmdutil.findcmd(cmd, commands.table)
198
199 for cmd in cmds:
200 var = 'attend-%s' % cmd
201 if ui.config('pager', var):
202 usepager = ui.configbool('pager', var)
203 break
204 if (cmd in attend or
205 (cmd not in ignore and not attend)):
206 usepager = True
207 break
208
209 if usepager:
210 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
211 ui.setconfig('ui', 'interactive', False, 'pager')
212 return p
213
214 def _newchgui(srcui, csystem, attachio):
170 def _newchgui(srcui, csystem, attachio):
215 class chgui(srcui.__class__):
171 class chgui(srcui.__class__):
216 def __init__(self, src=None):
172 def __init__(self, src=None):
217 super(chgui, self).__init__(src)
173 super(chgui, self).__init__(src)
218 if src:
174 if src:
219 self._csystem = getattr(src, '_csystem', csystem)
175 self._csystem = getattr(src, '_csystem', csystem)
220 else:
176 else:
221 self._csystem = csystem
177 self._csystem = csystem
222
178
223 def system(self, cmd, environ=None, cwd=None, onerr=None,
179 def system(self, cmd, environ=None, cwd=None, onerr=None,
224 errprefix=None):
180 errprefix=None):
225 # fallback to the original system method if the output needs to be
181 # fallback to the original system method if the output needs to be
226 # captured (to self._buffers), or the output stream is not stdout
182 # captured (to self._buffers), or the output stream is not stdout
227 # (e.g. stderr, cStringIO), because the chg client is not aware of
183 # (e.g. stderr, cStringIO), because the chg client is not aware of
228 # these situations and will behave differently (write to stdout).
184 # these situations and will behave differently (write to stdout).
229 if (any(s[1] for s in self._bufferstates)
185 if (any(s[1] for s in self._bufferstates)
230 or not util.safehasattr(self.fout, 'fileno')
186 or not util.safehasattr(self.fout, 'fileno')
231 or self.fout.fileno() != util.stdout.fileno()):
187 or self.fout.fileno() != util.stdout.fileno()):
232 return super(chgui, self).system(cmd, environ, cwd, onerr,
188 return super(chgui, self).system(cmd, environ, cwd, onerr,
233 errprefix)
189 errprefix)
234 self.flush()
190 self.flush()
235 rc = self._csystem(cmd, util.shellenviron(environ), cwd)
191 rc = self._csystem(cmd, util.shellenviron(environ), cwd)
236 if rc and onerr:
192 if rc and onerr:
237 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
193 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
238 util.explainexit(rc)[0])
194 util.explainexit(rc)[0])
239 if errprefix:
195 if errprefix:
240 errmsg = '%s: %s' % (errprefix, errmsg)
196 errmsg = '%s: %s' % (errprefix, errmsg)
241 raise onerr(errmsg)
197 raise onerr(errmsg)
242 return rc
198 return rc
243
199
244 def _runpager(self, cmd):
200 def _runpager(self, cmd):
245 self._csystem(cmd, util.shellenviron(), type='pager',
201 self._csystem(cmd, util.shellenviron(), type='pager',
246 cmdtable={'attachio': attachio})
202 cmdtable={'attachio': attachio})
247
203
248 return chgui(srcui)
204 return chgui(srcui)
249
205
250 def _loadnewui(srcui, args):
206 def _loadnewui(srcui, args):
251 from . import dispatch # avoid cycle
207 from . import dispatch # avoid cycle
252
208
253 newui = srcui.__class__.load()
209 newui = srcui.__class__.load()
254 for a in ['fin', 'fout', 'ferr', 'environ']:
210 for a in ['fin', 'fout', 'ferr', 'environ']:
255 setattr(newui, a, getattr(srcui, a))
211 setattr(newui, a, getattr(srcui, a))
256 if util.safehasattr(srcui, '_csystem'):
212 if util.safehasattr(srcui, '_csystem'):
257 newui._csystem = srcui._csystem
213 newui._csystem = srcui._csystem
258
214
259 # command line args
215 # command line args
260 args = args[:]
216 args = args[:]
261 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
217 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
262
218
263 # stolen from tortoisehg.util.copydynamicconfig()
219 # stolen from tortoisehg.util.copydynamicconfig()
264 for section, name, value in srcui.walkconfig():
220 for section, name, value in srcui.walkconfig():
265 source = srcui.configsource(section, name)
221 source = srcui.configsource(section, name)
266 if ':' in source or source == '--config':
222 if ':' in source or source == '--config':
267 # path:line or command line
223 # path:line or command line
268 continue
224 continue
269 newui.setconfig(section, name, value, source)
225 newui.setconfig(section, name, value, source)
270
226
271 # load wd and repo config, copied from dispatch.py
227 # load wd and repo config, copied from dispatch.py
272 cwds = dispatch._earlygetopt(['--cwd'], args)
228 cwds = dispatch._earlygetopt(['--cwd'], args)
273 cwd = cwds and os.path.realpath(cwds[-1]) or None
229 cwd = cwds and os.path.realpath(cwds[-1]) or None
274 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
230 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
275 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
231 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
276
232
277 return (newui, newlui)
233 return (newui, newlui)
278
234
279 class channeledsystem(object):
235 class channeledsystem(object):
280 """Propagate ui.system() request in the following format:
236 """Propagate ui.system() request in the following format:
281
237
282 payload length (unsigned int),
238 payload length (unsigned int),
283 type, '\0',
239 type, '\0',
284 cmd, '\0',
240 cmd, '\0',
285 cwd, '\0',
241 cwd, '\0',
286 envkey, '=', val, '\0',
242 envkey, '=', val, '\0',
287 ...
243 ...
288 envkey, '=', val
244 envkey, '=', val
289
245
290 if type == 'system', waits for:
246 if type == 'system', waits for:
291
247
292 exitcode length (unsigned int),
248 exitcode length (unsigned int),
293 exitcode (int)
249 exitcode (int)
294
250
295 if type == 'pager', repetitively waits for a command name ending with '\n'
251 if type == 'pager', repetitively waits for a command name ending with '\n'
296 and executes it defined by cmdtable, or exits the loop if the command name
252 and executes it defined by cmdtable, or exits the loop if the command name
297 is empty.
253 is empty.
298 """
254 """
299 def __init__(self, in_, out, channel):
255 def __init__(self, in_, out, channel):
300 self.in_ = in_
256 self.in_ = in_
301 self.out = out
257 self.out = out
302 self.channel = channel
258 self.channel = channel
303
259
304 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
260 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
305 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
261 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
306 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
262 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
307 data = '\0'.join(args)
263 data = '\0'.join(args)
308 self.out.write(struct.pack('>cI', self.channel, len(data)))
264 self.out.write(struct.pack('>cI', self.channel, len(data)))
309 self.out.write(data)
265 self.out.write(data)
310 self.out.flush()
266 self.out.flush()
311
267
312 if type == 'system':
268 if type == 'system':
313 length = self.in_.read(4)
269 length = self.in_.read(4)
314 length, = struct.unpack('>I', length)
270 length, = struct.unpack('>I', length)
315 if length != 4:
271 if length != 4:
316 raise error.Abort(_('invalid response'))
272 raise error.Abort(_('invalid response'))
317 rc, = struct.unpack('>i', self.in_.read(4))
273 rc, = struct.unpack('>i', self.in_.read(4))
318 return rc
274 return rc
319 elif type == 'pager':
275 elif type == 'pager':
320 while True:
276 while True:
321 cmd = self.in_.readline()[:-1]
277 cmd = self.in_.readline()[:-1]
322 if not cmd:
278 if not cmd:
323 break
279 break
324 if cmdtable and cmd in cmdtable:
280 if cmdtable and cmd in cmdtable:
325 _log('pager subcommand: %s' % cmd)
281 _log('pager subcommand: %s' % cmd)
326 cmdtable[cmd]()
282 cmdtable[cmd]()
327 else:
283 else:
328 raise error.Abort(_('unexpected command: %s') % cmd)
284 raise error.Abort(_('unexpected command: %s') % cmd)
329 else:
285 else:
330 raise error.ProgrammingError('invalid S channel type: %s' % type)
286 raise error.ProgrammingError('invalid S channel type: %s' % type)
331
287
332 _iochannels = [
288 _iochannels = [
333 # server.ch, ui.fp, mode
289 # server.ch, ui.fp, mode
334 ('cin', 'fin', 'rb'),
290 ('cin', 'fin', 'rb'),
335 ('cout', 'fout', 'wb'),
291 ('cout', 'fout', 'wb'),
336 ('cerr', 'ferr', 'wb'),
292 ('cerr', 'ferr', 'wb'),
337 ]
293 ]
338
294
339 class chgcmdserver(commandserver.server):
295 class chgcmdserver(commandserver.server):
340 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
296 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
341 super(chgcmdserver, self).__init__(
297 super(chgcmdserver, self).__init__(
342 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
298 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
343 repo, fin, fout)
299 repo, fin, fout)
344 self.clientsock = sock
300 self.clientsock = sock
345 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
301 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
346 self.hashstate = hashstate
302 self.hashstate = hashstate
347 self.baseaddress = baseaddress
303 self.baseaddress = baseaddress
348 if hashstate is not None:
304 if hashstate is not None:
349 self.capabilities = self.capabilities.copy()
305 self.capabilities = self.capabilities.copy()
350 self.capabilities['validate'] = chgcmdserver.validate
306 self.capabilities['validate'] = chgcmdserver.validate
351
307
352 def cleanup(self):
308 def cleanup(self):
353 super(chgcmdserver, self).cleanup()
309 super(chgcmdserver, self).cleanup()
354 # dispatch._runcatch() does not flush outputs if exception is not
310 # dispatch._runcatch() does not flush outputs if exception is not
355 # handled by dispatch._dispatch()
311 # handled by dispatch._dispatch()
356 self.ui.flush()
312 self.ui.flush()
357 self._restoreio()
313 self._restoreio()
358
314
359 def attachio(self):
315 def attachio(self):
360 """Attach to client's stdio passed via unix domain socket; all
316 """Attach to client's stdio passed via unix domain socket; all
361 channels except cresult will no longer be used
317 channels except cresult will no longer be used
362 """
318 """
363 # tell client to sendmsg() with 1-byte payload, which makes it
319 # tell client to sendmsg() with 1-byte payload, which makes it
364 # distinctive from "attachio\n" command consumed by client.read()
320 # distinctive from "attachio\n" command consumed by client.read()
365 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
321 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
366 clientfds = osutil.recvfds(self.clientsock.fileno())
322 clientfds = osutil.recvfds(self.clientsock.fileno())
367 _log('received fds: %r\n' % clientfds)
323 _log('received fds: %r\n' % clientfds)
368
324
369 ui = self.ui
325 ui = self.ui
370 ui.flush()
326 ui.flush()
371 first = self._saveio()
327 first = self._saveio()
372 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
328 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
373 assert fd > 0
329 assert fd > 0
374 fp = getattr(ui, fn)
330 fp = getattr(ui, fn)
375 os.dup2(fd, fp.fileno())
331 os.dup2(fd, fp.fileno())
376 os.close(fd)
332 os.close(fd)
377 if not first:
333 if not first:
378 continue
334 continue
379 # reset buffering mode when client is first attached. as we want
335 # reset buffering mode when client is first attached. as we want
380 # to see output immediately on pager, the mode stays unchanged
336 # to see output immediately on pager, the mode stays unchanged
381 # when client re-attached. ferr is unchanged because it should
337 # when client re-attached. ferr is unchanged because it should
382 # be unbuffered no matter if it is a tty or not.
338 # be unbuffered no matter if it is a tty or not.
383 if fn == 'ferr':
339 if fn == 'ferr':
384 newfp = fp
340 newfp = fp
385 else:
341 else:
386 # make it line buffered explicitly because the default is
342 # make it line buffered explicitly because the default is
387 # decided on first write(), where fout could be a pager.
343 # decided on first write(), where fout could be a pager.
388 if fp.isatty():
344 if fp.isatty():
389 bufsize = 1 # line buffered
345 bufsize = 1 # line buffered
390 else:
346 else:
391 bufsize = -1 # system default
347 bufsize = -1 # system default
392 newfp = os.fdopen(fp.fileno(), mode, bufsize)
348 newfp = os.fdopen(fp.fileno(), mode, bufsize)
393 setattr(ui, fn, newfp)
349 setattr(ui, fn, newfp)
394 setattr(self, cn, newfp)
350 setattr(self, cn, newfp)
395
351
396 self.cresult.write(struct.pack('>i', len(clientfds)))
352 self.cresult.write(struct.pack('>i', len(clientfds)))
397
353
398 def _saveio(self):
354 def _saveio(self):
399 if self._oldios:
355 if self._oldios:
400 return False
356 return False
401 ui = self.ui
357 ui = self.ui
402 for cn, fn, _mode in _iochannels:
358 for cn, fn, _mode in _iochannels:
403 ch = getattr(self, cn)
359 ch = getattr(self, cn)
404 fp = getattr(ui, fn)
360 fp = getattr(ui, fn)
405 fd = os.dup(fp.fileno())
361 fd = os.dup(fp.fileno())
406 self._oldios.append((ch, fp, fd))
362 self._oldios.append((ch, fp, fd))
407 return True
363 return True
408
364
409 def _restoreio(self):
365 def _restoreio(self):
410 ui = self.ui
366 ui = self.ui
411 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
367 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
412 newfp = getattr(ui, fn)
368 newfp = getattr(ui, fn)
413 # close newfp while it's associated with client; otherwise it
369 # close newfp while it's associated with client; otherwise it
414 # would be closed when newfp is deleted
370 # would be closed when newfp is deleted
415 if newfp is not fp:
371 if newfp is not fp:
416 newfp.close()
372 newfp.close()
417 # restore original fd: fp is open again
373 # restore original fd: fp is open again
418 os.dup2(fd, fp.fileno())
374 os.dup2(fd, fp.fileno())
419 os.close(fd)
375 os.close(fd)
420 setattr(self, cn, ch)
376 setattr(self, cn, ch)
421 setattr(ui, fn, fp)
377 setattr(ui, fn, fp)
422 del self._oldios[:]
378 del self._oldios[:]
423
379
424 def validate(self):
380 def validate(self):
425 """Reload the config and check if the server is up to date
381 """Reload the config and check if the server is up to date
426
382
427 Read a list of '\0' separated arguments.
383 Read a list of '\0' separated arguments.
428 Write a non-empty list of '\0' separated instruction strings or '\0'
384 Write a non-empty list of '\0' separated instruction strings or '\0'
429 if the list is empty.
385 if the list is empty.
430 An instruction string could be either:
386 An instruction string could be either:
431 - "unlink $path", the client should unlink the path to stop the
387 - "unlink $path", the client should unlink the path to stop the
432 outdated server.
388 outdated server.
433 - "redirect $path", the client should attempt to connect to $path
389 - "redirect $path", the client should attempt to connect to $path
434 first. If it does not work, start a new server. It implies
390 first. If it does not work, start a new server. It implies
435 "reconnect".
391 "reconnect".
436 - "exit $n", the client should exit directly with code n.
392 - "exit $n", the client should exit directly with code n.
437 This may happen if we cannot parse the config.
393 This may happen if we cannot parse the config.
438 - "reconnect", the client should close the connection and
394 - "reconnect", the client should close the connection and
439 reconnect.
395 reconnect.
440 If neither "reconnect" nor "redirect" is included in the instruction
396 If neither "reconnect" nor "redirect" is included in the instruction
441 list, the client can continue with this server after completing all
397 list, the client can continue with this server after completing all
442 the instructions.
398 the instructions.
443 """
399 """
444 from . import dispatch # avoid cycle
400 from . import dispatch # avoid cycle
445
401
446 args = self._readlist()
402 args = self._readlist()
447 try:
403 try:
448 self.ui, lui = _loadnewui(self.ui, args)
404 self.ui, lui = _loadnewui(self.ui, args)
449 except error.ParseError as inst:
405 except error.ParseError as inst:
450 dispatch._formatparse(self.ui.warn, inst)
406 dispatch._formatparse(self.ui.warn, inst)
451 self.ui.flush()
407 self.ui.flush()
452 self.cresult.write('exit 255')
408 self.cresult.write('exit 255')
453 return
409 return
454 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
410 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
455 insts = []
411 insts = []
456 if newhash.mtimehash != self.hashstate.mtimehash:
412 if newhash.mtimehash != self.hashstate.mtimehash:
457 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
413 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
458 insts.append('unlink %s' % addr)
414 insts.append('unlink %s' % addr)
459 # mtimehash is empty if one or more extensions fail to load.
415 # mtimehash is empty if one or more extensions fail to load.
460 # to be compatible with hg, still serve the client this time.
416 # to be compatible with hg, still serve the client this time.
461 if self.hashstate.mtimehash:
417 if self.hashstate.mtimehash:
462 insts.append('reconnect')
418 insts.append('reconnect')
463 if newhash.confighash != self.hashstate.confighash:
419 if newhash.confighash != self.hashstate.confighash:
464 addr = _hashaddress(self.baseaddress, newhash.confighash)
420 addr = _hashaddress(self.baseaddress, newhash.confighash)
465 insts.append('redirect %s' % addr)
421 insts.append('redirect %s' % addr)
466 _log('validate: %s\n' % insts)
422 _log('validate: %s\n' % insts)
467 self.cresult.write('\0'.join(insts) or '\0')
423 self.cresult.write('\0'.join(insts) or '\0')
468
424
469 def chdir(self):
425 def chdir(self):
470 """Change current directory
426 """Change current directory
471
427
472 Note that the behavior of --cwd option is bit different from this.
428 Note that the behavior of --cwd option is bit different from this.
473 It does not affect --config parameter.
429 It does not affect --config parameter.
474 """
430 """
475 path = self._readstr()
431 path = self._readstr()
476 if not path:
432 if not path:
477 return
433 return
478 _log('chdir to %r\n' % path)
434 _log('chdir to %r\n' % path)
479 os.chdir(path)
435 os.chdir(path)
480
436
481 def setumask(self):
437 def setumask(self):
482 """Change umask"""
438 """Change umask"""
483 mask = struct.unpack('>I', self._read(4))[0]
439 mask = struct.unpack('>I', self._read(4))[0]
484 _log('setumask %r\n' % mask)
440 _log('setumask %r\n' % mask)
485 os.umask(mask)
441 os.umask(mask)
486
442
487 def getpager(self):
488 """Read cmdargs and write pager command to r-channel if enabled
489
490 If pager isn't enabled, this writes '\0' because channeledoutput
491 does not allow to write empty data.
492 """
493 from . import dispatch # avoid cycle
494
495 args = self._readlist()
496 try:
497 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
498 args)
499 except (error.Abort, error.AmbiguousCommand, error.CommandError,
500 error.UnknownCommand):
501 cmd = None
502 options = {}
503 if not cmd or 'pager' not in options:
504 self.cresult.write('\0')
505 return
506
507 pagercmd = _setuppagercmd(self.ui, options, cmd)
508 if pagercmd:
509 # Python's SIGPIPE is SIG_IGN by default. change to SIG_DFL so
510 # we can exit if the pipe to the pager is closed
511 if util.safehasattr(signal, 'SIGPIPE') and \
512 signal.getsignal(signal.SIGPIPE) == signal.SIG_IGN:
513 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
514 self.cresult.write(pagercmd)
515 else:
516 self.cresult.write('\0')
517
518 def runcommand(self):
443 def runcommand(self):
519 return super(chgcmdserver, self).runcommand()
444 return super(chgcmdserver, self).runcommand()
520
445
521 def setenv(self):
446 def setenv(self):
522 """Clear and update os.environ
447 """Clear and update os.environ
523
448
524 Note that not all variables can make an effect on the running process.
449 Note that not all variables can make an effect on the running process.
525 """
450 """
526 l = self._readlist()
451 l = self._readlist()
527 try:
452 try:
528 newenv = dict(s.split('=', 1) for s in l)
453 newenv = dict(s.split('=', 1) for s in l)
529 except ValueError:
454 except ValueError:
530 raise ValueError('unexpected value in setenv request')
455 raise ValueError('unexpected value in setenv request')
531 _log('setenv: %r\n' % sorted(newenv.keys()))
456 _log('setenv: %r\n' % sorted(newenv.keys()))
532 encoding.environ.clear()
457 encoding.environ.clear()
533 encoding.environ.update(newenv)
458 encoding.environ.update(newenv)
534
459
535 capabilities = commandserver.server.capabilities.copy()
460 capabilities = commandserver.server.capabilities.copy()
536 capabilities.update({'attachio': attachio,
461 capabilities.update({'attachio': attachio,
537 'chdir': chdir,
462 'chdir': chdir,
538 'getpager': getpager,
539 'runcommand': runcommand,
463 'runcommand': runcommand,
540 'setenv': setenv,
464 'setenv': setenv,
541 'setumask': setumask})
465 'setumask': setumask})
542
466
543 def _tempaddress(address):
467 def _tempaddress(address):
544 return '%s.%d.tmp' % (address, os.getpid())
468 return '%s.%d.tmp' % (address, os.getpid())
545
469
546 def _hashaddress(address, hashstr):
470 def _hashaddress(address, hashstr):
547 # if the basename of address contains '.', use only the left part. this
471 # if the basename of address contains '.', use only the left part. this
548 # makes it possible for the client to pass 'server.tmp$PID' and follow by
472 # makes it possible for the client to pass 'server.tmp$PID' and follow by
549 # an atomic rename to avoid locking when spawning new servers.
473 # an atomic rename to avoid locking when spawning new servers.
550 dirname, basename = os.path.split(address)
474 dirname, basename = os.path.split(address)
551 basename = basename.split('.', 1)[0]
475 basename = basename.split('.', 1)[0]
552 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
476 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
553
477
554 class chgunixservicehandler(object):
478 class chgunixservicehandler(object):
555 """Set of operations for chg services"""
479 """Set of operations for chg services"""
556
480
557 pollinterval = 1 # [sec]
481 pollinterval = 1 # [sec]
558
482
559 def __init__(self, ui):
483 def __init__(self, ui):
560 self.ui = ui
484 self.ui = ui
561 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
485 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
562 self._lastactive = time.time()
486 self._lastactive = time.time()
563
487
564 def bindsocket(self, sock, address):
488 def bindsocket(self, sock, address):
565 self._inithashstate(address)
489 self._inithashstate(address)
566 self._checkextensions()
490 self._checkextensions()
567 self._bind(sock)
491 self._bind(sock)
568 self._createsymlink()
492 self._createsymlink()
569
493
570 def _inithashstate(self, address):
494 def _inithashstate(self, address):
571 self._baseaddress = address
495 self._baseaddress = address
572 if self.ui.configbool('chgserver', 'skiphash', False):
496 if self.ui.configbool('chgserver', 'skiphash', False):
573 self._hashstate = None
497 self._hashstate = None
574 self._realaddress = address
498 self._realaddress = address
575 return
499 return
576 self._hashstate = hashstate.fromui(self.ui)
500 self._hashstate = hashstate.fromui(self.ui)
577 self._realaddress = _hashaddress(address, self._hashstate.confighash)
501 self._realaddress = _hashaddress(address, self._hashstate.confighash)
578
502
579 def _checkextensions(self):
503 def _checkextensions(self):
580 if not self._hashstate:
504 if not self._hashstate:
581 return
505 return
582 if extensions.notloaded():
506 if extensions.notloaded():
583 # one or more extensions failed to load. mtimehash becomes
507 # one or more extensions failed to load. mtimehash becomes
584 # meaningless because we do not know the paths of those extensions.
508 # meaningless because we do not know the paths of those extensions.
585 # set mtimehash to an illegal hash value to invalidate the server.
509 # set mtimehash to an illegal hash value to invalidate the server.
586 self._hashstate.mtimehash = ''
510 self._hashstate.mtimehash = ''
587
511
588 def _bind(self, sock):
512 def _bind(self, sock):
589 # use a unique temp address so we can stat the file and do ownership
513 # use a unique temp address so we can stat the file and do ownership
590 # check later
514 # check later
591 tempaddress = _tempaddress(self._realaddress)
515 tempaddress = _tempaddress(self._realaddress)
592 util.bindunixsocket(sock, tempaddress)
516 util.bindunixsocket(sock, tempaddress)
593 self._socketstat = os.stat(tempaddress)
517 self._socketstat = os.stat(tempaddress)
594 # rename will replace the old socket file if exists atomically. the
518 # rename will replace the old socket file if exists atomically. the
595 # old server will detect ownership change and exit.
519 # old server will detect ownership change and exit.
596 util.rename(tempaddress, self._realaddress)
520 util.rename(tempaddress, self._realaddress)
597
521
598 def _createsymlink(self):
522 def _createsymlink(self):
599 if self._baseaddress == self._realaddress:
523 if self._baseaddress == self._realaddress:
600 return
524 return
601 tempaddress = _tempaddress(self._baseaddress)
525 tempaddress = _tempaddress(self._baseaddress)
602 os.symlink(os.path.basename(self._realaddress), tempaddress)
526 os.symlink(os.path.basename(self._realaddress), tempaddress)
603 util.rename(tempaddress, self._baseaddress)
527 util.rename(tempaddress, self._baseaddress)
604
528
605 def _issocketowner(self):
529 def _issocketowner(self):
606 try:
530 try:
607 stat = os.stat(self._realaddress)
531 stat = os.stat(self._realaddress)
608 return (stat.st_ino == self._socketstat.st_ino and
532 return (stat.st_ino == self._socketstat.st_ino and
609 stat.st_mtime == self._socketstat.st_mtime)
533 stat.st_mtime == self._socketstat.st_mtime)
610 except OSError:
534 except OSError:
611 return False
535 return False
612
536
613 def unlinksocket(self, address):
537 def unlinksocket(self, address):
614 if not self._issocketowner():
538 if not self._issocketowner():
615 return
539 return
616 # it is possible to have a race condition here that we may
540 # it is possible to have a race condition here that we may
617 # remove another server's socket file. but that's okay
541 # remove another server's socket file. but that's okay
618 # since that server will detect and exit automatically and
542 # since that server will detect and exit automatically and
619 # the client will start a new server on demand.
543 # the client will start a new server on demand.
620 try:
544 try:
621 os.unlink(self._realaddress)
545 os.unlink(self._realaddress)
622 except OSError as exc:
546 except OSError as exc:
623 if exc.errno != errno.ENOENT:
547 if exc.errno != errno.ENOENT:
624 raise
548 raise
625
549
626 def printbanner(self, address):
550 def printbanner(self, address):
627 # no "listening at" message should be printed to simulate hg behavior
551 # no "listening at" message should be printed to simulate hg behavior
628 pass
552 pass
629
553
630 def shouldexit(self):
554 def shouldexit(self):
631 if not self._issocketowner():
555 if not self._issocketowner():
632 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
556 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
633 return True
557 return True
634 if time.time() - self._lastactive > self._idletimeout:
558 if time.time() - self._lastactive > self._idletimeout:
635 self.ui.debug('being idle too long. exiting.\n')
559 self.ui.debug('being idle too long. exiting.\n')
636 return True
560 return True
637 return False
561 return False
638
562
639 def newconnection(self):
563 def newconnection(self):
640 self._lastactive = time.time()
564 self._lastactive = time.time()
641
565
642 def createcmdserver(self, repo, conn, fin, fout):
566 def createcmdserver(self, repo, conn, fin, fout):
643 return chgcmdserver(self.ui, repo, fin, fout, conn,
567 return chgcmdserver(self.ui, repo, fin, fout, conn,
644 self._hashstate, self._baseaddress)
568 self._hashstate, self._baseaddress)
645
569
646 def chgunixservice(ui, repo, opts):
570 def chgunixservice(ui, repo, opts):
647 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
571 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
648 # start another chg. drop it to avoid possible side effects.
572 # start another chg. drop it to avoid possible side effects.
649 if 'CHGINTERNALMARK' in encoding.environ:
573 if 'CHGINTERNALMARK' in encoding.environ:
650 del encoding.environ['CHGINTERNALMARK']
574 del encoding.environ['CHGINTERNALMARK']
651
575
652 if repo:
576 if repo:
653 # one chgserver can serve multiple repos. drop repo information
577 # one chgserver can serve multiple repos. drop repo information
654 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
578 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
655 h = chgunixservicehandler(ui)
579 h = chgunixservicehandler(ui)
656 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
580 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
General Comments 0
You need to be logged in to leave comments. Login now