chiark / gitweb /
prefork-interp: wip
[chiark-utils.git] / cprogs / prefork-interp.c
1 /*
2  * "Interpreter" that you can put in #! like this
3  *   #!/usr/bin/prefork-interp [<options>] <interpreter>
4  */
5
6 /*
7  * Process structure:
8  *  client (C wrapper)        connects to server
9  *                              (including reading ack byte)
10  *                            if fails or garbage
11  *                            === acquire lock ===
12  *                               makes new listening socket
13  *                               makes first-instance socketpair
14  *                            forks setup (script, sock fds indicated in env)
15  *                            fd0, fd1, fd2: from-outer-caller
16  *                            other fd: call(client-end)(fake)
17  *                            reaps setup (and reports error)
18  *                            (implicitly releases lock)
19  *
20  *     setup (pre-exec)       fd0: null,
21  *                            fd[12: fd2-from-outer-caller
22  *                            env fds: listener, call(server-end)(fake)
23  *                            env fds: orig-fd[01]
24  *                            close fd: lockfile
25  *
26  *     setup (script)         runs initialisation parts of the script
27  *                            at prefork establishment point:
28  *     setup (pm) [1]         opens syslog
29  *                            forks for server
30  *                [2]         exits
31  *
32  #        server (pm) [1]     [fd0: null],
33  *                            [fd[12: fd2-from-outer-caller]
34  *                            right away, forks one fa-monitor
35  *                    [2]     closes outer caller fds and call(fake)
36  *        [server (pm)]       fd[012]: null
37  *                            other fds: listener, syslog
38  *                            runs in loop accepting and forking,
39  *                            reaping and limiting children (incl fa-monitor)
40  *                            reports failures of monitors to syslog
41  *                            
42  *         f-a monitor        forks executor
43  *                            closes fd: listener
44  *                            [fd[12: fd2-from-outer-caller]
45  *                            [other fds: call(server-end)(fake), syslog]
46  *                            runs as monitor, below
47  *
48  *
49  *  [client (C wrapper)]      if client connect succeeds:
50  *                            now fd: call(client-end)
51  *                               sends message with: cmdline, env
52  *                               sends fds
53  *
54  *        [server (script)]   accepts, forks monitor
55  *
56  *          monitor [1]       [fd[012]: null]
57  *                            other fds: syslog, call(server-end)
58  *                            sends ack byte
59  *                            receives args, env, fds
60  *                            forks executor
61  *
62  *            executor        sorts out fds:
63  *                            fd0, fd1, fd2: from-outer-caller
64  *                            close fds: call(server-end)
65  *                            retained fds: syslog
66  *
67  *                            sets cmdline, env
68  *                            runs main part of script
69  *                            exits normally
70  *
71  *          [monitor]         [fd[012]: null]
72  *                            [other fds: call(server-end), syslog]
73  *                            reaps executor
74  *                            reports status via socket
75  *
76  *    [client (C wrapper)]    [fd0, fd1, fd2: from-outer-caller]
77  *                            [other fd: call(client-end)]
78  *                            receives status, exits appropriately
79  *                            (if was bad signal, reports to stderr, exits 127)
80  */
81
82 struct sockaddr_un sun;
83
84 #define ACK_BYTE '\n'
85
86 static struct sockaddr_unix socket_sun;
87
88 static void propagate_exit_status(int status, const char *what) {
89   int r;
90
91   if (WIFEXITED(status)) {
92     _exit(status);
93   }
94
95   if (WIFSIGNALED(status)) {
96     int sig = WTERMSIG(status);
97     char *signame = strsignal(sig);
98     if (signame == 0) signame = "unknown signal";
99
100     if (! WCOREDUMP(status) &&
101         (sig == SIGINT ||
102          sig == SIGHUP ||
103          sig == SIGPIPE ||
104          sig == SIGKILL)) {
105       struct sigaction sa;
106       FILLZERO(sa);
107       sa.sa_handler = SIG_DFL;
108       r = sigaction(sig, &sa, 0);
109       if (r) diee("failed to reset signal handler while propagating %s",
110                   signame);
111       
112       sigset_t sset;
113       sigemptyset(&sset);
114       sigaddset(&sset, sig);
115       r = sigprocmask(SA_UNBLOCK, sset, 0);
116       if (r) diee("failed to reset signal block while propagating %s",
117                   signame);
118
119       raise(sig);
120       die("unexpectedly kept running after raising (to propagate) %s",
121           signame);
122     }
123
124     die("setup failed due to signal %d %s%s", sig, signame,
125         WCOREDUMP(status) ? " (core dumped)" : "");
126   }
127
128   die("setup failed with weird wait status %d 0x%x", status, status);
129 }
130
131 static void die_data_overflow __attribute((noreturn)) {
132   die("cannot handle data with length >2^32");
133 }
134
135 static void prepare_data(size_t *len, char **buf,
136                          const void *data, size_t dl) {
137   if (len) {
138     if (dl >= SIZE_MAX - *len)
139       die_data_overlow();
140     *len += dl;
141   }
142   if (buf) {
143     memcpy(*buf, data, dl);
144     *buf += dl;
145   }
146 }
147   
148 static void prepare_length(size_t *len, char **buf, size_t dl) {
149   if (dl > UINT32_MAX) die_data_overflow();
150   uint32_t dl = htonl(dl);
151   prepare_data(len, buf, &dl, sizeof(dl));
152 }
153
154 static void prepare_string(size_t *len, char **buf, const char *string) {
155   size_t sl = strlen(s);
156   prepare_data(len, buf, s, sl+1);
157 }
158
159 static void prepare_message(size_t *len, char **buf,
160                             const char *const *argv) {
161   const char *s;
162
163   const char *const *p = environ;
164   while ((s = *p++)) {
165     if (strchr(s, '='))
166       prepare_string(len, buf, s);
167   }
168
169   prepare_string(len, buf, "");
170
171   p = argv;
172   while ((s = *p++))
173     prepare_string(len, buf, s);
174 }
175
176 static void send_fd(int via_fd, int payload_fd) {
177   union {
178     struct cmsghdr align;
179     char buf[CMSG_SPACE(sizeof(payload_fd))];
180   } msg;
181   struct msghdr msg;
182   FILLZERO(msg);
183
184   struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
185   cmsg->cmsg_level = SOL_SOCKET;
186   cmsg->cmsg_type = SCM_RIGHTS;
187   cmsg->cmsg_len = CMSG_LEN(sizeof(payload_fd));
188   *(int*)CMSG_DATA(cmsg) = payload_fd;
189
190   char dummy_byte = 0;
191
192   struct iovec iov;
193   FIULLZERO(iov);
194   iov.iov_base = &dummy_byte;
195   iov.iov_len = 1;
196
197   msg.msg_name = 0;
198   msg.msg_iov = &iov;
199   msg.msg_iovlen = 1;
200   msg.msg_control = msg.buf;
201   msg.msg_controllen = sizeof(msg.buf);
202
203   for (;;) {
204     ssize_t r = sendmsg(via_fd, &msg, 0);
205     if (r == -1) {
206       if (errno == EINTR) continue;
207       diee("send fd");
208     }
209     assert!(r == 1);
210     break;
211   }
212 }
213
214 static void send_request(int call_fd, const char *const *argv) {
215   // Sending these first makes it easier for the script to
216   // use buffered IO for the message.
217   send_fd(call_fd, 0);
218   send_fd(call_fd, 1);
219   send_fd(call_fd, 2);
220
221   size_t len = 4;
222   prepare_message(&len, 0, argv);
223   char *m = malloc(len);
224   if (!m) diee("failed to allocate for message");
225   char *p = m;
226   prepare_length(0, &p, len - 4);
227   prepare_message(0, &p, argv);
228   assert(p == m + len);
229
230   p = m;
231   while (len) {
232     ssize_t r = write(call_fd, p, len);
233     if (r==-1) {
234       if (errno == EINTR) continue;
235       diee("write request");
236     }
237     assert(r <= len);
238     assert(r > 0);
239     len -= r;
240     p += r;
241   }
242 }
243
244 // Returns: call(client-end) fd, or -1 to mean "is garbage"
245 // find_socket_path must have been called
246 static int connect_existing(void) {
247   int r;
248   int fd = -1;
249
250   bool isgarbage = check_garbage();
251   if (isgarbage) goto x_garbage;
252
253   fd = socket(AF_UNIX, SOCK_STREAM, 0);
254   if (fd==-1) diee("socket() for client");
255
256   salen_t salen = sizeof(sun);
257   r = connect(client, (const struct sockaddr*)&socket_sun, salen);
258   if (r==-1) {
259     if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
260     diee("connect() %s", socket_path);
261   }
262
263   for (;;) {
264     char ack;
265     sr = read(fd, &ack, 1);
266     if (sr == -1) {
267       if (errno==ECONNRESET) goto x_garbage;
268       if (errno==EINTR) continue;
269       diee("read() ack byte");
270     }
271     if (sr == 0) { goto x_garbage; }
272     if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack);
273     break;
274   }
275
276   // We're committed now, send the request (or bail out)
277   send_request(call, argv);
278
279   return fd;
280
281  x_garbage:
282   if (fd >= 0) close(fd);
283   return -1;
284 }
285
286 static void become_setup(int sfd, int fake_pair[2])
287   __attribute__((noreturn))
288 {
289   close(fake_pair[0]);
290   int call_fd = fake_pair[1];
291
292   int fd0_save = dup(0);  if (fd0_save < 0) diee("dup stdin");
293   int fd1_save = dup(1);  if (fd1_save < 0) diee("dup stdin");
294
295   int null_0 = open("/dev/null", O_RDONLY);  if (null_0 < 0) diee("open null");
296   if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin");
297   if (dup2(2, 1) != 1) die("dup2 stderr onto stdout");
298
299   putenv(asprintf("PREFORK_INTERP=%d,%d,%d,%d,%s",
300                   sfd, call_fd, fd0_save, fd1_save, socket_path));
301   execv(
302 }
303
304 static int connect_or_spawn(void) {
305   int fd = connect_existing();
306   if (fd >= 0) return fd;
307
308   int lockfd = acquire_lock();
309   fd = connect_existing();
310   if (fd >= 0) { close(lockfd); return fd; }
311
312   // We must start a fresh one, and we hold the lock
313
314   r = unlink(socketpath);
315   if (r<0) diee("failed to remove stale socket %s", socketpath);
316
317   int fake_pair[2];
318   r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair);
319   if (r<0) diee("socketpair() for fake initial connection");
320
321   int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
322   if (sfd<0) diee("socket() for new listener");
323
324   salen_t salen = sizeof(sun);
325   r= bind(sfd, (const struct sockaddr*)&socket_sun, saledn);
326   if (r<0) diee("bind() on new listener");
327
328   // We never want callers to get ECONNREFUSED!.
329   // There is a race here: from my RTFM they may get ECONNREFUSED
330   // if they tr between our bind() and listen().  But if they do, they'll
331   // acquire the lock (serialising with us) and retry, and then it will work.
332   r = listen(sfd, INT_MAX);
333   if (r<0) diee("listen() for new listener");
334
335   pid_t setup_pid = fork();
336   if (setup_pid == (pid_t)-1) diee("fork for spawn setup");
337   if (!setup_pid) become_setup(sfd, fake_pair);
338   close(fake_pair[1]);
339   close(sfd);
340
341   int status;
342   pid_t got = waitpid(setup_pid, &status, 0);
343   if (got == (pid_t)-1) diee("waitpid setup [%ld]", (long)setup_pid);
344   if (got != setup_pid) diee("waitpid setup [%ld] gave [%ld]!",
345                              (long)setup_pid, (long)got);
346   if (status != 0) propagate_exit_status(status);
347
348   close(lockfd);
349   return fake_pair[0];
350 }
351
352 int main(int argc, const char *const *argv) {
353   script = process_opts(argc, argv);
354
355   find_socket_path();
356   FILLZERO(sun);
357   sun.sun_family = AF_UNIX;
358   assert(strlen(socket_path) <= sizeof(sun.sun_path));
359   strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
360
361   int call_fd = connect_or_spawn();
362 }