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