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