Added new SILC_STATUS_ERR_TIMEDOUT status type.
[crypto.git] / lib / silcutil / unix / silcunixschedule.c
index 951e1aeab53fa223355b8974576396733a5fe2f3..846cfa4bf68e2aac91751e0e83253dbeeca5e556 100644 (file)
 /* $Id$ */
 
 #include "silcincludes.h"
+#include "silcschedule_i.h"
 
 /* Calls normal select() system call. */
 
-int silc_select(int n, fd_set *readfds, fd_set *writefds,
-               fd_set *exceptfds, struct timeval *timeout)
+int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count, 
+               struct timeval *timeout)
 {
-  return select(n, readfds, writefds, exceptfds, timeout);
+  fd_set in, out;
+  int ret, i, max_fd = 0;
+
+  FD_ZERO(&in);
+  FD_ZERO(&out);
+
+  for (i = 0; i < fds_count; i++) {
+    if (!fds[i].events)
+      continue;
+
+    if (fds[i].fd > max_fd)
+      max_fd = fds[i].fd;
+
+    if (fds[i].events & SILC_TASK_READ)
+      FD_SET(fds[i].fd, &in);
+    if (fds[i].events & SILC_TASK_WRITE)
+      FD_SET(fds[i].fd, &out);
+
+    fds[i].revents = 0;
+  }
+
+  ret = select(max_fd + 1, &in, &out, NULL, timeout);
+  if (ret <= 0)
+    return ret;
+
+  for (i = 0; i < fds_count; i++) {
+    if (!fds[i].events)
+      continue;
+
+    if (FD_ISSET(fds[i].fd, &in))
+      fds[i].revents |= SILC_TASK_READ;
+    if (FD_ISSET(fds[i].fd, &out))
+      fds[i].revents |= SILC_TASK_WRITE;
+  }
+
+  return ret;
+}
+
+#define SIGNAL_COUNT 32
+
+typedef struct {
+  SilcUInt32 signal;
+  SilcTaskCallback callback;
+  void *context;
+  bool call;
+} SilcUnixSignal;
+
+/* Internal context. */
+typedef struct {
+  void *app_context;
+  int wakeup_pipe[2];
+  SilcTask wakeup_task;
+  sigset_t signals;
+  sigset_t signals_blocked;
+  SilcUnixSignal signal_call[SIGNAL_COUNT];
+} *SilcUnixScheduler;
+
+#ifdef SILC_THREADS
+
+SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+  unsigned char c;
+
+  read(internal->wakeup_pipe[0], &c, 1);
+}
+
+#endif /* SILC_THREADS */
+
+/* Initializes the platform specific scheduler.  This for example initializes
+   the wakeup mechanism of the scheduler.  In multi-threaded environment
+   the scheduler needs to be wakenup when tasks are added or removed from
+   the task queues.  Returns context to the platform specific scheduler. */
+
+void *silc_schedule_internal_init(SilcSchedule schedule,
+                                 void *app_context)
+{
+  SilcUnixScheduler internal;
+
+  internal = silc_calloc(1, sizeof(*internal));
+  if (!internal)
+    return NULL;
+
+  sigemptyset(&internal->signals);
+
+#ifdef SILC_THREADS
+  if (pipe(internal->wakeup_pipe)) {
+    SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
+    silc_free(internal);
+    return NULL;
+  }
+
+  internal->wakeup_task = 
+    silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
+                          silc_schedule_wakeup_cb, internal,
+                          0, 0, SILC_TASK_FD, 
+                          SILC_TASK_PRI_NORMAL);
+  if (!internal->wakeup_task) {
+    SILC_LOG_ERROR(("Could not add a wakeup task, threads won't work"));
+    close(internal->wakeup_pipe[0]);
+    close(internal->wakeup_pipe[1]);
+    silc_free(internal);
+    return NULL;
+  }
+#endif
+
+  internal->app_context = app_context;
+
+  return (void *)internal;
+}
+
+/* Uninitializes the platform specific scheduler context. */
+
+void silc_schedule_internal_uninit(void *context)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+
+  if (!internal)
+    return;
+
+#ifdef SILC_THREADS
+  close(internal->wakeup_pipe[0]);
+  close(internal->wakeup_pipe[1]);
+#endif
+
+  silc_free(internal);
+}
+
+/* Wakes up the scheduler */
+
+void silc_schedule_internal_wakeup(void *context)
+{
+#ifdef SILC_THREADS
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+
+  if (!internal)
+    return;
+
+  write(internal->wakeup_pipe[1], "!", 1);
+#endif
+}
+
+void silc_schedule_internal_signal_register(void *context,
+                                           SilcUInt32 signal,
+                                            SilcTaskCallback callback,
+                                            void *callback_context)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+  int i;
+
+  if (!internal)
+    return;
+
+  sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
+
+  for (i = 0; i < SIGNAL_COUNT; i++) {
+    if (!internal->signal_call[i].signal) {
+      internal->signal_call[i].signal = signal;
+      internal->signal_call[i].callback = callback;
+      internal->signal_call[i].context = callback_context;
+      internal->signal_call[i].call = FALSE;
+      break;
+    }
+  }
+
+  sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
+  sigaddset(&internal->signals, signal);
+}
+
+void silc_schedule_internal_signal_unregister(void *context,
+                                             SilcUInt32 signal,
+                                              SilcTaskCallback callback,
+                                              void *callback_context)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+  int i;
+
+  if (!internal)
+    return;
+
+  sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
+
+  for (i = 0; i < SIGNAL_COUNT; i++) {
+    if (internal->signal_call[i].signal == signal &&
+       internal->signal_call[i].callback == callback &&
+       internal->signal_call[i].context == callback_context) {
+      internal->signal_call[i].signal = 0;
+      internal->signal_call[i].callback = NULL;
+      internal->signal_call[i].context = NULL;
+      internal->signal_call[i].call = FALSE;
+    }
+  }
+
+  sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
+  sigdelset(&internal->signals, signal);
+}
+
+/* Mark signal to be called later. */
+
+void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+  int i;
+
+  if (!internal)
+    return;
+
+  sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
+
+  for (i = 0; i < SIGNAL_COUNT; i++) {
+    if (internal->signal_call[i].signal == signal)
+      internal->signal_call[i].call = TRUE;
+  }
+
+  sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
+}
+                                        
+/* Call all signals */
+
+void silc_schedule_internal_signals_call(void *context,
+                                         SilcSchedule schedule)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+  int i;
+
+  if (!internal)
+    return;
+
+  sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
+
+  for (i = 0; i < SIGNAL_COUNT; i++) {
+    if (internal->signal_call[i].call &&
+        internal->signal_call[i].callback) {
+      internal->signal_call[i].callback(schedule, internal->app_context,
+                                       SILC_TASK_INTERRUPT,
+                                       internal->signal_call[i].signal,
+                                       internal->signal_call[i].context);
+      internal->signal_call[i].call = FALSE;
+    }
+  }
+
+  sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
+}
+
+/* Block registered signals in scheduler. */
+
+void silc_schedule_internal_signals_block(void *context)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+
+  if (!internal)
+    return;
+
+  sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
+}
+
+/* Unblock registered signals in schedule. */
+
+void silc_schedule_internal_signals_unblock(void *context)
+{
+  SilcUnixScheduler internal = (SilcUnixScheduler)context;
+
+  if (!internal)
+    return;
+
+  sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
 }