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