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