9aec2507e5ab1ce9f6158434ed378a4538481dc9
[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 /* Adds a timeout task into the task queue. This function is used by
155    silc_task_register function. Returns a pointer to the registered 
156    task. Timeout tasks are sorted by their timeout value in ascending
157    order. The priority matters if there are more than one task with
158    same timeout. */
159
160 SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask new,
161                                SilcTaskPriority priority)
162 {
163   SilcTask task, prev, next;
164
165   /* Take the first task in the queue */
166   task = queue->task;
167
168   /* Take last task from the list */
169   prev = task->prev;
170     
171   switch(priority) {
172   case SILC_TASK_PRI_LOW:
173     /* Lowest priority. The task is added at the end of the list. */
174     while(prev != task) {
175
176       /* If we have longer timeout than with the task head of us
177          we have found our spot. */
178       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
179         break;
180
181       /* If we are equal size of timeout we will be after it. */
182       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
183         break;
184
185       /* We have shorter timeout, compare to next one. */
186       prev = prev->prev;
187     }
188     /* Found a spot from the list, add the task to the list. */
189     next = prev->next;
190     new->prev = prev;
191     new->next = next;
192     prev->next = new;
193     next->prev = new;
194     
195     if (prev == task) {
196       /* Check if we are going to be the first task in the queue */
197       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
198         break;
199       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
200         break;
201
202       /* We are now the first task in queue */
203       queue->task = new;
204     }
205     break;
206   case SILC_TASK_PRI_NORMAL:
207     /* Normal priority. The task is added before lower priority tasks
208        but after tasks with higher priority. */
209     while(prev != task) {
210
211       /* If we have longer timeout than with the task head of us
212          we have found our spot. */
213       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
214         break;
215
216       /* If we are equal size of timeout, priority kicks in place. */
217       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
218         if (prev->priority >= SILC_TASK_PRI_NORMAL)
219           break;
220
221       /* We have shorter timeout or higher priority, compare to next one. */
222       prev = prev->prev;
223     }
224     /* Found a spot from the list, add the task to the list. */
225     next = prev->next;
226     new->prev = prev;
227     new->next = next;
228     prev->next = new;
229     next->prev = new;
230     
231     if (prev == task) {
232       /* Check if we are going to be the first task in the queue */
233       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
234         break;
235       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
236         if (prev->priority >= SILC_TASK_PRI_NORMAL)
237           break;
238
239       /* We are now the first task in queue */
240       queue->task = new;
241     }
242     break;
243   case SILC_TASK_PRI_HIGH:
244     /* High priority. The task is added before lower priority tasks
245        but after tasks with higher priority. */
246     while(prev != task) {
247
248       /* If we have longer timeout than with the task head of us
249          we have found our spot. */
250       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
251         break;
252
253       /* If we are equal size of timeout, priority kicks in place. */
254       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
255         if (prev->priority >= SILC_TASK_PRI_HIGH)
256           break;
257
258       /* We have shorter timeout or higher priority, compare to next one. */
259       prev = prev->prev;
260     }
261     /* Found a spot from the list, add the task to the list. */
262     next = prev->next;
263     new->prev = prev;
264     new->next = next;
265     prev->next = new;
266     next->prev = new;
267     
268     if (prev == task) {
269       /* Check if we are going to be the first task in the queue */
270       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
271         break;
272       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
273         if (prev->priority >= SILC_TASK_PRI_HIGH)
274           break;
275
276       /* We are now the first task in queue */
277       queue->task = new;
278     }
279     break;
280   case SILC_TASK_PRI_REALTIME:
281     /* Highest priority. The task is added at the head of the list. 
282        The last registered task is added to the very head of the list
283        thus we get the LIFO (Last-In-First-Out) order. */
284     next = task->next;
285     while(next != task) {
286
287       /* If we have shorter timeout than the next task we've found
288          our spot. */
289       if (silc_task_timeout_compare(&new->timeout, &next->timeout))
290         break;
291
292       /* If we are equal size of timeout we will be first. */
293       if (!silc_task_timeout_compare(&next->timeout, &new->timeout))
294         break;
295
296       /* We have longer timeout, compare to next one. */
297       next = next->next;
298     }
299     /* Found a spot from the list, add the task to the list. */
300     prev = next->prev;
301     new->next = next;
302     new->prev = prev;
303     prev->next = new;
304     next->prev = new;
305     
306     if (next == task) {
307       /* Check if we are going to be the first task in the queue */
308       if (silc_task_timeout_compare(&next->timeout, &new->timeout))
309         break;
310
311       /* We are now the first task in queue */
312       queue->task = new;
313     }
314   default:
315     silc_free(new);
316     return NULL;
317   }
318
319   return new;
320 }
321
322 /* Registers a new task into the task queue. The task becomes valid
323    automatically when it is registered. Returns a pointer to the 
324    registered task. */
325
326 SilcTask silc_task_register(SilcTaskQueue queue, int fd, 
327                             SilcTaskCallback cb, void *context, 
328                             long seconds, long useconds, 
329                             SilcTaskType type, SilcTaskPriority priority)
330 {
331   SilcTask new;
332   int timeout = FALSE;
333
334   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", 
335                   fd, type, priority));
336
337   /* If the task is generic task, we check whether this task has already
338      been registered. Generic tasks are registered only once and after that
339      the same task applies to all file descriptors to be registered. */
340   if ((type == SILC_TASK_GENERIC) && queue->task) {
341     SilcTask task;
342
343     task = queue->task;
344     while(1) {
345       if ((task->callback == cb) && (task->context == context)) {
346         SILC_LOG_DEBUG(("Found matching generic task, using the match"));
347
348         /* Add the fd to be listened, the task found now applies to this
349            fd as well. */
350         silc_schedule_set_listen_fd(fd, (1L << SILC_TASK_READ));
351         return task;
352       }
353
354       if (queue->task == task->next)
355         break;
356       
357       task = task->next;
358     }
359   }
360
361   new = silc_calloc(1, sizeof(*new));
362   new->fd = fd;
363   new->context = context;
364   new->callback = cb;
365   new->valid = TRUE;
366   new->priority = priority;
367   new->iomask = (1L << SILC_TASK_READ);
368   new->next = new;
369   new->prev = new;
370
371   /* If the task is non-timeout task we have to tell the scheduler that we
372      would like to have these tasks scheduled at some odd distant future. */
373   if (type != SILC_TASK_TIMEOUT)
374     silc_schedule_set_listen_fd(fd, (1L << SILC_TASK_READ));
375
376   /* Create timeout if marked to be timeout task */
377   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
378     gettimeofday(&new->timeout, NULL);
379     new->timeout.tv_sec += seconds + (useconds / 1000000L);
380     new->timeout.tv_usec += (useconds % 1000000L);
381     if (new->timeout.tv_usec > 999999L) {
382       new->timeout.tv_sec += 1;
383       new->timeout.tv_usec -= 1000000L;
384     }
385     timeout = TRUE;
386   }
387
388   /* Is this first task of the queue? */
389   if (queue->task == NULL) {
390     queue->task = new;
391     return new;
392   }
393
394   if (timeout)
395     return silc_task_add_timeout(queue, new, priority);
396   else
397     return silc_task_add(queue, new, priority);
398 }
399
400 /* Removes (unregisters) a task from particular task queue. This function
401    is used internally by scheduler. One should not call this function
402    to unregister tasks, instead silc_task_unregister_task function
403    should be used. */
404
405 int silc_task_remove(SilcTaskQueue queue, SilcTask task)
406 {
407   SilcTask first, old, next;
408
409   if (!queue || !queue->task)
410     return FALSE;
411
412   first = queue->task;
413
414   /* Unregister all tasks in queue */
415   if (task == SILC_ALL_TASKS) {
416     SILC_LOG_DEBUG(("Removing all tasks at once"));
417     next = first;
418
419     while(1) {
420       next = next->next;
421       silc_free(next->prev);
422       if (next == first)
423         break;
424     }
425
426     queue->task = NULL;
427     return TRUE;
428   }
429
430   SILC_LOG_DEBUG(("Removing task"));
431
432   /* Unregister the task */
433   old = first;
434   while(1) {
435     if (old == task) {
436       SilcTask prev, next;
437
438       prev = old->prev;
439       next = old->next;
440       prev->next = next;
441       next->prev = prev;
442
443       if (prev == old && next == old)
444         queue->task = NULL;
445       if (queue->task == old)
446         queue->task = next;
447
448       silc_free(old);
449       return TRUE;
450     }
451     old = old->next;
452
453     if (old == first)
454       return FALSE;
455   }
456 }
457
458 /* Unregisters a task from the task queue. This is the unregister_task
459    function pointer in task queue object. One should use this function
460    to unregister tasks. This function invalidates the task. */
461
462 void silc_task_unregister(SilcTaskQueue queue, SilcTask task)
463 {
464
465   /* Unregister all tasks */
466   if (task == SILC_ALL_TASKS) {
467     SilcTask next;
468     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
469
470     if (queue->task == NULL)
471       return;
472
473     next = queue->task;
474     
475     while(1) {
476       if (next->valid)
477         next->valid = FALSE;
478       if (queue->task == next->next)
479         break;
480       next = next->next;
481     }
482     return;
483   }
484
485   SILC_LOG_DEBUG(("Unregistering task"));
486
487   /* Unregister the specific task */
488   if (task->valid)
489     task->valid = FALSE;
490 }
491
492 /* Unregister a task by file descriptor. This invalidates the task. */
493
494 void silc_task_unregister_by_fd(SilcTaskQueue queue, int fd)
495 {
496   SilcTask next;
497
498   SILC_LOG_DEBUG(("Unregister task by fd"));
499
500   if (queue->task == NULL)
501     return;
502
503   next = queue->task;
504
505   while(1) {
506     if (next->fd == fd)
507       next->valid = FALSE;
508     if (queue->task == next->next)
509       break;
510     next = next->next;
511   }
512 }
513
514 /* Sets the I/O mask for the task. Only one I/O type can be set at a
515    time. */
516
517 void silc_task_set_iotype(SilcTask task, int type)
518 {
519   task->iomask |= (1L << type);
520 }
521
522 /* Resets the I/O mask to the type sent as argument. */
523
524 void silc_task_reset_iotype(SilcTask task, int type)
525 {
526   task->iomask = (1L << type);
527 }
528
529 /* Compare two time values. If the first argument is smaller than the
530    second this function returns TRUE. */
531
532 int silc_task_timeout_compare(struct timeval *smaller, 
533                               struct timeval *bigger)
534 {
535   if ((smaller->tv_sec < bigger->tv_sec) ||
536       ((smaller->tv_sec == bigger->tv_sec) &&
537        (smaller->tv_usec < bigger->tv_usec)))
538     return TRUE;
539
540   return FALSE;
541 }