Improved signals support in scheduler.
[crypto.git] / lib / silcutil / silcschedule.c
1 /*
2
3   silcschedule.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 #include "silcschedule_i.h"
24
25 /* Forward declarations */
26 typedef struct SilcTaskQueueStruct *SilcTaskQueue;
27
28 /* System specific routines. Implemented under unix/, win32/ and such. */
29
30 /* System specific select(). Returns same values as normal select(). */
31 int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count, 
32                 struct timeval *timeout);
33
34 /* Initializes the platform specific scheduler.  This for example initializes
35    the wakeup mechanism of the scheduler.  In multi-threaded environment
36    the scheduler needs to be wakenup when tasks are added or removed from
37    the task queues.  Returns context to the platform specific scheduler. */
38 void *silc_schedule_internal_init(SilcSchedule schedule);
39
40 /* Uninitializes the platform specific scheduler context. */
41 void silc_schedule_internal_uninit(void *context);
42
43 /* Wakes up the scheduler. This is platform specific routine */
44 void silc_schedule_internal_wakeup(void *context);
45
46 /* Register signal */
47 void silc_schedule_internal_signal_register(void *context,
48                                             SilcUInt32 signal,
49                                             SilcTaskCallback callback,
50                                             void *callback_context);
51
52 /* Unregister signal */
53 void silc_schedule_internal_signal_unregister(void *context,
54                                               SilcUInt32 signal,
55                                               SilcTaskCallback callback,
56                                               void *callback_context);
57
58 /* Mark signal to be called later. */
59 void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal);
60
61 /* Call all signals */
62 void silc_schedule_internal_signals_call(void *context,
63                                          SilcSchedule schedule);
64
65 /* Block registered signals in scheduler. */
66 void silc_schedule_internal_signals_block(void *context);
67
68 /* Unblock registered signals in schedule. */
69 void silc_schedule_internal_signals_unblock(void *context);
70
71 /* Internal task management routines. */
72
73 static void silc_task_queue_alloc(SilcTaskQueue *queue);
74 static void silc_task_queue_free(SilcTaskQueue queue);
75 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd);
76 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
77                               SilcTaskPriority priority);
78 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first);
79 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
80                                       SilcTaskPriority priority);
81 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task);
82 static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
83                                               struct timeval *bigger);
84 static void silc_task_del_by_context(SilcTaskQueue queue, void *context);
85 static void silc_task_del_by_callback(SilcTaskQueue queue,
86                                       SilcTaskCallback callback);
87 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd);
88
89 /* Returns the task queue by task type */
90 #define SILC_SCHEDULE_GET_QUEUE(type)                           \
91   (type == SILC_TASK_FD ? schedule->fd_queue :                  \
92    type == SILC_TASK_TIMEOUT ? schedule->timeout_queue :        \
93    schedule->generic_queue)
94
95 /* Locks. These also blocks signals that we care about and thus guarantee
96    that while we are in scheduler no signals can happen.  This way we can
97    synchronise signals with SILC Scheduler. */
98 #define SILC_SCHEDULE_LOCK(schedule)                            \
99 do {                                                            \
100   silc_schedule_internal_signals_block(schedule->internal);     \
101   silc_mutex_lock(schedule->lock);                              \
102 } while (0)
103 #define SILC_SCHEDULE_UNLOCK(schedule)                          \
104 do {                                                            \
105   silc_mutex_unlock(schedule->lock);                            \
106   silc_schedule_internal_signals_unblock(schedule->internal);   \
107 } while (0)
108
109 /* SILC Task object. Represents one task in the scheduler. */
110 struct SilcTaskStruct {
111   SilcUInt32 fd;
112   struct timeval timeout;
113   SilcTaskCallback callback;
114   void *context;
115   bool valid;
116   SilcTaskPriority priority;
117   SilcTaskType type;
118
119   /* Pointers forming doubly linked circular list */
120   struct SilcTaskStruct *next;
121   struct SilcTaskStruct *prev;
122 };
123
124 /* SILC Task Queue object. The queue holds all the tasks in the scheduler.
125    There are always three task queues in the scheduler. One for non-timeout
126    tasks (fd tasks performing tasks over specified file descriptor), 
127    one for timeout tasks and one for generic tasks. */
128 struct SilcTaskQueueStruct {
129   SilcTask task;                /* Pointer to all tasks */
130   struct timeval timeout;       /* Current timeout */
131   SILC_MUTEX_DEFINE(lock);      /* Queue's lock */
132 };
133
134 /* 
135    SILC Scheduler structure.
136
137    This is the actual schedule object in SILC. Both SILC client and server 
138    uses this same scheduler. Actually, this scheduler could be used by any
139    program needing scheduling.
140
141    Following short description of the fields:
142
143    SilcTaskQueue fd_queue
144
145        Task queue hook for non-timeout tasks. Usually this means that these
146        tasks perform different kind of I/O on file descriptors. File 
147        descriptors are usually network sockets but they actually can be
148        any file descriptors. This hook is initialized in silc_schedule_init
149        function. Timeout tasks should not be added to this queue because
150        they will never expire.
151
152    SilcTaskQueue timeout_queue
153
154        Task queue hook for timeout tasks. This hook is reserved specificly
155        for tasks with timeout. Non-timeout tasks should not be added to this
156        queue because they will never get scheduled. This hook is also
157        initialized in silc_schedule_init function.
158
159    SilcTaskQueue generic_queue
160
161        Task queue hook for generic tasks. This hook is reserved specificly
162        for generic tasks, tasks that apply to all file descriptors, except
163        to those that have specificly registered a non-timeout task. This hook
164        is also initialized in silc_schedule_init function.
165
166    SilcScheduleFd fd_list
167
168        List of file descriptors the scheduler is supposed to be listenning.
169        This is updated internally.
170
171    SilcUInt32 max_fd
172    SilcUInt32 last_fd
173
174        Size of the fd_list list. There can be `max_fd' many tasks in
175        the scheduler at once. The `last_fd' is the last valid entry
176        in the fd_list.
177
178    struct timeval *timeout;
179
180        Pointer to the schedules next timeout. Value of this timeout is
181        automatically updated in the silc_schedule function.
182
183    bool valid
184
185        Marks validity of the scheduler. This is a boolean value. When this
186        is false the scheduler is terminated and the program will end. This
187        set to true when the scheduler is initialized with silc_schedule_init
188        function.
189
190    fd_set in
191    fd_set out
192
193        File descriptor sets for select(). These are automatically managed
194        by the scheduler and should not be touched otherwise.
195
196    void *internal
197
198        System specific scheduler context.
199
200    SILC_MUTEX_DEFINE(lock)
201   
202        Scheduler lock.
203
204    bool signal_tasks
205
206        TRUE when tasks has been registered from signals.  Next round in
207        scheduler will call the callbacks when this is TRUE.
208
209 */
210 struct SilcScheduleStruct {
211   SilcTaskQueue fd_queue;
212   SilcTaskQueue timeout_queue;
213   SilcTaskQueue generic_queue;
214   SilcScheduleFd fd_list;
215   SilcUInt32 max_fd;
216   SilcUInt32 last_fd;
217   struct timeval *timeout;
218   bool valid;
219   void *internal;
220   SILC_MUTEX_DEFINE(lock);
221   bool is_locked;
222   bool signal_tasks;
223 };
224
225 /* Initializes the scheduler. This returns the scheduler context that
226    is given as arugment usually to all silc_schedule_* functions.
227    The `max_tasks' indicates the number of maximum tasks that the
228    scheduler can handle. */
229
230 SilcSchedule silc_schedule_init(int max_tasks)
231 {
232   SilcSchedule schedule;
233
234   SILC_LOG_DEBUG(("Initializing scheduler"));
235
236   schedule = silc_calloc(1, sizeof(*schedule));
237
238   /* Allocate three task queues, one for file descriptor based tasks,
239      one for timeout tasks and one for generic tasks. */
240   silc_task_queue_alloc(&schedule->fd_queue);
241   silc_task_queue_alloc(&schedule->timeout_queue);
242   silc_task_queue_alloc(&schedule->generic_queue);
243
244   if (!max_tasks)
245     max_tasks = 200;
246
247   /* Initialize the scheduler */
248   schedule->fd_list = silc_calloc(max_tasks, sizeof(*schedule->fd_list));
249   schedule->max_fd = max_tasks;
250   schedule->timeout = NULL;
251   schedule->valid = TRUE;
252
253   /* Allocate scheduler lock */
254   silc_mutex_alloc(&schedule->lock);
255
256   /* Initialize the platform specific scheduler. */
257   schedule->internal = silc_schedule_internal_init(schedule);
258
259   return schedule;
260 }
261
262 /* Uninitializes the schedule. This is called when the program is ready
263    to end. This removes all tasks and task queues. Returns FALSE if the
264    scheduler could not be uninitialized. This happens when the scheduler
265    is still valid and silc_schedule_stop has not been called. */
266
267 bool silc_schedule_uninit(SilcSchedule schedule)
268 {
269   SILC_LOG_DEBUG(("Uninitializing scheduler"));
270
271   if (schedule->valid == TRUE)
272     return FALSE;
273
274   /* Unregister all tasks */
275   silc_schedule_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
276   silc_schedule_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
277   silc_schedule_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
278
279   /* Unregister all task queues */
280   silc_task_queue_free(schedule->fd_queue);
281   silc_task_queue_free(schedule->timeout_queue);
282   silc_task_queue_free(schedule->generic_queue);
283
284   silc_free(schedule->fd_list);
285
286   /* Uninit the platform specific scheduler. */
287   silc_schedule_internal_uninit(schedule->internal);
288
289   silc_mutex_free(schedule->lock);
290
291   return TRUE;
292 }
293
294 /* Enlarge the capabilities of the scheduler to handle tasks to `max_tasks'. */
295
296 bool silc_schedule_reinit(SilcSchedule schedule, int max_tasks)
297 {
298   SILC_SCHEDULE_LOCK(schedule);
299   if (schedule->max_fd <= max_tasks)
300     return FALSE;
301   schedule->fd_list = silc_realloc(schedule->fd_list, 
302                                    (sizeof(*schedule->fd_list) * max_tasks));
303   schedule->max_fd = max_tasks;
304   SILC_SCHEDULE_UNLOCK(schedule);
305   return TRUE;
306 }
307
308 /* Stops the schedule even if it is not supposed to be stopped yet. 
309    After calling this, one should call silc_schedule_uninit (after the 
310    silc_schedule has returned). */
311
312 void silc_schedule_stop(SilcSchedule schedule)
313 {
314   SILC_LOG_DEBUG(("Stopping scheduler"));
315   SILC_SCHEDULE_LOCK(schedule);
316   schedule->valid = FALSE;
317   SILC_SCHEDULE_UNLOCK(schedule);
318 }
319
320 /* Executes nontimeout tasks. It then checks whether any of ther fd tasks
321    was signaled by the silc_select. If some task was not signaled then
322    all generic tasks are executed for that task. The generic tasks are
323    never executed for task that has explicit fd task set. */
324 /* This holds the schedule->lock and the queue locks. */
325
326 static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
327 {
328   SilcTask task;
329   int i, last_fd = schedule->last_fd;
330   SilcUInt32 fd;
331
332   for (i = 0; i <= last_fd; i++) {
333     if (schedule->fd_list[i].events == 0)
334       continue;
335
336     fd = schedule->fd_list[i].fd;
337
338     /* First check whether this fd has task in the fd queue */
339     silc_mutex_lock(schedule->fd_queue->lock);
340     task = silc_task_find(schedule->fd_queue, fd);
341
342     /* If the task was found then execute its callbacks. If not then
343        execute all generic tasks for that fd. */
344     if (task) {
345       /* Validity of the task is checked always before and after
346          execution beacuse the task might have been unregistered
347          in the callback function, ie. it is not valid anymore. */
348
349       /* Is the task ready for reading */
350       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
351         silc_mutex_unlock(schedule->fd_queue->lock);
352         SILC_SCHEDULE_UNLOCK(schedule);
353         task->callback(schedule, SILC_TASK_READ, task->fd, task->context);
354         SILC_SCHEDULE_LOCK(schedule);
355         silc_mutex_lock(schedule->fd_queue->lock);
356       }
357
358       /* Is the task ready for writing */
359       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
360         silc_mutex_unlock(schedule->fd_queue->lock);
361         SILC_SCHEDULE_UNLOCK(schedule);
362         task->callback(schedule, SILC_TASK_WRITE, task->fd, task->context);
363         SILC_SCHEDULE_LOCK(schedule);
364         silc_mutex_lock(schedule->fd_queue->lock);
365       }
366
367       if (!task->valid)
368         silc_schedule_task_remove(schedule->fd_queue, task);
369
370       silc_mutex_unlock(schedule->fd_queue->lock);
371     } else {
372       /* Run generic tasks for this fd. */
373
374       silc_mutex_unlock(schedule->fd_queue->lock);
375
376       silc_mutex_lock(schedule->generic_queue->lock);
377       if (!schedule->generic_queue->task) {
378         silc_mutex_unlock(schedule->generic_queue->lock);
379         continue;
380       }
381
382       task = schedule->generic_queue->task;
383       while(1) {
384         /* Validity of the task is checked always before and after
385            execution beacuse the task might have been unregistered
386            in the callback function, ie. it is not valid anymore. */
387
388         /* Is the task ready for reading */                             
389         if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
390           silc_mutex_unlock(schedule->generic_queue->lock);
391           SILC_SCHEDULE_UNLOCK(schedule);
392           task->callback(schedule, SILC_TASK_READ, fd, task->context);
393           SILC_SCHEDULE_LOCK(schedule);
394           silc_mutex_lock(schedule->generic_queue->lock);
395         }
396
397         /* Is the task ready for writing */                             
398         if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
399           silc_mutex_unlock(schedule->generic_queue->lock);
400           SILC_SCHEDULE_UNLOCK(schedule);
401           task->callback(schedule, SILC_TASK_WRITE, fd, task->context);
402           SILC_SCHEDULE_LOCK(schedule);
403           silc_mutex_lock(schedule->generic_queue->lock);
404         }
405
406         if (!task->valid) {
407           /* Invalid (unregistered) tasks are removed from the
408              task queue. */
409           if (schedule->generic_queue->task == task->next) {
410             silc_schedule_task_remove(schedule->generic_queue, task);
411             silc_mutex_unlock(schedule->generic_queue->lock);
412             break;
413           }
414
415           task = task->next;
416           silc_schedule_task_remove(schedule->generic_queue, task);
417           continue;
418         }
419
420         /* Break if there isn't more tasks in the queue */
421         if (schedule->generic_queue->task == task->next)
422           break;
423
424         task = task->next;
425       }                 
426
427       silc_mutex_unlock(schedule->generic_queue->lock);
428     }
429   }
430 }
431
432 /* Executes all tasks whose timeout has expired. The task is removed from
433    the task queue after the callback function has returned. Also, invalid
434    tasks are removed here. We don't have to care about priorities because 
435    tasks are already sorted in their priority order at the registration 
436    phase. */
437 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
438
439 static void silc_schedule_dispatch_timeout(SilcSchedule schedule)
440 {
441   SilcTaskQueue queue = schedule->timeout_queue;
442   SilcTask task;
443   struct timeval curtime;
444
445   SILC_LOG_DEBUG(("Running timeout tasks"));
446
447   silc_gettimeofday(&curtime);
448
449   queue = schedule->timeout_queue;
450   if (queue && queue->task) {
451     task = queue->task;
452
453     /* Walk thorugh all tasks in the particular task queue and run all 
454        the expired tasks. */
455     while(1) {
456       /* Execute the task if the timeout has expired */
457       if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
458         if (task->valid) {
459           silc_mutex_unlock(queue->lock);
460           SILC_SCHEDULE_UNLOCK(schedule);
461           task->callback(schedule, SILC_TASK_EXPIRE, task->fd, task->context);
462           SILC_SCHEDULE_LOCK(schedule);
463           silc_mutex_lock(queue->lock);
464         }
465
466         /* Break if there isn't more tasks in the queue */
467         if (queue->task == task->next) {
468           silc_schedule_task_remove(queue, task);
469           break;
470         }
471
472         task = task->next;
473
474         /* Remove the task from queue */
475         silc_schedule_task_remove(queue, task->prev);
476       } else {
477         /* The timeout hasn't expired, check for next one */
478
479         /* Break if there isn't more tasks in the queue */
480         if (queue->task == task->next)
481           break;
482
483         task = task->next;
484       }
485     }
486   }
487 }
488
489 /* Calculates next timeout for select(). This is the timeout value
490    when at earliest some of the timeout tasks expire. If this is in the
491    past, they will be run now. */
492 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
493
494 static void silc_schedule_select_timeout(SilcSchedule schedule)
495 {
496   SilcTaskQueue queue = schedule->timeout_queue;
497   SilcTask task;
498   struct timeval curtime;
499
500   /* Get the current time */
501   silc_gettimeofday(&curtime);
502   schedule->timeout = NULL;
503
504   /* First task in the task queue has always the smallest timeout. */
505   task = queue->task;
506   while(1) {
507     if (task && task->valid == TRUE) {
508       /* If the timeout is in past, we will run the task and all other
509          timeout tasks from the past. */
510       if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
511         silc_schedule_dispatch_timeout(schedule);
512                                                 
513         /* The task(s) has expired and doesn't exist on the task queue
514            anymore. We continue with new timeout. */
515         queue = schedule->timeout_queue;
516         task = queue->task;
517         if (task == NULL || task->valid == FALSE)
518           break;
519       }
520
521       /* Calculate the next timeout for select() */
522       queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
523       queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
524       if (queue->timeout.tv_sec < 0)
525         queue->timeout.tv_sec = 0;
526
527       /* We wouldn't want to go under zero, check for it. */
528       if (queue->timeout.tv_usec < 0) {
529         queue->timeout.tv_sec -= 1;
530         if (queue->timeout.tv_sec < 0)
531           queue->timeout.tv_sec = 0;
532         queue->timeout.tv_usec += 1000000L;
533       }
534
535       /* We've got the timeout value */
536       break;
537     } else {
538       /* Task is not valid, remove it and try next one. */
539       silc_schedule_task_remove(queue, task);
540       task = queue->task;
541       if (queue->task == NULL)
542         break;
543     }
544   }
545
546   /* Save the timeout */
547   if (task) {
548     schedule->timeout = &queue->timeout;
549     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
550                     schedule->timeout->tv_usec));
551   }
552 }
553
554 /* Runs the scheduler once and then returns. */
555
556 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
557 {
558   struct timeval timeout;
559   int ret;
560
561   SILC_LOG_DEBUG(("In scheduler loop"));
562
563   if (!schedule->is_locked)
564     SILC_SCHEDULE_LOCK(schedule);
565
566   /* If the task queues aren't initialized or we aren't valid anymore
567      we will return */
568   if ((!schedule->fd_queue && !schedule->timeout_queue 
569        && !schedule->generic_queue) || schedule->valid == FALSE) {
570     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
571     if (!schedule->is_locked)
572       SILC_SCHEDULE_UNLOCK(schedule);
573     return FALSE;
574   }
575
576   /* Calculate next timeout for silc_select(). This is the timeout value
577      when at earliest some of the timeout tasks expire. */
578   silc_mutex_lock(schedule->timeout_queue->lock);
579   silc_schedule_select_timeout(schedule);
580   silc_mutex_unlock(schedule->timeout_queue->lock);
581
582   if (timeout_usecs >= 0) {
583     timeout.tv_sec = 0;
584     timeout.tv_usec = timeout_usecs;
585     schedule->timeout = &timeout;
586   }
587
588   SILC_SCHEDULE_UNLOCK(schedule);
589
590   /* Deliver signals if any has been set to be called */
591   if (schedule->signal_tasks) {
592     silc_schedule_internal_signals_call(schedule->internal, schedule);
593     schedule->signal_tasks = FALSE;
594   }
595
596   /* This is the main select(). The program blocks here until some
597      of the selected file descriptors change status or the selected
598      timeout expires. */
599   SILC_LOG_DEBUG(("Select"));
600   ret = silc_select(schedule->fd_list, schedule->last_fd + 1, 
601                     schedule->timeout);
602
603   SILC_SCHEDULE_LOCK(schedule);
604
605   switch (ret) {
606   case -1:
607     /* Error */
608     if (errno == EINTR)
609       break;
610     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
611     break;
612   case 0:
613     /* Timeout */
614     silc_mutex_lock(schedule->timeout_queue->lock);
615     silc_schedule_dispatch_timeout(schedule);
616     silc_mutex_unlock(schedule->timeout_queue->lock);
617     break;
618   default:
619     /* There is some data available now */
620     SILC_LOG_DEBUG(("Running non-timeout tasks"));
621     silc_schedule_dispatch_nontimeout(schedule);
622     break;
623   }
624
625   if (!schedule->is_locked)
626     SILC_SCHEDULE_UNLOCK(schedule);
627
628   return TRUE;
629 }
630
631 /* The SILC scheduler. This is actually the main routine in SILC programs.
632    When this returns the program is to be ended. Before this function can
633    be called, one must call silc_schedule_init function. */
634
635 void silc_schedule(SilcSchedule schedule)
636 {
637   SILC_LOG_DEBUG(("Running scheduler"));
638
639   if (schedule->valid == FALSE) {
640     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
641     return;
642   }
643
644   SILC_SCHEDULE_LOCK(schedule);
645   schedule->is_locked = TRUE;
646
647   /* Start the scheduler loop */
648   while (silc_schedule_one(schedule, -1)) 
649     ;
650
651   SILC_SCHEDULE_UNLOCK(schedule);
652 }
653
654 /* Wakes up the scheduler. This is used only in multi-threaded
655    environments where threads may add new tasks or remove old tasks
656    from task queues. This is called to wake up the scheduler in the
657    main thread so that it detects the changes in the task queues.
658    If threads support is not compiled in this function has no effect.
659    Implementation of this function is platform specific. */
660
661 void silc_schedule_wakeup(SilcSchedule schedule)
662 {
663 #ifdef SILC_THREADS
664   SILC_LOG_DEBUG(("Wakeup scheduler"));
665   SILC_SCHEDULE_LOCK(schedule);
666   silc_schedule_internal_wakeup(schedule->internal);
667   SILC_SCHEDULE_UNLOCK(schedule);
668 #endif
669 }
670
671 /* Add new task to the scheduler */
672
673 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
674                                 SilcTaskCallback callback, void *context, 
675                                 long seconds, long useconds, 
676                                 SilcTaskType type, 
677                                 SilcTaskPriority priority)
678 {
679   SilcTask newtask;
680   SilcTaskQueue queue;
681   int timeout = FALSE;
682
683   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
684                   type, priority));
685
686   queue = SILC_SCHEDULE_GET_QUEUE(type);
687     
688   /* If the task is generic task, we check whether this task has already
689      been registered. Generic tasks are registered only once and after that
690      the same task applies to all file descriptors to be registered. */
691   if (type == SILC_TASK_GENERIC) {
692     silc_mutex_lock(queue->lock);
693
694     if (queue->task) {
695       SilcTask task = queue->task;
696       while(1) {
697         if ((task->callback == callback) && (task->context == context)) {
698           SILC_LOG_DEBUG(("Found matching generic task, using the match"));
699           
700           silc_mutex_unlock(queue->lock);
701
702           /* Add the fd to be listened, the task found now applies to this
703              fd as well. */
704           silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
705           return task;
706         }
707         
708         if (queue->task == task->next)
709           break;
710         
711         task = task->next;
712       }
713     }
714
715     silc_mutex_unlock(queue->lock);
716   }
717
718   newtask = silc_calloc(1, sizeof(*newtask));
719   newtask->fd = fd;
720   newtask->context = context;
721   newtask->callback = callback;
722   newtask->valid = TRUE;
723   newtask->priority = priority;
724   newtask->type = type;
725   newtask->next = newtask;
726   newtask->prev = newtask;
727
728   /* Create timeout if marked to be timeout task */
729   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
730     silc_gettimeofday(&newtask->timeout);
731     newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
732     newtask->timeout.tv_usec += (useconds % 1000000L);
733     if (newtask->timeout.tv_usec > 999999L) {
734       newtask->timeout.tv_sec += 1;
735       newtask->timeout.tv_usec -= 1000000L;
736     }
737     timeout = TRUE;
738   }
739
740   /* If the task is non-timeout task we have to tell the scheduler that we
741      would like to have these tasks scheduled at some odd distant future. */
742   if (type != SILC_TASK_TIMEOUT)
743     silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
744
745   silc_mutex_lock(queue->lock);
746
747   /* Is this first task of the queue? */
748   if (queue->task == NULL) {
749     queue->task = newtask;
750     silc_mutex_unlock(queue->lock);
751     return newtask;
752   }
753
754   if (timeout)
755     newtask = silc_task_add_timeout(queue, newtask, priority);
756   else
757     newtask = silc_task_add(queue, newtask, priority);
758
759   silc_mutex_unlock(queue->lock);
760
761   return newtask;
762 }
763
764 /* Removes a task from the scheduler */
765
766 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
767 {
768   SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
769
770   /* Unregister all tasks */
771   if (task == SILC_ALL_TASKS) {
772     SilcTask next;
773     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
774
775     silc_mutex_lock(queue->lock);
776
777     if (!queue->task) {
778       silc_mutex_unlock(queue->lock);
779       return;
780     }
781
782     next = queue->task;
783     
784     while(1) {
785       if (next->valid)
786         next->valid = FALSE;
787       if (queue->task == next->next)
788         break;
789       next = next->next;
790     }
791
792     silc_mutex_unlock(queue->lock);
793     return;
794   }
795
796   SILC_LOG_DEBUG(("Unregistering task"));
797
798   silc_mutex_lock(queue->lock);
799
800   /* Unregister the specific task */
801   if (task->valid)
802     task->valid = FALSE;
803
804   silc_mutex_unlock(queue->lock);
805 }
806
807 /* Remove task by fd */
808
809 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
810 {
811   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
812
813   silc_task_del_by_fd(schedule->timeout_queue, fd);
814   silc_task_del_by_fd(schedule->fd_queue, fd);
815 }
816
817 /* Remove task by task callback. */
818
819 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
820                                         SilcTaskCallback callback)
821 {
822   SILC_LOG_DEBUG(("Unregister task by callback"));
823
824   silc_task_del_by_callback(schedule->timeout_queue, callback);
825   silc_task_del_by_callback(schedule->fd_queue, callback);
826   silc_task_del_by_callback(schedule->generic_queue, callback);
827 }
828
829 /* Remove task by context. */
830
831 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
832 {
833   SILC_LOG_DEBUG(("Unregister task by context"));
834
835   silc_task_del_by_context(schedule->timeout_queue, context);
836   silc_task_del_by_context(schedule->fd_queue, context);
837   silc_task_del_by_context(schedule->generic_queue, context);
838 }
839
840 /* Sets a file descriptor to be listened by select() in scheduler. One can
841    call this directly if wanted. This can be called multiple times for
842    one file descriptor to set different iomasks. */
843
844 void silc_schedule_set_listen_fd(SilcSchedule schedule,
845                                  SilcUInt32 fd, SilcTaskEvent iomask)
846 {
847   int i;
848   bool found = FALSE;
849
850   SILC_SCHEDULE_LOCK(schedule);
851
852   for (i = 0; i < schedule->max_fd; i++)
853     if (schedule->fd_list[i].fd == fd) {
854       schedule->fd_list[i].fd = fd;
855       schedule->fd_list[i].events = iomask;
856       if (i > schedule->last_fd)
857         schedule->last_fd = i;
858       found = TRUE;
859       break;
860     }
861
862   if (!found)
863     for (i = 0; i < schedule->max_fd; i++)
864       if (schedule->fd_list[i].events == 0) {
865         schedule->fd_list[i].fd = fd;
866         schedule->fd_list[i].events = iomask;
867         if (i > schedule->last_fd)
868           schedule->last_fd = i;
869         break;
870       }
871
872   SILC_SCHEDULE_UNLOCK(schedule);
873 }
874
875 /* Removes a file descriptor from listen list. */
876
877 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
878 {
879   int i;
880
881   SILC_SCHEDULE_LOCK(schedule);
882
883   SILC_LOG_DEBUG(("Unset listen fd %d", fd));
884
885   for (i = 0; i < schedule->max_fd; i++)
886     if (schedule->fd_list[i].fd == fd) {
887       schedule->fd_list[i].fd = 0;
888       schedule->fd_list[i].events = 0;
889       if (schedule->last_fd == i)
890         schedule->last_fd = schedule->max_fd - 1;
891       break;
892     }
893
894   SILC_SCHEDULE_UNLOCK(schedule);
895 }
896
897 /* Register a new signal */
898
899 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
900                                    SilcTaskCallback callback, void *context)
901 {
902   silc_schedule_internal_signal_register(schedule->internal, signal,
903                                          callback, context);
904 }
905
906 /* Unregister a new signal */
907
908 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
909                                      SilcTaskCallback callback, void *context)
910 {
911   silc_schedule_internal_signal_unregister(schedule->internal, signal,
912                                            callback, context);
913 }
914
915 /* Call signal indicated by `signal'. */
916
917 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
918 {
919   /* Mark that signals needs to be delivered later. */
920   silc_schedule_internal_signal_call(schedule->internal, signal);
921   schedule->signal_tasks = TRUE;
922 }
923
924 /* Allocates a newtask task queue into the scheduler */
925
926 static void silc_task_queue_alloc(SilcTaskQueue *queue)
927 {
928   *queue = silc_calloc(1, sizeof(**queue));
929   silc_mutex_alloc(&(*queue)->lock);
930 }
931
932 /* Free's a task queue. */
933
934 static void silc_task_queue_free(SilcTaskQueue queue)
935 {
936   silc_mutex_free(queue->lock);
937   silc_free(queue);
938 }
939
940 /* Return task by its fd. */
941
942 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
943 {
944   SilcTask next;
945
946   if (!queue->task)
947     return NULL;
948
949   next = queue->task;
950
951   while (1) {
952     if (next->fd == fd)
953       return next;
954     if (queue->task == next->next)
955       return NULL;
956     next = next->next;
957   }
958
959   return NULL;
960 }
961
962 /* Adds a non-timeout task into the task queue. This function is used
963    by silc_task_register function. Returns a pointer to the registered 
964    task. */
965
966 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
967                               SilcTaskPriority priority)
968 {
969   SilcTask task, next, prev;
970
971   /* Take the first task in the queue */
972   task = queue->task;
973
974   switch(priority) {
975   case SILC_TASK_PRI_LOW:
976     /* Lowest priority. The task is added at the end of the list. */
977     prev = task->prev;
978     newtask->prev = prev;
979     newtask->next = task;
980     prev->next = newtask;
981     task->prev = newtask;
982     break;
983   case SILC_TASK_PRI_NORMAL:
984     /* Normal priority. The task is added before lower priority tasks
985        but after tasks with higher priority. */
986     prev = task->prev;
987     while(prev != task) {
988       if (prev->priority > SILC_TASK_PRI_LOW)
989         break;
990       prev = prev->prev;
991     }
992     if (prev == task) {
993       /* There are only lower priorities in the list, we will
994          sit before them and become the first task in the queue. */
995       prev = task->prev;
996       newtask->prev = prev;
997       newtask->next = task;
998       task->prev = newtask;
999       prev->next = newtask;
1000
1001       /* We are now the first task in queue */
1002       queue->task = newtask;
1003     } else {
1004       /* Found a spot from the list, add the task to the list. */
1005       next = prev->next;
1006       newtask->prev = prev;
1007       newtask->next = next;
1008       prev->next = newtask;
1009       next->prev = newtask;
1010     }
1011     break;
1012   default:
1013     silc_free(newtask);
1014     return NULL;
1015   }
1016
1017   return newtask;
1018 }
1019
1020 /* Return the timeout task with smallest timeout. */
1021
1022 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1023 {
1024   SilcTask prev, task;
1025
1026   prev = first->prev;
1027
1028   if (first == prev)
1029     return first;
1030
1031   task = first;
1032   while (1) {
1033     if (first == prev)
1034       break;
1035
1036     if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
1037       task = prev;
1038
1039     prev = prev->prev;
1040   }
1041
1042   return task;
1043 }
1044
1045 /* Adds a timeout task into the task queue. This function is used by
1046    silc_task_register function. Returns a pointer to the registered 
1047    task. Timeout tasks are sorted by their timeout value in ascending
1048    order. The priority matters if there are more than one task with
1049    same timeout. */
1050
1051 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1052                                       SilcTaskPriority priority)
1053 {
1054   SilcTask task, prev, next;
1055
1056   /* Take the first task in the queue */
1057   task = queue->task;
1058
1059   /* Take last task from the list */
1060   prev = task->prev;
1061     
1062   switch(priority) {
1063   case SILC_TASK_PRI_LOW:
1064     /* Lowest priority. The task is added at the end of the list. */
1065     while(prev != task) {
1066
1067       /* If we have longer timeout than with the task head of us
1068          we have found our spot. */
1069       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1070                                              &newtask->timeout))
1071         break;
1072
1073       /* If we are equal size of timeout we will be after it. */
1074       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1075                                               &prev->timeout))
1076         break;
1077
1078       /* We have shorter timeout, compare to next one. */
1079       prev = prev->prev;
1080     }
1081     /* Found a spot from the list, add the task to the list. */
1082     next = prev->next;
1083     newtask->prev = prev;
1084     newtask->next = next;
1085     prev->next = newtask;
1086     next->prev = newtask;
1087     
1088     if (prev == task) {
1089       /* Check if we are going to be the first task in the queue */
1090       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1091                                              &newtask->timeout))
1092         break;
1093       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1094                                               &prev->timeout))
1095         break;
1096
1097       /* We are now the first task in queue */
1098       queue->task = newtask;
1099     }
1100     break;
1101   case SILC_TASK_PRI_NORMAL:
1102     /* Normal priority. The task is added before lower priority tasks
1103        but after tasks with higher priority. */
1104     while(prev != task) {
1105
1106       /* If we have longer timeout than with the task head of us
1107          we have found our spot. */
1108       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1109                                              &newtask->timeout))
1110         break;
1111
1112       /* If we are equal size of timeout, priority kicks in place. */
1113       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1114                                               &prev->timeout))
1115         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1116           break;
1117
1118       /* We have shorter timeout or higher priority, compare to next one. */
1119       prev = prev->prev;
1120     }
1121     /* Found a spot from the list, add the task to the list. */
1122     next = prev->next;
1123     newtask->prev = prev;
1124     newtask->next = next;
1125     prev->next = newtask;
1126     next->prev = newtask;
1127     
1128     if (prev == task) {
1129       /* Check if we are going to be the first task in the queue */
1130       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1131                                              &newtask->timeout))
1132         break;
1133       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1134                                               &prev->timeout))
1135         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1136           break;
1137
1138       /* We are now the first task in queue */
1139       queue->task = newtask;
1140     }
1141     break;
1142   default:
1143     silc_free(newtask);
1144     return NULL;
1145   }
1146
1147   return newtask;
1148 }
1149
1150 /* Removes (unregisters) a task from particular task queue. This function
1151    is used internally by scheduler. This must be called holding the 
1152    queue->lock. */
1153
1154 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1155 {
1156   SilcTask first, old, next;
1157
1158   if (!queue || !task)
1159     return FALSE;
1160
1161   if (!queue->task) {
1162     return FALSE;
1163   }
1164
1165   first = queue->task;
1166
1167   /* Unregister all tasks in queue */
1168   if (task == SILC_ALL_TASKS) {
1169     SILC_LOG_DEBUG(("Removing all tasks at once"));
1170     next = first;
1171
1172     while(1) {
1173       next = next->next;
1174       silc_free(next->prev);
1175       if (next == first)
1176         break;
1177     }
1178
1179     queue->task = NULL;
1180     return TRUE;
1181   }
1182
1183   SILC_LOG_DEBUG(("Removing task"));
1184
1185   /* Unregister the task */
1186   old = first;
1187   while(1) {
1188     if (old == task) {
1189       SilcTask prev, next;
1190
1191       prev = old->prev;
1192       next = old->next;
1193       prev->next = next;
1194       next->prev = prev;
1195
1196       if (prev == old && next == old)
1197         queue->task = NULL;
1198       if (queue->task == old)
1199         queue->task = silc_task_get_first(queue, next);
1200       
1201       silc_free(old);
1202       return TRUE;
1203     }
1204     old = old->prev;
1205
1206     if (old == first) {
1207       return FALSE;
1208     }
1209   }
1210 }
1211
1212 /* Compare two time values. If the first argument is smaller than the
1213    second this function returns TRUE. */
1214
1215 static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
1216                                               struct timeval *bigger)
1217 {
1218   if ((smaller->tv_sec < bigger->tv_sec) ||
1219       ((smaller->tv_sec == bigger->tv_sec) &&
1220        (smaller->tv_usec < bigger->tv_usec)))
1221     return TRUE;
1222
1223   return FALSE;
1224 }
1225
1226 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1227 {
1228   SilcTask next;
1229
1230   silc_mutex_lock(queue->lock);
1231
1232   if (!queue->task) {
1233     silc_mutex_unlock(queue->lock);
1234     return;
1235   }
1236
1237   next = queue->task;
1238
1239   while(1) {
1240     if (next->fd == fd)
1241       next->valid = FALSE;
1242     if (queue->task == next->next)
1243       break;
1244     next = next->next;
1245   }
1246
1247   silc_mutex_unlock(queue->lock);
1248 }
1249
1250 static void silc_task_del_by_callback(SilcTaskQueue queue,
1251                                       SilcTaskCallback callback)
1252 {
1253   SilcTask next;
1254
1255   silc_mutex_lock(queue->lock);
1256
1257   if (!queue->task) {
1258     silc_mutex_unlock(queue->lock);
1259     return;
1260   }
1261
1262   next = queue->task;
1263
1264   while(1) {
1265     if (next->callback == callback)
1266       next->valid = FALSE;
1267     if (queue->task == next->next)
1268       break;
1269     next = next->next;
1270   }
1271
1272   silc_mutex_unlock(queue->lock);
1273 }
1274
1275 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1276 {
1277   SilcTask next;
1278
1279   silc_mutex_lock(queue->lock);
1280
1281   if (!queue->task) {
1282     silc_mutex_unlock(queue->lock);
1283     return;
1284   }
1285
1286   next = queue->task;
1287
1288   while(1) {
1289     if (next->context == context)
1290       next->valid = FALSE;
1291     if (queue->task == next->next)
1292       break;
1293     next = next->next;
1294   }
1295
1296   silc_mutex_unlock(queue->lock);
1297 }