application.ini: per semver, version bump to 1.0.2 for bugfix
[conkeror.git] / conkeror-spawn-helper.c
blob6251b3864c1fa3c4c6b90a7c4563ba18cf717f5d
1 /**
2 * (C) Copyright 2008 Jeremy Maitin-Shepard
4 * Use, modification, and distribution are subject to the terms specified in the
5 * COPYING file.
6 **/
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <errno.h>
13 #include <stdlib.h>
14 #include <signal.h>
15 #include <sys/wait.h>
16 #include <sys/stat.h>
17 #include <string.h>
18 #include <fcntl.h>
19 #include <netinet/in.h>
20 #include <dirent.h>
21 #include <sys/resource.h>
22 #include <arpa/inet.h>
24 void fail(const char *msg) {
25 fprintf(stderr, "%s\n", msg);
26 exit(1);
29 void failerr(const char *msg) {
30 perror(msg);
31 exit(1);
34 #define TRY(var, foo) var = foo; while (var == -1) { if(errno != EINTR) failerr(#foo); }
36 void *Malloc(size_t count) { void *r = malloc(count); if (!r) fail("malloc"); return r; }
38 /**
39 * read_all: read from the specified file descriptor, returning a
40 * malloc-allocated buffer containing the data that was read; the
41 * number of bytes read is stored in *bytes_read. If max_bytes is
42 * non-negative, it specifies the maximum number of bytes to read.
43 * Otherwise, read_all reads from the file descriptor until the end of
44 * file is reached.
46 char *read_all(int fd, int max_bytes, int *bytes_read) {
47 int capacity = 256;
48 if (max_bytes > 0)
49 capacity = max_bytes;
50 char *buffer = Malloc(capacity);
51 int count = 0;
52 if (max_bytes < 0 || max_bytes > 0) {
53 while (1) {
54 int remain;
55 if (count == capacity) {
56 capacity *= 2;
57 buffer = realloc(buffer, capacity);
58 if (!buffer)
59 fail("realloc failed");
61 remain = capacity - count;
62 if (max_bytes > 0 && remain > max_bytes)
63 remain = max_bytes;
64 TRY(remain, read(fd, buffer + count, remain));
65 count += remain;
66 if (remain == 0 || count == max_bytes)
67 break;
70 *bytes_read = count;
71 return buffer;
74 /**
75 * next_term: return the next NUL terminated string from buffer, and
76 * adjust buffer and len accordingly.
78 char *next_term(char **buffer, int *len) {
79 char *p = *buffer;
80 int x = 0;
81 int max_len = *len;
82 while (x < max_len && p[x])
83 ++x;
84 if (x == max_len)
85 fail("error parsing");
86 *buffer += x + 1;
87 *len -= (x + 1);
88 return p;
91 struct fd_info {
92 int desired_fd;
93 int orig_fd;
94 char *path;
95 int open_mode;
96 int perms;
99 void write_all(int fd, const char *buf, int len) {
100 int result;
101 do {
102 TRY(result, write(fd, buf, len));
103 buf += result;
104 len -= result;
105 } while (len > 0);
109 * my_connect: Create a connection to the local Conkeror process on
110 * the specified TCP port. After connecting, the properly formatted
111 * header specifying the client_key and the "role" (file descriptor or
112 * -1 to indicate the control socket) are sent as well. The file
113 * descriptor for the socket is returned.
115 int my_connect(int port, char *client_key, int role) {
116 int sockfd;
117 int result;
118 struct sockaddr_in sa;
120 TRY(sockfd, socket(PF_INET, SOCK_STREAM, 0));
121 sa.sin_family = AF_INET;
122 sa.sin_port = htons(port);
123 sa.sin_addr.s_addr = inet_addr("127.0.0.1");
124 memset(sa.sin_zero, 0, sizeof(sa.sin_zero));
126 TRY(result, connect(sockfd, (struct sockaddr *)&sa, sizeof(sa)));
128 /* Send the client key */
129 write_all(sockfd, client_key, strlen(client_key));
131 /* Send the role */
132 if (role < 0) {
133 write_all(sockfd, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 15);
135 else {
136 char buf[16];
137 snprintf(buf, 16, "%15d", role);
138 write_all(sockfd, buf, 15);
141 return sockfd;
144 int child_pid = 0;
145 int control_fd;
148 * sigchld_handler: reap any waitable children. Once the child
149 * process exits, send the exit status back over the control socket,
150 * then exit. */
151 void sigchld_handler(int sig) {
152 int status;
153 int pid;
154 int err;
156 while (1) {
157 pid = waitpid(-1, &status, WNOHANG);
158 if (pid == 0)
159 return;
160 if (pid == -1) {
161 if (errno == ECHILD)
162 break;
163 failerr("waitpid");
166 /* Our child process exited */
167 if (pid == child_pid && (WIFEXITED(status) || WIFSIGNALED(status))) {
168 char buf[30];
169 snprintf(buf, 30, "%d", status);
170 write_all(control_fd, buf, strlen(buf) + 1);
171 exit(0);
176 void check_duplicate_fds(struct fd_info *fds, int fd_count) {
177 int i, j;
178 for (i = 0; i < fd_count; ++i) {
179 for (j = i + 1; j < fd_count; ++j) {
180 if (fds[i].desired_fd == fds[j].desired_fd)
181 fail("duplicate redirection requested");
187 * setup_fds: Make the requested redirections. For each entry in the
188 * fds array, rename orig_fd to desired_fd.
190 void setup_fds(struct fd_info *fds, int fd_count) {
191 int i, j, result;
192 for (i = 0; i < fd_count; ++i) {
193 int fd = fds[i].desired_fd;
194 if (fd == fds[i].orig_fd) {
195 /* file descriptor is already correct, nothing needs to be done for it */
196 continue;
198 /* Check if this file descriptor is still in use by any subsequent
199 redirection. */
200 for (j = i + 1; j < fd_count; ++j) {
201 if (fd == fds[j].orig_fd) {
202 /* It is in use. Pick a new file descriptor for fds[j]. */
203 int fd_new;
204 TRY(fd_new, dup(fds[j].orig_fd));
205 close(fds[j].orig_fd);
206 fds[j].orig_fd = fd_new;
207 break;
210 TRY(result, dup2(fds[i].orig_fd, fd));
211 close(fds[i].orig_fd);
215 int main(int argc, char **argv) {
217 int port;
218 char *client_key, *server_key, *executable, *workdir;
219 char **my_argv;
220 struct fd_info *fds;
221 int fd_count;
222 int i;
223 sigset_t my_mask, my_old_mask;
225 if (argc != 3 || (port = atoi(argv[2])) == 0)
226 fail("Invalid arguments");
228 sigemptyset(&my_mask);
229 sigaddset(&my_mask, SIGCHLD);
231 /* Block SIGPIPE to avoid a signal being generated while writing to a socket */
232 signal(SIGPIPE, SIG_IGN);
234 /* Close everything except STDERR. Mozilla leaves us with a bunch
235 of junk file descriptors. */
237 DIR *dir = opendir("/proc/self/fd");
238 if (!dir) {
239 /* No proc filesystem available, just loop through file descriptors */
240 struct rlimit file_lim;
241 int max_fileno = 1024;
242 if (getrlimit(RLIMIT_NOFILE, &file_lim) == 0)
243 max_fileno = file_lim.rlim_cur;
244 for (i = 0; i < max_fileno; ++i) {
245 if (i == STDERR_FILENO)
246 continue;
247 close(i);
249 } else {
250 struct dirent *dir_ent;
251 int dir_fd = dirfd(dir);
252 while ((dir_ent = readdir(dir)) != NULL) {
253 int file_desc = atoi(dir_ent->d_name);
254 if (file_desc == STDERR_FILENO || file_desc == dir_fd)
255 continue;
256 close(file_desc);
258 closedir(dir);
262 /* Create a default redirection of STDIN and STDOUT to /dev/null, because some
263 programs except STDIN and STDOUT to always be present. Any user-specified
264 redirections will override these.
267 /* At this point, the only open file descriptor is STDERR (2). Therefore, the
268 next two calls to open are guaranteed to use file descriptors 1 and 2
269 (STDIN and STDOUT, respectively).
271 if (open("/dev/null", O_RDONLY) != STDIN_FILENO)
272 fail("Failed to redirect STDIN to /dev/null");
274 if (open("/dev/null", O_RDWR) != STDOUT_FILENO)
275 fail("Failed to redirect STDOUT to /dev/null");
277 /* Parse key file */
279 char *buf;
280 int len;
281 int my_argc;
282 /* Read the entire file into buf. */
284 int file;
285 TRY(file, open(argv[1], O_RDONLY));
286 buf = read_all(file, -1, &len);
287 close(file);
289 /* Remove the temporary file */
290 remove(argv[1]);
292 client_key = next_term(&buf, &len);
293 server_key = next_term(&buf, &len);
294 executable = next_term(&buf, &len);
295 workdir = next_term(&buf, &len);
296 my_argc = atoi(next_term(&buf, &len));
297 my_argv = Malloc(sizeof(char *) * (my_argc + 1));
298 for (i = 0; i < my_argc; ++i)
299 my_argv[i] = next_term(&buf, &len);
300 my_argv[my_argc] = NULL;
301 fd_count = atoi(next_term(&buf, &len));
302 if (fd_count < 0) fail("invalid fd count");
303 fds = Malloc(sizeof(struct fd_info) * fd_count);
304 for (i = 0; i < fd_count; ++i) {
305 fds[i].desired_fd = atoi(next_term(&buf, &len));
306 fds[i].path = next_term(&buf, &len);
307 if (fds[i].path[0]) {
308 fds[i].open_mode = atoi(next_term(&buf, &len));
309 fds[i].perms = atoi(next_term(&buf, &len));
312 if (len != 0)
313 fail("invalid input file");
316 /* Validate the file descriptor redirection request. */
317 check_duplicate_fds(fds, fd_count);
319 /* Create the control socket connection. */
320 control_fd = my_connect(port, client_key, -1);
322 /* Create a socket connection or open a local file for each
323 requested file descriptor redirection. */
324 for (i = 0; i < fd_count; ++i) {
325 if (fds[i].path[0]) {
326 TRY(fds[i].orig_fd, open(fds[i].path, fds[i].open_mode, fds[i].perms));
327 } else {
328 fds[i].orig_fd = my_connect(port, client_key, fds[i].desired_fd);
332 /* Check server key */
334 int len = strlen(server_key);
335 int read_len;
336 char *buf = read_all(control_fd, len, &read_len);
337 if (len != read_len || memcmp(buf, server_key, len) != 0)
338 fail("server key mismatch");
339 free(buf);
342 /* Block SIGCHLD */
343 sigprocmask(SIG_BLOCK, &my_mask, &my_old_mask);
345 /* Create the child process */
346 child_pid = fork();
347 if (child_pid == 0) {
348 int result;
349 /* Unblock SIGCHLD */
350 sigprocmask(SIG_SETMASK, &my_old_mask, NULL);
352 /* Reset the SIGPIPE signal handler. */
353 signal(SIGPIPE, SIG_DFL);
355 /* Close the control socket, as it isn't needed from the child. */
356 close(control_fd);
358 /* Change to the specified working directory. */
359 if (workdir[0] != 0) {
360 if (chdir(workdir) == -1)
361 failerr(workdir);
364 /* Rearrange file descriptors according to the user specification */
365 setup_fds(fds, fd_count);
367 /* Exec */
368 TRY(result, execv(executable, my_argv));
370 } else if (child_pid == -1) {
371 failerr("fork");
372 } else {
373 /* We are in the parent process */
374 char msg;
375 int count;
377 /* Install SIGCHLD handler */
379 struct sigaction act;
380 act.sa_handler = sigchld_handler;
381 sigemptyset(&act.sa_mask);
382 act.sa_flags = SA_NOCLDSTOP;
383 sigaction(SIGCHLD, &act, NULL);
385 /* Unblock SIGCHLD */
386 sigprocmask(SIG_SETMASK, &my_old_mask, NULL);
388 /* Close all of the redirection file descriptors, as we don't need
389 them from the parent. */
390 for (i = 0; i < fd_count; ++i)
391 close(fds[i].orig_fd);
393 /* Wait for a message from the server telling us to exit early. */
394 TRY(count, read(control_fd, &msg, 1));
396 if (count == 0) {
397 /* End of file received: exit without killing child */
398 return 0;
401 /* Assume msg == 0 until we support more messages */
402 TRY(count, kill(child_pid, SIGTERM));
403 return 0;