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