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