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