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