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: orig-fd[01], listener,
23  *                            env fds: call(server-end)(fake)
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 // Returns: call(client-end) fd, or -1 to mean "is garbage"
86 // find_socket_path must have been called
87 static int attempt_connect_existing(void) {
88   int r;
89   int fd = -1;
90
91   FILLZERO(sun);
92   sun.sun_family = AF_UNIX;
93   assert(strlen(socket_path) <= sizeof(sun.sun_path));
94   strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
95
96   bool isgarbage = check_garbage();
97   if (isgarbage) goto x_garbage;
98
99   fd = socket(AF_UNIX, SOCK_STREAM, 0);
100   if (fd==-1) diee("socket() for client");
101
102   salen_t salen = sizeof(sun);
103   r = connect(client, (const struct sockaddr*)sun, salen);
104   if (r==-1) {
105     if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
106     diee("connect() %s", socket_path);
107   }
108
109   for (;;) {
110     char ack;
111     sr = read(fd, &ack, 1);
112     if (sr == -1) {
113       if (errno==ECONNRESET) goto x_garbage;
114       if (errno==EINTR) continue;
115       diee("read() ack byte");
116     }
117     if (sr == 0) { goto x_garbage; }
118     if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack);
119     return fd;
120   }
121
122  x_garbage:
123   if (fd >= 0) close(fd);
124   return -1;
125 }
126
127 static int connect_or_spawn(void) {
128   int fd = connect_existing();
129   if (fd >= 0) return fd;
130
131   let acquire_lock();
132     fd = connect_existing();
133
134 int main(int argc, const char *const *argv) {
135   script = process_opts(argc, argv);
136
137   find_socket_path();
138   int fd = connect_or_spawn();
139 }