##// END OF EJS Templates
chg: fallback to original hg if stdio fds are missing...
Jun Wu -
r46093:5eee6f4f default
parent child Browse files
Show More
@@ -1,470 +1,484
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");
229 const char *lc_ctype_env = getenv("LC_CTYPE");
230 if (lc_ctype_env == NULL) {
230 if (lc_ctype_env == NULL) {
231 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
231 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
232 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
232 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
233 } else {
233 } else {
234 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
234 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
235 abortmsgerrno("failed to setenv CHGORIG_LC_CTYPE");
235 abortmsgerrno("failed to setenv CHGORIG_LC_CTYPE");
236 }
236 }
237 }
237 }
238
238
239 if (putenv("CHGINTERNALMARK=") != 0)
239 if (putenv("CHGINTERNALMARK=") != 0)
240 abortmsgerrno("failed to putenv");
240 abortmsgerrno("failed to putenv");
241 if (execvp(hgcmd, (char **)argv) < 0)
241 if (execvp(hgcmd, (char **)argv) < 0)
242 abortmsgerrno("failed to exec cmdserver");
242 abortmsgerrno("failed to exec cmdserver");
243 free(argv);
243 free(argv);
244 }
244 }
245
245
246 /* 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. */
247 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
247 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
248 {
248 {
249 static const struct timespec sleepreq = {0, 10 * 1000000};
249 static const struct timespec sleepreq = {0, 10 * 1000000};
250 int pst = 0;
250 int pst = 0;
251
251
252 debugmsg("try connect to %s repeatedly", opts->initsockname);
252 debugmsg("try connect to %s repeatedly", opts->initsockname);
253
253
254 unsigned int timeoutsec = 60; /* default: 60 seconds */
254 unsigned int timeoutsec = 60; /* default: 60 seconds */
255 const char *timeoutenv = getenv("CHGTIMEOUT");
255 const char *timeoutenv = getenv("CHGTIMEOUT");
256 if (timeoutenv)
256 if (timeoutenv)
257 sscanf(timeoutenv, "%u", &timeoutsec);
257 sscanf(timeoutenv, "%u", &timeoutsec);
258
258
259 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
259 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
260 hgclient_t *hgc = hgc_open(opts->initsockname);
260 hgclient_t *hgc = hgc_open(opts->initsockname);
261 if (hgc) {
261 if (hgc) {
262 debugmsg("rename %s to %s", opts->initsockname,
262 debugmsg("rename %s to %s", opts->initsockname,
263 opts->sockname);
263 opts->sockname);
264 int r = rename(opts->initsockname, opts->sockname);
264 int r = rename(opts->initsockname, opts->sockname);
265 if (r != 0)
265 if (r != 0)
266 abortmsgerrno("cannot rename");
266 abortmsgerrno("cannot rename");
267 return hgc;
267 return hgc;
268 }
268 }
269
269
270 if (pid > 0) {
270 if (pid > 0) {
271 /* collect zombie if child process fails to start */
271 /* collect zombie if child process fails to start */
272 int r = waitpid(pid, &pst, WNOHANG);
272 int r = waitpid(pid, &pst, WNOHANG);
273 if (r != 0)
273 if (r != 0)
274 goto cleanup;
274 goto cleanup;
275 }
275 }
276
276
277 nanosleep(&sleepreq, NULL);
277 nanosleep(&sleepreq, NULL);
278 }
278 }
279
279
280 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
280 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
281 return NULL;
281 return NULL;
282
282
283 cleanup:
283 cleanup:
284 if (WIFEXITED(pst)) {
284 if (WIFEXITED(pst)) {
285 if (WEXITSTATUS(pst) == 0)
285 if (WEXITSTATUS(pst) == 0)
286 abortmsg("could not connect to cmdserver "
286 abortmsg("could not connect to cmdserver "
287 "(exited with status 0)");
287 "(exited with status 0)");
288 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
288 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
289 exit(WEXITSTATUS(pst));
289 exit(WEXITSTATUS(pst));
290 } else if (WIFSIGNALED(pst)) {
290 } else if (WIFSIGNALED(pst)) {
291 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
291 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
292 } else {
292 } else {
293 abortmsg("error while waiting for cmdserver");
293 abortmsg("error while waiting for cmdserver");
294 }
294 }
295 return NULL;
295 return NULL;
296 }
296 }
297
297
298 /* Connect to a cmdserver. Will start a new server on demand. */
298 /* Connect to a cmdserver. Will start a new server on demand. */
299 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
299 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
300 {
300 {
301 const char *sockname =
301 const char *sockname =
302 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
302 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
303 debugmsg("try connect to %s", sockname);
303 debugmsg("try connect to %s", sockname);
304 hgclient_t *hgc = hgc_open(sockname);
304 hgclient_t *hgc = hgc_open(sockname);
305 if (hgc)
305 if (hgc)
306 return hgc;
306 return hgc;
307
307
308 /* prevent us from being connected to an outdated server: we were
308 /* prevent us from being connected to an outdated server: we were
309 * told by a server to redirect to opts->redirectsockname and that
309 * told by a server to redirect to opts->redirectsockname and that
310 * 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
311 * again because it will probably tell us the same thing. */
311 * again because it will probably tell us the same thing. */
312 if (sockname == opts->redirectsockname)
312 if (sockname == opts->redirectsockname)
313 unlink(opts->sockname);
313 unlink(opts->sockname);
314
314
315 debugmsg("start cmdserver at %s", opts->initsockname);
315 debugmsg("start cmdserver at %s", opts->initsockname);
316
316
317 pid_t pid = fork();
317 pid_t pid = fork();
318 if (pid < 0)
318 if (pid < 0)
319 abortmsg("failed to fork cmdserver process");
319 abortmsg("failed to fork cmdserver process");
320 if (pid == 0) {
320 if (pid == 0) {
321 execcmdserver(opts);
321 execcmdserver(opts);
322 } else {
322 } else {
323 hgc = retryconnectcmdserver(opts, pid);
323 hgc = retryconnectcmdserver(opts, pid);
324 }
324 }
325
325
326 return hgc;
326 return hgc;
327 }
327 }
328
328
329 static void killcmdserver(const struct cmdserveropts *opts)
329 static void killcmdserver(const struct cmdserveropts *opts)
330 {
330 {
331 /* resolve config hash */
331 /* resolve config hash */
332 char *resolvedpath = realpath(opts->sockname, NULL);
332 char *resolvedpath = realpath(opts->sockname, NULL);
333 if (resolvedpath) {
333 if (resolvedpath) {
334 unlink(resolvedpath);
334 unlink(resolvedpath);
335 free(resolvedpath);
335 free(resolvedpath);
336 }
336 }
337 }
337 }
338
338
339 /* 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
340 * Return 1 if reconnect is needed, otherwise 0 */
340 * Return 1 if reconnect is needed, otherwise 0 */
341 static int runinstructions(struct cmdserveropts *opts, const char **insts)
341 static int runinstructions(struct cmdserveropts *opts, const char **insts)
342 {
342 {
343 int needreconnect = 0;
343 int needreconnect = 0;
344 if (!insts)
344 if (!insts)
345 return needreconnect;
345 return needreconnect;
346
346
347 assert(insts);
347 assert(insts);
348 opts->redirectsockname[0] = '\0';
348 opts->redirectsockname[0] = '\0';
349 const char **pinst;
349 const char **pinst;
350 for (pinst = insts; *pinst; pinst++) {
350 for (pinst = insts; *pinst; pinst++) {
351 debugmsg("instruction: %s", *pinst);
351 debugmsg("instruction: %s", *pinst);
352 if (strncmp(*pinst, "unlink ", 7) == 0) {
352 if (strncmp(*pinst, "unlink ", 7) == 0) {
353 unlink(*pinst + 7);
353 unlink(*pinst + 7);
354 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
354 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
355 int r = snprintf(opts->redirectsockname,
355 int r = snprintf(opts->redirectsockname,
356 sizeof(opts->redirectsockname), "%s",
356 sizeof(opts->redirectsockname), "%s",
357 *pinst + 9);
357 *pinst + 9);
358 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
358 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
359 abortmsg("redirect path is too long (%d)", r);
359 abortmsg("redirect path is too long (%d)", r);
360 needreconnect = 1;
360 needreconnect = 1;
361 } else if (strncmp(*pinst, "exit ", 5) == 0) {
361 } else if (strncmp(*pinst, "exit ", 5) == 0) {
362 int n = 0;
362 int n = 0;
363 if (sscanf(*pinst + 5, "%d", &n) != 1)
363 if (sscanf(*pinst + 5, "%d", &n) != 1)
364 abortmsg("cannot read the exit code");
364 abortmsg("cannot read the exit code");
365 exit(n);
365 exit(n);
366 } else if (strcmp(*pinst, "reconnect") == 0) {
366 } else if (strcmp(*pinst, "reconnect") == 0) {
367 needreconnect = 1;
367 needreconnect = 1;
368 } else {
368 } else {
369 abortmsg("unknown instruction: %s", *pinst);
369 abortmsg("unknown instruction: %s", *pinst);
370 }
370 }
371 }
371 }
372 return needreconnect;
372 return needreconnect;
373 }
373 }
374
374
375 /*
375 /*
376 * Test whether the command is unsupported or not. This is not designed to
376 * Test whether the command and the environment is unsupported or not.
377 * cover all cases. But it's fast, does not depend on the server.
377 *
378 * If any of the stdio file descriptors are not present (rare, but some tools
379 * might spawn new processes without stdio instead of redirecting them to the
380 * null device), then mark it as not supported because attachio won't work
381 * correctly.
382 *
383 * The command list is not designed to cover all cases. But it's fast, and does
384 * not depend on the server.
378 */
385 */
379 static int isunsupported(int argc, const char *argv[])
386 static int isunsupported(int argc, const char *argv[])
380 {
387 {
381 enum { SERVE = 1,
388 enum { SERVE = 1,
382 DAEMON = 2,
389 DAEMON = 2,
383 SERVEDAEMON = SERVE | DAEMON,
390 SERVEDAEMON = SERVE | DAEMON,
384 };
391 };
385 unsigned int state = 0;
392 unsigned int state = 0;
386 int i;
393 int i;
394 /* use fcntl to test missing stdio fds */
395 if (fcntl(STDIN_FILENO, F_GETFD) == -1 ||
396 fcntl(STDOUT_FILENO, F_GETFD) == -1 ||
397 fcntl(STDERR_FILENO, F_GETFD) == -1) {
398 debugmsg("stdio fds are missing");
399 return 1;
400 }
387 for (i = 0; i < argc; ++i) {
401 for (i = 0; i < argc; ++i) {
388 if (strcmp(argv[i], "--") == 0)
402 if (strcmp(argv[i], "--") == 0)
389 break;
403 break;
390 /*
404 /*
391 * there can be false positives but no false negative
405 * there can be false positives but no false negative
392 * we cannot assume `serve` will always be first argument
406 * we cannot assume `serve` will always be first argument
393 * because global options can be passed before the command name
407 * because global options can be passed before the command name
394 */
408 */
395 if (strcmp("serve", argv[i]) == 0)
409 if (strcmp("serve", argv[i]) == 0)
396 state |= SERVE;
410 state |= SERVE;
397 else if (strcmp("-d", argv[i]) == 0 ||
411 else if (strcmp("-d", argv[i]) == 0 ||
398 strcmp("--daemon", argv[i]) == 0)
412 strcmp("--daemon", argv[i]) == 0)
399 state |= DAEMON;
413 state |= DAEMON;
400 }
414 }
401 return (state & SERVEDAEMON) == SERVEDAEMON;
415 return (state & SERVEDAEMON) == SERVEDAEMON;
402 }
416 }
403
417
404 static void execoriginalhg(const char *argv[])
418 static void execoriginalhg(const char *argv[])
405 {
419 {
406 debugmsg("execute original hg");
420 debugmsg("execute original hg");
407 if (execvp(gethgcmd(), (char **)argv) < 0)
421 if (execvp(gethgcmd(), (char **)argv) < 0)
408 abortmsgerrno("failed to exec original hg");
422 abortmsgerrno("failed to exec original hg");
409 }
423 }
410
424
411 int main(int argc, const char *argv[], const char *envp[])
425 int main(int argc, const char *argv[], const char *envp[])
412 {
426 {
413 if (getenv("CHGDEBUG"))
427 if (getenv("CHGDEBUG"))
414 enabledebugmsg();
428 enabledebugmsg();
415
429
416 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
430 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
417 enablecolor();
431 enablecolor();
418
432
419 if (getenv("CHGINTERNALMARK"))
433 if (getenv("CHGINTERNALMARK"))
420 abortmsg("chg started by chg detected.\n"
434 abortmsg("chg started by chg detected.\n"
421 "Please make sure ${HG:-hg} is not a symlink or "
435 "Please make sure ${HG:-hg} is not a symlink or "
422 "wrapper to chg. Alternatively, set $CHGHG to the "
436 "wrapper to chg. Alternatively, set $CHGHG to the "
423 "path of real hg.");
437 "path of real hg.");
424
438
425 if (isunsupported(argc - 1, argv + 1))
439 if (isunsupported(argc - 1, argv + 1))
426 execoriginalhg(argv);
440 execoriginalhg(argv);
427
441
428 struct cmdserveropts opts;
442 struct cmdserveropts opts;
429 initcmdserveropts(&opts);
443 initcmdserveropts(&opts);
430 setcmdserveropts(&opts);
444 setcmdserveropts(&opts);
431 setcmdserverargs(&opts, argc, argv);
445 setcmdserverargs(&opts, argc, argv);
432
446
433 if (argc == 2) {
447 if (argc == 2) {
434 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
448 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
435 killcmdserver(&opts);
449 killcmdserver(&opts);
436 return 0;
450 return 0;
437 }
451 }
438 }
452 }
439
453
440 hgclient_t *hgc;
454 hgclient_t *hgc;
441 size_t retry = 0;
455 size_t retry = 0;
442 while (1) {
456 while (1) {
443 hgc = connectcmdserver(&opts);
457 hgc = connectcmdserver(&opts);
444 if (!hgc)
458 if (!hgc)
445 abortmsg("cannot open hg client");
459 abortmsg("cannot open hg client");
446 hgc_setenv(hgc, envp);
460 hgc_setenv(hgc, envp);
447 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
461 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
448 int needreconnect = runinstructions(&opts, insts);
462 int needreconnect = runinstructions(&opts, insts);
449 free(insts);
463 free(insts);
450 if (!needreconnect)
464 if (!needreconnect)
451 break;
465 break;
452 hgc_close(hgc);
466 hgc_close(hgc);
453 if (++retry > 10)
467 if (++retry > 10)
454 abortmsg("too many redirections.\n"
468 abortmsg("too many redirections.\n"
455 "Please make sure %s is not a wrapper which "
469 "Please make sure %s is not a wrapper which "
456 "changes sensitive environment variables "
470 "changes sensitive environment variables "
457 "before executing hg. If you have to use a "
471 "before executing hg. If you have to use a "
458 "wrapper, wrap chg instead of hg.",
472 "wrapper, wrap chg instead of hg.",
459 gethgcmd());
473 gethgcmd());
460 }
474 }
461
475
462 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
476 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
463 atexit(waitpager);
477 atexit(waitpager);
464 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
478 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
465 restoresignalhandler();
479 restoresignalhandler();
466 hgc_close(hgc);
480 hgc_close(hgc);
467 freecmdserveropts(&opts);
481 freecmdserveropts(&opts);
468
482
469 return exitcode;
483 return exitcode;
470 }
484 }
@@ -1,461 +1,469
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 no stdout data should be printed after pager quits, and the buffered data
155 no stdout data should be printed after pager quits, and the buffered data
156 should never persist (issue6207)
156 should never persist (issue6207)
157
157
158 "killed!" may be printed if terminated by SIGPIPE, which isn't important
158 "killed!" may be printed if terminated by SIGPIPE, which isn't important
159 in this test.
159 in this test.
160
160
161 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
161 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
162 > import time
162 > import time
163 > from mercurial import error, registrar
163 > from mercurial import error, registrar
164 > cmdtable = {}
164 > cmdtable = {}
165 > command = registrar.command(cmdtable)
165 > command = registrar.command(cmdtable)
166 > @command(b'bulkwrite')
166 > @command(b'bulkwrite')
167 > def bulkwrite(ui, repo, *pats, **opts):
167 > def bulkwrite(ui, repo, *pats, **opts):
168 > ui.write(b'going to write massive data\n')
168 > ui.write(b'going to write massive data\n')
169 > ui.flush()
169 > ui.flush()
170 > t = time.time()
170 > t = time.time()
171 > while time.time() - t < 2:
171 > while time.time() - t < 2:
172 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
172 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
173 > raise error.Abort(b"write() doesn't block")
173 > raise error.Abort(b"write() doesn't block")
174 > EOF
174 > EOF
175
175
176 $ cat > $TESTTMP/fakepager.py <<'EOF'
176 $ cat > $TESTTMP/fakepager.py <<'EOF'
177 > import sys
177 > import sys
178 > import time
178 > import time
179 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
179 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
180 > time.sleep(1) # new data will be written
180 > time.sleep(1) # new data will be written
181 > EOF
181 > EOF
182
182
183 $ cat >> .hg/hgrc <<EOF
183 $ cat >> .hg/hgrc <<EOF
184 > [extensions]
184 > [extensions]
185 > bulkwrite = $TESTTMP/bulkwrite.py
185 > bulkwrite = $TESTTMP/bulkwrite.py
186 > EOF
186 > EOF
187
187
188 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
188 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
189 paged! 'going to write massive data\n'
189 paged! 'going to write massive data\n'
190 killed! (?)
190 killed! (?)
191 [255]
191 [255]
192
192
193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
194 paged! 'going to write massive data\n'
194 paged! 'going to write massive data\n'
195 killed! (?)
195 killed! (?)
196 [255]
196 [255]
197
197
198 $ cd ..
198 $ cd ..
199
199
200 missing stdio
201 -------------
202
203 $ CHGDEBUG=1 chg version -q 0<&-
204 chg: debug: * stdio fds are missing (glob)
205 chg: debug: * execute original hg (glob)
206 Mercurial Distributed SCM * (glob)
207
200 server lifecycle
208 server lifecycle
201 ----------------
209 ----------------
202
210
203 chg server should be restarted on code change, and old server will shut down
211 chg server should be restarted on code change, and old server will shut down
204 automatically. In this test, we use the following time parameters:
212 automatically. In this test, we use the following time parameters:
205
213
206 - "sleep 1" to make mtime different
214 - "sleep 1" to make mtime different
207 - "sleep 2" to notice mtime change (polling interval is 1 sec)
215 - "sleep 2" to notice mtime change (polling interval is 1 sec)
208
216
209 set up repository with an extension:
217 set up repository with an extension:
210
218
211 $ chg init extreload
219 $ chg init extreload
212 $ cd extreload
220 $ cd extreload
213 $ touch dummyext.py
221 $ touch dummyext.py
214 $ cat <<EOF >> .hg/hgrc
222 $ cat <<EOF >> .hg/hgrc
215 > [extensions]
223 > [extensions]
216 > dummyext = dummyext.py
224 > dummyext = dummyext.py
217 > EOF
225 > EOF
218
226
219 isolate socket directory for stable result:
227 isolate socket directory for stable result:
220
228
221 $ OLDCHGSOCKNAME=$CHGSOCKNAME
229 $ OLDCHGSOCKNAME=$CHGSOCKNAME
222 $ mkdir chgsock
230 $ mkdir chgsock
223 $ CHGSOCKNAME=`pwd`/chgsock/server
231 $ CHGSOCKNAME=`pwd`/chgsock/server
224
232
225 warm up server:
233 warm up server:
226
234
227 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
235 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
228 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
236 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
229
237
230 new server should be started if extension modified:
238 new server should be started if extension modified:
231
239
232 $ sleep 1
240 $ sleep 1
233 $ touch dummyext.py
241 $ touch dummyext.py
234 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
242 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
235 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
243 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
236 chg: debug: * instruction: reconnect (glob)
244 chg: debug: * instruction: reconnect (glob)
237 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
245 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
238
246
239 old server will shut down, while new server should still be reachable:
247 old server will shut down, while new server should still be reachable:
240
248
241 $ sleep 2
249 $ sleep 2
242 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
250 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
243
251
244 socket file should never be unlinked by old server:
252 socket file should never be unlinked by old server:
245 (simulates unowned socket by updating mtime, which makes sure server exits
253 (simulates unowned socket by updating mtime, which makes sure server exits
246 at polling cycle)
254 at polling cycle)
247
255
248 $ ls chgsock/server-*
256 $ ls chgsock/server-*
249 chgsock/server-* (glob)
257 chgsock/server-* (glob)
250 $ touch chgsock/server-*
258 $ touch chgsock/server-*
251 $ sleep 2
259 $ sleep 2
252 $ ls chgsock/server-*
260 $ ls chgsock/server-*
253 chgsock/server-* (glob)
261 chgsock/server-* (glob)
254
262
255 since no server is reachable from socket file, new server should be started:
263 since no server is reachable from socket file, new server should be started:
256 (this test makes sure that old server shut down automatically)
264 (this test makes sure that old server shut down automatically)
257
265
258 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
266 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
259 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
267 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
260
268
261 shut down servers and restore environment:
269 shut down servers and restore environment:
262
270
263 $ rm -R chgsock
271 $ rm -R chgsock
264 $ sleep 2
272 $ sleep 2
265 $ CHGSOCKNAME=$OLDCHGSOCKNAME
273 $ CHGSOCKNAME=$OLDCHGSOCKNAME
266 $ cd ..
274 $ cd ..
267
275
268 check that server events are recorded:
276 check that server events are recorded:
269
277
270 $ ls log
278 $ ls log
271 server.log
279 server.log
272 server.log.1
280 server.log.1
273
281
274 print only the last 10 lines, since we aren't sure how many records are
282 print only the last 10 lines, since we aren't sure how many records are
275 preserved (since setprocname isn't available on py3 and pure version,
283 preserved (since setprocname isn't available on py3 and pure version,
276 the 10th-most-recent line is different when using py3):
284 the 10th-most-recent line is different when using py3):
277
285
278 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
286 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
279 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
287 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
280 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
288 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
281 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
289 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
282 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
290 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
283 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
291 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
284 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
292 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
285 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
293 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
286 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
294 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
287 YYYY/MM/DD HH:MM:SS (PID)> validate: []
295 YYYY/MM/DD HH:MM:SS (PID)> validate: []
288 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
296 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
289 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
297 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
290
298
291 global data mutated by schems
299 global data mutated by schems
292 -----------------------------
300 -----------------------------
293
301
294 $ hg init schemes
302 $ hg init schemes
295 $ cd schemes
303 $ cd schemes
296
304
297 initial state
305 initial state
298
306
299 $ cat > .hg/hgrc <<'EOF'
307 $ cat > .hg/hgrc <<'EOF'
300 > [extensions]
308 > [extensions]
301 > schemes =
309 > schemes =
302 > [schemes]
310 > [schemes]
303 > foo = https://foo.example.org/
311 > foo = https://foo.example.org/
304 > EOF
312 > EOF
305 $ hg debugexpandscheme foo://expanded
313 $ hg debugexpandscheme foo://expanded
306 https://foo.example.org/expanded
314 https://foo.example.org/expanded
307 $ hg debugexpandscheme bar://unexpanded
315 $ hg debugexpandscheme bar://unexpanded
308 bar://unexpanded
316 bar://unexpanded
309
317
310 add bar
318 add bar
311
319
312 $ cat > .hg/hgrc <<'EOF'
320 $ cat > .hg/hgrc <<'EOF'
313 > [extensions]
321 > [extensions]
314 > schemes =
322 > schemes =
315 > [schemes]
323 > [schemes]
316 > foo = https://foo.example.org/
324 > foo = https://foo.example.org/
317 > bar = https://bar.example.org/
325 > bar = https://bar.example.org/
318 > EOF
326 > EOF
319 $ hg debugexpandscheme foo://expanded
327 $ hg debugexpandscheme foo://expanded
320 https://foo.example.org/expanded
328 https://foo.example.org/expanded
321 $ hg debugexpandscheme bar://expanded
329 $ hg debugexpandscheme bar://expanded
322 https://bar.example.org/expanded
330 https://bar.example.org/expanded
323
331
324 remove foo
332 remove foo
325
333
326 $ cat > .hg/hgrc <<'EOF'
334 $ cat > .hg/hgrc <<'EOF'
327 > [extensions]
335 > [extensions]
328 > schemes =
336 > schemes =
329 > [schemes]
337 > [schemes]
330 > bar = https://bar.example.org/
338 > bar = https://bar.example.org/
331 > EOF
339 > EOF
332 $ hg debugexpandscheme foo://unexpanded
340 $ hg debugexpandscheme foo://unexpanded
333 foo://unexpanded
341 foo://unexpanded
334 $ hg debugexpandscheme bar://expanded
342 $ hg debugexpandscheme bar://expanded
335 https://bar.example.org/expanded
343 https://bar.example.org/expanded
336
344
337 $ cd ..
345 $ cd ..
338
346
339 repository cache
347 repository cache
340 ----------------
348 ----------------
341
349
342 $ rm log/server.log*
350 $ rm log/server.log*
343 $ cp $HGRCPATH.unconfigured $HGRCPATH
351 $ cp $HGRCPATH.unconfigured $HGRCPATH
344 $ cat <<'EOF' >> $HGRCPATH
352 $ cat <<'EOF' >> $HGRCPATH
345 > [cmdserver]
353 > [cmdserver]
346 > log = $TESTTMP/log/server.log
354 > log = $TESTTMP/log/server.log
347 > max-repo-cache = 1
355 > max-repo-cache = 1
348 > track-log = command, repocache
356 > track-log = command, repocache
349 > EOF
357 > EOF
350
358
351 isolate socket directory for stable result:
359 isolate socket directory for stable result:
352
360
353 $ OLDCHGSOCKNAME=$CHGSOCKNAME
361 $ OLDCHGSOCKNAME=$CHGSOCKNAME
354 $ mkdir chgsock
362 $ mkdir chgsock
355 $ CHGSOCKNAME=`pwd`/chgsock/server
363 $ CHGSOCKNAME=`pwd`/chgsock/server
356
364
357 create empty repo and cache it:
365 create empty repo and cache it:
358
366
359 $ hg init cached
367 $ hg init cached
360 $ hg id -R cached
368 $ hg id -R cached
361 000000000000 tip
369 000000000000 tip
362 $ sleep 1
370 $ sleep 1
363
371
364 modify repo (and cache will be invalidated):
372 modify repo (and cache will be invalidated):
365
373
366 $ touch cached/a
374 $ touch cached/a
367 $ hg ci -R cached -Am 'add a'
375 $ hg ci -R cached -Am 'add a'
368 adding a
376 adding a
369 $ sleep 1
377 $ sleep 1
370
378
371 read cached repo:
379 read cached repo:
372
380
373 $ hg log -R cached
381 $ hg log -R cached
374 changeset: 0:ac82d8b1f7c4
382 changeset: 0:ac82d8b1f7c4
375 tag: tip
383 tag: tip
376 user: test
384 user: test
377 date: Thu Jan 01 00:00:00 1970 +0000
385 date: Thu Jan 01 00:00:00 1970 +0000
378 summary: add a
386 summary: add a
379
387
380 $ sleep 1
388 $ sleep 1
381
389
382 discard cached from LRU cache:
390 discard cached from LRU cache:
383
391
384 $ hg clone cached cached2
392 $ hg clone cached cached2
385 updating to branch default
393 updating to branch default
386 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 $ hg id -R cached2
395 $ hg id -R cached2
388 ac82d8b1f7c4 tip
396 ac82d8b1f7c4 tip
389 $ sleep 1
397 $ sleep 1
390
398
391 read uncached repo:
399 read uncached repo:
392
400
393 $ hg log -R cached
401 $ hg log -R cached
394 changeset: 0:ac82d8b1f7c4
402 changeset: 0:ac82d8b1f7c4
395 tag: tip
403 tag: tip
396 user: test
404 user: test
397 date: Thu Jan 01 00:00:00 1970 +0000
405 date: Thu Jan 01 00:00:00 1970 +0000
398 summary: add a
406 summary: add a
399
407
400 $ sleep 1
408 $ sleep 1
401
409
402 shut down servers and restore environment:
410 shut down servers and restore environment:
403
411
404 $ rm -R chgsock
412 $ rm -R chgsock
405 $ sleep 2
413 $ sleep 2
406 $ CHGSOCKNAME=$OLDCHGSOCKNAME
414 $ CHGSOCKNAME=$OLDCHGSOCKNAME
407
415
408 check server log:
416 check server log:
409
417
410 $ cat log/server.log | filterlog
418 $ cat log/server.log | filterlog
411 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
419 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
412 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
420 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
413 YYYY/MM/DD HH:MM:SS (PID)> init cached
421 YYYY/MM/DD HH:MM:SS (PID)> init cached
414 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
422 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
415 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
423 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
416 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
424 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
417 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
425 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
418 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
426 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
419 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
427 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
420 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
428 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
421 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
429 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
422 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
430 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
423 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
431 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
424 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
432 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
425 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
433 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
426 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
427
435
428 Test that chg works (sets to the user's actual LC_CTYPE) even when python
436 Test that chg works (sets to the user's actual LC_CTYPE) even when python
429 "coerces" the locale (py3.7+)
437 "coerces" the locale (py3.7+)
430
438
431 $ cat > $TESTTMP/debugenv.py <<EOF
439 $ cat > $TESTTMP/debugenv.py <<EOF
432 > from mercurial import encoding
440 > from mercurial import encoding
433 > from mercurial import registrar
441 > from mercurial import registrar
434 > cmdtable = {}
442 > cmdtable = {}
435 > command = registrar.command(cmdtable)
443 > command = registrar.command(cmdtable)
436 > @command(b'debugenv', [], b'', norepo=True)
444 > @command(b'debugenv', [], b'', norepo=True)
437 > def debugenv(ui):
445 > def debugenv(ui):
438 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
446 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
439 > v = encoding.environ.get(k)
447 > v = encoding.environ.get(k)
440 > if v is not None:
448 > if v is not None:
441 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
449 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
442 > EOF
450 > EOF
443 (hg keeps python's modified LC_CTYPE, chg doesn't)
451 (hg keeps python's modified LC_CTYPE, chg doesn't)
444 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
452 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
445 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
453 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
446 LC_CTYPE=C.UTF-8 (py37 !)
454 LC_CTYPE=C.UTF-8 (py37 !)
447 LC_CTYPE= (no-py37 !)
455 LC_CTYPE= (no-py37 !)
448 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
456 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
449 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
457 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
450 LC_CTYPE=
458 LC_CTYPE=
451 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
459 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
452 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
460 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
453 LC_CTYPE=unsupported_value
461 LC_CTYPE=unsupported_value
454 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
462 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
455 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
463 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
456 LC_CTYPE=
464 LC_CTYPE=
457 $ LANG= LC_ALL= LC_CTYPE= chg \
465 $ LANG= LC_ALL= LC_CTYPE= chg \
458 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
466 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
459 LC_ALL=
467 LC_ALL=
460 LC_CTYPE=
468 LC_CTYPE=
461 LANG=
469 LANG=
General Comments 0
You need to be logged in to leave comments. Login now