chiark / gitweb /
f37c8fdcb367f1b39993976109b9874b338a2f68
[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  *     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           [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  *                            implicitly closed 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 // Returns: call(client-end) fd, or -1 to mean "is garbage"
132 // find_socket_path must have been called
133 static int attempt_connect_existing(void) {
134   int r;
135   int fd = -1;
136
137   bool isgarbage = check_garbage();
138   if (isgarbage) goto x_garbage;
139
140   fd = socket(AF_UNIX, SOCK_STREAM, 0);
141   if (fd==-1) diee("socket() for client");
142
143   salen_t salen = sizeof(sun);
144   r = connect(client, (const struct sockaddr*)&socket_sun, salen);
145   if (r==-1) {
146     if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
147     diee("connect() %s", socket_path);
148   }
149
150   for (;;) {
151     char ack;
152     sr = read(fd, &ack, 1);
153     if (sr == -1) {
154       if (errno==ECONNRESET) goto x_garbage;
155       if (errno==EINTR) continue;
156       diee("read() ack byte");
157     }
158     if (sr == 0) { goto x_garbage; }
159     if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack);
160     return fd;
161   }
162
163  x_garbage:
164   if (fd >= 0) close(fd);
165   return -1;
166 }
167
168 static void become_setup(int sfd, int fake_pair[2])
169   __attribute__((noreturn))
170 {
171   close(fake_pair[0]);
172   int call_fd = fake_pair[1];
173
174   int fd0_save = dup(0);  if (fd0_save < 0) diee("dup stdin");
175   int fd1_save = dup(1);  if (fd1_save < 0) diee("dup stdin");
176
177   int null_0 = open("/dev/null", O_RDONLY);  if (null_0 < 0) diee("open null");
178   if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin");
179   if (dup2(2, 1) != 1) die("dup2 stderr onto stdout");
180
181   putenv(asprintf("PREFORK_INTERP=%d,%d,%d,%d,%s",
182                   sfd, call_fd, fd0_save, fd1_save, socket_path));
183   execv(
184 }
185
186 static int connect_or_spawn(void) {
187   int fd = connect_existing();
188   if (fd >= 0) return fd;
189
190   int lockfd = acquire_lock();
191   fd = connect_existing();
192   if (fd >= 0) { close(lockfd); return fd; }
193
194   // We must start a fresh one, and we hold the lock
195
196   r = unlink(socketpath);
197   if (r<0) diee("failed to remove stale socket %s", socketpath);
198
199   int fake_pair[2];
200   r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair);
201   if (r<0) diee("socketpair() for fake initial connection");
202
203   int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
204   if (sfd<0) diee("socket() for new listener");
205
206   salen_t salen = sizeof(sun);
207   r= bind(sfd, (const struct sockaddr*)&socket_sun, saledn);
208   if (r<0) diee("bind() on new listener");
209
210   // We never want callers to get ECONNREFUSED!.
211   // There is a race here: from my RTFM they may get ECONNREFUSED
212   // if they tr between our bind() and listen().  But if they do, they'll
213   // acquire the lock (serialising with us) and retry, and then it will work.
214   r = listen(sfd, INT_MAX);
215   if (r<0) diee("listen() for new listener");
216
217   pid_t setup_pid = fork();
218   if (setup_pid == (pid_t)-1) diee("fork for spawn setup");
219   if (!setup_pid) become_setup(sfd, fake_pair);
220   close(fake_pair[1]);
221   close(sfd);
222
223   int status;
224   pid_t got = waitpid(setup_pid, &status, 0);
225   if (got == (pid_t)-1) diee("waitpid setup [%ld]", (long)setup_pid);
226   if (got != setup_pid) diee("waitpid setup [%ld] gave [%ld]!",
227                              (long)setup_pid, (long)got);
228   if (status != 0) propagate_exit_status(status);
229
230   close(lockfd);
231   return fake_pair[0];
232 }
233
234 int main(int argc, const char *const *argv) {
235   script = process_opts(argc, argv);
236
237   find_socket_path();
238   FILLZERO(sun);
239   sun.sun_family = AF_UNIX;
240   assert(strlen(socket_path) <= sizeof(sun.sun_path));
241   strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
242
243   int call_fd = connect_or_spawn();
244 }