+
+ silc_mutex_unlock(schedule->lock);
+}
+
+/* Wakes up the scheduler. This is used only in multi-threaded
+ environments where threads may add new tasks or remove old tasks
+ from task queues. This is called to wake up the scheduler in the
+ main thread so that it detects the changes in the task queues.
+ If threads support is not compiled in this function has no effect.
+ Implementation of this function is platform specific. */
+
+void silc_schedule_wakeup(SilcSchedule schedule)
+{
+#ifdef SILC_THREADS
+ SILC_LOG_DEBUG(("Wakeup scheduler"));
+ silc_mutex_lock(schedule->lock);
+ silc_schedule_wakeup_internal(schedule->wakeup);
+ silc_mutex_unlock(schedule->lock);
+#endif
+}
+
+/* Add new task to the scheduler */
+
+SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
+ SilcTaskCallback callback, void *context,
+ long seconds, long useconds,
+ SilcTaskType type,
+ SilcTaskPriority priority)
+{
+ SilcTask newtask;
+ SilcTaskQueue queue;
+ int timeout = FALSE;
+
+ SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd,
+ type, priority));
+
+ queue = SILC_SCHEDULE_GET_QUEUE(type);
+
+ /* If the task is generic task, we check whether this task has already
+ been registered. Generic tasks are registered only once and after that
+ the same task applies to all file descriptors to be registered. */
+ if (type == SILC_TASK_GENERIC) {
+ silc_mutex_lock(queue->lock);
+
+ if (queue->task) {
+ SilcTask task = queue->task;
+ while(1) {
+ if ((task->callback == callback) && (task->context == context)) {
+ SILC_LOG_DEBUG(("Found matching generic task, using the match"));
+
+ silc_mutex_unlock(queue->lock);
+
+ /* Add the fd to be listened, the task found now applies to this
+ fd as well. */
+ silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
+ return task;
+ }
+
+ if (queue->task == task->next)
+ break;
+
+ task = task->next;
+ }
+ }
+
+ silc_mutex_unlock(queue->lock);
+ }
+
+ newtask = silc_calloc(1, sizeof(*newtask));
+ newtask->fd = fd;
+ newtask->context = context;
+ newtask->callback = callback;
+ newtask->valid = TRUE;
+ newtask->priority = priority;
+ newtask->type = type;
+ newtask->next = newtask;
+ newtask->prev = newtask;
+
+ /* Create timeout if marked to be timeout task */
+ if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
+ silc_gettimeofday(&newtask->timeout);
+ newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
+ newtask->timeout.tv_usec += (useconds % 1000000L);
+ if (newtask->timeout.tv_usec > 999999L) {
+ newtask->timeout.tv_sec += 1;
+ newtask->timeout.tv_usec -= 1000000L;
+ }
+ timeout = TRUE;
+ }
+
+ /* If the task is non-timeout task we have to tell the scheduler that we
+ would like to have these tasks scheduled at some odd distant future. */
+ if (type != SILC_TASK_TIMEOUT)
+ silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
+
+ silc_mutex_lock(queue->lock);
+
+ /* Is this first task of the queue? */
+ if (queue->task == NULL) {
+ queue->task = newtask;
+ silc_mutex_unlock(queue->lock);
+ return newtask;
+ }
+
+ if (timeout)
+ newtask = silc_task_add_timeout(queue, newtask, priority);
+ else
+ newtask = silc_task_add(queue, newtask, priority);
+
+ silc_mutex_unlock(queue->lock);
+
+ return newtask;
+}
+
+/* Removes a task from the scheduler */
+
+void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
+{
+ SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
+
+ /* Unregister all tasks */
+ if (task == SILC_ALL_TASKS) {
+ SilcTask next;
+ SILC_LOG_DEBUG(("Unregistering all tasks at once"));
+
+ silc_mutex_lock(queue->lock);
+
+ if (!queue->task) {
+ silc_mutex_unlock(queue->lock);
+ return;
+ }
+
+ next = queue->task;
+
+ while(1) {
+ if (next->valid)
+ next->valid = FALSE;
+ if (queue->task == next->next)
+ break;
+ next = next->next;
+ }
+
+ silc_mutex_unlock(queue->lock);
+ return;
+ }
+
+ SILC_LOG_DEBUG(("Unregistering task"));
+
+ silc_mutex_lock(queue->lock);
+
+ /* Unregister the specific task */
+ if (task->valid)
+ task->valid = FALSE;
+
+ silc_mutex_unlock(queue->lock);
+}
+
+/* Remove task by fd */
+
+void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
+{
+ SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
+
+ silc_task_del_by_fd(schedule->timeout_queue, fd);
+ silc_task_del_by_fd(schedule->fd_queue, fd);
+}
+
+/* Remove task by task callback. */
+
+void silc_schedule_task_del_by_callback(SilcSchedule schedule,
+ SilcTaskCallback callback)
+{
+ SILC_LOG_DEBUG(("Unregister task by callback"));
+
+ silc_task_del_by_callback(schedule->timeout_queue, callback);
+ silc_task_del_by_callback(schedule->fd_queue, callback);
+ silc_task_del_by_callback(schedule->generic_queue, callback);
+}
+
+/* Remove task by context. */
+
+void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
+{
+ SILC_LOG_DEBUG(("Unregister task by context"));
+
+ silc_task_del_by_context(schedule->timeout_queue, context);
+ silc_task_del_by_context(schedule->fd_queue, context);
+ silc_task_del_by_context(schedule->generic_queue, context);
+}
+
+/* Sets a file descriptor to be listened by select() in scheduler. One can
+ call this directly if wanted. This can be called multiple times for
+ one file descriptor to set different iomasks. */
+
+void silc_schedule_set_listen_fd(SilcSchedule schedule,
+ SilcUInt32 fd, SilcTaskEvent iomask)
+{
+ int i;
+ bool found = FALSE;
+
+ silc_mutex_lock(schedule->lock);
+
+ for (i = 0; i < schedule->max_fd; i++)
+ if (schedule->fd_list[i].fd == fd) {
+ schedule->fd_list[i].fd = fd;
+ schedule->fd_list[i].events = iomask;
+ if (i > schedule->last_fd)
+ schedule->last_fd = i;
+ found = TRUE;
+ break;
+ }
+
+ if (!found)
+ for (i = 0; i < schedule->max_fd; i++)
+ if (schedule->fd_list[i].events == 0) {
+ schedule->fd_list[i].fd = fd;
+ schedule->fd_list[i].events = iomask;
+ if (i > schedule->last_fd)
+ schedule->last_fd = i;
+ break;
+ }
+
+ silc_mutex_unlock(schedule->lock);
+}
+
+/* Removes a file descriptor from listen list. */
+
+void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
+{
+ int i;
+
+ silc_mutex_lock(schedule->lock);
+
+ SILC_LOG_DEBUG(("Unset listen fd %d", fd));
+
+ for (i = 0; i < schedule->max_fd; i++)
+ if (schedule->fd_list[i].fd == fd) {
+ schedule->fd_list[i].fd = 0;
+ schedule->fd_list[i].events = 0;
+ if (schedule->last_fd == i)
+ schedule->last_fd = schedule->max_fd - 1;
+ break;
+ }
+
+ silc_mutex_unlock(schedule->lock);
+}
+
+/* Allocates a newtask task queue into the scheduler */
+
+static void silc_task_queue_alloc(SilcTaskQueue *queue)
+{
+ *queue = silc_calloc(1, sizeof(**queue));
+ silc_mutex_alloc(&(*queue)->lock);
+}
+
+/* Free's a task queue. */
+
+static void silc_task_queue_free(SilcTaskQueue queue)
+{
+ silc_mutex_free(queue->lock);
+ silc_free(queue);
+}
+
+/* Return task by its fd. */
+
+static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
+{
+ SilcTask next;
+
+ if (!queue->task)
+ return NULL;
+
+ next = queue->task;
+
+ while (1) {
+ if (next->fd == fd)
+ return next;
+ if (queue->task == next->next)
+ return NULL;
+ next = next->next;
+ }
+
+ return NULL;
+}
+
+/* Adds a non-timeout task into the task queue. This function is used
+ by silc_task_register function. Returns a pointer to the registered
+ task. */
+
+static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask,
+ SilcTaskPriority priority)
+{
+ SilcTask task, next, prev;
+
+ /* Take the first task in the queue */
+ task = queue->task;
+
+ switch(priority) {
+ case SILC_TASK_PRI_LOW:
+ /* Lowest priority. The task is added at the end of the list. */
+ prev = task->prev;
+ newtask->prev = prev;
+ newtask->next = task;
+ prev->next = newtask;
+ task->prev = newtask;
+ break;
+ case SILC_TASK_PRI_NORMAL:
+ /* Normal priority. The task is added before lower priority tasks
+ but after tasks with higher priority. */
+ prev = task->prev;
+ while(prev != task) {
+ if (prev->priority > SILC_TASK_PRI_LOW)
+ break;
+ prev = prev->prev;
+ }
+ if (prev == task) {
+ /* There are only lower priorities in the list, we will
+ sit before them and become the first task in the queue. */
+ prev = task->prev;
+ newtask->prev = prev;
+ newtask->next = task;
+ task->prev = newtask;
+ prev->next = newtask;
+
+ /* We are now the first task in queue */
+ queue->task = newtask;
+ } else {
+ /* Found a spot from the list, add the task to the list. */
+ next = prev->next;
+ newtask->prev = prev;
+ newtask->next = next;
+ prev->next = newtask;
+ next->prev = newtask;
+ }
+ break;
+ default:
+ silc_free(newtask);
+ return NULL;
+ }
+
+ return newtask;
+}
+
+/* Return the timeout task with smallest timeout. */
+
+static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
+{
+ SilcTask prev, task;
+
+ prev = first->prev;
+
+ if (first == prev)
+ return first;
+
+ task = first;
+ while (1) {
+ if (first == prev)
+ break;
+
+ if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
+ task = prev;
+
+ prev = prev->prev;
+ }
+
+ return task;
+}
+
+/* Adds a timeout task into the task queue. This function is used by
+ silc_task_register function. Returns a pointer to the registered
+ task. Timeout tasks are sorted by their timeout value in ascending
+ order. The priority matters if there are more than one task with
+ same timeout. */
+
+static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
+ SilcTaskPriority priority)
+{
+ SilcTask task, prev, next;
+
+ /* Take the first task in the queue */
+ task = queue->task;
+
+ /* Take last task from the list */
+ prev = task->prev;
+
+ switch(priority) {
+ case SILC_TASK_PRI_LOW:
+ /* Lowest priority. The task is added at the end of the list. */
+ while(prev != task) {
+
+ /* If we have longer timeout than with the task head of us
+ we have found our spot. */
+ if (silc_schedule_task_timeout_compare(&prev->timeout,
+ &newtask->timeout))
+ break;
+
+ /* If we are equal size of timeout we will be after it. */
+ if (!silc_schedule_task_timeout_compare(&newtask->timeout,
+ &prev->timeout))
+ break;
+
+ /* We have shorter timeout, compare to next one. */
+ prev = prev->prev;
+ }
+ /* Found a spot from the list, add the task to the list. */
+ next = prev->next;
+ newtask->prev = prev;
+ newtask->next = next;
+ prev->next = newtask;
+ next->prev = newtask;
+
+ if (prev == task) {
+ /* Check if we are going to be the first task in the queue */
+ if (silc_schedule_task_timeout_compare(&prev->timeout,
+ &newtask->timeout))
+ break;
+ if (!silc_schedule_task_timeout_compare(&newtask->timeout,
+ &prev->timeout))
+ break;
+
+ /* We are now the first task in queue */
+ queue->task = newtask;
+ }
+ break;
+ case SILC_TASK_PRI_NORMAL:
+ /* Normal priority. The task is added before lower priority tasks
+ but after tasks with higher priority. */
+ while(prev != task) {
+
+ /* If we have longer timeout than with the task head of us
+ we have found our spot. */
+ if (silc_schedule_task_timeout_compare(&prev->timeout,
+ &newtask->timeout))
+ break;
+
+ /* If we are equal size of timeout, priority kicks in place. */
+ if (!silc_schedule_task_timeout_compare(&newtask->timeout,
+ &prev->timeout))
+ if (prev->priority >= SILC_TASK_PRI_NORMAL)
+ break;
+
+ /* We have shorter timeout or higher priority, compare to next one. */
+ prev = prev->prev;
+ }
+ /* Found a spot from the list, add the task to the list. */
+ next = prev->next;
+ newtask->prev = prev;
+ newtask->next = next;
+ prev->next = newtask;
+ next->prev = newtask;
+
+ if (prev == task) {
+ /* Check if we are going to be the first task in the queue */
+ if (silc_schedule_task_timeout_compare(&prev->timeout,
+ &newtask->timeout))
+ break;
+ if (!silc_schedule_task_timeout_compare(&newtask->timeout,
+ &prev->timeout))
+ if (prev->priority >= SILC_TASK_PRI_NORMAL)
+ break;
+
+ /* We are now the first task in queue */
+ queue->task = newtask;
+ }
+ break;
+ default:
+ silc_free(newtask);
+ return NULL;
+ }
+
+ return newtask;
+}
+
+/* Removes (unregisters) a task from particular task queue. This function
+ is used internally by scheduler. This must be called holding the
+ queue->lock. */
+
+static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
+{
+ SilcTask first, old, next;
+
+ if (!queue || !task)
+ return FALSE;
+
+ if (!queue->task) {
+ return FALSE;
+ }
+
+ first = queue->task;
+
+ /* Unregister all tasks in queue */
+ if (task == SILC_ALL_TASKS) {
+ SILC_LOG_DEBUG(("Removing all tasks at once"));
+ next = first;
+
+ while(1) {
+ next = next->next;
+ silc_free(next->prev);
+ if (next == first)
+ break;
+ }
+
+ queue->task = NULL;
+ return TRUE;
+ }
+
+ SILC_LOG_DEBUG(("Removing task"));
+
+ /* Unregister the task */
+ old = first;
+ while(1) {
+ if (old == task) {
+ SilcTask prev, next;
+
+ prev = old->prev;
+ next = old->next;
+ prev->next = next;
+ next->prev = prev;
+
+ if (prev == old && next == old)
+ queue->task = NULL;
+ if (queue->task == old)
+ queue->task = silc_task_get_first(queue, next);
+
+ silc_free(old);
+ return TRUE;
+ }
+ old = old->prev;
+
+ if (old == first) {
+ return FALSE;
+ }
+ }
+}
+
+/* Compare two time values. If the first argument is smaller than the
+ second this function returns TRUE. */
+
+static int silc_schedule_task_timeout_compare(struct timeval *smaller,
+ struct timeval *bigger)
+{
+ if ((smaller->tv_sec < bigger->tv_sec) ||
+ ((smaller->tv_sec == bigger->tv_sec) &&
+ (smaller->tv_usec < bigger->tv_usec)))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
+{
+ SilcTask next;
+
+ silc_mutex_lock(queue->lock);
+
+ if (!queue->task) {
+ silc_mutex_unlock(queue->lock);
+ return;
+ }
+
+ next = queue->task;
+
+ while(1) {
+ if (next->fd == fd)
+ next->valid = FALSE;
+ if (queue->task == next->next)
+ break;
+ next = next->next;
+ }
+
+ silc_mutex_unlock(queue->lock);
+}
+
+static void silc_task_del_by_callback(SilcTaskQueue queue,
+ SilcTaskCallback callback)
+{
+ SilcTask next;
+
+ silc_mutex_lock(queue->lock);
+
+ if (!queue->task) {
+ silc_mutex_unlock(queue->lock);
+ return;
+ }
+
+ next = queue->task;
+
+ while(1) {
+ if (next->callback == callback)
+ next->valid = FALSE;
+ if (queue->task == next->next)
+ break;
+ next = next->next;
+ }
+
+ silc_mutex_unlock(queue->lock);
+}
+
+static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
+{
+ SilcTask next;
+
+ silc_mutex_lock(queue->lock);
+
+ if (!queue->task) {
+ silc_mutex_unlock(queue->lock);
+ return;
+ }
+
+ next = queue->task;
+
+ while(1) {
+ if (next->context == context)
+ next->valid = FALSE;
+ if (queue->task == next->next)
+ break;
+ next = next->next;
+ }
+
+ silc_mutex_unlock(queue->lock);