Mercurial > hg
annotate contrib/chg/chg.c @ 28086:65d24ca35496
chg: forward job control signals to worker process (issue5051)
This is necessary to suspend/resume long pulls, interactive curses session,
etc.
The implementation is based on emacsclient, but our version doesn't test if
chg process is foreground or not before propagating SIGCONT. This is because
chg isn't always an interactive session. If we copy the SIGTTIN/SIGTTOU
emulation from emacsclient, non-interactive session can't be moved to a
background job.
$ chg pull
^Z
suspended
$ bg %1
[1] continued
[1] suspended (tty input) # wrong
https://github.com/emacs-mirror/emacs/blob/0e96320/lib-src/emacsclient.c#L1094
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Tue, 19 Jan 2016 22:31:59 +0900 |
parents | c0d1bf1b26b7 |
children | 66f6dad20c19 |
rev | line source |
---|---|
28060 | 1 /* |
2 * A fast client for Mercurial command server | |
3 * | |
4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org> | |
5 * | |
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. | |
8 */ | |
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/stat.h> | |
18 #include <sys/types.h> | |
19 #include <sys/un.h> | |
20 #include <sys/wait.h> | |
21 #include <time.h> | |
22 #include <unistd.h> | |
23 | |
24 #include "hgclient.h" | |
25 #include "util.h" | |
26 | |
27 #ifndef UNIX_PATH_MAX | |
28 #define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)NULL)->sun_path)) | |
29 #endif | |
30 | |
31 struct cmdserveropts { | |
32 char sockname[UNIX_PATH_MAX]; | |
33 char lockfile[UNIX_PATH_MAX]; | |
34 char pidfile[UNIX_PATH_MAX]; | |
35 }; | |
36 | |
37 static void preparesockdir(const char *sockdir) | |
38 { | |
39 int r; | |
40 r = mkdir(sockdir, 0700); | |
41 if (r < 0 && errno != EEXIST) | |
42 abortmsg("cannot create sockdir %s (errno = %d)", | |
43 sockdir, errno); | |
44 | |
45 struct stat st; | |
46 r = lstat(sockdir, &st); | |
47 if (r < 0) | |
48 abortmsg("cannot stat %s (errno = %d)", sockdir, errno); | |
49 if (!S_ISDIR(st.st_mode)) | |
50 abortmsg("cannot create sockdir %s (file exists)", sockdir); | |
51 if (st.st_uid != geteuid() || st.st_mode & 0077) | |
52 abortmsg("insecure sockdir %s", sockdir); | |
53 } | |
54 | |
55 static void setcmdserveropts(struct cmdserveropts *opts) | |
56 { | |
57 int r; | |
58 char sockdir[UNIX_PATH_MAX]; | |
59 const char *envsockname = getenv("CHGSOCKNAME"); | |
60 if (!envsockname) { | |
61 /* by default, put socket file in secure directory | |
62 * (permission of socket file may be ignored on some Unices) */ | |
63 const char *tmpdir = getenv("TMPDIR"); | |
64 if (!tmpdir) | |
65 tmpdir = "/tmp"; | |
66 r = snprintf(sockdir, sizeof(sockdir), "%s/chg%d", | |
67 tmpdir, geteuid()); | |
68 if (r < 0 || (size_t)r >= sizeof(sockdir)) | |
69 abortmsg("too long TMPDIR (r = %d)", r); | |
70 preparesockdir(sockdir); | |
71 } | |
72 | |
73 const char *basename = (envsockname) ? envsockname : sockdir; | |
74 const char *sockfmt = (envsockname) ? "%s" : "%s/server"; | |
75 const char *lockfmt = (envsockname) ? "%s.lock" : "%s/lock"; | |
76 const char *pidfmt = (envsockname) ? "%s.pid" : "%s/pid"; | |
77 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename); | |
78 if (r < 0 || (size_t)r >= sizeof(opts->sockname)) | |
79 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); | |
80 r = snprintf(opts->lockfile, sizeof(opts->lockfile), lockfmt, basename); | |
81 if (r < 0 || (size_t)r >= sizeof(opts->lockfile)) | |
82 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); | |
83 r = snprintf(opts->pidfile, sizeof(opts->pidfile), pidfmt, basename); | |
84 if (r < 0 || (size_t)r >= sizeof(opts->pidfile)) | |
85 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); | |
86 } | |
87 | |
88 /* | |
89 * Make lock file that indicates cmdserver process is about to start. Created | |
90 * lock file will be deleted by server. (0: success, -1: lock exists) | |
91 */ | |
92 static int lockcmdserver(const struct cmdserveropts *opts) | |
93 { | |
94 int r; | |
95 char info[32]; | |
96 r = snprintf(info, sizeof(info), "%d", getpid()); | |
97 if (r < 0 || (size_t)r >= sizeof(info)) | |
98 abortmsg("failed to format lock info"); | |
99 r = symlink(info, opts->lockfile); | |
100 if (r < 0 && errno != EEXIST) | |
101 abortmsg("failed to make lock %s (errno = %d)", | |
102 opts->lockfile, errno); | |
103 return r; | |
104 } | |
105 | |
106 static void execcmdserver(const struct cmdserveropts *opts) | |
107 { | |
108 const char *hgcmd = getenv("CHGHG"); | |
109 if (!hgcmd || hgcmd[0] == '\0') | |
110 hgcmd = getenv("HG"); | |
111 if (!hgcmd || hgcmd[0] == '\0') | |
112 hgcmd = "hg"; | |
113 | |
114 const char *argv[] = { | |
115 hgcmd, | |
116 "serve", | |
117 "--cwd", "/", | |
118 "--cmdserver", "chgunix", | |
119 "--address", opts->sockname, | |
120 "--daemon-pipefds", opts->lockfile, | |
121 "--pid-file", opts->pidfile, | |
122 "--config", "extensions.chgserver=", | |
123 /* wrap root ui so that it can be disabled/enabled by config */ | |
124 "--config", "progress.assume-tty=1", | |
125 NULL, | |
126 }; | |
127 if (execvp(hgcmd, (char **)argv) < 0) | |
128 abortmsg("failed to exec cmdserver (errno = %d)", errno); | |
129 } | |
130 | |
131 /* | |
132 * Sleep until lock file is deleted, i.e. cmdserver process starts listening. | |
133 * If pid is given, it also checks if the child process fails to start. | |
134 */ | |
135 static void waitcmdserver(const struct cmdserveropts *opts, pid_t pid) | |
136 { | |
137 static const struct timespec sleepreq = {0, 10 * 1000000}; | |
138 int pst = 0; | |
139 | |
140 for (unsigned int i = 0; i < 10 * 100; i++) { | |
141 int r; | |
142 struct stat lst; | |
143 | |
144 r = lstat(opts->lockfile, &lst); | |
145 if (r < 0 && errno == ENOENT) | |
146 return; /* lock file deleted by server */ | |
147 if (r < 0) | |
148 goto cleanup; | |
149 | |
150 if (pid > 0) { | |
151 /* collect zombie if child process fails to start */ | |
152 r = waitpid(pid, &pst, WNOHANG); | |
153 if (r != 0) | |
154 goto cleanup; | |
155 } | |
156 | |
157 nanosleep(&sleepreq, NULL); | |
158 } | |
159 | |
160 abortmsg("timed out waiting for cmdserver %s", opts->lockfile); | |
161 return; | |
162 | |
163 cleanup: | |
164 if (pid > 0) | |
165 /* lockfile should be made by this process */ | |
166 unlink(opts->lockfile); | |
167 if (WIFEXITED(pst)) { | |
168 abortmsg("cmdserver exited with status %d", WEXITSTATUS(pst)); | |
169 } else if (WIFSIGNALED(pst)) { | |
170 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst)); | |
171 } else { | |
172 abortmsg("error white waiting cmdserver"); | |
173 } | |
174 } | |
175 | |
176 /* Spawn new background cmdserver */ | |
177 static void startcmdserver(const struct cmdserveropts *opts) | |
178 { | |
179 debugmsg("start cmdserver at %s", opts->sockname); | |
180 | |
181 if (lockcmdserver(opts) < 0) { | |
182 debugmsg("lock file exists, waiting..."); | |
183 waitcmdserver(opts, 0); | |
184 return; | |
185 } | |
186 | |
187 /* remove dead cmdserver socket if any */ | |
188 unlink(opts->sockname); | |
189 | |
190 pid_t pid = fork(); | |
191 if (pid < 0) | |
192 abortmsg("failed to fork cmdserver process"); | |
193 if (pid == 0) { | |
194 /* bypass uisetup() of pager extension */ | |
195 int nullfd = open("/dev/null", O_WRONLY); | |
196 if (nullfd >= 0) { | |
197 dup2(nullfd, fileno(stdout)); | |
198 close(nullfd); | |
199 } | |
200 execcmdserver(opts); | |
201 } else { | |
202 waitcmdserver(opts, pid); | |
203 } | |
204 } | |
205 | |
206 static void killcmdserver(const struct cmdserveropts *opts, int sig) | |
207 { | |
208 FILE *fp = fopen(opts->pidfile, "r"); | |
209 if (!fp) | |
210 abortmsg("cannot open %s (errno = %d)", opts->pidfile, errno); | |
211 int pid = 0; | |
212 int n = fscanf(fp, "%d", &pid); | |
213 fclose(fp); | |
214 if (n != 1 || pid <= 0) | |
215 abortmsg("cannot read pid from %s", opts->pidfile); | |
216 | |
217 if (kill((pid_t)pid, sig) < 0) { | |
218 if (errno == ESRCH) | |
219 return; | |
220 abortmsg("cannot kill %d (errno = %d)", pid, errno); | |
221 } | |
222 } | |
223 | |
224 static pid_t peerpid = 0; | |
225 | |
226 static void forwardsignal(int sig) | |
227 { | |
228 assert(peerpid > 0); | |
229 if (kill(peerpid, sig) < 0) | |
230 abortmsg("cannot kill %d (errno = %d)", peerpid, errno); | |
231 debugmsg("forward signal %d", sig); | |
232 } | |
233 | |
28086
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
234 static void handlestopsignal(int sig) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
235 { |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
236 sigset_t unblockset, oldset; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
237 struct sigaction sa, oldsa; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
238 if (sigemptyset(&unblockset) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
239 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
240 if (sigaddset(&unblockset, sig) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
241 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
242 memset(&sa, 0, sizeof(sa)); |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
243 sa.sa_handler = SIG_DFL; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
244 sa.sa_flags = SA_RESTART; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
245 if (sigemptyset(&sa.sa_mask) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
246 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
247 |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
248 forwardsignal(sig); |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
249 if (raise(sig) < 0) /* resend to self */ |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
250 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
251 if (sigaction(sig, &sa, &oldsa) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
252 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
253 if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
254 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
255 /* resent signal will be handled before sigprocmask() returns */ |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
256 if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
257 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
258 if (sigaction(sig, &oldsa, NULL) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
259 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
260 return; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
261 |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
262 error: |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
263 abortmsg("failed to handle stop signal (errno = %d)", errno); |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
264 } |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
265 |
28060 | 266 static void setupsignalhandler(pid_t pid) |
267 { | |
268 if (pid <= 0) | |
269 return; | |
270 peerpid = pid; | |
271 | |
272 struct sigaction sa; | |
273 memset(&sa, 0, sizeof(sa)); | |
274 sa.sa_handler = forwardsignal; | |
275 sa.sa_flags = SA_RESTART; | |
28085
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
276 if (sigemptyset(&sa.sa_mask) < 0) |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
277 goto error; |
28060 | 278 |
28085
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
279 if (sigaction(SIGHUP, &sa, NULL) < 0) |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
280 goto error; |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
281 if (sigaction(SIGINT, &sa, NULL) < 0) |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
282 goto error; |
28060 | 283 |
284 /* terminate frontend by double SIGTERM in case of server freeze */ | |
285 sa.sa_flags |= SA_RESETHAND; | |
28085
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
286 if (sigaction(SIGTERM, &sa, NULL) < 0) |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
287 goto error; |
28086
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
288 |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
289 /* propagate job control requests to worker */ |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
290 sa.sa_handler = forwardsignal; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
291 sa.sa_flags = SA_RESTART; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
292 if (sigaction(SIGCONT, &sa, NULL) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
293 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
294 sa.sa_handler = handlestopsignal; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
295 sa.sa_flags = SA_RESTART; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
296 if (sigaction(SIGTSTP, &sa, NULL) < 0) |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
297 goto error; |
65d24ca35496
chg: forward job control signals to worker process (issue5051)
Yuya Nishihara <yuya@tcha.org>
parents:
28085
diff
changeset
|
298 |
28085
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
299 return; |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
300 |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
301 error: |
c0d1bf1b26b7
chg: verify return value of sigaction() and sigemptyset()
Yuya Nishihara <yuya@tcha.org>
parents:
28084
diff
changeset
|
302 abortmsg("failed to set up signal handlers (errno = %d)", errno); |
28060 | 303 } |
304 | |
305 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */ | |
306 static void setuppager(hgclient_t *hgc, const char *const args[], | |
307 size_t argsize) | |
308 { | |
309 const char *pagercmd = hgc_getpager(hgc, args, argsize); | |
310 if (!pagercmd) | |
311 return; | |
312 | |
313 int pipefds[2]; | |
314 if (pipe(pipefds) < 0) | |
315 return; | |
316 pid_t pid = fork(); | |
317 if (pid < 0) | |
318 goto error; | |
319 if (pid == 0) { | |
320 close(pipefds[0]); | |
321 if (dup2(pipefds[1], fileno(stdout)) < 0) | |
322 goto error; | |
323 if (isatty(fileno(stderr))) { | |
324 if (dup2(pipefds[1], fileno(stderr)) < 0) | |
325 goto error; | |
326 } | |
327 close(pipefds[1]); | |
328 hgc_attachio(hgc); /* reattach to pager */ | |
329 return; | |
330 } else { | |
331 dup2(pipefds[0], fileno(stdin)); | |
332 close(pipefds[0]); | |
333 close(pipefds[1]); | |
334 | |
335 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL); | |
336 if (r < 0) { | |
337 abortmsg("cannot start pager '%s' (errno = %d)", | |
338 pagercmd, errno); | |
339 } | |
340 return; | |
341 } | |
342 | |
343 error: | |
344 close(pipefds[0]); | |
345 close(pipefds[1]); | |
346 abortmsg("failed to prepare pager (errno = %d)", errno); | |
347 } | |
348 | |
349 int main(int argc, const char *argv[], const char *envp[]) | |
350 { | |
351 if (getenv("CHGDEBUG")) | |
352 enabledebugmsg(); | |
353 | |
354 struct cmdserveropts opts; | |
355 setcmdserveropts(&opts); | |
356 | |
357 if (argc == 2) { | |
358 int sig = 0; | |
359 if (strcmp(argv[1], "--kill-chg-daemon") == 0) | |
360 sig = SIGTERM; | |
361 if (strcmp(argv[1], "--reload-chg-daemon") == 0) | |
362 sig = SIGHUP; | |
363 if (sig > 0) { | |
364 killcmdserver(&opts, sig); | |
365 return 0; | |
366 } | |
367 } | |
368 | |
369 hgclient_t *hgc = hgc_open(opts.sockname); | |
370 if (!hgc) { | |
371 startcmdserver(&opts); | |
372 hgc = hgc_open(opts.sockname); | |
373 } | |
374 if (!hgc) | |
375 abortmsg("cannot open hg client"); | |
376 | |
377 setupsignalhandler(hgc_peerpid(hgc)); | |
378 hgc_setenv(hgc, envp); | |
379 setuppager(hgc, argv + 1, argc - 1); | |
380 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1); | |
381 hgc_close(hgc); | |
382 return exitcode; | |
383 } |