Some bugfixes for the new signals support
[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   /* Deliver signals if any has been set to be called */
567   if (schedule->signal_tasks) {
568     SILC_SCHEDULE_UNLOCK(schedule);
569     silc_schedule_internal_signals_call(schedule->internal, schedule);
570     schedule->signal_tasks = FALSE;
571     SILC_SCHEDULE_LOCK(schedule);
572   }
573
574   /* If the task queues aren't initialized or we aren't valid anymore
575      we will return */
576   if ((!schedule->fd_queue && !schedule->timeout_queue 
577        && !schedule->generic_queue) || schedule->valid == FALSE) {
578     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
579     if (!schedule->is_locked)
580       SILC_SCHEDULE_UNLOCK(schedule);
581     return FALSE;
582   }
583
584   /* Calculate next timeout for silc_select(). This is the timeout value
585      when at earliest some of the timeout tasks expire. */
586   silc_mutex_lock(schedule->timeout_queue->lock);
587   silc_schedule_select_timeout(schedule);
588   silc_mutex_unlock(schedule->timeout_queue->lock);
589
590   if (timeout_usecs >= 0) {
591     timeout.tv_sec = 0;
592     timeout.tv_usec = timeout_usecs;
593     schedule->timeout = &timeout;
594   }
595
596   SILC_SCHEDULE_UNLOCK(schedule);
597
598   /* This is the main select(). The program blocks here until some
599      of the selected file descriptors change status or the selected
600      timeout expires. */
601   SILC_LOG_DEBUG(("Select"));
602   ret = silc_select(schedule->fd_list, schedule->last_fd + 1, 
603                     schedule->timeout);
604
605   SILC_SCHEDULE_LOCK(schedule);
606
607   switch (ret) {
608   case -1:
609     /* Error */
610     if (errno == EINTR)
611       break;
612     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
613     break;
614   case 0:
615     /* Timeout */
616     silc_mutex_lock(schedule->timeout_queue->lock);
617     silc_schedule_dispatch_timeout(schedule);
618     silc_mutex_unlock(schedule->timeout_queue->lock);
619     break;
620   default:
621     /* There is some data available now */
622     SILC_LOG_DEBUG(("Running non-timeout tasks"));
623     silc_schedule_dispatch_nontimeout(schedule);
624     break;
625   }
626
627   if (!schedule->is_locked)
628     SILC_SCHEDULE_UNLOCK(schedule);
629
630   return TRUE;
631 }
632
633 /* The SILC scheduler. This is actually the main routine in SILC programs.
634    When this returns the program is to be ended. Before this function can
635    be called, one must call silc_schedule_init function. */
636
637 void silc_schedule(SilcSchedule schedule)
638 {
639   SILC_LOG_DEBUG(("Running scheduler"));
640
641   if (schedule->valid == FALSE) {
642     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
643     return;
644   }
645
646   SILC_SCHEDULE_LOCK(schedule);
647   schedule->is_locked = TRUE;
648
649   /* Start the scheduler loop */
650   while (silc_schedule_one(schedule, -1)) 
651     ;
652
653   SILC_SCHEDULE_UNLOCK(schedule);
654 }
655
656 /* Wakes up the scheduler. This is used only in multi-threaded
657    environments where threads may add new tasks or remove old tasks
658    from task queues. This is called to wake up the scheduler in the
659    main thread so that it detects the changes in the task queues.
660    If threads support is not compiled in this function has no effect.
661    Implementation of this function is platform specific. */
662
663 void silc_schedule_wakeup(SilcSchedule schedule)
664 {
665 #ifdef SILC_THREADS
666   SILC_LOG_DEBUG(("Wakeup scheduler"));
667   SILC_SCHEDULE_LOCK(schedule);
668   silc_schedule_internal_wakeup(schedule->internal);
669   SILC_SCHEDULE_UNLOCK(schedule);
670 #endif
671 }
672
673 /* Add new task to the scheduler */
674
675 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
676                                 SilcTaskCallback callback, void *context, 
677                                 long seconds, long useconds, 
678                                 SilcTaskType type, 
679                                 SilcTaskPriority priority)
680 {
681   SilcTask newtask;
682   SilcTaskQueue queue;
683   int timeout = FALSE;
684
685   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
686                   type, priority));
687
688   queue = SILC_SCHEDULE_GET_QUEUE(type);
689     
690   /* If the task is generic task, we check whether this task has already
691      been registered. Generic tasks are registered only once and after that
692      the same task applies to all file descriptors to be registered. */
693   if (type == SILC_TASK_GENERIC) {
694     silc_mutex_lock(queue->lock);
695
696     if (queue->task) {
697       SilcTask task = queue->task;
698       while(1) {
699         if ((task->callback == callback) && (task->context == context)) {
700           SILC_LOG_DEBUG(("Found matching generic task, using the match"));
701           
702           silc_mutex_unlock(queue->lock);
703
704           /* Add the fd to be listened, the task found now applies to this
705              fd as well. */
706           silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
707           return task;
708         }
709         
710         if (queue->task == task->next)
711           break;
712         
713         task = task->next;
714       }
715     }
716
717     silc_mutex_unlock(queue->lock);
718   }
719
720   newtask = silc_calloc(1, sizeof(*newtask));
721   newtask->fd = fd;
722   newtask->context = context;
723   newtask->callback = callback;
724   newtask->valid = TRUE;
725   newtask->priority = priority;
726   newtask->type = type;
727   newtask->next = newtask;
728   newtask->prev = newtask;
729
730   /* Create timeout if marked to be timeout task */
731   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
732     silc_gettimeofday(&newtask->timeout);
733     newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
734     newtask->timeout.tv_usec += (useconds % 1000000L);
735     if (newtask->timeout.tv_usec > 999999L) {
736       newtask->timeout.tv_sec += 1;
737       newtask->timeout.tv_usec -= 1000000L;
738     }
739     timeout = TRUE;
740   }
741
742   /* If the task is non-timeout task we have to tell the scheduler that we
743      would like to have these tasks scheduled at some odd distant future. */
744   if (type != SILC_TASK_TIMEOUT)
745     silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
746
747   silc_mutex_lock(queue->lock);
748
749   /* Is this first task of the queue? */
750   if (queue->task == NULL) {
751     queue->task = newtask;
752     silc_mutex_unlock(queue->lock);
753     return newtask;
754   }
755
756   if (timeout)
757     newtask = silc_task_add_timeout(queue, newtask, priority);
758   else
759     newtask = silc_task_add(queue, newtask, priority);
760
761   silc_mutex_unlock(queue->lock);
762
763   return newtask;
764 }
765
766 /* Removes a task from the scheduler */
767
768 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
769 {
770   SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
771
772   /* Unregister all tasks */
773   if (task == SILC_ALL_TASKS) {
774     SilcTask next;
775     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
776
777     silc_mutex_lock(queue->lock);
778
779     if (!queue->task) {
780       silc_mutex_unlock(queue->lock);
781       return;
782     }
783
784     next = queue->task;
785     
786     while(1) {
787       if (next->valid)
788         next->valid = FALSE;
789       if (queue->task == next->next)
790         break;
791       next = next->next;
792     }
793
794     silc_mutex_unlock(queue->lock);
795     return;
796   }
797
798   SILC_LOG_DEBUG(("Unregistering task"));
799
800   silc_mutex_lock(queue->lock);
801
802   /* Unregister the specific task */
803   if (task->valid)
804     task->valid = FALSE;
805
806   silc_mutex_unlock(queue->lock);
807 }
808
809 /* Remove task by fd */
810
811 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
812 {
813   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
814
815   silc_task_del_by_fd(schedule->timeout_queue, fd);
816   silc_task_del_by_fd(schedule->fd_queue, fd);
817 }
818
819 /* Remove task by task callback. */
820
821 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
822                                         SilcTaskCallback callback)
823 {
824   SILC_LOG_DEBUG(("Unregister task by callback"));
825
826   silc_task_del_by_callback(schedule->timeout_queue, callback);
827   silc_task_del_by_callback(schedule->fd_queue, callback);
828   silc_task_del_by_callback(schedule->generic_queue, callback);
829 }
830
831 /* Remove task by context. */
832
833 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
834 {
835   SILC_LOG_DEBUG(("Unregister task by context"));
836
837   silc_task_del_by_context(schedule->timeout_queue, context);
838   silc_task_del_by_context(schedule->fd_queue, context);
839   silc_task_del_by_context(schedule->generic_queue, context);
840 }
841
842 /* Sets a file descriptor to be listened by select() in scheduler. One can
843    call this directly if wanted. This can be called multiple times for
844    one file descriptor to set different iomasks. */
845
846 void silc_schedule_set_listen_fd(SilcSchedule schedule,
847                                  SilcUInt32 fd, SilcTaskEvent iomask)
848 {
849   int i;
850   bool found = FALSE;
851
852   SILC_SCHEDULE_LOCK(schedule);
853
854   for (i = 0; i < schedule->max_fd; i++)
855     if (schedule->fd_list[i].fd == fd) {
856       schedule->fd_list[i].fd = fd;
857       schedule->fd_list[i].events = iomask;
858       if (i > schedule->last_fd)
859         schedule->last_fd = i;
860       found = TRUE;
861       break;
862     }
863
864   if (!found)
865     for (i = 0; i < schedule->max_fd; i++)
866       if (schedule->fd_list[i].events == 0) {
867         schedule->fd_list[i].fd = fd;
868         schedule->fd_list[i].events = iomask;
869         if (i > schedule->last_fd)
870           schedule->last_fd = i;
871         break;
872       }
873
874   SILC_SCHEDULE_UNLOCK(schedule);
875 }
876
877 /* Removes a file descriptor from listen list. */
878
879 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
880 {
881   int i;
882
883   SILC_SCHEDULE_LOCK(schedule);
884
885   SILC_LOG_DEBUG(("Unset listen fd %d", fd));
886
887   for (i = 0; i < schedule->max_fd; i++)
888     if (schedule->fd_list[i].fd == fd) {
889       schedule->fd_list[i].fd = 0;
890       schedule->fd_list[i].events = 0;
891       if (schedule->last_fd == i)
892         schedule->last_fd = schedule->max_fd - 1;
893       break;
894     }
895
896   SILC_SCHEDULE_UNLOCK(schedule);
897 }
898
899 /* Register a new signal */
900
901 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
902                                    SilcTaskCallback callback, void *context)
903 {
904   silc_schedule_internal_signal_register(schedule->internal, signal,
905                                          callback, context);
906 }
907
908 /* Unregister a new signal */
909
910 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
911                                      SilcTaskCallback callback, void *context)
912 {
913   silc_schedule_internal_signal_unregister(schedule->internal, signal,
914                                            callback, context);
915 }
916
917 /* Call signal indicated by `signal'. */
918
919 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
920 {
921   /* Mark that signals needs to be delivered later. */
922   silc_schedule_internal_signal_call(schedule->internal, signal);
923   schedule->signal_tasks = TRUE;
924 }
925
926 /* Allocates a newtask task queue into the scheduler */
927
928 static void silc_task_queue_alloc(SilcTaskQueue *queue)
929 {
930   *queue = silc_calloc(1, sizeof(**queue));
931   silc_mutex_alloc(&(*queue)->lock);
932 }
933
934 /* Free's a task queue. */
935
936 static void silc_task_queue_free(SilcTaskQueue queue)
937 {
938   silc_mutex_free(queue->lock);
939   silc_free(queue);
940 }
941
942 /* Return task by its fd. */
943
944 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
945 {
946   SilcTask next;
947
948   if (!queue->task)
949     return NULL;
950
951   next = queue->task;
952
953   while (1) {
954     if (next->fd == fd)
955       return next;
956     if (queue->task == next->next)
957       return NULL;
958     next = next->next;
959   }
960
961   return NULL;
962 }
963
964 /* Adds a non-timeout task into the task queue. This function is used
965    by silc_task_register function. Returns a pointer to the registered 
966    task. */
967
968 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
969                               SilcTaskPriority priority)
970 {
971   SilcTask task, next, prev;
972
973   /* Take the first task in the queue */
974   task = queue->task;
975
976   switch(priority) {
977   case SILC_TASK_PRI_LOW:
978     /* Lowest priority. The task is added at the end of the list. */
979     prev = task->prev;
980     newtask->prev = prev;
981     newtask->next = task;
982     prev->next = newtask;
983     task->prev = newtask;
984     break;
985   case SILC_TASK_PRI_NORMAL:
986     /* Normal priority. The task is added before lower priority tasks
987        but after tasks with higher priority. */
988     prev = task->prev;
989     while(prev != task) {
990       if (prev->priority > SILC_TASK_PRI_LOW)
991         break;
992       prev = prev->prev;
993     }
994     if (prev == task) {
995       /* There are only lower priorities in the list, we will
996          sit before them and become the first task in the queue. */
997       prev = task->prev;
998       newtask->prev = prev;
999       newtask->next = task;
1000       task->prev = newtask;
1001       prev->next = newtask;
1002
1003       /* We are now the first task in queue */
1004       queue->task = newtask;
1005     } else {
1006       /* Found a spot from the list, add the task to the list. */
1007       next = prev->next;
1008       newtask->prev = prev;
1009       newtask->next = next;
1010       prev->next = newtask;
1011       next->prev = newtask;
1012     }
1013     break;
1014   default:
1015     silc_free(newtask);
1016     return NULL;
1017   }
1018
1019   return newtask;
1020 }
1021
1022 /* Return the timeout task with smallest timeout. */
1023
1024 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1025 {
1026   SilcTask prev, task;
1027
1028   prev = first->prev;
1029
1030   if (first == prev)
1031     return first;
1032
1033   task = first;
1034   while (1) {
1035     if (first == prev)
1036       break;
1037
1038     if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
1039       task = prev;
1040
1041     prev = prev->prev;
1042   }
1043
1044   return task;
1045 }
1046
1047 /* Adds a timeout task into the task queue. This function is used by
1048    silc_task_register function. Returns a pointer to the registered 
1049    task. Timeout tasks are sorted by their timeout value in ascending
1050    order. The priority matters if there are more than one task with
1051    same timeout. */
1052
1053 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1054                                       SilcTaskPriority priority)
1055 {
1056   SilcTask task, prev, next;
1057
1058   /* Take the first task in the queue */
1059   task = queue->task;
1060
1061   /* Take last task from the list */
1062   prev = task->prev;
1063     
1064   switch(priority) {
1065   case SILC_TASK_PRI_LOW:
1066     /* Lowest priority. The task is added at the end of the list. */
1067     while(prev != task) {
1068
1069       /* If we have longer timeout than with the task head of us
1070          we have found our spot. */
1071       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1072                                              &newtask->timeout))
1073         break;
1074
1075       /* If we are equal size of timeout we will be after it. */
1076       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1077                                               &prev->timeout))
1078         break;
1079
1080       /* We have shorter timeout, compare to next one. */
1081       prev = prev->prev;
1082     }
1083     /* Found a spot from the list, add the task to the list. */
1084     next = prev->next;
1085     newtask->prev = prev;
1086     newtask->next = next;
1087     prev->next = newtask;
1088     next->prev = newtask;
1089     
1090     if (prev == task) {
1091       /* Check if we are going to be the first task in the queue */
1092       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1093                                              &newtask->timeout))
1094         break;
1095       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1096                                               &prev->timeout))
1097         break;
1098
1099       /* We are now the first task in queue */
1100       queue->task = newtask;
1101     }
1102     break;
1103   case SILC_TASK_PRI_NORMAL:
1104     /* Normal priority. The task is added before lower priority tasks
1105        but after tasks with higher priority. */
1106     while(prev != task) {
1107
1108       /* If we have longer timeout than with the task head of us
1109          we have found our spot. */
1110       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1111                                              &newtask->timeout))
1112         break;
1113
1114       /* If we are equal size of timeout, priority kicks in place. */
1115       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1116                                               &prev->timeout))
1117         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1118           break;
1119
1120       /* We have shorter timeout or higher priority, compare to next one. */
1121       prev = prev->prev;
1122     }
1123     /* Found a spot from the list, add the task to the list. */
1124     next = prev->next;
1125     newtask->prev = prev;
1126     newtask->next = next;
1127     prev->next = newtask;
1128     next->prev = newtask;
1129     
1130     if (prev == task) {
1131       /* Check if we are going to be the first task in the queue */
1132       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1133                                              &newtask->timeout))
1134         break;
1135       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1136                                               &prev->timeout))
1137         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1138           break;
1139
1140       /* We are now the first task in queue */
1141       queue->task = newtask;
1142     }
1143     break;
1144   default:
1145     silc_free(newtask);
1146     return NULL;
1147   }
1148
1149   return newtask;
1150 }
1151
1152 /* Removes (unregisters) a task from particular task queue. This function
1153    is used internally by scheduler. This must be called holding the 
1154    queue->lock. */
1155
1156 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1157 {
1158   SilcTask first, old, next;
1159
1160   if (!queue || !task)
1161     return FALSE;
1162
1163   if (!queue->task) {
1164     return FALSE;
1165   }
1166
1167   first = queue->task;
1168
1169   /* Unregister all tasks in queue */
1170   if (task == SILC_ALL_TASKS) {
1171     SILC_LOG_DEBUG(("Removing all tasks at once"));
1172     next = first;
1173
1174     while(1) {
1175       next = next->next;
1176       silc_free(next->prev);
1177       if (next == first)
1178         break;
1179     }
1180
1181     queue->task = NULL;
1182     return TRUE;
1183   }
1184
1185   SILC_LOG_DEBUG(("Removing task"));
1186
1187   /* Unregister the task */
1188   old = first;
1189   while(1) {
1190     if (old == task) {
1191       SilcTask prev, next;
1192
1193       prev = old->prev;
1194       next = old->next;
1195       prev->next = next;
1196       next->prev = prev;
1197
1198       if (prev == old && next == old)
1199         queue->task = NULL;
1200       if (queue->task == old)
1201         queue->task = silc_task_get_first(queue, next);
1202       
1203       silc_free(old);
1204       return TRUE;
1205     }
1206     old = old->prev;
1207
1208     if (old == first) {
1209       return FALSE;
1210     }
1211   }
1212 }
1213
1214 /* Compare two time values. If the first argument is smaller than the
1215    second this function returns TRUE. */
1216
1217 static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
1218                                               struct timeval *bigger)
1219 {
1220   if ((smaller->tv_sec < bigger->tv_sec) ||
1221       ((smaller->tv_sec == bigger->tv_sec) &&
1222        (smaller->tv_usec < bigger->tv_usec)))
1223     return TRUE;
1224
1225   return FALSE;
1226 }
1227
1228 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1229 {
1230   SilcTask next;
1231
1232   silc_mutex_lock(queue->lock);
1233
1234   if (!queue->task) {
1235     silc_mutex_unlock(queue->lock);
1236     return;
1237   }
1238
1239   next = queue->task;
1240
1241   while(1) {
1242     if (next->fd == fd)
1243       next->valid = FALSE;
1244     if (queue->task == next->next)
1245       break;
1246     next = next->next;
1247   }
1248
1249   silc_mutex_unlock(queue->lock);
1250 }
1251
1252 static void silc_task_del_by_callback(SilcTaskQueue queue,
1253                                       SilcTaskCallback callback)
1254 {
1255   SilcTask next;
1256
1257   silc_mutex_lock(queue->lock);
1258
1259   if (!queue->task) {
1260     silc_mutex_unlock(queue->lock);
1261     return;
1262   }
1263
1264   next = queue->task;
1265
1266   while(1) {
1267     if (next->callback == callback)
1268       next->valid = FALSE;
1269     if (queue->task == next->next)
1270       break;
1271     next = next->next;
1272   }
1273
1274   silc_mutex_unlock(queue->lock);
1275 }
1276
1277 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1278 {
1279   SilcTask next;
1280
1281   silc_mutex_lock(queue->lock);
1282
1283   if (!queue->task) {
1284     silc_mutex_unlock(queue->lock);
1285     return;
1286   }
1287
1288   next = queue->task;
1289
1290   while(1) {
1291     if (next->context == context)
1292       next->valid = FALSE;
1293     if (queue->task == next->next)
1294       break;
1295     next = next->next;
1296   }
1297
1298   silc_mutex_unlock(queue->lock);
1299 }