blob: f3478edf1c362957add1dcb3d76242da146d830d [file] [log] [blame]
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <limits.h>
#include <sys/time.h>
#include <time.h>
#include "list.h"
#include "glib-ectomy.h"
struct timeout {
guint id;
guint interval;
struct timeval expiration;
gpointer data;
GSourceFunc function;
};
struct _GIOChannel {
int fd;
gboolean closed;
gboolean close_on_unref;
};
struct _GMainContext {
guint next_id;
glong next_timeout;
struct slist *timeouts;
struct slist *proc_timeouts;
gboolean timeout_lock;
struct slist *watches;
struct slist *proc_watches;
gboolean watch_lock;
};
struct _GMainLoop {
gboolean is_running;
GMainContext *context;
};
GIOError g_io_channel_read(GIOChannel *channel, gchar *buf, gsize count, gsize *bytes_read)
{
int fd = channel->fd;
gssize result;
if (channel->closed)
return G_IO_STATUS_ERROR;
/* At least according to the Debian manpage for read */
if (count > SSIZE_MAX)
count = SSIZE_MAX;
retry:
result = read (fd, buf, count);
if (result < 0) {
*bytes_read = 0;
switch (errno) {
#ifdef EINTR
case EINTR:
goto retry;
#endif
#ifdef EAGAIN
case EAGAIN:
return G_IO_STATUS_AGAIN;
#endif
default:
return G_IO_STATUS_ERROR;
}
}
*bytes_read = result;
return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
}
void g_io_channel_close(GIOChannel *channel)
{
if (!channel || channel->closed)
return;
close(channel->fd);
channel->closed = TRUE;
}
void g_io_channel_unref(GIOChannel *channel)
{
if (!channel)
return;
if (channel->close_on_unref && channel->fd >= 0)
g_io_channel_close(channel);
free(channel);
}
GIOChannel *g_io_channel_unix_new(int fd)
{
GIOChannel *channel;
channel = malloc(sizeof(GIOChannel));
if (!channel)
return NULL;
memset(channel, 0, sizeof(GIOChannel));
channel->fd = fd;
return channel;
}
void g_io_channel_set_close_on_unref(GIOChannel *channel, gboolean do_close)
{
channel->close_on_unref = do_close;
}
gint g_io_channel_unix_get_fd(GIOChannel *channel)
{
if (channel->closed)
return -1;
return channel->fd;
}
struct watch {
guint id;
GIOChannel *channel;
gint priority;
GIOCondition condition;
short *revents;
GIOFunc func;
gpointer user_data;
GDestroyNotify destroy;
};
static GMainContext *default_context = NULL;
static void watch_free(struct watch *watch)
{
if (watch->destroy)
watch->destroy(watch->user_data);
free(watch);
}
static GMainContext *g_main_context_default()
{
if (default_context)
return default_context;
default_context = malloc(sizeof(GMainContext));
if (!default_context)
return NULL;
memset(default_context, 0, sizeof(GMainContext));
default_context->next_timeout = -1;
default_context->next_id = 1;
return default_context;
}
void g_io_remove_watch(guint id)
{
GMainContext *context = g_main_context_default();
struct slist *l;
struct watch *w;
if (!context)
return;
for (l = context->watches; l != NULL; l = l->next) {
w = l->data;
if (w->id != id)
continue;
context->watches = slist_remove(context->watches, w);
watch_free(w);
return;
}
for (l = context->proc_watches; l != NULL; l = l->next) {
w = l->data;
if (w->id != id)
continue;
context->proc_watches = slist_remove(context->proc_watches, w);
watch_free(w);
return;
}
}
static struct slist *watch_list_add(struct slist *l, struct watch *watch)
{
struct slist *cur;
for (cur = l; cur != NULL; cur = cur->next) {
struct watch *w = cur->data;
if (w->priority >= watch->priority)
break;
}
return slist_insert_before(l, cur, watch);
}
guint g_io_add_watch_full(GIOChannel *channel, gint priority,
GIOCondition condition, GIOFunc func,
gpointer user_data, GDestroyNotify notify)
{
struct watch *watch;
GMainContext *context = g_main_context_default();
if (!context)
return 0;
watch = malloc(sizeof(struct watch));
if (!watch)
return 0;
watch->id = context->next_id++;
watch->channel = channel;
watch->priority = priority;
watch->condition = condition;
watch->func = func;
watch->user_data = user_data;
watch->destroy = notify;
if (context->watch_lock)
context->proc_watches = watch_list_add(context->proc_watches, watch);
else
context->watches = watch_list_add(context->watches, watch);
return watch->id;
}
guint g_io_add_watch(GIOChannel *channel, GIOCondition condition,
GIOFunc func, gpointer user_data)
{
return g_io_add_watch_full(channel, 0, condition,
func, user_data, NULL);
}
GMainLoop *g_main_loop_new(GMainContext *context, gboolean is_running)
{
GMainLoop *ml;
if (!context)
context = g_main_context_default();
if (!context)
return NULL;
ml = malloc(sizeof(GMainLoop));
if (!ml)
return NULL;
memset(ml, 0, sizeof(GMainLoop));
ml->context = context;
ml->is_running = is_running;
return ml;
}
static void timeout_handlers_prepare(GMainContext *context)
{
struct slist *l;
struct timeval tv;
glong msec, timeout = LONG_MAX;
gettimeofday(&tv, NULL);
for (l = context->timeouts; l != NULL; l = l->next) {
struct timeout *t = l->data;
/* calculate the remainning time */
msec = (t->expiration.tv_sec - tv.tv_sec) * 1000 +
(t->expiration.tv_usec - tv.tv_usec) / 1000;
if (msec < 0)
msec = 0;
timeout = MIN_TIMEOUT(timeout, msec);
}
/* set to min value found or NO timeout */
context->next_timeout = (timeout != LONG_MAX ? timeout : -1);
}
static int ptr_cmp(const void *t1, const void *t2)
{
return t1 - t2;
}
static void timeout_handlers_check(GMainContext *context)
{
struct timeval tv;
gettimeofday(&tv, NULL);
context->timeout_lock = TRUE;
while (context->timeouts) {
struct timeout *t = context->timeouts->data;
glong secs, msecs;
gboolean ret;
if (timercmp(&tv, &t->expiration, <)) {
context->timeouts = slist_remove(context->timeouts, t);
context->proc_timeouts = slist_append(context->proc_timeouts, t);
continue;
}
ret = t->function(t->data);
/* Check if the handler was removed/freed by the callback
* function */
if (!slist_find(context->timeouts, t, ptr_cmp))
continue;
context->timeouts = slist_remove(context->timeouts, t);
if (!ret) {
free(t);
continue;
}
/* update the next expiration time */
secs = t->interval / 1000;
msecs = t->interval - secs * 1000;
t->expiration.tv_sec = tv.tv_sec + secs;
t->expiration.tv_usec = tv.tv_usec + msecs * 1000;
if (t->expiration.tv_usec >= 1000000) {
t->expiration.tv_usec -= 1000000;
t->expiration.tv_sec++;
}
context->proc_timeouts = slist_append(context->proc_timeouts, t);
}
context->timeouts = context->proc_timeouts;
context->proc_timeouts = NULL;
context->timeout_lock = FALSE;
}
void g_main_loop_run(GMainLoop *loop)
{
int open_max = sysconf(_SC_OPEN_MAX);
struct pollfd *ufds;
GMainContext *context = loop->context;
ufds = malloc(open_max * sizeof(struct pollfd));
if (!ufds)
return;
loop->is_running = TRUE;
while (loop->is_running) {
int nfds;
struct slist *l;
struct watch *w;
for (nfds = 0, l = context->watches; l != NULL; l = l->next, nfds++) {
w = l->data;
ufds[nfds].fd = w->channel->fd;
ufds[nfds].events = w->condition;
ufds[nfds].revents = 0;
w->revents = &ufds[nfds].revents;
}
/* calculate the next timeout */
timeout_handlers_prepare(context);
if (poll(ufds, nfds, context->next_timeout) < 0)
continue;
context->watch_lock = TRUE;
while (context->watches) {
gboolean ret;
w = context->watches->data;
if (!*w->revents) {
context->watches = slist_remove(context->watches, w);
context->proc_watches = watch_list_add(context->proc_watches, w);
continue;
}
ret = w->func(w->channel, *w->revents, w->user_data);
/* Check if the watch was removed/freed by the callback
* function */
if (!slist_find(context->watches, w, ptr_cmp))
continue;
context->watches = slist_remove(context->watches, w);
if (!ret) {
watch_free(w);
continue;
}
context->proc_watches = watch_list_add(context->proc_watches, w);
}
context->watches = context->proc_watches;
context->proc_watches = NULL;
context->watch_lock = FALSE;
/* check expired timers */
timeout_handlers_check(loop->context);
}
free(ufds);
}
void g_main_loop_quit(GMainLoop *loop)
{
loop->is_running = FALSE;
}
void g_main_loop_unref(GMainLoop *loop)
{
if (!loop->context)
return;
slist_foreach(loop->context->watches, (slist_func_t)watch_free, NULL);
slist_free(loop->context->watches);
slist_foreach(loop->context->timeouts, (slist_func_t)free, NULL);
slist_free(loop->context->timeouts);
free(loop->context);
loop->context = NULL;
}
guint g_timeout_add(guint interval, GSourceFunc function, gpointer data)
{
GMainContext *context = g_main_context_default();
struct timeval tv;
guint secs;
guint msecs;
struct timeout *t;
if (!context || !function)
return 0;
t = malloc(sizeof(*t));
if (!t)
return 0;
memset(t, 0, sizeof(*t));
t->interval = interval;
t->function = function;
t->data = data;
gettimeofday(&tv, NULL);
secs = interval /1000;
msecs = interval - secs * 1000;
t->expiration.tv_sec = tv.tv_sec + secs;
t->expiration.tv_usec = tv.tv_usec + msecs * 1000;
if (t->expiration.tv_usec >= 1000000) {
t->expiration.tv_usec -= 1000000;
t->expiration.tv_sec++;
}
/* attach the timeout the default context */
t->id = context->next_id++;
if (context->timeout_lock)
context->proc_timeouts = slist_prepend(context->proc_timeouts, t);
else
context->timeouts = slist_prepend(context->timeouts, t);
return t->id;
}
gint g_timeout_remove(const guint id)
{
GMainContext *context = g_main_context_default();
struct slist *l;
struct timeout *t;
if (!context)
return -1;
l = context->timeouts;
while (l) {
t = l->data;
l = l->next;
if (t->id != id)
continue;
context->timeouts = slist_remove(context->timeouts, t);
free(t);
return 0;
}
l = context->proc_timeouts;
while (l) {
t = l->data;
l = l->next;
if (t->id != id)
continue;
context->proc_timeouts = slist_remove(context->proc_timeouts, t);
free(t);
return 0;
}
return -1;
}
/* UTF-8 Validation: approximate copy/paste from glib2. */
#define UNICODE_VALID(c) \
((c) < 0x110000 && \
(((c) & 0xFFFFF800) != 0xD800) && \
((c) < 0xFDD0 || (c) > 0xFDEF) && \
((c) & 0xFFFE) != 0xFFFE)
#define CONTINUATION_CHAR(c, val) \
do { \
if (((c) & 0xc0) != 0x80) /* 10xxxxxx */ \
goto failed; \
(val) <<= 6; \
(val) |= (c) & 0x3f; \
} while (0)
#define INCREMENT_AND_CHECK_MAX(p, i, max_len) \
do { \
(i)++; \
if ((p)[(i)] == '\0' || ((max_len) >= 0 && (i) >= (max_len))) \
goto failed; \
} while (0)
gboolean g_utf8_validate(const gchar *str, gssize max_len, const gchar **end)
{
unsigned long val, min, i;
const unsigned char *p, *last;
min = val = 0;
for (p = (unsigned char *) str, i = 0; p[i]; i++) {
if (max_len >= 0 && i >= max_len)
break;
if (p[i] < 128)
continue;
last = &p[i];
if ((p[i] & 0xe0) == 0xc0) { /* 110xxxxx */
if ((p[i] & 0x1e) == 0)
goto failed;
INCREMENT_AND_CHECK_MAX(p, i, max_len);
if ((p[i] & 0xc0) != 0x80)
goto failed; /* 10xxxxxx */
} else {
if ((p[i] & 0xf0) == 0xe0) {
/* 1110xxxx */
min = (1 << 11);
val = p[i] & 0x0f;
goto two_remaining;
} else if ((p[i] & 0xf8) == 0xf0) {
/* 11110xxx */
min = (1 << 16);
val = p[i] & 0x07;
} else
goto failed;
INCREMENT_AND_CHECK_MAX(p, i, max_len);
CONTINUATION_CHAR(p[i], val);
two_remaining:
INCREMENT_AND_CHECK_MAX(p, i, max_len);
CONTINUATION_CHAR(p[i], val);
INCREMENT_AND_CHECK_MAX(p, i, max_len);
CONTINUATION_CHAR(p[i], val);
if (val < min || !UNICODE_VALID(val))
goto failed;
}
}
if (end)
*end = (const gchar *) &p[i];
return TRUE;
failed:
if (end)
*end = (const gchar *) last;
return FALSE;
}