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