Mercurial > hg
comparison contrib/chg/chg.c @ 28060:726f8d6cc324
chg: import frontend sources
These files are copied from
https://bitbucket.org/yuja/chg/ -r f897faa79687
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Sun, 03 Jan 2016 12:39:27 +0900 |
parents | |
children | 3fc45956c978 |
comparison
equal
deleted
inserted
replaced
28059:740208f6f6af | 28060:726f8d6cc324 |
---|---|
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 | |
234 static void setupsignalhandler(pid_t pid) | |
235 { | |
236 if (pid <= 0) | |
237 return; | |
238 peerpid = pid; | |
239 | |
240 struct sigaction sa; | |
241 memset(&sa, 0, sizeof(sa)); | |
242 sa.sa_handler = forwardsignal; | |
243 sa.sa_flags = SA_RESTART; | |
244 | |
245 sigaction(SIGHUP, &sa, NULL); | |
246 sigaction(SIGINT, &sa, NULL); | |
247 | |
248 /* terminate frontend by double SIGTERM in case of server freeze */ | |
249 sa.sa_flags |= SA_RESETHAND; | |
250 sigaction(SIGTERM, &sa, NULL); | |
251 } | |
252 | |
253 /* This implementation is based on hgext/pager.py (pre 369741ef7253) */ | |
254 static void setuppager(hgclient_t *hgc, const char *const args[], | |
255 size_t argsize) | |
256 { | |
257 const char *pagercmd = hgc_getpager(hgc, args, argsize); | |
258 if (!pagercmd) | |
259 return; | |
260 | |
261 int pipefds[2]; | |
262 if (pipe(pipefds) < 0) | |
263 return; | |
264 pid_t pid = fork(); | |
265 if (pid < 0) | |
266 goto error; | |
267 if (pid == 0) { | |
268 close(pipefds[0]); | |
269 if (dup2(pipefds[1], fileno(stdout)) < 0) | |
270 goto error; | |
271 if (isatty(fileno(stderr))) { | |
272 if (dup2(pipefds[1], fileno(stderr)) < 0) | |
273 goto error; | |
274 } | |
275 close(pipefds[1]); | |
276 hgc_attachio(hgc); /* reattach to pager */ | |
277 return; | |
278 } else { | |
279 dup2(pipefds[0], fileno(stdin)); | |
280 close(pipefds[0]); | |
281 close(pipefds[1]); | |
282 | |
283 int r = execlp("/bin/sh", "/bin/sh", "-c", pagercmd, NULL); | |
284 if (r < 0) { | |
285 abortmsg("cannot start pager '%s' (errno = %d)", | |
286 pagercmd, errno); | |
287 } | |
288 return; | |
289 } | |
290 | |
291 error: | |
292 close(pipefds[0]); | |
293 close(pipefds[1]); | |
294 abortmsg("failed to prepare pager (errno = %d)", errno); | |
295 } | |
296 | |
297 int main(int argc, const char *argv[], const char *envp[]) | |
298 { | |
299 if (getenv("CHGDEBUG")) | |
300 enabledebugmsg(); | |
301 | |
302 struct cmdserveropts opts; | |
303 setcmdserveropts(&opts); | |
304 | |
305 if (argc == 2) { | |
306 int sig = 0; | |
307 if (strcmp(argv[1], "--kill-chg-daemon") == 0) | |
308 sig = SIGTERM; | |
309 if (strcmp(argv[1], "--reload-chg-daemon") == 0) | |
310 sig = SIGHUP; | |
311 if (sig > 0) { | |
312 killcmdserver(&opts, sig); | |
313 return 0; | |
314 } | |
315 } | |
316 | |
317 hgclient_t *hgc = hgc_open(opts.sockname); | |
318 if (!hgc) { | |
319 startcmdserver(&opts); | |
320 hgc = hgc_open(opts.sockname); | |
321 } | |
322 if (!hgc) | |
323 abortmsg("cannot open hg client"); | |
324 | |
325 setupsignalhandler(hgc_peerpid(hgc)); | |
326 hgc_setenv(hgc, envp); | |
327 setuppager(hgc, argv + 1, argc - 1); | |
328 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1); | |
329 hgc_close(hgc); | |
330 return exitcode; | |
331 } |