polypath: Break up child process handling
[secnet] / process.c
1 #define _GNU_SOURCE
2 #include "secnet.h"
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <errno.h>
6 #include <sys/wait.h>
7 #include <string.h>
8 #include "process.h"
9
10 /* Process handling - subprocesses, signals, etc. */
11
12 static bool_t signal_handling=False;
13 static sigset_t emptyset, fullset;
14 static sigset_t registered,pending;
15
16 struct child {
17 pid_t pid;
18 cstring_t desc;
19 process_callback_fn *cb;
20 void *cst;
21 bool_t finished;
22 struct child *next;
23 };
24
25 static struct child *children=NULL;
26
27 struct signotify {
28 int signum;
29 signal_notify_fn *notify;
30 void *cst;
31 struct signotify *next;
32 };
33
34 static struct signotify *sigs=NULL;
35
36 static int spw,spr; /* file descriptors for signal notification pipe */
37
38 /* Long-lived subprocesses can only be started once we've started
39 signal processing so that we can catch SIGCHLD for them and report
40 their exit status using the callback function. We block SIGCHLD
41 until signal processing has begun. */
42 pid_t makesubproc(process_entry_fn *entry, process_callback_fn *cb,
43 void *est, void *cst, cstring_t desc)
44 {
45 struct child *c;
46 pid_t p;
47
48 c=safe_malloc(sizeof(*c),"makesubproc");
49 c->desc=desc;
50 c->cb=cb;
51 c->cst=cst;
52
53 if (!signal_handling) {
54 fatal("makesubproc called before signal handling started");
55 }
56 p=fork();
57 if (p==0) {
58 /* Child process */
59 afterfork();
60 entry(est);
61 abort();
62 } else if (p==-1) {
63 fatal_perror("makesubproc (%s): fork",desc);
64 }
65 c->pid=p;
66 c->finished=False;
67 c->next=children;
68 children=c;
69 return p;
70 }
71
72 static signal_notify_fn sigchld_handler;
73 static void sigchld_handler(void *st, int signum)
74 {
75 struct child *i,*n,**p;
76 struct work {
77 pid_t pid;
78 process_callback_fn *cb;
79 void *cst;
80 int status;
81 struct work *next;
82 };
83 struct work *w=NULL, *nw;
84 pid_t rv;
85 int status;
86
87 for (i=children; i; i=i->next) {
88 rv=waitpid(i->pid,&status,WNOHANG);
89 if (rv==-1) {
90 fatal_perror("sigchld_handler: waitpid");
91 }
92 if (rv==i->pid) {
93 i->finished=True;
94
95 nw=safe_malloc(sizeof(*nw),"sigchld_handler");
96 nw->pid=i->pid;
97 nw->cb=i->cb;
98 nw->cst=i->cst;
99 nw->status=status;
100 nw->next=w;
101 w=nw;
102 }
103 }
104
105 /* Remove all the finished tasks from the list of children */
106 for (i=children, p=&children; i; i=n) {
107 n=i->next;
108 if (i->finished) {
109 free(i);
110 *p=n;
111 } else {
112 p=&i->next;
113 }
114 }
115
116 /* Notify as appropriate, then free the list */
117 while (w) {
118 w->cb(w->cst,w->pid,w->status);
119 nw=w;
120 w=w->next;
121 free(nw);
122 }
123 }
124
125 int sys_cmd(const char *path, const char *arg, ...)
126 {
127 va_list ap;
128 int rv, rc;
129 pid_t c;
130
131 c=fork();
132 if (c) {
133 /* Parent -> wait for child */
134 do {
135 rc = waitpid(c,&rv,0);
136 } while(rc < 0 && errno == EINTR);
137 if (rc < 0)
138 fatal_perror("sys_cmd: waitpid for %s", path);
139 if (rc != c) /* OS has gone mad */
140 fatal("sys_cmd: waitpid for %s returned wrong process ID!",
141 path);
142 if (rv) {
143 /* If the command failed report its exit status */
144 lg_exitstatus(0,"sys_cmd",0,M_ERR,rv,path);
145 }
146 } else if (c==0) {
147 char *args[100];
148 int i;
149 /* Child -> exec command */
150 /* Really we ought to strcpy() the arguments into the args array,
151 since the arguments are const char *. Since we'll exit anyway
152 if the execvp() fails this seems somewhat pointless, and
153 increases the chance of the child process failing before it
154 gets to exec(). */
155 afterfork();
156 va_start(ap,arg);
157 args[0]=(char *)arg; /* program name */
158 i=1;
159 while ((args[i++]=va_arg(ap,char *)));
160 execvp(path,args);
161 fprintf(stderr, "sys_cmd(%s,%s,...): %s\n", path, arg, strerror(errno));
162 _exit(1);
163 } else {
164 /* Error */
165 fatal_perror("sys_cmd(%s,%s,...)", path, arg);
166 }
167
168 return rv;
169 }
170
171 static beforepoll_fn signal_beforepoll;
172 static int signal_beforepoll(void *st, struct pollfd *fds, int *nfds_io,
173 int *timeout_io)
174 {
175 BEFOREPOLL_WANT_FDS(1);
176 fds[0].fd=spr;
177 fds[0].events=POLLIN;
178 return 0;
179 }
180
181 /* Bodge to work around Ubuntu's strict header files */
182 static void discard(int anything) {}
183
184 static afterpoll_fn signal_afterpoll;
185 static void signal_afterpoll(void *st, struct pollfd *fds, int nfds)
186 {
187 uint8_t buf[16];
188 struct signotify *n;
189 sigset_t todo,old;
190
191 if (nfds && (fds->revents & POLLIN)) {
192 discard(read(spr,buf,16));
193 /* We don't actually care what we read; as
194 long as there was at least one byte
195 (which there was) we'll pick up the
196 signals in the pending set */
197
198 /* We reset 'pending' before processing any of the signals
199 that were pending so that we don't miss any signals that
200 are delivered partway-through processing (all we assume
201 about signal notification routines is that they handle all
202 the work available at their _start_ and only optionally any
203 work that arrives part-way through their execution). */
204 sigprocmask(SIG_SETMASK,&fullset,&old);
205 todo=pending;
206 sigemptyset(&pending);
207 sigprocmask(SIG_SETMASK,&old,NULL);
208
209 for (n=sigs; n; n=n->next)
210 if (sigismember(&todo,n->signum))
211 n->notify(n->cst,n->signum);
212 }
213 }
214
215 void afterfork(void)
216 {
217 struct signotify *n;
218 sigset_t done;
219 struct sigaction sa;
220
221 clear_phase_hooks(PHASE_SHUTDOWN);
222 /* Prevents calls to fatal() etc. in the child from running off
223 and doing a lot of unhelpful things */
224
225 sigemptyset(&done);
226 for (n=sigs; n; n=n->next)
227 if (!sigismember(&done,n->signum)) {
228 sigaddset(&done,n->signum);
229 sa.sa_handler=SIG_DFL;
230 sa.sa_mask=emptyset;
231 sa.sa_flags=0;
232 sigaction(n->signum,&sa,NULL);
233 }
234
235 sigemptyset(&emptyset);
236 sigprocmask(SIG_SETMASK,&emptyset,NULL);
237 }
238
239 static void signal_handler(int signum)
240 {
241 int saved_errno;
242 uint8_t thing=0;
243 sigaddset(&pending,signum);
244 /* XXX the write() may set errno, which can make the main program fail.
245 However, signal handlers aren't allowed to modify anything which
246 is not of type sig_atomic_t. The world is broken. */
247 /* I have decided to save and restore errno anyway; on most
248 architectures on which secnet can run modifications to errno
249 will be atomic, and it seems to be the lesser of the two
250 evils. */
251 saved_errno=errno;
252 discard(write(spw,&thing,1));
253 /* We don't care if this fails (i.e. the pipe
254 is full) because the service routine will
255 spot the pending signal anyway */
256 errno=saved_errno;
257 }
258
259 static void register_signal_handler(struct signotify *s)
260 {
261 struct sigaction sa;
262 int rv;
263
264 if (!signal_handling) return;
265
266 if (sigismember(&registered,s->signum)) return;
267 sigaddset(&registered,s->signum);
268
269 sa.sa_handler=signal_handler;
270 sa.sa_mask=fullset;
271 sa.sa_flags=0;
272 rv=sigaction(s->signum,&sa,NULL);
273 if (rv!=0) {
274 fatal_perror("register_signal_handler: sigaction(%d)",s->signum);
275 }
276 }
277
278 void request_signal_notification(int signum, signal_notify_fn *notify,
279 void *cst)
280 {
281 struct signotify *s;
282 sigset_t old;
283
284 s=safe_malloc(sizeof(*s),"request_signal_notification");
285 s->signum=signum;
286 s->notify=notify;
287 s->cst=cst;
288 s->next=sigs;
289 sigprocmask(SIG_SETMASK,&fullset,&old);
290 sigs=s;
291 register_signal_handler(s);
292 sigprocmask(SIG_SETMASK,&old,NULL);
293 }
294
295 void start_signal_handling(void)
296 {
297 int p[2];
298 struct signotify *i;
299
300 sigemptyset(&emptyset);
301 sigfillset(&fullset);
302 sigemptyset(&registered);
303 sigemptyset(&pending);
304
305 pipe_cloexec(p);
306 spw=p[1];
307 spr=p[0];
308 setnonblock(spw);
309 setnonblock(spr);
310
311 register_for_poll(NULL,signal_beforepoll,signal_afterpoll,"signal");
312 signal_handling=True;
313
314 /* Register signal handlers for all the signals we're interested in */
315 for (i=sigs; i; i=i->next) {
316 register_signal_handler(i);
317 }
318
319 request_signal_notification(SIGCHLD,sigchld_handler,NULL);
320 }