diff options
| author | Joel Klinghed <the_jk@opera.com> | 2015-07-13 13:04:24 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@opera.com> | 2015-07-13 13:04:24 +0200 |
| commit | 1d8af5a018282dc6a93b9ed7c87d9d2f87287b14 (patch) | |
| tree | c47c19f7c27fd1774d1455b088eb207c611d30ed /src | |
| parent | 59709e4cb30f2ff8666522d5b758731ab618adbc (diff) | |
Copy the dependencies from sawmill project
Diffstat (limited to 'src')
| -rw-r--r-- | src/.gitignore | 5 | ||||
| -rw-r--r-- | src/Makefile.am | 9 | ||||
| -rw-r--r-- | src/common.h | 3 | ||||
| -rw-r--r-- | src/compiler.h | 42 | ||||
| -rw-r--r-- | src/dynstr.c | 95 | ||||
| -rw-r--r-- | src/dynstr.h | 122 | ||||
| -rw-r--r-- | src/macros.h | 19 | ||||
| -rw-r--r-- | src/main.c | 1 | ||||
| -rw-r--r-- | src/paths.c | 1030 | ||||
| -rw-r--r-- | src/paths.h | 261 | ||||
| -rw-r--r-- | src/ref.h | 95 | ||||
| -rw-r--r-- | src/safe_fifo.c | 152 | ||||
| -rw-r--r-- | src/safe_fifo.h | 71 | ||||
| -rw-r--r-- | src/strutil.c | 124 | ||||
| -rw-r--r-- | src/strutil.h | 81 | ||||
| -rw-r--r-- | src/thread-pthread.c | 344 | ||||
| -rw-r--r-- | src/thread.h | 266 | ||||
| -rw-r--r-- | src/timespec.c | 283 | ||||
| -rw-r--r-- | src/timespec.h | 26 |
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 */ @@ -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 */ |
