| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <malloc.h> |
| #include <string.h> |
| #include <limits.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <sys/file.h> |
| #include <ctype.h> |
| #include <dlfcn.h> |
| |
| #include <gmain.h> |
| |
| struct timeout { |
| guint id; |
| guint interval; |
| struct timeval expiration; |
| gpointer data; |
| GSourceFunc function; |
| }; |
| |
| struct _GIOChannel { |
| int fd; |
| int ref_count; |
| gboolean closed; |
| gboolean close_on_unref; |
| }; |
| |
| struct child_watch { |
| guint id; |
| GPid pid; |
| GChildWatchFunc function; |
| gpointer user_data; |
| }; |
| |
| struct _GMainContext { |
| guint next_id; |
| glong next_timeout; |
| |
| GSList *timeouts; |
| GSList *proc_timeouts; |
| gboolean timeout_lock; |
| |
| GSList *io_watches; |
| GSList *proc_io_watches; |
| gboolean io_lock; |
| |
| GSList *child_watches; |
| GSList *proc_child_watches; |
| gboolean child_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; |
| } |
| |
| GIOError g_io_channel_write(GIOChannel *channel, const gchar *buf, gsize count, |
| gsize *bytes_written) |
| { |
| 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 = write(fd, buf, count); |
| |
| if (result < 0) { |
| *bytes_written = 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_written = 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->ref_count > 0) |
| return; |
| |
| if (channel->close_on_unref && channel->fd >= 0) |
| g_io_channel_close(channel); |
| |
| g_free(channel); |
| } |
| |
| GIOChannel *g_io_channel_ref(GIOChannel *channel) |
| { |
| channel->ref_count++; |
| return channel; |
| } |
| |
| GIOChannel *g_io_channel_unix_new(int fd) |
| { |
| GIOChannel *channel; |
| |
| channel = g_new0(GIOChannel, 1); |
| |
| channel->fd = fd; |
| channel->ref_count = 1; |
| |
| 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 io_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 io_watch *watch) |
| { |
| if (watch->destroy) |
| watch->destroy(watch->user_data); |
| g_io_channel_unref(watch->channel); |
| g_free(watch); |
| } |
| |
| static GMainContext *g_main_context_default() |
| { |
| if (default_context) |
| return default_context; |
| |
| default_context = g_new0(GMainContext, 1); |
| |
| default_context->next_timeout = -1; |
| default_context->next_id = 1; |
| |
| return default_context; |
| } |
| |
| static gboolean g_io_remove_watch(GMainContext *context, guint id) |
| { |
| GSList *l; |
| struct io_watch *w; |
| |
| for (l = context->io_watches; l != NULL; l = l->next) { |
| w = l->data; |
| |
| if (w->id != id) |
| continue; |
| |
| context->io_watches = g_slist_remove(context->io_watches, w); |
| watch_free(w); |
| |
| return TRUE; |
| } |
| |
| for (l = context->proc_io_watches; l != NULL; l = l->next) { |
| w = l->data; |
| |
| if (w->id != id) |
| continue; |
| |
| context->proc_io_watches = g_slist_remove(context->proc_io_watches, w); |
| watch_free(w); |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean g_timeout_remove(GMainContext *context, const guint id) |
| { |
| GSList *l; |
| struct timeout *t; |
| |
| l = context->timeouts; |
| |
| while (l) { |
| t = l->data; |
| l = l->next; |
| |
| if (t->id != id) |
| continue; |
| |
| context->timeouts = g_slist_remove(context->timeouts, t); |
| g_free(t); |
| |
| return TRUE; |
| } |
| |
| l = context->proc_timeouts; |
| |
| while (l) { |
| t = l->data; |
| l = l->next; |
| |
| if (t->id != id) |
| continue; |
| |
| context->proc_timeouts = g_slist_remove(context->proc_timeouts, t); |
| g_free(t); |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| int watch_prio_cmp(struct io_watch *w1, struct io_watch *w2) |
| { |
| return w1->priority - w2->priority; |
| } |
| |
| #define watch_list_add(l, w) g_slist_insert_sorted((l), (w), (GCompareFunc) watch_prio_cmp) |
| |
| guint g_io_add_watch_full(GIOChannel *channel, gint priority, |
| GIOCondition condition, GIOFunc func, |
| gpointer user_data, GDestroyNotify notify) |
| { |
| struct io_watch *watch; |
| GMainContext *context = g_main_context_default(); |
| |
| watch = g_new(struct io_watch, 1); |
| |
| watch->id = context->next_id++; |
| watch->channel = g_io_channel_ref(channel); |
| watch->priority = priority; |
| watch->condition = condition; |
| watch->func = func; |
| watch->user_data = user_data; |
| watch->destroy = notify; |
| |
| if (context->io_lock) |
| context->proc_io_watches = watch_list_add(context->proc_io_watches, watch); |
| else |
| context->io_watches = watch_list_add(context->io_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(); |
| |
| ml = g_new0(GMainLoop, 1); |
| |
| ml->context = context; |
| ml->is_running = is_running; |
| |
| return ml; |
| } |
| |
| static void timeout_handlers_prepare(GMainContext *context) |
| { |
| GSList *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 = g_slist_remove(context->timeouts, t); |
| context->proc_timeouts = g_slist_append(context->proc_timeouts, t); |
| continue; |
| } |
| |
| ret = t->function(t->data); |
| |
| /* Check if the handler was removed/freed by the callback |
| * function */ |
| if (!g_slist_find_custom(context->timeouts, t, ptr_cmp)) |
| continue; |
| |
| context->timeouts = g_slist_remove(context->timeouts, t); |
| |
| if (!ret) { |
| g_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 = g_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 = g_new(struct pollfd, open_max); |
| |
| loop->is_running = TRUE; |
| |
| while (loop->is_running) { |
| int nfds; |
| GSList *l; |
| struct io_watch *w; |
| |
| for (nfds = 0, l = context->io_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->io_lock = TRUE; |
| |
| while (context->io_watches) { |
| gboolean ret; |
| |
| w = context->io_watches->data; |
| |
| if (!*w->revents) { |
| context->io_watches = g_slist_remove(context->io_watches, w); |
| context->proc_io_watches = watch_list_add(context->proc_io_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 (!g_slist_find_custom(context->io_watches, w, ptr_cmp)) |
| continue; |
| |
| context->io_watches = g_slist_remove(context->io_watches, w); |
| |
| if (!ret) { |
| watch_free(w); |
| continue; |
| } |
| |
| context->proc_io_watches = watch_list_add(context->proc_io_watches, w); |
| } |
| |
| context->io_watches = context->proc_io_watches; |
| context->proc_io_watches = NULL; |
| context->io_lock = FALSE; |
| |
| /* check expired timers */ |
| timeout_handlers_check(loop->context); |
| } |
| |
| g_free(ufds); |
| } |
| |
| void g_main_loop_quit(GMainLoop *loop) |
| { |
| loop->is_running = FALSE; |
| } |
| |
| void g_main_loop_unref(GMainLoop *loop) |
| { |
| if (!loop->context) |
| return; |
| |
| g_slist_foreach(loop->context->io_watches, (GFunc)watch_free, NULL); |
| g_slist_free(loop->context->io_watches); |
| |
| g_slist_foreach(loop->context->timeouts, (GFunc)g_free, NULL); |
| g_slist_free(loop->context->timeouts); |
| |
| g_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; |
| |
| t = g_new0(struct timeout, 1); |
| |
| 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 = g_slist_prepend(context->proc_timeouts, t); |
| else |
| context->timeouts = g_slist_prepend(context->timeouts, t); |
| |
| return t->id; |
| } |
| |
| guint g_idle_add(GSourceFunc function, gpointer data) |
| { |
| return g_timeout_add(1, function, data); |
| } |
| |
| /* GError */ |
| |
| GError* g_error_new_literal(GQuark domain, gint code, const gchar *message) |
| { |
| GError *err; |
| |
| err = g_new(GError, 1); |
| |
| err->domain = domain; |
| err->code = code; |
| err->message = g_strdup(message); |
| |
| return err; |
| } |
| |
| void g_set_error(GError **err, GQuark domain, gint code, |
| const gchar *format, ...) |
| { |
| gchar msg[1024]; |
| va_list ap; |
| |
| if (!err) |
| return; |
| |
| va_start(ap, format); |
| |
| vsnprintf(msg, sizeof(msg) - 1, format, ap); |
| |
| va_end(ap); |
| |
| *err = g_error_new_literal(domain, code, msg); |
| } |
| |
| void g_error_free(GError *err) |
| { |
| g_free(err->message); |
| g_free(err); |
| } |
| |
| /* Spawning related functions */ |
| |
| static int child_watch_pipe[2] = { -1, -1 }; |
| |
| static void sigchld_handler(int signal) |
| { |
| int ret; |
| ret = write(child_watch_pipe[1], "B", 1); |
| } |
| |
| static gboolean child_watch_remove(GMainContext *context, guint id) |
| { |
| GSList *l; |
| struct child_watch *w; |
| |
| for (l = context->child_watches; l != NULL; l = l->next) { |
| w = l->data; |
| |
| if (w->id != id) |
| continue; |
| |
| context->child_watches = |
| g_slist_remove(context->child_watches, w); |
| g_free(w); |
| |
| return TRUE; |
| } |
| |
| for (l = context->proc_child_watches; l != NULL; l = l->next) { |
| w = l->data; |
| |
| if (w->id != id) |
| continue; |
| |
| context->proc_child_watches = |
| g_slist_remove(context->proc_child_watches, w); |
| g_free(w); |
| |
| return TRUE; |
| } |
| |
| |
| return FALSE; |
| } |
| |
| static gboolean child_watch(GIOChannel *io, GIOCondition cond, gpointer user_data) |
| { |
| int ret; |
| char b[20]; |
| GMainContext *context = g_main_context_default(); |
| |
| ret = read(child_watch_pipe[0], b, 20); |
| |
| context->child_lock = TRUE; |
| |
| while (context->child_watches) { |
| gint status; |
| struct child_watch *w = context->child_watches->data; |
| |
| if (waitpid(w->pid, &status, WNOHANG) <= 0) { |
| context->child_watches = |
| g_slist_remove(context->child_watches, w); |
| context->proc_child_watches = |
| watch_list_add(context->proc_child_watches, w); |
| continue; |
| } |
| |
| w->function(w->pid, status, w->user_data); |
| |
| /* Check if the callback already removed us */ |
| if (!g_slist_find(context->child_watches, w)) |
| continue; |
| |
| context->child_watches = g_slist_remove(context->child_watches, w); |
| g_free(w); |
| } |
| |
| context->child_watches = context->proc_child_watches; |
| context->proc_child_watches = NULL; |
| context->child_lock = FALSE; |
| |
| return TRUE; |
| } |
| |
| static void init_child_pipe(void) |
| { |
| struct sigaction action; |
| GIOChannel *io; |
| |
| if (pipe(child_watch_pipe) < 0) { |
| fprintf(stderr, "Unable to initialize child watch pipe: %s (%d)\n", |
| strerror(errno), errno); |
| abort(); |
| } |
| |
| fcntl(child_watch_pipe[1], F_SETFL, |
| O_NONBLOCK | fcntl(child_watch_pipe[1], F_GETFL)); |
| |
| action.sa_handler = sigchld_handler; |
| sigemptyset(&action.sa_mask); |
| action.sa_flags = SA_NOCLDSTOP; |
| sigaction(SIGCHLD, &action, NULL); |
| |
| io = g_io_channel_unix_new(child_watch_pipe[0]); |
| g_io_add_watch(io, G_IO_IN, child_watch, NULL); |
| g_io_channel_unref(io); |
| } |
| |
| static void exec_child(const gchar *working_directory, |
| gchar **argv, gchar **envp, |
| GSpawnFlags flags, |
| GSpawnChildSetupFunc child_setup, |
| gpointer user_data) |
| { |
| int null; |
| |
| if (working_directory && chdir(working_directory) < 0) |
| _exit(EXIT_FAILURE); |
| |
| if (!(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN)) { |
| int open_max, fd, ret; |
| |
| ret = 0; |
| open_max = sysconf(_SC_OPEN_MAX); |
| for (fd = 3; fd < open_max && ret == 0; fd++) |
| ret = fcntl(fd, F_SETFD, FD_CLOEXEC); |
| } |
| |
| null = open("/dev/null", O_RDWR); |
| if (!(flags & G_SPAWN_CHILD_INHERITS_STDIN)) |
| dup2(null, STDIN_FILENO); |
| if (flags & G_SPAWN_STDOUT_TO_DEV_NULL) |
| dup2(null, STDOUT_FILENO); |
| if (flags & G_SPAWN_STDERR_TO_DEV_NULL) |
| dup2(null, STDERR_FILENO); |
| if (null > 2) |
| close(null); |
| |
| if (child_setup) |
| child_setup(user_data); |
| |
| if (envp) |
| execve(argv[0], argv, envp); |
| else |
| execv(argv[0], argv); |
| |
| /* exec failed if we get here */ |
| _exit(EXIT_FAILURE); |
| } |
| |
| gboolean g_spawn_async(const gchar *working_directory, |
| gchar **argv, gchar **envp, |
| GSpawnFlags flags, |
| GSpawnChildSetupFunc child_setup, |
| gpointer user_data, |
| GPid *child_pid, |
| GError **error) |
| { |
| GPid pid; |
| |
| if (access(argv[0], X_OK) < 0) { |
| g_set_error(error, 0, 0, "%s is not executable", argv[0]); |
| return FALSE; |
| } |
| |
| if (child_watch_pipe[0] < 0) |
| init_child_pipe(); |
| |
| /* Flush output streams so child doesn't get them */ |
| fflush(NULL); |
| |
| switch (pid = fork()) { |
| case -1: |
| g_set_error(error, 0, 0, "fork failed: %s", strerror(errno)); |
| return FALSE; |
| case 0: |
| exec_child(working_directory, argv, envp, flags, |
| child_setup, user_data); |
| break; |
| default: |
| if (child_pid) |
| *child_pid = pid; |
| return TRUE; |
| } |
| |
| /* Never reached */ |
| return FALSE; |
| } |
| |
| void g_spawn_close_pid(GPid pid) |
| { |
| return; |
| } |
| |
| guint g_child_watch_add(GPid pid, GChildWatchFunc func, gpointer user_data) |
| { |
| struct child_watch *w; |
| GMainContext *context = g_main_context_default(); |
| |
| if (child_watch_pipe[0] < 0) |
| init_child_pipe(); |
| |
| w = g_new(struct child_watch, 1); |
| |
| w->id = context->next_id++; |
| w->pid = pid; |
| w->function = func; |
| w->user_data = user_data; |
| |
| if (context->child_lock) |
| context->proc_child_watches = |
| watch_list_add(context->proc_child_watches, w); |
| else |
| context->child_watches = |
| watch_list_add(context->child_watches, w); |
| |
| return w->id; |
| } |
| |
| gboolean g_source_remove(guint tag) |
| { |
| GMainContext *context = g_main_context_default(); |
| |
| if (g_io_remove_watch(context, tag)) |
| return TRUE; |
| |
| if (g_timeout_remove(context, tag)) |
| return TRUE; |
| |
| if (child_watch_remove(context, tag)) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| /* 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; |
| } |
| |
| /* GSList functions */ |
| |
| GSList *g_slist_append(GSList *list, void *data) |
| { |
| GSList *entry, *tail; |
| |
| entry = g_new(GSList, 1); |
| |
| entry->data = data; |
| entry->next = NULL; |
| |
| if (!list) |
| return entry; |
| |
| /* Find the end of the list */ |
| for (tail = list; tail->next; tail = tail->next); |
| |
| tail->next = entry; |
| |
| return list; |
| } |
| |
| GSList *g_slist_prepend(GSList *list, void *data) |
| { |
| GSList *entry; |
| |
| entry = g_new(GSList, 1); |
| |
| entry->data = data; |
| entry->next = list; |
| |
| return entry; |
| } |
| |
| GSList *g_slist_insert_sorted(GSList *list, void *data, GCompareFunc cmp_func) |
| { |
| GSList *tmp, *prev, *entry; |
| int cmp; |
| |
| entry = g_new(GSList, 1); |
| |
| entry->data = data; |
| entry->next = NULL; |
| |
| if (!list) |
| return entry; |
| |
| prev = NULL; |
| tmp = list; |
| |
| cmp = cmp_func(data, tmp->data); |
| |
| while (tmp->next && cmp > 0) { |
| prev = tmp; |
| tmp = tmp->next; |
| |
| cmp = cmp_func(data, tmp->data); |
| } |
| |
| if (!tmp->next && cmp > 0) { |
| tmp->next = entry; |
| return list; |
| } |
| |
| if (prev) { |
| prev->next = entry; |
| entry->next = tmp; |
| return list; |
| } else { |
| entry->next = list; |
| return entry; |
| } |
| } |
| |
| GSList *g_slist_remove(GSList *list, void *data) |
| { |
| GSList *l, *next, *prev = NULL, *match = NULL; |
| |
| if (!list) |
| return NULL; |
| |
| for (l = list; l != NULL; l = l->next) { |
| if (l->data == data) { |
| match = l; |
| break; |
| } |
| prev = l; |
| } |
| |
| if (!match) |
| return list; |
| |
| next = match->next; |
| |
| g_free(match); |
| |
| /* If the head was removed, return the next element */ |
| if (!prev) |
| return next; |
| |
| prev->next = next; |
| |
| return list; |
| } |
| |
| GSList *g_slist_find(GSList *list, gconstpointer data) |
| { |
| GSList *l; |
| |
| for (l = list; l != NULL; l = l->next) { |
| if (l->data == data) |
| return l; |
| } |
| |
| return NULL; |
| } |
| |
| GSList *g_slist_find_custom(GSList *list, const void *data, |
| GCompareFunc cmp_func) |
| { |
| GSList *l; |
| |
| for (l = list; l != NULL; l = l->next) { |
| if (!cmp_func(l->data, data)) |
| return l; |
| } |
| |
| return NULL; |
| } |
| |
| static GSList *g_slist_sort_merge(GSList *l1, GSList *l2, |
| GCompareFunc cmp_func) |
| { |
| GSList list, *l; |
| int cmp; |
| |
| l = &list; |
| |
| while (l1 && l2) { |
| cmp = cmp_func(l1->data, l2->data); |
| |
| if (cmp <= 0) { |
| l = l->next = l1; |
| l1 = l1->next; |
| } else { |
| l = l->next = l2; |
| l2 = l2->next; |
| } |
| } |
| |
| l->next = l1 ? l1 : l2; |
| |
| return list.next; |
| } |
| |
| GSList *g_slist_sort(GSList *list, GCompareFunc cmp_func) |
| { |
| GSList *l1, *l2; |
| |
| if (!list || !list->next) |
| return list; |
| |
| l1 = list; |
| l2 = list->next; |
| |
| while ((l2 = l2->next) != NULL) { |
| if ((l2 = l2->next) == NULL) |
| break; |
| l1 = l1->next; |
| } |
| |
| l2 = l1->next; |
| l1->next = NULL; |
| |
| return g_slist_sort_merge(g_slist_sort(list, cmp_func), |
| g_slist_sort(l2, cmp_func), cmp_func); |
| } |
| |
| int g_slist_length(GSList *list) |
| { |
| int len; |
| |
| for (len = 0; list != NULL; list = list->next) |
| len++; |
| |
| return len; |
| } |
| |
| void g_slist_foreach(GSList *list, GFunc func, void *user_data) |
| { |
| while (list) { |
| GSList *next = list->next; |
| func(list->data, user_data); |
| list = next; |
| } |
| } |
| |
| void g_slist_free(GSList *list) |
| { |
| GSList *l, *next; |
| |
| for (l = list; l != NULL; l = next) { |
| next = l->next; |
| g_free(l); |
| } |
| } |
| |
| GSList *g_slist_nth(GSList *list, guint n) |
| { |
| while (n-- > 0 && list) |
| list = list->next; |
| |
| return list; |
| } |
| |
| gpointer g_slist_nth_data(GSList *list, guint n) |
| { |
| while (n-- > 0 && list) |
| list = list->next; |
| |
| return list ? list->data : NULL; |
| } |
| |
| gint g_slist_position(GSList *list, GSList *link) |
| { |
| gint i; |
| |
| for (i = 0; list; list = list->next, i++) { |
| if (list == link) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| GSList* g_slist_last(GSList *list) |
| { |
| if (list) |
| while (list->next) |
| list = list->next; |
| |
| return list; |
| } |
| |
| static inline GSList* _g_slist_remove_link(GSList *list, GSList *link) |
| { |
| GSList *tmp; |
| GSList *prev; |
| |
| prev = NULL; |
| tmp = list; |
| |
| while (tmp) { |
| if (tmp == link) { |
| if (prev) |
| prev->next = tmp->next; |
| if (list == tmp) |
| list = list->next; |
| |
| tmp->next = NULL; |
| break; |
| } |
| |
| prev = tmp; |
| tmp = tmp->next; |
| } |
| |
| return list; |
| } |
| |
| GSList* g_slist_delete_link(GSList *list, GSList *link) |
| { |
| list = _g_slist_remove_link(list, link); |
| g_free(link); |
| |
| return list; |
| } |
| |
| /* Memory allocation functions */ |
| |
| gpointer g_malloc(gulong n_bytes) |
| { |
| gpointer mem; |
| |
| if (!n_bytes) |
| return NULL; |
| |
| mem = malloc((size_t) n_bytes); |
| if (!mem) { |
| fprintf(stderr, "g_malloc: failed to allocate %lu bytes", |
| n_bytes); |
| abort(); |
| } |
| |
| return mem; |
| } |
| |
| gpointer g_malloc0(gulong n_bytes) |
| { |
| gpointer mem; |
| |
| if (!n_bytes) |
| return NULL; |
| |
| mem = g_malloc(n_bytes); |
| |
| memset(mem, 0, (size_t) n_bytes); |
| |
| return mem; |
| } |
| |
| gpointer g_try_malloc(gulong n_bytes) |
| { |
| if (!n_bytes) |
| return NULL; |
| |
| return malloc((size_t) n_bytes); |
| } |
| |
| gpointer g_try_malloc0(gulong n_bytes) |
| { |
| gpointer mem; |
| |
| mem = g_try_malloc(n_bytes); |
| if (mem) |
| memset(mem, 0, (size_t) n_bytes); |
| |
| return mem; |
| } |
| |
| gpointer g_realloc(gpointer mem, gulong n_bytes) |
| { |
| mem = realloc(mem, n_bytes); |
| if (!mem) { |
| fprintf(stderr, "g_realloc: failed to allocate %lu bytes", |
| n_bytes); |
| abort(); |
| } |
| |
| return mem; |
| } |
| |
| void g_free(gpointer mem) |
| { |
| if (mem) |
| free(mem); |
| } |
| |
| gchar *g_strdup(const gchar *str) |
| { |
| gchar *s; |
| |
| if (!str) |
| return NULL; |
| |
| s = strdup(str); |
| if (!s) { |
| fprintf(stderr, "strdup: failed to allocate new string"); |
| abort(); |
| } |
| |
| return s; |
| } |
| |
| gchar *g_strdup_printf(const gchar *format, ...) |
| { |
| va_list args; |
| gchar buffer[1024]; |
| gint length; |
| |
| va_start(args, format); |
| length = vsnprintf(buffer, sizeof(buffer) - 1, format, args); |
| va_end(args); |
| |
| return g_strdup(buffer); |
| } |
| |
| gchar *g_strdelimit(gchar *string, const gchar *delimiters, gchar new_delim) |
| { |
| register gchar *c; |
| |
| if (!string) |
| return NULL; |
| |
| for (c = string; *c; c++) |
| if (strchr(delimiters, *c)) |
| *c = new_delim; |
| |
| return string; |
| } |
| |
| /* GKeyFile */ |
| |
| struct _GKeyFile { |
| gchar *filename; |
| }; |
| |
| GKeyFile *g_key_file_new(void) |
| { |
| return g_new0(GKeyFile, 1); |
| } |
| |
| void g_key_file_free(GKeyFile *key_file) |
| { |
| g_free(key_file->filename); |
| g_free(key_file); |
| } |
| |
| gboolean g_key_file_load_from_file(GKeyFile *key_file, |
| const gchar *file, |
| GKeyFileFlags flags, |
| GError **error) |
| { |
| key_file->filename = g_strdup(file); |
| return TRUE; |
| } |
| |
| static char *next_line(const char *ptr) |
| { |
| char *nl; |
| |
| nl = strchr(ptr, '\n'); |
| if (!nl) |
| return NULL; |
| |
| if (nl[1] == '\0') |
| return NULL; |
| |
| return nl + 1; |
| } |
| |
| gchar *g_key_file_get_string(GKeyFile *key_file, |
| const gchar *group_name, |
| const gchar *key, |
| GError **error) |
| { |
| struct stat st; |
| char *map, *line, *group = NULL, *value = NULL; |
| off_t size; |
| size_t key_len, group_len; |
| int fd, err = 0; |
| |
| fd = open(key_file->filename, O_RDONLY); |
| if (fd < 0) { |
| g_set_error(error, 0, 0, "%s: %s", key_file->filename, |
| strerror(errno)); |
| return NULL; |
| } |
| |
| if (flock(fd, LOCK_SH) < 0) { |
| err = errno; |
| goto close; |
| } |
| |
| if (fstat(fd, &st) < 0) { |
| err = errno; |
| goto unlock; |
| } |
| |
| size = st.st_size; |
| |
| map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); |
| if (!map || map == MAP_FAILED) { |
| err = errno; |
| goto unlock; |
| } |
| |
| group_len = strlen(group_name); |
| key_len = strlen(key); |
| |
| for (line = map; line != NULL; line = next_line(line)) { |
| int i; |
| size_t to_copy, value_len; |
| char tmp[1024], *nl; |
| |
| if (*line == '#') |
| continue; |
| |
| if (!group) { |
| if (line[0] != '[' || strncmp(line + 1, group_name, group_len)) |
| continue; |
| if (line[group_len + 1] == ']') |
| group = line + 1; |
| continue; |
| } |
| |
| if (strncmp(line, key, key_len)) |
| continue; |
| |
| for (i = key_len; line[i] != '\n'; i++) { |
| if (line[i] == '=') |
| break; |
| if (!isspace(line[i])) |
| break; |
| } |
| |
| if (line[i] != '=') |
| continue; |
| |
| nl = strchr(line, '\n'); |
| if (!nl) |
| continue; |
| |
| value_len = nl - (line + i + 1); |
| to_copy = value_len > (sizeof(tmp) - 1) ? sizeof(tmp) - 1 : value_len; |
| memset(tmp, 0, sizeof(tmp)); |
| strncpy(tmp, line + i + 1, to_copy); |
| |
| value = g_strdup(tmp); |
| break; |
| } |
| |
| munmap(map, size); |
| |
| unlock: |
| flock(fd, LOCK_UN); |
| |
| close: |
| close(fd); |
| |
| if (err) |
| g_set_error(error, 0, 0, "%s: %s", key_file->filename, |
| strerror(err)); |
| else if (!group) |
| g_set_error(error, 0, 0, "%s: group %s not found", |
| key_file->filename, group_name); |
| else if (!value) |
| g_set_error(error, 0, 0, "%s: key %s not found", |
| key_file->filename, key); |
| |
| return value; |
| } |
| |
| gboolean g_key_file_get_boolean(GKeyFile *key_file, |
| const gchar *group_name, |
| const gchar *key, |
| GError **error) |
| { |
| gboolean ret; |
| gchar *str; |
| |
| str = g_key_file_get_string(key_file, group_name, key, error); |
| if (!str) |
| return FALSE; |
| |
| if (strcmp(str, "true") == 0 || strcmp(str, "1") == 0) |
| ret = TRUE; |
| else |
| ret = FALSE; |
| |
| g_free(str); |
| |
| return ret; |
| } |
| |
| /* GString */ |
| |
| #define MY_MAXSIZE ((gsize)-1) |
| |
| static gsize nearest_power(gsize base, gsize num) |
| { |
| gsize n = base; |
| |
| if (num > MY_MAXSIZE / 2) |
| return MY_MAXSIZE; |
| |
| while (n < num) |
| n <<= 1; |
| |
| return n; |
| } |
| |
| static void g_string_maybe_expand(GString *string, gsize len) |
| { |
| if (string->len + len < string->allocated_len) |
| return; |
| |
| string->allocated_len = nearest_power(1, string->len + len + 1); |
| string->str = g_realloc(string->str, string->allocated_len); |
| } |
| |
| static GString *g_string_sized_new(gsize dfl_size) |
| { |
| GString *string; |
| |
| string = g_new0(GString, 1); |
| |
| g_string_maybe_expand(string, dfl_size); |
| string->str[0] = '\0'; |
| |
| return string; |
| } |
| |
| static GString *g_string_append_len(GString *string, const gchar *val, gssize len) |
| { |
| g_string_maybe_expand(string, len); |
| |
| if (len == 1) |
| string->str[string->len] = *val; |
| else |
| memcpy(string->str + string->len, val, len); |
| |
| string->len += len; |
| string->str[string->len] = '\0'; |
| |
| return string; |
| } |
| |
| GString *g_string_new(const gchar *init) |
| { |
| GString *string; |
| gint len; |
| |
| if (init == NULL || *init == '\0') |
| return g_string_sized_new(2); |
| |
| len = strlen(init); |
| string = g_string_sized_new(len + 2); |
| |
| g_string_append_len(string, init, len); |
| |
| return string; |
| } |
| |
| void g_string_append_printf(GString *string, const gchar *format, ...) |
| { |
| gchar buffer[1024]; |
| gint length; |
| va_list args; |
| |
| va_start(args, format); |
| length = vsnprintf(buffer, sizeof(buffer) - 1, format, args); |
| va_end(args); |
| |
| g_string_append_len(string, buffer, length); |
| } |
| |
| gchar *g_string_free(GString *string, gboolean free_segment) |
| { |
| gchar *segment; |
| |
| if (free_segment) { |
| g_free(string->str); |
| segment = NULL; |
| } |
| else |
| segment = string->str; |
| |
| g_free(string); |
| |
| return segment; |
| } |