]> mj.ucw.cz Git - libucw.git/blob - lib/mainloop.c
Added a couple of functions for running of external commands with error
[libucw.git] / lib / mainloop.c
1 /*
2  *      Sherlock Library -- Main Loop
3  *
4  *      (c) 2004 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include "lib/lib.h"
13 #include "lib/mainloop.h"
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <signal.h>
20 #include <fcntl.h>
21 #include <errno.h>
22 #include <time.h>
23 #include <sys/poll.h>
24 #include <sys/wait.h>
25
26 sh_time_t now;
27 uns main_shutdown;
28
29 clist main_timer_list, main_file_list, main_hook_list, main_process_list;
30 static uns main_file_cnt;
31 static uns main_poll_table_obsolete, main_poll_table_size;
32 static struct pollfd *main_poll_table;
33 static uns main_sigchld_set_up;
34
35 void
36 main_init(void)
37 {
38   DBG("MAIN: Initializing");
39   clist_init(&main_timer_list);
40   clist_init(&main_file_list);
41   clist_init(&main_hook_list);
42   clist_init(&main_process_list);
43   now = time(NULL);
44 }
45
46 void
47 timer_add(struct main_timer *tm, sh_time_t expires)
48 {
49   if (tm->expires)
50     clist_remove(&tm->n);
51   tm->expires = expires;
52   if (expires)
53     {
54       cnode *t = main_timer_list.head.next;
55       while (t != &main_timer_list.head && ((struct main_timer *) t)->expires < expires)
56         t = t->next;
57       clist_insert_before(&tm->n, t);
58     }
59 }
60
61 void
62 timer_del(struct main_timer *tm)
63 {
64   timer_add(tm, 0);
65 }
66
67 static void
68 file_timer_expired(struct main_timer *tm)
69 {
70   struct main_file *fi = tm->data;
71   timer_del(&fi->timer);
72   if (fi->error_handler)
73     fi->error_handler(fi, MFERR_TIMEOUT);
74 }
75
76 void
77 file_add(struct main_file *fi)
78 {
79   ASSERT(!fi->n.next);
80   clist_add_tail(&main_file_list, &fi->n);
81   fi->timer.handler = file_timer_expired;
82   fi->timer.data = fi;
83   main_file_cnt++;
84   main_poll_table_obsolete = 1;
85   if (fcntl(fi->fd, F_SETFL, O_NONBLOCK) < 0)
86     log(L_ERROR, "Error setting fd %d to non-blocking mode: %m. Keep fingers crossed.", fi->fd);
87 }
88
89 void
90 file_chg(struct main_file *fi)
91 {
92   struct pollfd *p = fi->pollfd;
93   if (p)
94     {
95       p->events = 0;
96       if (fi->read_handler)
97         p->events |= POLLIN | POLLHUP | POLLERR;
98       if (fi->write_handler)
99         p->events |= POLLOUT | POLLERR;
100     }
101 }
102
103 void
104 file_del(struct main_file *fi)
105 {
106   ASSERT(fi->n.next);
107   timer_del(&fi->timer);
108   clist_remove(&fi->n);
109   main_file_cnt--;
110   main_poll_table_obsolete = 1;
111   fi->n.next = fi->n.prev = NULL;
112 }
113
114 static int
115 file_read_handler(struct main_file *fi)
116 {
117   while (fi->rpos < fi->rlen)
118     {
119       int l = read(fi->fd, fi->rbuf + fi->rpos, fi->rlen - fi->rpos);
120       DBG("MAIN: FD %d: read %d", fi->fd, l);
121       if (l < 0)
122         {
123           if (errno != EINTR && errno != EAGAIN && fi->error_handler)
124             fi->error_handler(fi, MFERR_READ);
125           return 0;
126         }
127       else if (!l)
128         break;
129       fi->rpos += l;
130     }
131   DBG("MAIN: FD %d done read %d of %d", fi->fd, fi->rpos, fi->rlen);
132   fi->read_handler = NULL;
133   file_chg(fi);
134   fi->read_done(fi);
135   return 1;
136 }
137
138 static int
139 file_write_handler(struct main_file *fi)
140 {
141   while (fi->wpos < fi->wlen)
142     {
143       int l = write(fi->fd, fi->wbuf + fi->wpos, fi->wlen - fi->wpos);
144       DBG("MAIN: FD %d: write %d", fi->fd, l);
145       if (l < 0)
146         {
147           if (errno != EINTR && errno != EAGAIN && fi->error_handler)
148             fi->error_handler(fi, MFERR_WRITE);
149           return 0;
150         }
151       fi->wpos += l;
152     }
153   DBG("MAIN: FD %d done write %d", fi->fd, fi->wpos);
154   fi->write_handler = NULL;
155   file_chg(fi);
156   fi->write_done(fi);
157   return 1;
158 }
159
160 void
161 file_read(struct main_file *fi, void *buf, uns len)
162 {
163   ASSERT(fi->n.next);
164   if (len)
165     {
166       fi->read_handler = file_read_handler;
167       fi->rbuf = buf;
168       fi->rpos = 0;
169       fi->rlen = len;
170     }
171   else
172     {
173       fi->read_handler = NULL;
174       fi->rbuf = NULL;
175       fi->rpos = fi->rlen = 0;
176     }
177   file_chg(fi);
178 }
179
180 void
181 file_write(struct main_file *fi, void *buf, uns len)
182 {
183   ASSERT(fi->n.next);
184   if (len)
185     {
186       fi->write_handler = file_write_handler;
187       fi->wbuf = buf;
188       fi->wpos = 0;
189       fi->wlen = len;
190     }
191   else
192     {
193       fi->write_handler = NULL;
194       fi->wbuf = NULL;
195       fi->wpos = fi->wlen = 0;
196     }
197   file_chg(fi);
198 }
199
200 void
201 file_set_timeout(struct main_file *fi, sh_time_t expires)
202 {
203   ASSERT(fi->n.next);
204   timer_add(&fi->timer, expires);
205 }
206
207 void
208 hook_add(struct main_hook *ho)
209 {
210   ASSERT(!ho->n.next);
211   clist_add_tail(&main_hook_list, &ho->n);
212 }
213
214 void
215 hook_del(struct main_hook *ho)
216 {
217   ASSERT(ho->n.next);
218   clist_remove(&ho->n);
219   ho->n.next = ho->n.prev = NULL;
220 }
221
222 static void
223 main_sigchld_handler(int x UNUSED)
224 {
225   DBG("SIGCHLD received");
226 }
227
228 void
229 process_add(struct main_process *mp)
230 {
231   ASSERT(!mp->n.next);
232   ASSERT(mp->handler);
233   clist_add_tail(&main_process_list, &mp->n);
234   if (!main_sigchld_set_up)
235     {
236       struct sigaction sa;
237       bzero(&sa, sizeof(sa));
238       sa.sa_handler = main_sigchld_handler;
239       sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
240       sigaction(SIGCHLD, &sa, NULL);
241       main_sigchld_set_up = 1;
242     }
243 }
244
245 void
246 process_del(struct main_process *mp)
247 {
248   ASSERT(mp->n.next);
249   clist_remove(&mp->n);
250   mp->pid = 0;
251   mp->n.next = NULL;
252 }
253
254 int
255 process_fork(struct main_process *mp)
256 {
257   pid_t pid = fork();
258   if (pid < 0)
259     return -1;
260   else if (!pid)
261     return 0;
262   else
263     {
264       DBG("MAIN: Forked process %d", (int) pid);
265       mp->pid = pid;
266       process_add(mp);
267       return 1;
268     }
269 }
270
271 void
272 main_debug(void)
273 {
274 #ifdef DEBUG
275   log(L_DEBUG, "### Main loop status on %d", (int)now);
276   log(L_DEBUG, "\tActive timers:");
277   struct main_timer *tm;
278   CLIST_WALK(tm, main_timer_list)
279     log(L_DEBUG, "\t\t%p (expires %d, data %p)", tm, (int)tm->expires, tm->data);
280   struct main_file *fi;
281   log(L_DEBUG, "\tActive files:");
282   CLIST_WALK(fi, main_file_list)
283     log(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, eh %p, expires %d, data %p)",
284         fi, fi->fd, fi->read_handler, fi->write_handler, fi->error_handler, fi->timer.expires, fi->data);
285   log(L_DEBUG, "\tActive hooks:");
286   struct main_hook *ho;
287   CLIST_WALK(ho, main_hook_list)
288     log(L_DEBUG, "\t\t%p (func %p, data %p)", ho, ho->handler, ho->data);
289   log(L_DEBUG, "\tActive processes:");
290   struct main_process *pr;
291   CLIST_WALK(pr, main_process_list)
292     log(L_DEBUG, "\t\t%p (pid %d, data %p)", pr, pr->pid, pr->data);
293 #endif
294 }
295
296 static void
297 main_rebuild_poll_table(void)
298 {
299   struct main_file *fi;
300   if (main_poll_table_size < main_file_cnt)
301     {
302       if (main_poll_table)
303         xfree(main_poll_table);
304       else
305         main_poll_table_size = 1;
306       while (main_poll_table_size < main_file_cnt)
307         main_poll_table_size *= 2;
308       main_poll_table = xmalloc(sizeof(struct pollfd) * main_poll_table_size);
309     }
310   struct pollfd *p = main_poll_table;
311   DBG("MAIN: Rebuliding poll table: %d of %d entries set", main_file_cnt, main_poll_table_size);
312   CLIST_WALK(fi, main_file_list)
313     {
314       p->fd = fi->fd;
315       fi->pollfd = p++;
316       file_chg(fi);
317     }
318   main_poll_table_obsolete = 0;
319 }
320
321 void
322 main_loop(void)
323 {
324   DBG("MAIN: Entering main_loop");
325   ASSERT(main_timer_list.head.next);
326
327   struct main_file *fi;
328   struct main_hook *ho;
329   struct main_timer *tm;
330   struct main_process *pr;
331   cnode *tmp;
332
333   while (!main_shutdown)
334     {
335       now = time(NULL);
336       sh_time_t wake = now + 60;
337       while ((tm = clist_head(&main_timer_list)) && tm->expires <= now)
338         {
339           DBG("MAIN: Timer %p expired", tm);
340           tm->handler(tm);
341         }
342       CLIST_WALK_DELSAFE(ho, main_hook_list, tmp)
343         {
344           DBG("MAIN: Hook %p", ho);
345           if (ho->handler(ho))
346             wake = 0;
347         }
348       if (main_poll_table_obsolete)
349         main_rebuild_poll_table();
350       if (!clist_empty(&main_process_list))
351         {
352           int stat;
353           pid_t pid;
354           while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
355             {
356               DBG("MAIN: Child %d exited with status %x", pid, stat);
357               CLIST_WALK(pr, main_process_list)
358                 if (pr->pid == pid)
359                   {
360                     pr->status = stat;
361                     process_del(pr);
362                     if (WIFEXITED(stat) && WEXITSTATUS(stat) < 256)
363                       {
364                         if (WEXITSTATUS(stat))
365                           sprintf(pr->status_msg, "died with exit code %d", WEXITSTATUS(stat));
366                         else
367                           pr->status_msg[0] = 0;
368                       }
369                     else if (WIFSIGNALED(stat))
370                       sprintf(pr->status_msg, "died on signal %d", WTERMSIG(stat));
371                     else
372                       sprintf(pr->status_msg, "died with status %x", stat);
373                     DBG("MAIN: Calling process exit handler");
374                     pr->handler(pr);
375                     break;
376                   }
377               wake = 0;
378             }
379         }
380       /* FIXME: Here is a small race window where SIGCHLD can come unnoticed. */
381       if ((tm = clist_head(&main_timer_list)) && tm->expires < wake)
382         wake = tm->expires;
383       int timeout = (wake ? (wake - now) * 1000 : 0);
384       DBG("MAIN: Poll for %d fds and timeout %d ms", main_file_cnt, timeout);
385       if (poll(main_poll_table, main_file_cnt, timeout))
386         {
387           struct pollfd *p = main_poll_table;
388           now = time(NULL);
389           CLIST_WALK(fi, main_file_list)
390             {
391               if (p->revents & (POLLIN | POLLHUP | POLLERR))
392                 {
393                   do
394                     DBG("MAIN: Read event on fd %d", p->fd);
395                   while (fi->read_handler && fi->read_handler(fi) && !main_poll_table_obsolete);
396                   if (main_poll_table_obsolete) /* File entries have been inserted or deleted => better not risk continuing to nowhere */
397                     break;
398                 }
399               if (p->revents & (POLLOUT | POLLERR))
400                 {
401                   do
402                     DBG("MAIN: Write event on fd %d", p->fd);
403                   while (fi->write_handler && fi->write_handler(fi) && !main_poll_table_obsolete);
404                   if (main_poll_table_obsolete)
405                     break;
406                 }
407               p++;
408             }
409         }
410     }
411 }
412
413 #ifdef TEST
414
415 static struct main_process mp;
416 static struct main_file fin, fout;
417 static struct main_hook hook;
418 static struct main_timer tm;
419
420 static byte rb[16];
421
422 static void dread(struct main_file *fi)
423 {
424   if (fi->rpos < fi->rlen)
425     {
426       log(L_INFO, "Read EOF");
427       file_del(fi);
428     }
429   else
430     {
431       log(L_INFO, "Read done");
432       file_read(fi, rb, sizeof(rb));
433     }
434 }
435
436 static void derror(struct main_file *fi, int cause)
437 {
438   log(L_INFO, "Error: %m !!! (cause %d)", cause);
439   file_del(fi);
440 }
441
442 static void dwrite(struct main_file *fi UNUSED)
443 {
444   log(L_INFO, "Write done");
445 }
446
447 static int dhook(struct main_hook *ho UNUSED)
448 {
449   log(L_INFO, "Hook called");
450   return 0;
451 }
452
453 static void dtimer(struct main_timer *tm)
454 {
455   log(L_INFO, "Timer tick");
456   timer_add(tm, now + 10);
457 }
458
459 static void dentry(struct main_process *pr UNUSED)
460 {
461   log(L_INFO, "*** SUBPROCESS START ***");
462   sleep(2);
463   log(L_INFO, "*** SUBPROCESS FINISH ***");
464 }
465
466 static void dexit(struct main_process *pr)
467 {
468   log(L_INFO, "Subprocess %d exited with status %x", pr->pid, pr->status);
469 }
470
471 int
472 main(void)
473 {
474   log_init(NULL);
475   main_init();
476
477   fin.fd = 0;
478   fin.read_done = dread;
479   fin.error_handler = derror;
480   file_add(&fin);
481   file_read(&fin, rb, sizeof(rb));
482
483   fout.fd = 1;
484   fout.write_done = dwrite;
485   fout.error_handler = derror;
486   file_add(&fout);
487   file_write(&fout, "Hello, world!\n", 14);
488
489   hook.handler = dhook;
490   hook_add(&hook);
491
492   tm.handler = dtimer;
493   timer_add(&tm, now + 1);
494
495   mp.handler = dexit;
496   if (!process_fork(&mp))
497     dentry();
498
499   main_debug();
500
501   main_loop();
502   log(L_INFO, "Finished.");
503 }
504
505 #endif