Created SILC Client Libary by moving stuff from silc/ directory.
[silc.git] / lib / silcutil / silcschedule.c
1 /*
2
3   silcschedule.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1998 - 2000 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 /*
21  * $Id$
22  * $Log$
23  * Revision 1.1  2000/09/13 17:45:16  priikone
24  *      Splitted SILC core library. Core library includes now only
25  *      SILC protocol specific stuff. New utility library includes the
26  *      old stuff from core library that is more generic purpose stuff.
27  *
28  * Revision 1.3  2000/07/18 06:51:58  priikone
29  *      Debug version bug fixes.
30  *
31  * Revision 1.2  2000/07/05 06:06:35  priikone
32  *      Global cosmetic change.
33  *
34  * Revision 1.1.1.1  2000/06/27 11:36:55  priikone
35  *      Imported from internal CVS/Added Log headers.
36  *
37  *
38  */
39
40 #include "silcincludes.h"
41
42 /* The actual schedule object. */
43 static SilcSchedule schedule;
44
45 /* Initializes the schedule. Sets the non-timeout task queue hook and
46    the timeout task queue hook. This must be called before the schedule
47    is able to work. */
48
49 void silc_schedule_init(SilcTaskQueue *fd_queue,
50                         SilcTaskQueue *timeout_queue,
51                         SilcTaskQueue *generic_queue,
52                         int max_fd)
53 {
54   int i;
55
56   SILC_LOG_DEBUG(("Initializing scheduler"));
57
58   /* Register the task queues if they are not registered already. In SILC
59      we have by default three task queues. One task queue for non-timeout
60      tasks which perform different kind of I/O on file descriptors, timeout
61      task queue for timeout tasks, and, generic non-timeout task queue whose
62      tasks apply to all connections. */
63   if (!*fd_queue)
64     silc_task_queue_alloc(fd_queue, TRUE);
65   if (!*timeout_queue)
66     silc_task_queue_alloc(timeout_queue, TRUE);
67   if (!*generic_queue)
68     silc_task_queue_alloc(generic_queue, TRUE);
69
70   /* Initialize the schedule */
71   memset(&schedule, 0, sizeof(schedule));
72   schedule.fd_queue = *fd_queue;
73   schedule.timeout_queue = *timeout_queue;
74   schedule.generic_queue = *generic_queue;
75   schedule.fd_list.fd = silc_calloc(max_fd, sizeof(int));
76   schedule.fd_list.last_fd = 0;
77   schedule.fd_list.max_fd = max_fd;
78   schedule.timeout = NULL;
79   schedule.valid = TRUE;
80   FD_ZERO(&schedule.in);
81   FD_ZERO(&schedule.out);
82   schedule.max_fd = -1;
83   for (i = 0; i < max_fd; i++)
84     schedule.fd_list.fd[i] = -1;
85 }
86
87 /* Uninitializes the schedule. This is called when the program is ready
88    to end. This removes all tasks and task queues. */
89
90 int silc_schedule_uninit()
91 {
92
93   SILC_LOG_DEBUG(("Uninitializing scheduler"));
94
95   if (schedule.valid == TRUE)
96     return FALSE;
97
98   /* Unregister all tasks */
99   if (schedule.fd_queue)
100     silc_task_remove(schedule.fd_queue, SILC_ALL_TASKS);
101   if (schedule.timeout_queue)
102     silc_task_remove(schedule.timeout_queue, SILC_ALL_TASKS);
103   if (schedule.generic_queue)
104     silc_task_remove(schedule.generic_queue, SILC_ALL_TASKS);
105
106   /* Unregister all task queues */
107   if (schedule.fd_queue)
108     silc_task_queue_free(schedule.fd_queue);
109   if (schedule.timeout_queue)
110     silc_task_queue_free(schedule.timeout_queue);
111   if (schedule.generic_queue)
112     silc_task_queue_free(schedule.generic_queue);
113
114   /* Clear the fd list */
115   if (schedule.fd_list.fd) {
116     memset(schedule.fd_list.fd, -1, schedule.fd_list.max_fd);
117     silc_free(schedule.fd_list.fd);
118   }
119
120   memset(&schedule, 'F', sizeof(schedule));
121   return TRUE;
122 }
123
124 /* Stops the schedule even if it is not supposed to be stopped yet. 
125    After calling this, one should call silc_schedule_uninit (after the 
126    silc_schedule has returned). */
127
128 void silc_schedule_stop()
129 {
130   SILC_LOG_DEBUG(("Stopping scheduler"));
131
132   if (schedule.valid == TRUE)
133     schedule.valid = FALSE;
134 }
135
136 /* Sets a file descriptor to be listened by select() in scheduler. One can
137    call this directly if wanted. This can be called multiple times for
138    one file descriptor to set different iomasks. */
139
140 void silc_schedule_set_listen_fd(int fd, unsigned int iomask)
141 {
142   assert(schedule.valid != FALSE);
143   assert(fd < schedule.fd_list.max_fd);
144
145   schedule.fd_list.fd[fd] = iomask;
146   
147   if (fd > schedule.fd_list.last_fd)
148     schedule.fd_list.last_fd = fd;
149 }
150
151 /* Removes a file descriptor from listen list. */
152
153 void silc_schedule_unset_listen_fd(int fd)
154 {
155   assert(schedule.valid != FALSE);
156   assert(fd < schedule.fd_list.max_fd);
157
158   schedule.fd_list.fd[fd] = -1;
159   
160   if (fd == schedule.fd_list.last_fd) {
161     int i;
162
163     for (i = fd; i >= 0; i--)
164       if (schedule.fd_list.fd[i] != -1)
165         break;
166
167     schedule.fd_list.last_fd = i;
168   }
169 }
170
171 /* Executes tasks matching the file descriptor set by select(). The task
172    remains on the task queue after execution. Invalid tasks are removed 
173    here from the task queue. This macro is used by silc_schedule function. 
174    We don't have to care about the tasks priority here because the tasks
175    are sorted in their priority order already at the registration phase. */
176
177 #define SILC_SCHEDULE_RUN_TASKS                                            \
178 do {                                                                       \
179   queue = schedule.fd_queue;                                               \
180   if (queue && queue->valid == TRUE && queue->task) {                      \
181     task = queue->task;                                                    \
182                                                                            \
183     /* Walk thorugh all tasks in the particular task queue and             \
184        execute the callback functions of those tasks matching the          \
185        fd set by select(). */                                              \
186     while(1) {                                                             \
187       /* Validity of the task is checked always before and after           \
188          execution beacuse the task might have been unregistered           \
189          in the callback function, ie. it is not valid anymore. */         \
190                                                                            \
191       if (task->valid) {                                                   \
192         /* Task ready for reading */                                       \
193         if ((FD_ISSET(task->fd, &schedule.in)) &&                          \
194             (task->iomask & (1L << SILC_TASK_READ))) {                     \
195           task->callback(queue, SILC_TASK_READ, task->context, task->fd);  \
196           is_run = TRUE;                                                   \
197         }                                                                  \
198       }                                                                    \
199                                                                            \
200       if (task->valid) {                                                   \
201         /* Task ready for writing */                                       \
202         if ((FD_ISSET(task->fd, &schedule.out)) &&                         \
203             (task->iomask & (1L << SILC_TASK_WRITE))) {                    \
204           task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
205           is_run = TRUE;                                                   \
206         }                                                                  \
207       }                                                                    \
208                                                                            \
209       if (!task->valid) {                                                  \
210         /* Invalid (unregistered) tasks are removed from the               \
211            task queue. */                                                  \
212         if (queue->task == task->next) {                                   \
213           silc_task_remove(queue, task);                                   \
214           break;                                                           \
215         }                                                                  \
216                                                                            \
217         task = task->next;                                                 \
218         silc_task_remove(queue, task->prev);                               \
219         continue;                                                          \
220       }                                                                    \
221                                                                            \
222       /* Break if there isn't more tasks in the queue */                   \
223       if (queue->task == task->next)                                       \
224         break;                                                             \
225                                                                            \
226       task = task->next;                                                   \
227     }                                                                      \
228   }                                                                        \
229 } while(0)
230
231 /* Selects tasks to be listened by select(). These are the non-timeout
232    tasks. This checks the scheduler's fd list. This macro is used by 
233    silc_schedule function. */
234
235 #define SILC_SCHEDULE_SELECT_TASKS                              \
236 do {                                                            \
237   for (i = 0; i <= schedule.fd_list.last_fd; i++) {             \
238     if (schedule.fd_list.fd[i] != -1) {                         \
239                                                                 \
240       /* Set the max fd value for select() to listen */         \
241       if (i > schedule.max_fd)                                  \
242         schedule.max_fd = i;                                    \
243                                                                 \
244       /* Add tasks for reading */                               \
245       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ)))    \
246         FD_SET(i, &schedule.in);                                \
247                                                                 \
248       /* Add tasks for writing */                               \
249       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE)))   \
250         FD_SET(i, &schedule.out);                               \
251     }                                                           \
252   }                                                             \
253 } while(0)
254
255 /* Executes all tasks whose timeout has expired. The task is removed from
256    the task queue after the callback function has returned. Also, invalid
257    tasks are removed here. The current time must be get before calling this
258    macro. This macro is used by silc_schedule function. We don't have to
259    care about priorities because tasks are already sorted in their priority
260    order at the registration phase. */
261
262 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS                                 \
263 do {                                                                    \
264   queue = schedule.timeout_queue;                                       \
265   if (queue && queue->valid == TRUE && queue->task) {                   \
266     task = queue->task;                                                 \
267                                                                         \
268     /* Walk thorugh all tasks in the particular task queue              \
269        and run all the expired tasks. */                                \
270     while(1) {                                                          \
271       /* Execute the task if the timeout has expired */                 \
272       if (silc_task_timeout_compare(&task->timeout, &curtime)) {        \
273                                                                         \
274         /* Task ready for reading */                                    \
275         if (task->valid) {                                              \
276           if ((task->iomask & (1L << SILC_TASK_READ)))                  \
277             task->callback(queue, SILC_TASK_READ,                       \
278                            task->context, task->fd);                    \
279         }                                                               \
280                                                                         \
281         /* Task ready for writing */                                    \
282         if (task->valid) {                                              \
283           if ((task->iomask & (1L << SILC_TASK_WRITE)))                 \
284             task->callback(queue, SILC_TASK_WRITE,                      \
285                            task->context, task->fd);                    \
286         }                                                               \
287                                                                         \
288         /* Break if there isn't more tasks in the queue */              \
289         if (queue->task == task->next) {                                \
290           /* Remove the task from queue */                              \
291           silc_task_remove(queue, task);                                \
292           break;                                                        \
293         }                                                               \
294                                                                         \
295         task = task->next;                                              \
296                                                                         \
297         /* Remove the task from queue */                                \
298         silc_task_remove(queue, task->prev);                            \
299       } else {                                                          \
300         /* The timeout hasn't expired, check for next one */            \
301                                                                         \
302         /* Break if there isn't more tasks in the queue */              \
303         if (queue->task == task->next)                                  \
304           break;                                                        \
305                                                                         \
306         task = task->next;                                              \
307       }                                                                 \
308     }                                                                   \
309   }                                                                     \
310 } while(0)
311
312 /* Calculates next timeout for select(). This is the timeout value
313    when at earliest some of the timeout tasks expire. If this is in the
314    past, they will be run now. This macro is used by the silc_schedule
315    function. */
316
317 #define SILC_SCHEDULE_SELECT_TIMEOUT                                        \
318 do {                                                                        \
319   if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) {    \
320     queue = schedule.timeout_queue;                                         \
321     task = NULL;                                                            \
322                                                                             \
323     /* Get the current time */                                              \
324     gettimeofday(&curtime, NULL);                                           \
325     schedule.timeout = NULL;                                                \
326                                                                             \
327     /* First task in the task queue has always the smallest timeout. */     \
328     task = queue->task;                                                     \
329     while(1) {                                                              \
330       if (task && task->valid == TRUE) {                                    \
331                                                                             \
332         /* If the timeout is in past, we will run the task and all other    \
333            timeout tasks from the past. */                                  \
334         if (silc_task_timeout_compare(&task->timeout, &curtime)) {          \
335           SILC_SCHEDULE_RUN_TIMEOUT_TASKS;                                  \
336                                                                             \
337           /* The task(s) has expired and doesn't exist on the task queue    \
338              anymore. We continue with new timeout. */                      \
339           queue = schedule.timeout_queue;                                   \
340           task = queue->task;                                               \
341           if (task == NULL || task->valid == FALSE)                         \
342             break;                                                          \
343           goto cont;                                                        \
344         } else {                                                            \
345  cont:                                                                      \
346           /* Calculate the next timeout for select() */                     \
347           queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;    \
348           queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
349                                                                             \
350           /* We wouldn't want to go under zero, check for it. */            \
351           if (queue->timeout.tv_usec < 0) {                                 \
352             queue->timeout.tv_sec -= 1;                                     \
353             queue->timeout.tv_usec += 1000000L;                             \
354           }                                                                 \
355         }                                                                   \
356         /* We've got the timeout value */                                   \
357         break;                                                              \
358       } else {                                                              \
359         /* Task is not valid, remove it and try next one. */                \
360         silc_task_remove(queue, task);                                      \
361         task = queue->task;                                                 \
362         if (queue->task == NULL)                                            \
363           break;                                                            \
364       }                                                                     \
365     }                                                                       \
366     /* Save the timeout */                                                  \
367     if (task)                                                               \
368       schedule.timeout = &queue->timeout;                                   \
369   }                                                                         \
370 } while(0)
371
372 /* Execute generic tasks. These are executed only and only if for the
373    specific fd there wasn't other non-timeout tasks. This checks the earlier
374    set fd list, thus the generic tasks apply to all specified fd's. All the
375    generic tasks are executed at once. */
376
377 #define SILC_SCHEDULE_RUN_GENERIC_TASKS                                      \
378 do {                                                                         \
379   if (is_run == FALSE) {                                                     \
380     SILC_LOG_DEBUG(("Running generic tasks"));                               \
381     for (i = 0; i <= schedule.fd_list.last_fd; i++)                          \
382       if (schedule.fd_list.fd[i] != -1) {                                    \
383                                                                              \
384         /* Check whether this fd is select()ed. */                           \
385         if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) {   \
386                                                                              \
387           /* It was selected. Now find the tasks from task queue and execute \
388              all generic tasks. */                                           \
389           if (schedule.generic_queue && schedule.generic_queue->valid) {     \
390             queue = schedule.generic_queue;                                  \
391                                                                              \
392             if (!queue->task)                                                \
393               break;                                                         \
394                                                                              \
395             task = queue->task;                                              \
396                                                                              \
397             while(1) {                                                       \
398               /* Validity of the task is checked always before and after     \
399                  execution beacuse the task might have been unregistered     \
400                  in the callback function, ie. it is not valid anymore. */   \
401                                                                              \
402               if (task->valid && schedule.fd_list.fd[i] != -1) {             \
403                 /* Task ready for reading */                                 \
404                 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ)))       \
405                   task->callback(queue, SILC_TASK_READ,                      \
406                                  task->context, i);                          \
407               }                                                              \
408                                                                              \
409               if (task->valid && schedule.fd_list.fd[i] != -1) {             \
410                 /* Task ready for writing */                                 \
411                 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE)))      \
412                   task->callback(queue, SILC_TASK_WRITE,                     \
413                                  task->context, i);                          \
414               }                                                              \
415                                                                              \
416               if (!task->valid) {                                            \
417                 /* Invalid (unregistered) tasks are removed from the         \
418                    task queue. */                                            \
419                 if (queue->task == task->next) {                             \
420                   silc_task_remove(queue, task);                             \
421                   break;                                                     \
422                 }                                                            \
423                                                                              \
424                 task = task->next;                                           \
425                 silc_task_remove(queue, task->prev);                         \
426                 continue;                                                    \
427               }                                                              \
428                                                                              \
429               /* Break if there isn't more tasks in the queue */             \
430               if (queue->task == task->next)                                 \
431                 break;                                                       \
432                                                                              \
433               task = task->next;                                             \
434             }                                                                \
435           }                                                                  \
436         }                                                                    \
437       }                                                                      \
438   }                                                                          \
439 } while(0)
440
441 /* The SILC scheduler. This is actually the main routine in SILC programs.
442    When this returns the program is to be ended. Before this function can
443    be called, one must call silc_schedule_init function. */
444
445 void silc_schedule()
446 {
447   int is_run, i;
448   SilcTask task;
449   SilcTaskQueue queue;
450   struct timeval curtime;
451
452   SILC_LOG_DEBUG(("Running scheduler"));
453
454   if (schedule.valid == FALSE) {
455     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
456     return;
457   }
458
459   /* Start the scheduler loop */
460   while(1) {
461
462     SILC_LOG_DEBUG(("In scheduler loop"));
463
464     /* If the task queues aren't initialized or we aren't valid anymore
465        we will return */
466     if ((!schedule.fd_queue && !schedule.timeout_queue 
467          && !schedule.generic_queue) || schedule.valid == FALSE) {
468       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
469       break;
470     }
471
472     /* Clear everything */
473     FD_ZERO(&schedule.in);
474     FD_ZERO(&schedule.out);
475     schedule.max_fd = -1;
476     is_run = FALSE;
477
478     /* Calculate next timeout for select(). This is the timeout value
479        when at earliest some of the timeout tasks expire. */
480     SILC_SCHEDULE_SELECT_TIMEOUT;
481
482     /* Add the file descriptors to the fd sets. These are the non-timeout
483        tasks. The select() listens to these file descriptors. */
484     SILC_SCHEDULE_SELECT_TASKS;
485
486     if (schedule.max_fd == -1) {
487       SILC_LOG_ERROR(("Nothing to listen, exiting"));
488       break;
489     }
490
491     if (schedule.timeout) {
492       SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
493                       schedule.timeout->tv_usec));
494     }
495
496     /* This is the main select(). The program blocks here until some
497        of the selected file descriptors change status or the selected
498        timeout expires. */
499     SILC_LOG_DEBUG(("Select"));
500     switch(select(schedule.max_fd + 1, &schedule.in, 
501                   &schedule.out, 0, schedule.timeout)) {
502     case -1:
503       /* Error */
504       SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
505       break;
506     case 0:
507       /* Timeout */
508       SILC_LOG_DEBUG(("Running timeout tasks"));
509       gettimeofday(&curtime, NULL);
510       SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
511       break;
512     default:
513       /* There is some data available now */
514       SILC_LOG_DEBUG(("Running non-timeout tasks"));
515       SILC_SCHEDULE_RUN_TASKS;
516
517       SILC_SCHEDULE_RUN_GENERIC_TASKS;
518       break;
519     }
520   }
521 }