updates.
[crypto.git] / lib / silcutil / unix / silcunixschedule.c
1 /*
2
3   silcunixschedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1998 - 2001 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23
24 /* Structure holding list of file descriptors, scheduler is supposed to
25    be listenning. The max_fd field is the maximum number of possible file
26    descriptors in the list. This value is set at the initialization
27    of the scheduler and it usually is the maximum number of connections 
28    allowed. */
29 typedef struct {
30   int *fd;
31   uint32 last_fd;
32   uint32 max_fd;
33 } SilcScheduleFdList;
34
35 /* 
36    SILC Unix Scheduler structure.
37
38    This is the actual schedule object in SILC. Both SILC client and server 
39    uses this same scheduler. Actually, this scheduler could be used by any
40    program needing scheduling.
41
42    Following short description of the fields:
43
44    SilcTaskQueue fd_queue
45
46        Task queue hook for non-timeout tasks. Usually this means that these
47        tasks perform different kind of I/O on file descriptors. File 
48        descriptors are usually network sockets but they actually can be
49        any file descriptors. This hook is initialized in silc_schedule_init
50        function. Timeout tasks should not be added to this queue because
51        they will never expire.
52
53    SilcTaskQueue timeout_queue
54
55        Task queue hook for timeout tasks. This hook is reserved specificly
56        for tasks with timeout. Non-timeout tasks should not be added to this
57        queue because they will never get scheduled. This hook is also
58        initialized in silc_schedule_init function.
59
60    SilcTaskQueue generic_queue
61
62        Task queue hook for generic tasks. This hook is reserved specificly
63        for generic tasks, tasks that apply to all file descriptors, except
64        to those that have specificly registered a non-timeout task. This hook
65        is also initialized in silc_schedule_init function.
66
67    SilcScheduleFdList fd_list
68
69        List of file descriptors the scheduler is supposed to be listenning.
70        This is updated internally.
71
72    struct timeval *timeout;
73
74        Pointer to the schedules next timeout. Value of this timeout is
75        automatically updated in the silc_schedule function.
76
77    int valid
78
79        Marks validity of the scheduler. This is a boolean value. When this
80        is false the scheduler is terminated and the program will end. This
81        set to true when the scheduler is initialized with silc_schedule_init
82        function.
83
84    fd_set in
85    fd_set out
86
87        File descriptor sets for select(). These are automatically managed
88        by the scheduler and should not be touched otherwise.
89
90    int max_fd
91
92        Number of maximum file descriptors for select(). This, as well, is
93        managed automatically by the scheduler and should be considered to 
94        be read-only field otherwise.
95
96 */
97 struct SilcScheduleStruct {
98   SilcTaskQueue fd_queue;
99   SilcTaskQueue timeout_queue;
100   SilcTaskQueue generic_queue;
101   SilcScheduleFdList fd_list;
102   struct timeval *timeout;
103   int valid;
104   fd_set in;
105   fd_set out;
106   int max_fd;
107 };
108
109 /* Initializes the scheduler. Sets the non-timeout task queue hook and
110    the timeout task queue hook. This must be called before the scheduler
111    is able to work. This will allocate the queue pointers if they are
112    not allocated. Returns the scheduler context that must be freed by
113    the silc_schedule_uninit function. */
114
115 SilcSchedule silc_schedule_init(SilcTaskQueue *fd_queue,
116                                 SilcTaskQueue *timeout_queue,
117                                 SilcTaskQueue *generic_queue,
118                                 int max_fd)
119 {
120   SilcSchedule schedule;
121   int i;
122
123   SILC_LOG_DEBUG(("Initializing scheduler"));
124
125   schedule = silc_calloc(1, sizeof(*schedule));
126
127   /* Register the task queues if they are not registered already. In SILC
128      we have by default three task queues. One task queue for non-timeout
129      tasks which perform different kind of I/O on file descriptors, timeout
130      task queue for timeout tasks, and, generic non-timeout task queue whose
131      tasks apply to all connections. */
132   if (!*fd_queue)
133     silc_task_queue_alloc(schedule, fd_queue, TRUE);
134   if (!*timeout_queue)
135     silc_task_queue_alloc(schedule, timeout_queue, TRUE);
136   if (!*generic_queue)
137     silc_task_queue_alloc(schedule, generic_queue, TRUE);
138
139   /* Initialize the scheduler */
140   schedule->fd_queue = *fd_queue;
141   schedule->timeout_queue = *timeout_queue;
142   schedule->generic_queue = *generic_queue;
143   schedule->fd_list.fd = silc_calloc(max_fd, sizeof(int));
144   schedule->fd_list.last_fd = 0;
145   schedule->fd_list.max_fd = max_fd;
146   schedule->timeout = NULL;
147   schedule->valid = TRUE;
148   FD_ZERO(&schedule->in);
149   FD_ZERO(&schedule->out);
150   schedule->max_fd = -1;
151   for (i = 0; i < max_fd; i++)
152     schedule->fd_list.fd[i] = -1;
153
154   return schedule;
155 }
156
157 /* Uninitializes the schedule. This is called when the program is ready
158    to end. This removes all tasks and task queues. Returns FALSE if the
159    scheduler could not be uninitialized. This happens when the scheduler
160    is still valid and silc_schedule_stop has not been called. */
161
162 bool silc_schedule_uninit(SilcSchedule schedule)
163 {
164
165   SILC_LOG_DEBUG(("Uninitializing scheduler"));
166
167   if (schedule->valid == TRUE)
168     return FALSE;
169
170   /* Unregister all tasks */
171   if (schedule->fd_queue)
172     silc_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
173   if (schedule->timeout_queue)
174     silc_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
175   if (schedule->generic_queue)
176     silc_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
177
178   /* Unregister all task queues */
179   if (schedule->fd_queue)
180     silc_task_queue_free(schedule->fd_queue);
181   if (schedule->timeout_queue)
182     silc_task_queue_free(schedule->timeout_queue);
183   if (schedule->generic_queue)
184     silc_task_queue_free(schedule->generic_queue);
185
186   /* Clear the fd list */
187   if (schedule->fd_list.fd) {
188     memset(schedule->fd_list.fd, -1, schedule->fd_list.max_fd);
189     silc_free(schedule->fd_list.fd);
190   }
191
192   memset(&schedule, 'F', sizeof(schedule));
193   return TRUE;
194 }
195
196 /* Stops the schedule even if it is not supposed to be stopped yet. 
197    After calling this, one should call silc_schedule_uninit (after the 
198    silc_schedule has returned). */
199
200 void silc_schedule_stop(SilcSchedule schedule)
201 {
202   SILC_LOG_DEBUG(("Stopping scheduler"));
203
204   if (schedule->valid == TRUE)
205     schedule->valid = FALSE;
206 }
207
208 /* Sets a file descriptor to be listened by select() in scheduler. One can
209    call this directly if wanted. This can be called multiple times for
210    one file descriptor to set different iomasks. */
211
212 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
213 {
214   schedule->fd_list.fd[fd] = iomask;
215   
216   if (fd > schedule->fd_list.last_fd)
217     schedule->fd_list.last_fd = fd;
218 }
219
220 /* Removes a file descriptor from listen list. */
221
222 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
223 {
224   schedule->fd_list.fd[fd] = -1;
225   
226   if (fd == schedule->fd_list.last_fd) {
227     int i;
228
229     for (i = fd; i >= 0; i--)
230       if (schedule->fd_list.fd[i] != -1)
231         break;
232
233     schedule->fd_list.last_fd = i < 0 ? 0 : i;
234   }
235 }
236
237 /* Executes tasks matching the file descriptor set by select(). The task
238    remains on the task queue after execution. Invalid tasks are removed 
239    here from the task queue. This macro is used by silc_schedule function. 
240    We don't have to care about the tasks priority here because the tasks
241    are sorted in their priority order already at the registration phase. */
242
243 #define SILC_SCHEDULE_RUN_TASKS                                            \
244 do {                                                                       \
245   queue = schedule->fd_queue;                                              \
246   if (queue && queue->valid == TRUE && queue->task) {                      \
247     task = queue->task;                                                    \
248                                                                            \
249     /* Walk thorugh all tasks in the particular task queue and             \
250        execute the callback functions of those tasks matching the          \
251        fd set by select(). */                                              \
252     while(1) {                                                             \
253       /* Validity of the task is checked always before and after           \
254          execution beacuse the task might have been unregistered           \
255          in the callback function, ie. it is not valid anymore. */         \
256                                                                            \
257       if (task->valid) {                                                   \
258         /* Task ready for reading */                                       \
259         if ((FD_ISSET(task->fd, &schedule->in)) &&                         \
260             (task->iomask & (1L << SILC_TASK_READ))) {                     \
261           task->callback(queue, SILC_TASK_READ, task->context, task->fd);  \
262           is_run = TRUE;                                                   \
263         }                                                                  \
264       }                                                                    \
265                                                                            \
266       if (task->valid) {                                                   \
267         /* Task ready for writing */                                       \
268         if ((FD_ISSET(task->fd, &schedule->out)) &&                        \
269             (task->iomask & (1L << SILC_TASK_WRITE))) {                    \
270           task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
271           is_run = TRUE;                                                   \
272         }                                                                  \
273       }                                                                    \
274                                                                            \
275       if (!task->valid) {                                                  \
276         /* Invalid (unregistered) tasks are removed from the               \
277            task queue. */                                                  \
278         if (queue->task == task->next) {                                   \
279           silc_task_remove(queue, task);                                   \
280           break;                                                           \
281         }                                                                  \
282                                                                            \
283         task = task->next;                                                 \
284         silc_task_remove(queue, task->prev);                               \
285         continue;                                                          \
286       }                                                                    \
287                                                                            \
288       /* Break if there isn't more tasks in the queue */                   \
289       if (queue->task == task->next)                                       \
290         break;                                                             \
291                                                                            \
292       task = task->next;                                                   \
293     }                                                                      \
294   }                                                                        \
295 } while(0)
296
297 /* Selects tasks to be listened by select(). These are the non-timeout
298    tasks. This checks the scheduler's fd list. This macro is used by 
299    silc_schedule function. */
300
301 #define SILC_SCHEDULE_SELECT_TASKS                              \
302 do {                                                            \
303   for (i = 0; i <= schedule->fd_list.last_fd; i++) {            \
304     if (schedule->fd_list.fd[i] != -1) {                                \
305                                                                 \
306       /* Set the max fd value for select() to listen */         \
307       if (i > schedule->max_fd)                                 \
308         schedule->max_fd = i;                                   \
309                                                                 \
310       /* Add tasks for reading */                               \
311       if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ)))   \
312         FD_SET(i, &schedule->in);                               \
313                                                                 \
314       /* Add tasks for writing */                               \
315       if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE)))  \
316         FD_SET(i, &schedule->out);                              \
317     }                                                           \
318   }                                                             \
319 } while(0)
320
321 /* Executes all tasks whose timeout has expired. The task is removed from
322    the task queue after the callback function has returned. Also, invalid
323    tasks are removed here. The current time must be get before calling this
324    macro. This macro is used by silc_schedule function. We don't have to
325    care about priorities because tasks are already sorted in their priority
326    order at the registration phase. */
327
328 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS                                 \
329 do {                                                                    \
330   queue = schedule->timeout_queue;                                      \
331   if (queue && queue->valid == TRUE && queue->task) {                   \
332     task = queue->task;                                                 \
333                                                                         \
334     /* Walk thorugh all tasks in the particular task queue              \
335        and run all the expired tasks. */                                \
336     while(1) {                                                          \
337       /* Execute the task if the timeout has expired */                 \
338       if (silc_task_timeout_compare(&task->timeout, &curtime)) {        \
339                                                                         \
340         /* Task ready for reading */                                    \
341         if (task->valid) {                                              \
342           if ((task->iomask & (1L << SILC_TASK_READ)))                  \
343             task->callback(queue, SILC_TASK_READ,                       \
344                            task->context, task->fd);                    \
345         }                                                               \
346                                                                         \
347         /* Task ready for writing */                                    \
348         if (task->valid) {                                              \
349           if ((task->iomask & (1L << SILC_TASK_WRITE)))                 \
350             task->callback(queue, SILC_TASK_WRITE,                      \
351                            task->context, task->fd);                    \
352         }                                                               \
353                                                                         \
354         /* Break if there isn't more tasks in the queue */              \
355         if (queue->task == task->next) {                                \
356           /* Remove the task from queue */                              \
357           silc_task_remove(queue, task);                                \
358           break;                                                        \
359         }                                                               \
360                                                                         \
361         task = task->next;                                              \
362                                                                         \
363         /* Remove the task from queue */                                \
364         silc_task_remove(queue, task->prev);                            \
365       } else {                                                          \
366         /* The timeout hasn't expired, check for next one */            \
367                                                                         \
368         /* Break if there isn't more tasks in the queue */              \
369         if (queue->task == task->next)                                  \
370           break;                                                        \
371                                                                         \
372         task = task->next;                                              \
373       }                                                                 \
374     }                                                                   \
375   }                                                                     \
376 } while(0)
377
378 /* Calculates next timeout for select(). This is the timeout value
379    when at earliest some of the timeout tasks expire. If this is in the
380    past, they will be run now. This macro is used by the silc_schedule
381    function. */
382
383 #define SILC_SCHEDULE_SELECT_TIMEOUT                                        \
384 do {                                                                        \
385   if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) {    \
386     queue = schedule->timeout_queue;                                        \
387     task = NULL;                                                            \
388                                                                             \
389     /* Get the current time */                                              \
390     gettimeofday(&curtime, NULL);                                           \
391     schedule->timeout = NULL;                                               \
392                                                                             \
393     /* First task in the task queue has always the smallest timeout. */     \
394     task = queue->task;                                                     \
395     while(1) {                                                              \
396       if (task && task->valid == TRUE) {                                    \
397                                                                             \
398         /* If the timeout is in past, we will run the task and all other    \
399            timeout tasks from the past. */                                  \
400         if (silc_task_timeout_compare(&task->timeout, &curtime)) {          \
401           SILC_SCHEDULE_RUN_TIMEOUT_TASKS;                                  \
402                                                                             \
403           /* The task(s) has expired and doesn't exist on the task queue    \
404              anymore. We continue with new timeout. */                      \
405           queue = schedule->timeout_queue;                                  \
406           task = queue->task;                                               \
407           if (task == NULL || task->valid == FALSE)                         \
408             break;                                                          \
409           goto cont;                                                        \
410         } else {                                                            \
411  cont:                                                                      \
412           /* Calculate the next timeout for select() */                     \
413           queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;    \
414           queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
415           if (queue->timeout.tv_sec < 0)                                    \
416             queue->timeout.tv_sec = 0;                                      \
417                                                                             \
418           /* We wouldn't want to go under zero, check for it. */            \
419           if (queue->timeout.tv_usec < 0) {                                 \
420             queue->timeout.tv_sec -= 1;                                     \
421             if (queue->timeout.tv_sec < 0)                                  \
422               queue->timeout.tv_sec = 0;                                    \
423             queue->timeout.tv_usec += 1000000L;                             \
424           }                                                                 \
425         }                                                                   \
426         /* We've got the timeout value */                                   \
427         break;                                                              \
428       } else {                                                              \
429         /* Task is not valid, remove it and try next one. */                \
430         silc_task_remove(queue, task);                                      \
431         task = queue->task;                                                 \
432         if (queue->task == NULL)                                            \
433           break;                                                            \
434       }                                                                     \
435     }                                                                       \
436     /* Save the timeout */                                                  \
437     if (task)                                                               \
438       schedule->timeout = &queue->timeout;                                  \
439   }                                                                         \
440 } while(0)
441
442 /* Execute generic tasks. These are executed only and only if for the
443    specific fd there wasn't other non-timeout tasks. This checks the earlier
444    set fd list, thus the generic tasks apply to all specified fd's. All the
445    generic tasks are executed at once. */
446
447 #define SILC_SCHEDULE_RUN_GENERIC_TASKS                                      \
448 do {                                                                         \
449   if (is_run == FALSE) {                                                     \
450     SILC_LOG_DEBUG(("Running generic tasks"));                               \
451     for (i = 0; i <= schedule->fd_list.last_fd; i++)                         \
452       if (schedule->fd_list.fd[i] != -1) {                                   \
453                                                                              \
454         /* Check whether this fd is select()ed. */                           \
455         if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) {   \
456                                                                              \
457           /* It was selected. Now find the tasks from task queue and execute \
458              all generic tasks. */                                           \
459           if (schedule->generic_queue && schedule->generic_queue->valid) {     \
460             queue = schedule->generic_queue;                                 \
461                                                                              \
462             if (!queue->task)                                                \
463               break;                                                         \
464                                                                              \
465             task = queue->task;                                              \
466                                                                              \
467             while(1) {                                                       \
468               /* Validity of the task is checked always before and after     \
469                  execution beacuse the task might have been unregistered     \
470                  in the callback function, ie. it is not valid anymore. */   \
471                                                                              \
472               if (task->valid && schedule->fd_list.fd[i] != -1) {            \
473                 /* Task ready for reading */                                 \
474                 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ)))      \
475                   task->callback(queue, SILC_TASK_READ,                      \
476                                  task->context, i);                          \
477               }                                                              \
478                                                                              \
479               if (task->valid && schedule->fd_list.fd[i] != -1) {            \
480                 /* Task ready for writing */                                 \
481                 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE)))             \
482                   task->callback(queue, SILC_TASK_WRITE,                     \
483                                  task->context, i);                          \
484               }                                                              \
485                                                                              \
486               if (!task->valid) {                                            \
487                 /* Invalid (unregistered) tasks are removed from the         \
488                    task queue. */                                            \
489                 if (queue->task == task->next) {                             \
490                   silc_task_remove(queue, task);                             \
491                   break;                                                     \
492                 }                                                            \
493                                                                              \
494                 task = task->next;                                           \
495                 silc_task_remove(queue, task->prev);                         \
496                 continue;                                                    \
497               }                                                              \
498                                                                              \
499               /* Break if there isn't more tasks in the queue */             \
500               if (queue->task == task->next)                                 \
501                 break;                                                       \
502                                                                              \
503               task = task->next;                                             \
504             }                                                                \
505           }                                                                  \
506         }                                                                    \
507       }                                                                      \
508   }                                                                          \
509 } while(0)
510
511 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
512 {
513   struct timeval timeout;
514   int is_run, i;
515   SilcTask task;
516   SilcTaskQueue queue;
517   struct timeval curtime;
518
519   SILC_LOG_DEBUG(("In scheduler loop"));
520
521   /* If the task queues aren't initialized or we aren't valid anymore
522      we will return */
523   if ((!schedule->fd_queue && !schedule->timeout_queue 
524        && !schedule->generic_queue) || schedule->valid == FALSE) {
525     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
526     return FALSE;
527   }
528
529   /* Clear everything */
530   FD_ZERO(&schedule->in);
531   FD_ZERO(&schedule->out);
532   schedule->max_fd = -1;
533   is_run = FALSE;
534
535   /* Calculate next timeout for select(). This is the timeout value
536      when at earliest some of the timeout tasks expire. */
537   SILC_SCHEDULE_SELECT_TIMEOUT;
538
539   /* Add the file descriptors to the fd sets. These are the non-timeout
540      tasks. The select() listens to these file descriptors. */
541   SILC_SCHEDULE_SELECT_TASKS;
542
543   if (schedule->max_fd == -1 && !schedule->timeout)
544     return FALSE;
545
546   if (schedule->timeout) {
547     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
548                     schedule->timeout->tv_usec));
549   }
550
551   if (timeout_usecs >= 0) {
552     timeout.tv_sec = 0;
553     timeout.tv_usec = timeout_usecs;
554     schedule->timeout = &timeout;
555   }
556
557   /* This is the main select(). The program blocks here until some
558      of the selected file descriptors change status or the selected
559      timeout expires. */
560   SILC_LOG_DEBUG(("Select"));
561   switch (select(schedule->max_fd + 1, &schedule->in,
562                  &schedule->out, 0, schedule->timeout)) {
563   case -1:
564     /* Error */
565     if (errno == EINTR)
566       break;
567     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
568     break;
569   case 0:
570     /* Timeout */
571     SILC_LOG_DEBUG(("Running timeout tasks"));
572     gettimeofday(&curtime, NULL);
573     SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
574     break;
575   default:
576     /* There is some data available now */
577     SILC_LOG_DEBUG(("Running non-timeout tasks"));
578     SILC_SCHEDULE_RUN_TASKS;
579
580     SILC_SCHEDULE_RUN_GENERIC_TASKS;
581     break;
582   }
583
584   return TRUE;
585 }
586
587 /* The SILC scheduler. This is actually the main routine in SILC programs.
588    When this returns the program is to be ended. Before this function can
589    be called, one must call silc_schedule_init function. */
590
591 void silc_schedule(SilcSchedule schedule)
592 {
593   SILC_LOG_DEBUG(("Running scheduler"));
594
595   if (schedule->valid == FALSE) {
596     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
597     return;
598   }
599
600   /* Start the scheduler loop */
601   while (silc_schedule_one(schedule, -1)) 
602     ;
603 }