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