chiark / gitweb /
prefork-interp: wip
[chiark-utils.git] / 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  *                            forks to logger(1)
29  *                            forks for server, now becomes like monitor below
30  *                            exits
31  *
32  *         server (script)    fd0: null, fd[12]: syslog
33  *                            other fds: orig-fd[01], listener,
34  *                            other fds: call(server-end)(fake)
35  *
36  *                            right away, forks one fake-accepted monitor:
37  *          f-a monitor       [fd0: null, fd[12]: syslgo]
38  *                            other fds: call(server-end)(fake)
39  *                            runs as monitor, below
40  *         
41  *         [server (script)]  fd0: null, fd[12]: syslog
42  *                            other fds: listener
43  *                            closes fds: orig-fd[01], call(server-end)fake)
44  *                            runs in loop accepting and forking,
45  *                            reaping and limiting children
46  *                            reports failures of monitors to syslog
47  *
48  *  [client (C wrapper)]      if client connect succeeds:
49  *                            now fd: call(client-end)
50  *                               sends message with: cmdline, env
51  *                               sends fds
52  *
53  *        [server (script)]   accepts, forks monitor
54  *
55  *          monitor           [fd0: null, fd[12]: syslgo]
56  *                            other fds: call(server-end)
57  *                            sends ack byte
58  *                            receives args, env, fds
59  *                            forks executor
60  *
61  *            executor        sorts out fds:
62  *                            fd0, fd1, fd2: from-outer-caller
63  *                            close fds: call(server-end)
64  *                            implicitly closed fds: syslog
65  *
66  *                            sets cmdline, env
67  *                            runs main part of script
68  *                            exits normally
69  *
70  *          [monitor]         [fd0: null, fd[12]: syslgo]
71  *                            [other fds: call(server-end)]
72  *                            reaps executor
73  *                            reports status via socket
74  *
75  *    [client (C wrapper)]    [fd0, fd1, fd2: from-outer-caller]
76  *                            [other fd: call(client-end)]
77  *                            receives status, exits appropriately
78  *                            (if was bad signal, reports to stderr, exits 127)
79  */
80
81 struct sockaddr_un sun;
82
83 #define ACK_BYTE '\n'
84
85 static struct sockaddr_unix socket_sun;
86
87 static void propagate_exit_status(int status, const char *what) {
88   int r;
89
90   if (WIFEXITED(status)) {
91     _exit(status);
92   }
93
94   if (WIFSIGNALED(status)) {
95     int sig = WTERMSIG(status);
96     char *signame = strsignal(sig);
97     if (signame == 0) signame = "unknown signal";
98
99     if (! WCOREDUMP(status) &&
100         (sig == SIGINT ||
101          sig == SIGHUP ||
102          sig == SIGPIPE ||
103          sig == SIGKILL)) {
104       struct sigaction sa;
105       FILLZERO(sa);
106       sa.sa_handler = SIG_DFL;
107       r = sigaction(sig, &sa, 0);
108       if (r) diee("failed to reset signal handler while propagating %s",
109                   signame);
110       
111       sigset_t sset;
112       sigemptyset(&sset);
113       sigaddset(&sset, sig);
114       r = sigprocmask(SA_UNBLOCK, sset, 0);
115       if (r) diee("failed to reset signal block while propagating %s",
116                   signame);
117
118       raise(sig);
119       die("unexpectedly kept running after raising (to propagate) %s",
120           signame);
121     }
122
123     die("setup failed due to signal %d %s%s", sig, signame,
124         WCOREDUMP(status) ? " (core dumped)" : "");
125   }
126
127   die("setup failed with weird wait status %d 0x%x", status, status);
128 }
129
130 // Returns: call(client-end) fd, or -1 to mean "is garbage"
131 // find_socket_path must have been called
132 static int attempt_connect_existing(void) {
133   int r;
134   int fd = -1;
135
136   bool isgarbage = check_garbage();
137   if (isgarbage) goto x_garbage;
138
139   fd = socket(AF_UNIX, SOCK_STREAM, 0);
140   if (fd==-1) diee("socket() for client");
141
142   salen_t salen = sizeof(sun);
143   r = connect(client, (const struct sockaddr*)&socket_sun, salen);
144   if (r==-1) {
145     if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
146     diee("connect() %s", socket_path);
147   }
148
149   for (;;) {
150     char ack;
151     sr = read(fd, &ack, 1);
152     if (sr == -1) {
153       if (errno==ECONNRESET) goto x_garbage;
154       if (errno==EINTR) continue;
155       diee("read() ack byte");
156     }
157     if (sr == 0) { goto x_garbage; }
158     if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack);
159     return fd;
160   }
161
162  x_garbage:
163   if (fd >= 0) close(fd);
164   return -1;
165 }
166
167 static void become_setup(int sfd, int fake_pair[2])
168   __attribute__((noreturn))
169 {
170   close(fake_pair[0]);
171   int call_fd = fake_pair[1];
172
173   int fd0_save = dup(0);  if (fd0_save < 0) diee("dup stdin");
174   int fd1_save = dup(1);  if (fd1_save < 0) diee("dup stdin");
175
176   int null_0 = open("/dev/null", O_RDONLY);  if (null_0 < 0) diee("open null");
177   if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin");
178   if (dup2(2, 1) != 1) die("dup2 stderr onto stdout");
179
180   putenv(asprintf("PREFORK_INTERP_FDS=%d,%d,%d,%d",
181                   sfd, call_fd, fd0_save, fd1_save));
182   execv(
183 }
184
185 static int connect_or_spawn(void) {
186   int fd = connect_existing();
187   if (fd >= 0) return fd;
188
189   int lockfd = acquire_lock();
190   fd = connect_existing();
191   if (fd >= 0) { close(lockfd); return fd; }
192
193   // We must start a fresh one, and we hold the lock
194
195   r = unlink(socketpath);
196   if (r<0) diee("failed to remove stale socket %s", socketpath);
197
198   int fake_pair[2];
199   r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair);
200   if (r<0) diee("socketpair() for fake initial connection");
201
202   int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
203   if (sfd<0) diee("socket() for new listener");
204
205   salen_t salen = sizeof(sun);
206   r= bind(sfd, (const struct sockaddr*)&socket_sun, saledn);
207   if (r<0) diee("bind() on new listener");
208
209   // We never want callers to get ECONNREFUSED!.
210   // There is a race here: from my RTFM they may get ECONNREFUSED
211   // if they tr between our bind() and listen().  But if they do, they'll
212   // acquire the lock (serialising with us) and retry, and then it will work.
213   r = listen(sfd, INT_MAX);
214   if (r<0) diee("listen() for new listener");
215
216   pid_t setup_pid = fork();
217   if (setup_pid == (pid_t)-1) diee("fork for spawn setup");
218   if (!setup_pid) become_setup(sfd, fake_pair);
219   close(fake_pair[1]);
220   close(sfd);
221
222   int status;
223   pid_t got = waitpid(setup_pid, &status, 0);
224   if (got == (pid_t)-1) diee("waitpid setup [%ld]", (long)setup_pid);
225   if (got != setup_pid) diee("waitpid setup [%ld] gave [%ld]!",
226                              (long)setup_pid, (long)got);
227   if (status != 0) propagate_exit_status(status);
228
229   close(lockfd);
230   return fake_pair[0];
231 }
232
233 int main(int argc, const char *const *argv) {
234   script = process_opts(argc, argv);
235
236   find_socket_path();
237   FILLZERO(sun);
238   sun.sun_family = AF_UNIX;
239   assert(strlen(socket_path) <= sizeof(sun.sun_path));
240   strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
241
242   int call_fd = connect_or_spawn();
243 }