updates.
[silc.git] / lib / silcutil / silctask.c
1 /*
2
3   silctask.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
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(SilcSchedule schedule, SilcTaskQueue *new, 
29                            bool valid)
30 {
31   SILC_LOG_DEBUG(("Allocating new task queue"));
32
33   *new = silc_calloc(1, sizeof(**new));
34
35   /* Set the pointers */
36   (*new)->schedule = schedule;
37   (*new)->valid = valid;
38 }
39
40 /* Free's a task queue. */
41
42 void silc_task_queue_free(SilcTaskQueue queue)
43 {
44   silc_free(queue);
45 }
46
47 /* Adds a non-timeout task into the task queue. This function is used
48    by silc_task_register function. Returns a pointer to the registered 
49    task. */
50
51 SilcTask silc_task_add(SilcTaskQueue queue, SilcTask new, 
52                        SilcTaskPriority priority)
53 {
54   SilcTask task, next, prev;
55
56   /* Take the first task in the queue */
57   task = queue->task;
58
59   switch(priority) {
60   case SILC_TASK_PRI_LOW:
61     /* Lowest priority. The task is added at the end of the list. */
62     prev = task->prev;
63     new->prev = prev;
64     new->next = task;
65     prev->next = new;
66     task->prev = new;
67     break;
68   case SILC_TASK_PRI_NORMAL:
69     /* Normal priority. The task is added before lower priority tasks
70        but after tasks with higher priority. */
71     prev = task->prev;
72     while(prev != task) {
73       if (prev->priority > SILC_TASK_PRI_LOW)
74         break;
75       prev = prev->prev;
76     }
77     if (prev == task) {
78       /* There are only lower priorities in the list, we will
79          sit before them and become the first task in the queue. */
80       prev = task->prev;
81       new->prev = prev;
82       new->next = task;
83       task->prev = new;
84       prev->next = new;
85
86       /* We are now the first task in queue */
87       queue->task = new;
88     } else {
89       /* Found a spot from the list, add the task to the list. */
90       next = prev->next;
91       new->prev = prev;
92       new->next = next;
93       prev->next = new;
94       next->prev = new;
95     }
96     break;
97   default:
98     silc_free(new);
99     return NULL;
100   }
101
102   return new;
103 }
104
105 /* Return the timeout task with smallest timeout. */
106
107 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
108 {
109   SilcTask prev, task;
110
111   prev = first->prev;
112
113   if (first == prev)
114     return first;
115
116   task = first;
117   while (1) {
118     if (first == prev)
119       break;
120
121     if (silc_task_timeout_compare(&prev->timeout, &task->timeout))
122       task = prev;
123
124     prev = prev->prev;
125   }
126
127   return task;
128 }
129
130 /* Adds a timeout task into the task queue. This function is used by
131    silc_task_register function. Returns a pointer to the registered 
132    task. Timeout tasks are sorted by their timeout value in ascending
133    order. The priority matters if there are more than one task with
134    same timeout. */
135
136 SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask new,
137                                SilcTaskPriority priority)
138 {
139   SilcTask task, prev, next;
140
141   /* Take the first task in the queue */
142   task = queue->task;
143
144   /* Take last task from the list */
145   prev = task->prev;
146     
147   switch(priority) {
148   case SILC_TASK_PRI_LOW:
149     /* Lowest priority. The task is added at the end of the list. */
150     while(prev != task) {
151
152       /* If we have longer timeout than with the task head of us
153          we have found our spot. */
154       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
155         break;
156
157       /* If we are equal size of timeout we will be after it. */
158       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
159         break;
160
161       /* We have shorter timeout, compare to next one. */
162       prev = prev->prev;
163     }
164     /* Found a spot from the list, add the task to the list. */
165     next = prev->next;
166     new->prev = prev;
167     new->next = next;
168     prev->next = new;
169     next->prev = new;
170     
171     if (prev == task) {
172       /* Check if we are going to be the first task in the queue */
173       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
174         break;
175       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
176         break;
177
178       /* We are now the first task in queue */
179       queue->task = new;
180     }
181     break;
182   case SILC_TASK_PRI_NORMAL:
183     /* Normal priority. The task is added before lower priority tasks
184        but after tasks with higher priority. */
185     while(prev != task) {
186
187       /* If we have longer timeout than with the task head of us
188          we have found our spot. */
189       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
190         break;
191
192       /* If we are equal size of timeout, priority kicks in place. */
193       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
194         if (prev->priority >= SILC_TASK_PRI_NORMAL)
195           break;
196
197       /* We have shorter timeout or higher priority, 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         if (prev->priority >= SILC_TASK_PRI_NORMAL)
213           break;
214
215       /* We are now the first task in queue */
216       queue->task = new;
217     }
218     break;
219   default:
220     silc_free(new);
221     return NULL;
222   }
223
224   return new;
225 }
226
227 /* Registers a new task to the task queue. Arguments are as follows:
228       
229    SilcTaskQueue queue        Queue where the task is to be registered
230    int fd                     File descriptor
231    SilcTaskCallback cb        Callback function to call
232    void *context              Context to be passed to callback function
233    long seconds               Seconds to timeout
234    long useconds              Microseconds to timeout
235    SilcTaskType type          Type of the task
236    SilcTaskPriority priority  Priority of the task
237    
238    The same function is used to register all types of tasks. The type
239    argument tells what type of the task is. Note that when registering
240    non-timeout tasks one should also pass 0 as timeout as timeout will
241    be ignored anyway. Also, note, that one cannot register timeout task
242    with 0 timeout. There cannot be zero timeouts, passing zero means
243    no timeout is used for the task and SILC_TASK_FD_TASK is used as
244    default task type in this case.
245    
246    One should be careful not to register timeout tasks to the non-timeout
247    task queue, because they will never expire. As one should not register
248    non-timeout tasks to timeout task queue because they will never get
249    scheduled.
250    
251    There is a one distinct difference between timeout and non-timeout
252    tasks when they are executed. Non-timeout tasks remain on the task
253    queue after execution. Timeout tasks, however, are removed from the
254    task queue after they have expired. It is safe to re-register a task 
255    in its own callback function. It is also safe to unregister a task 
256    in a callback function.
257    
258    Generic tasks apply to all file descriptors, however, one still must
259    pass the correct file descriptor to the function when registering
260    generic tasks. */
261
262 SilcTask silc_task_register(SilcTaskQueue queue, int fd, 
263                             SilcTaskCallback cb, void *context, 
264                             long seconds, long useconds, 
265                             SilcTaskType type, SilcTaskPriority priority)
266 {
267   SilcTask new;
268   int timeout = FALSE;
269
270   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", 
271                   fd, type, priority));
272
273   /* If the task is generic task, we check whether this task has already
274      been registered. Generic tasks are registered only once and after that
275      the same task applies to all file descriptors to be registered. */
276   if ((type == SILC_TASK_GENERIC) && queue->task) {
277     SilcTask task;
278
279     task = queue->task;
280     while(1) {
281       if ((task->callback == cb) && (task->context == context)) {
282         SILC_LOG_DEBUG(("Found matching generic task, using the match"));
283
284         /* Add the fd to be listened, the task found now applies to this
285            fd as well. */
286         silc_schedule_set_listen_fd(queue->schedule, 
287                                     fd, (1L << SILC_TASK_READ));
288         return task;
289       }
290
291       if (queue->task == task->next)
292         break;
293       
294       task = task->next;
295     }
296   }
297
298   new = silc_calloc(1, sizeof(*new));
299   new->fd = fd;
300   new->context = context;
301   new->callback = cb;
302   new->valid = TRUE;
303   new->priority = priority;
304   new->iomask = (1L << SILC_TASK_READ);
305   new->next = new;
306   new->prev = new;
307
308   /* If the task is non-timeout task we have to tell the scheduler that we
309      would like to have these tasks scheduled at some odd distant future. */
310   if (type != SILC_TASK_TIMEOUT)
311     silc_schedule_set_listen_fd(queue->schedule, fd, (1L << SILC_TASK_READ));
312
313   /* Create timeout if marked to be timeout task */
314   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
315     gettimeofday(&new->timeout, NULL);
316     new->timeout.tv_sec += seconds + (useconds / 1000000L);
317     new->timeout.tv_usec += (useconds % 1000000L);
318     if (new->timeout.tv_usec > 999999L) {
319       new->timeout.tv_sec += 1;
320       new->timeout.tv_usec -= 1000000L;
321     }
322     timeout = TRUE;
323   }
324
325   /* Is this first task of the queue? */
326   if (queue->task == NULL) {
327     queue->task = new;
328     return new;
329   }
330
331   if (timeout)
332     return silc_task_add_timeout(queue, new, priority);
333   else
334     return silc_task_add(queue, new, priority);
335 }
336
337 /* Removes (unregisters) a task from particular task queue. This function
338    is used internally by scheduler. One should not call this function
339    to unregister tasks, instead silc_task_unregister_task function
340    should be used. */
341
342 int silc_task_remove(SilcTaskQueue queue, SilcTask task)
343 {
344   SilcTask first, old, next;
345
346   if (!queue || !queue->task)
347     return FALSE;
348
349   first = queue->task;
350
351   /* Unregister all tasks in queue */
352   if (task == SILC_ALL_TASKS) {
353     SILC_LOG_DEBUG(("Removing all tasks at once"));
354     next = first;
355
356     while(1) {
357       next = next->next;
358       silc_free(next->prev);
359       if (next == first)
360         break;
361     }
362
363     queue->task = NULL;
364     return TRUE;
365   }
366
367   SILC_LOG_DEBUG(("Removing task"));
368
369   /* Unregister the task */
370   old = first;
371   while(1) {
372     if (old == task) {
373       SilcTask prev, next;
374
375       prev = old->prev;
376       next = old->next;
377       prev->next = next;
378       next->prev = prev;
379
380       if (prev == old && next == old)
381         queue->task = NULL;
382       if (queue->task == old)
383         queue->task = silc_task_get_first(queue, next);
384       
385       silc_free(old);
386       return TRUE;
387     }
388     old = old->prev;
389
390     if (old == first)
391       return FALSE;
392   }
393 }
394
395 /* Unregisters a task already in the queue. Arguments are as follows:
396    
397    SilcTaskQueue queue      Queue where from the task is unregistered
398    SilcTask task            Task to be unregistered
399    
400    The same function is used to unregister timeout and non-timeout 
401    tasks. One can also unregister all tasks from the queue by passing
402    SILC_ALL_TASKS as task to the function. It is safe to unregister
403    a task in a callback function. */
404
405 void silc_task_unregister(SilcTaskQueue queue, SilcTask task)
406 {
407
408   /* Unregister all tasks */
409   if (task == SILC_ALL_TASKS) {
410     SilcTask next;
411     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
412
413     if (queue->task == NULL)
414       return;
415
416     next = queue->task;
417     
418     while(1) {
419       if (next->valid)
420         next->valid = FALSE;
421       if (queue->task == next->next)
422         break;
423       next = next->next;
424     }
425     return;
426   }
427
428   SILC_LOG_DEBUG(("Unregistering task"));
429
430   /* Unregister the specific task */
431   if (task->valid)
432     task->valid = FALSE;
433 }
434
435 /* Unregister a task by file descriptor. This invalidates the task. */
436
437 void silc_task_unregister_by_fd(SilcTaskQueue queue, int fd)
438 {
439   SilcTask next;
440
441   SILC_LOG_DEBUG(("Unregister task by fd"));
442
443   if (queue->task == NULL)
444     return;
445
446   next = queue->task;
447
448   while(1) {
449     if (next->fd == fd)
450       next->valid = FALSE;
451     if (queue->task == next->next)
452       break;
453     next = next->next;
454   }
455 }
456
457 /* Unregister a task by callback function. This invalidates the task. */
458
459 void silc_task_unregister_by_callback(SilcTaskQueue queue, 
460                                       SilcTaskCallback callback)
461 {
462   SilcTask next;
463
464   SILC_LOG_DEBUG(("Unregister task by callback"));
465
466   if (queue->task == NULL)
467     return;
468
469   next = queue->task;
470
471   while(1) {
472     if (next->callback == callback)
473       next->valid = FALSE;
474     if (queue->task == next->next)
475       break;
476     next = next->next;
477   }
478 }
479
480 /* Unregister a task by context. This invalidates the task. */
481
482 void silc_task_unregister_by_context(SilcTaskQueue queue, void *context)
483 {
484   SilcTask next;
485
486   SILC_LOG_DEBUG(("Unregister task by context"));
487
488   if (queue->task == NULL)
489     return;
490
491   next = queue->task;
492
493   while(1) {
494     if (next->context == context)
495       next->valid = FALSE;
496     if (queue->task == next->next)
497       break;
498     next = next->next;
499   }
500 }
501
502 /* Sets the I/O type of the task. The scheduler checks for this value
503    and a task must always have at least one of the I/O types set at 
504    all time. When registering new task the type is set by default to
505    SILC_TASK_READ. If the task doesn't perform reading one must reset
506    the value to SILC_TASK_WRITE.
507    
508    The type sent as argumenet is masked into the task. If the tasks 
509    I/O mask already includes this type this function has no effect. 
510    Only one I/O type can be added at once. If the task must perform
511    both reading and writing one must call this function for value
512    SILC_TASK_WRITE as well. */
513
514 void silc_task_set_iotype(SilcTask task, int type)
515 {
516   task->iomask |= (1L << type);
517 }
518
519 /* Resets the mask to the type sent as argument. Note that this resets
520    the previous values to zero and then adds the type sent as argument.
521    This function can be used to remove one of the types masked earlier
522    to the task. */
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 }