updates.
[silc.git] / lib / silcutil / silctask.c
1 /*
2
3   silctask.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 /* $Id$ */
21
22 #include "silcincludes.h"
23
24 /* Allocates a new task queue into the Silc. If 'valid' is TRUE the
25    queue becomes valid task queue. If it is FALSE scheduler will skip
26    the queue. */
27
28 void silc_task_queue_alloc(SilcTaskQueue *new, int valid)
29 {
30
31   SILC_LOG_DEBUG(("Allocating new task queue"));
32
33   *new = silc_calloc(1, sizeof(**new));
34
35   /* Set the pointers */
36   (*new)->valid = valid;
37   (*new)->task = NULL;
38   (*new)->register_task = silc_task_register;
39   (*new)->unregister_task = silc_task_unregister;
40   (*new)->set_iotype = silc_task_set_iotype;
41   (*new)->reset_iotype = silc_task_reset_iotype;
42 }
43
44 /* Free's a task queue. */
45
46 void silc_task_queue_free(SilcTaskQueue old)
47 {
48   if (old)
49     silc_free(old);
50 }
51
52 /* Adds a non-timeout task into the task queue. This function is used
53    by silc_task_register function. Returns a pointer to the registered 
54    task. */
55
56 SilcTask silc_task_add(SilcTaskQueue queue, SilcTask new, 
57                        SilcTaskPriority priority)
58 {
59   SilcTask task, next, prev;
60
61   /* Take the first task in the queue */
62   task = queue->task;
63
64   switch(priority) {
65   case SILC_TASK_PRI_LOW:
66     /* Lowest priority. The task is added at the end of the list. */
67     prev = task->prev;
68     new->prev = prev;
69     new->next = task;
70     prev->next = new;
71     task->prev = new;
72     break;
73   case SILC_TASK_PRI_NORMAL:
74     /* Normal priority. The task is added before lower priority tasks
75        but after tasks with higher priority. */
76     prev = task->prev;
77     while(prev != task) {
78       if (prev->priority > SILC_TASK_PRI_LOW &&
79           prev->priority <= SILC_TASK_PRI_REALTIME)
80         break;
81       prev = prev->prev;
82     }
83     if (prev == task) {
84       /* There are only lower priorities in the list, we will
85          sit before them and become the first task in the queue. */
86       prev = task->prev;
87       new->prev = prev;
88       new->next = task;
89       task->prev = new;
90       prev->next = new;
91
92       /* We are now the first task in queue */
93       queue->task = new;
94     } else {
95       /* Found a spot from the list, add the task to the list. */
96       next = prev->next;
97       new->prev = prev;
98       new->next = next;
99       prev->next = new;
100       next->prev = new;
101     }
102     break;
103   case SILC_TASK_PRI_HIGH:
104     /* High priority. The task is added before lower priority tasks
105        but after tasks with higher priority. */
106     prev = task->prev;
107     while(prev != task) {
108       if (prev->priority > SILC_TASK_PRI_NORMAL &&
109           prev->priority <= SILC_TASK_PRI_REALTIME)
110         break;
111       prev = prev->prev;
112     }
113     if (prev == task) {
114       /* There are only lower priorities in the list, we will
115          sit before them and become the first task in the queue. */
116       prev = task->prev;
117       new->prev = prev;
118       new->next = task;
119       task->prev = new;
120       prev->next = new;
121
122       /* We are now the first task in queue */
123       queue->task = new;
124     } else {
125       /* Found a spot from the list, add the task to the list. */
126       next = prev->next;
127       new->prev = prev;
128       new->next = next;
129       prev->next = new;
130       next->prev = new;
131     }
132     break;
133   case SILC_TASK_PRI_REALTIME:
134     /* Highest priority. The task is added at the head of the list. 
135        The last registered task is added to the very head of the list
136        thus we get the LIFO (Last-In-First-Out) order. */
137     prev = task->prev;
138     new->prev = prev;
139     new->next = task;
140     prev->next = new;
141     task->prev = new;
142
143     /* We are the first task in the queue */
144     queue->task = new;
145     break;
146   default:
147     silc_free(new);
148     return NULL;
149   }
150
151   return new;
152 }
153
154 #if 0
155 void dump_tasks(SilcTaskQueue queue)
156 {
157   SilcTask first, prev;
158
159   if (!queue->task)
160     return;
161
162   first = queue->task;
163
164   fprintf(stderr, "\nqueue->task:\t%p\t%d\n", queue->task,
165           queue->task->timeout.tv_sec);
166
167   prev = first->prev;
168   while (1) {
169     if (first == prev)
170       break;
171
172     fprintf(stderr, "task       :\t%p\t%d\n", prev, prev->timeout.tv_sec);
173
174     prev = prev->prev;
175   }
176   fprintf(stderr, "\n");
177 }
178 #endif
179
180 /* Return the timeout task with smallest timeout. */
181
182 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
183 {
184   SilcTask prev, task;
185
186   prev = first->prev;
187
188   if (first == prev)
189     return first;
190
191   task = first;
192   while (1) {
193     if (first == prev)
194       break;
195
196     if (silc_task_timeout_compare(&prev->timeout, &task->timeout))
197       task = prev;
198
199     prev = prev->prev;
200   }
201
202   return task;
203 }
204
205 /* Adds a timeout task into the task queue. This function is used by
206    silc_task_register function. Returns a pointer to the registered 
207    task. Timeout tasks are sorted by their timeout value in ascending
208    order. The priority matters if there are more than one task with
209    same timeout. */
210
211 SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask new,
212                                SilcTaskPriority priority)
213 {
214   SilcTask task, prev, next;
215
216   /* Take the first task in the queue */
217   task = queue->task;
218
219   /* Take last task from the list */
220   prev = task->prev;
221     
222   switch(priority) {
223   case SILC_TASK_PRI_LOW:
224     /* Lowest priority. The task is added at the end of the list. */
225     while(prev != task) {
226
227       /* If we have longer timeout than with the task head of us
228          we have found our spot. */
229       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
230         break;
231
232       /* If we are equal size of timeout we will be after it. */
233       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
234         break;
235
236       /* We have shorter timeout, compare to next one. */
237       prev = prev->prev;
238     }
239     /* Found a spot from the list, add the task to the list. */
240     next = prev->next;
241     new->prev = prev;
242     new->next = next;
243     prev->next = new;
244     next->prev = new;
245     
246     if (prev == task) {
247       /* Check if we are going to be the first task in the queue */
248       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
249         break;
250       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
251         break;
252
253       /* We are now the first task in queue */
254       queue->task = new;
255     }
256     break;
257   case SILC_TASK_PRI_NORMAL:
258     /* Normal priority. The task is added before lower priority tasks
259        but after tasks with higher priority. */
260     while(prev != task) {
261
262       /* If we have longer timeout than with the task head of us
263          we have found our spot. */
264       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
265         break;
266
267       /* If we are equal size of timeout, priority kicks in place. */
268       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
269         if (prev->priority >= SILC_TASK_PRI_NORMAL)
270           break;
271
272       /* We have shorter timeout or higher priority, compare to next one. */
273       prev = prev->prev;
274     }
275     /* Found a spot from the list, add the task to the list. */
276     next = prev->next;
277     new->prev = prev;
278     new->next = next;
279     prev->next = new;
280     next->prev = new;
281     
282     if (prev == task) {
283       /* Check if we are going to be the first task in the queue */
284       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
285         break;
286       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
287         if (prev->priority >= SILC_TASK_PRI_NORMAL)
288           break;
289
290       /* We are now the first task in queue */
291       queue->task = new;
292     }
293     break;
294   case SILC_TASK_PRI_HIGH:
295     /* High priority. The task is added before lower priority tasks
296        but after tasks with higher priority. */
297     while(prev != task) {
298
299       /* If we have longer timeout than with the task head of us
300          we have found our spot. */
301       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
302         break;
303
304       /* If we are equal size of timeout, priority kicks in place. */
305       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
306         if (prev->priority >= SILC_TASK_PRI_HIGH)
307           break;
308
309       /* We have shorter timeout or higher priority, compare to next one. */
310       prev = prev->prev;
311     }
312     /* Found a spot from the list, add the task to the list. */
313     next = prev->next;
314     new->prev = prev;
315     new->next = next;
316     prev->next = new;
317     next->prev = new;
318     
319     if (prev == task) {
320       /* Check if we are going to be the first task in the queue */
321       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
322         break;
323       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
324         if (prev->priority >= SILC_TASK_PRI_HIGH)
325           break;
326
327       /* We are now the first task in queue */
328       queue->task = new;
329     }
330     break;
331   case SILC_TASK_PRI_REALTIME:
332     /* Highest priority. The task is added at the head of the list. 
333        The last registered task is added to the very head of the list
334        thus we get the LIFO (Last-In-First-Out) order. */
335     next = task->next;
336     while(next != task) {
337
338       /* If we have shorter timeout than the next task we've found
339          our spot. */
340       if (silc_task_timeout_compare(&new->timeout, &next->timeout))
341         break;
342
343       /* If we are equal size of timeout we will be first. */
344       if (!silc_task_timeout_compare(&next->timeout, &new->timeout))
345         break;
346
347       /* We have longer timeout, compare to next one. */
348       next = next->next;
349     }
350     /* Found a spot from the list, add the task to the list. */
351     prev = next->prev;
352     new->next = next;
353     new->prev = prev;
354     prev->next = new;
355     next->prev = new;
356     
357     if (next == task) {
358       /* Check if we are going to be the first task in the queue */
359       if (silc_task_timeout_compare(&next->timeout, &new->timeout))
360         break;
361
362       /* We are now the first task in queue */
363       queue->task = new;
364     }
365   default:
366     silc_free(new);
367     return NULL;
368   }
369
370 #if 0
371   dump_tasks(queue);
372 #endif
373
374   return new;
375 }
376
377 /* Registers a new task into the task queue. The task becomes valid
378    automatically when it is registered. Returns a pointer to the 
379    registered task. */
380
381 SilcTask silc_task_register(SilcTaskQueue queue, int fd, 
382                             SilcTaskCallback cb, void *context, 
383                             long seconds, long useconds, 
384                             SilcTaskType type, SilcTaskPriority priority)
385 {
386   SilcTask new;
387   int timeout = FALSE;
388
389   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", 
390                   fd, type, priority));
391
392   /* If the task is generic task, we check whether this task has already
393      been registered. Generic tasks are registered only once and after that
394      the same task applies to all file descriptors to be registered. */
395   if ((type == SILC_TASK_GENERIC) && queue->task) {
396     SilcTask task;
397
398     task = queue->task;
399     while(1) {
400       if ((task->callback == cb) && (task->context == context)) {
401         SILC_LOG_DEBUG(("Found matching generic task, using the match"));
402
403         /* Add the fd to be listened, the task found now applies to this
404            fd as well. */
405         silc_schedule_set_listen_fd(fd, (1L << SILC_TASK_READ));
406         return task;
407       }
408
409       if (queue->task == task->next)
410         break;
411       
412       task = task->next;
413     }
414   }
415
416   new = silc_calloc(1, sizeof(*new));
417   new->fd = fd;
418   new->context = context;
419   new->callback = cb;
420   new->valid = TRUE;
421   new->priority = priority;
422   new->iomask = (1L << SILC_TASK_READ);
423   new->next = new;
424   new->prev = new;
425
426   /* If the task is non-timeout task we have to tell the scheduler that we
427      would like to have these tasks scheduled at some odd distant future. */
428   if (type != SILC_TASK_TIMEOUT)
429     silc_schedule_set_listen_fd(fd, (1L << SILC_TASK_READ));
430
431   /* Create timeout if marked to be timeout task */
432   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
433     gettimeofday(&new->timeout, NULL);
434     new->timeout.tv_sec += seconds + (useconds / 1000000L);
435     new->timeout.tv_usec += (useconds % 1000000L);
436     if (new->timeout.tv_usec > 999999L) {
437       new->timeout.tv_sec += 1;
438       new->timeout.tv_usec -= 1000000L;
439     }
440     timeout = TRUE;
441   }
442
443   /* Is this first task of the queue? */
444   if (queue->task == NULL) {
445     queue->task = new;
446     return new;
447   }
448
449   if (timeout)
450     return silc_task_add_timeout(queue, new, priority);
451   else
452     return silc_task_add(queue, new, priority);
453 }
454
455 /* Removes (unregisters) a task from particular task queue. This function
456    is used internally by scheduler. One should not call this function
457    to unregister tasks, instead silc_task_unregister_task function
458    should be used. */
459
460 int silc_task_remove(SilcTaskQueue queue, SilcTask task)
461 {
462   SilcTask first, old, next;
463
464   if (!queue || !queue->task)
465     return FALSE;
466
467   first = queue->task;
468
469   /* Unregister all tasks in queue */
470   if (task == SILC_ALL_TASKS) {
471     SILC_LOG_DEBUG(("Removing all tasks at once"));
472     next = first;
473
474     while(1) {
475       next = next->next;
476       silc_free(next->prev);
477       if (next == first)
478         break;
479     }
480
481     queue->task = NULL;
482     return TRUE;
483   }
484
485   SILC_LOG_DEBUG(("Removing task"));
486
487   /* Unregister the task */
488   old = first;
489   while(1) {
490     if (old == task) {
491       SilcTask prev, next;
492
493       prev = old->prev;
494       next = old->next;
495       prev->next = next;
496       next->prev = prev;
497
498       if (prev == old && next == old)
499         queue->task = NULL;
500       if (queue->task == old)
501         queue->task = silc_task_get_first(queue, next);
502       /*queue->task = next;*/
503       
504 #if 0
505       dump_tasks(queue);
506 #endif
507
508       silc_free(old);
509       return TRUE;
510     }
511     old = old->prev;
512
513     if (old == first)
514       return FALSE;
515   }
516 }
517
518 /* Unregisters a task from the task queue. This is the unregister_task
519    function pointer in task queue object. One should use this function
520    to unregister tasks. This function invalidates the task. */
521
522 void silc_task_unregister(SilcTaskQueue queue, SilcTask task)
523 {
524
525   /* Unregister all tasks */
526   if (task == SILC_ALL_TASKS) {
527     SilcTask next;
528     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
529
530     if (queue->task == NULL)
531       return;
532
533     next = queue->task;
534     
535     while(1) {
536       if (next->valid)
537         next->valid = FALSE;
538       if (queue->task == next->next)
539         break;
540       next = next->next;
541     }
542     return;
543   }
544
545   SILC_LOG_DEBUG(("Unregistering task"));
546
547   /* Unregister the specific task */
548   if (task->valid)
549     task->valid = FALSE;
550 }
551
552 /* Unregister a task by file descriptor. This invalidates the task. */
553
554 void silc_task_unregister_by_fd(SilcTaskQueue queue, int fd)
555 {
556   SilcTask next;
557
558   SILC_LOG_DEBUG(("Unregister task by fd"));
559
560   if (queue->task == NULL)
561     return;
562
563   next = queue->task;
564
565   while(1) {
566     if (next->fd == fd)
567       next->valid = FALSE;
568     if (queue->task == next->next)
569       break;
570     next = next->next;
571   }
572 }
573
574 /* Unregister a task by callback function. This invalidates the task. */
575
576 void silc_task_unregister_by_callback(SilcTaskQueue queue, 
577                                       SilcTaskCallback callback)
578 {
579   SilcTask next;
580
581   SILC_LOG_DEBUG(("Unregister task by callback"));
582
583   if (queue->task == NULL)
584     return;
585
586   next = queue->task;
587
588   while(1) {
589     if (next->callback == callback)
590       next->valid = FALSE;
591     if (queue->task == next->next)
592       break;
593     next = next->next;
594   }
595 }
596
597 /* Sets the I/O mask for the task. Only one I/O type can be set at a
598    time. */
599
600 void silc_task_set_iotype(SilcTask task, int type)
601 {
602   task->iomask |= (1L << type);
603 }
604
605 /* Resets the I/O mask to the type sent as argument. */
606
607 void silc_task_reset_iotype(SilcTask task, int type)
608 {
609   task->iomask = (1L << type);
610 }
611
612 /* Compare two time values. If the first argument is smaller than the
613    second this function returns TRUE. */
614
615 int silc_task_timeout_compare(struct timeval *smaller, 
616                               struct timeval *bigger)
617 {
618   if ((smaller->tv_sec < bigger->tv_sec) ||
619       ((smaller->tv_sec == bigger->tv_sec) &&
620        (smaller->tv_usec < bigger->tv_usec)))
621     return TRUE;
622
623   return FALSE;
624 }