]> mj.ucw.cz Git - libucw.git/blob - ucw/mainloop.c
0af9bf14ddc21f15c8af9a8ce50be658d1237d0b
[libucw.git] / ucw / mainloop.c
1 /*
2  *      UCW Library -- Main Loop
3  *
4  *      (c) 2004--2010 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 "ucw/lib.h"
13 #include "ucw/heap.h"
14 #include "ucw/mainloop.h"
15 #include "ucw/threads.h"
16 #include "ucw/gary.h"
17
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <signal.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <time.h>
26 #include <sys/poll.h>
27 #include <sys/wait.h>
28 #include <sys/time.h>
29
30 #ifdef CONFIG_UCW_THREADS
31 #include <pthread.h>
32 #define THREAD_SIGMASK pthread_sigmask
33 #else
34 #define THREAD_SIGMASK sigprocmask
35 #endif
36
37 #ifdef CONFIG_UCW_EPOLL
38 #include <sys/epoll.h>
39 #endif
40
41 #define MAIN_TIMER_LESS(x,y) ((x)->expires < (y)->expires)
42 #define MAIN_TIMER_SWAP(heap,a,b,t) (t=heap[a], heap[a]=heap[b], heap[b]=t, heap[a]->index=(a), heap[b]->index=(b))
43
44 #define EPOLL_BUF_SIZE 256
45
46 static void
47 do_main_get_time(struct main_context *m)
48 {
49   struct timeval tv;
50   gettimeofday(&tv, NULL);
51   m->now_seconds = tv.tv_sec;
52   m->now = (timestamp_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
53 }
54
55 struct main_context *
56 main_new(void)
57 {
58   struct main_context *m = xmalloc_zero(sizeof(*m));
59
60   DBG("MAIN: New context");
61   clist_init(&m->file_list);
62   clist_init(&m->file_active_list);
63   clist_init(&m->hook_list);
64   clist_init(&m->hook_done_list);
65   clist_init(&m->process_list);
66   clist_init(&m->signal_list);
67 #ifdef CONFIG_UCW_EPOLL
68   m->epoll_fd = epoll_create(64);
69   if (m->epoll_fd < 0)
70     die("epoll_create() failed: %m");
71   m->epoll_events = xmalloc(EPOLL_BUF_SIZE * sizeof(struct epoll_event *));
72   clist_init(&m->file_recalc_list);
73 #else
74   m->poll_table_obsolete = 1;
75 #endif
76   do_main_get_time(m);
77   sigemptyset(&m->want_signals);
78   m->sig_pipe_recv = m->sig_pipe_send = -1;
79
80   return m;
81 }
82
83 void
84 main_delete(struct main_context *m)
85 {
86   if (m->sigchld_handler)
87     signal_del(m->sigchld_handler);
88   if (m->sig_pipe_file)
89     file_del(m->sig_pipe_file);
90   if (m->sig_pipe_recv >= 0)
91     {
92       close(m->sig_pipe_recv);
93       close(m->sig_pipe_send);
94     }
95   ASSERT(clist_empty(&m->file_list));
96   ASSERT(clist_empty(&m->file_active_list));
97   ASSERT(clist_empty(&m->hook_list));
98   ASSERT(clist_empty(&m->hook_done_list));
99   ASSERT(clist_empty(&m->process_list));
100   ASSERT(clist_empty(&m->signal_list));
101   GARY_FREE(m->timer_table);
102 #ifdef CONFIG_UCW_EPOLL
103   ASSERT(clist_empty(&m->file_recalc_list));
104   xfree(m->epoll_events);
105   close(m->epoll_fd);
106 #else
107   GARY_FREE(m->poll_table);
108   GARY_FREE(m->poll_file_table);
109 #endif
110   xfree(m);
111   // FIXME: Some mechanism for cleaning up after fork()
112 }
113
114 struct main_context *
115 main_switch_context(struct main_context *m)
116 {
117   struct ucwlib_context *c = ucwlib_thread_context();
118   struct main_context *m0 = c->main_context;
119
120   /*
121    *  Not only we need to switch the signal sets of the two contexts,
122    *  but it is also necessary to avoid invoking a signal handler
123    *  in the middle of changing c->main_context.
124    */
125   if (m0 && !clist_empty(&m0->signal_list))
126     THREAD_SIGMASK(SIG_BLOCK, &m0->want_signals, NULL);
127   c->main_context = m;
128   if (m && !clist_empty(&m->signal_list))
129     THREAD_SIGMASK(SIG_UNBLOCK, &m->want_signals, NULL);
130
131   return m0;
132 }
133
134 struct main_context *
135 main_current(void)
136 {
137   struct ucwlib_context *c = ucwlib_thread_context();
138   struct main_context *m = c->main_context;
139   ASSERT(m);
140   return m;
141 }
142
143 void
144 main_init(void)
145 {
146   struct main_context *m = main_switch_context(main_new());
147   ASSERT(!m);
148 }
149
150 void
151 main_cleanup(void)
152 {
153   struct main_context *m = main_switch_context(NULL);
154   main_delete(m);
155 }
156
157 void
158 main_get_time(void)
159 {
160   do_main_get_time(main_current());
161 }
162
163 static inline uns
164 count_timers(struct main_context *m)
165 {
166   if (m->timer_table)
167     return GARY_SIZE(m->timer_table) - 1;
168   else
169     return 0;
170 }
171
172 void
173 timer_add(struct main_timer *tm, timestamp_t expires)
174 {
175   struct main_context *m = main_current();
176
177   if (!m->timer_table)
178     {
179       GARY_INIT(m->timer_table, 1);
180       m->timer_table[0] = NULL;
181     }
182
183   if (expires)
184     DBG("MAIN: Setting timer %p (expire at now+%lld)", tm, (long long)(expires - m->now));
185   else
186     DBG("MAIN: Clearing timer %p", tm);
187   uns num_timers = count_timers(m);
188   if (tm->expires < expires)
189     {
190       if (!tm->expires)
191         {
192           tm->expires = expires;
193           tm->index = num_timers + 1;
194           *GARY_PUSH(m->timer_table, 1) = tm;
195           HEAP_INSERT(struct main_timer *, m->timer_table, tm->index, MAIN_TIMER_LESS, MAIN_TIMER_SWAP);
196         }
197       else
198         {
199           tm->expires = expires;
200           HEAP_INCREASE(struct main_timer *, m->timer_table, num_timers, MAIN_TIMER_LESS, MAIN_TIMER_SWAP, tm->index);
201         }
202     }
203   else if (tm->expires > expires)
204     {
205       if (!expires)
206         {
207           ASSERT(tm->index && tm->index <= num_timers);
208           HEAP_DELETE(struct main_timer *, m->timer_table, num_timers, MAIN_TIMER_LESS, MAIN_TIMER_SWAP, tm->index);
209           tm->index = 0;
210           tm->expires = 0;
211           GARY_POP(m->timer_table, 1);
212         }
213       else
214         {
215           tm->expires = expires;
216           HEAP_DECREASE(struct main_timer *, m->timer_table, num_timers, MAIN_TIMER_LESS, MAIN_TIMER_SWAP, tm->index);
217         }
218     }
219 }
220
221 void
222 timer_add_rel(struct main_timer *tm, timestamp_t expires_delta)
223 {
224   struct main_context *m = main_current();
225   return timer_add(tm, m->now + expires_delta);
226 }
227
228 void
229 timer_del(struct main_timer *tm)
230 {
231   timer_add(tm, 0);
232 }
233
234 static uns
235 file_want_events(struct main_file *fi)
236 {
237   uns events = 0;
238   if (fi->read_handler)
239     events |= POLLIN | POLLHUP | POLLERR;
240   if (fi->write_handler)
241     events |= POLLOUT | POLLERR;
242   return events;
243 }
244
245 void
246 file_add(struct main_file *fi)
247 {
248   struct main_context *m = main_current();
249
250   DBG("MAIN: Adding file %p (fd=%d)", fi, fi->fd);
251   ASSERT(!clist_is_linked(&fi->n));
252   clist_add_tail(&m->file_list, &fi->n);
253   m->file_cnt++;
254 #ifdef CONFIG_UCW_EPOLL
255   struct epoll_event evt = {
256     .events = file_want_events(fi),
257     .data.ptr = fi,
258   };
259   if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, fi->fd, &evt) < 0)
260     die("epoll_ctl() failed: %m");
261   fi->last_want_events = evt.events;
262 #else
263   m->poll_table_obsolete = 1;
264 #endif
265   if (fcntl(fi->fd, F_SETFL, O_NONBLOCK) < 0)
266     msg(L_ERROR, "Error setting fd %d to non-blocking mode: %m. Keep fingers crossed.", fi->fd);
267 }
268
269 void
270 file_chg(struct main_file *fi)
271 {
272 #ifdef CONFIG_UCW_EPOLL
273   clist_remove(&fi->n);
274   clist_add_tail(&main_current()->file_recalc_list, &fi->n);
275 #else
276   struct pollfd *p = fi->pollfd;
277   if (p)
278     p->events = file_want_events(fi);
279 #endif
280 }
281
282 void
283 file_del(struct main_file *fi)
284 {
285   struct main_context *m = main_current();
286
287   DBG("MAIN: Deleting file %p (fd=%d)", fi, fi->fd);
288   ASSERT(clist_is_linked(&fi->n));
289   clist_unlink(&fi->n);
290   m->file_cnt--;
291 #ifdef CONFIG_UCW_EPOLL
292   if (epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, fi->fd, NULL) < 0)
293     die("epoll_ctl() failed: %m");
294 #else
295   m->poll_table_obsolete = 1;
296 #endif
297 }
298
299 void
300 file_close_all(void)
301 {
302   struct main_context *m = main_current();
303
304   CLIST_FOR_EACH(struct main_file *, f, m->file_list)
305     close(f->fd);
306 }
307
308 void
309 hook_add(struct main_hook *ho)
310 {
311   struct main_context *m = main_current();
312
313   DBG("MAIN: Adding hook %p", ho);
314   ASSERT(!clist_is_linked(&ho->n));
315   clist_add_tail(&m->hook_list, &ho->n);
316 }
317
318 void
319 hook_del(struct main_hook *ho)
320 {
321   DBG("MAIN: Deleting hook %p", ho);
322   ASSERT(clist_is_linked(&ho->n));
323   clist_unlink(&ho->n);
324 }
325
326 static void
327 sigchld_received(struct main_signal *sg UNUSED)
328 {
329   struct main_context *m = main_current();
330   int stat;
331   pid_t pid;
332
333   while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
334     {
335       DBG("MAIN: Child %d exited with status %x", pid, stat);
336       CLIST_FOR_EACH(struct main_process *, pr, m->process_list)
337         if (pr->pid == pid)
338           {
339             pr->status = stat;
340             process_del(pr);
341             format_exit_status(pr->status_msg, pr->status);
342             DBG("MAIN: Calling process exit handler");
343             pr->handler(pr);
344             break;
345           }
346     }
347 }
348
349 void
350 process_add(struct main_process *mp)
351 {
352   struct main_context *m = main_current();
353
354   DBG("MAIN: Adding process %p (pid=%d)", mp, mp->pid);
355   ASSERT(!clist_is_linked(&mp->n));
356   ASSERT(mp->handler);
357   clist_add_tail(&m->process_list, &mp->n);
358   if (!m->sigchld_handler)
359     {
360       struct main_signal *sg = xmalloc_zero(sizeof(*sg));
361       m->sigchld_handler = sg;
362       sg->signum = SIGCHLD;
363       sg->handler = sigchld_received;
364       signal_add(sg);
365     }
366 }
367
368 void
369 process_del(struct main_process *mp)
370 {
371   DBG("MAIN: Deleting process %p (pid=%d)", mp, mp->pid);
372   ASSERT(clist_is_linked(&mp->n));
373   clist_unlink(&mp->n);
374 }
375
376 int
377 process_fork(struct main_process *mp)
378 {
379   pid_t pid = fork();
380   if (pid < 0)
381     {
382       DBG("MAIN: Fork failed");
383       mp->status = -1;
384       format_exit_status(mp->status_msg, -1);
385       mp->handler(mp);
386       return 1;
387     }
388   else if (!pid)
389     return 0;
390   else
391     {
392       DBG("MAIN: Forked process %d", (int) pid);
393       mp->pid = pid;
394       process_add(mp);
395       return 1;
396     }
397 }
398
399 static int
400 pipe_read_handler(struct main_file *mf UNUSED)
401 {
402   struct main_context *m = main_current();
403   int signum;
404   int n = read(m->sig_pipe_recv, &signum, sizeof(signum));
405
406   if (n < 0)
407     {
408       if (errno != EAGAIN)
409         msg(L_ERROR, "Error reading signal pipe: %m");
410       return 0;
411     }
412   ASSERT(n == sizeof(signum));
413
414   DBG("MAIN: Sigpipe: received signal %d", signum);
415   struct main_signal iter = { .signum = -1 };
416   struct main_signal *sg = clist_head(&m->signal_list);
417   while (sg)
418     {
419       if (sg->signum == signum)
420         {
421           DBG("MAIN: Sigpipe: invoking handler %p", sg);
422           clist_insert_after(&iter.n, &sg->n);
423           sg->handler(sg);
424           sg = clist_next(&m->signal_list, &iter.n);
425           clist_remove(&iter.n);
426         }
427       else
428         sg = clist_next(&m->signal_list, &sg->n);
429     }
430
431   return 1;
432 }
433
434 static void
435 pipe_configure(int fd)
436 {
437   int flags;
438   if ((flags = fcntl(fd, F_GETFL)) < 0 || fcntl(fd, F_SETFL, flags|O_NONBLOCK) < 0)
439     die("Could not set file descriptor %d to non-blocking: %m", fd);
440 }
441
442 static void
443 pipe_setup(struct main_context *m)
444 {
445   DBG("MAIN: Sigpipe: Setting up the pipe");
446
447   int pipe_result[2];
448   if (pipe(pipe_result) == -1)
449     die("Could not create signal pipe: %m");
450   pipe_configure(pipe_result[0]);
451   pipe_configure(pipe_result[1]);
452   m->sig_pipe_recv = pipe_result[0];
453   m->sig_pipe_send = pipe_result[1];
454
455   struct main_file *f = xmalloc_zero(sizeof(*f));
456   m->sig_pipe_file = f;
457   f->fd = m->sig_pipe_recv;
458   f->read_handler = pipe_read_handler;
459   file_add(f);
460 }
461
462 static void
463 signal_handler_pipe(int signum)
464 {
465   struct main_context *m = main_current();
466 #ifdef LOCAL_DEBUG
467   msg(L_DEBUG | L_SIGHANDLER, "MAIN: Sigpipe: sending signal %d down the drain", signum);
468 #endif
469   write(m->sig_pipe_send, &signum, sizeof(signum));
470 }
471
472 void
473 signal_add(struct main_signal *ms)
474 {
475   struct main_context *m = main_current();
476
477   DBG("MAIN: Adding signal %p (sig=%d)", ms, ms->signum);
478
479   ASSERT(!clist_is_linked(&ms->n));
480   // Adding at the head of the list is better if we are in the middle of walking the list.
481   clist_add_head(&m->signal_list, &ms->n);
482   if (m->sig_pipe_recv < 0)
483     pipe_setup(m);
484
485   struct sigaction sa = {
486     .sa_handler = signal_handler_pipe,
487     .sa_flags = SA_NOCLDSTOP | SA_RESTART,
488   };
489   sigaction(ms->signum, &sa, NULL);
490
491   sigset_t ss;
492   sigemptyset(&ss);
493   sigaddset(&ss, ms->signum);
494   THREAD_SIGMASK(SIG_UNBLOCK, &ss, NULL);
495   sigaddset(&m->want_signals, ms->signum);
496 }
497
498 void
499 signal_del(struct main_signal *ms)
500 {
501   struct main_context *m = main_current();
502
503   DBG("MAIN: Deleting signal %p (sig=%d)", ms, ms->signum);
504
505   ASSERT(clist_is_linked(&ms->n));
506   clist_unlink(&ms->n);
507
508   int another = 0;
509   CLIST_FOR_EACH(struct main_signal *, s, m->signal_list)
510     if (s->signum == ms->signum)
511       another++;
512   if (!another)
513     {
514       sigset_t ss;
515       sigemptyset(&ss);
516       sigaddset(&ss, ms->signum);
517       THREAD_SIGMASK(SIG_BLOCK, &ss, NULL);
518       sigdelset(&m->want_signals, ms->signum);
519     }
520 }
521
522 void
523 main_debug_context(struct main_context *m UNUSED)
524 {
525 #ifdef CONFIG_DEBUG
526   msg(L_DEBUG, "### Main loop status on %lld", (long long) m->now);
527   msg(L_DEBUG, "\tActive timers:");
528   uns num_timers = count_timers(m);
529   for (uns i = 1; i <= num_timers; i++)
530     {
531       struct main_timer *tm = m->timer_table[i];
532       msg(L_DEBUG, "\t\t%p (expires %lld, data %p)", tm, (long long)(tm->expires ? tm->expires - m->now : 999999), tm->data);
533     }
534   msg(L_DEBUG, "\tActive files:");
535   CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
536     msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p)",
537         fi, fi->fd, fi->read_handler, fi->write_handler, fi->data);
538   CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
539     msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p) [pending events: %x]",
540         fi, fi->fd, fi->read_handler, fi->write_handler, fi->data, fi->events);
541     // FIXME: Can we display status of block_io requests somehow?
542 #ifdef CONFIG_UCW_EPOLL
543   CLIST_FOR_EACH(struct main_file *, fi, m->file_recalc_list)
544     msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p) [pending recalculation]",
545         fi, fi->fd, fi->read_handler, fi->write_handler, fi->data);
546 #endif
547   msg(L_DEBUG, "\tActive hooks:");
548   CLIST_FOR_EACH(struct main_hook *, ho, m->hook_done_list)
549     msg(L_DEBUG, "\t\t%p (func %p, data %p)", ho, ho->handler, ho->data);
550   CLIST_FOR_EACH(struct main_hook *, ho, m->hook_list)
551     msg(L_DEBUG, "\t\t%p (func %p, data %p)", ho, ho->handler, ho->data);
552   msg(L_DEBUG, "\tActive processes:");
553   CLIST_FOR_EACH(struct main_process *, pr, m->process_list)
554     msg(L_DEBUG, "\t\t%p (pid %d, func %p, data %p)", pr, pr->pid, pr->handler, pr->data);
555   msg(L_DEBUG, "\tActive signal catchers:");
556   CLIST_FOR_EACH(struct main_signal *, sg, m->signal_list)
557     if (sg->signum < 0)
558       msg(L_DEBUG, "\t\t(placeholder)");
559     else
560       msg(L_DEBUG, "\t\t%p (sig %d, func %p, data %p)", sg, sg->signum, sg->handler, sg->data);
561 #endif
562 }
563
564 static void
565 process_timers(struct main_context *m)
566 {
567   struct main_timer *tm;
568   while (count_timers(m) && (tm = m->timer_table[1])->expires <= m->now)
569     {
570       DBG("MAIN: Timer %p expired at now-%lld", tm, (long long)(m->now - tm->expires));
571       tm->handler(tm);
572     }
573 }
574
575 static enum main_hook_return
576 process_hooks(struct main_context *m)
577 {
578   int hook_min = HOOK_RETRY;
579   int hook_max = HOOK_SHUTDOWN;
580   struct main_hook *ho;
581
582   while (ho = clist_remove_head(&m->hook_list))
583     {
584       clist_add_tail(&m->hook_done_list, &ho->n);
585       DBG("MAIN: Hook %p", ho);
586       int ret = ho->handler(ho);
587       hook_min = MIN(hook_min, ret);
588       hook_max = MAX(hook_max, ret);
589     }
590   clist_move(&m->hook_list, &m->hook_done_list);
591   if (hook_min == HOOK_SHUTDOWN ||
592     hook_min == HOOK_DONE && hook_max == HOOK_DONE ||
593     m->shutdown)
594     {
595       DBG("MAIN: Shut down by %s", m->shutdown ? "main_shutdown" : "a hook");
596       return HOOK_SHUTDOWN;
597     }
598   if (hook_max == HOOK_RETRY)
599     return HOOK_RETRY;
600   else
601     return HOOK_IDLE;
602 }
603
604 #ifdef CONFIG_UCW_EPOLL
605
606 static void
607 recalc_files(struct main_context *m)
608 {
609   struct main_file *fi;
610
611   while (fi = clist_remove_head(&m->file_recalc_list))
612     {
613       struct epoll_event evt = {
614         .events = file_want_events(fi),
615         .data.ptr = fi,
616       };
617       if (evt.events != fi->last_want_events)
618         {
619           DBG("MAIN: Changing requested events for fd %d to %x", fi->fd, evt.events);
620           fi->last_want_events = evt.events;
621           if (epoll_ctl(main_current()->epoll_fd, EPOLL_CTL_MOD, fi->fd, &evt) < 0)
622             die("epoll_ctl() failed: %m");
623         }
624       clist_add_tail(&m->file_list, &fi->n);
625     }
626 }
627
628 #else
629
630 static void
631 rebuild_poll_table(struct main_context *m)
632 {
633   GARY_INIT_OR_RESIZE(m->poll_table, m->file_cnt);
634   GARY_INIT_OR_RESIZE(m->poll_file_table, m->file_cnt);
635   DBG("MAIN: Rebuilding poll table: %d entries", m->file_cnt);
636
637   struct pollfd *p = m->poll_table;
638   struct main_file **pf = m->poll_file_table;
639   CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
640     {
641       p->fd = fi->fd;
642       p->events = file_want_events(fi);
643       fi->pollfd = p++;
644       *pf++ = fi;
645     }
646   m->poll_table_obsolete = 0;
647 }
648
649 #endif
650
651 void
652 main_loop(void)
653 {
654   DBG("MAIN: Entering main_loop");
655   struct main_context *m = main_current();
656
657   do_main_get_time(m);
658   for (;;)
659     {
660       timestamp_t wake = m->now + 1000000000;
661       process_timers(m);
662       switch (process_hooks(m))
663         {
664         case HOOK_SHUTDOWN:
665           return;
666         case HOOK_RETRY:
667           wake = 0;
668           break;
669         default: ;
670         }
671       if (count_timers(m))
672         wake = MIN(wake, m->timer_table[1]->expires);
673       do_main_get_time(m);
674       int timeout = ((wake > m->now) ? wake - m->now : 0);
675
676 #ifdef CONFIG_UCW_EPOLL
677       recalc_files(m);
678       DBG("MAIN: Epoll for %d fds and timeout %d ms", m->file_cnt, timeout);
679       int n = epoll_wait(m->epoll_fd, m->epoll_events, EPOLL_BUF_SIZE, timeout);
680 #else
681       if (m->poll_table_obsolete)
682         rebuild_poll_table(m);
683       DBG("MAIN: Poll for %d fds and timeout %d ms", m->file_cnt, timeout);
684       int n = poll(m->poll_table, m->file_cnt, timeout);
685 #endif
686
687       DBG("\t-> %d events", n);
688       if (n < 0 && errno != EAGAIN && errno != EINTR)
689         die("(e)poll failed: %m");
690       timestamp_t old_now = m->now;
691       do_main_get_time(m);
692       m->idle_time += m->now - old_now;
693
694       if (n <= 0)
695         continue;
696
697       // Relink all files with a pending event to file_active_list
698 #ifdef CONFIG_UCW_EPOLL
699       for (int i=0; i<n; i++)
700         {
701           struct epoll_event *e = &m->epoll_events[i];
702           struct main_file *fi = e->data.ptr;
703           clist_remove(&fi->n);
704           clist_add_tail(&m->file_active_list, &fi->n);
705           fi->events = e->events;
706         }
707 #else
708       struct pollfd *p = m->poll_table;
709       struct main_file **pf = m->poll_file_table;
710       for (uns i=0; i < m->file_cnt; i++)
711         if (p[i].revents)
712           {
713             struct main_file *fi = pf[i];
714             clist_remove(&fi->n);
715             clist_add_tail(&m->file_active_list, &fi->n);
716             fi->events = p[i].revents;
717           }
718 #endif
719
720       // Process the buffered file events
721       struct main_file *fi;
722       while (fi = clist_remove_head(&m->file_active_list))
723         {
724           clist_add_tail(&m->file_list, &fi->n);
725           if (fi->events & (POLLIN | POLLHUP | POLLERR))
726             {
727               do
728                 DBG("MAIN: Read event on fd %d", fi->fd);
729               while (fi->read_handler && fi->read_handler(fi));
730             }
731           if (fi->events & (POLLOUT | POLLERR))
732             {
733               do
734                 DBG("MAIN: Write event on fd %d", fi->fd);
735               while (fi->write_handler && fi->write_handler(fi));
736             }
737         }
738     }
739 }
740
741 #ifdef TEST
742
743 static struct main_process mp;
744 static struct main_block_io fin, fout;
745 static struct main_hook hook;
746 static struct main_timer tm;
747 static struct main_signal sg;
748
749 static byte rb[16];
750
751 static void dread(struct main_block_io *bio)
752 {
753   if (bio->rpos < bio->rlen)
754     {
755       msg(L_INFO, "Read EOF");
756       block_io_del(bio);
757     }
758   else
759     {
760       msg(L_INFO, "Read done");
761       block_io_read(bio, rb, sizeof(rb));
762     }
763 }
764
765 static void derror(struct main_block_io *bio, int cause)
766 {
767   msg(L_INFO, "Error: %m !!! (cause %d)", cause);
768   block_io_del(bio);
769 }
770
771 static void dwrite(struct main_block_io *bio UNUSED)
772 {
773   msg(L_INFO, "Write done");
774 }
775
776 static int dhook(struct main_hook *ho UNUSED)
777 {
778   msg(L_INFO, "Hook called");
779   return 0;
780 }
781
782 static void dtimer(struct main_timer *tm)
783 {
784   msg(L_INFO, "Timer tick");
785   timer_add_rel(tm, 11000);
786   timer_add_rel(tm, 10000);
787 }
788
789 static void dentry(void)
790 {
791   msg(L_INFO, "*** SUBPROCESS START ***");
792   sleep(2);
793   msg(L_INFO, "*** SUBPROCESS FINISH ***");
794   exit(0);
795 }
796
797 static void dexit(struct main_process *pr)
798 {
799   msg(L_INFO, "Subprocess %d exited with status %x", pr->pid, pr->status);
800 }
801
802 static void dsignal(struct main_signal *sg UNUSED)
803 {
804   msg(L_INFO, "SIGINT received (use Ctrl-\\ to really quit)");
805 }
806
807 int
808 main(void)
809 {
810   log_init(NULL);
811   main_init();
812
813   fin.read_done = dread;
814   fin.error_handler = derror;
815   block_io_add(&fin, 0);
816   block_io_read(&fin, rb, sizeof(rb));
817
818   fout.write_done = dwrite;
819   fout.error_handler = derror;
820   block_io_add(&fout, 1);
821   block_io_write(&fout, "Hello, world!\n", 14);
822
823   hook.handler = dhook;
824   hook_add(&hook);
825
826   tm.handler = dtimer;
827   timer_add_rel(&tm,  1000);
828
829   sg.signum = SIGINT;
830   sg.handler = dsignal;
831   signal_add(&sg);
832
833   mp.handler = dexit;
834   if (!process_fork(&mp))
835     dentry();
836
837   main_debug();
838
839   main_loop();
840   msg(L_INFO, "Finished.");
841 }
842
843 #endif