chiark / gitweb /
changelog: Describe 0.3.0~beta2
[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     if (*nfds_io<1) {
187         *nfds_io=1;
188         return ERANGE;
189     }
190     *nfds_io=1;
191     fds[0].fd=spr;
192     fds[0].events=POLLIN;
193     return 0;
194 }
195
196 /* Bodge to work around Ubuntu's strict header files */
197 static void discard(int anything) {}
198
199 static afterpoll_fn signal_afterpoll;
200 static void signal_afterpoll(void *st, struct pollfd *fds, int nfds)
201 {
202     uint8_t buf[16];
203     struct signotify *n;
204     sigset_t todo,old;
205
206     if (nfds && (fds->revents & POLLIN)) {
207         discard(read(spr,buf,16));
208                           /* We don't actually care what we read; as
209                              long as there was at least one byte
210                              (which there was) we'll pick up the
211                              signals in the pending set */
212         
213         /* We reset 'pending' before processing any of the signals
214            that were pending so that we don't miss any signals that
215            are delivered partway-through processing (all we assume
216            about signal notification routines is that they handle all
217            the work available at their _start_ and only optionally any
218            work that arrives part-way through their execution). */
219         sigprocmask(SIG_SETMASK,&fullset,&old);
220         todo=pending;
221         sigemptyset(&pending);
222         sigprocmask(SIG_SETMASK,&old,NULL);
223         
224         for (n=sigs; n; n=n->next)
225             if (sigismember(&todo,n->signum))
226                 n->notify(n->cst,n->signum);
227     }
228 }
229
230 static void set_default_signals(void)
231 {
232     struct signotify *n;
233     sigset_t done;
234     struct sigaction sa;
235
236     sigemptyset(&done);
237     for (n=sigs; n; n=n->next)
238         if (!sigismember(&done,n->signum)) {
239             sigaddset(&done,n->signum);
240             sa.sa_handler=SIG_DFL;
241             sa.sa_mask=emptyset;
242             sa.sa_flags=0;
243             sigaction(n->signum,&sa,NULL);
244         }
245 }
246
247 static void signal_handler(int signum)
248 {
249     int saved_errno;
250     uint8_t thing=0;
251     sigaddset(&pending,signum);
252     /* XXX the write() may set errno, which can make the main program fail.
253        However, signal handlers aren't allowed to modify anything which
254        is not of type sig_atomic_t.  The world is broken. */
255     /* I have decided to save and restore errno anyway; on most
256        architectures on which secnet can run modifications to errno
257        will be atomic, and it seems to be the lesser of the two
258        evils. */
259     saved_errno=errno;
260     discard(write(spw,&thing,1));
261                          /* We don't care if this fails (i.e. the pipe
262                             is full) because the service routine will
263                             spot the pending signal anyway */
264     errno=saved_errno;
265 }
266
267 static void register_signal_handler(struct signotify *s)
268 {
269     struct sigaction sa;
270     int rv;
271
272     if (!signal_handling) return;
273
274     if (sigismember(&registered,s->signum)) return;
275     sigaddset(&registered,s->signum);
276
277     sa.sa_handler=signal_handler;
278     sa.sa_mask=fullset;
279     sa.sa_flags=0;
280     rv=sigaction(s->signum,&sa,NULL);
281     if (rv!=0) {
282         fatal_perror("register_signal_handler: sigaction(%d)",s->signum);
283     }
284 }
285
286 void request_signal_notification(int signum, signal_notify_fn *notify,
287                                  void *cst)
288 {
289     struct signotify *s;
290     sigset_t old;
291
292     s=safe_malloc(sizeof(*s),"request_signal_notification");
293     s->signum=signum;
294     s->notify=notify;
295     s->cst=cst;
296     s->next=sigs;
297     sigprocmask(SIG_SETMASK,&fullset,&old);
298     sigs=s;
299     register_signal_handler(s);
300     sigprocmask(SIG_SETMASK,&old,NULL);
301 }
302
303 void start_signal_handling(void)
304 {
305     int p[2];
306     struct signotify *i;
307
308     sigemptyset(&emptyset);
309     sigfillset(&fullset);
310     sigemptyset(&registered);
311     sigemptyset(&pending);
312
313     if (pipe(p)!=0) {
314         fatal_perror("start_signal_handling: pipe");
315     }
316     spw=p[1];
317     spr=p[0];
318     if (fcntl(spw, F_SETFL, fcntl(spw, F_GETFL)|O_NONBLOCK)==-1) {
319         fatal_perror("start_signal_handling: fcntl(O_NONBLOCK)");
320     }
321
322     register_for_poll(NULL,signal_beforepoll,signal_afterpoll,1,"signal");
323     signal_handling=True;
324
325     /* Register signal handlers for all the signals we're interested in */
326     for (i=sigs; i; i=i->next) {
327         register_signal_handler(i);
328     }
329
330     request_signal_notification(SIGCHLD,sigchld_handler,NULL);
331 }