##// END OF EJS Templates
chg: import frontend sources...
Yuya Nishihara -
r28060:726f8d6c default
parent child Browse files
Show More
@@ -0,0 +1,50
1 HG = hg
2
3 TARGET = chg
4 SRCS = chg.c hgclient.c util.c
5 OBJS = $(SRCS:.c=.o)
6
7 CFLAGS ?= -O2 -Wall -Wextra -pedantic -g
8 CPPFLAGS ?= -D_FORTIFY_SOURCE=2
9 override CFLAGS += -std=gnu99
10
11 DESTDIR =
12 PREFIX = /usr/local
13 MANDIR = $(PREFIX)/share/man/man1
14
15 CHGSOCKDIR = /tmp/chg$(shell id -u)
16 CHGSOCKNAME = $(CHGSOCKDIR)/server
17
18 .PHONY: all
19 all: $(TARGET)
20
21 $(TARGET): $(OBJS)
22 $(CC) $(LDFLAGS) -o $@ $(OBJS)
23
24 chg.o: hgclient.h util.h
25 hgclient.o: hgclient.h util.h
26 util.o: util.h
27
28 .PHONY: install
29 install: $(TARGET)
30 install -d $(DESTDIR)$(PREFIX)/bin
31 install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
32 install -d $(DESTDIR)$(MANDIR)
33 install -m 644 chg.1 $(DESTDIR)$(MANDIR)
34
35 .PHONY: serve
36 serve:
37 [ -d $(CHGSOCKDIR) ] || ( umask 077; mkdir $(CHGSOCKDIR) )
38 $(HG) serve --cwd / --cmdserver chgunix \
39 --address $(CHGSOCKNAME) \
40 --config extensions.chgserver= \
41 --config progress.assume-tty=1 \
42 --config cmdserver.log=/dev/stderr
43
44 .PHONY: clean
45 clean:
46 $(RM) $(OBJS)
47
48 .PHONY: distclean
49 distclean:
50 $(RM) $(OBJS) $(TARGET)
@@ -0,0 +1,32
1 cHg
2 ===
3
4 A fast client for Mercurial command server running on Unix.
5
6 Install:
7
8 $ make
9 $ make install
10
11 Usage:
12
13 $ chg help # show help of Mercurial
14 $ alias hg=chg # replace hg command
15 $ chg --kill-chg-daemon # terminate background server
16 $ chg --reload-chg-daemon # reload configuration files
17
18 Environment variables:
19
20 Although cHg tries to update environment variables, some of them cannot be
21 changed after spawning the server. The following variables are specially
22 handled:
23
24 * configuration files are reloaded if HGPLAIN or HGPLAINEXCEPT changed, but
25 some behaviors won't change correctly.
26 * CHGHG or HG specifies the path to the hg executable spawned as the
27 background command server.
28
29 The following variables are available for testing:
30
31 * CHGDEBUG enables debug messages.
32 * CHGSOCKNAME specifies the socket path of the background cmdserver.
@@ -0,0 +1,44
1 .\" Hey, EMACS: -*- nroff -*-
2 .\" First parameter, NAME, should be all caps
3 .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
4 .\" other parameters are allowed: see man(7), man(1)
5 .TH CHG 1 "March 3, 2013"
6 .\" Please adjust this date whenever revising the manpage.
7 .\"
8 .\" Some roff macros, for reference:
9 .\" .nh disable hyphenation
10 .\" .hy enable hyphenation
11 .\" .ad l left justify
12 .\" .ad b justify to both left and right margins
13 .\" .nf disable filling
14 .\" .fi enable filling
15 .\" .br insert line break
16 .\" .sp <n> insert n+1 empty lines
17 .\" for manpage-specific macros, see man(7)
18 .SH NAME
19 chg \- a fast client for Mercurial command server
20 .SH SYNOPSIS
21 .B chg
22 .IR command " [" options "] [" arguments "]..."
23 .br
24 .SH DESCRIPTION
25 The
26 .B chg
27 command is the wrapper for
28 .B hg
29 command.
30 It uses the Mercurial command server to reduce start-up overhead.
31 .SH OPTIONS
32 This program accepts the same command line syntax as the
33 .B hg
34 command. Additionally it accepts the following options.
35 .TP
36 .B \-\-kill\-chg\-daemon
37 Terminate the background command servers.
38 .TP
39 .B \-\-reload\-chg\-daemon
40 Reload configuration files.
41 .SH SEE ALSO
42 .BR hg (1),
43 .SH AUTHOR
44 Written by Yuya Nishihara <yuya@tcha.org>.
@@ -0,0 +1,331
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 }
This diff has been collapsed as it changes many lines, (527 lines changed) Show them Hide them
@@ -0,0 +1,527
1 /*
2 * A command server client that uses Unix domain socket
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 <arpa/inet.h> /* for ntohl(), htonl() */
11 #include <assert.h>
12 #include <ctype.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <signal.h>
16 #include <stdint.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/socket.h>
21 #include <sys/stat.h>
22 #include <sys/un.h>
23 #include <unistd.h>
24
25 #include "hgclient.h"
26 #include "util.h"
27
28 enum {
29 CAP_GETENCODING = 0x0001,
30 CAP_RUNCOMMAND = 0x0002,
31 /* cHg extension: */
32 CAP_ATTACHIO = 0x0100,
33 CAP_CHDIR = 0x0200,
34 CAP_GETPAGER = 0x0400,
35 CAP_SETENV = 0x0800,
36 };
37
38 typedef struct {
39 const char *name;
40 unsigned int flag;
41 } cappair_t;
42
43 static const cappair_t captable[] = {
44 {"getencoding", CAP_GETENCODING},
45 {"runcommand", CAP_RUNCOMMAND},
46 {"attachio", CAP_ATTACHIO},
47 {"chdir", CAP_CHDIR},
48 {"getpager", CAP_GETPAGER},
49 {"setenv", CAP_SETENV},
50 {NULL, 0}, /* terminator */
51 };
52
53 typedef struct {
54 char ch;
55 char *data;
56 size_t maxdatasize;
57 size_t datasize;
58 } context_t;
59
60 struct hgclient_tag_ {
61 int sockfd;
62 pid_t pid;
63 context_t ctx;
64 unsigned int capflags;
65 };
66
67 static const size_t defaultdatasize = 4096;
68
69 static void initcontext(context_t *ctx)
70 {
71 ctx->ch = '\0';
72 ctx->data = malloc(defaultdatasize);
73 ctx->maxdatasize = (ctx->data) ? defaultdatasize : 0;
74 ctx->datasize = 0;
75 debugmsg("initialize context buffer with size %zu", ctx->maxdatasize);
76 }
77
78 static void enlargecontext(context_t *ctx, size_t newsize)
79 {
80 if (newsize <= ctx->maxdatasize)
81 return;
82
83 newsize = defaultdatasize
84 * ((newsize + defaultdatasize - 1) / defaultdatasize);
85 char *p = realloc(ctx->data, newsize);
86 if (!p)
87 abortmsg("failed to allocate buffer");
88 ctx->data = p;
89 ctx->maxdatasize = newsize;
90 debugmsg("enlarge context buffer to %zu", ctx->maxdatasize);
91 }
92
93 static void freecontext(context_t *ctx)
94 {
95 debugmsg("free context buffer");
96 free(ctx->data);
97 ctx->data = NULL;
98 ctx->maxdatasize = 0;
99 ctx->datasize = 0;
100 }
101
102 /* Read channeled response from cmdserver */
103 static void readchannel(hgclient_t *hgc)
104 {
105 assert(hgc);
106
107 ssize_t rsize = recv(hgc->sockfd, &hgc->ctx.ch, sizeof(hgc->ctx.ch), 0);
108 if (rsize != sizeof(hgc->ctx.ch))
109 abortmsg("failed to read channel");
110
111 uint32_t datasize_n;
112 rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0);
113 if (rsize != sizeof(datasize_n))
114 abortmsg("failed to read data size");
115
116 /* datasize denotes the maximum size to write if input request */
117 hgc->ctx.datasize = ntohl(datasize_n);
118 enlargecontext(&hgc->ctx, hgc->ctx.datasize);
119
120 if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S')
121 return; /* assumes input request */
122
123 size_t cursize = 0;
124 while (cursize < hgc->ctx.datasize) {
125 rsize = recv(hgc->sockfd, hgc->ctx.data + cursize,
126 hgc->ctx.datasize - cursize, 0);
127 if (rsize < 0)
128 abortmsg("failed to read data block");
129 cursize += rsize;
130 }
131 }
132
133 static void sendall(int sockfd, const void *data, size_t datasize)
134 {
135 const char *p = data;
136 const char *const endp = p + datasize;
137 while (p < endp) {
138 ssize_t r = send(sockfd, p, endp - p, 0);
139 if (r < 0)
140 abortmsg("cannot communicate (errno = %d)", errno);
141 p += r;
142 }
143 }
144
145 /* Write lengh-data block to cmdserver */
146 static void writeblock(const hgclient_t *hgc)
147 {
148 assert(hgc);
149
150 const uint32_t datasize_n = htonl(hgc->ctx.datasize);
151 sendall(hgc->sockfd, &datasize_n, sizeof(datasize_n));
152
153 sendall(hgc->sockfd, hgc->ctx.data, hgc->ctx.datasize);
154 }
155
156 static void writeblockrequest(const hgclient_t *hgc, const char *chcmd)
157 {
158 debugmsg("request %s, block size %zu", chcmd, hgc->ctx.datasize);
159
160 char buf[strlen(chcmd) + 1];
161 memcpy(buf, chcmd, sizeof(buf) - 1);
162 buf[sizeof(buf) - 1] = '\n';
163 sendall(hgc->sockfd, buf, sizeof(buf));
164
165 writeblock(hgc);
166 }
167
168 /* Build '\0'-separated list of args. argsize < 0 denotes that args are
169 * terminated by NULL. */
170 static void packcmdargs(context_t *ctx, const char *const args[],
171 ssize_t argsize)
172 {
173 ctx->datasize = 0;
174 const char *const *const end = (argsize >= 0) ? args + argsize : NULL;
175 for (const char *const *it = args; it != end && *it; ++it) {
176 const size_t n = strlen(*it) + 1; /* include '\0' */
177 enlargecontext(ctx, ctx->datasize + n);
178 memcpy(ctx->data + ctx->datasize, *it, n);
179 ctx->datasize += n;
180 }
181
182 if (ctx->datasize > 0)
183 --ctx->datasize; /* strip last '\0' */
184 }
185
186 /* Extract '\0'-separated list of args to new buffer, terminated by NULL */
187 static const char **unpackcmdargsnul(const context_t *ctx)
188 {
189 const char **args = NULL;
190 size_t nargs = 0, maxnargs = 0;
191 const char *s = ctx->data;
192 const char *e = ctx->data + ctx->datasize;
193 for (;;) {
194 if (nargs + 1 >= maxnargs) { /* including last NULL */
195 maxnargs += 256;
196 args = realloc(args, maxnargs * sizeof(args[0]));
197 if (!args)
198 abortmsg("failed to allocate args buffer");
199 }
200 args[nargs] = s;
201 nargs++;
202 s = memchr(s, '\0', e - s);
203 if (!s)
204 break;
205 s++;
206 }
207 args[nargs] = NULL;
208 return args;
209 }
210
211 static void handlereadrequest(hgclient_t *hgc)
212 {
213 context_t *ctx = &hgc->ctx;
214 size_t r = fread(ctx->data, sizeof(ctx->data[0]), ctx->datasize, stdin);
215 ctx->datasize = r;
216 writeblock(hgc);
217 }
218
219 /* Read single-line */
220 static void handlereadlinerequest(hgclient_t *hgc)
221 {
222 context_t *ctx = &hgc->ctx;
223 if (!fgets(ctx->data, ctx->datasize, stdin))
224 ctx->data[0] = '\0';
225 ctx->datasize = strlen(ctx->data);
226 writeblock(hgc);
227 }
228
229 /* Execute the requested command and write exit code */
230 static void handlesystemrequest(hgclient_t *hgc)
231 {
232 context_t *ctx = &hgc->ctx;
233 enlargecontext(ctx, ctx->datasize + 1);
234 ctx->data[ctx->datasize] = '\0'; /* terminate last string */
235
236 const char **args = unpackcmdargsnul(ctx);
237 if (!args[0] || !args[1])
238 abortmsg("missing command or cwd in system request");
239 debugmsg("run '%s' at '%s'", args[0], args[1]);
240 int32_t r = runshellcmd(args[0], args + 2, args[1]);
241 free(args);
242
243 uint32_t r_n = htonl(r);
244 memcpy(ctx->data, &r_n, sizeof(r_n));
245 ctx->datasize = sizeof(r_n);
246 writeblock(hgc);
247 }
248
249 /* Read response of command execution until receiving 'r'-esult */
250 static void handleresponse(hgclient_t *hgc)
251 {
252 for (;;) {
253 readchannel(hgc);
254 context_t *ctx = &hgc->ctx;
255 debugmsg("response read from channel %c, size %zu",
256 ctx->ch, ctx->datasize);
257 switch (ctx->ch) {
258 case 'o':
259 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
260 stdout);
261 break;
262 case 'e':
263 fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize,
264 stderr);
265 break;
266 case 'd':
267 /* assumes last char is '\n' */
268 ctx->data[ctx->datasize - 1] = '\0';
269 debugmsg("server: %s", ctx->data);
270 break;
271 case 'r':
272 return;
273 case 'I':
274 handlereadrequest(hgc);
275 break;
276 case 'L':
277 handlereadlinerequest(hgc);
278 break;
279 case 'S':
280 handlesystemrequest(hgc);
281 break;
282 default:
283 if (isupper(ctx->ch))
284 abortmsg("cannot handle response (ch = %c)",
285 ctx->ch);
286 }
287 }
288 }
289
290 static unsigned int parsecapabilities(const char *s, const char *e)
291 {
292 unsigned int flags = 0;
293 while (s < e) {
294 const char *t = strchr(s, ' ');
295 if (!t || t > e)
296 t = e;
297 const cappair_t *cap;
298 for (cap = captable; cap->flag; ++cap) {
299 size_t n = t - s;
300 if (strncmp(s, cap->name, n) == 0 &&
301 strlen(cap->name) == n) {
302 flags |= cap->flag;
303 break;
304 }
305 }
306 s = t + 1;
307 }
308 return flags;
309 }
310
311 static void readhello(hgclient_t *hgc)
312 {
313 readchannel(hgc);
314 context_t *ctx = &hgc->ctx;
315 if (ctx->ch != 'o')
316 abortmsg("unexpected channel of hello message (ch = %c)",
317 ctx->ch);
318 enlargecontext(ctx, ctx->datasize + 1);
319 ctx->data[ctx->datasize] = '\0';
320 debugmsg("hello received: %s (size = %zu)", ctx->data, ctx->datasize);
321
322 const char *s = ctx->data;
323 const char *const dataend = ctx->data + ctx->datasize;
324 while (s < dataend) {
325 const char *t = strchr(s, ':');
326 if (!t || t[1] != ' ')
327 break;
328 const char *u = strchr(t + 2, '\n');
329 if (!u)
330 u = dataend;
331 if (strncmp(s, "capabilities:", t - s + 1) == 0) {
332 hgc->capflags = parsecapabilities(t + 2, u);
333 } else if (strncmp(s, "pid:", t - s + 1) == 0) {
334 hgc->pid = strtol(t + 2, NULL, 10);
335 }
336 s = u + 1;
337 }
338 debugmsg("capflags=0x%04x, pid=%d", hgc->capflags, hgc->pid);
339 }
340
341 static void attachio(hgclient_t *hgc)
342 {
343 debugmsg("request attachio");
344 static const char chcmd[] = "attachio\n";
345 sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1);
346 readchannel(hgc);
347 context_t *ctx = &hgc->ctx;
348 if (ctx->ch != 'I')
349 abortmsg("unexpected response for attachio (ch = %c)", ctx->ch);
350
351 static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
352 struct msghdr msgh;
353 memset(&msgh, 0, sizeof(msgh));
354 struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */
355 msgh.msg_iov = &iov;
356 msgh.msg_iovlen = 1;
357 char fdbuf[CMSG_SPACE(sizeof(fds))];
358 msgh.msg_control = fdbuf;
359 msgh.msg_controllen = sizeof(fdbuf);
360 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
361 cmsg->cmsg_level = SOL_SOCKET;
362 cmsg->cmsg_type = SCM_RIGHTS;
363 cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
364 memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
365 msgh.msg_controllen = cmsg->cmsg_len;
366 ssize_t r = sendmsg(hgc->sockfd, &msgh, 0);
367 if (r < 0)
368 abortmsg("sendmsg failed (errno = %d)", errno);
369
370 handleresponse(hgc);
371 int32_t n;
372 if (ctx->datasize != sizeof(n))
373 abortmsg("unexpected size of attachio result");
374 memcpy(&n, ctx->data, sizeof(n));
375 n = ntohl(n);
376 if (n != sizeof(fds) / sizeof(fds[0]))
377 abortmsg("failed to send fds (n = %d)", n);
378 }
379
380 static void chdirtocwd(hgclient_t *hgc)
381 {
382 if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize))
383 abortmsg("failed to getcwd (errno = %d)", errno);
384 hgc->ctx.datasize = strlen(hgc->ctx.data);
385 writeblockrequest(hgc, "chdir");
386 }
387
388 /*!
389 * Open connection to per-user cmdserver
390 *
391 * If no background server running, returns NULL.
392 */
393 hgclient_t *hgc_open(const char *sockname)
394 {
395 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
396 if (fd < 0)
397 abortmsg("cannot create socket (errno = %d)", errno);
398
399 /* don't keep fd on fork(), so that it can be closed when the parent
400 * process get terminated. */
401 int flags = fcntl(fd, F_GETFD);
402 if (flags < 0)
403 abortmsg("cannot get flags of socket (errno = %d)", errno);
404 if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
405 abortmsg("cannot set flags of socket (errno = %d)", errno);
406
407 struct sockaddr_un addr;
408 addr.sun_family = AF_UNIX;
409 strncpy(addr.sun_path, sockname, sizeof(addr.sun_path));
410 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
411
412 debugmsg("connect to %s", addr.sun_path);
413 int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
414 if (r < 0) {
415 close(fd);
416 if (errno == ENOENT || errno == ECONNREFUSED)
417 return NULL;
418 abortmsg("cannot connect to %s (errno = %d)",
419 addr.sun_path, errno);
420 }
421
422 hgclient_t *hgc = malloc(sizeof(hgclient_t));
423 if (!hgc)
424 abortmsg("failed to allocate hgclient object");
425 memset(hgc, 0, sizeof(*hgc));
426 hgc->sockfd = fd;
427 initcontext(&hgc->ctx);
428
429 readhello(hgc);
430 if (!(hgc->capflags & CAP_RUNCOMMAND))
431 abortmsg("insufficient capability: runcommand");
432 if (hgc->capflags & CAP_ATTACHIO)
433 attachio(hgc);
434 if (hgc->capflags & CAP_CHDIR)
435 chdirtocwd(hgc);
436
437 return hgc;
438 }
439
440 /*!
441 * Close connection and free allocated memory
442 */
443 void hgc_close(hgclient_t *hgc)
444 {
445 assert(hgc);
446 freecontext(&hgc->ctx);
447 close(hgc->sockfd);
448 free(hgc);
449 }
450
451 pid_t hgc_peerpid(const hgclient_t *hgc)
452 {
453 assert(hgc);
454 return hgc->pid;
455 }
456
457 /*!
458 * Execute the specified Mercurial command
459 *
460 * @return result code
461 */
462 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize)
463 {
464 assert(hgc);
465
466 packcmdargs(&hgc->ctx, args, argsize);
467 writeblockrequest(hgc, "runcommand");
468 handleresponse(hgc);
469
470 int32_t exitcode_n;
471 if (hgc->ctx.datasize != sizeof(exitcode_n)) {
472 abortmsg("unexpected size of exitcode");
473 }
474 memcpy(&exitcode_n, hgc->ctx.data, sizeof(exitcode_n));
475 return ntohl(exitcode_n);
476 }
477
478 /*!
479 * (Re-)send client's stdio channels so that the server can access to tty
480 */
481 void hgc_attachio(hgclient_t *hgc)
482 {
483 assert(hgc);
484 if (!(hgc->capflags & CAP_ATTACHIO))
485 return;
486 attachio(hgc);
487 }
488
489 /*!
490 * Get pager command for the given Mercurial command args
491 *
492 * If no pager enabled, returns NULL. The return value becomes invalid
493 * once you run another request to hgc.
494 */
495 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
496 size_t argsize)
497 {
498 assert(hgc);
499
500 if (!(hgc->capflags & CAP_GETPAGER))
501 return NULL;
502
503 packcmdargs(&hgc->ctx, args, argsize);
504 writeblockrequest(hgc, "getpager");
505 handleresponse(hgc);
506
507 if (hgc->ctx.datasize < 1 || hgc->ctx.data[0] == '\0')
508 return NULL;
509 enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1);
510 hgc->ctx.data[hgc->ctx.datasize] = '\0';
511 return hgc->ctx.data;
512 }
513
514 /*!
515 * Update server's environment variables
516 *
517 * @param envp list of environment variables in "NAME=VALUE" format,
518 * terminated by NULL.
519 */
520 void hgc_setenv(hgclient_t *hgc, const char *const envp[])
521 {
522 assert(hgc && envp);
523 if (!(hgc->capflags & CAP_SETENV))
524 return;
525 packcmdargs(&hgc->ctx, envp, /*argsize*/ -1);
526 writeblockrequest(hgc, "setenv");
527 }
@@ -0,0 +1,29
1 /*
2 * A command server client that uses Unix domain socket
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 #ifndef HGCLIENT_H_
11 #define HGCLIENT_H_
12
13 #include <sys/types.h>
14
15 struct hgclient_tag_;
16 typedef struct hgclient_tag_ hgclient_t;
17
18 hgclient_t *hgc_open(const char *sockname);
19 void hgc_close(hgclient_t *hgc);
20
21 pid_t hgc_peerpid(const hgclient_t *hgc);
22
23 int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize);
24 void hgc_attachio(hgclient_t *hgc);
25 const char *hgc_getpager(hgclient_t *hgc, const char *const args[],
26 size_t argsize);
27 void hgc_setenv(hgclient_t *hgc, const char *const envp[]);
28
29 #endif /* HGCLIENT_H_ */
@@ -0,0 +1,121
1 /*
2 * Utility functions
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 <signal.h>
11 #include <stdarg.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17
18 #include "util.h"
19
20 void abortmsg(const char *fmt, ...)
21 {
22 va_list args;
23 va_start(args, fmt);
24 fputs("\033[1;31mchg: abort: ", stderr);
25 vfprintf(stderr, fmt, args);
26 fputs("\033[m\n", stderr);
27 va_end(args);
28
29 exit(255);
30 }
31
32 static int debugmsgenabled = 0;
33
34 void enabledebugmsg(void)
35 {
36 debugmsgenabled = 1;
37 }
38
39 void debugmsg(const char *fmt, ...)
40 {
41 if (!debugmsgenabled)
42 return;
43
44 va_list args;
45 va_start(args, fmt);
46 fputs("\033[1;30mchg: debug: ", stderr);
47 vfprintf(stderr, fmt, args);
48 fputs("\033[m\n", stderr);
49 va_end(args);
50 }
51
52 /*
53 * Execute a shell command in mostly the same manner as system(), with the
54 * give environment variables, after chdir to the given cwd. Returns a status
55 * code compatible with the Python subprocess module.
56 */
57 int runshellcmd(const char *cmd, const char *envp[], const char *cwd)
58 {
59 enum { F_SIGINT = 1, F_SIGQUIT = 2, F_SIGMASK = 4, F_WAITPID = 8 };
60 unsigned int doneflags = 0;
61 int status = 0;
62 struct sigaction newsa, oldsaint, oldsaquit;
63 sigset_t oldmask;
64
65 /* block or mask signals just as system() does */
66 newsa.sa_handler = SIG_IGN;
67 newsa.sa_flags = 0;
68 if (sigemptyset(&newsa.sa_mask) < 0)
69 goto done;
70 if (sigaction(SIGINT, &newsa, &oldsaint) < 0)
71 goto done;
72 doneflags |= F_SIGINT;
73 if (sigaction(SIGQUIT, &newsa, &oldsaquit) < 0)
74 goto done;
75 doneflags |= F_SIGQUIT;
76
77 if (sigaddset(&newsa.sa_mask, SIGCHLD) < 0)
78 goto done;
79 if (sigprocmask(SIG_BLOCK, &newsa.sa_mask, &oldmask) < 0)
80 goto done;
81 doneflags |= F_SIGMASK;
82
83 pid_t pid = fork();
84 if (pid < 0)
85 goto done;
86 if (pid == 0) {
87 sigaction(SIGINT, &oldsaint, NULL);
88 sigaction(SIGQUIT, &oldsaquit, NULL);
89 sigprocmask(SIG_SETMASK, &oldmask, NULL);
90 if (cwd && chdir(cwd) < 0)
91 _exit(127);
92 const char *argv[] = {"sh", "-c", cmd, NULL};
93 if (envp) {
94 execve("/bin/sh", (char **)argv, (char **)envp);
95 } else {
96 execv("/bin/sh", (char **)argv);
97 }
98 _exit(127);
99 } else {
100 if (waitpid(pid, &status, 0) < 0)
101 goto done;
102 doneflags |= F_WAITPID;
103 }
104
105 done:
106 if (doneflags & F_SIGINT)
107 sigaction(SIGINT, &oldsaint, NULL);
108 if (doneflags & F_SIGQUIT)
109 sigaction(SIGQUIT, &oldsaquit, NULL);
110 if (doneflags & F_SIGMASK)
111 sigprocmask(SIG_SETMASK, &oldmask, NULL);
112
113 /* no way to report other errors, use 127 (= shell termination) */
114 if (!(doneflags & F_WAITPID))
115 return 127;
116 if (WIFEXITED(status))
117 return WEXITSTATUS(status);
118 if (WIFSIGNALED(status))
119 return -WTERMSIG(status);
120 return 127;
121 }
@@ -0,0 +1,24
1 /*
2 * Utility functions
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 #ifndef UTIL_H_
11 #define UTIL_H_
12
13 #ifdef __GNUC__
14 #define PRINTF_FORMAT_ __attribute__((format(printf, 1, 2)))
15 #endif
16
17 void abortmsg(const char *fmt, ...) PRINTF_FORMAT_;
18
19 void enabledebugmsg(void);
20 void debugmsg(const char *fmt, ...) PRINTF_FORMAT_;
21
22 int runshellcmd(const char *cmd, const char *envp[], const char *cwd);
23
24 #endif /* UTIL_H_ */
General Comments 0
You need to be logged in to leave comments. Login now