]> mj.ucw.cz Git - libucw.git/blob - lib/mainloop.c
95709a311a0c08743015950e6c84f6160098e26b
[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   clist_add_tail(&main_process_list, &mp->n);
233   if (!main_sigchld_set_up)
234     {
235       struct sigaction sa;
236       bzero(&sa, sizeof(sa));
237       sa.sa_handler = main_sigchld_handler;
238       sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
239       sigaction(SIGCHLD, &sa, NULL);
240       main_sigchld_set_up = 1;
241     }
242 }
243
244 void
245 process_del(struct main_process *mp)
246 {
247   ASSERT(mp->n.next);
248   clist_remove(&mp->n);
249   mp->pid = 0;
250   mp->n.next = NULL;
251 }
252
253 int
254 process_fork(struct main_process *mp)
255 {
256   pid_t pid = fork();
257   if (pid < 0)
258     return -1;
259   else if (!pid)
260     return 0;
261   else
262     {
263       DBG("MAIN: Forked process %d", (int) pid);
264       mp->pid = pid;
265       process_add(mp);
266       return 1;
267     }
268 }
269
270 void
271 main_debug(void)
272 {
273 #ifdef DEBUG
274   log(L_DEBUG, "### Main loop status on %d", (int)now);
275   log(L_DEBUG, "\tActive timers:");
276   struct main_timer *tm;
277   CLIST_WALK(tm, main_timer_list)
278     log(L_DEBUG, "\t\t%p (expires %d, data %p)", tm, (int)tm->expires, tm->data);
279   struct main_file *fi;
280   log(L_DEBUG, "\tActive files:");
281   CLIST_WALK(fi, main_file_list)
282     log(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, eh %p, expires %d, data %p)",
283         fi, fi->fd, fi->read_handler, fi->write_handler, fi->error_handler, fi->timer.expires, fi->data);
284   log(L_DEBUG, "\tActive hooks:");
285   struct main_hook *ho;
286   CLIST_WALK(ho, main_hook_list)
287     log(L_DEBUG, "\t\t%p (func %p, data %p)", ho, ho->handler, ho->data);
288   log(L_DEBUG, "\tActive processes:");
289   struct main_process *pr;
290   CLIST_WALK(pr, main_process_list)
291     log(L_DEBUG, "\t\t%p (pid %d, data %p)", pr, pr->pid, pr->data);
292 #endif
293 }
294
295 static void
296 main_rebuild_poll_table(void)
297 {
298   struct main_file *fi;
299   if (main_poll_table_size < main_file_cnt)
300     {
301       if (main_poll_table)
302         xfree(main_poll_table);
303       else
304         main_poll_table_size = 1;
305       while (main_poll_table_size < main_file_cnt)
306         main_poll_table_size *= 2;
307       main_poll_table = xmalloc(sizeof(struct pollfd) * main_poll_table_size);
308     }
309   struct pollfd *p = main_poll_table;
310   DBG("MAIN: Rebuliding poll table: %d of %d entries set", main_file_cnt, main_poll_table_size);
311   CLIST_WALK(fi, main_file_list)
312     {
313       p->fd = fi->fd;
314       fi->pollfd = p++;
315       file_chg(fi);
316     }
317   main_poll_table_obsolete = 0;
318 }
319
320 void
321 main_loop(void)
322 {
323   DBG("MAIN: Entering main_loop");
324   ASSERT(main_timer_list.head.next);
325
326   struct main_file *fi;
327   struct main_hook *ho;
328   struct main_timer *tm;
329   struct main_process *pr;
330   cnode *tmp;
331
332   while (!main_shutdown)
333     {
334       now = time(NULL);
335       sh_time_t wake = now + 60;
336       while ((tm = clist_head(&main_timer_list)) && tm->expires <= now)
337         {
338           DBG("MAIN: Timer %p expired", tm);
339           tm->handler(tm);
340         }
341       CLIST_WALK_DELSAFE(ho, main_hook_list, tmp)
342         {
343           DBG("MAIN: Hook %p", ho);
344           if (ho->handler(ho))
345             wake = 0;
346         }
347       if (main_poll_table_obsolete)
348         main_rebuild_poll_table();
349       if (!clist_empty(&main_process_list))
350         {
351           int stat;
352           pid_t pid;
353           while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
354             {
355               DBG("MAIN: Child %d exited with status %x", pid, stat);
356               CLIST_WALK(pr, main_process_list)
357                 if (pr->pid == pid)
358                   {
359                     pr->status = stat;
360                     process_del(pr);
361                     if (WIFEXITED(stat) && WEXITSTATUS(stat) < 256)
362                       {
363                         if (WEXITSTATUS(stat))
364                           sprintf(pr->status_msg, "died with exit code %d", WEXITSTATUS(stat));
365                         else
366                           pr->status_msg[0] = 0;
367                       }
368                     else if (WIFSIGNALED(stat))
369                       sprintf(pr->status_msg, "died on signal %d", WTERMSIG(stat));
370                     else
371                       sprintf(pr->status_msg, "died with status %x", stat);
372                     DBG("MAIN: Calling process exit handler");
373                     pr->handler(pr);
374                     break;
375                   }
376               wake = 0;
377             }
378         }
379       /* FIXME: Here is a small race window where SIGCHLD can come unnoticed. */
380       if ((tm = clist_head(&main_timer_list)) && tm->expires < wake)
381         wake = tm->expires;
382       int timeout = (wake ? (wake - now) * 1000 : 0);
383       DBG("MAIN: Poll for %d fds and timeout %d ms", main_file_cnt, timeout);
384       if (poll(main_poll_table, main_file_cnt, timeout))
385         {
386           struct pollfd *p = main_poll_table;
387           now = time(NULL);
388           CLIST_WALK(fi, main_file_list)
389             {
390               if (p->revents & (POLLIN | POLLHUP | POLLERR))
391                 {
392                   do
393                     DBG("MAIN: Read event on fd %d", p->fd);
394                   while (fi->read_handler && fi->read_handler(fi) && !main_poll_table_obsolete);
395                   if (main_poll_table_obsolete) /* File entries have been inserted or deleted => better not risk continuing to nowhere */
396                     break;
397                 }
398               if (p->revents & (POLLOUT | POLLERR))
399                 {
400                   do
401                     DBG("MAIN: Write event on fd %d", p->fd);
402                   while (fi->write_handler && fi->write_handler(fi) && !main_poll_table_obsolete);
403                   if (main_poll_table_obsolete)
404                     break;
405                 }
406               p++;
407             }
408         }
409     }
410 }
411
412 #ifdef TEST
413
414 static struct main_process mp;
415 static struct main_file fin, fout;
416 static struct main_hook hook;
417 static struct main_timer tm;
418
419 static byte rb[16];
420
421 static void dread(struct main_file *fi)
422 {
423   if (fi->rpos < fi->rlen)
424     {
425       log(L_INFO, "Read EOF");
426       file_del(fi);
427     }
428   else
429     {
430       log(L_INFO, "Read done");
431       file_read(fi, rb, sizeof(rb));
432     }
433 }
434
435 static void derror(struct main_file *fi, int cause)
436 {
437   log(L_INFO, "Error: %m !!! (cause %d)", cause);
438   file_del(fi);
439 }
440
441 static void dwrite(struct main_file *fi UNUSED)
442 {
443   log(L_INFO, "Write done");
444 }
445
446 static int dhook(struct main_hook *ho UNUSED)
447 {
448   log(L_INFO, "Hook called");
449   return 0;
450 }
451
452 static void dtimer(struct main_timer *tm)
453 {
454   log(L_INFO, "Timer tick");
455   timer_add(tm, now + 10);
456 }
457
458 static void dentry(struct main_process *pr UNUSED)
459 {
460   log(L_INFO, "*** SUBPROCESS START ***");
461   sleep(2);
462   log(L_INFO, "*** SUBPROCESS FINISH ***");
463 }
464
465 static void dexit(struct main_process *pr)
466 {
467   log(L_INFO, "Subprocess %d exited with status %x", pr->pid, pr->status);
468 }
469
470 int
471 main(void)
472 {
473   log_init(NULL);
474   main_init();
475
476   fin.fd = 0;
477   fin.read_done = dread;
478   fin.error_handler = derror;
479   file_add(&fin);
480   file_read(&fin, rb, sizeof(rb));
481
482   fout.fd = 1;
483   fout.write_done = dwrite;
484   fout.error_handler = derror;
485   file_add(&fout);
486   file_write(&fout, "Hello, world!\n", 14);
487
488   hook.handler = dhook;
489   hook_add(&hook);
490
491   tm.handler = dtimer;
492   timer_add(&tm, now + 1);
493
494   mp.handler = dexit;
495   if (!process_fork(&mp))
496     dentry();
497
498   main_debug();
499
500   main_loop();
501   log(L_INFO, "Finished.");
502 }
503
504 #endif