2 * (C) Copyright 2008 Jeremy Maitin-Shepard
4 * Use, modification, and distribution are subject to the terms specified in the
9 #include <sys/socket.h>
19 #include <netinet/in.h>
21 #include <sys/resource.h>
22 #include <arpa/inet.h>
24 void fail(const char *msg
) {
25 fprintf(stderr
, "%s\n", msg
);
29 void failerr(const char *msg
) {
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
; }
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
46 char *read_all(int fd
, int max_bytes
, int *bytes_read
) {
50 char *buffer
= Malloc(capacity
);
52 if (max_bytes
< 0 || max_bytes
> 0) {
55 if (count
== capacity
) {
57 buffer
= realloc(buffer
, capacity
);
59 fail("realloc failed");
61 remain
= capacity
- count
;
62 if (max_bytes
> 0 && remain
> max_bytes
)
64 TRY(remain
, read(fd
, buffer
+ count
, remain
));
66 if (remain
== 0 || count
== max_bytes
)
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
) {
82 while (x
< max_len
&& p
[x
])
85 fail("error parsing");
99 void write_all(int fd
, const char *buf
, int len
) {
102 TRY(result
, write(fd
, buf
, len
));
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
) {
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
));
133 write_all(sockfd
, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 15);
137 snprintf(buf
, 16, "%15d", role
);
138 write_all(sockfd
, buf
, 15);
148 * sigchld_handler: reap any waitable children. Once the child
149 * process exits, send the exit status back over the control socket,
151 void sigchld_handler(int sig
) {
157 pid
= waitpid(-1, &status
, WNOHANG
);
166 /* Our child process exited */
167 if (pid
== child_pid
&& (WIFEXITED(status
) || WIFSIGNALED(status
))) {
169 snprintf(buf
, 30, "%d", status
);
170 write_all(control_fd
, buf
, strlen(buf
) + 1);
176 void check_duplicate_fds(struct fd_info
*fds
, int fd_count
) {
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
) {
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 */
198 /* Check if this file descriptor is still in use by any subsequent
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]. */
204 TRY(fd_new
, dup(fds
[j
].orig_fd
));
205 close(fds
[j
].orig_fd
);
206 fds
[j
].orig_fd
= fd_new
;
210 TRY(result
, dup2(fds
[i
].orig_fd
, fd
));
211 close(fds
[i
].orig_fd
);
215 int main(int argc
, char **argv
) {
218 char *client_key
, *server_key
, *executable
, *workdir
;
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");
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
)
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
)
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");
282 /* Read the entire file into buf. */
285 TRY(file
, open(argv
[1], O_RDONLY
));
286 buf
= read_all(file
, -1, &len
);
289 /* Remove the temporary file */
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
));
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
));
328 fds
[i
].orig_fd
= my_connect(port
, client_key
, fds
[i
].desired_fd
);
332 /* Check server key */
334 int len
= strlen(server_key
);
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");
343 sigprocmask(SIG_BLOCK
, &my_mask
, &my_old_mask
);
345 /* Create the child process */
347 if (child_pid
== 0) {
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. */
358 /* Change to the specified working directory. */
359 if (workdir
[0] != 0) {
360 if (chdir(workdir
) == -1)
364 /* Rearrange file descriptors according to the user specification */
365 setup_fds(fds
, fd_count
);
368 TRY(result
, execv(executable
, my_argv
));
370 } else if (child_pid
== -1) {
373 /* We are in the parent process */
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));
397 /* End of file received: exit without killing child */
401 /* Assume msg == 0 until we support more messages */
402 TRY(count
, kill(child_pid
, SIGTERM
));