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