##// END OF EJS Templates
chg: force-set LC_CTYPE on server start to actual value from the environment...
Kyle Lippincott -
r44733:04a3ae7a default
parent child Browse files
Show More
@@ -1,456 +1,466 b''
1 /*
1 /*
2 * A fast client for Mercurial command server
2 * A fast client for Mercurial command server
3 *
3 *
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 *
5 *
6 * This software may be used and distributed according to the terms of the
6 * This software may be used and distributed according to the terms of the
7 * GNU General Public License version 2 or any later version.
7 * GNU General Public License version 2 or any later version.
8 */
8 */
9
9
10 #include <assert.h>
10 #include <assert.h>
11 #include <errno.h>
11 #include <errno.h>
12 #include <fcntl.h>
12 #include <fcntl.h>
13 #include <signal.h>
13 #include <signal.h>
14 #include <stdio.h>
14 #include <stdio.h>
15 #include <stdlib.h>
15 #include <stdlib.h>
16 #include <string.h>
16 #include <string.h>
17 #include <sys/file.h>
17 #include <sys/file.h>
18 #include <sys/stat.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
19 #include <sys/types.h>
20 #include <sys/un.h>
20 #include <sys/un.h>
21 #include <sys/wait.h>
21 #include <sys/wait.h>
22 #include <time.h>
22 #include <time.h>
23 #include <unistd.h>
23 #include <unistd.h>
24
24
25 #include "hgclient.h"
25 #include "hgclient.h"
26 #include "procutil.h"
26 #include "procutil.h"
27 #include "util.h"
27 #include "util.h"
28
28
29 #ifndef PATH_MAX
29 #ifndef PATH_MAX
30 #define PATH_MAX 4096
30 #define PATH_MAX 4096
31 #endif
31 #endif
32
32
33 struct cmdserveropts {
33 struct cmdserveropts {
34 char sockname[PATH_MAX];
34 char sockname[PATH_MAX];
35 char initsockname[PATH_MAX];
35 char initsockname[PATH_MAX];
36 char redirectsockname[PATH_MAX];
36 char redirectsockname[PATH_MAX];
37 size_t argsize;
37 size_t argsize;
38 const char **args;
38 const char **args;
39 };
39 };
40
40
41 static void initcmdserveropts(struct cmdserveropts *opts)
41 static void initcmdserveropts(struct cmdserveropts *opts)
42 {
42 {
43 memset(opts, 0, sizeof(struct cmdserveropts));
43 memset(opts, 0, sizeof(struct cmdserveropts));
44 }
44 }
45
45
46 static void freecmdserveropts(struct cmdserveropts *opts)
46 static void freecmdserveropts(struct cmdserveropts *opts)
47 {
47 {
48 free(opts->args);
48 free(opts->args);
49 opts->args = NULL;
49 opts->args = NULL;
50 opts->argsize = 0;
50 opts->argsize = 0;
51 }
51 }
52
52
53 /*
53 /*
54 * Test if an argument is a sensitive flag that should be passed to the server.
54 * Test if an argument is a sensitive flag that should be passed to the server.
55 * Return 0 if not, otherwise the number of arguments starting from the current
55 * Return 0 if not, otherwise the number of arguments starting from the current
56 * one that should be passed to the server.
56 * one that should be passed to the server.
57 */
57 */
58 static size_t testsensitiveflag(const char *arg)
58 static size_t testsensitiveflag(const char *arg)
59 {
59 {
60 static const struct {
60 static const struct {
61 const char *name;
61 const char *name;
62 size_t narg;
62 size_t narg;
63 } flags[] = {
63 } flags[] = {
64 {"--config", 1}, {"--cwd", 1}, {"--repo", 1},
64 {"--config", 1}, {"--cwd", 1}, {"--repo", 1},
65 {"--repository", 1}, {"--traceback", 0}, {"-R", 1},
65 {"--repository", 1}, {"--traceback", 0}, {"-R", 1},
66 };
66 };
67 size_t i;
67 size_t i;
68 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
68 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
69 size_t len = strlen(flags[i].name);
69 size_t len = strlen(flags[i].name);
70 size_t narg = flags[i].narg;
70 size_t narg = flags[i].narg;
71 if (memcmp(arg, flags[i].name, len) == 0) {
71 if (memcmp(arg, flags[i].name, len) == 0) {
72 if (arg[len] == '\0') {
72 if (arg[len] == '\0') {
73 /* --flag (value) */
73 /* --flag (value) */
74 return narg + 1;
74 return narg + 1;
75 } else if (arg[len] == '=' && narg > 0) {
75 } else if (arg[len] == '=' && narg > 0) {
76 /* --flag=value */
76 /* --flag=value */
77 return 1;
77 return 1;
78 } else if (flags[i].name[1] != '-') {
78 } else if (flags[i].name[1] != '-') {
79 /* short flag */
79 /* 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, int argc,
90 static void setcmdserverargs(struct cmdserveropts *opts, int argc,
91 const char *argv[])
91 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 =
103 opts->args =
104 reallocx(opts->args, (n + opts->argsize) * sizeof(char *));
104 reallocx(opts->args, (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 abortmsgerrno("cannot create sockdir %s", sockdir);
117 abortmsgerrno("cannot create sockdir %s", sockdir);
118
118
119 struct stat st;
119 struct stat st;
120 r = lstat(sockdir, &st);
120 r = lstat(sockdir, &st);
121 if (r < 0)
121 if (r < 0)
122 abortmsgerrno("cannot stat %s", sockdir);
122 abortmsgerrno("cannot stat %s", sockdir);
123 if (!S_ISDIR(st.st_mode))
123 if (!S_ISDIR(st.st_mode))
124 abortmsg("cannot create sockdir %s (file exists)", sockdir);
124 abortmsg("cannot create sockdir %s (file exists)", sockdir);
125 if (st.st_uid != geteuid() || st.st_mode & 0077)
125 if (st.st_uid != geteuid() || st.st_mode & 0077)
126 abortmsg("insecure sockdir %s", sockdir);
126 abortmsg("insecure sockdir %s", sockdir);
127 }
127 }
128
128
129 /*
129 /*
130 * Check if a socket directory exists and is only owned by the current user.
130 * Check if a socket directory exists and is only owned by the current user.
131 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
131 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
132 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
132 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
133 * ignored if the directory is not owned by the user with mode 0700.
133 * ignored if the directory is not owned by the user with mode 0700.
134 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
134 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
135 */
135 */
136 static int checkruntimedir(const char *sockdir)
136 static int checkruntimedir(const char *sockdir)
137 {
137 {
138 struct stat st;
138 struct stat st;
139 int r = lstat(sockdir, &st);
139 int r = lstat(sockdir, &st);
140 if (r < 0) /* ex. does not exist */
140 if (r < 0) /* ex. does not exist */
141 return 0;
141 return 0;
142 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
142 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
143 return 0;
143 return 0;
144 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
144 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
145 }
145 }
146
146
147 static void getdefaultsockdir(char sockdir[], size_t size)
147 static void getdefaultsockdir(char sockdir[], size_t size)
148 {
148 {
149 /* by default, put socket file in secure directory
149 /* by default, put socket file in secure directory
150 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
150 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
151 * (permission of socket file may be ignored on some Unices) */
151 * (permission of socket file may be ignored on some Unices) */
152 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
152 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
153 int r;
153 int r;
154 if (runtimedir && checkruntimedir(runtimedir)) {
154 if (runtimedir && checkruntimedir(runtimedir)) {
155 r = snprintf(sockdir, size, "%s/chg", runtimedir);
155 r = snprintf(sockdir, size, "%s/chg", runtimedir);
156 } else {
156 } else {
157 const char *tmpdir = getenv("TMPDIR");
157 const char *tmpdir = getenv("TMPDIR");
158 if (!tmpdir)
158 if (!tmpdir)
159 tmpdir = "/tmp";
159 tmpdir = "/tmp";
160 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
160 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
161 }
161 }
162 if (r < 0 || (size_t)r >= size)
162 if (r < 0 || (size_t)r >= size)
163 abortmsg("too long TMPDIR (r = %d)", r);
163 abortmsg("too long TMPDIR (r = %d)", r);
164 }
164 }
165
165
166 static void setcmdserveropts(struct cmdserveropts *opts)
166 static void setcmdserveropts(struct cmdserveropts *opts)
167 {
167 {
168 int r;
168 int r;
169 char sockdir[PATH_MAX];
169 char sockdir[PATH_MAX];
170 const char *envsockname = getenv("CHGSOCKNAME");
170 const char *envsockname = getenv("CHGSOCKNAME");
171 if (!envsockname) {
171 if (!envsockname) {
172 getdefaultsockdir(sockdir, sizeof(sockdir));
172 getdefaultsockdir(sockdir, sizeof(sockdir));
173 preparesockdir(sockdir);
173 preparesockdir(sockdir);
174 }
174 }
175
175
176 const char *basename = (envsockname) ? envsockname : sockdir;
176 const char *basename = (envsockname) ? envsockname : sockdir;
177 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
177 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
178 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
178 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
179 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
179 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
180 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
180 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
181 r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u",
181 r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u",
182 opts->sockname, (unsigned)getpid());
182 opts->sockname, (unsigned)getpid());
183 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
183 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
184 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
184 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
185 }
185 }
186
186
187 static const char *gethgcmd(void)
187 static const char *gethgcmd(void)
188 {
188 {
189 static const char *hgcmd = NULL;
189 static const char *hgcmd = NULL;
190 if (!hgcmd) {
190 if (!hgcmd) {
191 hgcmd = getenv("CHGHG");
191 hgcmd = getenv("CHGHG");
192 if (!hgcmd || hgcmd[0] == '\0')
192 if (!hgcmd || hgcmd[0] == '\0')
193 hgcmd = getenv("HG");
193 hgcmd = getenv("HG");
194 if (!hgcmd || hgcmd[0] == '\0')
194 if (!hgcmd || hgcmd[0] == '\0')
195 #ifdef HGPATH
195 #ifdef HGPATH
196 hgcmd = (HGPATH);
196 hgcmd = (HGPATH);
197 #else
197 #else
198 hgcmd = "hg";
198 hgcmd = "hg";
199 #endif
199 #endif
200 }
200 }
201 return hgcmd;
201 return hgcmd;
202 }
202 }
203
203
204 static void execcmdserver(const struct cmdserveropts *opts)
204 static void execcmdserver(const struct cmdserveropts *opts)
205 {
205 {
206 const char *hgcmd = gethgcmd();
206 const char *hgcmd = gethgcmd();
207
207
208 const char *baseargv[] = {
208 const char *baseargv[] = {
209 hgcmd,
209 hgcmd,
210 "serve",
210 "serve",
211 "--cmdserver",
211 "--cmdserver",
212 "chgunix",
212 "chgunix",
213 "--address",
213 "--address",
214 opts->initsockname,
214 opts->initsockname,
215 "--daemon-postexec",
215 "--daemon-postexec",
216 "chdir:/",
216 "chdir:/",
217 };
217 };
218 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
218 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
219 size_t argsize = baseargvsize + opts->argsize + 1;
219 size_t argsize = baseargvsize + opts->argsize + 1;
220
220
221 const char **argv = mallocx(sizeof(char *) * argsize);
221 const char **argv = mallocx(sizeof(char *) * argsize);
222 memcpy(argv, baseargv, sizeof(baseargv));
222 memcpy(argv, baseargv, sizeof(baseargv));
223 if (opts->args) {
223 if (opts->args) {
224 size_t size = sizeof(char *) * opts->argsize;
224 size_t size = sizeof(char *) * opts->argsize;
225 memcpy(argv + baseargvsize, opts->args, size);
225 memcpy(argv + baseargvsize, opts->args, size);
226 }
226 }
227 argv[argsize - 1] = NULL;
227 argv[argsize - 1] = NULL;
228
228
229 const char *lc_ctype_env = getenv("LC_CTYPE");
230 if (lc_ctype_env == NULL) {
231 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
232 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
233 } else {
234 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
235 abortmsgerrno("failed to setenv CHGORIG_LC_CTYYPE");
236 }
237 }
238
229 if (putenv("CHGINTERNALMARK=") != 0)
239 if (putenv("CHGINTERNALMARK=") != 0)
230 abortmsgerrno("failed to putenv");
240 abortmsgerrno("failed to putenv");
231 if (execvp(hgcmd, (char **)argv) < 0)
241 if (execvp(hgcmd, (char **)argv) < 0)
232 abortmsgerrno("failed to exec cmdserver");
242 abortmsgerrno("failed to exec cmdserver");
233 free(argv);
243 free(argv);
234 }
244 }
235
245
236 /* Retry until we can connect to the server. Give up after some time. */
246 /* Retry until we can connect to the server. Give up after some time. */
237 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
247 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
238 {
248 {
239 static const struct timespec sleepreq = {0, 10 * 1000000};
249 static const struct timespec sleepreq = {0, 10 * 1000000};
240 int pst = 0;
250 int pst = 0;
241
251
242 debugmsg("try connect to %s repeatedly", opts->initsockname);
252 debugmsg("try connect to %s repeatedly", opts->initsockname);
243
253
244 unsigned int timeoutsec = 60; /* default: 60 seconds */
254 unsigned int timeoutsec = 60; /* default: 60 seconds */
245 const char *timeoutenv = getenv("CHGTIMEOUT");
255 const char *timeoutenv = getenv("CHGTIMEOUT");
246 if (timeoutenv)
256 if (timeoutenv)
247 sscanf(timeoutenv, "%u", &timeoutsec);
257 sscanf(timeoutenv, "%u", &timeoutsec);
248
258
249 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
259 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
250 hgclient_t *hgc = hgc_open(opts->initsockname);
260 hgclient_t *hgc = hgc_open(opts->initsockname);
251 if (hgc) {
261 if (hgc) {
252 debugmsg("rename %s to %s", opts->initsockname,
262 debugmsg("rename %s to %s", opts->initsockname,
253 opts->sockname);
263 opts->sockname);
254 int r = rename(opts->initsockname, opts->sockname);
264 int r = rename(opts->initsockname, opts->sockname);
255 if (r != 0)
265 if (r != 0)
256 abortmsgerrno("cannot rename");
266 abortmsgerrno("cannot rename");
257 return hgc;
267 return hgc;
258 }
268 }
259
269
260 if (pid > 0) {
270 if (pid > 0) {
261 /* collect zombie if child process fails to start */
271 /* collect zombie if child process fails to start */
262 int r = waitpid(pid, &pst, WNOHANG);
272 int r = waitpid(pid, &pst, WNOHANG);
263 if (r != 0)
273 if (r != 0)
264 goto cleanup;
274 goto cleanup;
265 }
275 }
266
276
267 nanosleep(&sleepreq, NULL);
277 nanosleep(&sleepreq, NULL);
268 }
278 }
269
279
270 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
280 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
271 return NULL;
281 return NULL;
272
282
273 cleanup:
283 cleanup:
274 if (WIFEXITED(pst)) {
284 if (WIFEXITED(pst)) {
275 if (WEXITSTATUS(pst) == 0)
285 if (WEXITSTATUS(pst) == 0)
276 abortmsg("could not connect to cmdserver "
286 abortmsg("could not connect to cmdserver "
277 "(exited with status 0)");
287 "(exited with status 0)");
278 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
288 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
279 exit(WEXITSTATUS(pst));
289 exit(WEXITSTATUS(pst));
280 } else if (WIFSIGNALED(pst)) {
290 } else if (WIFSIGNALED(pst)) {
281 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
291 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
282 } else {
292 } else {
283 abortmsg("error while waiting for cmdserver");
293 abortmsg("error while waiting for cmdserver");
284 }
294 }
285 return NULL;
295 return NULL;
286 }
296 }
287
297
288 /* Connect to a cmdserver. Will start a new server on demand. */
298 /* Connect to a cmdserver. Will start a new server on demand. */
289 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
299 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
290 {
300 {
291 const char *sockname =
301 const char *sockname =
292 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
302 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
293 debugmsg("try connect to %s", sockname);
303 debugmsg("try connect to %s", sockname);
294 hgclient_t *hgc = hgc_open(sockname);
304 hgclient_t *hgc = hgc_open(sockname);
295 if (hgc)
305 if (hgc)
296 return hgc;
306 return hgc;
297
307
298 /* prevent us from being connected to an outdated server: we were
308 /* prevent us from being connected to an outdated server: we were
299 * told by a server to redirect to opts->redirectsockname and that
309 * told by a server to redirect to opts->redirectsockname and that
300 * address does not work. we do not want to connect to the server
310 * address does not work. we do not want to connect to the server
301 * again because it will probably tell us the same thing. */
311 * again because it will probably tell us the same thing. */
302 if (sockname == opts->redirectsockname)
312 if (sockname == opts->redirectsockname)
303 unlink(opts->sockname);
313 unlink(opts->sockname);
304
314
305 debugmsg("start cmdserver at %s", opts->initsockname);
315 debugmsg("start cmdserver at %s", opts->initsockname);
306
316
307 pid_t pid = fork();
317 pid_t pid = fork();
308 if (pid < 0)
318 if (pid < 0)
309 abortmsg("failed to fork cmdserver process");
319 abortmsg("failed to fork cmdserver process");
310 if (pid == 0) {
320 if (pid == 0) {
311 execcmdserver(opts);
321 execcmdserver(opts);
312 } else {
322 } else {
313 hgc = retryconnectcmdserver(opts, pid);
323 hgc = retryconnectcmdserver(opts, pid);
314 }
324 }
315
325
316 return hgc;
326 return hgc;
317 }
327 }
318
328
319 static void killcmdserver(const struct cmdserveropts *opts)
329 static void killcmdserver(const struct cmdserveropts *opts)
320 {
330 {
321 /* resolve config hash */
331 /* resolve config hash */
322 char *resolvedpath = realpath(opts->sockname, NULL);
332 char *resolvedpath = realpath(opts->sockname, NULL);
323 if (resolvedpath) {
333 if (resolvedpath) {
324 unlink(resolvedpath);
334 unlink(resolvedpath);
325 free(resolvedpath);
335 free(resolvedpath);
326 }
336 }
327 }
337 }
328
338
329 /* Run instructions sent from the server like unlink and set redirect path
339 /* Run instructions sent from the server like unlink and set redirect path
330 * Return 1 if reconnect is needed, otherwise 0 */
340 * Return 1 if reconnect is needed, otherwise 0 */
331 static int runinstructions(struct cmdserveropts *opts, const char **insts)
341 static int runinstructions(struct cmdserveropts *opts, const char **insts)
332 {
342 {
333 int needreconnect = 0;
343 int needreconnect = 0;
334 if (!insts)
344 if (!insts)
335 return needreconnect;
345 return needreconnect;
336
346
337 assert(insts);
347 assert(insts);
338 opts->redirectsockname[0] = '\0';
348 opts->redirectsockname[0] = '\0';
339 const char **pinst;
349 const char **pinst;
340 for (pinst = insts; *pinst; pinst++) {
350 for (pinst = insts; *pinst; pinst++) {
341 debugmsg("instruction: %s", *pinst);
351 debugmsg("instruction: %s", *pinst);
342 if (strncmp(*pinst, "unlink ", 7) == 0) {
352 if (strncmp(*pinst, "unlink ", 7) == 0) {
343 unlink(*pinst + 7);
353 unlink(*pinst + 7);
344 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
354 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
345 int r = snprintf(opts->redirectsockname,
355 int r = snprintf(opts->redirectsockname,
346 sizeof(opts->redirectsockname), "%s",
356 sizeof(opts->redirectsockname), "%s",
347 *pinst + 9);
357 *pinst + 9);
348 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
358 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
349 abortmsg("redirect path is too long (%d)", r);
359 abortmsg("redirect path is too long (%d)", r);
350 needreconnect = 1;
360 needreconnect = 1;
351 } else if (strncmp(*pinst, "exit ", 5) == 0) {
361 } else if (strncmp(*pinst, "exit ", 5) == 0) {
352 int n = 0;
362 int n = 0;
353 if (sscanf(*pinst + 5, "%d", &n) != 1)
363 if (sscanf(*pinst + 5, "%d", &n) != 1)
354 abortmsg("cannot read the exit code");
364 abortmsg("cannot read the exit code");
355 exit(n);
365 exit(n);
356 } else if (strcmp(*pinst, "reconnect") == 0) {
366 } else if (strcmp(*pinst, "reconnect") == 0) {
357 needreconnect = 1;
367 needreconnect = 1;
358 } else {
368 } else {
359 abortmsg("unknown instruction: %s", *pinst);
369 abortmsg("unknown instruction: %s", *pinst);
360 }
370 }
361 }
371 }
362 return needreconnect;
372 return needreconnect;
363 }
373 }
364
374
365 /*
375 /*
366 * Test whether the command is unsupported or not. This is not designed to
376 * Test whether the command is unsupported or not. This is not designed to
367 * cover all cases. But it's fast, does not depend on the server and does
377 * cover all cases. But it's fast, does not depend on the server and does
368 * not return false positives.
378 * not return false positives.
369 */
379 */
370 static int isunsupported(int argc, const char *argv[])
380 static int isunsupported(int argc, const char *argv[])
371 {
381 {
372 enum { SERVE = 1,
382 enum { SERVE = 1,
373 DAEMON = 2,
383 DAEMON = 2,
374 SERVEDAEMON = SERVE | DAEMON,
384 SERVEDAEMON = SERVE | DAEMON,
375 };
385 };
376 unsigned int state = 0;
386 unsigned int state = 0;
377 int i;
387 int i;
378 for (i = 0; i < argc; ++i) {
388 for (i = 0; i < argc; ++i) {
379 if (strcmp(argv[i], "--") == 0)
389 if (strcmp(argv[i], "--") == 0)
380 break;
390 break;
381 if (i == 0 && strcmp("serve", argv[i]) == 0)
391 if (i == 0 && strcmp("serve", argv[i]) == 0)
382 state |= SERVE;
392 state |= SERVE;
383 else if (strcmp("-d", argv[i]) == 0 ||
393 else if (strcmp("-d", argv[i]) == 0 ||
384 strcmp("--daemon", argv[i]) == 0)
394 strcmp("--daemon", argv[i]) == 0)
385 state |= DAEMON;
395 state |= DAEMON;
386 }
396 }
387 return (state & SERVEDAEMON) == SERVEDAEMON;
397 return (state & SERVEDAEMON) == SERVEDAEMON;
388 }
398 }
389
399
390 static void execoriginalhg(const char *argv[])
400 static void execoriginalhg(const char *argv[])
391 {
401 {
392 debugmsg("execute original hg");
402 debugmsg("execute original hg");
393 if (execvp(gethgcmd(), (char **)argv) < 0)
403 if (execvp(gethgcmd(), (char **)argv) < 0)
394 abortmsgerrno("failed to exec original hg");
404 abortmsgerrno("failed to exec original hg");
395 }
405 }
396
406
397 int main(int argc, const char *argv[], const char *envp[])
407 int main(int argc, const char *argv[], const char *envp[])
398 {
408 {
399 if (getenv("CHGDEBUG"))
409 if (getenv("CHGDEBUG"))
400 enabledebugmsg();
410 enabledebugmsg();
401
411
402 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
412 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
403 enablecolor();
413 enablecolor();
404
414
405 if (getenv("CHGINTERNALMARK"))
415 if (getenv("CHGINTERNALMARK"))
406 abortmsg("chg started by chg detected.\n"
416 abortmsg("chg started by chg detected.\n"
407 "Please make sure ${HG:-hg} is not a symlink or "
417 "Please make sure ${HG:-hg} is not a symlink or "
408 "wrapper to chg. Alternatively, set $CHGHG to the "
418 "wrapper to chg. Alternatively, set $CHGHG to the "
409 "path of real hg.");
419 "path of real hg.");
410
420
411 if (isunsupported(argc - 1, argv + 1))
421 if (isunsupported(argc - 1, argv + 1))
412 execoriginalhg(argv);
422 execoriginalhg(argv);
413
423
414 struct cmdserveropts opts;
424 struct cmdserveropts opts;
415 initcmdserveropts(&opts);
425 initcmdserveropts(&opts);
416 setcmdserveropts(&opts);
426 setcmdserveropts(&opts);
417 setcmdserverargs(&opts, argc, argv);
427 setcmdserverargs(&opts, argc, argv);
418
428
419 if (argc == 2) {
429 if (argc == 2) {
420 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
430 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
421 killcmdserver(&opts);
431 killcmdserver(&opts);
422 return 0;
432 return 0;
423 }
433 }
424 }
434 }
425
435
426 hgclient_t *hgc;
436 hgclient_t *hgc;
427 size_t retry = 0;
437 size_t retry = 0;
428 while (1) {
438 while (1) {
429 hgc = connectcmdserver(&opts);
439 hgc = connectcmdserver(&opts);
430 if (!hgc)
440 if (!hgc)
431 abortmsg("cannot open hg client");
441 abortmsg("cannot open hg client");
432 hgc_setenv(hgc, envp);
442 hgc_setenv(hgc, envp);
433 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
443 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
434 int needreconnect = runinstructions(&opts, insts);
444 int needreconnect = runinstructions(&opts, insts);
435 free(insts);
445 free(insts);
436 if (!needreconnect)
446 if (!needreconnect)
437 break;
447 break;
438 hgc_close(hgc);
448 hgc_close(hgc);
439 if (++retry > 10)
449 if (++retry > 10)
440 abortmsg("too many redirections.\n"
450 abortmsg("too many redirections.\n"
441 "Please make sure %s is not a wrapper which "
451 "Please make sure %s is not a wrapper which "
442 "changes sensitive environment variables "
452 "changes sensitive environment variables "
443 "before executing hg. If you have to use a "
453 "before executing hg. If you have to use a "
444 "wrapper, wrap chg instead of hg.",
454 "wrapper, wrap chg instead of hg.",
445 gethgcmd());
455 gethgcmd());
446 }
456 }
447
457
448 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
458 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
449 atexit(waitpager);
459 atexit(waitpager);
450 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
460 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
451 restoresignalhandler();
461 restoresignalhandler();
452 hgc_close(hgc);
462 hgc_close(hgc);
453 freecmdserveropts(&opts);
463 freecmdserveropts(&opts);
454
464
455 return exitcode;
465 return exitcode;
456 }
466 }
@@ -1,738 +1,714 b''
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """command server extension for cHg
8 """command server extension for cHg
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'setenv' command
19 'setenv' command
20 replace os.environ completely
20 replace os.environ completely
21
21
22 'setumask' command (DEPRECATED)
22 'setumask' command (DEPRECATED)
23 'setumask2' command
23 'setumask2' command
24 set umask
24 set umask
25
25
26 'validate' command
26 'validate' command
27 reload the config and check if the server is up to date
27 reload the config and check if the server is up to date
28
28
29 Config
29 Config
30 ------
30 ------
31
31
32 ::
32 ::
33
33
34 [chgserver]
34 [chgserver]
35 # how long (in seconds) should an idle chg server exit
35 # how long (in seconds) should an idle chg server exit
36 idletimeout = 3600
36 idletimeout = 3600
37
37
38 # whether to skip config or env change checks
38 # whether to skip config or env change checks
39 skiphash = False
39 skiphash = False
40 """
40 """
41
41
42 from __future__ import absolute_import
42 from __future__ import absolute_import
43
43
44 import inspect
44 import inspect
45 import os
45 import os
46 import re
46 import re
47 import socket
47 import socket
48 import stat
48 import stat
49 import struct
49 import struct
50 import time
50 import time
51
51
52 from .i18n import _
52 from .i18n import _
53 from .pycompat import (
53 from .pycompat import (
54 getattr,
54 getattr,
55 setattr,
55 setattr,
56 )
56 )
57
57
58 from . import (
58 from . import (
59 commandserver,
59 commandserver,
60 encoding,
60 encoding,
61 error,
61 error,
62 extensions,
62 extensions,
63 node,
63 node,
64 pycompat,
64 pycompat,
65 util,
65 util,
66 )
66 )
67
67
68 from .utils import (
68 from .utils import (
69 hashutil,
69 hashutil,
70 procutil,
70 procutil,
71 stringutil,
71 stringutil,
72 )
72 )
73
73
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 node.hex(hashutil.sha1(stringutil.pprint(items)).digest())
77 return node.hex(hashutil.sha1(stringutil.pprint(items)).digest())
78
78
79
79
80 # sensitive config sections affecting confighash
80 # sensitive config sections affecting confighash
81 _configsections = [
81 _configsections = [
82 b'alias', # affects global state commands.table
82 b'alias', # affects global state commands.table
83 b'eol', # uses setconfig('eol', ...)
83 b'eol', # uses setconfig('eol', ...)
84 b'extdiff', # uisetup will register new commands
84 b'extdiff', # uisetup will register new commands
85 b'extensions',
85 b'extensions',
86 ]
86 ]
87
87
88 _configsectionitems = [
88 _configsectionitems = [
89 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
89 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
90 ]
90 ]
91
91
92 # sensitive environment variables affecting confighash
92 # sensitive environment variables affecting confighash
93 _envre = re.compile(
93 _envre = re.compile(
94 br'''\A(?:
94 br'''\A(?:
95 CHGHG
95 CHGHG
96 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
96 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
97 |HG(?:ENCODING|PLAIN).*
97 |HG(?:ENCODING|PLAIN).*
98 |LANG(?:UAGE)?
98 |LANG(?:UAGE)?
99 |LC_.*
99 |LC_.*
100 |LD_.*
100 |LD_.*
101 |PATH
101 |PATH
102 |PYTHON.*
102 |PYTHON.*
103 |TERM(?:INFO)?
103 |TERM(?:INFO)?
104 |TZ
104 |TZ
105 )\Z''',
105 )\Z''',
106 re.X,
106 re.X,
107 )
107 )
108
108
109
109
110 def _confighash(ui):
110 def _confighash(ui):
111 """return a quick hash for detecting config/env changes
111 """return a quick hash for detecting config/env changes
112
112
113 confighash is the hash of sensitive config items and environment variables.
113 confighash is the hash of sensitive config items and environment variables.
114
114
115 for chgserver, it is designed that once confighash changes, the server is
115 for chgserver, it is designed that once confighash changes, the server is
116 not qualified to serve its client and should redirect the client to a new
116 not qualified to serve its client and should redirect the client to a new
117 server. different from mtimehash, confighash change will not mark the
117 server. different from mtimehash, confighash change will not mark the
118 server outdated and exit since the user can have different configs at the
118 server outdated and exit since the user can have different configs at the
119 same time.
119 same time.
120 """
120 """
121 sectionitems = []
121 sectionitems = []
122 for section in _configsections:
122 for section in _configsections:
123 sectionitems.append(ui.configitems(section))
123 sectionitems.append(ui.configitems(section))
124 for section, item in _configsectionitems:
124 for section, item in _configsectionitems:
125 sectionitems.append(ui.config(section, item))
125 sectionitems.append(ui.config(section, item))
126 sectionhash = _hashlist(sectionitems)
126 sectionhash = _hashlist(sectionitems)
127 # If $CHGHG is set, the change to $HG should not trigger a new chg server
127 # If $CHGHG is set, the change to $HG should not trigger a new chg server
128 if b'CHGHG' in encoding.environ:
128 if b'CHGHG' in encoding.environ:
129 ignored = {b'HG'}
129 ignored = {b'HG'}
130 else:
130 else:
131 ignored = set()
131 ignored = set()
132 envitems = [
132 envitems = [
133 (k, v)
133 (k, v)
134 for k, v in pycompat.iteritems(encoding.environ)
134 for k, v in pycompat.iteritems(encoding.environ)
135 if _envre.match(k) and k not in ignored
135 if _envre.match(k) and k not in ignored
136 ]
136 ]
137 envhash = _hashlist(sorted(envitems))
137 envhash = _hashlist(sorted(envitems))
138 return sectionhash[:6] + envhash[:6]
138 return sectionhash[:6] + envhash[:6]
139
139
140
140
141 def _getmtimepaths(ui):
141 def _getmtimepaths(ui):
142 """get a list of paths that should be checked to detect change
142 """get a list of paths that should be checked to detect change
143
143
144 The list will include:
144 The list will include:
145 - extensions (will not cover all files for complex extensions)
145 - extensions (will not cover all files for complex extensions)
146 - mercurial/__version__.py
146 - mercurial/__version__.py
147 - python binary
147 - python binary
148 """
148 """
149 modules = [m for n, m in extensions.extensions(ui)]
149 modules = [m for n, m in extensions.extensions(ui)]
150 try:
150 try:
151 from . import __version__
151 from . import __version__
152
152
153 modules.append(__version__)
153 modules.append(__version__)
154 except ImportError:
154 except ImportError:
155 pass
155 pass
156 files = []
156 files = []
157 if pycompat.sysexecutable:
157 if pycompat.sysexecutable:
158 files.append(pycompat.sysexecutable)
158 files.append(pycompat.sysexecutable)
159 for m in modules:
159 for m in modules:
160 try:
160 try:
161 files.append(pycompat.fsencode(inspect.getabsfile(m)))
161 files.append(pycompat.fsencode(inspect.getabsfile(m)))
162 except TypeError:
162 except TypeError:
163 pass
163 pass
164 return sorted(set(files))
164 return sorted(set(files))
165
165
166
166
167 def _mtimehash(paths):
167 def _mtimehash(paths):
168 """return a quick hash for detecting file changes
168 """return a quick hash for detecting file changes
169
169
170 mtimehash calls stat on given paths and calculate a hash based on size and
170 mtimehash calls stat on given paths and calculate a hash based on size and
171 mtime of each file. mtimehash does not read file content because reading is
171 mtime of each file. mtimehash does not read file content because reading is
172 expensive. therefore it's not 100% reliable for detecting content changes.
172 expensive. therefore it's not 100% reliable for detecting content changes.
173 it's possible to return different hashes for same file contents.
173 it's possible to return different hashes for same file contents.
174 it's also possible to return a same hash for different file contents for
174 it's also possible to return a same hash for different file contents for
175 some carefully crafted situation.
175 some carefully crafted situation.
176
176
177 for chgserver, it is designed that once mtimehash changes, the server is
177 for chgserver, it is designed that once mtimehash changes, the server is
178 considered outdated immediately and should no longer provide service.
178 considered outdated immediately and should no longer provide service.
179
179
180 mtimehash is not included in confighash because we only know the paths of
180 mtimehash is not included in confighash because we only know the paths of
181 extensions after importing them (there is imp.find_module but that faces
181 extensions after importing them (there is imp.find_module but that faces
182 race conditions). We need to calculate confighash without importing.
182 race conditions). We need to calculate confighash without importing.
183 """
183 """
184
184
185 def trystat(path):
185 def trystat(path):
186 try:
186 try:
187 st = os.stat(path)
187 st = os.stat(path)
188 return (st[stat.ST_MTIME], st.st_size)
188 return (st[stat.ST_MTIME], st.st_size)
189 except OSError:
189 except OSError:
190 # could be ENOENT, EPERM etc. not fatal in any case
190 # could be ENOENT, EPERM etc. not fatal in any case
191 pass
191 pass
192
192
193 return _hashlist(pycompat.maplist(trystat, paths))[:12]
193 return _hashlist(pycompat.maplist(trystat, paths))[:12]
194
194
195
195
196 class hashstate(object):
196 class hashstate(object):
197 """a structure storing confighash, mtimehash, paths used for mtimehash"""
197 """a structure storing confighash, mtimehash, paths used for mtimehash"""
198
198
199 def __init__(self, confighash, mtimehash, mtimepaths):
199 def __init__(self, confighash, mtimehash, mtimepaths):
200 self.confighash = confighash
200 self.confighash = confighash
201 self.mtimehash = mtimehash
201 self.mtimehash = mtimehash
202 self.mtimepaths = mtimepaths
202 self.mtimepaths = mtimepaths
203
203
204 @staticmethod
204 @staticmethod
205 def fromui(ui, mtimepaths=None):
205 def fromui(ui, mtimepaths=None):
206 if mtimepaths is None:
206 if mtimepaths is None:
207 mtimepaths = _getmtimepaths(ui)
207 mtimepaths = _getmtimepaths(ui)
208 confighash = _confighash(ui)
208 confighash = _confighash(ui)
209 mtimehash = _mtimehash(mtimepaths)
209 mtimehash = _mtimehash(mtimepaths)
210 ui.log(
210 ui.log(
211 b'cmdserver',
211 b'cmdserver',
212 b'confighash = %s mtimehash = %s\n',
212 b'confighash = %s mtimehash = %s\n',
213 confighash,
213 confighash,
214 mtimehash,
214 mtimehash,
215 )
215 )
216 return hashstate(confighash, mtimehash, mtimepaths)
216 return hashstate(confighash, mtimehash, mtimepaths)
217
217
218
218
219 def _newchgui(srcui, csystem, attachio):
219 def _newchgui(srcui, csystem, attachio):
220 class chgui(srcui.__class__):
220 class chgui(srcui.__class__):
221 def __init__(self, src=None):
221 def __init__(self, src=None):
222 super(chgui, self).__init__(src)
222 super(chgui, self).__init__(src)
223 if src:
223 if src:
224 self._csystem = getattr(src, '_csystem', csystem)
224 self._csystem = getattr(src, '_csystem', csystem)
225 else:
225 else:
226 self._csystem = csystem
226 self._csystem = csystem
227
227
228 def _runsystem(self, cmd, environ, cwd, out):
228 def _runsystem(self, cmd, environ, cwd, out):
229 # fallback to the original system method if
229 # fallback to the original system method if
230 # a. the output stream is not stdout (e.g. stderr, cStringIO),
230 # a. the output stream is not stdout (e.g. stderr, cStringIO),
231 # b. or stdout is redirected by protectfinout(),
231 # b. or stdout is redirected by protectfinout(),
232 # because the chg client is not aware of these situations and
232 # because the chg client is not aware of these situations and
233 # will behave differently (i.e. write to stdout).
233 # will behave differently (i.e. write to stdout).
234 if (
234 if (
235 out is not self.fout
235 out is not self.fout
236 or not util.safehasattr(self.fout, b'fileno')
236 or not util.safehasattr(self.fout, b'fileno')
237 or self.fout.fileno() != procutil.stdout.fileno()
237 or self.fout.fileno() != procutil.stdout.fileno()
238 or self._finoutredirected
238 or self._finoutredirected
239 ):
239 ):
240 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
240 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
241 self.flush()
241 self.flush()
242 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
242 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
243
243
244 def _runpager(self, cmd, env=None):
244 def _runpager(self, cmd, env=None):
245 self._csystem(
245 self._csystem(
246 cmd,
246 cmd,
247 procutil.shellenviron(env),
247 procutil.shellenviron(env),
248 type=b'pager',
248 type=b'pager',
249 cmdtable={b'attachio': attachio},
249 cmdtable={b'attachio': attachio},
250 )
250 )
251 return True
251 return True
252
252
253 return chgui(srcui)
253 return chgui(srcui)
254
254
255
255
256 def _loadnewui(srcui, args, cdebug):
256 def _loadnewui(srcui, args, cdebug):
257 from . import dispatch # avoid cycle
257 from . import dispatch # avoid cycle
258
258
259 newui = srcui.__class__.load()
259 newui = srcui.__class__.load()
260 for a in [b'fin', b'fout', b'ferr', b'environ']:
260 for a in [b'fin', b'fout', b'ferr', b'environ']:
261 setattr(newui, a, getattr(srcui, a))
261 setattr(newui, a, getattr(srcui, a))
262 if util.safehasattr(srcui, b'_csystem'):
262 if util.safehasattr(srcui, b'_csystem'):
263 newui._csystem = srcui._csystem
263 newui._csystem = srcui._csystem
264
264
265 # command line args
265 # command line args
266 options = dispatch._earlyparseopts(newui, args)
266 options = dispatch._earlyparseopts(newui, args)
267 dispatch._parseconfig(newui, options[b'config'])
267 dispatch._parseconfig(newui, options[b'config'])
268
268
269 # stolen from tortoisehg.util.copydynamicconfig()
269 # stolen from tortoisehg.util.copydynamicconfig()
270 for section, name, value in srcui.walkconfig():
270 for section, name, value in srcui.walkconfig():
271 source = srcui.configsource(section, name)
271 source = srcui.configsource(section, name)
272 if b':' in source or source == b'--config' or source.startswith(b'$'):
272 if b':' in source or source == b'--config' or source.startswith(b'$'):
273 # path:line or command line, or environ
273 # path:line or command line, or environ
274 continue
274 continue
275 newui.setconfig(section, name, value, source)
275 newui.setconfig(section, name, value, source)
276
276
277 # load wd and repo config, copied from dispatch.py
277 # load wd and repo config, copied from dispatch.py
278 cwd = options[b'cwd']
278 cwd = options[b'cwd']
279 cwd = cwd and os.path.realpath(cwd) or None
279 cwd = cwd and os.path.realpath(cwd) or None
280 rpath = options[b'repository']
280 rpath = options[b'repository']
281 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
281 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
282
282
283 extensions.populateui(newui)
283 extensions.populateui(newui)
284 commandserver.setuplogging(newui, fp=cdebug)
284 commandserver.setuplogging(newui, fp=cdebug)
285 if newui is not newlui:
285 if newui is not newlui:
286 extensions.populateui(newlui)
286 extensions.populateui(newlui)
287 commandserver.setuplogging(newlui, fp=cdebug)
287 commandserver.setuplogging(newlui, fp=cdebug)
288
288
289 return (newui, newlui)
289 return (newui, newlui)
290
290
291
291
292 class channeledsystem(object):
292 class channeledsystem(object):
293 """Propagate ui.system() request in the following format:
293 """Propagate ui.system() request in the following format:
294
294
295 payload length (unsigned int),
295 payload length (unsigned int),
296 type, '\0',
296 type, '\0',
297 cmd, '\0',
297 cmd, '\0',
298 cwd, '\0',
298 cwd, '\0',
299 envkey, '=', val, '\0',
299 envkey, '=', val, '\0',
300 ...
300 ...
301 envkey, '=', val
301 envkey, '=', val
302
302
303 if type == 'system', waits for:
303 if type == 'system', waits for:
304
304
305 exitcode length (unsigned int),
305 exitcode length (unsigned int),
306 exitcode (int)
306 exitcode (int)
307
307
308 if type == 'pager', repetitively waits for a command name ending with '\n'
308 if type == 'pager', repetitively waits for a command name ending with '\n'
309 and executes it defined by cmdtable, or exits the loop if the command name
309 and executes it defined by cmdtable, or exits the loop if the command name
310 is empty.
310 is empty.
311 """
311 """
312
312
313 def __init__(self, in_, out, channel):
313 def __init__(self, in_, out, channel):
314 self.in_ = in_
314 self.in_ = in_
315 self.out = out
315 self.out = out
316 self.channel = channel
316 self.channel = channel
317
317
318 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
318 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
319 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')]
319 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')]
320 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
320 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
321 data = b'\0'.join(args)
321 data = b'\0'.join(args)
322 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
322 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
323 self.out.write(data)
323 self.out.write(data)
324 self.out.flush()
324 self.out.flush()
325
325
326 if type == b'system':
326 if type == b'system':
327 length = self.in_.read(4)
327 length = self.in_.read(4)
328 (length,) = struct.unpack(b'>I', length)
328 (length,) = struct.unpack(b'>I', length)
329 if length != 4:
329 if length != 4:
330 raise error.Abort(_(b'invalid response'))
330 raise error.Abort(_(b'invalid response'))
331 (rc,) = struct.unpack(b'>i', self.in_.read(4))
331 (rc,) = struct.unpack(b'>i', self.in_.read(4))
332 return rc
332 return rc
333 elif type == b'pager':
333 elif type == b'pager':
334 while True:
334 while True:
335 cmd = self.in_.readline()[:-1]
335 cmd = self.in_.readline()[:-1]
336 if not cmd:
336 if not cmd:
337 break
337 break
338 if cmdtable and cmd in cmdtable:
338 if cmdtable and cmd in cmdtable:
339 cmdtable[cmd]()
339 cmdtable[cmd]()
340 else:
340 else:
341 raise error.Abort(_(b'unexpected command: %s') % cmd)
341 raise error.Abort(_(b'unexpected command: %s') % cmd)
342 else:
342 else:
343 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
343 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
344
344
345
345
346 _iochannels = [
346 _iochannels = [
347 # server.ch, ui.fp, mode
347 # server.ch, ui.fp, mode
348 (b'cin', b'fin', 'rb'),
348 (b'cin', b'fin', 'rb'),
349 (b'cout', b'fout', 'wb'),
349 (b'cout', b'fout', 'wb'),
350 (b'cerr', b'ferr', 'wb'),
350 (b'cerr', b'ferr', 'wb'),
351 ]
351 ]
352
352
353
353
354 class chgcmdserver(commandserver.server):
354 class chgcmdserver(commandserver.server):
355 def __init__(
355 def __init__(
356 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
356 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
357 ):
357 ):
358 super(chgcmdserver, self).__init__(
358 super(chgcmdserver, self).__init__(
359 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
359 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
360 repo,
360 repo,
361 fin,
361 fin,
362 fout,
362 fout,
363 prereposetups,
363 prereposetups,
364 )
364 )
365 self.clientsock = sock
365 self.clientsock = sock
366 self._ioattached = False
366 self._ioattached = False
367 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
367 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
368 self.hashstate = hashstate
368 self.hashstate = hashstate
369 self.baseaddress = baseaddress
369 self.baseaddress = baseaddress
370 if hashstate is not None:
370 if hashstate is not None:
371 self.capabilities = self.capabilities.copy()
371 self.capabilities = self.capabilities.copy()
372 self.capabilities[b'validate'] = chgcmdserver.validate
372 self.capabilities[b'validate'] = chgcmdserver.validate
373
373
374 def cleanup(self):
374 def cleanup(self):
375 super(chgcmdserver, self).cleanup()
375 super(chgcmdserver, self).cleanup()
376 # dispatch._runcatch() does not flush outputs if exception is not
376 # dispatch._runcatch() does not flush outputs if exception is not
377 # handled by dispatch._dispatch()
377 # handled by dispatch._dispatch()
378 self.ui.flush()
378 self.ui.flush()
379 self._restoreio()
379 self._restoreio()
380 self._ioattached = False
380 self._ioattached = False
381
381
382 def attachio(self):
382 def attachio(self):
383 """Attach to client's stdio passed via unix domain socket; all
383 """Attach to client's stdio passed via unix domain socket; all
384 channels except cresult will no longer be used
384 channels except cresult will no longer be used
385 """
385 """
386 # tell client to sendmsg() with 1-byte payload, which makes it
386 # tell client to sendmsg() with 1-byte payload, which makes it
387 # distinctive from "attachio\n" command consumed by client.read()
387 # distinctive from "attachio\n" command consumed by client.read()
388 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
388 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
389 clientfds = util.recvfds(self.clientsock.fileno())
389 clientfds = util.recvfds(self.clientsock.fileno())
390 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
390 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
391
391
392 ui = self.ui
392 ui = self.ui
393 ui.flush()
393 ui.flush()
394 self._saveio()
394 self._saveio()
395 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
395 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
396 assert fd > 0
396 assert fd > 0
397 fp = getattr(ui, fn)
397 fp = getattr(ui, fn)
398 os.dup2(fd, fp.fileno())
398 os.dup2(fd, fp.fileno())
399 os.close(fd)
399 os.close(fd)
400 if self._ioattached:
400 if self._ioattached:
401 continue
401 continue
402 # reset buffering mode when client is first attached. as we want
402 # reset buffering mode when client is first attached. as we want
403 # to see output immediately on pager, the mode stays unchanged
403 # to see output immediately on pager, the mode stays unchanged
404 # when client re-attached. ferr is unchanged because it should
404 # when client re-attached. ferr is unchanged because it should
405 # be unbuffered no matter if it is a tty or not.
405 # be unbuffered no matter if it is a tty or not.
406 if fn == b'ferr':
406 if fn == b'ferr':
407 newfp = fp
407 newfp = fp
408 else:
408 else:
409 # make it line buffered explicitly because the default is
409 # make it line buffered explicitly because the default is
410 # decided on first write(), where fout could be a pager.
410 # decided on first write(), where fout could be a pager.
411 if fp.isatty():
411 if fp.isatty():
412 bufsize = 1 # line buffered
412 bufsize = 1 # line buffered
413 else:
413 else:
414 bufsize = -1 # system default
414 bufsize = -1 # system default
415 newfp = os.fdopen(fp.fileno(), mode, bufsize)
415 newfp = os.fdopen(fp.fileno(), mode, bufsize)
416 setattr(ui, fn, newfp)
416 setattr(ui, fn, newfp)
417 setattr(self, cn, newfp)
417 setattr(self, cn, newfp)
418
418
419 self._ioattached = True
419 self._ioattached = True
420 self.cresult.write(struct.pack(b'>i', len(clientfds)))
420 self.cresult.write(struct.pack(b'>i', len(clientfds)))
421
421
422 def _saveio(self):
422 def _saveio(self):
423 if self._oldios:
423 if self._oldios:
424 return
424 return
425 ui = self.ui
425 ui = self.ui
426 for cn, fn, _mode in _iochannels:
426 for cn, fn, _mode in _iochannels:
427 ch = getattr(self, cn)
427 ch = getattr(self, cn)
428 fp = getattr(ui, fn)
428 fp = getattr(ui, fn)
429 fd = os.dup(fp.fileno())
429 fd = os.dup(fp.fileno())
430 self._oldios.append((ch, fp, fd))
430 self._oldios.append((ch, fp, fd))
431
431
432 def _restoreio(self):
432 def _restoreio(self):
433 ui = self.ui
433 ui = self.ui
434 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
434 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
435 newfp = getattr(ui, fn)
435 newfp = getattr(ui, fn)
436 # close newfp while it's associated with client; otherwise it
436 # close newfp while it's associated with client; otherwise it
437 # would be closed when newfp is deleted
437 # would be closed when newfp is deleted
438 if newfp is not fp:
438 if newfp is not fp:
439 newfp.close()
439 newfp.close()
440 # restore original fd: fp is open again
440 # restore original fd: fp is open again
441 os.dup2(fd, fp.fileno())
441 os.dup2(fd, fp.fileno())
442 os.close(fd)
442 os.close(fd)
443 setattr(self, cn, ch)
443 setattr(self, cn, ch)
444 setattr(ui, fn, fp)
444 setattr(ui, fn, fp)
445 del self._oldios[:]
445 del self._oldios[:]
446
446
447 def validate(self):
447 def validate(self):
448 """Reload the config and check if the server is up to date
448 """Reload the config and check if the server is up to date
449
449
450 Read a list of '\0' separated arguments.
450 Read a list of '\0' separated arguments.
451 Write a non-empty list of '\0' separated instruction strings or '\0'
451 Write a non-empty list of '\0' separated instruction strings or '\0'
452 if the list is empty.
452 if the list is empty.
453 An instruction string could be either:
453 An instruction string could be either:
454 - "unlink $path", the client should unlink the path to stop the
454 - "unlink $path", the client should unlink the path to stop the
455 outdated server.
455 outdated server.
456 - "redirect $path", the client should attempt to connect to $path
456 - "redirect $path", the client should attempt to connect to $path
457 first. If it does not work, start a new server. It implies
457 first. If it does not work, start a new server. It implies
458 "reconnect".
458 "reconnect".
459 - "exit $n", the client should exit directly with code n.
459 - "exit $n", the client should exit directly with code n.
460 This may happen if we cannot parse the config.
460 This may happen if we cannot parse the config.
461 - "reconnect", the client should close the connection and
461 - "reconnect", the client should close the connection and
462 reconnect.
462 reconnect.
463 If neither "reconnect" nor "redirect" is included in the instruction
463 If neither "reconnect" nor "redirect" is included in the instruction
464 list, the client can continue with this server after completing all
464 list, the client can continue with this server after completing all
465 the instructions.
465 the instructions.
466 """
466 """
467 from . import dispatch # avoid cycle
467 from . import dispatch # avoid cycle
468
468
469 args = self._readlist()
469 args = self._readlist()
470 try:
470 try:
471 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
471 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
472 except error.ParseError as inst:
472 except error.ParseError as inst:
473 dispatch._formatparse(self.ui.warn, inst)
473 dispatch._formatparse(self.ui.warn, inst)
474 self.ui.flush()
474 self.ui.flush()
475 self.cresult.write(b'exit 255')
475 self.cresult.write(b'exit 255')
476 return
476 return
477 except error.Abort as inst:
477 except error.Abort as inst:
478 self.ui.error(_(b"abort: %s\n") % inst)
478 self.ui.error(_(b"abort: %s\n") % inst)
479 if inst.hint:
479 if inst.hint:
480 self.ui.error(_(b"(%s)\n") % inst.hint)
480 self.ui.error(_(b"(%s)\n") % inst.hint)
481 self.ui.flush()
481 self.ui.flush()
482 self.cresult.write(b'exit 255')
482 self.cresult.write(b'exit 255')
483 return
483 return
484 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
484 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
485 insts = []
485 insts = []
486 if newhash.mtimehash != self.hashstate.mtimehash:
486 if newhash.mtimehash != self.hashstate.mtimehash:
487 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
487 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
488 insts.append(b'unlink %s' % addr)
488 insts.append(b'unlink %s' % addr)
489 # mtimehash is empty if one or more extensions fail to load.
489 # mtimehash is empty if one or more extensions fail to load.
490 # to be compatible with hg, still serve the client this time.
490 # to be compatible with hg, still serve the client this time.
491 if self.hashstate.mtimehash:
491 if self.hashstate.mtimehash:
492 insts.append(b'reconnect')
492 insts.append(b'reconnect')
493 if newhash.confighash != self.hashstate.confighash:
493 if newhash.confighash != self.hashstate.confighash:
494 addr = _hashaddress(self.baseaddress, newhash.confighash)
494 addr = _hashaddress(self.baseaddress, newhash.confighash)
495 insts.append(b'redirect %s' % addr)
495 insts.append(b'redirect %s' % addr)
496 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
496 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
497 self.cresult.write(b'\0'.join(insts) or b'\0')
497 self.cresult.write(b'\0'.join(insts) or b'\0')
498
498
499 def chdir(self):
499 def chdir(self):
500 """Change current directory
500 """Change current directory
501
501
502 Note that the behavior of --cwd option is bit different from this.
502 Note that the behavior of --cwd option is bit different from this.
503 It does not affect --config parameter.
503 It does not affect --config parameter.
504 """
504 """
505 path = self._readstr()
505 path = self._readstr()
506 if not path:
506 if not path:
507 return
507 return
508 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
508 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
509 os.chdir(path)
509 os.chdir(path)
510
510
511 def setumask(self):
511 def setumask(self):
512 """Change umask (DEPRECATED)"""
512 """Change umask (DEPRECATED)"""
513 # BUG: this does not follow the message frame structure, but kept for
513 # BUG: this does not follow the message frame structure, but kept for
514 # backward compatibility with old chg clients for some time
514 # backward compatibility with old chg clients for some time
515 self._setumask(self._read(4))
515 self._setumask(self._read(4))
516
516
517 def setumask2(self):
517 def setumask2(self):
518 """Change umask"""
518 """Change umask"""
519 data = self._readstr()
519 data = self._readstr()
520 if len(data) != 4:
520 if len(data) != 4:
521 raise ValueError(b'invalid mask length in setumask2 request')
521 raise ValueError(b'invalid mask length in setumask2 request')
522 self._setumask(data)
522 self._setumask(data)
523
523
524 def _setumask(self, data):
524 def _setumask(self, data):
525 mask = struct.unpack(b'>I', data)[0]
525 mask = struct.unpack(b'>I', data)[0]
526 self.ui.log(b'chgserver', b'setumask %r\n', mask)
526 self.ui.log(b'chgserver', b'setumask %r\n', mask)
527 os.umask(mask)
527 os.umask(mask)
528
528
529 def runcommand(self):
529 def runcommand(self):
530 # pager may be attached within the runcommand session, which should
530 # pager may be attached within the runcommand session, which should
531 # be detached at the end of the session. otherwise the pager wouldn't
531 # be detached at the end of the session. otherwise the pager wouldn't
532 # receive EOF.
532 # receive EOF.
533 globaloldios = self._oldios
533 globaloldios = self._oldios
534 self._oldios = []
534 self._oldios = []
535 try:
535 try:
536 return super(chgcmdserver, self).runcommand()
536 return super(chgcmdserver, self).runcommand()
537 finally:
537 finally:
538 self._restoreio()
538 self._restoreio()
539 self._oldios = globaloldios
539 self._oldios = globaloldios
540
540
541 def setenv(self):
541 def setenv(self):
542 """Clear and update os.environ
542 """Clear and update os.environ
543
543
544 Note that not all variables can make an effect on the running process.
544 Note that not all variables can make an effect on the running process.
545 """
545 """
546 l = self._readlist()
546 l = self._readlist()
547 try:
547 try:
548 newenv = dict(s.split(b'=', 1) for s in l)
548 newenv = dict(s.split(b'=', 1) for s in l)
549 except ValueError:
549 except ValueError:
550 raise ValueError(b'unexpected value in setenv request')
550 raise ValueError(b'unexpected value in setenv request')
551 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
551 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
552
552
553 # Python3 has some logic to "coerce" the C locale to a UTF-8 capable
554 # one, and it sets LC_CTYPE in the environment to C.UTF-8 if none of
555 # 'LC_CTYPE', 'LC_ALL' or 'LANG' are set (to any value). This can be
556 # disabled with PYTHONCOERCECLOCALE=0 in the environment.
557 #
558 # When fromui is called via _inithashstate, python has already set
559 # this, so that's in the environment right when we start up the hg
560 # process. Then chg will call us and tell us to set the environment to
561 # the one it has; this might NOT have LC_CTYPE, so we'll need to
562 # carry-forward the LC_CTYPE that was coerced in these situations.
563 #
564 # If this is not handled, we will fail config+env validation and fail
565 # to start chg. If this is just ignored instead of carried forward, we
566 # may have different behavior between chg and non-chg.
567 if pycompat.ispy3:
568 # Rename for wordwrapping purposes
569 oldenv = encoding.environ
570 if not any(
571 e.get(b'PYTHONCOERCECLOCALE') == b'0' for e in [oldenv, newenv]
572 ):
573 keys = [b'LC_CTYPE', b'LC_ALL', b'LANG']
574 old_keys = [k for k, v in oldenv.items() if k in keys and v]
575 new_keys = [k for k, v in newenv.items() if k in keys and v]
576 # If the user's environment (from chg) doesn't have ANY of the
577 # keys that python looks for, and the environment (from
578 # initialization) has ONLY LC_CTYPE and it's set to C.UTF-8,
579 # carry it forward.
580 if (
581 not new_keys
582 and old_keys == [b'LC_CTYPE']
583 and oldenv[b'LC_CTYPE'] == b'C.UTF-8'
584 ):
585 newenv[b'LC_CTYPE'] = oldenv[b'LC_CTYPE']
586
587 encoding.environ.clear()
553 encoding.environ.clear()
588 encoding.environ.update(newenv)
554 encoding.environ.update(newenv)
589
555
590 capabilities = commandserver.server.capabilities.copy()
556 capabilities = commandserver.server.capabilities.copy()
591 capabilities.update(
557 capabilities.update(
592 {
558 {
593 b'attachio': attachio,
559 b'attachio': attachio,
594 b'chdir': chdir,
560 b'chdir': chdir,
595 b'runcommand': runcommand,
561 b'runcommand': runcommand,
596 b'setenv': setenv,
562 b'setenv': setenv,
597 b'setumask': setumask,
563 b'setumask': setumask,
598 b'setumask2': setumask2,
564 b'setumask2': setumask2,
599 }
565 }
600 )
566 )
601
567
602 if util.safehasattr(procutil, b'setprocname'):
568 if util.safehasattr(procutil, b'setprocname'):
603
569
604 def setprocname(self):
570 def setprocname(self):
605 """Change process title"""
571 """Change process title"""
606 name = self._readstr()
572 name = self._readstr()
607 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
573 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
608 procutil.setprocname(name)
574 procutil.setprocname(name)
609
575
610 capabilities[b'setprocname'] = setprocname
576 capabilities[b'setprocname'] = setprocname
611
577
612
578
613 def _tempaddress(address):
579 def _tempaddress(address):
614 return b'%s.%d.tmp' % (address, os.getpid())
580 return b'%s.%d.tmp' % (address, os.getpid())
615
581
616
582
617 def _hashaddress(address, hashstr):
583 def _hashaddress(address, hashstr):
618 # if the basename of address contains '.', use only the left part. this
584 # if the basename of address contains '.', use only the left part. this
619 # makes it possible for the client to pass 'server.tmp$PID' and follow by
585 # makes it possible for the client to pass 'server.tmp$PID' and follow by
620 # an atomic rename to avoid locking when spawning new servers.
586 # an atomic rename to avoid locking when spawning new servers.
621 dirname, basename = os.path.split(address)
587 dirname, basename = os.path.split(address)
622 basename = basename.split(b'.', 1)[0]
588 basename = basename.split(b'.', 1)[0]
623 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
589 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
624
590
625
591
626 class chgunixservicehandler(object):
592 class chgunixservicehandler(object):
627 """Set of operations for chg services"""
593 """Set of operations for chg services"""
628
594
629 pollinterval = 1 # [sec]
595 pollinterval = 1 # [sec]
630
596
631 def __init__(self, ui):
597 def __init__(self, ui):
632 self.ui = ui
598 self.ui = ui
633 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
599 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
634 self._lastactive = time.time()
600 self._lastactive = time.time()
635
601
636 def bindsocket(self, sock, address):
602 def bindsocket(self, sock, address):
637 self._inithashstate(address)
603 self._inithashstate(address)
638 self._checkextensions()
604 self._checkextensions()
639 self._bind(sock)
605 self._bind(sock)
640 self._createsymlink()
606 self._createsymlink()
641 # no "listening at" message should be printed to simulate hg behavior
607 # no "listening at" message should be printed to simulate hg behavior
642
608
643 def _inithashstate(self, address):
609 def _inithashstate(self, address):
644 self._baseaddress = address
610 self._baseaddress = address
645 if self.ui.configbool(b'chgserver', b'skiphash'):
611 if self.ui.configbool(b'chgserver', b'skiphash'):
646 self._hashstate = None
612 self._hashstate = None
647 self._realaddress = address
613 self._realaddress = address
648 return
614 return
649 self._hashstate = hashstate.fromui(self.ui)
615 self._hashstate = hashstate.fromui(self.ui)
650 self._realaddress = _hashaddress(address, self._hashstate.confighash)
616 self._realaddress = _hashaddress(address, self._hashstate.confighash)
651
617
652 def _checkextensions(self):
618 def _checkextensions(self):
653 if not self._hashstate:
619 if not self._hashstate:
654 return
620 return
655 if extensions.notloaded():
621 if extensions.notloaded():
656 # one or more extensions failed to load. mtimehash becomes
622 # one or more extensions failed to load. mtimehash becomes
657 # meaningless because we do not know the paths of those extensions.
623 # meaningless because we do not know the paths of those extensions.
658 # set mtimehash to an illegal hash value to invalidate the server.
624 # set mtimehash to an illegal hash value to invalidate the server.
659 self._hashstate.mtimehash = b''
625 self._hashstate.mtimehash = b''
660
626
661 def _bind(self, sock):
627 def _bind(self, sock):
662 # use a unique temp address so we can stat the file and do ownership
628 # use a unique temp address so we can stat the file and do ownership
663 # check later
629 # check later
664 tempaddress = _tempaddress(self._realaddress)
630 tempaddress = _tempaddress(self._realaddress)
665 util.bindunixsocket(sock, tempaddress)
631 util.bindunixsocket(sock, tempaddress)
666 self._socketstat = os.stat(tempaddress)
632 self._socketstat = os.stat(tempaddress)
667 sock.listen(socket.SOMAXCONN)
633 sock.listen(socket.SOMAXCONN)
668 # rename will replace the old socket file if exists atomically. the
634 # rename will replace the old socket file if exists atomically. the
669 # old server will detect ownership change and exit.
635 # old server will detect ownership change and exit.
670 util.rename(tempaddress, self._realaddress)
636 util.rename(tempaddress, self._realaddress)
671
637
672 def _createsymlink(self):
638 def _createsymlink(self):
673 if self._baseaddress == self._realaddress:
639 if self._baseaddress == self._realaddress:
674 return
640 return
675 tempaddress = _tempaddress(self._baseaddress)
641 tempaddress = _tempaddress(self._baseaddress)
676 os.symlink(os.path.basename(self._realaddress), tempaddress)
642 os.symlink(os.path.basename(self._realaddress), tempaddress)
677 util.rename(tempaddress, self._baseaddress)
643 util.rename(tempaddress, self._baseaddress)
678
644
679 def _issocketowner(self):
645 def _issocketowner(self):
680 try:
646 try:
681 st = os.stat(self._realaddress)
647 st = os.stat(self._realaddress)
682 return (
648 return (
683 st.st_ino == self._socketstat.st_ino
649 st.st_ino == self._socketstat.st_ino
684 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
650 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
685 )
651 )
686 except OSError:
652 except OSError:
687 return False
653 return False
688
654
689 def unlinksocket(self, address):
655 def unlinksocket(self, address):
690 if not self._issocketowner():
656 if not self._issocketowner():
691 return
657 return
692 # it is possible to have a race condition here that we may
658 # it is possible to have a race condition here that we may
693 # remove another server's socket file. but that's okay
659 # remove another server's socket file. but that's okay
694 # since that server will detect and exit automatically and
660 # since that server will detect and exit automatically and
695 # the client will start a new server on demand.
661 # the client will start a new server on demand.
696 util.tryunlink(self._realaddress)
662 util.tryunlink(self._realaddress)
697
663
698 def shouldexit(self):
664 def shouldexit(self):
699 if not self._issocketowner():
665 if not self._issocketowner():
700 self.ui.log(
666 self.ui.log(
701 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
667 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
702 )
668 )
703 return True
669 return True
704 if time.time() - self._lastactive > self._idletimeout:
670 if time.time() - self._lastactive > self._idletimeout:
705 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
671 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
706 return True
672 return True
707 return False
673 return False
708
674
709 def newconnection(self):
675 def newconnection(self):
710 self._lastactive = time.time()
676 self._lastactive = time.time()
711
677
712 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
678 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
713 return chgcmdserver(
679 return chgcmdserver(
714 self.ui,
680 self.ui,
715 repo,
681 repo,
716 fin,
682 fin,
717 fout,
683 fout,
718 conn,
684 conn,
719 prereposetups,
685 prereposetups,
720 self._hashstate,
686 self._hashstate,
721 self._baseaddress,
687 self._baseaddress,
722 )
688 )
723
689
724
690
725 def chgunixservice(ui, repo, opts):
691 def chgunixservice(ui, repo, opts):
726 # CHGINTERNALMARK is set by chg client. It is an indication of things are
692 # CHGINTERNALMARK is set by chg client. It is an indication of things are
727 # started by chg so other code can do things accordingly, like disabling
693 # started by chg so other code can do things accordingly, like disabling
728 # demandimport or detecting chg client started by chg client. When executed
694 # demandimport or detecting chg client started by chg client. When executed
729 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
695 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
730 # environ cleaner.
696 # environ cleaner.
731 if b'CHGINTERNALMARK' in encoding.environ:
697 if b'CHGINTERNALMARK' in encoding.environ:
732 del encoding.environ[b'CHGINTERNALMARK']
698 del encoding.environ[b'CHGINTERNALMARK']
699 # Python3.7+ "coerces" the LC_CTYPE environment variable to a UTF-8 one if
700 # it thinks the current value is "C". This breaks the hash computation and
701 # causes chg to restart loop.
702 if b'CHGORIG_LC_CTYPE' in encoding.environ:
703 encoding.environ[b'LC_CTYPE'] = encoding.environ[b'CHGORIG_LC_CTYPE']
704 del encoding.environ[b'CHGORIG_LC_CTYPE']
705 elif b'CHG_CLEAR_LC_CTYPE' in encoding.environ:
706 if b'LC_CTYPE' in encoding.environ:
707 del encoding.environ[b'LC_CTYPE']
708 del encoding.environ[b'CHG_CLEAR_LC_CTYPE']
733
709
734 if repo:
710 if repo:
735 # one chgserver can serve multiple repos. drop repo information
711 # one chgserver can serve multiple repos. drop repo information
736 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
712 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
737 h = chgunixservicehandler(ui)
713 h = chgunixservicehandler(ui)
738 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
714 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,355 +1,368 b''
1 #require chg
1 #require chg
2
2
3 $ mkdir log
3 $ mkdir log
4 $ cp $HGRCPATH $HGRCPATH.unconfigured
4 $ cp $HGRCPATH $HGRCPATH.unconfigured
5 $ cat <<'EOF' >> $HGRCPATH
5 $ cat <<'EOF' >> $HGRCPATH
6 > [cmdserver]
6 > [cmdserver]
7 > log = $TESTTMP/log/server.log
7 > log = $TESTTMP/log/server.log
8 > max-log-files = 1
8 > max-log-files = 1
9 > max-log-size = 10 kB
9 > max-log-size = 10 kB
10 > EOF
10 > EOF
11 $ cp $HGRCPATH $HGRCPATH.orig
11 $ cp $HGRCPATH $HGRCPATH.orig
12
12
13 $ filterlog () {
13 $ filterlog () {
14 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
14 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
15 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
15 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
16 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
16 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
17 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
17 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
18 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
18 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
19 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
19 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
20 > }
20 > }
21
21
22 init repo
22 init repo
23
23
24 $ chg init foo
24 $ chg init foo
25 $ cd foo
25 $ cd foo
26
26
27 ill-formed config
27 ill-formed config
28
28
29 $ chg status
29 $ chg status
30 $ echo '=brokenconfig' >> $HGRCPATH
30 $ echo '=brokenconfig' >> $HGRCPATH
31 $ chg status
31 $ chg status
32 hg: parse error at * (glob)
32 hg: parse error at * (glob)
33 [255]
33 [255]
34
34
35 $ cp $HGRCPATH.orig $HGRCPATH
35 $ cp $HGRCPATH.orig $HGRCPATH
36
36
37 long socket path
37 long socket path
38
38
39 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
39 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
40 $ mkdir -p $sockpath
40 $ mkdir -p $sockpath
41 $ bakchgsockname=$CHGSOCKNAME
41 $ bakchgsockname=$CHGSOCKNAME
42 $ CHGSOCKNAME=$sockpath/server
42 $ CHGSOCKNAME=$sockpath/server
43 $ export CHGSOCKNAME
43 $ export CHGSOCKNAME
44 $ chg root
44 $ chg root
45 $TESTTMP/foo
45 $TESTTMP/foo
46 $ rm -rf $sockpath
46 $ rm -rf $sockpath
47 $ CHGSOCKNAME=$bakchgsockname
47 $ CHGSOCKNAME=$bakchgsockname
48 $ export CHGSOCKNAME
48 $ export CHGSOCKNAME
49
49
50 $ cd ..
50 $ cd ..
51
51
52 editor
52 editor
53 ------
53 ------
54
54
55 $ cat >> pushbuffer.py <<EOF
55 $ cat >> pushbuffer.py <<EOF
56 > def reposetup(ui, repo):
56 > def reposetup(ui, repo):
57 > repo.ui.pushbuffer(subproc=True)
57 > repo.ui.pushbuffer(subproc=True)
58 > EOF
58 > EOF
59
59
60 $ chg init editor
60 $ chg init editor
61 $ cd editor
61 $ cd editor
62
62
63 by default, system() should be redirected to the client:
63 by default, system() should be redirected to the client:
64
64
65 $ touch foo
65 $ touch foo
66 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
66 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
67 > | egrep "HG:|run 'cat"
67 > | egrep "HG:|run 'cat"
68 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
68 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
69 HG: Enter commit message. Lines beginning with 'HG:' are removed.
69 HG: Enter commit message. Lines beginning with 'HG:' are removed.
70 HG: Leave message empty to abort commit.
70 HG: Leave message empty to abort commit.
71 HG: --
71 HG: --
72 HG: user: test
72 HG: user: test
73 HG: branch 'default'
73 HG: branch 'default'
74 HG: added foo
74 HG: added foo
75
75
76 but no redirection should be made if output is captured:
76 but no redirection should be made if output is captured:
77
77
78 $ touch bar
78 $ touch bar
79 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
79 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
80 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
80 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
81 > | egrep "HG:|run 'cat"
81 > | egrep "HG:|run 'cat"
82 [1]
82 [1]
83
83
84 check that commit commands succeeded:
84 check that commit commands succeeded:
85
85
86 $ hg log -T '{rev}:{desc}\n'
86 $ hg log -T '{rev}:{desc}\n'
87 1:bufferred
87 1:bufferred
88 0:channeled
88 0:channeled
89
89
90 $ cd ..
90 $ cd ..
91
91
92 pager
92 pager
93 -----
93 -----
94
94
95 $ cat >> fakepager.py <<EOF
95 $ cat >> fakepager.py <<EOF
96 > import sys
96 > import sys
97 > for line in sys.stdin:
97 > for line in sys.stdin:
98 > sys.stdout.write('paged! %r\n' % line)
98 > sys.stdout.write('paged! %r\n' % line)
99 > EOF
99 > EOF
100
100
101 enable pager extension globally, but spawns the master server with no tty:
101 enable pager extension globally, but spawns the master server with no tty:
102
102
103 $ chg init pager
103 $ chg init pager
104 $ cd pager
104 $ cd pager
105 $ cat >> $HGRCPATH <<EOF
105 $ cat >> $HGRCPATH <<EOF
106 > [extensions]
106 > [extensions]
107 > pager =
107 > pager =
108 > [pager]
108 > [pager]
109 > pager = "$PYTHON" $TESTTMP/fakepager.py
109 > pager = "$PYTHON" $TESTTMP/fakepager.py
110 > EOF
110 > EOF
111 $ chg version > /dev/null
111 $ chg version > /dev/null
112 $ touch foo
112 $ touch foo
113 $ chg ci -qAm foo
113 $ chg ci -qAm foo
114
114
115 pager should be enabled if the attached client has a tty:
115 pager should be enabled if the attached client has a tty:
116
116
117 $ chg log -l1 -q --config ui.formatted=True
117 $ chg log -l1 -q --config ui.formatted=True
118 paged! '0:1f7b0de80e11\n'
118 paged! '0:1f7b0de80e11\n'
119 $ chg log -l1 -q --config ui.formatted=False
119 $ chg log -l1 -q --config ui.formatted=False
120 0:1f7b0de80e11
120 0:1f7b0de80e11
121
121
122 chg waits for pager if runcommand raises
122 chg waits for pager if runcommand raises
123
123
124 $ cat > $TESTTMP/crash.py <<EOF
124 $ cat > $TESTTMP/crash.py <<EOF
125 > from mercurial import registrar
125 > from mercurial import registrar
126 > cmdtable = {}
126 > cmdtable = {}
127 > command = registrar.command(cmdtable)
127 > command = registrar.command(cmdtable)
128 > @command(b'crash')
128 > @command(b'crash')
129 > def pagercrash(ui, repo, *pats, **opts):
129 > def pagercrash(ui, repo, *pats, **opts):
130 > ui.write(b'going to crash\n')
130 > ui.write(b'going to crash\n')
131 > raise Exception('.')
131 > raise Exception('.')
132 > EOF
132 > EOF
133
133
134 $ cat > $TESTTMP/fakepager.py <<EOF
134 $ cat > $TESTTMP/fakepager.py <<EOF
135 > from __future__ import absolute_import
135 > from __future__ import absolute_import
136 > import sys
136 > import sys
137 > import time
137 > import time
138 > for line in iter(sys.stdin.readline, ''):
138 > for line in iter(sys.stdin.readline, ''):
139 > if 'crash' in line: # only interested in lines containing 'crash'
139 > if 'crash' in line: # only interested in lines containing 'crash'
140 > # if chg exits when pager is sleeping (incorrectly), the output
140 > # if chg exits when pager is sleeping (incorrectly), the output
141 > # will be captured by the next test case
141 > # will be captured by the next test case
142 > time.sleep(1)
142 > time.sleep(1)
143 > sys.stdout.write('crash-pager: %s' % line)
143 > sys.stdout.write('crash-pager: %s' % line)
144 > EOF
144 > EOF
145
145
146 $ cat >> .hg/hgrc <<EOF
146 $ cat >> .hg/hgrc <<EOF
147 > [extensions]
147 > [extensions]
148 > crash = $TESTTMP/crash.py
148 > crash = $TESTTMP/crash.py
149 > EOF
149 > EOF
150
150
151 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
151 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
152 crash-pager: going to crash
152 crash-pager: going to crash
153 [255]
153 [255]
154
154
155 $ cd ..
155 $ cd ..
156
156
157 server lifecycle
157 server lifecycle
158 ----------------
158 ----------------
159
159
160 chg server should be restarted on code change, and old server will shut down
160 chg server should be restarted on code change, and old server will shut down
161 automatically. In this test, we use the following time parameters:
161 automatically. In this test, we use the following time parameters:
162
162
163 - "sleep 1" to make mtime different
163 - "sleep 1" to make mtime different
164 - "sleep 2" to notice mtime change (polling interval is 1 sec)
164 - "sleep 2" to notice mtime change (polling interval is 1 sec)
165
165
166 set up repository with an extension:
166 set up repository with an extension:
167
167
168 $ chg init extreload
168 $ chg init extreload
169 $ cd extreload
169 $ cd extreload
170 $ touch dummyext.py
170 $ touch dummyext.py
171 $ cat <<EOF >> .hg/hgrc
171 $ cat <<EOF >> .hg/hgrc
172 > [extensions]
172 > [extensions]
173 > dummyext = dummyext.py
173 > dummyext = dummyext.py
174 > EOF
174 > EOF
175
175
176 isolate socket directory for stable result:
176 isolate socket directory for stable result:
177
177
178 $ OLDCHGSOCKNAME=$CHGSOCKNAME
178 $ OLDCHGSOCKNAME=$CHGSOCKNAME
179 $ mkdir chgsock
179 $ mkdir chgsock
180 $ CHGSOCKNAME=`pwd`/chgsock/server
180 $ CHGSOCKNAME=`pwd`/chgsock/server
181
181
182 warm up server:
182 warm up server:
183
183
184 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
184 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
185 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
185 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
186
186
187 new server should be started if extension modified:
187 new server should be started if extension modified:
188
188
189 $ sleep 1
189 $ sleep 1
190 $ touch dummyext.py
190 $ touch dummyext.py
191 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
191 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
192 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
192 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
193 chg: debug: * instruction: reconnect (glob)
193 chg: debug: * instruction: reconnect (glob)
194 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
194 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
195
195
196 old server will shut down, while new server should still be reachable:
196 old server will shut down, while new server should still be reachable:
197
197
198 $ sleep 2
198 $ sleep 2
199 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
199 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
200
200
201 socket file should never be unlinked by old server:
201 socket file should never be unlinked by old server:
202 (simulates unowned socket by updating mtime, which makes sure server exits
202 (simulates unowned socket by updating mtime, which makes sure server exits
203 at polling cycle)
203 at polling cycle)
204
204
205 $ ls chgsock/server-*
205 $ ls chgsock/server-*
206 chgsock/server-* (glob)
206 chgsock/server-* (glob)
207 $ touch chgsock/server-*
207 $ touch chgsock/server-*
208 $ sleep 2
208 $ sleep 2
209 $ ls chgsock/server-*
209 $ ls chgsock/server-*
210 chgsock/server-* (glob)
210 chgsock/server-* (glob)
211
211
212 since no server is reachable from socket file, new server should be started:
212 since no server is reachable from socket file, new server should be started:
213 (this test makes sure that old server shut down automatically)
213 (this test makes sure that old server shut down automatically)
214
214
215 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
215 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
216 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
216 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
217
217
218 shut down servers and restore environment:
218 shut down servers and restore environment:
219
219
220 $ rm -R chgsock
220 $ rm -R chgsock
221 $ sleep 2
221 $ sleep 2
222 $ CHGSOCKNAME=$OLDCHGSOCKNAME
222 $ CHGSOCKNAME=$OLDCHGSOCKNAME
223 $ cd ..
223 $ cd ..
224
224
225 check that server events are recorded:
225 check that server events are recorded:
226
226
227 $ ls log
227 $ ls log
228 server.log
228 server.log
229 server.log.1
229 server.log.1
230
230
231 print only the last 10 lines, since we aren't sure how many records are
231 print only the last 10 lines, since we aren't sure how many records are
232 preserved (since setprocname isn't available on py3, the 10th-most-recent line
232 preserved (since setprocname isn't available on py3, the 10th-most-recent line
233 is different when using py3):
233 is different when using py3):
234
234
235 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
235 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
236 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (py3 !)
236 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (py3 !)
237 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
237 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
238 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (no-py3 !)
238 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (no-py3 !)
239 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
239 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
240 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
240 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
241 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
241 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
242 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
242 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
243 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
243 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
244 YYYY/MM/DD HH:MM:SS (PID)> validate: []
244 YYYY/MM/DD HH:MM:SS (PID)> validate: []
245 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
245 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
246 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
246 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
247
247
248 repository cache
248 repository cache
249 ----------------
249 ----------------
250
250
251 $ rm log/server.log*
251 $ rm log/server.log*
252 $ cp $HGRCPATH.unconfigured $HGRCPATH
252 $ cp $HGRCPATH.unconfigured $HGRCPATH
253 $ cat <<'EOF' >> $HGRCPATH
253 $ cat <<'EOF' >> $HGRCPATH
254 > [cmdserver]
254 > [cmdserver]
255 > log = $TESTTMP/log/server.log
255 > log = $TESTTMP/log/server.log
256 > max-repo-cache = 1
256 > max-repo-cache = 1
257 > track-log = command, repocache
257 > track-log = command, repocache
258 > EOF
258 > EOF
259
259
260 isolate socket directory for stable result:
260 isolate socket directory for stable result:
261
261
262 $ OLDCHGSOCKNAME=$CHGSOCKNAME
262 $ OLDCHGSOCKNAME=$CHGSOCKNAME
263 $ mkdir chgsock
263 $ mkdir chgsock
264 $ CHGSOCKNAME=`pwd`/chgsock/server
264 $ CHGSOCKNAME=`pwd`/chgsock/server
265
265
266 create empty repo and cache it:
266 create empty repo and cache it:
267
267
268 $ hg init cached
268 $ hg init cached
269 $ hg id -R cached
269 $ hg id -R cached
270 000000000000 tip
270 000000000000 tip
271 $ sleep 1
271 $ sleep 1
272
272
273 modify repo (and cache will be invalidated):
273 modify repo (and cache will be invalidated):
274
274
275 $ touch cached/a
275 $ touch cached/a
276 $ hg ci -R cached -Am 'add a'
276 $ hg ci -R cached -Am 'add a'
277 adding a
277 adding a
278 $ sleep 1
278 $ sleep 1
279
279
280 read cached repo:
280 read cached repo:
281
281
282 $ hg log -R cached
282 $ hg log -R cached
283 changeset: 0:ac82d8b1f7c4
283 changeset: 0:ac82d8b1f7c4
284 tag: tip
284 tag: tip
285 user: test
285 user: test
286 date: Thu Jan 01 00:00:00 1970 +0000
286 date: Thu Jan 01 00:00:00 1970 +0000
287 summary: add a
287 summary: add a
288
288
289 $ sleep 1
289 $ sleep 1
290
290
291 discard cached from LRU cache:
291 discard cached from LRU cache:
292
292
293 $ hg clone cached cached2
293 $ hg clone cached cached2
294 updating to branch default
294 updating to branch default
295 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 $ hg id -R cached2
296 $ hg id -R cached2
297 ac82d8b1f7c4 tip
297 ac82d8b1f7c4 tip
298 $ sleep 1
298 $ sleep 1
299
299
300 read uncached repo:
300 read uncached repo:
301
301
302 $ hg log -R cached
302 $ hg log -R cached
303 changeset: 0:ac82d8b1f7c4
303 changeset: 0:ac82d8b1f7c4
304 tag: tip
304 tag: tip
305 user: test
305 user: test
306 date: Thu Jan 01 00:00:00 1970 +0000
306 date: Thu Jan 01 00:00:00 1970 +0000
307 summary: add a
307 summary: add a
308
308
309 $ sleep 1
309 $ sleep 1
310
310
311 shut down servers and restore environment:
311 shut down servers and restore environment:
312
312
313 $ rm -R chgsock
313 $ rm -R chgsock
314 $ sleep 2
314 $ sleep 2
315 $ CHGSOCKNAME=$OLDCHGSOCKNAME
315 $ CHGSOCKNAME=$OLDCHGSOCKNAME
316
316
317 check server log:
317 check server log:
318
318
319 $ cat log/server.log | filterlog
319 $ cat log/server.log | filterlog
320 YYYY/MM/DD HH:MM:SS (PID)> init cached
320 YYYY/MM/DD HH:MM:SS (PID)> init cached
321 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
321 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
322 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
322 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
323 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
323 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
324 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
324 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
325 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
325 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
326 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
326 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
327 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
327 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
328 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
328 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
329 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
329 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
330 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
330 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
331 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
331 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
332 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
332 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
333 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
333 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
334
334
335 Test that chg works even when python "coerces" the locale (py3.7+, which is done
335 Test that chg works (sets to the user's actual LC_CTYPE) even when python
336 by default if none of LC_ALL, LC_CTYPE, or LANG are set in the environment)
336 "coerces" the locale (py3.7+)
337
337
338 $ cat > $TESTTMP/debugenv.py <<EOF
338 $ cat > $TESTTMP/debugenv.py <<EOF
339 > from mercurial import encoding
339 > from mercurial import encoding
340 > from mercurial import registrar
340 > from mercurial import registrar
341 > cmdtable = {}
341 > cmdtable = {}
342 > command = registrar.command(cmdtable)
342 > command = registrar.command(cmdtable)
343 > @command(b'debugenv', [], b'', norepo=True)
343 > @command(b'debugenv', [], b'', norepo=True)
344 > def debugenv(ui):
344 > def debugenv(ui):
345 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
345 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
346 > v = encoding.environ.get(k)
346 > v = encoding.environ.get(k)
347 > if v is not None:
347 > if v is not None:
348 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
348 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
349 > EOF
349 > EOF
350 (hg keeps python's modified LC_CTYPE, chg doesn't)
351 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
352 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
353 LC_CTYPE=C.UTF-8 (py37 !)
354 LC_CTYPE= (no-py37 !)
355 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
356 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
357 LC_CTYPE=
358 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
359 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
360 LC_CTYPE=unsupported_value
361 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
362 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
363 LC_CTYPE=
350 $ LANG= LC_ALL= LC_CTYPE= chg \
364 $ LANG= LC_ALL= LC_CTYPE= chg \
351 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
365 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
352 LC_ALL=
366 LC_ALL=
353 LC_CTYPE=C.UTF-8 (py37 !)
367 LC_CTYPE=
354 LC_CTYPE= (no-py37 !)
355 LANG=
368 LANG=
General Comments 0
You need to be logged in to leave comments. Login now