summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@opera.com>2015-07-13 13:04:24 +0200
committerJoel Klinghed <the_jk@opera.com>2015-07-13 13:04:24 +0200
commit1d8af5a018282dc6a93b9ed7c87d9d2f87287b14 (patch)
treec47c19f7c27fd1774d1455b088eb207c611d30ed /src
parent59709e4cb30f2ff8666522d5b758731ab618adbc (diff)
Copy the dependencies from sawmill project
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore5
-rw-r--r--src/Makefile.am9
-rw-r--r--src/common.h3
-rw-r--r--src/compiler.h42
-rw-r--r--src/dynstr.c95
-rw-r--r--src/dynstr.h122
-rw-r--r--src/macros.h19
-rw-r--r--src/main.c1
-rw-r--r--src/paths.c1030
-rw-r--r--src/paths.h261
-rw-r--r--src/ref.h95
-rw-r--r--src/safe_fifo.c152
-rw-r--r--src/safe_fifo.h71
-rw-r--r--src/strutil.c124
-rw-r--r--src/strutil.h81
-rw-r--r--src/thread-pthread.c344
-rw-r--r--src/thread.h266
-rw-r--r--src/timespec.c283
-rw-r--r--src/timespec.h26
19 files changed, 3021 insertions, 8 deletions
diff --git a/src/.gitignore b/src/.gitignore
index e17b04a..6815d91 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1,6 +1 @@
-/compiler.h
-/paths.h
-/safe_fifo.h
-/thread.h
-/timespec.h
/timer
diff --git a/src/Makefile.am b/src/Makefile.am
index c4ad583..7131b86 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,11 @@ AM_CPPFLAGS = @DEFINES@ -DVERSION='"@VERSION@"'
bin_PROGRAMS = timer
-timer_SOURCES = main.c common.h compiler.h safe_fifo.h thread.h paths.h
+timer_SOURCES = main.c common.h compiler.h safe_fifo.h thread.h paths.h \
+ safe_fifo.c timespec.h timespec.c paths.c ref.h macros.h \
+ strutil.h strutil.c dynstr.h dynstr.c
+if HAVE_PTHREAD
+timer_SOURCES += thread-pthread.c
+endif
timer_CFLAGS = @XCB_CFLAGS@
-timer_LDADD = libpaths.a libcommon.a @XCB_LIBS@ @CLOCK_LIBS@
+timer_LDADD = @XCB_LIBS@
diff --git a/src/common.h b/src/common.h
index 974e03f..87fa887 100644
--- a/src/common.h
+++ b/src/common.h
@@ -8,6 +8,9 @@
#include <stdbool.h>
#include <stdlib.h>
+#include "compiler.h"
+#include "macros.h"
+
#ifdef DEBUG
# include <assert.h>
#else
diff --git a/src/compiler.h b/src/compiler.h
new file mode 100644
index 0000000..6a5ce4a
--- /dev/null
+++ b/src/compiler.h
@@ -0,0 +1,42 @@
+/**
+ * \file compiler.h
+ * Part of common.h, defines macros for hints to the compiler.
+ */
+
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#if HAVE___ATTRIBUTE__
+# define MALLOC __attribute__((malloc))
+# define NONULL __attribute__((nonnull))
+# define NONULL_ARGS(...) __attribute__((nonnull (__VA_ARGS__)))
+# define PRINTF(_n,_m) __attribute__((format (printf, (_n), (_m))))
+# define UNUSED __attribute__((unused))
+#else
+# warning Less compile time checks as compiler is unsupported
+/**
+ * Mark a function as always returning a new unique pointer or NULL
+ */
+# define MALLOC
+/**
+ * Mark a function as not taking NULL to any of its pointer parameters
+ */
+# define NONULL
+/**
+ * Mark a function as not taking NULL to the given list of pointer parameters.
+ * The list of parameters is 1 based.
+ */
+# define NONULL_ARGS(...)
+/**
+ * Mark a function as taking printf format style parameter.
+ * @param _n the 1 based index of the format parameter
+ * @param _m the 1 based index of the first variable list parameter
+ */
+# define PRINTF(_n,_m)
+/**
+ * Mark a function paramter as being unused
+ */
+# define UNUSED
+#endif
+
+#endif /* COMPILER_H */
diff --git a/src/dynstr.c b/src/dynstr.c
new file mode 100644
index 0000000..fa9e9e0
--- /dev/null
+++ b/src/dynstr.c
@@ -0,0 +1,95 @@
+#include "common.h"
+
+#include "dynstr.h"
+
+dynstr_t* dynstr_new(void)
+{
+ dynstr_t* str = calloc(1, sizeof(dynstr_t));
+ if (str)
+ {
+ str->size = 64;
+ str->data = malloc(str->size);
+ if (!str->data)
+ {
+ free(str);
+ return NULL;
+ }
+ }
+ return str;
+}
+
+dynstr_t* dynstr_new_strn(const char* cstr, size_t len)
+{
+ dynstr_t* str = calloc(1, sizeof(dynstr_t));
+ assert(cstr);
+ if (str)
+ {
+ str->len = len;
+ str->size = len + 1;
+ str->data = malloc(str->size);
+ if (!str->data)
+ {
+ free(str);
+ return NULL;
+ }
+ memcpy(str->data, cstr, len);
+ }
+ return str;
+}
+
+void dynstr_free(dynstr_t* str)
+{
+ if (str)
+ {
+ free(str->data);
+ free(str);
+ }
+}
+
+char* dynstr_done(dynstr_t* str)
+{
+ char* ret;
+ assert(str);
+ assert(str->len < str->size);
+ str->data[str->len] = '\0';
+ ret = str->data;
+ free(str);
+ return ret;
+}
+
+const char* dynstr_peek(dynstr_t* str)
+{
+ assert(str);
+ assert(str->len < str->size);
+ str->data[str->len] = '\0';
+ return str->data;
+}
+
+bool dynstr_nappend(dynstr_t* str, const char* add, size_t len)
+{
+ assert(str && add);
+ if (str->len + len >= str->size)
+ {
+ size_t ns = MAX(str->size * 2, str->len + len + 1);
+ char* tmp = realloc(str->data, ns);
+ if (!tmp)
+ {
+ return false;
+ }
+ str->data = tmp;
+ str->size = ns;
+ }
+ memcpy(str->data + str->len, add, len);
+ str->len += len;
+ return true;
+}
+
+void dynstr_cut(dynstr_t* str, size_t newlen)
+{
+ assert(str);
+ assert(newlen <= str->len);
+ if (newlen < str->len)
+ {
+ str->len = newlen;
+ }
+}
diff --git a/src/dynstr.h b/src/dynstr.h
new file mode 100644
index 0000000..f18e4f2
--- /dev/null
+++ b/src/dynstr.h
@@ -0,0 +1,122 @@
+/**
+ * @file dynstr.h
+ * Dynamically allocated string.
+ */
+
+#ifndef DYNSTR_H
+#define DYNSTR_H
+
+/**
+ * Dynamically allocated string.
+ * If you modify the content of the struct your self (please don't) make sure
+ * len is ALWAYS less than size.
+ */
+typedef struct dynstr_t
+{
+ /**
+ * String data, size large and filled with len bytes of string.
+ * Not null-terminated.
+ */
+ char* data;
+ /**
+ * Number of bytes in data
+ */
+ size_t len;
+ /**
+ * Size of data in bytes
+ */
+ size_t size;
+} dynstr_t;
+
+#include <string.h>
+
+/**
+ * Allocate a dynamically allocated string.
+ * Returned string is empty but has allocate at least one byte of space.
+ * Returns NULL in case of allocation error.
+ * @return empty string
+ */
+MALLOC dynstr_t* dynstr_new(void);
+/**
+ * Allocate a dynamically allocated string and initialize with parameters.
+ * Returned string has at least one more byte allocated than len.
+ * Returns NULL in case of allocation error.
+ * @param str string to copy len bytes from, may not be NULL
+ * @param len number of bytes to copy from str
+ * @return string with given data
+ */
+MALLOC NONULL dynstr_t* dynstr_new_strn(const char* str, size_t len);
+/**
+ * Allocate a dynamically allocated string and initialize with parameter.
+ * Returned string has space for at least one more byte.
+ * Returns NULL in case of allocation error.
+ * @param str null-terminated string to copy from, may not be NULL
+ * @return string with given data
+ */
+static inline MALLOC NONULL dynstr_t* dynstr_new_str(const char* str)
+{
+ assert(str);
+ return dynstr_new_strn(str, strlen(str));
+}
+/**
+ * Free a dynamic string.
+ * @param str string to free, may be NULL
+ */
+void dynstr_free(dynstr_t* str);
+
+/**
+ * Return null-terminated static string and free dynamic string.
+ * @param str string to free, may not be NULL
+ * @return null-terminated content of str, never NULL
+ */
+NONULL char* dynstr_done(dynstr_t* str);
+
+/**
+ * Return null-terminated static string.
+ * The returned string is only valid until the next call to a dynstr method
+ * on the same string.
+ * @param str string to free, may not be NULL
+ * @return null-terminated content of str, never NULL
+ */
+NONULL const char* dynstr_peek(dynstr_t* str);
+
+/**
+ * Append len bytes of data from add to dynamic string.
+ * @param str string to append to, may not be NULL
+ * @param add string to append, may not be NULL
+ * @param len number of bytes to append from add
+ * @return false in case of allocation error
+ */
+NONULL bool dynstr_nappend(dynstr_t* str, const char* add, size_t len);
+
+/**
+ * Append string to dynamic string.
+ * @param str string to append to, may not be NULL
+ * @param add null-terminated string to append, may not be NULL
+ * @return false in case of allocation error
+ */
+static inline NONULL bool dynstr_append(dynstr_t* str, const char* add)
+{
+ assert(add);
+ return dynstr_nappend(str, add, strlen(add));
+}
+
+/**
+ * Append character to dynamic string.
+ * @param str string to append to, may not be NULL
+ * @param c character to append
+ * @return false in case of allocation error
+ */
+static inline NONULL bool dynstr_appendc(dynstr_t* str, char c)
+{
+ return dynstr_nappend(str, &c, 1);
+}
+
+/**
+ * Make dynamic string shorter by cutting of a bit at the end.
+ * @param str string to cut shorter, may not be NULL
+ * @param newlen new length of string
+ */
+NONULL void dynstr_cut(dynstr_t* str, size_t newlen);
+
+#endif /* DYNSTR_H */
diff --git a/src/macros.h b/src/macros.h
new file mode 100644
index 0000000..06d59c6
--- /dev/null
+++ b/src/macros.h
@@ -0,0 +1,19 @@
+/**
+ * \file macros.h
+ * Part of common.h, defines common small macros if needed
+ */
+
+#ifndef MACROS_H
+#define MACROS_H
+
+#ifndef MIN
+/** x < y ? x : y */
+# define MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y))
+#endif
+
+#ifndef MAX
+/** x > y ? x : y */
+# define MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y))
+#endif
+
+#endif /* MACROS_H */
diff --git a/src/main.c b/src/main.c
index d6beed4..91ed9f3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,6 @@
#define HAVE_STRUCT_TIMESPEC 1
-#include "compiler.h"
#include "paths.h"
#include "safe_fifo.h"
#include "thread.h"
diff --git a/src/paths.c b/src/paths.c
new file mode 100644
index 0000000..6a253ca
--- /dev/null
+++ b/src/paths.c
@@ -0,0 +1,1030 @@
+#include "common.h"
+
+#include "paths.h"
+#include "ref.h"
+#include "thread.h"
+#include "safe_fifo.h"
+#include "strutil.h"
+
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SEARCH_CACHE_SIZE (2)
+
+typedef struct search_t
+{
+ const char* dir;
+ const char** dirs;
+ char* match;
+ const char* match_name;
+ bool plain;
+ paths_find_callback_t find_callback;
+ void* userdata;
+} search_t;
+
+typedef struct searchargs_t
+{
+ bool quit;
+ safe_fifo_t* queue;
+ thread_lock_t* lock;
+ search_t* cache[SEARCH_CACHE_SIZE];
+} searchargs_t;
+
+struct paths_t
+{
+ REF_DECLARE();
+ char* user_data;
+ char* user_config;
+ char* user_cache;
+ char* user_runtime;
+ char** search_data;
+ char** search_config;
+
+ searchargs_t* search;
+#ifdef DEBUG
+ thread_t* searcher;
+#endif
+};
+
+static void free_list(char** list);
+static void free_search_data(search_t* search);
+static bool paths_init(paths_t* paths);
+static char* build_path(paths_t* paths, paths_source_t source, ...);
+static bool create_dir(paths_t* paths, paths_source_t source,
+ const char* addon, size_t addon_len);
+static void* searcher(void* _args);
+static search_t* get_search(searchargs_t* args);
+static void put_search(searchargs_t* args, search_t* search);
+
+paths_t* paths_new(void)
+{
+ paths_t* paths = calloc(1, sizeof(paths_t));
+ if (!paths)
+ {
+ return NULL;
+ }
+ if (!REF_INIT(paths))
+ {
+ free(paths);
+ return NULL;
+ }
+ for (;;)
+ {
+ paths->search = calloc(1, sizeof(searchargs_t));
+ if (!paths->search)
+ {
+ break;
+ }
+ paths->search->lock = thread_lock_new();
+ if (!paths->search->lock)
+ {
+ break;
+ }
+ paths->search->queue = safe_fifo_new();
+ if (!paths->search->queue)
+ {
+ break;
+ }
+ safe_fifo_ref(paths->search->queue);
+#ifdef DEBUG
+ paths->searcher = thread_joinable_new(searcher, paths->search);
+ if (!paths->searcher)
+ {
+ safe_fifo_unref(paths->search->queue);
+ break;
+ }
+#else
+ if (!thread_new(searcher, paths->search))
+ {
+ safe_fifo_unref(paths->search->queue);
+ break;
+ }
+#endif
+ if (!paths_init(paths))
+ {
+ break;
+ }
+ return paths;
+ }
+ paths_unref(paths);
+ return NULL;
+}
+
+void paths_ref(paths_t* paths)
+{
+ assert(paths);
+ REF_INC(paths);
+}
+
+void paths_unref(paths_t* paths)
+{
+ if (paths && REF_DEC(paths))
+ {
+ if (paths->search)
+ {
+ if (paths->search->queue)
+ {
+ search_t* search = get_search(paths->search);
+ paths->search->quit = true;
+ if (search)
+ {
+ memset(search, 0, sizeof(search_t));
+ safe_fifo_push(paths->search->queue, search);
+ }
+ safe_fifo_unref(paths->search->queue);
+ }
+#ifdef DEBUG
+ thread_join(paths->searcher);
+#endif
+ }
+ free(paths->user_data);
+ free(paths->user_config);
+ free(paths->user_cache);
+ free(paths->user_runtime);
+ free_list(paths->search_data);
+ free_list(paths->search_config);
+ REF_FREE(paths);
+ free(paths);
+ }
+}
+
+const char* paths_user_dir(paths_t* paths, paths_source_t source)
+{
+ assert(paths);
+ switch (source)
+ {
+ case PATHS_DATA:
+ return paths->user_data;
+ case PATHS_CONFIG:
+ return paths->user_config;
+ case PATHS_CACHE:
+ return paths->user_cache;
+ case PATHS_RUNTIME:
+ return paths->user_runtime;
+ }
+ assert(false);
+ return NULL;
+}
+
+const char** paths_search_dirs(paths_t* paths, paths_source_t source)
+{
+ assert(paths);
+ switch (source)
+ {
+ case PATHS_DATA:
+ return (const char**)paths->search_data;
+ case PATHS_CONFIG:
+ return (const char**)paths->search_config;
+ case PATHS_CACHE:
+ case PATHS_RUNTIME:
+ return NULL;
+ }
+ assert(false);
+ return NULL;
+}
+
+struct paths_file_t
+{
+ paths_source_t source;
+ char* path, * target;
+ int fd;
+};
+
+paths_file_t* paths_write(paths_t* paths, paths_source_t source,
+ const char* filename, unsigned int flags, ...)
+{
+ paths_file_t* file;
+ int f;
+ const char* subpath;
+ assert(filename);
+ if (source == PATHS_RUNTIME && !paths->user_runtime)
+ {
+ errno = ENOENT;
+ return NULL;
+ }
+ if (filename[0] == '/')
+ {
+ filename++;
+ }
+ subpath = strrchr(filename, '/');
+ if (subpath)
+ {
+ if (!create_dir(paths, source, filename, subpath - filename))
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ if (!create_dir(paths, source, NULL, 0))
+ {
+ return NULL;
+ }
+ }
+ file = calloc(1, sizeof(paths_file_t));
+ if (!file)
+ {
+ return NULL;
+ }
+ file->source = source;
+ if (source == PATHS_CONFIG &&
+ (((flags & PATHS_CREATE) && (flags & PATHS_TRUNCATE)) ||
+ ((flags & PATHS_CREATE) && (flags & PATHS_EXCLUSIVE))))
+ {
+ file->target = build_path(paths, source, filename, NULL);
+ if (!file->target)
+ {
+ free(file);
+ return NULL;
+ }
+ file->path = build_path(paths, source, ".#", filename, NULL);
+ }
+ else
+ {
+ file->path = build_path(paths, source, filename, NULL);
+ }
+ if (!file->path)
+ {
+ free(file->target);
+ free(file);
+ return NULL;
+ }
+ f = O_WRONLY;
+ if ((flags & PATHS_APPEND))
+ {
+ f |= O_APPEND;
+ }
+ if ((flags & PATHS_CREATE))
+ {
+ f |= O_CREAT;
+ if ((flags & PATHS_EXCLUSIVE))
+ {
+ f |= O_EXCL;
+ }
+ }
+ if ((flags & PATHS_TRUNCATE))
+ {
+ f |= O_TRUNC;
+ }
+#ifdef O_EXLOCK
+ f |= O_EXLOCK;
+#endif
+ if ((f & O_CREAT))
+ {
+ va_list args;
+ va_start(args, flags);
+ file->fd = open(file->path, f, va_arg(args, int));
+ va_end(args);
+ }
+ else
+ {
+ file->fd = open(file->path, f);
+ }
+ if (file->fd < 0)
+ {
+ free(file->path);
+ free(file->target);
+ free(file);
+ return NULL;
+ }
+#ifndef O_EXLOCK
+ for (;;)
+ {
+ if (flock(file->fd, LOCK_EX) == 0)
+ {
+ break;
+ }
+ if (errno != EINTR)
+ {
+ if ((flags & O_CREAT))
+ {
+ unlink(file->path);
+ }
+ close(file->fd);
+ free(file->path);
+ free(file->target);
+ free(file);
+ return NULL;
+ }
+ }
+#endif
+ return file;
+}
+
+ssize_t paths_file_write(paths_file_t* file,
+ const void* data, size_t size)
+{
+ size_t pos = 0;
+ assert(file);
+ assert(size == 0 || data);
+ while (pos < size)
+ {
+ ssize_t ret = write(file->fd, data + pos, size - pos);
+ if (ret < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK ||
+ errno == EINTR)
+ {
+ /* Non fatal errors */
+ if (pos > 0)
+ {
+ return pos;
+ }
+ }
+ return -1;
+ }
+ if (ret == 0)
+ {
+ return pos;
+ }
+ pos += ret;
+ }
+ return size;
+}
+
+int paths_file_close(paths_file_t* file)
+{
+ assert(file);
+ if (close(file->fd))
+ {
+ return -1;
+ }
+ file->fd = -1;
+ if (file->target)
+ {
+ size_t len = strlen(file->target);
+ char* tmp = malloc(len + 2);
+ int ret;
+ if (!tmp)
+ {
+ return -1;
+ }
+ memcpy(tmp, file->target, len);
+ memcpy(tmp + len, "~", 2);
+ ret = rename(file->target, tmp);
+ free(tmp);
+ if (ret && errno == ENOENT)
+ {
+ int olderrno = errno;
+ /* Check if rename failed because target doesn't exist */
+ if (access(file->target, F_OK) == 0 ||
+ errno != ENOENT)
+ {
+ /* Unable to create backup */
+ errno = olderrno;
+ return -1;
+ }
+ }
+ if (rename(file->path, file->target))
+ {
+ return -1;
+ }
+ free(file->target);
+ }
+ free(file->path);
+ free(file);
+ return 0;
+}
+
+void paths_file_abort(paths_file_t* file)
+{
+ if (file)
+ {
+ if (file->fd >= 0)
+ {
+ close(file->fd);
+ }
+ unlink(file->path);
+ free(file->path);
+ free(file->target);
+ free(file);
+ }
+}
+
+bool create_dir(paths_t* paths, paths_source_t source, const char* addon,
+ size_t addon_len)
+{
+ const char* dir = paths_user_dir(paths, source);
+ char* tmp = NULL;
+ assert(dir);
+ assert(addon || !addon_len);
+ if (addon_len)
+ {
+ size_t dlen = strlen(dir);
+ tmp = malloc(dlen + 1 + addon_len + 1);
+ if (!tmp)
+ {
+ return false;
+ }
+ memcpy(tmp, dir, dlen);
+ tmp[dlen++] = '/';
+ memcpy(tmp + dlen, addon, addon_len);
+ tmp[dlen + addon_len] = '\0';
+ dir = tmp;
+ }
+ if (mkdir(dir, 0700))
+ {
+ free(tmp);
+ return errno == EEXIST;
+ }
+ free(tmp);
+ return true;
+}
+
+void free_list(char** list)
+{
+ if (list)
+ {
+ char** i;
+ for (i = list; *i; i++)
+ {
+ free(*i);
+ }
+ free(list);
+ }
+}
+
+typedef struct homedir_t
+{
+ char* path;
+ size_t len;
+} homedir_t;
+
+static char* get_userdir(const char* envvar, homedir_t* homedir,
+ const char* addon);
+static char** get_searchdirs(const char* envvar, const char* default_list);
+
+static bool valid_runtime(const char* path);
+
+bool paths_init(paths_t* paths)
+{
+ homedir_t homedir = { NULL, 0 };
+ paths->user_data = get_userdir("XDG_DATA_HOME", &homedir, "/.local/share");
+ paths->user_config = get_userdir("XDG_CONFIG_HOME", &homedir, "/.config");
+ paths->user_cache = get_userdir("XDG_CACHE_HOME", &homedir, "/.cache");
+ paths->user_runtime = get_userdir("XDG_RUNTIME_HOME", NULL, NULL);
+ free(homedir.path);
+ if (!paths->user_data || !paths->user_config || !paths->user_cache)
+ {
+ return false;
+ }
+ if (paths->user_runtime)
+ {
+ if (!valid_runtime(paths->user_runtime))
+ {
+ free(paths->user_runtime);
+ paths->user_runtime = NULL;
+ }
+ }
+ paths->search_data = get_searchdirs("XDG_DATA_DIRS",
+ "/usr/local/share:/usr/share");
+ if (!paths->search_data)
+ {
+ return false;
+ }
+ paths->search_config = get_searchdirs("XDG_CONFIG_DIRS", "/etc/xdg");
+ if (!paths->search_config)
+ {
+ return false;
+ }
+ return true;
+}
+
+static void cleanup_dir(char* path, size_t len)
+{
+ while (len > 1 && path[len - 1] == '/')
+ {
+ path[--len] = '\0';
+ }
+}
+
+static char* get_homedir(void)
+{
+ const char* data = getenv("HOME");
+ size_t len;
+ char* ret;
+ if (data && *data)
+ {
+ len = strlen(data);
+ ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, data, len + 1);
+ cleanup_dir(ret, len);
+ }
+ return ret;
+ }
+ for (;;)
+ {
+ long size = sysconf(_SC_GETPW_R_SIZE_MAX);
+ char* buf;
+ struct passwd entry, *result = NULL;
+ assert(size != -1);
+ if (size == -1)
+ {
+ break;
+ }
+ buf = malloc(size);
+ if (!buf)
+ {
+ break;
+ }
+ if (getpwuid_r(getuid(), &entry, buf, size, &result) != 0)
+ {
+ /* Error getting entry */
+ free(buf);
+ break;
+ }
+ if (!result)
+ {
+ /* No entry found */
+ free(buf);
+ break;
+ }
+ len = strlen(entry.pw_dir);
+ if (len == 0)
+ {
+ free(buf);
+ break;
+ }
+ ret = malloc(len + 1);
+ if (!ret)
+ {
+ free(buf);
+ return NULL;
+ }
+ memcpy(ret, entry.pw_dir, len + 1);
+ free(buf);
+ cleanup_dir(ret, len);
+ return ret;
+ }
+ /* When all else fails */
+ return strdup("/");
+}
+
+char* get_userdir(const char* envvar, homedir_t* homedir, const char* addon)
+{
+ const char* data = getenv(envvar);
+ size_t len;
+ char* ret;
+ assert(!addon || addon[0] == '/');
+ if (data && *data)
+ {
+ len = strlen(data);
+ ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, data, len + 1);
+ cleanup_dir(ret, len);
+ }
+ return ret;
+ }
+ if (!homedir && !addon)
+ {
+ return NULL;
+ }
+ assert(homedir && addon);
+ if (!homedir->path)
+ {
+ homedir->path = get_homedir();
+ if (!homedir->path)
+ {
+ return NULL;
+ }
+ homedir->len = strlen(homedir->path);
+ }
+ len = homedir->len + strlen(addon);
+ ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, homedir->path, homedir->len);
+ if (homedir->len > 1)
+ {
+ assert(homedir->path[homedir->len - 1] != '/');
+ memcpy(ret + homedir->len, addon, (len - homedir->len) + 1);
+ }
+ else
+ {
+ assert(homedir->path[0] == '/');
+ len--;
+ memcpy(ret + homedir->len, addon + 1, len - homedir->len);
+ ret[len] = '\0';
+ }
+ }
+ return ret;
+}
+
+static void cleanup_dirs(char** dirs)
+{
+ char** d;
+ for (d = dirs; *d; d++)
+ {
+ cleanup_dir(*d, strlen(*d));
+ }
+}
+
+static char** splitlist(const char* str)
+{
+ size_t count = 0, size = 2;
+ char** ret = calloc(size + 1, sizeof(char*));
+ const char* last = str, * pos;
+ if (!ret)
+ {
+ return NULL;
+ }
+ for (;;)
+ {
+ size_t len;
+ pos = strchr(last, ':');
+ if (!pos)
+ {
+ pos = last + strlen(last);
+ }
+
+ if (count == size)
+ {
+ size_t ns = size * 2;
+ char** tmp = realloc(ret, (ns + 1) * sizeof(char*));
+ if (!tmp)
+ {
+ free_list(ret);
+ return NULL;
+ }
+ memset(tmp + size, 0, (ns + 1 - size) * sizeof(char*));
+ ret = tmp;
+ size = ns;
+ }
+ len = pos - last;
+ ret[count] = strdup_len(last, len);
+ if (!ret[count])
+ {
+ free_list(ret);
+ return NULL;
+ }
+ count++;
+
+ if (*pos)
+ {
+ last = pos + 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+ assert(!ret[count]);
+ return ret;
+}
+
+char** get_searchdirs(const char* envvar, const char* default_list)
+{
+ const char* data = getenv(envvar);
+ char** ret;
+ bool cleanup;
+ if (data && *data)
+ {
+ ret = splitlist(data);
+ cleanup = true;
+ }
+ else
+ {
+ ret = splitlist(default_list);
+ cleanup = false;
+ }
+ if (!ret)
+ {
+ return NULL;
+ }
+ if (cleanup)
+ {
+ cleanup_dirs(ret);
+ }
+ return ret;
+}
+
+bool valid_runtime(const char* path)
+{
+ struct stat buf;
+ if (stat(path, &buf) != 0)
+ {
+ return false;
+ }
+ if (buf.st_uid != getuid())
+ {
+ return false;
+ }
+ if ((buf.st_mode & 0777) != 0700)
+ {
+ return false;
+ }
+ return true;
+}
+
+char* build_path(paths_t* paths, paths_source_t source, ...)
+{
+ va_list args;
+ bool first = true;
+ dynstr_t* ret;
+ const char* base = paths_user_dir(paths, source), * part;
+ assert(base);
+ ret = dynstr_new_str(base);
+ if (!ret)
+ {
+ return NULL;
+ }
+ va_start(args, source);
+ while ((part = va_arg(args, const char*)))
+ {
+ if (first)
+ {
+ dynstr_appendc(ret, '/');
+ first = false;
+ }
+ dynstr_append(ret, part);
+ }
+ return dynstr_done(ret);
+}
+
+static bool need_glob(char* match)
+{
+ const char* magic_chars = "*?[]{}";
+ char* s;
+ bool need_unescape = false;
+ for (s = match; *s; s++)
+ {
+ if (*s == '\\')
+ {
+ s++;
+ need_unescape = true;
+ }
+ else if (strchr(magic_chars, *s))
+ {
+ return true;
+ }
+ }
+ if (need_unescape)
+ {
+ unescape(match);
+ }
+ return false;
+}
+
+void paths_find(paths_t* paths, paths_source_t source,
+ const char* match, paths_find_callback_t find_callback,
+ void* userdata)
+{
+ search_t* search;
+ assert(paths && match && find_callback);
+ search = get_search(paths->search);
+ search->dir = paths_user_dir(paths, source);
+ search->dirs = paths_search_dirs(paths, source);
+ search->match = strdup(match);
+ search->find_callback = find_callback;
+ search->userdata = userdata;
+ if (!search->match)
+ {
+ find_callback(userdata, NULL);
+ put_search(paths->search, search);
+ return;
+ }
+ if (*search->match == '/')
+ {
+ size_t len = strlen(search->match);
+ memmove(search->match, search->match + 1, len);
+ }
+ if (!*search->match)
+ {
+ find_callback(userdata, NULL);
+ put_search(paths->search, search);
+ return;
+ }
+ search->plain = !need_glob(search->match);
+ if (search->plain)
+ {
+ search->match_name = strrchr(search->match, '/');
+ if (search->match_name)
+ {
+ search->match_name++;
+ }
+ else
+ {
+ search->match_name = search->match;
+ }
+ }
+ safe_fifo_push(paths->search->queue, search);
+}
+
+search_t* get_search(searchargs_t* args)
+{
+ size_t i;
+ thread_lock_lock(args->lock);
+ for (i = 0; i < SEARCH_CACHE_SIZE; i++)
+ {
+ if (args->cache[i])
+ {
+ search_t* ret = args->cache[i];
+ args->cache[i] = NULL;
+ thread_lock_unlock(args->lock);
+ return ret;
+ }
+ }
+ thread_lock_unlock(args->lock);
+ return malloc(sizeof(search_t));
+}
+
+void put_search(searchargs_t* args, search_t* search)
+{
+ size_t i;
+ thread_lock_lock(args->lock);
+ for (i = 0; i < SEARCH_CACHE_SIZE; i++)
+ {
+ if (!args->cache[i])
+ {
+ args->cache[i] = search;
+ free_search_data(search);
+ thread_lock_unlock(args->lock);
+ return;
+ }
+ }
+ thread_lock_unlock(args->lock);
+ free_search_data(search);
+ free(search);
+}
+
+void free_search_data(search_t* search)
+{
+ free(search->match);
+}
+
+static bool search_dir(search_t* search, const char* dir)
+{
+ bool canceled = false;
+ if (search->plain)
+ {
+ DIR* dh;
+ struct dirent* d;
+ char* path = NULL;
+ size_t dirlen = strlen(dir);
+ size_t matchlen = strlen(search->match);
+ if (search->match == search->match_name)
+ {
+ path = malloc(dirlen + 1 + matchlen + 1);
+ if (!path)
+ {
+ return false;
+ }
+ memcpy(path, dir, dirlen + 1);
+ }
+ else
+ {
+ size_t subdirlen = search->match_name - 1 - search->match;
+ matchlen = strlen(search->match_name);
+ path = malloc(dirlen + 1 + subdirlen + 1 + matchlen + 1);
+ assert(search->match_name > search->match);
+ assert(search->match_name[-1] == '/');
+ if (!path)
+ {
+ return false;
+ }
+ memcpy(path, dir, dirlen);
+ path[dirlen++] = '/';
+ memcpy(path + dirlen, search->match, subdirlen);
+ dirlen += subdirlen;
+ path[dirlen] = '\0';
+ }
+ dh = opendir(path);
+ if (!dh)
+ {
+ free(path);
+ return false;
+ }
+ path[dirlen] = '/';
+ while ((d = readdir(dh)))
+ {
+ if (strcmp(d->d_name, search->match_name) == 0)
+ {
+ memcpy(path + dirlen + 1, search->match_name, matchlen + 1);
+ if (!search->find_callback(search->userdata, path))
+ {
+ canceled = true;
+ break;
+ }
+ }
+ }
+ closedir(dh);
+ free(path);
+ }
+ else
+ {
+ glob_t data;
+ dynstr_t* str;
+ char* pattern;
+ str = dynstr_new_str(dir);
+ if (!str)
+ {
+ return false;
+ }
+ if (!dynstr_escape(str, "*?[]{}") || !dynstr_appendc(str, '/') ||
+ !dynstr_append(str, search->match))
+ {
+ dynstr_free(str);
+ return false;
+ }
+ pattern = dynstr_done(str);
+ memset(&data, 0, sizeof(glob_t));
+ if (glob(pattern, GLOB_NOSORT, NULL, &data) == 0)
+ {
+ size_t i;
+ for (i = 0; i < data.gl_pathc; i++)
+ {
+ if (!search->find_callback(search->userdata, data.gl_pathv[i]))
+ {
+ canceled = true;
+ break;
+ }
+ }
+ globfree(&data);
+ }
+ free(pattern);
+ }
+ return canceled;
+}
+
+void* searcher(void* _args)
+{
+ searchargs_t* args = _args;
+ size_t i;
+
+ while (!args->quit)
+ {
+ search_t* search = safe_fifo_pop(args->queue);
+ bool canceled = false;
+ if (!search->match)
+ {
+ free(search);
+ assert(args->quit);
+ break;
+ }
+ do
+ {
+ if (search->dir)
+ {
+ if (args->quit ||
+ (canceled = search_dir(search, search->dir)))
+ {
+ break;
+ }
+ }
+ if (search->dirs)
+ {
+ const char** dir;
+ for (dir = search->dirs; *dir; dir++)
+ {
+ if (args->quit ||
+ (canceled = search_dir(search, *dir)))
+ {
+ break;
+ }
+ }
+ }
+ } while (false);
+ if (!canceled)
+ {
+ search->find_callback(search->userdata, NULL);
+ }
+ put_search(args, search);
+ }
+
+ /* Free all items in the queue */
+ for (;;)
+ {
+ search_t* search = safe_fifo_trypop(args->queue);
+ if (!search)
+ {
+ break;
+ }
+ put_search(args, search);
+ }
+
+ safe_fifo_unref(args->queue);
+ thread_lock_free(args->lock);
+ for (i = 0; i < SEARCH_CACHE_SIZE; i++)
+ {
+ if (args->cache[i])
+ {
+ free_search_data(args->cache[i]);
+ free(args->cache[i]);
+ }
+ }
+ free(args);
+ return NULL;
+}
diff --git a/src/paths.h b/src/paths.h
new file mode 100644
index 0000000..8be56f8
--- /dev/null
+++ b/src/paths.h
@@ -0,0 +1,261 @@
+/**
+ * \file paths.h
+ * XDG Directory Specification implementation
+ */
+
+#ifndef PATHS_H
+#define PATHS_H
+
+/**
+ * Opaque type for paths
+ */
+typedef struct paths_t paths_t;
+
+/**
+ * Opaque type for a currently open file
+ */
+typedef struct paths_file_t paths_file_t;
+
+/**
+ * The different sources or directories if you will that are in the
+ * specification.
+ */
+typedef enum paths_source_t
+{
+ /** directory for data files */
+ PATHS_DATA,
+ /** directory for config files */
+ PATHS_CONFIG,
+ /** directory for cache files */
+ PATHS_CACHE,
+ /** directory for runtime files */
+ PATHS_RUNTIME,
+} paths_source_t;
+
+/** Do not block write calls */
+#define PATHS_NONBLOCK (0x01)
+/** Append on each write */
+#define PATHS_APPEND (0x02)
+/** Create file if needed */
+#define PATHS_CREATE (0x04)
+/** Truncate file when opening */
+#define PATHS_TRUNCATE (0x08)
+/** If PATHS_CREATE is set, give error if file already exists */
+#define PATHS_EXCLUSIVE (0x10)
+
+/**
+ * Create a new paths.
+ * Initializes the list of directories from enviroment variables.
+ * Returned object has a reference count of one.
+ * Threadsafe.
+ * Returns NULL in case of allocation errors.
+ * @return a new paths
+ */
+MALLOC paths_t* paths_new(void);
+
+/**
+ * Increase the reference count for paths.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ */
+NONULL void paths_ref(paths_t* paths);
+
+/**
+ * Decrease the reference count for paths and free if it reached zero.
+ * Threadsafe.
+ * @param paths paths object, may be NULL
+ */
+void paths_unref(paths_t* paths);
+
+/**
+ * Get single directory where user-specific files should be written.
+ * May only return NULL for PATHS_RUNTIME all other sources are guaranteed to
+ * return a non-NULL string. They may however return non-existant paths.
+ * PATHS_RUNTIME will return NULL if no such path was found as it has no
+ * default.
+ * If you wish paths to be created if missing, please use paths_write().
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @param source requested directory
+ * @return absolute path to directory or NULL
+ */
+NONULL const char* paths_user_dir(paths_t* paths, paths_source_t source);
+
+/**
+ * Get preference ordered list of directories to search for files.
+ * All paths are absolute and NULL terminated. The list itself is NULL
+ * terminated and may never be empty. Path returned by paths_user_dir() for
+ * the same source is not included.
+ * Will return NULL for PATHS_CACHE and PATHS_RUNTIME as they are only
+ * user-specific writable directories.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @param source requested directory
+ * @return NULL terminated list of absolute paths to search or NULL
+ */
+NONULL const char** paths_search_dirs(paths_t* paths, paths_source_t source);
+
+/**
+ * Callback called with the result(s) from paths_find(). May be called from
+ * any thread. Return false to cancel the search, true to continue until all
+ * has been found. filename will be NULL when no more matches was found.
+ * The filename pointer is only valid until the callback has returned.
+ * @param userdata userdata given to paths_find()
+ * @param filenamme found file or NULL if no more results
+ * @return true to continue the search, false to not */
+typedef bool (* paths_find_callback_t)(void* userdata, const char* filename);
+
+/**
+ * Asynchronous search for matching files in directories for source.
+ * Returned in preference order, first paths_user_dir() and then
+ * paths_search_dirs().
+ * The callback is always called at least once.
+ * Match may use *, ?, [] and {} glob matches. It may also contain / characters
+ * to search in subdirectories
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @param source requested directory
+ * @param match glob pattern to search with. may not be NULL
+ * @param find_callback callback to call when finding results, may not be NULL
+ * @param userdata argument to callback, may be NULL
+ */
+NONULL_ARGS(1, 3, 4)
+void paths_find(paths_t* paths, paths_source_t source, const char* match,
+ paths_find_callback_t find_callback, void* userdata);
+
+/**
+ * Synchronous setup to create file in directory.
+ * Returns NULL in case of error, see errno for details.
+ * Depending on source and convention, the actual file may not be created with
+ * the given name until paths_file_close().
+ * The file will eventually always end up in paths_user_dir() for directory.
+ * If any part of the target path is missing it will be created if possible.
+ * If flags contains PATHS_CREATE then the 5th argument mode_t mode must be
+ * given, just as for open().
+ * Threadsafe but what happens if two threads at the same time opens the same
+ * target file is up to the platforms locking mechanism.
+ * The returned paths_file_t is not threadsafe.
+ * @param paths paths object, may not be NULL
+ * @param source target directoru
+ * @param filename relative path of target file, may not be NULL
+ * @param flags flags
+ * @param ... mode if PATHS_CREATE is included in flags
+ * @return a file reference or NULL
+ */
+NONULL paths_file_t* paths_write(paths_t* paths, paths_source_t source,
+ const char* filename, unsigned int flags,
+ ...);
+
+/**
+ * Write data to file.
+ * Works just as libc write().
+ * Not threadsafe,
+ * the paths_file_t must only be accessed by one thread at a time.
+ * @param file target file, may not be NULL
+ * @param data data to write, may be NULL if size is zero
+ * @param size the maximum number of bytes to read from data
+ * @return number of bytes written to target
+ */
+NONULL_ARGS(1)
+ssize_t paths_file_write(paths_file_t* file, const void* data, size_t size);
+
+/**
+ * Close and finish saving file.
+ * If the file was saved successfully, the paths_file_t object is freed.
+ * If the save failed in any way, the object is not freed so you must call
+ * paths_file_abort() to finish up (or call paths_file_close() again and
+ * hope for better luck this time).
+ * Not threadsafe,
+ * the paths_file_t must only be accessed by one thread at a time.
+ * @param file target file, may not be NULL
+ * @return zero on success, non-zero in case of error
+ */
+NONULL int paths_file_close(paths_file_t* file);
+
+/**
+ * Abort saving the file. The target file is removed unless the original version
+ * before paths_write() could be restored.
+ * Not threadsafe,
+ * the paths_file_t must only be accessed by one thread at a time.
+ * @param file target file, may be NULL
+ */
+void paths_file_abort(paths_file_t* file);
+
+/**
+ * Get absolute path to directory where user-specific data files should be
+ * written. Returned directory may not exist.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @return absolute path to data directory
+ */
+static inline NONULL const char* paths_user_data(paths_t* paths)
+{
+ return paths_user_dir(paths, PATHS_DATA);
+}
+
+/**
+ * Get absolute path to directory where user-specific config files should be
+ * written. Returned directory may not exist.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @return absolute path to config directory
+ */
+static inline NONULL const char* paths_user_config(paths_t* paths)
+{
+ return paths_user_dir(paths, PATHS_CONFIG);
+}
+
+/**
+ * Get absolute path to directory where user-specific cache files should be
+ * written. Returned directory may not exist.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @return absolute path to cache directory
+ */
+static inline NONULL const char* paths_user_cache(paths_t* paths)
+{
+ return paths_user_dir(paths, PATHS_CACHE);
+}
+
+/**
+ * Get absolute path to directory where user-specific runtime files should be
+ * written.
+ * If not defined in enviroment or not valid NULL is returned.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @return absolute path to runtime directory or NULL
+ */
+static inline NONULL const char* paths_user_runtime(paths_t* paths)
+{
+ return paths_user_dir(paths, PATHS_RUNTIME);
+}
+
+/**
+ * Get preference ordered list of directories to search for data files.
+ * All paths are absolute and NULL terminated. The list itself is NULL
+ * terminated and may never be empty. Path returned by paths_user_data()
+ * is not included.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @return NULL terminated list of absolute paths to search
+ */
+static inline NONULL const char** paths_data(paths_t* paths)
+{
+ return paths_search_dirs(paths, PATHS_DATA);
+}
+
+/**
+ * Get preference ordered list of directories to search for config files.
+ * All paths are absolute and NULL terminated. The list itself is NULL
+ * terminated and may never be empty. Path returned by paths_user_config()
+ * is not included.
+ * Threadsafe.
+ * @param paths paths object, may not be NULL
+ * @return NULL terminated list of absolute paths to search
+ */
+static inline NONULL const char** paths_config(paths_t* paths)
+{
+ return paths_search_dirs(paths, PATHS_CONFIG);
+}
+
+#endif /* PATHS_H */
diff --git a/src/ref.h b/src/ref.h
new file mode 100644
index 0000000..ea36ec7
--- /dev/null
+++ b/src/ref.h
@@ -0,0 +1,95 @@
+/**
+ * \file ref.h
+ * Thread-safe reference counter.
+ */
+
+#ifndef REF_H
+#define REF_H
+
+#if __GNUC__ >= 4
+# define REF_DECLARE() volatile int _ref
+# define REF_INIT(ptr) \
+ ((ptr)->_ref = 1)
+# define REF_FREE(ptr)
+# define REF_INC(ptr) \
+ __sync_add_and_fetch(&((ptr)->_ref), 1)
+# define REF_DEC(ptr) \
+ (__sync_sub_and_fetch(&((ptr)->_ref), 1) <= 0)
+
+#else
+# warning Unsupported compiler, horrible fallback threadsafe reference counters
+
+# include "thread.h"
+
+/**
+ * Declare reference counter. Use in struct declaration.
+ */
+# define REF_DECLARE() int _ref; thread_lock_t* _reflock
+/**
+ * Initialize reference counter. Must be first MACRO used.
+ * @param ptr pointer to the struct, may not be NULL
+ * @return false in case of allocation errors
+ */
+# define REF_INIT(ptr) \
+ _ref_init(&((ptr)->_ref), &((ptr)->_reflock))
+/**
+ * Free reference counter.
+ * @param ptr pointer to the struct, may not be NULL
+ */
+# define REF_FREE(ptr) thread_lock_free((ptr)->_reflock)
+/**
+ * Increase reference counter.
+ * @param ptr pointer to the struct, may not be NULL
+ */
+# define REF_INC(ptr) _ref_inc(&((ptr)->_ref), (ptr)->_reflock)
+/**
+ * Decrease reference counter.
+ * @param ptr pointer to the struct, may not be NULL
+ * @return true if reference counter now is zero
+ */
+# define REF_DEC(ptr) _ref_dec(&((ptr)->_ref), (ptr)->_reflock)
+
+/**
+ * Fallback implementation of REF_INIT.
+ * @param value pointer to reference counter, may not be NULL
+ * @param lock pointer to reference counter lock, may not be NULL
+ * @return true if both pointers was initialized correctly
+ */
+static inline NONULL bool _ref_init(int* value, thread_lock_t** lock)
+{
+ assert(value);
+ assert(lock);
+ *value = 1;
+ *lock = thread_lock_new();
+ return *lock;
+}
+
+/**
+ * Fallback implementation of REF_INC.
+ * @param value pointer to reference counter, may not be NULL
+ * @param lock reference counter lock, may not be NULL
+ */
+static inline NONULL void _ref_inc(int* value, thread_lock_t* lock)
+{
+ thread_lock_lock(lock);
+ (*value)++;
+ thread_lock_unlock(lock);
+}
+
+/**
+ * Fallback implementation of REF_DEC.
+ * @param value pointer to reference counter, may not be NULL
+ * @param lock reference counter lock, may not be NULL
+ * @return true if reference counter was decreased to 0
+ */
+static inline NONULL bool _ref_dec(int* value, thread_lock_t* lock)
+{
+ bool ret;
+ thread_lock_lock(lock);
+ ret = --(*value) == 0;
+ thread_lock_unlock(lock);
+ return ret;
+}
+#endif
+
+#endif /* REF_H */
diff --git a/src/safe_fifo.c b/src/safe_fifo.c
new file mode 100644
index 0000000..cdd634f
--- /dev/null
+++ b/src/safe_fifo.c
@@ -0,0 +1,152 @@
+#include "common.h"
+
+#include "safe_fifo.h"
+#include "thread.h"
+#include "ref.h"
+
+#include <string.h>
+
+struct safe_fifo_t
+{
+ REF_DECLARE();
+ thread_lock_t* lock;
+ thread_cond_t* empty;
+ size_t pos, fill, size;
+ void** data;
+};
+
+safe_fifo_t* safe_fifo_new(void)
+{
+ safe_fifo_t* fifo = calloc(1, sizeof(safe_fifo_t));
+ if (fifo)
+ {
+ if (!REF_INIT(fifo))
+ {
+ free(fifo);
+ return NULL;
+ }
+ fifo->lock = thread_lock_new();
+ fifo->empty = thread_cond_new();
+ if (!fifo->lock || !fifo->empty)
+ {
+ thread_lock_free(fifo->lock);
+ thread_cond_free(fifo->empty);
+ free(fifo);
+ return NULL;
+ }
+ }
+ return fifo;
+}
+
+void safe_fifo_ref(safe_fifo_t* fifo)
+{
+ assert(fifo);
+ REF_INC(fifo);
+}
+
+void safe_fifo_unref(safe_fifo_t* fifo)
+{
+ if (fifo && REF_DEC(fifo))
+ {
+ thread_cond_free(fifo->empty);
+ thread_lock_free(fifo->lock);
+ free(fifo->data);
+ REF_FREE(fifo);
+ free(fifo);
+ }
+}
+
+void safe_fifo_push(safe_fifo_t* fifo, void* item)
+{
+ assert(fifo && item);
+ thread_lock_lock(fifo->lock);
+ do
+ {
+ if (fifo->pos > 0)
+ {
+ fifo->fill -= fifo->pos;
+ memmove(fifo->data, fifo->data + fifo->pos,
+ fifo->fill * sizeof(void*));
+ fifo->pos = 0;
+ }
+ if (fifo->fill == fifo->size)
+ {
+ size_t ns = MAX(4, fifo->size * 2);
+ void** tmp = realloc(fifo->data, ns * sizeof(void*));
+ if (!tmp)
+ {
+ break;
+ }
+ fifo->size = ns;
+ fifo->data = tmp;
+ }
+ fifo->data[fifo->fill++] = item;
+ } while (false);
+ thread_cond_signal(fifo->empty);
+ thread_lock_unlock(fifo->lock);
+}
+
+void* safe_fifo_pop(safe_fifo_t* fifo)
+{
+ void* ret;
+ assert(fifo);
+ thread_lock_lock(fifo->lock);
+ for (;;)
+ {
+ if (fifo->fill > 0)
+ {
+ ret = fifo->data[fifo->pos++];
+ if (fifo->pos == fifo->fill)
+ {
+ fifo->pos = fifo->fill = 0;
+ }
+ break;
+ }
+ thread_cond_wait(fifo->empty, fifo->lock);
+ }
+ thread_lock_unlock(fifo->lock);
+ return ret;
+}
+
+void* safe_fifo_trypop(safe_fifo_t* fifo)
+{
+ void* ret = NULL;
+ assert(fifo);
+ thread_lock_lock(fifo->lock);
+ if (fifo->fill > 0)
+ {
+ ret = fifo->data[fifo->pos++];
+ if (fifo->pos == fifo->fill)
+ {
+ fifo->pos = fifo->fill = 0;
+ }
+ }
+ thread_lock_unlock(fifo->lock);
+ return ret;
+}
+
+void* safe_fifo_timedpop(safe_fifo_t* fifo, const struct timespec* abstime)
+{
+ void* ret;
+ assert(fifo);
+ thread_lock_lock(fifo->lock);
+ for (;;)
+ {
+ if (fifo->fill > 0)
+ {
+ ret = fifo->data[fifo->pos++];
+ if (fifo->pos == fifo->fill)
+ {
+ fifo->pos = fifo->fill = 0;
+ }
+ break;
+ }
+ if (!thread_cond_timedwait(fifo->empty, fifo->lock, abstime))
+ {
+ ret = NULL;
+ break;
+ }
+ }
+ thread_lock_unlock(fifo->lock);
+ return ret;
+}
diff --git a/src/safe_fifo.h b/src/safe_fifo.h
new file mode 100644
index 0000000..4644c33
--- /dev/null
+++ b/src/safe_fifo.h
@@ -0,0 +1,71 @@
+/**
+ * \file safe_fifo.h
+ * Thread-safe FIFO queue.
+ */
+
+#ifndef SAFE_FIFO_H
+#define SAFE_FIFO_H
+
+#include "timespec.h"
+
+/**
+ * Opaque type describing a thread-safe FIFO queue
+ */
+typedef struct safe_fifo_t safe_fifo_t;
+
+/**
+ * Create a new queue.
+ * The new queue will have a reference count of one.
+ * Returns NULL in case of allocation errors.
+ * @return a new queue
+ */
+MALLOC safe_fifo_t* safe_fifo_new(void);
+
+/**
+ * Increase the reference count on the queue.
+ * @param fifo queue, may not be NULL
+ */
+NONULL void safe_fifo_ref(safe_fifo_t* fifo);
+
+/**
+ * Decrease the reference count on the queue and free if it goes down to zero.
+ * @param fifo queue, may be NULL
+ */
+void safe_fifo_unref(safe_fifo_t* fifo);
+
+/**
+ * Push an item on the queue.
+ * Will never block. In case of allocation errors, new items are ignored.
+ * @param fifo queue, may not be NULL
+ * @param item pointer to add to queue, may not be NULL
+ */
+NONULL void safe_fifo_push(safe_fifo_t* fifo, void* item);
+
+/**
+ * Pop an item from the queue.
+ * Will block until there is an item available.
+ * @param fifo queue, may not be NULL
+ * @return oldest item pushed onto the queue
+ */
+NONULL void* safe_fifo_pop(safe_fifo_t* fifo);
+
+/**
+ * Try to pop an item from the queue.
+ * Will not wait until there is an item available.
+ * @param fifo queue, may not be NULL
+ * @return oldest item in queue if any otherwise NULL
+ */
+NONULL void* safe_fifo_trypop(safe_fifo_t* fifo);
+
+/**
+ * Try to pop an item from the queue.
+ * Waits until abstime has occurred before giving up and returning NULL.
+ * Use thread_abstime() to fill out the abstime.
+ * @param fifo queue, may not be NULL
+ * @param abstime absolute time when to give up
+ * @return oldest item in queue if any otherwise NULL
+ */
+NONULL void* safe_fifo_timedpop(safe_fifo_t* fifo,
+ const struct timespec *abstime);
+
+#endif /* SAFE_FIFO_H */
diff --git a/src/strutil.c b/src/strutil.c
new file mode 100644
index 0000000..7d436ec
--- /dev/null
+++ b/src/strutil.c
@@ -0,0 +1,124 @@
+#include "common.h"
+
+#include "strutil.h"
+
+#include <string.h>
+
+char* strdup_len(const char* src, size_t len)
+{
+ char* ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, src, len);
+ ret[len] = '\0';
+ }
+ return ret;
+}
+
+static inline bool escaped(char* start, char* cur)
+{
+ return cur > start && cur[-1] == '\\' && !escaped(start, cur - 1);
+}
+
+void unescape(char* str)
+{
+ char* end = str + strlen(str);
+ char* s;
+ for (s = end - 1; s >= str; s--)
+ {
+ if (*s == '\\' && s < end - 1 && !escaped(str, s))
+ {
+ end--;
+ memmove(s, s + 1, end - s);
+ }
+ }
+ *end = '\0';
+}
+
+char* escape(const char* str, const char* chars)
+{
+ dynstr_t* d = dynstr_new_str(str);
+ if (d)
+ {
+ if (dynstr_escape(d, chars))
+ {
+ return dynstr_done(d);
+ }
+ dynstr_free(d);
+ }
+ return NULL;
+}
+
+bool dynstr_escape(dynstr_t* str, const char* chars)
+{
+ char* s;
+ for (s = str->data + str->len - 1; s >= str->data; s--)
+ {
+ if (*s == '\\' || strchr(chars, *s))
+ {
+ size_t pos = s - str->data;
+ if (!dynstr_appendc(str, '/'))
+ {
+ return false;
+ }
+ s = str->data + pos;
+ memmove(s + 1, s, str->len - (pos + 1));
+ *s = '\\';
+ }
+ }
+ return true;
+}
+
+static void free_list(char** list, size_t count)
+{
+ char** l;
+ for (l = list; count-- > 0; l++)
+ {
+ free(*l);
+ }
+ free(list);
+}
+
+char** split_len(const char* start, const char* end, char delim)
+{
+ char** ret;
+ size_t count = 0, size = 2;
+ assert(start && end);
+ assert(start <= end);
+ ret = malloc((size + 1) * sizeof(char*));
+ if (ret)
+ {
+ const char* last = start;
+ for (;;)
+ {
+ const char* p;
+ for (p = last; p < end && *p != delim; p++);
+ if (count == size)
+ {
+ size_t ns = size * 2;
+ char** tmp = realloc(ret, (ns + 1) * sizeof(char*));
+ if (!tmp)
+ {
+ free_list(ret, count);
+ return NULL;
+ }
+ size = ns;
+ ret = tmp;
+ }
+ ret[count] = strdup_len(last, p - last);
+ if (!ret[count])
+ {
+ free_list(ret, count);
+ return NULL;
+ }
+ count++;
+ if (p == end)
+ {
+ break;
+ }
+ last = p + 1;
+ }
+ ret[count] = NULL;
+ }
+ return ret;
+}
diff --git a/src/strutil.h b/src/strutil.h
new file mode 100644
index 0000000..f4dea54
--- /dev/null
+++ b/src/strutil.h
@@ -0,0 +1,81 @@
+/**
+ * \file strutil.h
+ * A collection of string utilities.
+ */
+
+#ifndef STRUTIL_H
+#define STRUTIL_H
+
+#include "dynstr.h"
+
+#include <string.h>
+
+/**
+ * Allocate a string initialized with the len first bytes of src.
+ * Similar to strndup but it doesn't check for '\0' in src.
+ * Returns NULL in case of allocation errors.
+ * @param src string to copy len bytes from, may not be NULL
+ * @param len bytes to copy from src
+ * @return newly allocate null-terminated string
+ */
+MALLOC NONULL char* strdup_len(const char* src, size_t len);
+
+/**
+ * Unescape (remove backslashes from) a null-terminated string inline.
+ * Does not have any special handling for sequences like \\n, \\t or \\0.
+ * @param str null-terminated string to remove backslashes from, may not be NULL
+ */
+NONULL void unescape(char* str);
+
+/**
+ * Escape all listed characters if they appear in string with backslash.
+ * Also escapes backslash.
+ * Returns NULL in case of allocation errors.
+ * @param str null-terminated string to escape characters in if needed,
+ * may not be NULL
+ * @param chars null-terminated string containing all characters that need to
+ * be escaped, may not be NULL
+ * @return newly allocated null-terminated copy of string with
+ * characters escaped
+ */
+MALLOC NONULL char* escape(const char* str, const char* chars);
+
+/**
+ * Escape all listed characters if they appear in the dynamic string with
+ * backslash inline. Also escapes backslash.
+ * @param str dynamic string to escape characters in if needed,
+ * may not be NULL
+ * @param chars null-terminated string containing all characters that need to
+ * be escaped, may not be NULL
+ * @return false in case of allocation errors
+ */
+NONULL bool dynstr_escape(dynstr_t* str, const char* chars);
+
+/**
+ * Split string by delimiter.
+ * Returned list will always contain at least one item and will be
+ * null-terminated.
+ * Returns NULL in case of allocation errors.
+ * @param start start of string to split by delimiter
+ * @param end end of string to split by delimiter
+ * @param delim delimiter to use
+ * @return null-terminated list of strings or NULL
+ */
+NONULL char** split_len(const char* start, const char* end, char delim);
+
+/**
+ * Split string by delimiter.
+ * Returned list will always contain at least one item and will be
+ * null-terminated.
+ * Returns NULL in case of allocation errors.
+ * @param str string to split by delimiter
+ * @param delim delimiter to use
+ * @return null-terminated list of strings or NULL
+ */
+NONULL static inline char** split(const char* str, char delim)
+{
+ assert(str);
+ return split_len(str, str + strlen(str), delim);
+}
+
+#endif /* STRUTIL_H */
diff --git a/src/thread-pthread.c b/src/thread-pthread.c
new file mode 100644
index 0000000..ad35d77
--- /dev/null
+++ b/src/thread-pthread.c
@@ -0,0 +1,344 @@
+#include "common.h"
+
+#include "thread.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+#if !HAVE_PTHREAD_YIELD
+#include <sched.h>
+#endif
+
+#if !HAVE_CLOCK_GETTIME
+# include <sys/time.h>
+#endif
+
+#ifdef DEBUG
+#define ABORTFAIL(x) \
+ do { \
+ if ((x)) abort(); \
+ } while (false)
+#else
+#define ABORTFAIL(x) (x)
+#endif
+
+bool thread_new(thread_run_t run, void* userdata)
+{
+ pthread_t thread;
+ pthread_attr_t attr;
+ assert(run);
+ if (pthread_attr_init(&attr))
+ {
+ return NULL;
+ }
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
+ {
+ pthread_attr_destroy(&attr);
+ return NULL;
+ }
+ if (pthread_create(&thread, &attr, run, userdata))
+ {
+ pthread_attr_destroy(&attr);
+ return false;
+ }
+ pthread_attr_destroy(&attr);
+ return true;
+}
+
+struct thread_t
+{
+ pthread_t thread;
+};
+
+thread_t* thread_joinable_new(thread_run_t run, void* userdata)
+{
+ thread_t* ret = malloc(sizeof(thread_t));
+ pthread_attr_t attr;
+ assert(run);
+ if (!ret)
+ {
+ return NULL;
+ }
+ if (pthread_attr_init(&attr))
+ {
+ free(ret);
+ return NULL;
+ }
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE))
+ {
+ pthread_attr_destroy(&attr);
+ free(ret);
+ return NULL;
+ }
+ if (pthread_create(&ret->thread, &attr, run, userdata))
+ {
+ pthread_attr_destroy(&attr);
+ free(ret);
+ return NULL;
+ }
+ pthread_attr_destroy(&attr);
+ return ret;
+}
+
+void* thread_join(thread_t* thread)
+{
+ void* ret;
+ assert(thread);
+ ABORTFAIL(pthread_join(thread->thread, &ret));
+ free(thread);
+ return ret;
+}
+
+struct thread_id_t
+{
+ pthread_t thread;
+};
+
+thread_id_t* thread_id_new(thread_t* thread)
+{
+ thread_id_t* id = malloc(sizeof(thread_id_t));
+ if (id)
+ {
+ id->thread = thread ? thread->thread : pthread_self();
+ }
+ return id;
+}
+
+void thread_id_free(thread_id_t* id)
+{
+ free(id);
+}
+
+bool thread_id_is_current(thread_id_t* id)
+{
+ assert(id);
+ return id->thread == pthread_self();
+}
+
+void thread_yield(void)
+{
+#if HAVE_PTHREAD_YIELD
+# if PTHREAD_YIELD_VOID
+ pthread_yield();
+# else
+ ABORTFAIL(pthread_yield());
+# endif
+#else
+ ABORTFAIL(sched_yield());
+#endif
+}
+
+struct thread_lock_t
+{
+ pthread_mutex_t mutex;
+};
+
+thread_lock_t* thread_lock_new(void)
+{
+ thread_lock_t* lock = malloc(sizeof(thread_lock_t));
+ if (!lock)
+ {
+ return lock;
+ }
+ for (;;)
+ {
+#ifdef DEBUG
+ pthread_mutexattr_t attr;
+ if (pthread_mutexattr_init(&attr))
+ {
+ break;
+ }
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ if (pthread_mutex_init(&lock->mutex, &attr))
+ {
+ pthread_mutexattr_destroy(&attr);
+ break;
+ }
+ pthread_mutexattr_destroy(&attr);
+#else
+ if (pthread_mutex_init(&lock->mutex, NULL))
+ {
+ break;
+ }
+#endif
+ return lock;
+ }
+ free(lock);
+ return NULL;
+}
+
+thread_lock_t* thread_lock_recursive_new(void)
+{
+ thread_lock_t* lock = malloc(sizeof(thread_lock_t));
+ for (;;)
+ {
+ if (!lock)
+ {
+ break;
+ }
+ pthread_mutexattr_t attr;
+ if (pthread_mutexattr_init(&attr))
+ {
+ break;
+ }
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ if (pthread_mutex_init(&lock->mutex, &attr))
+ {
+ pthread_mutexattr_destroy(&attr);
+ break;
+ }
+ pthread_mutexattr_destroy(&attr);
+ return lock;
+ }
+ free(lock);
+ return NULL;
+}
+
+void thread_lock_free(thread_lock_t* lock)
+{
+ if (lock)
+ {
+ pthread_mutex_destroy(&lock->mutex);
+ free(lock);
+ }
+}
+
+void thread_lock_lock(thread_lock_t* lock)
+{
+ assert(lock);
+ ABORTFAIL(pthread_mutex_lock(&lock->mutex));
+}
+
+void thread_lock_unlock(thread_lock_t* lock)
+{
+ assert(lock);
+ ABORTFAIL(pthread_mutex_unlock(&lock->mutex));
+}
+
+struct thread_cond_t
+{
+ pthread_cond_t cond;
+};
+
+thread_cond_t* thread_cond_new(void)
+{
+ thread_cond_t* cond = malloc(sizeof(thread_cond_t));
+ if (cond)
+ {
+ if (pthread_cond_init(&cond->cond, NULL))
+ {
+ free(cond);
+ return NULL;
+ }
+ }
+ return cond;
+}
+
+void thread_cond_free(thread_cond_t* cond)
+{
+ if (cond)
+ {
+ pthread_cond_destroy(&cond->cond);
+ free(cond);
+ }
+}
+
+void thread_cond_wait(thread_cond_t* cond, thread_lock_t* lock)
+{
+ assert(cond);
+ assert(lock);
+ ABORTFAIL(pthread_cond_wait(&cond->cond, &lock->mutex));
+}
+
+bool thread_cond_timedwait(thread_cond_t* cond, thread_lock_t* lock,
+ const struct timespec *abstime)
+{
+ int ret;
+ assert(cond);
+ assert(lock);
+ assert(abstime);
+ ret = pthread_cond_timedwait(&cond->cond, &lock->mutex, abstime);
+ if (ret == 0)
+ {
+ return true;
+ }
+#ifdef DEBUG
+ if (ret != ETIMEDOUT)
+ {
+ abort();
+ }
+#endif
+ return false;
+}
+
+void thread_cond_signal(thread_cond_t* cond)
+{
+ assert(cond);
+ ABORTFAIL(pthread_cond_signal(&cond->cond));
+}
+
+void thread_cond_broadcast(thread_cond_t* cond)
+{
+ ABORTFAIL(pthread_cond_broadcast(&cond->cond));
+}
+
+void thread_abstime(struct timespec *abstime, unsigned long add_ms)
+{
+#if HAVE_CLOCK_GETTIME
+ clock_gettime(CLOCK_REALTIME, abstime);
+#else
+ {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ abstime->tv_sec = tv.tv_sec;
+ abstime->tv_nsec = tv.tv_usec * 1000;
+ }
+#endif
+ timespec_addms(abstime, add_ms);
+}
+
+struct thread_data_t
+{
+ pthread_key_t key;
+};
+
+thread_data_t *thread_data_new(thread_data_value_free_t free_value)
+{
+ thread_data_t *data = malloc(sizeof(thread_data_t));
+ if (data)
+ {
+ if (pthread_key_create(&data->key, free_value))
+ {
+ free(data);
+ return NULL;
+ }
+ }
+ return data;
+}
+
+void thread_data_free(thread_data_t *data)
+{
+ if (data)
+ {
+ pthread_key_delete(data->key);
+ free(data);
+ }
+}
+
+void *thread_data_value(thread_data_t *data)
+{
+ assert(data);
+ return pthread_getspecific(data->key);
+}
+
+void thread_data_set(thread_data_t *data, void *value)
+{
+ assert(data);
+ ABORTFAIL(pthread_setspecific(data->key, value));
+}
+
+void thread_once(thread_once_t *once, thread_run_once_t run)
+{
+ assert(once && run);
+ ABORTFAIL(pthread_once(once, run));
+}
diff --git a/src/thread.h b/src/thread.h
new file mode 100644
index 0000000..c0a21b0
--- /dev/null
+++ b/src/thread.h
@@ -0,0 +1,266 @@
+/**
+ * \file thread.h
+ * A collection of thread methods.
+ */
+
+#ifndef THREAD_H
+#define THREAD_H
+
+#include "timespec.h"
+
+#if HAVE_PTHREAD
+#include <pthread.h>
+/**
+ * Opaque type defining an once variable.
+ */
+typedef pthread_once_t thread_once_t;
+/**
+ * Init value for an once variable
+ */
+#define THREAD_ONCE_INIT PTHREAD_ONCE_INIT
+#else
+# error missing thread_once_t implementation
+#endif
+
+/**
+ * Opaque type defining a joinable thread.
+ */
+typedef struct thread_t thread_t;
+/**
+ * Opaque type defining an id.
+ */
+typedef struct thread_id_t thread_id_t;
+/**
+ * Opaque type defining a lock.
+ */
+typedef struct thread_lock_t thread_lock_t;
+/**
+ * Opaque type defining a conditional.
+ */
+typedef struct thread_cond_t thread_cond_t;
+/**
+ * Opaque type defining a thread local variable.
+ */
+typedef struct thread_data_t thread_data_t;
+
+/**
+ * Signature or function type for a thread run method.
+ * A thread run method is the thread main() if you will.
+ * When the method returns the thread is terminated.
+ * @param userdata argument given when starting the thread
+ */
+typedef void* (* thread_run_t)(void* userdata);
+
+/**
+ * Create and start a thread.
+ * @param run thread run method
+ * @param userdata argument to run method, may not be NULL
+ * @return true if thread was started without errors, may be NULL
+ */
+NONULL_ARGS(1)
+bool thread_new(thread_run_t run, void* userdata);
+
+/**
+ * Create and start a joinable thread.
+ * Must call thread_join to free thread resources.
+ * @param run thread run method, may not be NULL
+ * @param userdata argument to run method, may be NULL
+ * @return NULL in case of error
+ */
+NONULL_ARGS(1)
+MALLOC thread_t* thread_joinable_new(thread_run_t run, void* userdata);
+
+/**
+ * Wait for a joinable thread to exit, free it and return the result.
+ * @param thread result from call to thread_joinable_new, may not be NULL
+ * @return result of the thread run method
+ */
+NONULL void* thread_join(thread_t* thread);
+
+
+/**
+ * Create a unique thread id pointing to the given or current thread.
+ * If no thread is given then a id for the current running thread is returned.
+ * Returns NULL in case of error.
+ * @param thread result from call to thread_joinable_new or NULL
+ * @return a new thread id
+ */
+MALLOC thread_id_t* thread_id_new(thread_t* thread);
+
+/**
+ * Free a thread id.
+ * @param id thread id to free, may be NULL
+ */
+void thread_id_free(thread_id_t* id);
+
+/**
+ * Return true if the current thread matches the thread id.
+ * @param id thread id to check, may not be NULL
+ * @return true if the id matches the current thread
+ */
+NONULL bool thread_id_is_current(thread_id_t* id);
+
+/**
+ * Thread yield, give another thread the chance to run.
+ */
+void thread_yield(void);
+
+/**
+ * Create a possibly non-recursive lock (mutex).
+ * Returns NULL in case of error.
+ * @return a new thread lock
+ */
+MALLOC thread_lock_t* thread_lock_new(void);
+
+/**
+ * Create a recursive lock (mutex).
+ * Returns NULL in case of error.
+ * @return a new thread lock
+ */
+MALLOC thread_lock_t* thread_lock_recursive_new(void);
+
+/**
+ * Free a lock.
+ * The lock must not have a owner when freed or undefined behaviour will ensue.
+ * @param lock lock to free, may be NULL
+ */
+void thread_lock_free(thread_lock_t* lock);
+
+/**
+ * Take ownership of the lock.
+ * If another thread has ownership of the lock, wait until that thread releases
+ * the lock.
+ * If the current thread already has ownership of the lock and the lock is
+ * recursive the current thread now owns the lock twice.
+ * Undefined behavior (abort or deadlock probably) if the lock isn't recursive
+ * and the current thread already has ownership.
+ * @param lock lock to take ownership of
+ */
+NONULL void thread_lock_lock(thread_lock_t* lock);
+/**
+ * Release ownership of the lock.
+ * Only call if the current thread has one or more ownerships of the lock.
+ * If the lock is recursive only one ownership is released.
+ * Undefined behaviour (abort or deadlock probably) if the lock isn't owned
+ * by the current thread.
+ * @param lock lock to release
+ */
+NONULL void thread_lock_unlock(thread_lock_t* lock);
+
+/**
+ * Create a conditional. Conditionals can be used to signal other threads
+ * that a condition has been fulfilled if they were waiting for it.
+ * Returns NULL in case of error.
+ * @return new conditional
+ */
+MALLOC thread_cond_t* thread_cond_new(void);
+/**
+ * Free a conditional. No thread may be waiting on the conditional or undefined
+ * behaviour will ensue.
+ * @param cond conditional to free, may be NULL
+ */
+void thread_cond_free(thread_cond_t* cond);
+
+/**
+ * Setup the current thread to wait for the condition to be signalled.
+ * The lock must be owned by the current thread before calling and will be
+ * release while the current thread is waiting but reclaimed upon return.
+ * @param cond conditonal to wait upon, may not be NULL
+ * @param lock lock to release and retake ownership of, may not be NULL
+ */
+NONULL void thread_cond_wait(thread_cond_t* cond, thread_lock_t* lock);
+
+/**
+ * Setup the current thread to wait for the condition to be signalled until
+ * abstime.
+ * The lock must be owned by the current thread before calling and will be
+ * release while the current thread is waiting but reclaimed upon return.
+ * Use thread_abstime() to fill out the abstime.
+ * @param cond conditonal to wait upon, may not be NULL
+ * @param lock lock to release and retake ownership of, may not be NULL
+ * @param abstime time when to give up and return, may not be NULL
+ * @return true if the condition was signalled, false if it timed out
+ */
+NONULL bool thread_cond_timedwait(thread_cond_t* cond, thread_lock_t* lock,
+ const struct timespec *abstime);
+
+/**
+ * Signal one thread that is currently waiting on the given condition.
+ * The selected thread (which one is random) will be woken up.
+ * The calling thread may or may not own the lock when calling
+ * thread_cond_signal but the signalled thread will not wake up before the lock
+ * is released.
+ * @param cond conditional to signal, may not be NULL
+ */
+NONULL void thread_cond_signal(thread_cond_t* cond);
+
+/**
+ * Signal all threads that is currently waiting on the given condition.
+ * All waiting threads will be woken up but only one may get ownership
+ * of the lock.
+ * The calling thread may or may not own the lock when calling
+ * thread_cond_broadcast but the signalled threads will not wake up before the
+ * lock is released.
+ * @param cond conditional to signal, may not be NULL
+ */
+NONULL void thread_cond_broadcast(thread_cond_t* cond);
+
+/**
+ * Get the current absolute time and add a certain number of milliseconds
+ * to it. To be used with thread_cond_timedwait.
+ * @param abstime struct to file with the current absolute time and the
+ * extra milliseconds.
+ * @param add_ms milliseconds to add to the current absolute time
+ */
+NONULL void thread_abstime(struct timespec *abstime, unsigned long add_ms);
+
+/**
+ * Function type for a thread local variable value destructor
+ */
+typedef void (* thread_data_value_free_t)(void *value);
+
+/**
+ * Create a thread local variable.
+ * Returns NULL in case of error.
+ * @param free_value destructor for set non-null value at thread exit,
+ * may be NULL
+ * @return new thread local variable or NULL
+ */
+MALLOC thread_data_t *thread_data_new(thread_data_value_free_t free_value);
+
+/**
+ * Free a thread local variable.
+ * Does not call any destructor.
+ * @param data thread local variable to free, may be NULL
+ */
+void thread_data_free(thread_data_t *data);
+
+/**
+ * Get the thread local value of the variable.
+ * @param data thread local variable, may not be NULL
+ * @return thread local value for variable
+ */
+NONULL void *thread_data_value(thread_data_t *data);
+
+/**
+ * Set thread local value for variable.
+ * @param data thread local variable, may not be NULL
+ * @param value new value, may be NULL
+ */
+NONULL_ARGS(1)
+void thread_data_set(thread_data_t *data, void *value);
+
+/**
+ * Function type for a thread once method
+ */
+typedef void (* thread_run_once_t)(void);
+
+/**
+ * The first thread calling this method will run "run". Any subsequent thread
+ * calling thread_once for the same once variable will do nothing.
+ * @param once address to a static once variable, may not be NULL
+ * @param run method to run if this is the first thread, may not be NULL
+ */
+NONULL void thread_once(thread_once_t *once, thread_run_once_t run);
+
+#endif /* THREAD_H */
diff --git a/src/timespec.c b/src/timespec.c
new file mode 100644
index 0000000..42bc157
--- /dev/null
+++ b/src/timespec.c
@@ -0,0 +1,283 @@
+#include "common.h"
+
+#include "timespec.h"
+
+#if !HAVE_CLOCK_GETTIME
+# include <sys/time.h>
+#endif
+
+#if HAVE_CLOCK_GETTIME
+static bool support_monotonic = true;
+#elif defined(__MACH__)
+#include <mach/mach_time.h>
+static struct mach_timebase_info timebase_info = { 0, 0 };
+
+/*
+ * The methods below to muldiv128 have the listed copyright:
+ *
+ * Copyright (c) 1999, 2003, 2006, 2007 Apple Inc. All rights reserved.
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ */
+
+typedef struct
+{
+ uint64_t high;
+ uint64_t low;
+} uint128_t;
+
+/* acc += add */
+static inline void add128_128(uint128_t *acc, uint128_t *add)
+{
+ acc->high += add->high;
+ acc->low += add->low;
+ if (acc->low < add->low)
+ {
+ acc->high++; // carry
+ }
+}
+
+/* acc -= sub */
+static inline void sub128_128(uint128_t *acc, uint128_t *sub)
+{
+ acc->high -= sub->high;
+ if (acc->low < sub->low)
+ {
+ acc->high--; // borrow
+ }
+ acc->low -= sub->low;
+}
+
+static inline double uint128_double(uint128_t *u)
+{
+ return (((double)(1ULL << 32)) * ((double)(1ULL << 32))) *
+ u->high + u->low; // may loses precision
+ }
+
+/* 64x64 -> 128 bit multiplication */
+static inline void mul64x64(uint64_t x, uint64_t y, uint128_t *prod)
+{
+ uint128_t add;
+ /*
+ * Split the two 64-bit multiplicands into 32-bit parts:
+ * x => 2^32 * x1 + x2
+ * y => 2^32 * y1 + y2
+ */
+ uint32_t x1 = (uint32_t)(x >> 32);
+ uint32_t x2 = (uint32_t)x;
+ uint32_t y1 = (uint32_t)(y >> 32);
+ uint32_t y2 = (uint32_t)y;
+ /*
+ * direct multiplication:
+ * x * y => 2^64 * (x1 * y1) + 2^32 (x1 * y2 + x2 * y1) + (x2 * y2)
+ * The first and last terms are direct assignmenet into the uint128_t
+ * structure. Then we add the middle two terms separately, to avoid
+ * 64-bit overflow. (We could use the Karatsuba algorithm to save
+ * one multiply, but it is harder to deal with 64-bit overflows.)
+ */
+ prod->high = (uint64_t)x1 * (uint64_t)y1;
+ prod->low = (uint64_t)x2 * (uint64_t)y2;
+ add.low = (uint64_t)x1 * (uint64_t)y2;
+ add.high = (add.low >> 32);
+ add.low <<= 32;
+ add128_128(prod, &add);
+ add.low = (uint64_t)x2 * (uint64_t)y1;
+ add.high = (add.low >> 32);
+ add.low <<= 32;
+ add128_128(prod, &add);
+}
+
+/* (x * y / divisor) */
+static uint64_t muldiv128(uint64_t x, uint64_t y, uint64_t divisor)
+{
+ uint128_t temp;
+ uint128_t divisor128 = {0, divisor};
+ uint64_t result = 0;
+ double recip;
+
+ mul64x64(x, y, &temp);
+
+ /*
+ * Now divide by the divisor. We use floating point to calculate an
+ * approximate answer and update the results. Then we iterate and
+ * calculate a correction from the difference.
+ */
+ recip = 1.0 / (double)divisor;
+ while (temp.high || temp.low >= divisor)
+ {
+ uint128_t backmul;
+ uint64_t uapprox;
+
+ uapprox = (uint64_t)(uint128_double(&temp) * recip);
+ mul64x64(uapprox, divisor, &backmul);
+ /*
+ * Because we are using unsigned integers, we need to approach the
+ * answer from the lesser side. So if our estimate is too large
+ * we need to decrease it until it is smaller.
+ */
+ while (backmul.high > temp.high ||
+ (backmul.high == temp.high && backmul.low > temp.low))
+ {
+ sub128_128(&backmul, &divisor128);
+ uapprox--;
+ }
+ sub128_128(&temp, &backmul);
+ result += uapprox;
+ }
+ return result;
+}
+#endif
+
+void timespec_now(struct timespec *ts)
+{
+ assert(ts);
+#if HAVE_CLOCK_GETTIME
+ if (support_monotonic)
+ {
+ if (clock_gettime(CLOCK_MONOTONIC, ts) == 0)
+ {
+ return;
+ }
+ support_monotonic = false;
+ }
+ clock_gettime(CLOCK_REALTIME, ts);
+#elif defined( __MACH__)
+ if (timebase_info.denom == 0)
+ {
+ mach_timebase_info(&timebase_info);
+ }
+ {
+ uint64_t time;
+ if (timebase_info.denom == timebase_info.numer)
+ {
+ time = mach_absolute_time();
+ }
+ else
+ {
+ time = muldiv128(timebase_info.numer, mach_absolute_time(),
+ timebase_info.denom);
+ }
+ ts->tv_sec = time / 1000000000;
+ ts->tv_nsec = time % 1000000000;
+ }
+#else
+ {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = tv.tv_usec * 1000;
+ }
+#endif
+}
+
+void timespec_addms(struct timespec *ts, unsigned long ms)
+{
+ const unsigned int sec = ms / 1000;
+ assert(ts);
+ ms -= sec * 1000;
+ ts->tv_nsec += ms * 1000000;
+ ts->tv_sec += ts->tv_nsec / 1000000000 + sec;
+ ts->tv_nsec = ts->tv_nsec % 1000000000;
+}
+
+void timespec_add(struct timespec *ts, const struct timespec *add)
+{
+ assert(ts && add);
+ ts->tv_nsec += add->tv_nsec;
+ ts->tv_sec += ts->tv_nsec / 1000000000 + add->tv_sec;
+ ts->tv_nsec = ts->tv_nsec % 1000000000;
+}
+
+int timespec_sub(struct timespec *ts, const struct timespec *sub)
+{
+ assert(ts && sub);
+ if (ts->tv_sec < sub->tv_sec)
+ {
+ ts->tv_sec = sub->tv_sec - ts->tv_sec;
+ if (ts->tv_nsec <= sub->tv_nsec)
+ {
+ ts->tv_nsec = sub->tv_nsec - ts->tv_nsec;
+ }
+ else
+ {
+ ts->tv_sec--;
+ ts->tv_nsec = 1000000000 + sub->tv_nsec - ts->tv_nsec;
+ }
+ return -1;
+ }
+ else if (ts->tv_sec > sub->tv_sec)
+ {
+ ts->tv_sec -= sub->tv_sec;
+ if (ts->tv_nsec < sub->tv_nsec)
+ {
+ ts->tv_sec--;
+ ts->tv_nsec = 1000000000 + ts->tv_nsec - sub->tv_nsec;
+ }
+ else
+ {
+ ts->tv_nsec -= sub->tv_nsec;
+ }
+ return 1;
+ }
+ else /* if (ts->tv_sec == sub->tv_sec) */
+ {
+ ts->tv_sec = 0;
+ if (ts->tv_nsec < sub->tv_nsec)
+ {
+ ts->tv_nsec = sub->tv_nsec - ts->tv_nsec;
+ return -1;
+ }
+ else if (ts->tv_nsec > sub->tv_nsec)
+ {
+ ts->tv_nsec -= sub->tv_nsec;
+ return 1;
+ }
+ else
+ {
+ ts->tv_nsec = 0;
+ return 0;
+ }
+ }
+}
+
+int timespec_cmp(const struct timespec *x, const struct timespec *y)
+{
+ assert(x && y);
+ if (x->tv_sec < y->tv_sec)
+ {
+ return -1;
+ }
+ else if (x->tv_sec > y->tv_sec)
+ {
+ return 1;
+ }
+ else /* if (x->tv_sec == y->tv_sec) */
+ {
+ if (x->tv_nsec < y->tv_nsec)
+ {
+ return -1;
+ }
+ else if (x->tv_nsec > y->tv_nsec)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+}
+
diff --git a/src/timespec.h b/src/timespec.h
new file mode 100644
index 0000000..2bf1182
--- /dev/null
+++ b/src/timespec.h
@@ -0,0 +1,26 @@
+/**
+ * \file timespec.h
+ * Declares timespec struct if platform is missing it
+ */
+
+#ifndef TIMESPEC_H
+#define TIMESPEC_H
+
+#include <time.h>
+#if !HAVE_STRUCT_TIMESPEC
+struct timespec
+{
+ time_t tv_sec;
+ long tv_nsec;
+};
+#endif
+
+NONULL void timespec_now(struct timespec *ts);
+
+NONULL void timespec_addms(struct timespec *ts, unsigned long ms);
+NONULL void timespec_add(struct timespec *ts, const struct timespec *add);
+
+NONULL int timespec_sub(struct timespec *ts, const struct timespec *sub);
+NONULL int timespec_cmp(const struct timespec *x, const struct timespec *y);
+
+#endif /* TIMESPEC_H */