2 * "Interpreter" that you can put in #! like this
3 * #!/usr/bin/prefork-interp [<options>] <interpreter>
8 * client (C wrapper) connects to server
9 * (including reading ack byte)
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)
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]
26 * setup (script) runs initialisation parts of the script
27 * at prefork establishment point:
29 * forks for server, now becomes like monitor below
32 * server (script) fd0: null, fd[12]: syslog
33 * other fds: orig-fd[01], listener,
34 * other fds: call(server-end)(fake)
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
41 * [server (script)] fd0: null, fd[12]: syslog
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
48 * [client (C wrapper)] if client connect succeeds:
49 * now fd: call(client-end)
50 * sends message with: cmdline, env
53 * [server (script)] accepts, forks monitor
55 * monitor [fd0: null, fd[12]: syslgo]
56 * other fds: call(server-end)
58 * receives args, env, fds
61 * executor sorts out fds:
62 * fd0, fd1, fd2: from-outer-caller
63 * close fds: call(server-end)
64 * implicitly closed fds: syslog
67 * runs main part of script
70 * [monitor] [fd0: null, fd[12]: syslgo]
71 * [other fds: call(server-end)]
73 * reports status via socket
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)
81 struct sockaddr_un sun;
85 static struct sockaddr_unix socket_sun;
87 static void propagate_exit_status(int status, const char *what) {
90 if (WIFEXITED(status)) {
94 if (WIFSIGNALED(status)) {
95 int sig = WTERMSIG(status);
96 char *signame = strsignal(sig);
97 if (signame == 0) signame = "unknown signal";
99 if (! WCOREDUMP(status) &&
106 sa.sa_handler = SIG_DFL;
107 r = sigaction(sig, &sa, 0);
108 if (r) diee("failed to reset signal handler while propagating %s",
113 sigaddset(&sset, sig);
114 r = sigprocmask(SA_UNBLOCK, sset, 0);
115 if (r) diee("failed to reset signal block while propagating %s",
119 die("unexpectedly kept running after raising (to propagate) %s",
123 die("setup failed due to signal %d %s%s", sig, signame,
124 WCOREDUMP(status) ? " (core dumped)" : "");
127 die("setup failed with weird wait status %d 0x%x", status, status);
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) {
136 bool isgarbage = check_garbage();
137 if (isgarbage) goto x_garbage;
139 fd = socket(AF_UNIX, SOCK_STREAM, 0);
140 if (fd==-1) diee("socket() for client");
142 salen_t salen = sizeof(sun);
143 r = connect(client, (const struct sockaddr*)&socket_sun, salen);
145 if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
146 diee("connect() %s", socket_path);
151 sr = read(fd, &ack, 1);
153 if (errno==ECONNRESET) goto x_garbage;
154 if (errno==EINTR) continue;
155 diee("read() ack byte");
157 if (sr == 0) { goto x_garbage; }
158 if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack);
163 if (fd >= 0) close(fd);
167 static void become_setup(int sfd, int fake_pair[2])
168 __attribute__((noreturn))
171 int call_fd = fake_pair[1];
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");
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");
180 putenv(asprintf("PREFORK_INTERP_FDS=%d,%d,%d,%d",
181 sfd, call_fd, fd0_save, fd1_save));
185 static int connect_or_spawn(void) {
186 int fd = connect_existing();
187 if (fd >= 0) return fd;
189 int lockfd = acquire_lock();
190 fd = connect_existing();
191 if (fd >= 0) { close(lockfd); return fd; }
193 // We must start a fresh one, and we hold the lock
195 r = unlink(socketpath);
196 if (r<0) diee("failed to remove stale socket %s", socketpath);
199 r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair);
200 if (r<0) diee("socketpair() for fake initial connection");
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
203 if (sfd<0) diee("socket() for new listener");
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");
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");
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);
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);
233 int main(int argc, const char *const *argv) {
234 script = process_opts(argc, argv);
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));
242 int call_fd = connect_or_spawn();