##// END OF EJS Templates
chg: allows default hg path to be overridden...
Jun Wu -
r28605:baa07320 default
parent child Browse files
Show More
@@ -1,49 +1,52 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 ifdef HGPATH
11 override CPPFLAGS += -DHGPATH=\"$(HGPATH)\"
12 endif
10
13
11 DESTDIR =
14 DESTDIR =
12 PREFIX = /usr/local
15 PREFIX = /usr/local
13 MANDIR = $(PREFIX)/share/man/man1
16 MANDIR = $(PREFIX)/share/man/man1
14
17
15 CHGSOCKDIR = /tmp/chg$(shell id -u)
18 CHGSOCKDIR = /tmp/chg$(shell id -u)
16 CHGSOCKNAME = $(CHGSOCKDIR)/server
19 CHGSOCKNAME = $(CHGSOCKDIR)/server
17
20
18 .PHONY: all
21 .PHONY: all
19 all: $(TARGET)
22 all: $(TARGET)
20
23
21 $(TARGET): $(OBJS)
24 $(TARGET): $(OBJS)
22 $(CC) $(LDFLAGS) -o $@ $(OBJS)
25 $(CC) $(LDFLAGS) -o $@ $(OBJS)
23
26
24 chg.o: hgclient.h util.h
27 chg.o: hgclient.h util.h
25 hgclient.o: hgclient.h util.h
28 hgclient.o: hgclient.h util.h
26 util.o: util.h
29 util.o: util.h
27
30
28 .PHONY: install
31 .PHONY: install
29 install: $(TARGET)
32 install: $(TARGET)
30 install -d $(DESTDIR)$(PREFIX)/bin
33 install -d $(DESTDIR)$(PREFIX)/bin
31 install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
34 install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
32 install -d $(DESTDIR)$(MANDIR)
35 install -d $(DESTDIR)$(MANDIR)
33 install -m 644 chg.1 $(DESTDIR)$(MANDIR)
36 install -m 644 chg.1 $(DESTDIR)$(MANDIR)
34
37
35 .PHONY: serve
38 .PHONY: serve
36 serve:
39 serve:
37 [ -d $(CHGSOCKDIR) ] || ( umask 077; mkdir $(CHGSOCKDIR) )
40 [ -d $(CHGSOCKDIR) ] || ( umask 077; mkdir $(CHGSOCKDIR) )
38 $(HG) serve --cwd / --cmdserver chgunix \
41 $(HG) serve --cwd / --cmdserver chgunix \
39 --address $(CHGSOCKNAME) \
42 --address $(CHGSOCKNAME) \
40 --config extensions.chgserver= \
43 --config extensions.chgserver= \
41 --config cmdserver.log=/dev/stderr
44 --config cmdserver.log=/dev/stderr
42
45
43 .PHONY: clean
46 .PHONY: clean
44 clean:
47 clean:
45 $(RM) $(OBJS)
48 $(RM) $(OBJS)
46
49
47 .PHONY: distclean
50 .PHONY: distclean
48 distclean:
51 distclean:
49 $(RM) $(OBJS) $(TARGET)
52 $(RM) $(OBJS) $(TARGET)
@@ -1,568 +1,572 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 size_t argsize;
36 size_t argsize;
37 const char **args;
37 const char **args;
38 int lockfd;
38 int lockfd;
39 };
39 };
40
40
41 static void initcmdserveropts(struct cmdserveropts *opts) {
41 static void initcmdserveropts(struct cmdserveropts *opts) {
42 memset(opts, 0, sizeof(struct cmdserveropts));
42 memset(opts, 0, sizeof(struct cmdserveropts));
43 opts->lockfd = -1;
43 opts->lockfd = -1;
44 }
44 }
45
45
46 static void freecmdserveropts(struct cmdserveropts *opts) {
46 static void freecmdserveropts(struct cmdserveropts *opts) {
47 free(opts->args);
47 free(opts->args);
48 opts->args = NULL;
48 opts->args = NULL;
49 opts->argsize = 0;
49 opts->argsize = 0;
50 }
50 }
51
51
52 /*
52 /*
53 * Test if an argument is a sensitive flag that should be passed to the server.
53 * Test if an argument is a sensitive flag that should be passed to the server.
54 * Return 0 if not, otherwise the number of arguments starting from the current
54 * Return 0 if not, otherwise the number of arguments starting from the current
55 * one that should be passed to the server.
55 * one that should be passed to the server.
56 */
56 */
57 static size_t testsensitiveflag(const char *arg)
57 static size_t testsensitiveflag(const char *arg)
58 {
58 {
59 static const struct {
59 static const struct {
60 const char *name;
60 const char *name;
61 size_t narg;
61 size_t narg;
62 } flags[] = {
62 } flags[] = {
63 {"--config", 1},
63 {"--config", 1},
64 {"--cwd", 1},
64 {"--cwd", 1},
65 {"--repo", 1},
65 {"--repo", 1},
66 {"--repository", 1},
66 {"--repository", 1},
67 {"--traceback", 0},
67 {"--traceback", 0},
68 {"-R", 1},
68 {"-R", 1},
69 };
69 };
70 size_t i;
70 size_t i;
71 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
71 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
72 size_t len = strlen(flags[i].name);
72 size_t len = strlen(flags[i].name);
73 size_t narg = flags[i].narg;
73 size_t narg = flags[i].narg;
74 if (memcmp(arg, flags[i].name, len) == 0) {
74 if (memcmp(arg, flags[i].name, len) == 0) {
75 if (arg[len] == '\0') { /* --flag (value) */
75 if (arg[len] == '\0') { /* --flag (value) */
76 return narg + 1;
76 return narg + 1;
77 } else if (arg[len] == '=' && narg > 0) { /* --flag=value */
77 } else if (arg[len] == '=' && narg > 0) { /* --flag=value */
78 return 1;
78 return 1;
79 } else if (flags[i].name[1] != '-') { /* short flag */
79 } else if (flags[i].name[1] != '-') { /* short flag */
80 return 1;
80 return 1;
81 }
81 }
82 }
82 }
83 }
83 }
84 return 0;
84 return 0;
85 }
85 }
86
86
87 /*
87 /*
88 * Parse argv[] and put sensitive flags to opts->args
88 * Parse argv[] and put sensitive flags to opts->args
89 */
89 */
90 static void setcmdserverargs(struct cmdserveropts *opts,
90 static void setcmdserverargs(struct cmdserveropts *opts,
91 int argc, const char *argv[])
91 int argc, const char *argv[])
92 {
92 {
93 size_t i, step;
93 size_t i, step;
94 opts->argsize = 0;
94 opts->argsize = 0;
95 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
95 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
96 if (!argv[i])
96 if (!argv[i])
97 continue; /* pass clang-analyse */
97 continue; /* pass clang-analyse */
98 if (strcmp(argv[i], "--") == 0)
98 if (strcmp(argv[i], "--") == 0)
99 break;
99 break;
100 size_t n = testsensitiveflag(argv[i]);
100 size_t n = testsensitiveflag(argv[i]);
101 if (n == 0 || i + n > (size_t)argc)
101 if (n == 0 || i + n > (size_t)argc)
102 continue;
102 continue;
103 opts->args = reallocx(opts->args,
103 opts->args = reallocx(opts->args,
104 (n + opts->argsize) * sizeof(char *));
104 (n + opts->argsize) * sizeof(char *));
105 memcpy(opts->args + opts->argsize, argv + i,
105 memcpy(opts->args + opts->argsize, argv + i,
106 sizeof(char *) * n);
106 sizeof(char *) * n);
107 opts->argsize += n;
107 opts->argsize += n;
108 step = n;
108 step = n;
109 }
109 }
110 }
110 }
111
111
112 static void preparesockdir(const char *sockdir)
112 static void preparesockdir(const char *sockdir)
113 {
113 {
114 int r;
114 int r;
115 r = mkdir(sockdir, 0700);
115 r = mkdir(sockdir, 0700);
116 if (r < 0 && errno != EEXIST)
116 if (r < 0 && errno != EEXIST)
117 abortmsg("cannot create sockdir %s (errno = %d)",
117 abortmsg("cannot create sockdir %s (errno = %d)",
118 sockdir, errno);
118 sockdir, errno);
119
119
120 struct stat st;
120 struct stat st;
121 r = lstat(sockdir, &st);
121 r = lstat(sockdir, &st);
122 if (r < 0)
122 if (r < 0)
123 abortmsg("cannot stat %s (errno = %d)", sockdir, errno);
123 abortmsg("cannot stat %s (errno = %d)", sockdir, errno);
124 if (!S_ISDIR(st.st_mode))
124 if (!S_ISDIR(st.st_mode))
125 abortmsg("cannot create sockdir %s (file exists)", sockdir);
125 abortmsg("cannot create sockdir %s (file exists)", sockdir);
126 if (st.st_uid != geteuid() || st.st_mode & 0077)
126 if (st.st_uid != geteuid() || st.st_mode & 0077)
127 abortmsg("insecure sockdir %s", sockdir);
127 abortmsg("insecure sockdir %s", sockdir);
128 }
128 }
129
129
130 static void setcmdserveropts(struct cmdserveropts *opts)
130 static void setcmdserveropts(struct cmdserveropts *opts)
131 {
131 {
132 int r;
132 int r;
133 char sockdir[UNIX_PATH_MAX];
133 char sockdir[UNIX_PATH_MAX];
134 const char *envsockname = getenv("CHGSOCKNAME");
134 const char *envsockname = getenv("CHGSOCKNAME");
135 if (!envsockname) {
135 if (!envsockname) {
136 /* by default, put socket file in secure directory
136 /* by default, put socket file in secure directory
137 * (permission of socket file may be ignored on some Unices) */
137 * (permission of socket file may be ignored on some Unices) */
138 const char *tmpdir = getenv("TMPDIR");
138 const char *tmpdir = getenv("TMPDIR");
139 if (!tmpdir)
139 if (!tmpdir)
140 tmpdir = "/tmp";
140 tmpdir = "/tmp";
141 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d",
141 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d",
142 tmpdir, geteuid());
142 tmpdir, geteuid());
143 if (r < 0 || (size_t)r >= sizeof(sockdir))
143 if (r < 0 || (size_t)r >= sizeof(sockdir))
144 abortmsg("too long TMPDIR (r = %d)", r);
144 abortmsg("too long TMPDIR (r = %d)", r);
145 preparesockdir(sockdir);
145 preparesockdir(sockdir);
146 }
146 }
147
147
148 const char *basename = (envsockname) ? envsockname : sockdir;
148 const char *basename = (envsockname) ? envsockname : sockdir;
149 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
149 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
150 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
150 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock";
151 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
151 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
152 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
152 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
153 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
153 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
154 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
154 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename);
155 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
155 if (r < 0 || (size_t)r >= sizeof(opts->lockfile))
156 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
156 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
157 }
157 }
158
158
159 /*
159 /*
160 * Acquire a file lock that indicates a client is trying to start and connect
160 * Acquire a file lock that indicates a client is trying to start and connect
161 * to a server, before executing a command. The lock is released upon exit or
161 * to a server, before executing a command. The lock is released upon exit or
162 * explicit unlock. Will block if the lock is held by another process.
162 * explicit unlock. Will block if the lock is held by another process.
163 */
163 */
164 static void lockcmdserver(struct cmdserveropts *opts)
164 static void lockcmdserver(struct cmdserveropts *opts)
165 {
165 {
166 if (opts->lockfd == -1) {
166 if (opts->lockfd == -1) {
167 opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
167 opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
168 if (opts->lockfd == -1)
168 if (opts->lockfd == -1)
169 abortmsg("cannot create lock file %s", opts->lockfile);
169 abortmsg("cannot create lock file %s", opts->lockfile);
170 }
170 }
171 int r = flock(opts->lockfd, LOCK_EX);
171 int r = flock(opts->lockfd, LOCK_EX);
172 if (r == -1)
172 if (r == -1)
173 abortmsg("cannot acquire lock");
173 abortmsg("cannot acquire lock");
174 }
174 }
175
175
176 /*
176 /*
177 * Release the file lock held by calling lockcmdserver. Will do nothing if
177 * Release the file lock held by calling lockcmdserver. Will do nothing if
178 * lockcmdserver is not called.
178 * lockcmdserver is not called.
179 */
179 */
180 static void unlockcmdserver(struct cmdserveropts *opts)
180 static void unlockcmdserver(struct cmdserveropts *opts)
181 {
181 {
182 if (opts->lockfd == -1)
182 if (opts->lockfd == -1)
183 return;
183 return;
184 flock(opts->lockfd, LOCK_UN);
184 flock(opts->lockfd, LOCK_UN);
185 close(opts->lockfd);
185 close(opts->lockfd);
186 opts->lockfd = -1;
186 opts->lockfd = -1;
187 }
187 }
188
188
189 static const char *gethgcmd(void)
189 static const char *gethgcmd(void)
190 {
190 {
191 static const char *hgcmd = NULL;
191 static const char *hgcmd = NULL;
192 if (!hgcmd) {
192 if (!hgcmd) {
193 hgcmd = getenv("CHGHG");
193 hgcmd = getenv("CHGHG");
194 if (!hgcmd || hgcmd[0] == '\0')
194 if (!hgcmd || hgcmd[0] == '\0')
195 hgcmd = getenv("HG");
195 hgcmd = getenv("HG");
196 if (!hgcmd || hgcmd[0] == '\0')
196 if (!hgcmd || hgcmd[0] == '\0')
197 #ifdef HGPATH
198 hgcmd = (HGPATH);
199 #else
197 hgcmd = "hg";
200 hgcmd = "hg";
201 #endif
198 }
202 }
199 return hgcmd;
203 return hgcmd;
200 }
204 }
201
205
202 static void execcmdserver(const struct cmdserveropts *opts)
206 static void execcmdserver(const struct cmdserveropts *opts)
203 {
207 {
204 const char *hgcmd = gethgcmd();
208 const char *hgcmd = gethgcmd();
205
209
206 const char *baseargv[] = {
210 const char *baseargv[] = {
207 hgcmd,
211 hgcmd,
208 "serve",
212 "serve",
209 "--cmdserver", "chgunix",
213 "--cmdserver", "chgunix",
210 "--address", opts->sockname,
214 "--address", opts->sockname,
211 "--daemon-postexec", "chdir:/",
215 "--daemon-postexec", "chdir:/",
212 "--config", "extensions.chgserver=",
216 "--config", "extensions.chgserver=",
213 };
217 };
214 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
218 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
215 size_t argsize = baseargvsize + opts->argsize + 1;
219 size_t argsize = baseargvsize + opts->argsize + 1;
216
220
217 const char **argv = mallocx(sizeof(char *) * argsize);
221 const char **argv = mallocx(sizeof(char *) * argsize);
218 memcpy(argv, baseargv, sizeof(baseargv));
222 memcpy(argv, baseargv, sizeof(baseargv));
219 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
223 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
220 argv[argsize - 1] = NULL;
224 argv[argsize - 1] = NULL;
221
225
222 if (putenv("CHGINTERNALMARK=") != 0)
226 if (putenv("CHGINTERNALMARK=") != 0)
223 abortmsg("failed to putenv (errno = %d)", errno);
227 abortmsg("failed to putenv (errno = %d)", errno);
224 if (execvp(hgcmd, (char **)argv) < 0)
228 if (execvp(hgcmd, (char **)argv) < 0)
225 abortmsg("failed to exec cmdserver (errno = %d)", errno);
229 abortmsg("failed to exec cmdserver (errno = %d)", errno);
226 free(argv);
230 free(argv);
227 }
231 }
228
232
229 /* Retry until we can connect to the server. Give up after some time. */
233 /* Retry until we can connect to the server. Give up after some time. */
230 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
234 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
231 {
235 {
232 static const struct timespec sleepreq = {0, 10 * 1000000};
236 static const struct timespec sleepreq = {0, 10 * 1000000};
233 int pst = 0;
237 int pst = 0;
234
238
235 for (unsigned int i = 0; i < 10 * 100; i++) {
239 for (unsigned int i = 0; i < 10 * 100; i++) {
236 hgclient_t *hgc = hgc_open(opts->sockname);
240 hgclient_t *hgc = hgc_open(opts->sockname);
237 if (hgc)
241 if (hgc)
238 return hgc;
242 return hgc;
239
243
240 if (pid > 0) {
244 if (pid > 0) {
241 /* collect zombie if child process fails to start */
245 /* collect zombie if child process fails to start */
242 int r = waitpid(pid, &pst, WNOHANG);
246 int r = waitpid(pid, &pst, WNOHANG);
243 if (r != 0)
247 if (r != 0)
244 goto cleanup;
248 goto cleanup;
245 }
249 }
246
250
247 nanosleep(&sleepreq, NULL);
251 nanosleep(&sleepreq, NULL);
248 }
252 }
249
253
250 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
254 abortmsg("timed out waiting for cmdserver %s", opts->sockname);
251 return NULL;
255 return NULL;
252
256
253 cleanup:
257 cleanup:
254 if (WIFEXITED(pst)) {
258 if (WIFEXITED(pst)) {
255 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
259 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
256 exit(WEXITSTATUS(pst));
260 exit(WEXITSTATUS(pst));
257 } else if (WIFSIGNALED(pst)) {
261 } else if (WIFSIGNALED(pst)) {
258 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
262 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
259 } else {
263 } else {
260 abortmsg("error white waiting cmdserver");
264 abortmsg("error white waiting cmdserver");
261 }
265 }
262 return NULL;
266 return NULL;
263 }
267 }
264
268
265 /* Connect to a cmdserver. Will start a new server on demand. */
269 /* Connect to a cmdserver. Will start a new server on demand. */
266 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
270 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
267 {
271 {
268 const char *sockname = opts->redirectsockname[0] ?
272 const char *sockname = opts->redirectsockname[0] ?
269 opts->redirectsockname : opts->sockname;
273 opts->redirectsockname : opts->sockname;
270 hgclient_t *hgc = hgc_open(sockname);
274 hgclient_t *hgc = hgc_open(sockname);
271 if (hgc)
275 if (hgc)
272 return hgc;
276 return hgc;
273
277
274 lockcmdserver(opts);
278 lockcmdserver(opts);
275 hgc = hgc_open(sockname);
279 hgc = hgc_open(sockname);
276 if (hgc) {
280 if (hgc) {
277 unlockcmdserver(opts);
281 unlockcmdserver(opts);
278 debugmsg("cmdserver is started by another process");
282 debugmsg("cmdserver is started by another process");
279 return hgc;
283 return hgc;
280 }
284 }
281
285
282 /* prevent us from being connected to an outdated server: we were
286 /* prevent us from being connected to an outdated server: we were
283 * told by a server to redirect to opts->redirectsockname and that
287 * told by a server to redirect to opts->redirectsockname and that
284 * address does not work. we do not want to connect to the server
288 * address does not work. we do not want to connect to the server
285 * again because it will probably tell us the same thing. */
289 * again because it will probably tell us the same thing. */
286 if (sockname == opts->redirectsockname)
290 if (sockname == opts->redirectsockname)
287 unlink(opts->sockname);
291 unlink(opts->sockname);
288
292
289 debugmsg("start cmdserver at %s", opts->sockname);
293 debugmsg("start cmdserver at %s", opts->sockname);
290
294
291 pid_t pid = fork();
295 pid_t pid = fork();
292 if (pid < 0)
296 if (pid < 0)
293 abortmsg("failed to fork cmdserver process");
297 abortmsg("failed to fork cmdserver process");
294 if (pid == 0) {
298 if (pid == 0) {
295 /* do not leak lockfd to hg */
299 /* do not leak lockfd to hg */
296 close(opts->lockfd);
300 close(opts->lockfd);
297 execcmdserver(opts);
301 execcmdserver(opts);
298 } else {
302 } else {
299 hgc = retryconnectcmdserver(opts, pid);
303 hgc = retryconnectcmdserver(opts, pid);
300 }
304 }
301
305
302 unlockcmdserver(opts);
306 unlockcmdserver(opts);
303 return hgc;
307 return hgc;
304 }
308 }
305
309
306 static void killcmdserver(const struct cmdserveropts *opts)
310 static void killcmdserver(const struct cmdserveropts *opts)
307 {
311 {
308 /* resolve config hash */
312 /* resolve config hash */
309 char *resolvedpath = realpath(opts->sockname, NULL);
313 char *resolvedpath = realpath(opts->sockname, NULL);
310 if (resolvedpath) {
314 if (resolvedpath) {
311 unlink(resolvedpath);
315 unlink(resolvedpath);
312 free(resolvedpath);
316 free(resolvedpath);
313 }
317 }
314 }
318 }
315
319
316 static pid_t peerpid = 0;
320 static pid_t peerpid = 0;
317
321
318 static void forwardsignal(int sig)
322 static void forwardsignal(int sig)
319 {
323 {
320 assert(peerpid > 0);
324 assert(peerpid > 0);
321 if (kill(peerpid, sig) < 0)
325 if (kill(peerpid, sig) < 0)
322 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
326 abortmsg("cannot kill %d (errno = %d)", peerpid, errno);
323 debugmsg("forward signal %d", sig);
327 debugmsg("forward signal %d", sig);
324 }
328 }
325
329
326 static void handlestopsignal(int sig)
330 static void handlestopsignal(int sig)
327 {
331 {
328 sigset_t unblockset, oldset;
332 sigset_t unblockset, oldset;
329 struct sigaction sa, oldsa;
333 struct sigaction sa, oldsa;
330 if (sigemptyset(&unblockset) < 0)
334 if (sigemptyset(&unblockset) < 0)
331 goto error;
335 goto error;
332 if (sigaddset(&unblockset, sig) < 0)
336 if (sigaddset(&unblockset, sig) < 0)
333 goto error;
337 goto error;
334 memset(&sa, 0, sizeof(sa));
338 memset(&sa, 0, sizeof(sa));
335 sa.sa_handler = SIG_DFL;
339 sa.sa_handler = SIG_DFL;
336 sa.sa_flags = SA_RESTART;
340 sa.sa_flags = SA_RESTART;
337 if (sigemptyset(&sa.sa_mask) < 0)
341 if (sigemptyset(&sa.sa_mask) < 0)
338 goto error;
342 goto error;
339
343
340 forwardsignal(sig);
344 forwardsignal(sig);
341 if (raise(sig) < 0) /* resend to self */
345 if (raise(sig) < 0) /* resend to self */
342 goto error;
346 goto error;
343 if (sigaction(sig, &sa, &oldsa) < 0)
347 if (sigaction(sig, &sa, &oldsa) < 0)
344 goto error;
348 goto error;
345 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
349 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0)
346 goto error;
350 goto error;
347 /* resent signal will be handled before sigprocmask() returns */
351 /* resent signal will be handled before sigprocmask() returns */
348 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
352 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
349 goto error;
353 goto error;
350 if (sigaction(sig, &oldsa, NULL) < 0)
354 if (sigaction(sig, &oldsa, NULL) < 0)
351 goto error;
355 goto error;
352 return;
356 return;
353
357
354 error:
358 error:
355 abortmsg("failed to handle stop signal (errno = %d)", errno);
359 abortmsg("failed to handle stop signal (errno = %d)", errno);
356 }
360 }
357
361
358 static void setupsignalhandler(pid_t pid)
362 static void setupsignalhandler(pid_t pid)
359 {
363 {
360 if (pid <= 0)
364 if (pid <= 0)
361 return;
365 return;
362 peerpid = pid;
366 peerpid = pid;
363
367
364 struct sigaction sa;
368 struct sigaction sa;
365 memset(&sa, 0, sizeof(sa));
369 memset(&sa, 0, sizeof(sa));
366 sa.sa_handler = forwardsignal;
370 sa.sa_handler = forwardsignal;
367 sa.sa_flags = SA_RESTART;
371 sa.sa_flags = SA_RESTART;
368 if (sigemptyset(&sa.sa_mask) < 0)
372 if (sigemptyset(&sa.sa_mask) < 0)
369 goto error;
373 goto error;
370
374
371 if (sigaction(SIGHUP, &sa, NULL) < 0)
375 if (sigaction(SIGHUP, &sa, NULL) < 0)
372 goto error;
376 goto error;
373 if (sigaction(SIGINT, &sa, NULL) < 0)
377 if (sigaction(SIGINT, &sa, NULL) < 0)
374 goto error;
378 goto error;
375
379
376 /* terminate frontend by double SIGTERM in case of server freeze */
380 /* terminate frontend by double SIGTERM in case of server freeze */
377 sa.sa_flags |= SA_RESETHAND;
381 sa.sa_flags |= SA_RESETHAND;
378 if (sigaction(SIGTERM, &sa, NULL) < 0)
382 if (sigaction(SIGTERM, &sa, NULL) < 0)
379 goto error;
383 goto error;
380
384
381 /* propagate job control requests to worker */
385 /* propagate job control requests to worker */
382 sa.sa_handler = forwardsignal;
386 sa.sa_handler = forwardsignal;
383 sa.sa_flags = SA_RESTART;
387 sa.sa_flags = SA_RESTART;
384 if (sigaction(SIGCONT, &sa, NULL) < 0)
388 if (sigaction(SIGCONT, &sa, NULL) < 0)
385 goto error;
389 goto error;
386 sa.sa_handler = handlestopsignal;
390 sa.sa_handler = handlestopsignal;
387 sa.sa_flags = SA_RESTART;
391 sa.sa_flags = SA_RESTART;
388 if (sigaction(SIGTSTP, &sa, NULL) < 0)
392 if (sigaction(SIGTSTP, &sa, NULL) < 0)
389 goto error;
393 goto error;
390
394
391 return;
395 return;
392
396
393 error:
397 error:
394 abortmsg("failed to set up signal handlers (errno = %d)", errno);
398 abortmsg("failed to set up signal handlers (errno = %d)", errno);
395 }
399 }
396
400
397 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
401 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */
398 static void setuppager(hgclient_t *hgc, const char *const args[],
402 static void setuppager(hgclient_t *hgc, const char *const args[],
399 size_t argsize)
403 size_t argsize)
400 {
404 {
401 const char *pagercmd = hgc_getpager(hgc, args, argsize);
405 const char *pagercmd = hgc_getpager(hgc, args, argsize);
402 if (!pagercmd)
406 if (!pagercmd)
403 return;
407 return;
404
408
405 int pipefds[2];
409 int pipefds[2];
406 if (pipe(pipefds) < 0)
410 if (pipe(pipefds) < 0)
407 return;
411 return;
408 pid_t pid = fork();
412 pid_t pid = fork();
409 if (pid < 0)
413 if (pid < 0)
410 goto error;
414 goto error;
411 if (pid == 0) {
415 if (pid == 0) {
412 close(pipefds[0]);
416 close(pipefds[0]);
413 if (dup2(pipefds[1], fileno(stdout)) < 0)
417 if (dup2(pipefds[1], fileno(stdout)) < 0)
414 goto error;
418 goto error;
415 if (isatty(fileno(stderr))) {
419 if (isatty(fileno(stderr))) {
416 if (dup2(pipefds[1], fileno(stderr)) < 0)
420 if (dup2(pipefds[1], fileno(stderr)) < 0)
417 goto error;
421 goto error;
418 }
422 }
419 close(pipefds[1]);
423 close(pipefds[1]);
420 hgc_attachio(hgc); /* reattach to pager */
424 hgc_attachio(hgc); /* reattach to pager */
421 return;
425 return;
422 } else {
426 } else {
423 dup2(pipefds[0], fileno(stdin));
427 dup2(pipefds[0], fileno(stdin));
424 close(pipefds[0]);
428 close(pipefds[0]);
425 close(pipefds[1]);
429 close(pipefds[1]);
426
430
427 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
431 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL);
428 if (r < 0) {
432 if (r < 0) {
429 abortmsg("cannot start pager '%s' (errno = %d)",
433 abortmsg("cannot start pager '%s' (errno = %d)",
430 pagercmd, errno);
434 pagercmd, errno);
431 }
435 }
432 return;
436 return;
433 }
437 }
434
438
435 error:
439 error:
436 close(pipefds[0]);
440 close(pipefds[0]);
437 close(pipefds[1]);
441 close(pipefds[1]);
438 abortmsg("failed to prepare pager (errno = %d)", errno);
442 abortmsg("failed to prepare pager (errno = %d)", errno);
439 }
443 }
440
444
441 /* Run instructions sent from the server like unlink and set redirect path
445 /* Run instructions sent from the server like unlink and set redirect path
442 * Return 1 if reconnect is needed, otherwise 0 */
446 * Return 1 if reconnect is needed, otherwise 0 */
443 static int runinstructions(struct cmdserveropts *opts, const char **insts)
447 static int runinstructions(struct cmdserveropts *opts, const char **insts)
444 {
448 {
445 int needreconnect = 0;
449 int needreconnect = 0;
446 if (!insts)
450 if (!insts)
447 return needreconnect;
451 return needreconnect;
448
452
449 assert(insts);
453 assert(insts);
450 opts->redirectsockname[0] = '\0';
454 opts->redirectsockname[0] = '\0';
451 const char **pinst;
455 const char **pinst;
452 for (pinst = insts; *pinst; pinst++) {
456 for (pinst = insts; *pinst; pinst++) {
453 debugmsg("instruction: %s", *pinst);
457 debugmsg("instruction: %s", *pinst);
454 if (strncmp(*pinst, "unlink ", 7) == 0) {
458 if (strncmp(*pinst, "unlink ", 7) == 0) {
455 unlink(*pinst + 7);
459 unlink(*pinst + 7);
456 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
460 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
457 int r = snprintf(opts->redirectsockname,
461 int r = snprintf(opts->redirectsockname,
458 sizeof(opts->redirectsockname),
462 sizeof(opts->redirectsockname),
459 "%s", *pinst + 9);
463 "%s", *pinst + 9);
460 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
464 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
461 abortmsg("redirect path is too long (%d)", r);
465 abortmsg("redirect path is too long (%d)", r);
462 needreconnect = 1;
466 needreconnect = 1;
463 } else if (strncmp(*pinst, "exit ", 5) == 0) {
467 } else if (strncmp(*pinst, "exit ", 5) == 0) {
464 int n = 0;
468 int n = 0;
465 if (sscanf(*pinst + 5, "%d", &n) != 1)
469 if (sscanf(*pinst + 5, "%d", &n) != 1)
466 abortmsg("cannot read the exit code");
470 abortmsg("cannot read the exit code");
467 exit(n);
471 exit(n);
468 } else if (strcmp(*pinst, "reconnect") == 0) {
472 } else if (strcmp(*pinst, "reconnect") == 0) {
469 needreconnect = 1;
473 needreconnect = 1;
470 } else {
474 } else {
471 abortmsg("unknown instruction: %s", *pinst);
475 abortmsg("unknown instruction: %s", *pinst);
472 }
476 }
473 }
477 }
474 return needreconnect;
478 return needreconnect;
475 }
479 }
476
480
477 /*
481 /*
478 * Test whether the command is unsupported or not. This is not designed to
482 * Test whether the command is unsupported or not. This is not designed to
479 * cover all cases. But it's fast, does not depend on the server and does
483 * cover all cases. But it's fast, does not depend on the server and does
480 * not return false positives.
484 * not return false positives.
481 */
485 */
482 static int isunsupported(int argc, const char *argv[])
486 static int isunsupported(int argc, const char *argv[])
483 {
487 {
484 enum {
488 enum {
485 SERVE = 1,
489 SERVE = 1,
486 DAEMON = 2,
490 DAEMON = 2,
487 SERVEDAEMON = SERVE | DAEMON,
491 SERVEDAEMON = SERVE | DAEMON,
488 TIME = 4,
492 TIME = 4,
489 };
493 };
490 unsigned int state = 0;
494 unsigned int state = 0;
491 int i;
495 int i;
492 for (i = 0; i < argc; ++i) {
496 for (i = 0; i < argc; ++i) {
493 if (strcmp(argv[i], "--") == 0)
497 if (strcmp(argv[i], "--") == 0)
494 break;
498 break;
495 if (i == 0 && strcmp("serve", argv[i]) == 0)
499 if (i == 0 && strcmp("serve", argv[i]) == 0)
496 state |= SERVE;
500 state |= SERVE;
497 else if (strcmp("-d", argv[i]) == 0 ||
501 else if (strcmp("-d", argv[i]) == 0 ||
498 strcmp("--daemon", argv[i]) == 0)
502 strcmp("--daemon", argv[i]) == 0)
499 state |= DAEMON;
503 state |= DAEMON;
500 else if (strcmp("--time", argv[i]) == 0)
504 else if (strcmp("--time", argv[i]) == 0)
501 state |= TIME;
505 state |= TIME;
502 }
506 }
503 return (state & TIME) == TIME ||
507 return (state & TIME) == TIME ||
504 (state & SERVEDAEMON) == SERVEDAEMON;
508 (state & SERVEDAEMON) == SERVEDAEMON;
505 }
509 }
506
510
507 static void execoriginalhg(const char *argv[])
511 static void execoriginalhg(const char *argv[])
508 {
512 {
509 debugmsg("execute original hg");
513 debugmsg("execute original hg");
510 if (execvp(gethgcmd(), (char **)argv) < 0)
514 if (execvp(gethgcmd(), (char **)argv) < 0)
511 abortmsg("failed to exec original hg (errno = %d)", errno);
515 abortmsg("failed to exec original hg (errno = %d)", errno);
512 }
516 }
513
517
514 int main(int argc, const char *argv[], const char *envp[])
518 int main(int argc, const char *argv[], const char *envp[])
515 {
519 {
516 if (getenv("CHGDEBUG"))
520 if (getenv("CHGDEBUG"))
517 enabledebugmsg();
521 enabledebugmsg();
518
522
519 if (getenv("CHGINTERNALMARK"))
523 if (getenv("CHGINTERNALMARK"))
520 abortmsg("chg started by chg detected.\n"
524 abortmsg("chg started by chg detected.\n"
521 "Please make sure ${HG:-hg} is not a symlink or "
525 "Please make sure ${HG:-hg} is not a symlink or "
522 "wrapper to chg. Alternatively, set $CHGHG to the "
526 "wrapper to chg. Alternatively, set $CHGHG to the "
523 "path of real hg.");
527 "path of real hg.");
524
528
525 if (isunsupported(argc - 1, argv + 1))
529 if (isunsupported(argc - 1, argv + 1))
526 execoriginalhg(argv);
530 execoriginalhg(argv);
527
531
528 struct cmdserveropts opts;
532 struct cmdserveropts opts;
529 initcmdserveropts(&opts);
533 initcmdserveropts(&opts);
530 setcmdserveropts(&opts);
534 setcmdserveropts(&opts);
531 setcmdserverargs(&opts, argc, argv);
535 setcmdserverargs(&opts, argc, argv);
532
536
533 if (argc == 2) {
537 if (argc == 2) {
534 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
538 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
535 killcmdserver(&opts);
539 killcmdserver(&opts);
536 return 0;
540 return 0;
537 }
541 }
538 }
542 }
539
543
540 hgclient_t *hgc;
544 hgclient_t *hgc;
541 size_t retry = 0;
545 size_t retry = 0;
542 while (1) {
546 while (1) {
543 hgc = connectcmdserver(&opts);
547 hgc = connectcmdserver(&opts);
544 if (!hgc)
548 if (!hgc)
545 abortmsg("cannot open hg client");
549 abortmsg("cannot open hg client");
546 hgc_setenv(hgc, envp);
550 hgc_setenv(hgc, envp);
547 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
551 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
548 int needreconnect = runinstructions(&opts, insts);
552 int needreconnect = runinstructions(&opts, insts);
549 free(insts);
553 free(insts);
550 if (!needreconnect)
554 if (!needreconnect)
551 break;
555 break;
552 hgc_close(hgc);
556 hgc_close(hgc);
553 if (++retry > 10)
557 if (++retry > 10)
554 abortmsg("too many redirections.\n"
558 abortmsg("too many redirections.\n"
555 "Please make sure %s is not a wrapper which "
559 "Please make sure %s is not a wrapper which "
556 "changes sensitive environment variables "
560 "changes sensitive environment variables "
557 "before executing hg. If you have to use a "
561 "before executing hg. If you have to use a "
558 "wrapper, wrap chg instead of hg.",
562 "wrapper, wrap chg instead of hg.",
559 gethgcmd());
563 gethgcmd());
560 }
564 }
561
565
562 setupsignalhandler(hgc_peerpid(hgc));
566 setupsignalhandler(hgc_peerpid(hgc));
563 setuppager(hgc, argv + 1, argc - 1);
567 setuppager(hgc, argv + 1, argc - 1);
564 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
568 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
565 hgc_close(hgc);
569 hgc_close(hgc);
566 freecmdserveropts(&opts);
570 freecmdserveropts(&opts);
567 return exitcode;
571 return exitcode;
568 }
572 }
General Comments 0
You need to be logged in to leave comments. Login now