diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index bbbb1f6..d59c8b9 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -15,6 +15,8 @@ '-I3rd/c-ares', '-I3rd/libuv/include', '-I3rd/libsodium/src/libsodium/include', +'-I3rd/libcork/include', +'-I3rd/libipset/include', ] compilation_database_folder = '' diff --git a/3rd/c-ares b/3rd/c-ares index bba4dc5..ce3a0ee 160000 --- a/3rd/c-ares +++ b/3rd/c-ares @@ -1 +1 @@ -Subproject commit bba4dc573f5c31b0145d09aa2c245e94896cf1df +Subproject commit ce3a0ee056d0f52c904dfe27784809903648acc0 diff --git a/3rd/libcork/include/cli.h b/3rd/libcork/include/cli.h new file mode 100644 index 0000000..b5d20c1 --- /dev/null +++ b/3rd/libcork/include/cli.h @@ -0,0 +1,18 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CLI_H +#define LIBCORK_CLI_H + +/*** include all of the parts ***/ + +#include + +#endif /* LIBCORK_CLI_H */ diff --git a/3rd/libcork/include/cli/commands.h b/3rd/libcork/include/cli/commands.h new file mode 100644 index 0000000..1569a74 --- /dev/null +++ b/3rd/libcork/include/cli/commands.h @@ -0,0 +1,61 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_COMMANDS_H +#define LIBCORK_COMMANDS_H + +#include + + +typedef void +(*cork_leaf_command_run)(int argc, char **argv); + +typedef int +(*cork_option_parser)(int argc, char **argv); + +enum cork_command_type { + CORK_COMMAND_SET, + CORK_LEAF_COMMAND +}; + +struct cork_command { + enum cork_command_type type; + const char *name; + const char *short_desc; + const char *usage_suffix; + const char *full_help; + + int + (*parse_options)(int argc, char **argv); + + struct cork_command **set; + cork_leaf_command_run run; +}; + +#define cork_command_set(name, sd, parse_options, set) \ +{ \ + CORK_COMMAND_SET, name, sd, NULL, NULL, \ + parse_options, set, NULL \ +} + +#define cork_leaf_command(name, sd, us, fh, parse_options, run) \ +{ \ + CORK_LEAF_COMMAND, name, sd, us, fh, \ + parse_options, NULL, run \ +} + +CORK_API void +cork_command_show_help(struct cork_command *command, const char *message); + +CORK_API int +cork_command_main(struct cork_command *root, int argc, char **argv); + + +#endif /* LIBCORK_COMMANDS_H */ diff --git a/3rd/libcork/include/config.h b/3rd/libcork/include/config.h new file mode 100644 index 0000000..72eb4fb --- /dev/null +++ b/3rd/libcork/include/config.h @@ -0,0 +1,18 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_H +#define LIBCORK_CONFIG_H + +/*** include all of the parts ***/ + +#include + +#endif /* LIBCORK_CONFIG_H */ diff --git a/3rd/libcork/include/config/arch.h b/3rd/libcork/include/config/arch.h new file mode 100644 index 0000000..eaed991 --- /dev/null +++ b/3rd/libcork/include/config/arch.h @@ -0,0 +1,45 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_ARCH_H +#define LIBCORK_CONFIG_ARCH_H + + +/*----------------------------------------------------------------------- + * Platform + */ + +#if defined(__i386__) || defined(_M_IX86) +#define CORK_CONFIG_ARCH_X86 1 +#else +#define CORK_CONFIG_ARCH_X86 0 +#endif + +#if defined(__x86_64__) || defined(_M_X64) +#define CORK_CONFIG_ARCH_X64 1 +#else +#define CORK_CONFIG_ARCH_X64 0 +#endif + +#if defined(__powerpc__) || defined(__ppc__) +/* GCC-ish compiler */ +#define CORK_CONFIG_ARCH_PPC 1 +#elif defined(_M_PPC) +/* VS-ish compiler */ +#define CORK_CONFIG_ARCH_PPC 1 +#elif defined(_ARCH_PPC) +/* Something called XL C/C++? */ +#define CORK_CONFIG_ARCH_PPC 1 +#else +#define CORK_CONFIG_ARCH_PPC 0 +#endif + + +#endif /* LIBCORK_CONFIG_ARCH_H */ diff --git a/3rd/libcork/include/config/bsd.h b/3rd/libcork/include/config/bsd.h new file mode 100644 index 0000000..4c89c07 --- /dev/null +++ b/3rd/libcork/include/config/bsd.h @@ -0,0 +1,34 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_BSD_H +#define LIBCORK_CONFIG_BSD_H + +/*----------------------------------------------------------------------- + * Endianness + */ + +#include + +#if BYTE_ORDER == BIG_ENDIAN +#define CORK_CONFIG_IS_BIG_ENDIAN 1 +#define CORK_CONFIG_IS_LITTLE_ENDIAN 0 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define CORK_CONFIG_IS_BIG_ENDIAN 0 +#define CORK_CONFIG_IS_LITTLE_ENDIAN 1 +#else +#error "Cannot determine system endianness" +#endif + +#define CORK_HAVE_REALLOCF 1 +#define CORK_HAVE_PTHREADS 1 + + +#endif /* LIBCORK_CONFIG_BSD_H */ diff --git a/3rd/libcork/include/config/config.h b/3rd/libcork/include/config/config.h new file mode 100644 index 0000000..1cf8f2f --- /dev/null +++ b/3rd/libcork/include/config/config.h @@ -0,0 +1,74 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_CONFIG_H +#define LIBCORK_CONFIG_CONFIG_H + + +/* If you want to skip autodetection, define this to 1, and provide a + * libcork/config/custom.h header file. */ + +#if !defined(CORK_CONFIG_SKIP_AUTODETECT) +#define CORK_CONFIG_SKIP_AUTODETECT 0 +#endif + + +#if CORK_CONFIG_SKIP_AUTODETECT +/* The user has promised that they'll define everything themselves. */ +#include + +#else +/* Otherwise autodetect! */ + + +/**** VERSION ****/ + +#include + + +/**** ARCHITECTURES ****/ + +#include + + +/**** PLATFORMS ****/ +#if (defined(__unix__) || defined(unix)) && !defined(USG) +/* We need this to test for BSD, but it's a good idea to have for + * any brand of Unix.*/ +#include +#endif + +#if defined(__linux) +/* Do some Linux-specific autodetection. */ +#include + +#elif defined(__APPLE__) && defined(__MACH__) +/* Do some Mac OS X-specific autodetection. */ +#include + +#elif defined(BSD) && (BSD >= 199103) +/* Do some BSD (4.3 code base or newer)specific autodetection. */ +#include + +#endif /* platforms */ + + +/**** COMPILERS ****/ + +#if defined(__GNUC__) +/* Do some GCC-specific autodetection. */ +#include + +#endif /* compilers */ + + +#endif /* autodetect or not */ + + +#endif /* LIBCORK_CONFIG_CONFIG_H */ diff --git a/3rd/libcork/include/config/gcc.h b/3rd/libcork/include/config/gcc.h new file mode 100644 index 0000000..d7bbffa --- /dev/null +++ b/3rd/libcork/include/config/gcc.h @@ -0,0 +1,91 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_GCC_H +#define LIBCORK_CONFIG_GCC_H + +/* Figure out the GCC version */ + +#if defined(__GNUC_PATCHLEVEL__) +#define CORK_CONFIG_GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100 \ + + __GNUC_PATCHLEVEL__) +#else +#define CORK_CONFIG_GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100) +#endif + + +/*----------------------------------------------------------------------- + * Compiler attributes + */ + +/* The GCC assembly syntax has been available basically forever. */ + +#if defined(CORK_CONFIG_GCC_VERSION) +#define CORK_CONFIG_HAVE_GCC_ASM 1 +#else +#define CORK_CONFIG_HAVE_GCC_ASM 0 +#endif + +/* The GCC atomic instrinsics are available as of GCC 4.1.0. */ + +#if CORK_CONFIG_GCC_VERSION >= 40100 +#define CORK_CONFIG_HAVE_GCC_ATOMICS 1 +#else +#define CORK_CONFIG_HAVE_GCC_ATOMICS 0 +#endif + +/* The attributes we want to use are available as of GCC 2.96. */ + +#if CORK_CONFIG_GCC_VERSION >= 29600 +#define CORK_CONFIG_HAVE_GCC_ATTRIBUTES 1 +#else +#define CORK_CONFIG_HAVE_GCC_ATTRIBUTES 0 +#endif + +/* __int128 seems to be available on 64-bit platforms as of GCC 4.6. The + * attribute((mode(TI))) syntax seems to be available as of 4.1. */ + +#if CORK_CONFIG_ARCH_X64 && CORK_CONFIG_GCC_VERSION >= 40600 +#define CORK_CONFIG_HAVE_GCC_INT128 1 +#else +#define CORK_CONFIG_HAVE_GCC_INT128 0 +#endif + +#if CORK_CONFIG_ARCH_X64 && CORK_CONFIG_GCC_VERSION >= 40100 +#define CORK_CONFIG_HAVE_GCC_MODE_ATTRIBUTE 1 +#else +#define CORK_CONFIG_HAVE_GCC_MODE_ATTRIBUTE 0 +#endif + +/* Statement expressions have been available since GCC 3.1. */ + +#if CORK_CONFIG_GCC_VERSION >= 30100 +#define CORK_CONFIG_HAVE_GCC_STATEMENT_EXPRS 1 +#else +#define CORK_CONFIG_HAVE_GCC_STATEMENT_EXPRS 0 +#endif + +/* Thread-local storage has been available since GCC 3.3, but not on Mac + * OS X. */ + +#if !(defined(__APPLE__) && defined(__MACH__)) +#if CORK_CONFIG_GCC_VERSION >= 30300 +#define CORK_CONFIG_HAVE_THREAD_STORAGE_CLASS 1 +#else +#define CORK_CONFIG_HAVE_THREAD_STORAGE_CLASS 0 +#endif +#else +#define CORK_CONFIG_HAVE_THREAD_STORAGE_CLASS 0 +#endif + + +#endif /* LIBCORK_CONFIG_GCC_H */ diff --git a/3rd/libcork/include/config/linux.h b/3rd/libcork/include/config/linux.h new file mode 100644 index 0000000..535924d --- /dev/null +++ b/3rd/libcork/include/config/linux.h @@ -0,0 +1,34 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_LINUX_H +#define LIBCORK_CONFIG_LINUX_H + +/*----------------------------------------------------------------------- + * Endianness + */ + +#include + +#if __BYTE_ORDER == __BIG_ENDIAN +#define CORK_CONFIG_IS_BIG_ENDIAN 1 +#define CORK_CONFIG_IS_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __LITTLE_ENDIAN +#define CORK_CONFIG_IS_BIG_ENDIAN 0 +#define CORK_CONFIG_IS_LITTLE_ENDIAN 1 +#else +#error "Cannot determine system endianness" +#endif + +#define CORK_HAVE_REALLOCF 0 +#define CORK_HAVE_PTHREADS 1 + + +#endif /* LIBCORK_CONFIG_LINUX_H */ diff --git a/3rd/libcork/include/config/macosx.h b/3rd/libcork/include/config/macosx.h new file mode 100644 index 0000000..aa50755 --- /dev/null +++ b/3rd/libcork/include/config/macosx.h @@ -0,0 +1,34 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_MACOSX_H +#define LIBCORK_CONFIG_MACOSX_H + +/*----------------------------------------------------------------------- + * Endianness + */ + +#include + +#if BYTE_ORDER == BIG_ENDIAN +#define CORK_CONFIG_IS_BIG_ENDIAN 1 +#define CORK_CONFIG_IS_LITTLE_ENDIAN 0 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define CORK_CONFIG_IS_BIG_ENDIAN 0 +#define CORK_CONFIG_IS_LITTLE_ENDIAN 1 +#else +#error "Cannot determine system endianness" +#endif + +#define CORK_HAVE_REALLOCF 1 +#define CORK_HAVE_PTHREADS 1 + + +#endif /* LIBCORK_CONFIG_MACOSX_H */ diff --git a/3rd/libcork/include/config/version.h.in b/3rd/libcork/include/config/version.h.in new file mode 100644 index 0000000..9a2eee4 --- /dev/null +++ b/3rd/libcork/include/config/version.h.in @@ -0,0 +1,25 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CONFIG_VERSION_H +#define LIBCORK_CONFIG_VERSION_H + + +/*----------------------------------------------------------------------- + * Library version + */ + +#define CORK_CONFIG_VERSION_MAJOR @VERSION_MAJOR@ +#define CORK_CONFIG_VERSION_MINOR @VERSION_MINOR@ +#define CORK_CONFIG_VERSION_PATCH @VERSION_PATCH@ +#define CORK_CONFIG_VERSION_STRING "@VERSION@" +#define CORK_CONFIG_REVISION "@GIT_SHA1@" + + +#endif /* LIBCORK_CONFIG_VERSION_H */ diff --git a/3rd/libcork/include/core.h b/3rd/libcork/include/core.h new file mode 100644 index 0000000..083e18f --- /dev/null +++ b/3rd/libcork/include/core.h @@ -0,0 +1,29 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_H +#define LIBCORK_CORE_H + +/*** include all of the parts ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* LIBCORK_CORE_H */ diff --git a/3rd/libcork/include/core/allocator.h b/3rd/libcork/include/core/allocator.h new file mode 100644 index 0000000..11fc0f0 --- /dev/null +++ b/3rd/libcork/include/core/allocator.h @@ -0,0 +1,409 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_ALLOCATOR_H +#define LIBCORK_CORE_ALLOCATOR_H + +#include +#include + +#include +#include +#include +#include +#include + + +/*----------------------------------------------------------------------- + * Allocator interface + */ + +struct cork_alloc; + +typedef void * +(*cork_alloc_calloc_f)(const struct cork_alloc *alloc, + size_t count, size_t size); + +typedef void * +(*cork_alloc_malloc_f)(const struct cork_alloc *alloc, size_t size); + +/* Must not free `ptr` if allocation fails. */ +typedef void * +(*cork_alloc_realloc_f)(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size); + +typedef void +(*cork_alloc_free_f)(const struct cork_alloc *alloc, void *ptr, size_t size); + +struct cork_alloc { + const struct cork_alloc *parent; + void *user_data; + cork_free_f free_user_data; + cork_alloc_calloc_f calloc; + cork_alloc_malloc_f malloc; + cork_alloc_realloc_f realloc; + cork_alloc_calloc_f xcalloc; + cork_alloc_malloc_f xmalloc; + cork_alloc_realloc_f xrealloc; + cork_alloc_free_f free; +}; + +/* NOT thread-safe; must be called before most other libcork functions. + * Allocator will automatically be freed at process exit. */ +CORK_API struct cork_alloc * +cork_alloc_new_alloc(const struct cork_alloc *parent); + + +CORK_API void +cork_alloc_set_user_data(struct cork_alloc *alloc, + void *user_data, cork_free_f free_user_data); + +/* These variants must always return a valid pointer. If allocation fails, they + * should abort the process or transfer control in some other way to an error + * handler or cleanup routine. + * + * If you only provide implementations of the `x` variants, we'll provide + * default implementations of these that abort the process if a memory + * allocation fails. */ + +CORK_API void +cork_alloc_set_calloc(struct cork_alloc *alloc, cork_alloc_calloc_f calloc); + +CORK_API void +cork_alloc_set_malloc(struct cork_alloc *alloc, cork_alloc_malloc_f malloc); + +CORK_API void +cork_alloc_set_realloc(struct cork_alloc *alloc, cork_alloc_realloc_f realloc); + +/* These variants can return a NULL pointer if allocation fails. */ + +CORK_API void +cork_alloc_set_xcalloc(struct cork_alloc *alloc, cork_alloc_calloc_f xcalloc); + +CORK_API void +cork_alloc_set_xmalloc(struct cork_alloc *alloc, cork_alloc_malloc_f xmalloc); + +CORK_API void +cork_alloc_set_xrealloc(struct cork_alloc *alloc, + cork_alloc_realloc_f xrealloc); + + +CORK_API void +cork_alloc_set_free(struct cork_alloc *alloc, cork_alloc_free_f free); + + +/* Low-level use of an allocator. */ + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_calloc(const struct cork_alloc *alloc, size_t count, size_t size) +{ + return alloc->calloc(alloc, count, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_malloc(const struct cork_alloc *alloc, size_t size) +{ + return alloc->malloc(alloc, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_realloc(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + return alloc->realloc(alloc, ptr, old_size, new_size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_xcalloc(const struct cork_alloc *alloc, size_t count, size_t size) +{ + return alloc->xcalloc(alloc, count, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_xmalloc(const struct cork_alloc *alloc, size_t size) +{ + return alloc->xmalloc(alloc, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_xrealloc(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + return alloc->xrealloc(alloc, ptr, old_size, new_size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_alloc_xreallocf(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + void *result = alloc->xrealloc(alloc, ptr, old_size, new_size); + if (result == NULL) { + alloc->free(alloc, ptr, old_size); + return NULL; + } else { + return result; + } +} + +CORK_ATTR_UNUSED +static void +cork_alloc_free(const struct cork_alloc *alloc, void *ptr, size_t size) +{ + return alloc->free(alloc, ptr, size); +} + +CORK_ATTR_UNUSED +static void +cork_alloc_cfree(const struct cork_alloc *alloc, void *ptr, + size_t count, size_t size) +{ + assert(count < (SIZE_MAX / size)); + return alloc->free(alloc, ptr, count * size); +} + +#define cork_alloc_new(alloc, type) \ + cork_alloc_malloc((alloc), sizeof(type)) +#define cork_alloc_xnew(alloc, type) \ + cork_alloc_xmalloc((alloc), sizeof(type)) +#define cork_alloc_delete(alloc, type, ptr) \ + cork_alloc_free((alloc), (ptr), sizeof(type)) + +/* string-related helper functions */ + +CORK_ATTR_MALLOC +CORK_API const char * +cork_alloc_strdup(const struct cork_alloc *alloc, const char *str); + +CORK_ATTR_MALLOC +CORK_API const char * +cork_alloc_strndup(const struct cork_alloc *alloc, + const char *str, size_t size); + +CORK_ATTR_MALLOC +CORK_API const char * +cork_alloc_xstrdup(const struct cork_alloc *alloc, const char *str); + +CORK_ATTR_MALLOC +CORK_API const char * +cork_alloc_xstrndup(const struct cork_alloc *alloc, + const char *str, size_t size); + +CORK_API void +cork_alloc_strfree(const struct cork_alloc *alloc, const char *str); + + +/*----------------------------------------------------------------------- + * Using the allocator interface + */ + +/* All of the functions that you use to actually allocate memory assume that + * cork_current_allocator() returns the allocator instance that should be used. + * Your easiest approach is to do nothing special; in that case, all of the + * libcork memory allocation functions will transparently use the standard + * malloc/free family of functions. + * + * If you're writing a library, and want to allow your library clients to + * provide a separate custom memory allocator then the one they can already + * override for libcork itself, you should declare a pair of functions for + * getting and setting your library's current allocator (like libcork itself + * does), and (only when compiling the source of your library) define + * `cork_current_allocator` as a macro that aliases the getter function. That + * will cause the libcork memory allocation functions to use whichever allocator + * your library user has provided. + * + * If you're writing an application, and want to provide a single allocator that + * all libcork-using libraries will pick up, just call cork_set_allocator before + * calling any other library functions. Other libraries will use this as a + * default and everything that uses libcork's memory allocation functions will + * use your custom allocator. */ + + +/* libcork's current allocator */ + +extern const struct cork_alloc *cork_allocator; + +/* We take control and will free when the process exits. This is *NOT* + * thread-safe; it's only safe to call before you've called *ANY* other libcork + * function (or any function from any other library that uses libcork). You can + * only call this at most once. */ +CORK_API void +cork_set_allocator(const struct cork_alloc *alloc); + + +/* The current allocator of whichever library is being compiled. */ + +#if !defined(cork_current_allocator) +#define cork_current_allocator() (cork_allocator) +#endif + + +/* using an allocator */ + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_calloc(size_t count, size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_calloc(alloc, count, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_malloc(size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_malloc(alloc, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_realloc(void *ptr, size_t old_size, size_t new_size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_realloc(alloc, ptr, old_size, new_size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_xcalloc(size_t count, size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_xcalloc(alloc, count, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_xmalloc(size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_xmalloc(alloc, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_xrealloc(void *ptr, size_t old_size, size_t new_size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_xrealloc(alloc, ptr, old_size, new_size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static void * +cork_xreallocf(void *ptr, size_t old_size, size_t new_size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_xreallocf(alloc, ptr, old_size, new_size); +} + +CORK_ATTR_UNUSED +static void +cork_free(void *ptr, size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + cork_alloc_free(alloc, ptr, size); +} + +CORK_ATTR_UNUSED +static void +cork_cfree(void *ptr, size_t count, size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + cork_alloc_cfree(alloc, ptr, count, size); +} + +#define cork_new(type) cork_malloc(sizeof(type)) +#define cork_xnew(type) cork_xmalloc(sizeof(type)) +#define cork_delete(type, ptr) cork_free((ptr), sizeof(type)) + + +/* string-related helper functions */ + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static const char * +cork_strdup(const char *str) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_strdup(alloc, str); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static const char * +cork_strndup(const char *str, size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_strndup(alloc, str, size); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static const char * +cork_xstrdup(const char *str) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_xstrdup(alloc, str); +} + +CORK_ATTR_MALLOC +CORK_ATTR_UNUSED +static const char * +cork_xstrndup(const char *str, size_t size) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_xstrndup(alloc, str, size); +} + +CORK_ATTR_UNUSED +static void +cork_strfree(const char *str) +{ + const struct cork_alloc *alloc = cork_current_allocator(); + return cork_alloc_strfree(alloc, str); +} + + +/*----------------------------------------------------------------------- + * Debugging allocator + */ + +/* An allocator that adds some additional debugging checks: + * + * - We verify that every "free" call (cork_free, cork_cfree, cork_delete, + * cork_realloc) is passed the "correct" size — i.e., the same size that was + * passed in to the correspond "new" call (cork_malloc, cork_calloc, + * cork_realloc, cork_new). + */ + +struct cork_alloc * +cork_debug_alloc_new(const struct cork_alloc *parent); + + +#endif /* LIBCORK_CORE_ALLOCATOR_H */ diff --git a/3rd/libcork/include/core/api.h b/3rd/libcork/include/core/api.h new file mode 100644 index 0000000..0d3c365 --- /dev/null +++ b/3rd/libcork/include/core/api.h @@ -0,0 +1,56 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_API_H +#define LIBCORK_CORE_API_H + +#include +#include + + +/*----------------------------------------------------------------------- + * Calling conventions + */ + +/* If you're using libcork as a shared library, you don't need to do anything + * special; the following will automatically set things up so that libcork's + * public symbols are imported from the library. When we build the shared + * library, we define this ourselves to export the symbols. */ + +#if !defined(CORK_API) +#define CORK_API CORK_IMPORT +#endif + + +/*----------------------------------------------------------------------- + * Library version + */ + +#define CORK_VERSION_MAJOR CORK_CONFIG_VERSION_MAJOR +#define CORK_VERSION_MINOR CORK_CONFIG_VERSION_MINOR +#define CORK_VERSION_PATCH CORK_CONFIG_VERSION_PATCH + +#define CORK_MAKE_VERSION(major, minor, patch) \ + ((major * 1000000) + (minor * 1000) + patch) + +#define CORK_VERSION \ + CORK_MAKE_VERSION(CORK_VERSION_MAJOR, \ + CORK_VERSION_MINOR, \ + CORK_VERSION_PATCH) + +CORK_API const char * +cork_version_string(void) + CORK_ATTR_CONST; + +CORK_API const char * +cork_revision_string(void) + CORK_ATTR_CONST; + + +#endif /* LIBCORK_CORE_API_H */ diff --git a/3rd/libcork/include/core/attributes.h b/3rd/libcork/include/core/attributes.h new file mode 100644 index 0000000..60caa3c --- /dev/null +++ b/3rd/libcork/include/core/attributes.h @@ -0,0 +1,172 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_ATTRIBUTES_H +#define LIBCORK_CORE_ATTRIBUTES_H + +#include + + +/* + * Declare a “const” function. + * + * A const function is one whose return value depends only on its + * parameters. This is slightly more strict than a “pure” function; a + * const function is not allowed to read from global variables, whereas + * a pure function is. + * + * int square(int x) CORK_ATTR_CONST; + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_CONST __attribute__((const)) +#else +#define CORK_ATTR_CONST +#endif + + +/* + * Declare a “pure” function. + * + * A pure function is one whose return value depends only on its + * parameters, and global variables. + * + * int square(int x) CORK_ATTR_PURE; + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_PURE __attribute__((pure)) +#else +#define CORK_ATTR_PURE +#endif + + +/* + * Declare that a function returns a newly allocated pointer. + * + * The compiler can use this information to generate more accurate + * aliasing information, since it can infer that the result of the + * function cannot alias any other existing pointer. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_MALLOC __attribute__((malloc)) +#else +#define CORK_ATTR_MALLOC +#endif + + +/* + * Declare that a function shouldn't be inlined. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_NOINLINE __attribute__((noinline)) +#else +#define CORK_ATTR_NOINLINE +#endif + + +/* + * Declare an entity that isn't used. + * + * This lets you keep -Wall activated in several cases where you're + * obligated to define something that you don't intend to use. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_UNUSED __attribute__((unused)) +#else +#define CORK_ATTR_UNUSED +#endif + + +/* + * Declare a function that takes in printf-like parameters. + * + * When the compiler supports this attribute, it will check the format + * string, and the following arguments, to make sure that they match. + * format_index and args_index are 1-based. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_PRINTF(format_index, args_index) \ + __attribute__((format(printf, format_index, args_index))) +#else +#define CORK_ATTR_PRINTF(format_index, args_index) +#endif + + +/* + * Declare a var-arg function whose last parameter must be a NULL + * sentinel value. + * + * When the compiler supports this attribute, it will check the actual + * parameters whenever this function is called, and ensure that the last + * parameter is a @c NULL. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_ATTR_SENTINEL __attribute__((sentinel)) +#else +#define CORK_ATTR_SENTINEL +#endif + + +/* + * Declare that a boolean expression is likely to be true or false. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_LIKELY(expr) __builtin_expect((expr), 1) +#define CORK_UNLIKELY(expr) __builtin_expect((expr), 0) +#else +#define CORK_LIKELY(expr) (expr) +#define CORK_UNLIKELY(expr) (expr) +#endif + +/* + * Declare that a function is part of the current library's public API, or that + * it's internal to the current library. + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_EXPORT __attribute__((visibility("default"))) +#define CORK_IMPORT __attribute__((visibility("default"))) +#define CORK_LOCAL __attribute__((visibility("hidden"))) +#else +#define CORK_EXPORT +#define CORK_IMPORT +#define CORK_LOCAL +#endif + + +/* + * Declare a static function that should automatically be called at program + * startup. + */ + +/* TODO: When we implement a full Windows port, [1] describes how best to + * implement an initialization function under Visual Studio. + * + * [1] http://stackoverflow.com/questions/1113409/attribute-constructor-equivalent-in-vc + */ + +#if CORK_CONFIG_HAVE_GCC_ATTRIBUTES +#define CORK_INITIALIZER(name) \ +__attribute__((constructor)) \ +static void \ +name(void) +#else +#error "Don't know how to implement initialization functions of this platform" +#endif + + +#endif /* LIBCORK_CORE_ATTRIBUTES_H */ diff --git a/3rd/libcork/include/core/byte-order.h b/3rd/libcork/include/core/byte-order.h new file mode 100644 index 0000000..6761faa --- /dev/null +++ b/3rd/libcork/include/core/byte-order.h @@ -0,0 +1,186 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_BYTE_ORDER_H +#define LIBCORK_CORE_BYTE_ORDER_H + + +#include +#include + + +/* Constants to represent big endianness and little endianness */ +#define CORK_BIG_ENDIAN 4321 +#define CORK_LITTLE_ENDIAN 1234 + +/* Whether the current host is big- or little-endian. HOST gives us the + * current system's endianness; OTHER gives the opposite endianness. + * The _NAME macros can be used in debugging messages and other + * human-readable output. + * + * Note that we actually detect the endianness in the various header + * files in the libcork/config directory, since we want to keep + * everything detection-related separated out from what we define based + * on that detection. */ + +#if CORK_CONFIG_IS_BIG_ENDIAN +#define CORK_HOST_ENDIANNESS CORK_BIG_ENDIAN +#define CORK_OTHER_ENDIANNESS CORK_LITTLE_ENDIAN +#define CORK_HOST_ENDIANNESS_NAME "big" +#define CORK_OTHER_ENDIANNESS_NAME "little" + +#elif CORK_CONFIG_IS_LITTLE_ENDIAN +#define CORK_HOST_ENDIANNESS CORK_LITTLE_ENDIAN +#define CORK_OTHER_ENDIANNESS CORK_BIG_ENDIAN +#define CORK_HOST_ENDIANNESS_NAME "little" +#define CORK_OTHER_ENDIANNESS_NAME "big" + +#else +#error "Unknown endianness" +#endif + + +/* Returns the byte-swapped version an integer, regardless of the + * underlying endianness. + * + * These macros only require an rvalue as their parameter (which can + * therefore be any arbitrary expression), and they don't modify the + * original contents if it happens to be a variable. */ + +#define CORK_SWAP_UINT16(__u16) \ + (((((uint16_t) __u16) & 0xff00u) >> 8) | \ + ((((uint16_t) __u16) & 0x00ffu) << 8)) + +#define CORK_SWAP_UINT32(__u32) \ + (((((uint32_t) __u32) & 0xff000000u) >> 24) | \ + ((((uint32_t) __u32) & 0x00ff0000u) >> 8) | \ + ((((uint32_t) __u32) & 0x0000ff00u) << 8) | \ + ((((uint32_t) __u32) & 0x000000ffu) << 24)) + +#define CORK_SWAP_UINT64(__u64) \ + (((((uint64_t) __u64) & UINT64_C(0xff00000000000000)) >> 56) | \ + ((((uint64_t) __u64) & UINT64_C(0x00ff000000000000)) >> 40) | \ + ((((uint64_t) __u64) & UINT64_C(0x0000ff0000000000)) >> 24) | \ + ((((uint64_t) __u64) & UINT64_C(0x000000ff00000000)) >> 8) | \ + ((((uint64_t) __u64) & UINT64_C(0x00000000ff000000)) << 8) | \ + ((((uint64_t) __u64) & UINT64_C(0x0000000000ff0000)) << 24) | \ + ((((uint64_t) __u64) & UINT64_C(0x000000000000ff00)) << 40) | \ + ((((uint64_t) __u64) & UINT64_C(0x00000000000000ff)) << 56)) + +/* Bytes-swaps an integer variable in place. + * + * These macros require an lvalue as their parameter; the contents of + * this variable will be modified by the macro. */ + +#define CORK_SWAP_IN_PLACE_UINT16(__u16) \ + do { \ + (__u16) = CORK_SWAP_UINT16(__u16); \ + } while (0) + +#define CORK_SWAP_IN_PLACE_UINT32(__u32) \ + do { \ + (__u32) = CORK_SWAP_UINT32(__u32); \ + } while (0) + +#define CORK_SWAP_IN_PLACE_UINT64(__u64) \ + do { \ + (__u64) = CORK_SWAP_UINT64(__u64); \ + } while (0) + + +/* + * A slew of swapping macros whose operation depends on the endianness + * of the current system: + * + * uint16_t CORK_UINT16_BIG_TO_HOST(u16) + * uint32_t CORK_UINT32_BIG_TO_HOST(u32) + * uint64_t CORK_UINT64_BIG_TO_HOST(u64) + * uint16_t CORK_UINT16_LITTLE_TO_HOST(u16) + * uint32_t CORK_UINT32_LITTLE_TO_HOST(u32) + * uint64_t CORK_UINT64_LITTLE_TO_HOST(u64) + * void CORK_UINT16_BIG_TO_HOST_IN_PLACE(&u16) + * void CORK_UINT32_BIG_TO_HOST_IN_PLACE(&u32) + * void CORK_UINT64_BIG_TO_HOST_IN_PLACE(&u64) + * void CORK_UINT16_LITTLE_TO_HOST_IN_PLACE(&u16) + * void CORK_UINT32_LITTLE_TO_HOST_IN_PLACE(&u32) + * void CORK_UINT64_LITTLE_TO_HOST_IN_PLACE(&u64) + * + * uint16_t CORK_UINT16_HOST_TO_BIG(u16) + * uint32_t CORK_UINT32_HOST_TO_BIG(u32) + * uint64_t CORK_UINT64_HOST_TO_BIG(u64) + * uint16_t CORK_UINT16_HOST_TO_LITTLE(u16) + * uint32_t CORK_UINT32_HOST_TO_LITTLE(u32) + * uint64_t CORK_UINT64_HOST_TO_LITTLE(u64) + * void CORK_UINT16_HOST_TO_BIG_IN_PLACE(&u16) + * void CORK_UINT32_HOST_TO_BIG_IN_PLACE(&u32) + * void CORK_UINT64_HOST_TO_BIG_IN_PLACE(&u64) + * void CORK_UINT16_HOST_TO_LITTLE_IN_PLACE(&u16) + * void CORK_UINT32_HOST_TO_LITTLE_IN_PLACE(&u32) + * void CORK_UINT64_HOST_TO_LITTLE_IN_PLACE(&u64) + */ + +#if CORK_HOST_ENDIANNESS == CORK_BIG_ENDIAN + +#define CORK_UINT16_BIG_TO_HOST(__u16) (__u16) /* nothing to do */ +#define CORK_UINT16_LITTLE_TO_HOST(__u16) CORK_SWAP_UINT16(__u16) + +#define CORK_UINT32_BIG_TO_HOST(__u32) (__u32) /* nothing to do */ +#define CORK_UINT32_LITTLE_TO_HOST(__u32) CORK_SWAP_UINT32(__u32) + +#define CORK_UINT64_BIG_TO_HOST(__u64) (__u64) /* nothing to do */ +#define CORK_UINT64_LITTLE_TO_HOST(__u64) CORK_SWAP_UINT64(__u64) + +#define CORK_UINT16_BIG_TO_HOST_IN_PLACE(__u16) /* nothing to do */ +#define CORK_UINT16_LITTLE_TO_HOST_IN_PLACE(__u16) CORK_SWAP_IN_PLACE_UINT16(__u16) + +#define CORK_UINT32_BIG_TO_HOST_IN_PLACE(__u32) /* nothing to do */ +#define CORK_UINT32_LITTLE_TO_HOST_IN_PLACE(__u32) CORK_SWAP_IN_PLACE_UINT32(__u32) + +#define CORK_UINT64_BIG_TO_HOST_IN_PLACE(__u64) /* nothing to do */ +#define CORK_UINT64_LITTLE_TO_HOST_IN_PLACE(__u64) CORK_SWAP_IN_PLACE_UINT64(__u64) + +#elif CORK_HOST_ENDIANNESS == CORK_LITTLE_ENDIAN + +#define CORK_UINT16_BIG_TO_HOST(__u16) CORK_SWAP_UINT16(__u16) +#define CORK_UINT16_LITTLE_TO_HOST(__u16) (__u16) /* nothing to do */ + +#define CORK_UINT32_BIG_TO_HOST(__u32) CORK_SWAP_UINT32(__u32) +#define CORK_UINT32_LITTLE_TO_HOST(__u32) (__u32) /* nothing to do */ + +#define CORK_UINT64_BIG_TO_HOST(__u64) CORK_SWAP_UINT64(__u64) +#define CORK_UINT64_LITTLE_TO_HOST(__u64) (__u64) /* nothing to do */ + +#define CORK_UINT16_BIG_TO_HOST_IN_PLACE(__u16) CORK_SWAP_IN_PLACE_UINT16(__u16) +#define CORK_UINT16_LITTLE_TO_HOST_IN_PLACE(__u16) /* nothing to do */ + +#define CORK_UINT32_BIG_TO_HOST_IN_PLACE(__u32) CORK_SWAP_IN_PLACE_UINT32(__u32) +#define CORK_UINT32_LITTLE_TO_HOST_IN_PLACE(__u32) /* nothing to do */ + +#define CORK_UINT64_BIG_TO_HOST_IN_PLACE(__u64) CORK_SWAP_IN_PLACE_UINT64(__u64) +#define CORK_UINT64_LITTLE_TO_HOST_IN_PLACE(__u64) /* nothing to do */ + +#endif + + +#define CORK_UINT16_HOST_TO_BIG(__u16) CORK_UINT16_BIG_TO_HOST(__u16) +#define CORK_UINT32_HOST_TO_BIG(__u32) CORK_UINT32_BIG_TO_HOST(__u32) +#define CORK_UINT64_HOST_TO_BIG(__u64) CORK_UINT64_BIG_TO_HOST(__u64) +#define CORK_UINT16_HOST_TO_LITTLE(__u16) CORK_UINT16_LITTLE_TO_HOST(__u16) +#define CORK_UINT32_HOST_TO_LITTLE(__u32) CORK_UINT32_LITTLE_TO_HOST(__u32) +#define CORK_UINT64_HOST_TO_LITTLE(__u64) CORK_UINT64_LITTLE_TO_HOST(__u64) +#define CORK_UINT16_HOST_TO_BIG_IN_PLACE(__u16) CORK_UINT16_BIG_TO_HOST_IN_PLACE(__u16) +#define CORK_UINT32_HOST_TO_BIG_IN_PLACE(__u32) CORK_UINT32_BIG_TO_HOST_IN_PLACE(__u32) +#define CORK_UINT64_HOST_TO_BIG_IN_PLACE(__u64) CORK_UINT64_BIG_TO_HOST_IN_PLACE(__u64) +#define CORK_UINT16_HOST_TO_LITTLE_IN_PLACE(__u16) CORK_UINT16_LITTLE_TO_HOST_IN_PLACE(__u16) +#define CORK_UINT32_HOST_TO_LITTLE_IN_PLACE(__u32) CORK_UINT32_LITTLE_TO_HOST_IN_PLACE(__u32) +#define CORK_UINT64_HOST_TO_LITTLE_IN_PLACE(__u64) CORK_UINT64_LITTLE_TO_HOST_IN_PLACE(__u64) + + +#endif /* LIBCORK_CORE_BYTE_ORDER_H */ diff --git a/3rd/libcork/include/core/callbacks.h b/3rd/libcork/include/core/callbacks.h new file mode 100644 index 0000000..a63365f --- /dev/null +++ b/3rd/libcork/include/core/callbacks.h @@ -0,0 +1,46 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_CALLBACKS_H +#define LIBCORK_CORE_CALLBACKS_H + + +#include + + +typedef int +(*cork_copy_f)(void *user_data, void *dest, const void *src); + +typedef void +(*cork_done_f)(void *user_data, void *value); + +typedef void +(*cork_free_f)(void *value); + +typedef cork_hash +(*cork_hash_f)(void *user_data, const void *value); + +typedef bool +(*cork_equals_f)(void *user_data, const void *value1, const void *value2); + +typedef void +(*cork_init_f)(void *user_data, void *value); + +#define cork_free_user_data(parent) \ + ((parent)->free_user_data == NULL? (void) 0: \ + (parent)->free_user_data((parent)->user_data)) + +typedef void * +(*cork_new_f)(void *user_data); + +typedef int +(*cork_run_f)(void *user_data); + + +#endif /* LIBCORK_CORE_CALLBACKS_H */ diff --git a/3rd/libcork/include/core/error.h b/3rd/libcork/include/core/error.h new file mode 100644 index 0000000..67b731e --- /dev/null +++ b/3rd/libcork/include/core/error.h @@ -0,0 +1,139 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_ERROR_H +#define LIBCORK_CORE_ERROR_H + +#include +#include +#include +#include + +#include +#include +#include + + +/* Should be a hash of a string representing the error code. */ +typedef uint32_t cork_error; + +/* An error code that represents “no error”. */ +#define CORK_ERROR_NONE ((cork_error) 0) + +CORK_API bool +cork_error_occurred(void); + +CORK_API cork_error +cork_error_code(void); + +CORK_API const char * +cork_error_message(void); + + +CORK_API void +cork_error_clear(void); + +CORK_API void +cork_error_set_printf(cork_error code, const char *format, ...) + CORK_ATTR_PRINTF(2,3); + +CORK_API void +cork_error_set_string(cork_error code, const char *str); + +CORK_API void +cork_error_set_vprintf(cork_error code, const char *format, va_list args) + CORK_ATTR_PRINTF(2,0); + +CORK_API void +cork_error_prefix_printf(const char *format, ...) + CORK_ATTR_PRINTF(1,2); + +CORK_API void +cork_error_prefix_string(const char *str); + +CORK_API void +cork_error_prefix_vprintf(const char *format, va_list arg) + CORK_ATTR_PRINTF(1,0); + + +/* deprecated */ +CORK_API void +cork_error_set(uint32_t error_class, unsigned int error_code, + const char *format, ...) + CORK_ATTR_PRINTF(3,4); + +/* deprecated */ +CORK_API void +cork_error_prefix(const char *format, ...) + CORK_ATTR_PRINTF(1,2); + + +/*----------------------------------------------------------------------- + * Built-in errors + */ + +#define CORK_PARSE_ERROR 0x95dfd3c8 +#define CORK_REDEFINED 0x171629cb +#define CORK_UNDEFINED 0xedc3d7d9 +#define CORK_UNKNOWN_ERROR 0x8cb0880d + +#define cork_parse_error(...) \ + cork_error_set_printf(CORK_PARSE_ERROR, __VA_ARGS__) +#define cork_redefined(...) \ + cork_error_set_printf(CORK_REDEFINED, __VA_ARGS__) +#define cork_undefined(...) \ + cork_error_set_printf(CORK_UNDEFINED, __VA_ARGS__) + +CORK_API void +cork_system_error_set(void); + +CORK_API void +cork_system_error_set_explicit(int err); + +CORK_API void +cork_unknown_error_set_(const char *location); + +#define cork_unknown_error() \ + cork_unknown_error_set_(__func__) + + +/*----------------------------------------------------------------------- + * Abort on failure + */ + +#define cork_abort_(func, file, line, fmt, ...) \ + do { \ + fprintf(stderr, fmt "\n in %s (%s:%u)\n", \ + __VA_ARGS__, (func), (file), (unsigned int) (line)); \ + abort(); \ + } while (0) + +#define cork_abort(fmt, ...) \ + cork_abort_(__func__, __FILE__, __LINE__, fmt, __VA_ARGS__) + +CORK_ATTR_UNUSED +static void * +cork_abort_if_null_(void *ptr, const char *msg, const char *func, + const char *file, unsigned int line) +{ + if (CORK_UNLIKELY(ptr == NULL)) { + cork_abort_(func, file, line, "%s", msg); + } else { + return ptr; + } +} + +#define cork_abort_if_null(ptr, msg) \ + (cork_abort_if_null_(ptr, msg, __func__, __FILE__, __LINE__)) + +#define cork_unreachable() \ + cork_abort("%s", "Code should not be reachable") + + +#endif /* LIBCORK_CORE_ERROR_H */ diff --git a/3rd/libcork/include/core/gc.h b/3rd/libcork/include/core/gc.h new file mode 100644 index 0000000..bc251ea --- /dev/null +++ b/3rd/libcork/include/core/gc.h @@ -0,0 +1,67 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_GC_REFCOUNT_H +#define LIBCORK_GC_REFCOUNT_H + + +#include +#include + + +struct cork_gc; + +/* A callback for recursing through the children of a garbage-collected + * object. */ +typedef void +(*cork_gc_recurser)(struct cork_gc *gc, void *obj, void *ud); + +typedef void +(*cork_gc_free_func)(void *obj); + +typedef void +(*cork_gc_recurse_func)(struct cork_gc *gc, void *self, + cork_gc_recurser recurser, void *ud); + +/* An interface that each garbage-collected object must implement. */ +struct cork_gc_obj_iface { + /* Perform additional cleanup; does *NOT* need to deallocate the + * object itself, or release any child references */ + cork_gc_free_func free; + cork_gc_recurse_func recurse; +}; + + +CORK_API void +cork_gc_init(void); + +CORK_API void +cork_gc_done(void); + + +CORK_API void * +cork_gc_alloc(size_t instance_size, struct cork_gc_obj_iface *iface); + +#define cork_gc_new_iface(obj_type, iface) \ + ((obj_type *) \ + (cork_gc_alloc(sizeof(obj_type), (iface)))) + +#define cork_gc_new(struct_name) \ + (cork_gc_new_iface(struct struct_name, &struct_name##__gc)) + + +CORK_API void * +cork_gc_incref(void *obj); + +CORK_API void +cork_gc_decref(void *obj); + + +#endif /* LIBCORK_GC_REFCOUNT_H */ diff --git a/3rd/libcork/include/core/hash.h b/3rd/libcork/include/core/hash.h new file mode 100644 index 0000000..824eb82 --- /dev/null +++ b/3rd/libcork/include/core/hash.h @@ -0,0 +1,356 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_HASH_H +#define LIBCORK_CORE_HASH_H + + +#include +#include +#include +#include +#include + + +#ifndef CORK_HASH_ATTRIBUTES +#define CORK_HASH_ATTRIBUTES CORK_ATTR_UNUSED static inline +#endif + + +typedef uint32_t cork_hash; + +typedef struct { + cork_u128 u128; +} cork_big_hash; + +#define cork_big_hash_equal(h1, h2) (cork_u128_eq((h1).u128, (h2).u128)) + +#define CORK_BIG_HASH_INIT() {{{{0}}}} + +/* We currently use MurmurHash3 [1], which is public domain, as our hash + * implementation. + * + * [1] http://code.google.com/p/smhasher/ + */ + +#define CORK_ROTL32(a,b) (((a) << ((b) & 0x1f)) | ((a) >> (32 - ((b) & 0x1f)))) +#define CORK_ROTL64(a,b) (((a) << ((b) & 0x3f)) | ((a) >> (64 - ((b) & 0x3f)))) + +CORK_ATTR_UNUSED +static inline +uint32_t cork_fmix32(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +CORK_ATTR_UNUSED +static inline +uint64_t cork_fmix64(uint64_t k) +{ + k ^= k >> 33; + k *= UINT64_C(0xff51afd7ed558ccd); + k ^= k >> 33; + k *= UINT64_C(0xc4ceb9fe1a85ec53); + k ^= k >> 33; + return k; +} + +CORK_HASH_ATTRIBUTES +cork_hash +cork_stable_hash_buffer(cork_hash seed, const void *src, size_t len) +{ + typedef uint32_t __attribute__((__may_alias__)) cork_aliased_uint32_t; + + /* This is exactly the same as cork_murmur_hash_x86_32, but with a byte swap + * to make sure that we always process the uint32s little-endian. */ + const unsigned int nblocks = len / 4; + const cork_aliased_uint32_t *blocks = (const cork_aliased_uint32_t *) src; + const cork_aliased_uint32_t *end = blocks + nblocks; + const cork_aliased_uint32_t *curr; + const uint8_t *tail = (const uint8_t *) end; + + uint32_t h1 = seed; + uint32_t c1 = 0xcc9e2d51; + uint32_t c2 = 0x1b873593; + uint32_t k1 = 0; + + /* body */ + for (curr = blocks; curr != end; curr++) { + uint32_t k1 = CORK_UINT32_HOST_TO_LITTLE(*curr); + + k1 *= c1; + k1 = CORK_ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = CORK_ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + } + + /* tail */ + switch (len & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = CORK_ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + /* finalization */ + h1 ^= len; + h1 = cork_fmix32(h1); + return h1; +} + +#define cork_murmur_hash_x86_32(seed, src, len, dest) \ +do { \ + typedef uint32_t __attribute__((__may_alias__)) cork_aliased_uint32_t; \ + \ + const unsigned int nblocks = len / 4; \ + const cork_aliased_uint32_t *blocks = (const cork_aliased_uint32_t *) src; \ + const cork_aliased_uint32_t *end = blocks + nblocks; \ + const cork_aliased_uint32_t *curr; \ + const uint8_t *tail = (const uint8_t *) end; \ + \ + uint32_t h1 = seed; \ + uint32_t c1 = 0xcc9e2d51; \ + uint32_t c2 = 0x1b873593; \ + uint32_t k1 = 0; \ + \ + /* body */ \ + for (curr = blocks; curr != end; curr++) { \ + uint32_t k1 = *curr; \ + \ + k1 *= c1; \ + k1 = CORK_ROTL32(k1,15); \ + k1 *= c2; \ + \ + h1 ^= k1; \ + h1 = CORK_ROTL32(h1,13); \ + h1 = h1*5+0xe6546b64; \ + } \ + \ + /* tail */ \ + switch (len & 3) { \ + case 3: k1 ^= tail[2] << 16; \ + case 2: k1 ^= tail[1] << 8; \ + case 1: k1 ^= tail[0]; \ + k1 *= c1; k1 = CORK_ROTL32(k1,15); k1 *= c2; h1 ^= k1; \ + }; \ + \ + /* finalization */ \ + h1 ^= len; \ + h1 = cork_fmix32(h1); \ + *(dest) = h1; \ +} while (0) + +#define cork_murmur_hash_x86_128(seed, src, len, dest) \ +do { \ + typedef uint32_t __attribute__((__may_alias__)) cork_aliased_uint32_t; \ + \ + const unsigned int nblocks = len / 16; \ + const cork_aliased_uint32_t *blocks = (const cork_aliased_uint32_t *) src; \ + const cork_aliased_uint32_t *end = blocks + (nblocks * 4); \ + const cork_aliased_uint32_t *curr; \ + const uint8_t *tail = (const uint8_t *) end; \ + \ + uint32_t h1 = cork_u128_be32(seed.u128, 0); \ + uint32_t h2 = cork_u128_be32(seed.u128, 1); \ + uint32_t h3 = cork_u128_be32(seed.u128, 2); \ + uint32_t h4 = cork_u128_be32(seed.u128, 3); \ + \ + uint32_t c1 = 0x239b961b; \ + uint32_t c2 = 0xab0e9789; \ + uint32_t c3 = 0x38b34ae5; \ + uint32_t c4 = 0xa1e38b93; \ + \ + uint32_t k1 = 0; \ + uint32_t k2 = 0; \ + uint32_t k3 = 0; \ + uint32_t k4 = 0; \ + \ + /* body */ \ + for (curr = blocks; curr != end; curr += 4) { \ + uint32_t k1 = curr[0]; \ + uint32_t k2 = curr[1]; \ + uint32_t k3 = curr[2]; \ + uint32_t k4 = curr[3]; \ + \ + k1 *= c1; k1 = CORK_ROTL32(k1,15); k1 *= c2; h1 ^= k1; \ + h1 = CORK_ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; \ + \ + k2 *= c2; k2 = CORK_ROTL32(k2,16); k2 *= c3; h2 ^= k2; \ + h2 = CORK_ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; \ + \ + k3 *= c3; k3 = CORK_ROTL32(k3,17); k3 *= c4; h3 ^= k3; \ + h3 = CORK_ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; \ + \ + k4 *= c4; k4 = CORK_ROTL32(k4,18); k4 *= c1; h4 ^= k4; \ + h4 = CORK_ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; \ + } \ + \ + /* tail */ \ + switch (len & 15) { \ + case 15: k4 ^= tail[14] << 16; \ + case 14: k4 ^= tail[13] << 8; \ + case 13: k4 ^= tail[12] << 0; \ + k4 *= c4; k4 = CORK_ROTL32(k4,18); k4 *= c1; h4 ^= k4; \ + \ + case 12: k3 ^= tail[11] << 24; \ + case 11: k3 ^= tail[10] << 16; \ + case 10: k3 ^= tail[ 9] << 8; \ + case 9: k3 ^= tail[ 8] << 0; \ + k3 *= c3; k3 = CORK_ROTL32(k3,17); k3 *= c4; h3 ^= k3; \ + \ + case 8: k2 ^= tail[ 7] << 24; \ + case 7: k2 ^= tail[ 6] << 16; \ + case 6: k2 ^= tail[ 5] << 8; \ + case 5: k2 ^= tail[ 4] << 0; \ + k2 *= c2; k2 = CORK_ROTL32(k2,16); k2 *= c3; h2 ^= k2; \ + \ + case 4: k1 ^= tail[ 3] << 24; \ + case 3: k1 ^= tail[ 2] << 16; \ + case 2: k1 ^= tail[ 1] << 8; \ + case 1: k1 ^= tail[ 0] << 0; \ + k1 *= c1; k1 = CORK_ROTL32(k1,15); k1 *= c2; h1 ^= k1; \ + }; \ + \ + /* finalization */ \ + \ + h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; \ + \ + h1 += h2; h1 += h3; h1 += h4; \ + h2 += h1; h3 += h1; h4 += h1; \ + \ + h1 = cork_fmix32(h1); \ + h2 = cork_fmix32(h2); \ + h3 = cork_fmix32(h3); \ + h4 = cork_fmix32(h4); \ + \ + h1 += h2; h1 += h3; h1 += h4; \ + h2 += h1; h3 += h1; h4 += h1; \ + \ + (dest)->u128 = cork_u128_from_32(h1, h2, h3, h4); \ +} while (0) + +#define cork_murmur_hash_x64_128(seed, src, len, dest) \ +do { \ + typedef uint64_t __attribute__((__may_alias__)) cork_aliased_uint64_t; \ + \ + const unsigned int nblocks = len / 16; \ + const cork_aliased_uint64_t *blocks = (const cork_aliased_uint64_t *) src; \ + const cork_aliased_uint64_t *end = blocks + (nblocks * 2); \ + const cork_aliased_uint64_t *curr; \ + const uint8_t *tail = (const uint8_t *) end; \ + \ + uint64_t h1 = cork_u128_be64(seed.u128, 0); \ + uint64_t h2 = cork_u128_be64(seed.u128, 1); \ + \ + uint64_t c1 = UINT64_C(0x87c37b91114253d5); \ + uint64_t c2 = UINT64_C(0x4cf5ad432745937f); \ + \ + uint64_t k1 = 0; \ + uint64_t k2 = 0; \ + \ + /* body */ \ + for (curr = blocks; curr != end; curr += 2) { \ + uint64_t k1 = curr[0]; \ + uint64_t k2 = curr[1]; \ + \ + k1 *= c1; k1 = CORK_ROTL64(k1,31); k1 *= c2; h1 ^= k1; \ + h1 = CORK_ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; \ + \ + k2 *= c2; k2 = CORK_ROTL64(k2,33); k2 *= c1; h2 ^= k2; \ + h2 = CORK_ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; \ + } \ + \ + /* tail */ \ + switch (len & 15) { \ + case 15: k2 ^= (uint64_t) (tail[14]) << 48; \ + case 14: k2 ^= (uint64_t) (tail[13]) << 40; \ + case 13: k2 ^= (uint64_t) (tail[12]) << 32; \ + case 12: k2 ^= (uint64_t) (tail[11]) << 24; \ + case 11: k2 ^= (uint64_t) (tail[10]) << 16; \ + case 10: k2 ^= (uint64_t) (tail[ 9]) << 8; \ + case 9: k2 ^= (uint64_t) (tail[ 8]) << 0; \ + k2 *= c2; k2 = CORK_ROTL64(k2,33); k2 *= c1; h2 ^= k2; \ + \ + case 8: k1 ^= (uint64_t) (tail[ 7]) << 56; \ + case 7: k1 ^= (uint64_t) (tail[ 6]) << 48; \ + case 6: k1 ^= (uint64_t) (tail[ 5]) << 40; \ + case 5: k1 ^= (uint64_t) (tail[ 4]) << 32; \ + case 4: k1 ^= (uint64_t) (tail[ 3]) << 24; \ + case 3: k1 ^= (uint64_t) (tail[ 2]) << 16; \ + case 2: k1 ^= (uint64_t) (tail[ 1]) << 8; \ + case 1: k1 ^= (uint64_t) (tail[ 0]) << 0; \ + k1 *= c1; k1 = CORK_ROTL64(k1,31); k1 *= c2; h1 ^= k1; \ + }; \ + \ + /* finalization */ \ + \ + h1 ^= len; h2 ^= len; \ + \ + h1 += h2; \ + h2 += h1; \ + \ + h1 = cork_fmix64(h1); \ + h2 = cork_fmix64(h2); \ + \ + h1 += h2; \ + h2 += h1; \ + \ + (dest)->u128 = cork_u128_from_64(h1, h2); \ +} while (0) + + +#include +CORK_HASH_ATTRIBUTES +cork_hash +cork_hash_buffer(cork_hash seed, const void *src, size_t len) +{ +#if CORK_SIZEOF_POINTER == 8 + cork_big_hash big_seed = {cork_u128_from_32(seed, seed, seed, seed)}; + cork_big_hash hash; + cork_murmur_hash_x64_128(big_seed, src, len, &hash); + return cork_u128_be32(hash.u128, 0); +#else + cork_hash hash = 0; + cork_murmur_hash_x86_32(seed, src, len, &hash); + return hash; +#endif +} + + +CORK_HASH_ATTRIBUTES +cork_big_hash +cork_big_hash_buffer(cork_big_hash seed, const void *src, size_t len) +{ + cork_big_hash result; +#if CORK_SIZEOF_POINTER == 8 + cork_murmur_hash_x64_128(seed, src, len, &result); +#else + cork_murmur_hash_x86_128(seed, src, len, &result); +#endif + return result; +} + + +#define cork_hash_variable(seed, val) \ + (cork_hash_buffer((seed), &(val), sizeof((val)))) +#define cork_stable_hash_variable(seed, val) \ + (cork_stable_hash_buffer((seed), &(val), sizeof((val)))) +#define cork_big_hash_variable(seed, val) \ + (cork_big_hash_buffer((seed), &(val), sizeof((val)))) + + +#endif /* LIBCORK_CORE_HASH_H */ diff --git a/3rd/libcork/include/core/id.h b/3rd/libcork/include/core/id.h new file mode 100644 index 0000000..3e94179 --- /dev/null +++ b/3rd/libcork/include/core/id.h @@ -0,0 +1,35 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_ID_H +#define LIBCORK_CORE_ID_H + +#include + + +struct cork_uid { + const char *name; +}; + +typedef const struct cork_uid *cork_uid; + +#define CORK_UID_NONE ((cork_uid) NULL) + +#define cork_uid_define_named(c_name, name) \ + static const struct cork_uid c_name##__id = { name }; \ + static cork_uid c_name = &c_name##__id; +#define cork_uid_define(c_name) \ + cork_uid_define_named(c_name, #c_name) + +#define cork_uid_equal(id1, id2) ((id1) == (id2)) +#define cork_uid_hash(id) ((cork_hash) (uintptr_t) (id)) +#define cork_uid_name(id) ((id)->name) + + +#endif /* LIBCORK_CORE_ID_H */ diff --git a/3rd/libcork/include/core/mempool.h b/3rd/libcork/include/core/mempool.h new file mode 100644 index 0000000..43a73c6 --- /dev/null +++ b/3rd/libcork/include/core/mempool.h @@ -0,0 +1,71 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORK_MEMPOOL_H +#define LIBCORK_CORK_MEMPOOL_H + + +#include +#include +#include +#include +#include + + +#define CORK_MEMPOOL_DEFAULT_BLOCK_SIZE 4096 + + +struct cork_mempool; + + +CORK_API struct cork_mempool * +cork_mempool_new_size_ex(size_t element_size, size_t block_size); + +#define cork_mempool_new_size(element_size) \ + (cork_mempool_new_size_ex \ + ((element_size), CORK_MEMPOOL_DEFAULT_BLOCK_SIZE)) + +#define cork_mempool_new_ex(type, block_size) \ + (cork_mempool_new_size_ex(sizeof(type), (block_size))) + +#define cork_mempool_new(type) \ + (cork_mempool_new_size(sizeof(type))) + +CORK_API void +cork_mempool_free(struct cork_mempool *mp); + + +CORK_API void +cork_mempool_set_user_data(struct cork_mempool *mp, + void *user_data, cork_free_f free_user_data); + +CORK_API void +cork_mempool_set_init_object(struct cork_mempool *mp, cork_init_f init_object); + +CORK_API void +cork_mempool_set_done_object(struct cork_mempool *mp, cork_done_f done_object); + +/* Deprecated; you should now use separate calls to cork_mempool_set_user_data, + * cork_mempool_set_init_object, and cork_mempool_set_done_object. */ +CORK_API void +cork_mempool_set_callbacks(struct cork_mempool *mp, + void *user_data, cork_free_f free_user_data, + cork_init_f init_object, + cork_done_f done_object); + + +CORK_API void * +cork_mempool_new_object(struct cork_mempool *mp); + + +CORK_API void +cork_mempool_free_object(struct cork_mempool *mp, void *ptr); + + +#endif /* LIBCORK_CORK_MEMPOOL_H */ diff --git a/3rd/libcork/include/core/net-addresses.h b/3rd/libcork/include/core/net-addresses.h new file mode 100644 index 0000000..5de73b2 --- /dev/null +++ b/3rd/libcork/include/core/net-addresses.h @@ -0,0 +1,147 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_NET_ADDRESSES_H +#define LIBCORK_CORE_NET_ADDRESSES_H + + +#include + +#include +#include +#include + + +/*----------------------------------------------------------------------- + * IP addresses + */ + +struct cork_ipv4 { + union { + uint8_t u8[4]; + uint16_t u16[2]; + uint32_t u32; + } _; +}; + +struct cork_ipv6 { + union { + uint8_t u8[16]; + uint16_t u16[8]; + uint32_t u32[4]; + uint64_t u64[2]; + } _; +}; + +struct cork_ip { + /* Which version of IP address this is. */ + unsigned int version; + union { + struct cork_ipv4 v4; + struct cork_ipv6 v6; + } ip; +}; + + +#define CORK_IPV4_STRING_LENGTH (sizeof "xxx.xxx.xxx.xxx") +#define CORK_IPV6_STRING_LENGTH \ + (sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") +#define CORK_IP_STRING_LENGTH CORK_IPV6_STRING_LENGTH + + +/*** IPv4 ***/ + +/* src must be well-formed: 4 bytes, big-endian */ +#define cork_ipv4_copy(addr, src) \ + (memcpy((addr), (src), sizeof(struct cork_ipv4))) + +#define cork_ipv4_equal(a1, a2) \ + ((a1)->_.u32 == (a2)->_.u32) + +CORK_API int +cork_ipv4_init(struct cork_ipv4 *addr, const char *str); + +CORK_API bool +cork_ipv4_equal_(const struct cork_ipv4 *addr1, const struct cork_ipv4 *addr2); + +CORK_API void +cork_ipv4_to_raw_string(const struct cork_ipv4 *addr, char *dest); + +CORK_API bool +cork_ipv4_is_valid_network(const struct cork_ipv4 *addr, + unsigned int cidr_prefix); + + +/*** IPv6 ***/ + +/* src must be well-formed: 16 bytes, big-endian */ +#define cork_ipv6_copy(addr, src) \ + (memcpy((addr), (src), sizeof(struct cork_ipv6))) + +#define cork_ipv6_equal(a1, a2) \ + ((a1)->_.u64[0] == (a2)->_.u64[0] && \ + (a1)->_.u64[1] == (a2)->_.u64[1]) + +CORK_API int +cork_ipv6_init(struct cork_ipv6 *addr, const char *str); + +CORK_API bool +cork_ipv6_equal_(const struct cork_ipv6 *addr1, const struct cork_ipv6 *addr2); + +CORK_API void +cork_ipv6_to_raw_string(const struct cork_ipv6 *addr, char *dest); + +CORK_API bool +cork_ipv6_is_valid_network(const struct cork_ipv6 *addr, + unsigned int cidr_prefix); + + +/*** Generic IP ***/ + +#define cork_ip_equal(a1, a2) \ + ((a1)->version == 4? \ + ((a2)->version == 4 && cork_ipv4_equal(&(a1)->ip.v4, &(a2)->ip.v4)): \ + ((a2)->version == 6 && cork_ipv6_equal(&(a1)->ip.v6, &(a2)->ip.v6))) + +/* src must be well-formed: 4 bytes, big-endian */ +#define cork_ip_from_ipv4(addr, src) \ + do { \ + (addr)->version = 4; \ + cork_ipv4_copy(&(addr)->ip.v4, (src)); \ + } while (0) + +/* src must be well-formed: 16 bytes, big-endian */ +#define cork_ip_from_ipv6(addr, src) \ + do { \ + (addr)->version = 6; \ + cork_ipv6_copy(&(addr)->ip.v6, (src)); \ + } while (0) + +/* src must be well-formed: 4 bytes, big-endian */ +CORK_API void +cork_ip_from_ipv4_(struct cork_ip *addr, const void *src); + +/* src must be well-formed: 16 bytes, big-endian */ +CORK_API void +cork_ip_from_ipv6_(struct cork_ip *addr, const void *src); + +CORK_API int +cork_ip_init(struct cork_ip *addr, const char *str); + +CORK_API bool +cork_ip_equal_(const struct cork_ip *addr1, const struct cork_ip *addr2); + +CORK_API void +cork_ip_to_raw_string(const struct cork_ip *addr, char *dest); + +CORK_API bool +cork_ip_is_valid_network(const struct cork_ip *addr, unsigned int cidr_prefix); + + +#endif /* LIBCORK_CORE_NET_ADDRESSES_H */ diff --git a/3rd/libcork/include/core/timestamp.h b/3rd/libcork/include/core/timestamp.h new file mode 100644 index 0000000..4eba7b1 --- /dev/null +++ b/3rd/libcork/include/core/timestamp.h @@ -0,0 +1,87 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_TIMESTAMP_H +#define LIBCORK_CORE_TIMESTAMP_H + + +#include +#include +#include +#include + + +typedef uint64_t cork_timestamp; + + +#define cork_timestamp_init_sec(ts, sec) \ + do { \ + *(ts) = (((uint64_t) (sec)) << 32); \ + } while (0) + +#define cork_timestamp_init_gsec(ts, sec, gsec) \ + do { \ + *(ts) = (((uint64_t) (sec)) << 32) | \ + (((uint64_t) (gsec)) & 0xffffffff); \ + } while (0) + +#define cork_timestamp_init_msec(ts, sec, msec) \ + do { \ + *(ts) = (((uint64_t) (sec)) << 32) | \ + ((((uint64_t) (msec)) << 32) / 1000); \ + } while (0) + +#define cork_timestamp_init_usec(ts, sec, usec) \ + do { \ + *(ts) = (((uint64_t) (sec)) << 32) | \ + ((((uint64_t) (usec)) << 32) / 1000000); \ + } while (0) + +#define cork_timestamp_init_nsec(ts, sec, nsec) \ + do { \ + *(ts) = (((uint64_t) (sec)) << 32) | \ + ((((uint64_t) (nsec)) << 32) / 1000000000); \ + } while (0) + + +CORK_API void +cork_timestamp_init_now(cork_timestamp *ts); + + +#define cork_timestamp_sec(ts) ((uint32_t) ((ts) >> 32)) +#define cork_timestamp_gsec(ts) ((uint32_t) ((ts) & 0xffffffff)) + +CORK_ATTR_UNUSED +static inline uint64_t +cork_timestamp_gsec_to_units(const cork_timestamp ts, uint64_t denom) +{ + uint64_t half = ((uint64_t) 1 << 31) / denom; + uint64_t gsec = cork_timestamp_gsec(ts); + gsec += half; + gsec *= denom; + gsec >>= 32; + return gsec; +} + +#define cork_timestamp_msec(ts) cork_timestamp_gsec_to_units(ts, 1000) +#define cork_timestamp_usec(ts) cork_timestamp_gsec_to_units(ts, 1000000) +#define cork_timestamp_nsec(ts) cork_timestamp_gsec_to_units(ts, 1000000000) + + +CORK_API int +cork_timestamp_format_utc(const cork_timestamp ts, const char *format, + struct cork_buffer *dest); + +CORK_API int +cork_timestamp_format_local(const cork_timestamp ts, const char *format, + struct cork_buffer *dest); + + +#endif /* LIBCORK_CORE_TIMESTAMP_H */ diff --git a/3rd/libcork/include/core/types.h b/3rd/libcork/include/core/types.h new file mode 100644 index 0000000..f469bed --- /dev/null +++ b/3rd/libcork/include/core/types.h @@ -0,0 +1,82 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_TYPES_H +#define LIBCORK_CORE_TYPES_H + +/* For now, we assume that the C99 integer types are available using the + * standard headers. */ + +#include +#include +#include +#include +#include + + +/* Define preprocessor macros that contain the size of several built-in + * types. Again, we assume that we have the C99 definitions available. */ + +#if SHRT_MAX == INT8_MAX +#define CORK_SIZEOF_SHORT 1 +#elif SHRT_MAX == INT16_MAX +#define CORK_SIZEOF_SHORT 2 +#elif SHRT_MAX == INT32_MAX +#define CORK_SIZEOF_SHORT 4 +#elif SHRT_MAX == INT64_MAX +#define CORK_SIZEOF_SHORT 8 +#else +#error "Cannot determine size of short" +#endif + +#if INT_MAX == INT8_MAX +#define CORK_SIZEOF_INT 1 +#elif INT_MAX == INT16_MAX +#define CORK_SIZEOF_INT 2 +#elif INT_MAX == INT32_MAX +#define CORK_SIZEOF_INT 4 +#elif INT_MAX == INT64_MAX +#define CORK_SIZEOF_INT 8 +#else +#error "Cannot determine size of int" +#endif + +#if LONG_MAX == INT8_MAX +#define CORK_SIZEOF_LONG 1 +#elif LONG_MAX == INT16_MAX +#define CORK_SIZEOF_LONG 2 +#elif LONG_MAX == INT32_MAX +#define CORK_SIZEOF_LONG 4 +#elif LONG_MAX == INT64_MAX +#define CORK_SIZEOF_LONG 8 +#else +#error "Cannot determine size of long" +#endif + +#if INTPTR_MAX == INT8_MAX +#define CORK_SIZEOF_POINTER 1 +#elif INTPTR_MAX == INT16_MAX +#define CORK_SIZEOF_POINTER 2 +#elif INTPTR_MAX == INT32_MAX +#define CORK_SIZEOF_POINTER 4 +#elif INTPTR_MAX == INT64_MAX +#define CORK_SIZEOF_POINTER 8 +#else +#error "Cannot determine size of void *" +#endif + + +/* Return a pointer to a @c struct, given a pointer to one of its + * fields. */ +#define cork_container_of(field, struct_type, field_name) \ + ((struct_type *) (- offsetof(struct_type, field_name) + \ + (void *) (field))) + +#endif /* LIBCORK_CORE_TYPES_H */ diff --git a/3rd/libcork/include/core/u128.h b/3rd/libcork/include/core/u128.h new file mode 100644 index 0000000..692f096 --- /dev/null +++ b/3rd/libcork/include/core/u128.h @@ -0,0 +1,223 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_U128_H +#define LIBCORK_CORE_U128_H + + +#include +#include +#include +#include + +typedef struct { + union { + uint8_t u8[16]; + uint16_t u16[8]; + uint32_t u32[4]; + uint64_t u64[2]; +#if CORK_HOST_ENDIANNESS == CORK_BIG_ENDIAN + struct { uint64_t hi; uint64_t lo; } be64; +#else + struct { uint64_t lo; uint64_t hi; } be64; +#endif +#if CORK_CONFIG_HAVE_GCC_INT128 +#define CORK_U128_HAVE_U128 1 + unsigned __int128 u128; +#elif CORK_CONFIG_HAVE_GCC_MODE_ATTRIBUTE +#define CORK_U128_HAVE_U128 1 + unsigned int u128 __attribute__((mode(TI))); +#else +#define CORK_U128_HAVE_U128 0 +#endif + } _; +} cork_u128; + + +/* i0-3 are given in big-endian order, regardless of host endianness */ +CORK_ATTR_UNUSED +static cork_u128 +cork_u128_from_32(uint32_t i0, uint32_t i1, uint32_t i2, uint32_t i3) +{ + cork_u128 value; +#if CORK_HOST_ENDIANNESS == CORK_BIG_ENDIAN + value._.u32[0] = i0; + value._.u32[1] = i1; + value._.u32[2] = i2; + value._.u32[3] = i3; +#else + value._.u32[3] = i0; + value._.u32[2] = i1; + value._.u32[1] = i2; + value._.u32[0] = i3; +#endif + return value; +} + +/* i0-1 are given in big-endian order, regardless of host endianness */ +CORK_ATTR_UNUSED +static cork_u128 +cork_u128_from_64(uint64_t i0, uint64_t i1) +{ + cork_u128 value; +#if CORK_HOST_ENDIANNESS == CORK_BIG_ENDIAN + value._.u64[0] = i0; + value._.u64[1] = i1; +#else + value._.u64[1] = i0; + value._.u64[0] = i1; +#endif + return value; +} + + +#if CORK_HOST_ENDIANNESS == CORK_BIG_ENDIAN +#define cork_u128_be8(val, idx) ((val)._.u8[(idx)]) +#define cork_u128_be16(val, idx) ((val)._.u16[(idx)]) +#define cork_u128_be32(val, idx) ((val)._.u32[(idx)]) +#define cork_u128_be64(val, idx) ((val)._.u64[(idx)]) +#else +#define cork_u128_be8(val, idx) ((val)._.u8[15 - (idx)]) +#define cork_u128_be16(val, idx) ((val)._.u16[7 - (idx)]) +#define cork_u128_be32(val, idx) ((val)._.u32[3 - (idx)]) +#define cork_u128_be64(val, idx) ((val)._.u64[1 - (idx)]) +#endif + + +CORK_ATTR_UNUSED +static cork_u128 +cork_u128_add(cork_u128 a, cork_u128 b) +{ + cork_u128 result; +#if CORK_U128_HAVE_U128 + result._.u128 = a._.u128 + b._.u128; +#else + result._.be64.lo = a._.be64.lo + b._.be64.lo; + result._.be64.hi = + a._.be64.hi + b._.be64.hi + (result._.be64.lo < a._.be64.lo); +#endif + return result; +} + +CORK_ATTR_UNUSED +static cork_u128 +cork_u128_sub(cork_u128 a, cork_u128 b) +{ + cork_u128 result; +#if CORK_U128_HAVE_U128 + result._.u128 = a._.u128 - b._.u128; +#else + result._.be64.lo = a._.be64.lo - b._.be64.lo; + result._.be64.hi = + a._.be64.hi - b._.be64.hi - (result._.be64.lo > a._.be64.lo); +#endif + return result; +} + + +CORK_ATTR_UNUSED +static bool +cork_u128_eq(cork_u128 a, cork_u128 b) +{ +#if CORK_U128_HAVE_U128 + return (a._.u128 == b._.u128); +#else + return (a._.be64.hi == b._.be64.hi) && (a._.be64.lo == b._.be64.lo); +#endif +} + +CORK_ATTR_UNUSED +static bool +cork_u128_ne(cork_u128 a, cork_u128 b) +{ +#if CORK_U128_HAVE_U128 + return (a._.u128 != b._.u128); +#else + return (a._.be64.hi != b._.be64.hi) || (a._.be64.lo != b._.be64.lo); +#endif +} + +CORK_ATTR_UNUSED +static bool +cork_u128_lt(cork_u128 a, cork_u128 b) +{ +#if CORK_U128_HAVE_U128 + return (a._.u128 < b._.u128); +#else + if (a._.be64.hi == b._.be64.hi) { + return a._.be64.lo < b._.be64.lo; + } else { + return a._.be64.hi < b._.be64.hi; + } +#endif +} + +CORK_ATTR_UNUSED +static bool +cork_u128_le(cork_u128 a, cork_u128 b) +{ +#if CORK_U128_HAVE_U128 + return (a._.u128 <= b._.u128); +#else + if (a._.be64.hi == b._.be64.hi) { + return a._.be64.lo <= b._.be64.lo; + } else { + return a._.be64.hi <= b._.be64.hi; + } +#endif +} + +CORK_ATTR_UNUSED +static bool +cork_u128_gt(cork_u128 a, cork_u128 b) +{ +#if CORK_U128_HAVE_U128 + return (a._.u128 > b._.u128); +#else + if (a._.be64.hi == b._.be64.hi) { + return a._.be64.lo > b._.be64.lo; + } else { + return a._.be64.hi > b._.be64.hi; + } +#endif +} + +CORK_ATTR_UNUSED +static bool +cork_u128_ge(cork_u128 a, cork_u128 b) +{ +#if CORK_U128_HAVE_U128 + return (a._.u128 >= b._.u128); +#else + if (a._.be64.hi == b._.be64.hi) { + return a._.be64.lo >= b._.be64.lo; + } else { + return a._.be64.hi >= b._.be64.hi; + } +#endif +} + + +/* log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322 */ +#define CORK_U128_DECIMAL_LENGTH 44 /* ~= 128 / 3 + 1 + 1 */ + +CORK_API const char * +cork_u128_to_decimal(char *buf, cork_u128 val); + + +#define CORK_U128_HEX_LENGTH 33 + +CORK_API const char * +cork_u128_to_hex(char *buf, cork_u128 val); + +CORK_API const char * +cork_u128_to_padded_hex(char *buf, cork_u128 val); + + +#endif /* LIBCORK_CORE_U128_H */ diff --git a/3rd/libcork/include/ds.h b/3rd/libcork/include/ds.h new file mode 100644 index 0000000..8cedd21 --- /dev/null +++ b/3rd/libcork/include/ds.h @@ -0,0 +1,26 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_H +#define LIBCORK_DS_H + +/*** include all of the parts ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* LIBCORK_DS_H */ diff --git a/3rd/libcork/include/ds/array.h b/3rd/libcork/include/ds/array.h new file mode 100644 index 0000000..d86a8b6 --- /dev/null +++ b/3rd/libcork/include/ds/array.h @@ -0,0 +1,161 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_ARRAY_H +#define LIBCORK_DS_ARRAY_H + + +#include +#include +#include + + +/*----------------------------------------------------------------------- + * Resizable arrays + */ + +struct cork_array_priv; + +struct cork_raw_array { + void *items; + size_t size; + struct cork_array_priv *priv; +}; + +CORK_API void +cork_raw_array_init(struct cork_raw_array *array, size_t element_size); + +CORK_API void +cork_raw_array_done(struct cork_raw_array *array); + +CORK_API void +cork_raw_array_set_callback_data(struct cork_raw_array *array, + void *user_data, cork_free_f free_user_data); + +CORK_API void +cork_raw_array_set_init(struct cork_raw_array *array, cork_init_f init); + +CORK_API void +cork_raw_array_set_done(struct cork_raw_array *array, cork_done_f done); + +CORK_API void +cork_raw_array_set_reuse(struct cork_raw_array *array, cork_init_f reuse); + +CORK_API void +cork_raw_array_set_remove(struct cork_raw_array *array, cork_done_f remove); + +CORK_API size_t +cork_raw_array_element_size(const struct cork_raw_array *array); + +CORK_API void +cork_raw_array_clear(struct cork_raw_array *array); + +CORK_API void * +cork_raw_array_elements(const struct cork_raw_array *array); + +CORK_API void * +cork_raw_array_at(const struct cork_raw_array *array, size_t index); + +CORK_API size_t +cork_raw_array_size(const struct cork_raw_array *array); + +CORK_API bool +cork_raw_array_is_empty(const struct cork_raw_array *array); + +CORK_API void +cork_raw_array_ensure_size(struct cork_raw_array *array, size_t count); + +CORK_API void * +cork_raw_array_append(struct cork_raw_array *array); + +CORK_API int +cork_raw_array_copy(struct cork_raw_array *dest, + const struct cork_raw_array *src, + cork_copy_f copy, void *user_data); + + +/*----------------------------------------------------------------------- + * Type-checked resizable arrays + */ + +#define cork_array(T) \ + struct { \ + T *items; \ + size_t size; \ + struct cork_array_priv *priv; \ + } + +#define cork_array_element_size(arr) (sizeof((arr)->items[0])) +#define cork_array_elements(arr) ((arr)->items) +#define cork_array_at(arr, i) ((arr)->items[(i)]) +#define cork_array_size(arr) ((arr)->size) +#define cork_array_is_empty(arr) ((arr)->size == 0) +#define cork_array_to_raw(arr) ((struct cork_raw_array *) (void *) (arr)) + +#define cork_array_init(arr) \ + (cork_raw_array_init(cork_array_to_raw(arr), cork_array_element_size(arr))) +#define cork_array_done(arr) \ + (cork_raw_array_done(cork_array_to_raw(arr))) + +#define cork_array_set_callback_data(arr, ud, fud) \ + (cork_raw_array_set_callback_data(cork_array_to_raw(arr), (ud), (fud))) +#define cork_array_set_init(arr, i) \ + (cork_raw_array_set_init(cork_array_to_raw(arr), (i))) +#define cork_array_set_done(arr, d) \ + (cork_raw_array_set_done(cork_array_to_raw(arr), (d))) +#define cork_array_set_reuse(arr, r) \ + (cork_raw_array_set_reuse(cork_array_to_raw(arr), (r))) +#define cork_array_set_remove(arr, r) \ + (cork_raw_array_set_remove(cork_array_to_raw(arr), (r))) + +#define cork_array_clear(arr) \ + (cork_raw_array_clear(cork_array_to_raw(arr))) +#define cork_array_copy(d, s, c, ud) \ + (cork_raw_array_copy(cork_array_to_raw(d), cork_array_to_raw(s), (c), (ud))) + +#define cork_array_ensure_size(arr, count) \ + (cork_raw_array_ensure_size(cork_array_to_raw(arr), (count))) + +#define cork_array_append(arr, element) \ + (cork_raw_array_append(cork_array_to_raw(arr)), \ + ((arr)->items[(arr)->size - 1] = (element), (void) 0)) + +#define cork_array_append_get(arr) \ + (cork_raw_array_append(cork_array_to_raw(arr)), \ + &(arr)->items[(arr)->size - 1]) + + +/*----------------------------------------------------------------------- + * Builtin array types + */ + +CORK_API void +cork_raw_pointer_array_init(struct cork_raw_array *array, cork_free_f free); + +#define cork_pointer_array_init(arr, f) \ + (cork_raw_pointer_array_init(cork_array_to_raw(arr), (f))) + +struct cork_string_array { + const char **items; + size_t size; + struct cork_array_priv *priv; +}; + +CORK_API void +cork_string_array_init(struct cork_string_array *array); + +CORK_API void +cork_string_array_append(struct cork_string_array *array, const char *str); + +CORK_API void +cork_string_array_copy(struct cork_string_array *dest, + const struct cork_string_array *src); + + +#endif /* LIBCORK_DS_ARRAY_H */ diff --git a/3rd/libcork/include/ds/bitset.h b/3rd/libcork/include/ds/bitset.h new file mode 100644 index 0000000..f43ce09 --- /dev/null +++ b/3rd/libcork/include/ds/bitset.h @@ -0,0 +1,64 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_BITS_H +#define LIBCORK_DS_BITS_H + + +#include +#include + + +/*----------------------------------------------------------------------- + * Bit sets + */ + +struct cork_bitset { + uint8_t *bits; + size_t bit_count; + size_t byte_count; +}; + +CORK_API struct cork_bitset * +cork_bitset_new(size_t bit_count); + +CORK_API void +cork_bitset_free(struct cork_bitset *set); + +CORK_API void +cork_bitset_clear(struct cork_bitset *set); + +/* Extract the byte that contains a particular bit in an array. */ +#define cork_bitset_byte_for_bit(set, i) \ + ((set)->bits[(i) / 8]) + +/* Create a bit mask that extracts a particular bit from the byte that it lives + * in. */ +#define cork_bitset_pos_mask_for_bit(i) \ + (0x80 >> ((i) % 8)) + +/* Create a bit mask that extracts everything except for a particular bit from + * the byte that it lives in. */ +#define cork_bitset_neg_mask_for_bit(i) \ + (~cork_bitset_pos_mask_for_bit(i)) + +/* Return whether a particular bit is set in a byte array. Bits are numbered + * from 0, in a big-endian order. */ +#define cork_bitset_get(set, i) \ + ((cork_bitset_byte_for_bit(set, i) & cork_bitset_pos_mask_for_bit(i)) != 0) + +/* Set (or unset) a particular bit is set in a byte array. Bits are numbered + * from 0, in a big-endian order. */ +#define cork_bitset_set(set, i, val) \ + (cork_bitset_byte_for_bit(set, i) = \ + (cork_bitset_byte_for_bit(set, i) & cork_bitset_neg_mask_for_bit(i)) \ + | ((val)? cork_bitset_pos_mask_for_bit(i): 0)) + + +#endif /* LIBCORK_DS_BITS_H */ diff --git a/3rd/libcork/include/ds/buffer.h b/3rd/libcork/include/ds/buffer.h new file mode 100644 index 0000000..39fbbfa --- /dev/null +++ b/3rd/libcork/include/ds/buffer.h @@ -0,0 +1,163 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_BUFFER_H +#define LIBCORK_DS_BUFFER_H + + +#include + +#include +#include +#include + + +struct cork_buffer { + /* The current contents of the buffer. */ + void *buf; + /* The current size of the buffer. */ + size_t size; + /* The amount of space allocated for buf. */ + size_t allocated_size; +}; + + +CORK_API void +cork_buffer_init(struct cork_buffer *buffer); + +#define CORK_BUFFER_INIT() { NULL, 0, 0 } + +CORK_API struct cork_buffer * +cork_buffer_new(void); + +CORK_API void +cork_buffer_done(struct cork_buffer *buffer); + +CORK_API void +cork_buffer_free(struct cork_buffer *buffer); + + +CORK_API bool +cork_buffer_equal(const struct cork_buffer *buffer1, + const struct cork_buffer *buffer2); + + +CORK_API void +cork_buffer_ensure_size(struct cork_buffer *buffer, size_t desired_size); + + +CORK_API void +cork_buffer_clear(struct cork_buffer *buffer); + +CORK_API void +cork_buffer_truncate(struct cork_buffer *buffer, size_t length); + +#define cork_buffer_byte(buffer, i) (((const uint8_t *) (buffer)->buf)[(i)]) +#define cork_buffer_char(buffer, i) (((const char *) (buffer)->buf)[(i)]) + + +/*----------------------------------------------------------------------- + * A whole bunch of methods for adding data + */ + +#define cork_buffer_copy(dest, src) \ + (cork_buffer_set((dest), (src)->buf, (src)->size)) + +CORK_API void +cork_buffer_set(struct cork_buffer *buffer, const void *src, size_t length); + +#define cork_buffer_append_copy(dest, src) \ + (cork_buffer_append((dest), (src)->buf, (src)->size)) + +CORK_API void +cork_buffer_append(struct cork_buffer *buffer, const void *src, size_t length); + + +CORK_API void +cork_buffer_set_string(struct cork_buffer *buffer, const char *str); + +CORK_API void +cork_buffer_append_string(struct cork_buffer *buffer, const char *str); + +#define cork_buffer_set_literal(buffer, str) \ + (cork_buffer_set((buffer), (str), sizeof((str)) - 1)) + +#define cork_buffer_append_literal(buffer, str) \ + (cork_buffer_append((buffer), (str), sizeof((str)) - 1)) + + +CORK_API void +cork_buffer_printf(struct cork_buffer *buffer, const char *format, ...) + CORK_ATTR_PRINTF(2,3); + +CORK_API void +cork_buffer_append_printf(struct cork_buffer *buffer, const char *format, ...) + CORK_ATTR_PRINTF(2,3); + +CORK_API void +cork_buffer_vprintf(struct cork_buffer *buffer, const char *format, + va_list args) + CORK_ATTR_PRINTF(2,0); + +CORK_API void +cork_buffer_append_vprintf(struct cork_buffer *buffer, const char *format, + va_list args) + CORK_ATTR_PRINTF(2,0); + + +/*----------------------------------------------------------------------- + * Some helpers for pretty-printing data + */ + +CORK_API void +cork_buffer_append_indent(struct cork_buffer *buffer, size_t indent); + +CORK_API void +cork_buffer_append_c_string(struct cork_buffer *buffer, + const char *src, size_t length); + +CORK_API void +cork_buffer_append_hex_dump(struct cork_buffer *buffer, size_t indent, + const char *src, size_t length); + +CORK_API void +cork_buffer_append_multiline(struct cork_buffer *buffer, size_t indent, + const char *src, size_t length); + +CORK_API void +cork_buffer_append_binary(struct cork_buffer *buffer, size_t indent, + const char *src, size_t length); + + +/*----------------------------------------------------------------------- + * Buffer's managed buffer/slice implementation + */ + +#include +#include + +CORK_API struct cork_managed_buffer * +cork_buffer_to_managed_buffer(struct cork_buffer *buffer); + +CORK_API int +cork_buffer_to_slice(struct cork_buffer *buffer, struct cork_slice *slice); + + +/*----------------------------------------------------------------------- + * Buffer's stream consumer implementation + */ + +#include + +CORK_API struct cork_stream_consumer * +cork_buffer_to_stream_consumer(struct cork_buffer *buffer); + + +#endif /* LIBCORK_DS_BUFFER_H */ diff --git a/3rd/libcork/include/ds/dllist.h b/3rd/libcork/include/ds/dllist.h new file mode 100644 index 0000000..7fc22bf --- /dev/null +++ b/3rd/libcork/include/ds/dllist.h @@ -0,0 +1,151 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_DLLIST_H +#define LIBCORK_DS_DLLIST_H + +#include +#include + + +struct cork_dllist_item { + /* A pointer to the next element in the list. */ + struct cork_dllist_item *next; + /* A pointer to the previous element in the list. */ + struct cork_dllist_item *prev; +}; + + +struct cork_dllist { + /* The sentinel element for this list. */ + struct cork_dllist_item head; +}; + +#define CORK_DLLIST_INIT(list) { { &(list).head, &(list).head } } + +#define cork_dllist_init(list) \ + do { \ + (list)->head.next = &(list)->head; \ + (list)->head.prev = &(list)->head; \ + } while (0) + + + +/* DEPRECATED! Use cork_dllist_foreach or cork_dllist_visit instead. */ +typedef void +(*cork_dllist_map_func)(struct cork_dllist_item *element, void *user_data); + +CORK_API void +cork_dllist_map(struct cork_dllist *list, + cork_dllist_map_func func, void *user_data); + + +typedef int +cork_dllist_visit_f(void *ud, struct cork_dllist_item *element); + +CORK_API int +cork_dllist_visit(struct cork_dllist *list, void *ud, + cork_dllist_visit_f *visit); + + +#define cork_dllist_foreach_void(list, curr, _next) \ + for ((curr) = cork_dllist_start((list)), (_next) = (curr)->next; \ + !cork_dllist_is_end((list), (curr)); \ + (curr) = (_next), (_next) = (curr)->next) + +#define cork_dllist_foreach(list, curr, _next, etype, element, item_field) \ + for ((curr) = cork_dllist_start((list)), (_next) = (curr)->next, \ + (element) = cork_container_of((curr), etype, item_field); \ + !cork_dllist_is_end((list), (curr)); \ + (curr) = (_next), (_next) = (curr)->next, \ + (element) = cork_container_of((curr), etype, item_field)) + + +CORK_API size_t +cork_dllist_size(const struct cork_dllist *list); + + +#define cork_dllist_add_after(pred, element) \ + do { \ + (element)->prev = (pred); \ + (element)->next = (pred)->next; \ + (pred)->next->prev = (element); \ + (pred)->next = (element); \ + } while (0) + +#define cork_dllist_add_before(succ, element) \ + do { \ + (element)->next = (succ); \ + (element)->prev = (succ)->prev; \ + (succ)->prev->next = (element); \ + (succ)->prev = (element); \ + } while (0) + +#define cork_dllist_add_to_head(list, element) \ + cork_dllist_add_after(&(list)->head, (element)) + +#define cork_dllist_add_to_tail(list, element) \ + cork_dllist_add_before(&(list)->head, (element)) + +#define cork_dllist_add cork_dllist_add_to_tail + + +#define cork_dllist_add_list_to_head(dest, src) \ + do { \ + struct cork_dllist_item *dest_start = cork_dllist_start(dest); \ + struct cork_dllist_item *src_start = cork_dllist_start(src); \ + dest_start->prev = &(src)->head; \ + src_start->prev = &(dest)->head; \ + (src)->head.next = dest_start; \ + (dest)->head.next = src_start; \ + cork_dllist_remove(&(src)->head); \ + cork_dllist_init(src); \ + } while (0) + +#define cork_dllist_add_list_to_tail(dest, src) \ + do { \ + struct cork_dllist_item *dest_end = cork_dllist_end(dest); \ + struct cork_dllist_item *src_end = cork_dllist_end(src); \ + dest_end->next = &(src)->head; \ + src_end->next = &(dest)->head; \ + (src)->head.prev = dest_end; \ + (dest)->head.prev = src_end; \ + cork_dllist_remove(&(src)->head); \ + cork_dllist_init(src); \ + } while (0) + + +#define cork_dllist_remove(element) \ + do { \ + (element)->prev->next = (element)->next; \ + (element)->next->prev = (element)->prev; \ + } while (0) + + +#define cork_dllist_is_empty(list) \ + (cork_dllist_is_end((list), cork_dllist_start((list)))) + + +#define cork_dllist_head(list) \ + (((list)->head.next == &(list)->head)? NULL: (list)->head.next) +#define cork_dllist_tail(list) \ + (((list)->head.prev == &(list)->head)? NULL: (list)->head.prev) + +#define cork_dllist_start(list) \ + ((list)->head.next) +#define cork_dllist_end(list) \ + ((list)->head.prev) + +#define cork_dllist_is_start(list, element) \ + ((element) == &(list)->head) +#define cork_dllist_is_end(list, element) \ + ((element) == &(list)->head) + + +#endif /* LIBCORK_DS_DLLIST_H */ diff --git a/3rd/libcork/include/ds/hash-table.h b/3rd/libcork/include/ds/hash-table.h new file mode 100644 index 0000000..6a0eee4 --- /dev/null +++ b/3rd/libcork/include/ds/hash-table.h @@ -0,0 +1,159 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_HASH_TABLE_H +#define LIBCORK_DS_HASH_TABLE_H + +#include +#include +#include +#include +#include +#include + + +/*----------------------------------------------------------------------- + * Hash tables + */ + +struct cork_hash_table_entry { + cork_hash hash; + void *key; + void *value; +}; + + +struct cork_hash_table; + +CORK_API struct cork_hash_table * +cork_hash_table_new(size_t initial_size, unsigned int flags); + +CORK_API void +cork_hash_table_free(struct cork_hash_table *table); + + +CORK_API void +cork_hash_table_set_user_data(struct cork_hash_table *table, + void *user_data, cork_free_f free_user_data); + +CORK_API void +cork_hash_table_set_equals(struct cork_hash_table *table, cork_equals_f equals); + +CORK_API void +cork_hash_table_set_free_key(struct cork_hash_table *table, cork_free_f free); + +CORK_API void +cork_hash_table_set_free_value(struct cork_hash_table *table, cork_free_f free); + +CORK_API void +cork_hash_table_set_hash(struct cork_hash_table *table, cork_hash_f hash); + + +CORK_API void +cork_hash_table_clear(struct cork_hash_table *table); + + +CORK_API void +cork_hash_table_ensure_size(struct cork_hash_table *table, + size_t desired_count); + +CORK_API size_t +cork_hash_table_size(const struct cork_hash_table *table); + + +CORK_API void * +cork_hash_table_get(const struct cork_hash_table *table, const void *key); + +CORK_API void * +cork_hash_table_get_hash(const struct cork_hash_table *table, + cork_hash hash, const void *key); + +CORK_API struct cork_hash_table_entry * +cork_hash_table_get_entry(const struct cork_hash_table *table, + const void *key); + +CORK_API struct cork_hash_table_entry * +cork_hash_table_get_entry_hash(const struct cork_hash_table *table, + cork_hash hash, const void *key); + +CORK_API struct cork_hash_table_entry * +cork_hash_table_get_or_create(struct cork_hash_table *table, + void *key, bool *is_new); + +CORK_API struct cork_hash_table_entry * +cork_hash_table_get_or_create_hash(struct cork_hash_table *table, + cork_hash hash, void *key, bool *is_new); + +CORK_API void +cork_hash_table_put(struct cork_hash_table *table, + void *key, void *value, + bool *is_new, void **old_key, void **old_value); + +CORK_API void +cork_hash_table_put_hash(struct cork_hash_table *table, + cork_hash hash, void *key, void *value, + bool *is_new, void **old_key, void **old_value); + +CORK_API void +cork_hash_table_delete_entry(struct cork_hash_table *table, + struct cork_hash_table_entry *entry); + +CORK_API bool +cork_hash_table_delete(struct cork_hash_table *table, const void *key, + void **deleted_key, void **deleted_value); + +CORK_API bool +cork_hash_table_delete_hash(struct cork_hash_table *table, + cork_hash hash, const void *key, + void **deleted_key, void **deleted_value); + + +enum cork_hash_table_map_result { + /* Abort the current @ref cork_hash_table_map operation. */ + CORK_HASH_TABLE_MAP_ABORT = 0, + /* Continue on to the next entry in the hash table. */ + CORK_HASH_TABLE_MAP_CONTINUE = 1, + /* Delete the entry that was just processed, and then continue on to + * the next entry in the hash table. */ + CORK_HASH_TABLE_MAP_DELETE = 2 +}; + +typedef enum cork_hash_table_map_result +(*cork_hash_table_map_f)(void *user_data, struct cork_hash_table_entry *entry); + +CORK_API void +cork_hash_table_map(struct cork_hash_table *table, void *user_data, + cork_hash_table_map_f mapper); + + +struct cork_hash_table_iterator { + struct cork_hash_table *table; + void *priv; +}; + +CORK_API void +cork_hash_table_iterator_init(struct cork_hash_table *table, + struct cork_hash_table_iterator *iterator); + +CORK_API struct cork_hash_table_entry * +cork_hash_table_iterator_next(struct cork_hash_table_iterator *iterator); + + +/*----------------------------------------------------------------------- + * Built-in key types + */ + +CORK_API struct cork_hash_table * +cork_string_hash_table_new(size_t initial_size, unsigned int flags); + +CORK_API struct cork_hash_table * +cork_pointer_hash_table_new(size_t initial_size, unsigned int flags); + + +#endif /* LIBCORK_DS_HASH_TABLE_H */ diff --git a/3rd/libcork/include/ds/managed-buffer.h b/3rd/libcork/include/ds/managed-buffer.h new file mode 100644 index 0000000..e74ef3b --- /dev/null +++ b/3rd/libcork/include/ds/managed-buffer.h @@ -0,0 +1,76 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_MANAGED_BUFFER_H +#define LIBCORK_DS_MANAGED_BUFFER_H + +#include +#include +#include + + +/*----------------------------------------------------------------------- + * Managed buffers + */ + +struct cork_managed_buffer; + +struct cork_managed_buffer_iface { + /* Free the contents of a managed buffer, and the managed buffer + * object itself. */ + void + (*free)(struct cork_managed_buffer *buf); +}; + + +struct cork_managed_buffer { + /* The buffer that this instance manages */ + const void *buf; + /* The size of buf */ + size_t size; + /* A reference count for the buffer. If this drops to 0, the buffer + * will be finalized. */ + volatile int ref_count; + /* The managed buffer implementation for this instance. */ + struct cork_managed_buffer_iface *iface; +}; + + +CORK_API struct cork_managed_buffer * +cork_managed_buffer_new_copy(const void *buf, size_t size); + + +typedef void +(*cork_managed_buffer_freer)(void *buf, size_t size); + +CORK_API struct cork_managed_buffer * +cork_managed_buffer_new(const void *buf, size_t size, + cork_managed_buffer_freer free); + + +CORK_API struct cork_managed_buffer * +cork_managed_buffer_ref(struct cork_managed_buffer *buf); + +CORK_API void +cork_managed_buffer_unref(struct cork_managed_buffer *buf); + + +CORK_API int +cork_managed_buffer_slice(struct cork_slice *dest, + struct cork_managed_buffer *buffer, + size_t offset, size_t length); + +CORK_API int +cork_managed_buffer_slice_offset(struct cork_slice *dest, + struct cork_managed_buffer *buffer, + size_t offset); + + +#endif /* LIBCORK_DS_MANAGED_BUFFER_H */ diff --git a/3rd/libcork/include/ds/ring-buffer.h b/3rd/libcork/include/ds/ring-buffer.h new file mode 100644 index 0000000..d76affd --- /dev/null +++ b/3rd/libcork/include/ds/ring-buffer.h @@ -0,0 +1,60 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_RING_BUFFER_H +#define LIBCORK_DS_RING_BUFFER_H + +#include +#include + + +struct cork_ring_buffer { + /* The elements of the ring buffer */ + void **elements; + /* The number of elements that can be stored in this ring + * buffer. */ + size_t allocated_size; + /* The actual number of elements currently in the ring buffer. */ + size_t size; + /* The index of the next element to read from the buffer */ + size_t read_index; + /* The index of the next element to write into the buffer */ + size_t write_index; +}; + + +CORK_API int +cork_ring_buffer_init(struct cork_ring_buffer *buf, size_t size); + +CORK_API struct cork_ring_buffer * +cork_ring_buffer_new(size_t size); + +CORK_API void +cork_ring_buffer_done(struct cork_ring_buffer *buf); + +CORK_API void +cork_ring_buffer_free(struct cork_ring_buffer *buf); + + +#define cork_ring_buffer_is_empty(buf) ((buf)->size == 0) +#define cork_ring_buffer_is_full(buf) ((buf)->size == (buf)->allocated_size) + + +CORK_API int +cork_ring_buffer_add(struct cork_ring_buffer *buf, void *element); + +CORK_API void * +cork_ring_buffer_pop(struct cork_ring_buffer *buf); + +CORK_API void * +cork_ring_buffer_peek(struct cork_ring_buffer *buf); + + +#endif /* LIBCORK_DS_RING_BUFFER_H */ diff --git a/3rd/libcork/include/ds/slice.h b/3rd/libcork/include/ds/slice.h new file mode 100644 index 0000000..9daefee --- /dev/null +++ b/3rd/libcork/include/ds/slice.h @@ -0,0 +1,151 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_SLICE_H +#define LIBCORK_DS_SLICE_H + +#include +#include + + +/*----------------------------------------------------------------------- + * Error handling + */ + +/* hash of "libcork/ds/slice.h" */ +#define CORK_SLICE_ERROR 0x960ca750 + +enum cork_slice_error { + /* Trying to slice a nonexistent subset of a buffer */ + CORK_SLICE_INVALID_SLICE +}; + + +/*----------------------------------------------------------------------- + * Slices + */ + +struct cork_slice; + +struct cork_slice_iface { + /* Free the slice. Can be NULL if you don't need to free any + * underlying buffer. */ + void + (*free)(struct cork_slice *self); + + /* Create a copy of a slice. You can assume that offset and length + * refer to a valid subset of the buffer. */ + int + (*copy)(struct cork_slice *dest, const struct cork_slice *self, + size_t offset, size_t length); + + /* Create a “light” copy of a slice. A light copy is not allowed to exist + * longer than the slice that it was copied from, which can sometimes let + * you perform less work to produce the copy. You can assume that offset + * and length refer to a valid subset of the buffer. */ + int + (*light_copy)(struct cork_slice *dest, const struct cork_slice *self, + size_t offset, size_t length); + + /* Update the current slice to point at a different subset. You can + * assume that offset and length refer to a valid subset of the + * buffer. Can be NULL if you don't need to do anything special to + * the underlying buffer; in this case, we'll update the slice's buf + * and size fields for you. */ + int + (*slice)(struct cork_slice *self, size_t offset, size_t length); +}; + + +struct cork_slice { + /* The beginning of the sliced portion of the buffer. */ + const void *buf; + /* The length of the sliced portion of the buffer. */ + size_t size; + /* The slice implementation of the underlying buffer. */ + struct cork_slice_iface *iface; + /* An opaque pointer used by the slice implementation to refer to + * the underlying buffer. */ + void *user_data; +}; + + +CORK_API void +cork_slice_clear(struct cork_slice *slice); + +#define cork_slice_is_empty(slice) ((slice)->buf == NULL) + + +CORK_API int +cork_slice_copy(struct cork_slice *dest, const struct cork_slice *slice, + size_t offset, size_t length); + +#define cork_slice_copy_fast(dest, slice, offset, length) \ + ((slice)->iface->copy((dest), (slice), (offset), (length))) + +CORK_API int +cork_slice_copy_offset(struct cork_slice *dest, const struct cork_slice *slice, + size_t offset); + +#define cork_slice_copy_offset_fast(dest, slice, offset) \ + ((slice)->iface->copy \ + ((dest), (slice), (offset), (slice)->size - (offset))) + + +CORK_API int +cork_slice_light_copy(struct cork_slice *dest, const struct cork_slice *slice, + size_t offset, size_t length); + +#define cork_slice_light_copy_fast(dest, slice, offset, length) \ + ((slice)->iface->light_copy((dest), (slice), (offset), (length))) + +CORK_API int +cork_slice_light_copy_offset(struct cork_slice *dest, + const struct cork_slice *slice, size_t offset); + +#define cork_slice_light_copy_offset_fast(dest, slice, offset) \ + ((slice)->iface->light_copy \ + ((dest), (slice), (offset), (slice)->size - (offset))) + + +CORK_API int +cork_slice_slice(struct cork_slice *slice, size_t offset, size_t length); + +#define cork_slice_slice_fast(_slice, offset, length) \ + ((_slice)->iface->slice == NULL? \ + ((_slice)->buf += (offset), (_slice)->size = (length), 0): \ + ((_slice)->iface->slice((_slice), (offset), (length)))) + +CORK_API int +cork_slice_slice_offset(struct cork_slice *slice, size_t offset); + +#define cork_slice_slice_offset_fast(_slice, offset) \ + ((_slice)->iface->slice == NULL? \ + ((_slice)->buf += (offset), (_slice)->size -= (offset), 0): \ + ((_slice)->iface->slice \ + ((_slice), (offset), (_slice)->size - (offset)))) + + +CORK_API void +cork_slice_finish(struct cork_slice *slice); + +CORK_API bool +cork_slice_equal(const struct cork_slice *slice1, + const struct cork_slice *slice2); + +CORK_API void +cork_slice_init_static(struct cork_slice *dest, const void *buf, size_t size); + +CORK_API void +cork_slice_init_copy_once(struct cork_slice *dest, const void *buf, + size_t size); + + +#endif /* LIBCORK_DS_SLICE_H */ diff --git a/3rd/libcork/include/ds/stream.h b/3rd/libcork/include/ds/stream.h new file mode 100644 index 0000000..b369725 --- /dev/null +++ b/3rd/libcork/include/ds/stream.h @@ -0,0 +1,64 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_DS_STREAM_H +#define LIBCORK_DS_STREAM_H + +#include + +#include +#include + + +struct cork_stream_consumer { + int + (*data)(struct cork_stream_consumer *consumer, + const void *buf, size_t size, bool is_first_chunk); + + int + (*eof)(struct cork_stream_consumer *consumer); + + void + (*free)(struct cork_stream_consumer *consumer); +}; + + +#define cork_stream_consumer_data(consumer, buf, size, is_first) \ + ((consumer)->data((consumer), (buf), (size), (is_first))) + +#define cork_stream_consumer_eof(consumer) \ + ((consumer)->eof((consumer))) + +#define cork_stream_consumer_free(consumer) \ + ((consumer)->free((consumer))) + + +CORK_API int +cork_consume_fd(struct cork_stream_consumer *consumer, int fd); + +CORK_API int +cork_consume_file(struct cork_stream_consumer *consumer, FILE *fp); + +CORK_API int +cork_consume_file_from_path(struct cork_stream_consumer *consumer, + const char *path, int flags); + + +CORK_API struct cork_stream_consumer * +cork_fd_consumer_new(int fd); + +CORK_API struct cork_stream_consumer * +cork_file_consumer_new(FILE *fp); + +CORK_API struct cork_stream_consumer * +cork_file_from_path_consumer_new(const char *path, int flags); + + +#endif /* LIBCORK_DS_STREAM_H */ diff --git a/3rd/libcork/include/helpers/errors.h b/3rd/libcork/include/helpers/errors.h new file mode 100644 index 0000000..3776675 --- /dev/null +++ b/3rd/libcork/include/helpers/errors.h @@ -0,0 +1,142 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_HELPERS_ERRORS_H +#define LIBCORK_HELPERS_ERRORS_H + + +/* This header is *not* automatically included when you include + * libcork/core.h, since we define some macros that don't include a + * cork_ or CORK_ prefix. Don't want to pollute your namespace unless + * you ask for it! */ + + +#include +#include +#include + + +#if !defined(CORK_PRINT_ERRORS) +#define CORK_PRINT_ERRORS 0 +#endif + +#if !defined(CORK_PRINT_ERROR) +#if CORK_PRINT_ERRORS +#include +#define CORK_PRINT_ERROR_(func, file, line) \ + fprintf(stderr, "---\nError in %s (%s:%u)\n %s\n", \ + (func), (file), (unsigned int) (line), \ + cork_error_message()); +#define CORK_PRINT_ERROR() CORK_PRINT_ERROR_(__func__, __FILE__, __LINE__) +#else +#define CORK_PRINT_ERROR() /* do nothing */ +#endif +#endif + + +/* A bunch of macros for calling a function that returns an error. If + * an error occurs, it will automatically be propagated out as the + * result of your own function. With these macros, you won't have a + * check to check or modify the error condition; it's returned as-is. + * + * XZ_check + * + * where: + * + * X = what happens if an error occurs + * "e" = jump to the "error" label + * "rY" = return a default error result (Y defined below) + * "x" = return an error result that you specify + * + * Y = your return type + * "i" = int + * "p" = some pointer type + * + * Z = the return type of the function you're calling + * "e" = use cork_error_occurred() to check + * "i" = int + * "p" = some pointer type + * + * In all cases, we assume that your function has a cork_error parameter + * called "err". + */ + + +/* jump to "error" label */ + +#define ee_check(call) \ + do { \ + (call); \ + if (CORK_UNLIKELY(cork_error_occurred())) { \ + CORK_PRINT_ERROR(); \ + goto error; \ + } \ + } while (0) + +#define ei_check(call) \ + do { \ + int __rc = (call); \ + if (CORK_UNLIKELY(__rc != 0)) { \ + CORK_PRINT_ERROR(); \ + goto error; \ + } \ + } while (0) + +#define ep_check(call) \ + do { \ + const void *__result = (call); \ + if (CORK_UNLIKELY(__result == NULL)) { \ + CORK_PRINT_ERROR(); \ + goto error; \ + } \ + } while (0) + + +/* return specific error code */ + +#define xe_check(result, call) \ + do { \ + (call); \ + if (CORK_UNLIKELY(cork_error_occurred())) { \ + CORK_PRINT_ERROR(); \ + return result; \ + } \ + } while (0) + +#define xi_check(result, call) \ + do { \ + int __rc = (call); \ + if (CORK_UNLIKELY(__rc != 0)) { \ + CORK_PRINT_ERROR(); \ + return result; \ + } \ + } while (0) + +#define xp_check(result, call) \ + do { \ + const void *__result = (call); \ + if (CORK_UNLIKELY(__result == NULL)) { \ + CORK_PRINT_ERROR(); \ + return result; \ + } \ + } while (0) + + +/* return default error code */ + +#define rie_check(call) xe_check(-1, call) +#define rii_check(call) xi_check(__rc, call) +#define rip_check(call) xp_check(-1, call) +#define rpe_check(call) xe_check(NULL, call) +#define rpi_check(call) xi_check(NULL, call) +#define rpp_check(call) xp_check(NULL, call) + + +#endif /* LIBCORK_HELPERS_ERRORS_H */ diff --git a/3rd/libcork/include/helpers/gc.h b/3rd/libcork/include/helpers/gc.h new file mode 100644 index 0000000..4374244 --- /dev/null +++ b/3rd/libcork/include/helpers/gc.h @@ -0,0 +1,51 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_HELPERS_REFCOUNT_H +#define LIBCORK_HELPERS_REFCOUNT_H + + +#include +#include + + +#define _free_(name) \ +static void \ +name##__free(void *obj) + + +#define _recurse_(name) \ +static void \ +name##__recurse(struct cork_gc *gc, void *obj, \ + cork_gc_recurser recurse, void *ud) + + +#define _gc_(name) \ +static struct cork_gc_obj_iface name##__gc = { \ + name##__free, name##__recurse \ +}; + +#define _gc_no_free_(name) \ +static struct cork_gc_obj_iface name##__gc = { \ + NULL, name##__recurse \ +}; + +#define _gc_no_recurse_(name) \ +static struct cork_gc_obj_iface name##__gc = { \ + name##__free, NULL \ +}; + +#define _gc_leaf_(name) \ +static struct cork_gc_obj_iface name##__gc = { \ + NULL, NULL \ +}; + + +#endif /* LIBCORK_HELPERS_REFCOUNT_H */ diff --git a/3rd/libcork/include/helpers/posix.h b/3rd/libcork/include/helpers/posix.h new file mode 100644 index 0000000..7a933d5 --- /dev/null +++ b/3rd/libcork/include/helpers/posix.h @@ -0,0 +1,87 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_HELPERS_POSIX_H +#define LIBCORK_HELPERS_POSIX_H + +/* This header is *not* automatically included when you include + * libcork/core.h, since we define some macros that don't include a + * cork_ or CORK_ prefix. Don't want to pollute your namespace unless + * you ask for it! */ + +#include + +#include +#include +#include + + +#if !defined(CORK_PRINT_ERRORS) +#define CORK_PRINT_ERRORS 0 +#endif + +#if !defined(CORK_PRINT_ERROR) +#if CORK_PRINT_ERRORS +#include +#define CORK_PRINT_ERROR_(func, file, line) \ + fprintf(stderr, "---\nError in %s (%s:%u)\n %s\n", \ + (func), (file), (unsigned int) (line), \ + cork_error_message()); +#define CORK_PRINT_ERROR() CORK_PRINT_ERROR_(__func__, __FILE__, __LINE__) +#else +#define CORK_PRINT_ERROR() /* do nothing */ +#endif +#endif + + +#define xi_check_posix(call, on_error) \ + do { \ + while (true) { \ + if ((call) == -1) { \ + if (errno == EINTR) { \ + continue; \ + } else { \ + cork_system_error_set(); \ + CORK_PRINT_ERROR(); \ + on_error; \ + } \ + } else { \ + break; \ + } \ + } \ + } while (0) + +#define xp_check_posix(call, on_error) \ + do { \ + while (true) { \ + if ((call) == NULL) { \ + if (errno == EINTR) { \ + continue; \ + } else { \ + cork_system_error_set(); \ + CORK_PRINT_ERROR(); \ + on_error; \ + } \ + } else { \ + break; \ + } \ + } \ + } while (0) + + +#define ei_check_posix(call) xi_check_posix(call, goto error) +#define rii_check_posix(call) xi_check_posix(call, return -1) +#define rpi_check_posix(call) xi_check_posix(call, return NULL) + +#define ep_check_posix(call) xp_check_posix(call, goto error) +#define rip_check_posix(call) xp_check_posix(call, return -1) +#define rpp_check_posix(call) xp_check_posix(call, return NULL) + + +#endif /* LIBCORK_HELPERS_POSIX_H */ diff --git a/3rd/libcork/include/os.h b/3rd/libcork/include/os.h new file mode 100644 index 0000000..1163962 --- /dev/null +++ b/3rd/libcork/include/os.h @@ -0,0 +1,20 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_OS_H +#define LIBCORK_OS_H + +/*** include all of the parts ***/ + +#include +#include +#include + +#endif /* LIBCORK_OS_H */ diff --git a/3rd/libcork/include/os/files.h b/3rd/libcork/include/os/files.h new file mode 100644 index 0000000..82f1f30 --- /dev/null +++ b/3rd/libcork/include/os/files.h @@ -0,0 +1,271 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_FILES_H +#define LIBCORK_CORE_FILES_H + +#include +#include + + +/*----------------------------------------------------------------------- + * Paths + */ + +struct cork_path; + +/* path can be relative or absolute */ +CORK_API struct cork_path * +cork_path_new(const char *path); + +CORK_API struct cork_path * +cork_path_clone(const struct cork_path *other); + +CORK_API void +cork_path_free(struct cork_path *path); + + +CORK_API void +cork_path_set(struct cork_path *path, const char *content); + +CORK_API const char * +cork_path_get(const struct cork_path *path); + + +CORK_API int +cork_path_set_cwd(struct cork_path *path); + +CORK_API struct cork_path * +cork_path_cwd(void); + + +CORK_API int +cork_path_set_absolute(struct cork_path *path); + +CORK_API struct cork_path * +cork_path_absolute(const struct cork_path *other); + + +CORK_API void +cork_path_append(struct cork_path *path, const char *more); + +CORK_API void +cork_path_append_path(struct cork_path *path, const struct cork_path *more); + +CORK_API struct cork_path * +cork_path_join(const struct cork_path *other, const char *more); + +CORK_API struct cork_path * +cork_path_join_path(const struct cork_path *other, + const struct cork_path *more); + + +CORK_API void +cork_path_set_basename(struct cork_path *path); + +CORK_API struct cork_path * +cork_path_basename(const struct cork_path *other); + + +CORK_API void +cork_path_set_dirname(struct cork_path *path); + +CORK_API struct cork_path * +cork_path_dirname(const struct cork_path *other); + + +/*----------------------------------------------------------------------- + * Lists of paths + */ + +struct cork_path_list; + +CORK_API struct cork_path_list * +cork_path_list_new_empty(void); + +/* list must be a colon-separated list of paths */ +CORK_API struct cork_path_list * +cork_path_list_new(const char *list); + +CORK_API void +cork_path_list_free(struct cork_path_list *list); + +CORK_API const char * +cork_path_list_to_string(const struct cork_path_list *list); + +/* Takes control of path. path must not already be in the list. */ +CORK_API void +cork_path_list_add(struct cork_path_list *list, struct cork_path *path); + +CORK_API size_t +cork_path_list_size(const struct cork_path_list *list); + +/* The list still owns path; you must not free it or modify it. */ +CORK_API const struct cork_path * +cork_path_list_get(const struct cork_path_list *list, size_t index); + + +/*----------------------------------------------------------------------- + * Files + */ + +#define CORK_FILE_RECURSIVE 0x0001 +#define CORK_FILE_PERMISSIVE 0x0002 + +typedef unsigned int cork_file_mode; + +enum cork_file_type { + CORK_FILE_MISSING = 0, + CORK_FILE_REGULAR = 1, + CORK_FILE_DIRECTORY = 2, + CORK_FILE_SYMLINK = 3, + CORK_FILE_UNKNOWN = 4 +}; + +struct cork_file; + +CORK_API struct cork_file * +cork_file_new(const char *path); + +/* Takes control of path */ +CORK_API struct cork_file * +cork_file_new_from_path(struct cork_path *path); + +CORK_API void +cork_file_free(struct cork_file *file); + +/* File owns the result; you should not free it */ +CORK_API const struct cork_path * +cork_file_path(struct cork_file *file); + +CORK_API int +cork_file_exists(struct cork_file *file, bool *exists); + +CORK_API int +cork_file_type(struct cork_file *file, enum cork_file_type *type); + + +typedef int +(*cork_file_directory_iterator)(struct cork_file *child, const char *rel_name, + void *user_data); + +CORK_API int +cork_file_iterate_directory(struct cork_file *file, + cork_file_directory_iterator iterator, + void *user_data); + +/* If flags includes CORK_FILE_RECURSIVE, this creates parent directories, + * if needed. If flags doesn't include CORK_FILE_PERMISSIVE, then it's an error + * if the directory already exists. */ +CORK_API int +cork_file_mkdir(struct cork_file *file, cork_file_mode mode, + unsigned int flags); + +/* Removes a file or directory. If file is a directory, and flags contains + * CORK_FILE_RECURSIVE, then all of the directory's contents are removed, too. + * Otherwise, the directory must already be empty. */ +CORK_API int +cork_file_remove(struct cork_file *file, unsigned int flags); + + +CORK_API struct cork_file * +cork_path_list_find_file(const struct cork_path_list *list, + const char *rel_path); + + +/*----------------------------------------------------------------------- + * Lists of files + */ + +struct cork_file_list; + +CORK_API struct cork_file_list * +cork_file_list_new_empty(void); + +CORK_API struct cork_file_list * +cork_file_list_new(struct cork_path_list *path_list); + +CORK_API void +cork_file_list_free(struct cork_file_list *list); + +/* Takes control of file. file must not already be in the list. */ +CORK_API void +cork_file_list_add(struct cork_file_list *list, struct cork_file *file); + +CORK_API size_t +cork_file_list_size(struct cork_file_list *list); + +/* The list still owns file; you must not free it. Editing the file updates the + * entry in the list. */ +CORK_API struct cork_file * +cork_file_list_get(struct cork_file_list *list, size_t index); + + +CORK_API struct cork_file_list * +cork_path_list_find_files(const struct cork_path_list *list, + const char *rel_path); + + +/*----------------------------------------------------------------------- + * Walking a directory tree + */ + +#define CORK_SKIP_DIRECTORY 1 + +struct cork_dir_walker { + int + (*enter_directory)(struct cork_dir_walker *walker, const char *full_path, + const char *rel_path, const char *base_name); + + int + (*file)(struct cork_dir_walker *walker, const char *full_path, + const char *rel_path, const char *base_name); + + int + (*leave_directory)(struct cork_dir_walker *walker, const char *full_path, + const char *rel_path, const char *base_name); +}; + +#define cork_dir_walker_enter_directory(w, fp, rp, bn) \ + ((w)->enter_directory((w), (fp), (rp), (bn))) + +#define cork_dir_walker_file(w, fp, rp, bn) \ + ((w)->file((w), (fp), (rp), (bn))) + +#define cork_dir_walker_leave_directory(w, fp, rp, bn) \ + ((w)->leave_directory((w), (fp), (rp), (bn))) + + +CORK_API int +cork_walk_directory(const char *path, struct cork_dir_walker *walker); + + +/*----------------------------------------------------------------------- + * Standard paths and path lists + */ + +CORK_API struct cork_path * +cork_path_home(void); + + +CORK_API struct cork_path_list * +cork_path_config_paths(void); + +CORK_API struct cork_path_list * +cork_path_data_paths(void); + +CORK_API struct cork_path * +cork_path_user_cache_path(void); + +CORK_API struct cork_path * +cork_path_user_runtime_path(void); + + +#endif /* LIBCORK_CORE_FILES_H */ diff --git a/3rd/libcork/include/os/process.h b/3rd/libcork/include/os/process.h new file mode 100644 index 0000000..5d7813d --- /dev/null +++ b/3rd/libcork/include/os/process.h @@ -0,0 +1,28 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_CORE_PROCESS_H +#define LIBCORK_CORE_PROCESS_H + +#include + + +typedef void +(*cork_cleanup_function)(void); + +CORK_API void +cork_cleanup_at_exit_named(const char *name, int priority, + cork_cleanup_function function); + +#define cork_cleanup_at_exit(priority, function) \ + cork_cleanup_at_exit_named(#function, priority, function) + + +#endif /* LIBCORK_CORE_PROCESS_H */ diff --git a/3rd/libcork/include/os/subprocess.h b/3rd/libcork/include/os/subprocess.h new file mode 100644 index 0000000..31c1eb5 --- /dev/null +++ b/3rd/libcork/include/os/subprocess.h @@ -0,0 +1,197 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_OS_SUBPROCESS_H +#define LIBCORK_OS_SUBPROCESS_H + +#include + +#include +#include +#include +#include +#include + + +/*----------------------------------------------------------------------- + * Environments + */ + +struct cork_env; + +CORK_API struct cork_env * +cork_env_new(void); + +CORK_API struct cork_env * +cork_env_clone_current(void); + +CORK_API void +cork_env_free(struct cork_env *env); + + +CORK_API void +cork_env_replace_current(struct cork_env *env); + + +/* For all of the following, if env is NULL, these functions access or update + * the actual environment of the current process. Otherwise, they act on the + * given environment instance. */ + +CORK_API const char * +cork_env_get(struct cork_env *env, const char *name); + +CORK_API void +cork_env_add(struct cork_env *env, const char *name, const char *value); + +CORK_API void +cork_env_add_printf(struct cork_env *env, const char *name, + const char *format, ...) + CORK_ATTR_PRINTF(3,4); + +CORK_API void +cork_env_add_vprintf(struct cork_env *env, const char *name, + const char *format, va_list args) + CORK_ATTR_PRINTF(3,0); + +CORK_API void +cork_env_remove(struct cork_env *env, const char *name); + + +/*----------------------------------------------------------------------- + * Executing another process + */ + +struct cork_exec; + +CORK_API struct cork_exec * +cork_exec_new(const char *program); + +CORK_ATTR_SENTINEL +CORK_API struct cork_exec * +cork_exec_new_with_params(const char *program, ...); + +CORK_API struct cork_exec * +cork_exec_new_with_param_array(const char *program, char * const *params); + +CORK_API void +cork_exec_free(struct cork_exec *exec); + +CORK_API const char * +cork_exec_description(struct cork_exec *exec); + +CORK_API const char * +cork_exec_program(struct cork_exec *exec); + +CORK_API size_t +cork_exec_param_count(struct cork_exec *exec); + +CORK_API const char * +cork_exec_param(struct cork_exec *exec, size_t index); + +CORK_API void +cork_exec_add_param(struct cork_exec *exec, const char *param); + +/* Can return NULL */ +CORK_API struct cork_env * +cork_exec_env(struct cork_exec *exec); + +/* Takes control of env */ +CORK_API void +cork_exec_set_env(struct cork_exec *exec, struct cork_env *env); + +/* Can return NULL */ +CORK_API const char * +cork_exec_cwd(struct cork_exec *exec); + +CORK_API void +cork_exec_set_cwd(struct cork_exec *exec, const char *directory); + +CORK_API int +cork_exec_run(struct cork_exec *exec); + + +/*----------------------------------------------------------------------- + * Subprocesses + */ + +struct cork_subprocess; + +/* If env is NULL, we use the environment variables of the calling process. */ + +/* Takes control of body */ +CORK_API struct cork_subprocess * +cork_subprocess_new(void *user_data, cork_free_f free_user_data, + cork_run_f run, + struct cork_stream_consumer *stdout_consumer, + struct cork_stream_consumer *stderr_consumer, + int *exit_code); + +/* Takes control of exec */ +CORK_API struct cork_subprocess * +cork_subprocess_new_exec(struct cork_exec *exec, + struct cork_stream_consumer *stdout_consumer, + struct cork_stream_consumer *stderr_consumer, + int *exit_code); + +CORK_API void +cork_subprocess_free(struct cork_subprocess *sub); + +CORK_API struct cork_stream_consumer * +cork_subprocess_stdin(struct cork_subprocess *sub); + +CORK_API int +cork_subprocess_start(struct cork_subprocess *sub); + +CORK_API bool +cork_subprocess_is_finished(struct cork_subprocess *sub); + +CORK_API int +cork_subprocess_abort(struct cork_subprocess *sub); + +CORK_API bool +cork_subprocess_drain(struct cork_subprocess *sub); + +CORK_API int +cork_subprocess_wait(struct cork_subprocess *sub); + + +/*----------------------------------------------------------------------- + * Groups of subprocesses + */ + +struct cork_subprocess_group; + +CORK_API struct cork_subprocess_group * +cork_subprocess_group_new(void); + +CORK_API void +cork_subprocess_group_free(struct cork_subprocess_group *group); + +/* Takes control of sub */ +CORK_API void +cork_subprocess_group_add(struct cork_subprocess_group *group, + struct cork_subprocess *sub); + +CORK_API int +cork_subprocess_group_start(struct cork_subprocess_group *group); + +CORK_API bool +cork_subprocess_group_is_finished(struct cork_subprocess_group *group); + +CORK_API int +cork_subprocess_group_abort(struct cork_subprocess_group *group); + +CORK_API bool +cork_subprocess_group_drain(struct cork_subprocess_group *group); + +CORK_API int +cork_subprocess_group_wait(struct cork_subprocess_group *group); + + +#endif /* LIBCORK_OS_SUBPROCESS_H */ diff --git a/3rd/libcork/include/threads.h b/3rd/libcork/include/threads.h new file mode 100644 index 0000000..c008419 --- /dev/null +++ b/3rd/libcork/include/threads.h @@ -0,0 +1,19 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_THREADS_H +#define LIBCORK_THREADS_H + +/*** include all of the parts ***/ + +#include +#include + +#endif /* LIBCORK_THREADS_H */ diff --git a/3rd/libcork/include/threads/atomics.h b/3rd/libcork/include/threads/atomics.h new file mode 100644 index 0000000..d1f139b --- /dev/null +++ b/3rd/libcork/include/threads/atomics.h @@ -0,0 +1,50 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_THREADS_ATOMICS_H +#define LIBCORK_THREADS_ATOMICS_H + +#include +#include + +/*----------------------------------------------------------------------- + * GCC intrinsics + */ + +/* Ideally we can use GCC's intrinsics to define everything */ +#if defined(CORK_CONFIG_HAVE_GCC_ATOMICS) + +#define cork_int_atomic_add __sync_add_and_fetch +#define cork_uint_atomic_add __sync_add_and_fetch +#define cork_size_atomic_add __sync_add_and_fetch +#define cork_int_atomic_pre_add __sync_fetch_and_add +#define cork_uint_atomic_pre_add __sync_fetch_and_add +#define cork_size_atomic_pre_add __sync_fetch_and_add +#define cork_int_atomic_sub __sync_sub_and_fetch +#define cork_uint_atomic_sub __sync_sub_and_fetch +#define cork_size_atomic_sub __sync_sub_and_fetch +#define cork_int_atomic_pre_sub __sync_fetch_and_sub +#define cork_uint_atomic_pre_sub __sync_fetch_and_sub +#define cork_size_atomic_pre_sub __sync_fetch_and_sub +#define cork_int_cas __sync_val_compare_and_swap +#define cork_uint_cas __sync_val_compare_and_swap +#define cork_size_cas __sync_val_compare_and_swap +#define cork_ptr_cas __sync_val_compare_and_swap + + +/*----------------------------------------------------------------------- + * End of atomic implementations + */ +#else +#error "No atomics implementation!" +#endif + + +#endif /* LIBCORK_THREADS_ATOMICS_H */ diff --git a/3rd/libcork/include/threads/basics.h b/3rd/libcork/include/threads/basics.h new file mode 100644 index 0000000..6208569 --- /dev/null +++ b/3rd/libcork/include/threads/basics.h @@ -0,0 +1,221 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#ifndef LIBCORK_THREADS_BASICS_H +#define LIBCORK_THREADS_BASICS_H + +#include + +#include +#include +#include +#include + + +/*----------------------------------------------------------------------- + * Thread IDs + */ + +typedef unsigned int cork_thread_id; + +#define CORK_THREAD_NONE ((cork_thread_id) 0) + +/* Returns a valid ID for any thread — even the main thread and threads that + * aren't created by libcork. */ +CORK_API cork_thread_id +cork_current_thread_get_id(void); + + +/*----------------------------------------------------------------------- + * Threads + */ + +struct cork_thread; + +/* Returns NULL for the main thread, and for any thread not created via + * cork_thread_new/cork_thread_start. */ +CORK_API struct cork_thread * +cork_current_thread_get(void); + +CORK_API struct cork_thread * +cork_thread_new(const char *name, + void *user_data, cork_free_f free_user_data, + cork_run_f run); + +/* Thread must not have been started yet. */ +CORK_API void +cork_thread_free(struct cork_thread *thread); + +CORK_API const char * +cork_thread_get_name(struct cork_thread *thread); + +CORK_API cork_thread_id +cork_thread_get_id(struct cork_thread *thread); + +/* Can only be called once per thread. Thread will automatically be freed when + * its done. */ +CORK_API int +cork_thread_start(struct cork_thread *thread); + +/* Can only be called once per thread; must be called after cork_thread_start. */ +CORK_API int +cork_thread_join(struct cork_thread *thread); + + +/*----------------------------------------------------------------------- + * Executing something once + */ + +#if CORK_CONFIG_HAVE_GCC_ASM && (CORK_CONFIG_ARCH_X86 || CORK_CONFIG_ARCH_X64) +#define cork_pause() \ + do { \ + __asm__ __volatile__ ("pause"); \ + } while (0) +#else +#define cork_pause() do { /* do nothing */ } while (0) +#endif + + +#define cork_once_barrier(name) \ + static struct { \ + volatile int barrier; \ + cork_thread_id initializing_thread; \ + } name##__once; + +#define cork_once(name, call) \ + do { \ + if (CORK_LIKELY(name##__once.barrier == 2)) { \ + /* already initialized */ \ + } else { \ + /* Try to claim the ability to perform the initialization */ \ + int prior_state = cork_int_cas(&name##__once.barrier, 0, 1); \ + if (CORK_LIKELY(prior_state == 0)) { \ + CORK_ATTR_UNUSED int result; \ + /* we get to initialize */ \ + call; \ + result = cork_int_cas(&name##__once.barrier, 1, 2); \ + assert(result == 1); \ + } else { \ + /* someone else is initializing, spin/wait until done */ \ + while (name##__once.barrier != 2) { cork_pause(); } \ + } \ + } \ + } while (0) + +#define cork_once_recursive(name, call) \ + do { \ + if (CORK_LIKELY(name##__once.barrier == 2)) { \ + /* already initialized */ \ + } else { \ + /* Try to claim the ability to perform the initialization */ \ + int prior_state = cork_int_cas(&name##__once.barrier, 0, 1); \ + if (CORK_LIKELY(prior_state == 0)) { \ + CORK_ATTR_UNUSED int result; \ + /* we get to initialize */ \ + name##__once.initializing_thread = \ + cork_current_thread_get_id(); \ + call; \ + result = cork_int_cas(&name##__once.barrier, 1, 2); \ + assert(result == 1); \ + } else { \ + /* someone else is initializing, is it us? */ \ + if (name##__once.initializing_thread == \ + cork_current_thread_get_id()) { \ + /* yep, fall through to let our recursion continue */ \ + } else { \ + /* nope; wait for the initialization to finish */ \ + while (name##__once.barrier != 2) { cork_pause(); } \ + } \ + } \ + } \ + } while (0) + + +/*----------------------------------------------------------------------- + * Thread-local storage + */ + +/* Prefer, in order: + * + * 1) __thread storage class + * 2) pthread_key_t + */ + +#if CORK_CONFIG_HAVE_THREAD_STORAGE_CLASS +#define cork_tls(TYPE, NAME) \ +static __thread TYPE NAME##__tls; \ +\ +static TYPE * \ +NAME##_get(void) \ +{ \ + return &NAME##__tls; \ +} + +#define cork_tls_with_alloc(TYPE, NAME, allocate, deallocate) \ + cork_tls(TYPE, NAME) + +#elif CORK_HAVE_PTHREADS +#include +#include + +#include + +#define cork_tls_with_alloc(TYPE, NAME, allocate, deallocate) \ +static pthread_key_t NAME##__tls_key; \ +cork_once_barrier(NAME##__tls_barrier); \ +\ +static void \ +NAME##__tls_destroy(void *self) \ +{ \ + deallocate(self); \ +} \ +\ +static void \ +NAME##__create_key(void) \ +{ \ + CORK_ATTR_UNUSED int rc; \ + rc = pthread_key_create(&NAME##__tls_key, &NAME##__tls_destroy); \ + assert(rc == 0); \ +} \ +\ +static TYPE * \ +NAME##_get(void) \ +{ \ + TYPE *self; \ + cork_once(NAME##__tls_barrier, NAME##__create_key()); \ + self = pthread_getspecific(NAME##__tls_key); \ + if (CORK_UNLIKELY(self == NULL)) { \ + self = allocate(); \ + pthread_setspecific(NAME##__tls_key, self); \ + } \ + return self; \ +} + +#define cork_tls(TYPE, NAME) \ +\ +static TYPE * \ +NAME##__tls_allocate(void) \ +{ \ + return cork_calloc(1, sizeof(TYPE)); \ +} \ +\ +static void \ +NAME##__tls_deallocate(void *vself) \ +{ \ + cork_cfree(vself, 1, sizeof(TYPE)); \ +} \ +\ +cork_tls_with_alloc(TYPE, NAME, NAME##__tls_allocate, NAME##__tls_deallocate); + +#else +#error "No thread-local storage implementation!" +#endif + + +#endif /* LIBCORK_THREADS_BASICS_H */ diff --git a/3rd/libcork/src/cli/commands.c b/3rd/libcork/src/cli/commands.c new file mode 100644 index 0000000..f562076 --- /dev/null +++ b/3rd/libcork/src/cli/commands.c @@ -0,0 +1,225 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "libcork/cli.h" +#include "libcork/core.h" +#include "libcork/ds.h" + + +#define streq(a,b) (strcmp((a), (b)) == 0) + +static struct cork_buffer breadcrumbs_buf = CORK_BUFFER_INIT(); + +static void +cork_command_add_breadcrumb(struct cork_command *command) +{ + cork_buffer_append_printf(&breadcrumbs_buf, " %s", command->name); +} + +#define cork_command_breadcrumbs() ((char *) breadcrumbs_buf.buf) + +static void +cork_command_run(struct cork_command *command, int argc, char **argv); + +static struct cork_command * +cork_command_set_get_subcommand(struct cork_command *command, + const char *command_name) +{ + struct cork_command **curr; + for (curr = command->set; *curr != NULL; curr++) { + if (streq(command_name, (*curr)->name)) { + return *curr; + } + } + return NULL; +} + +static void +cork_command_set_show_help(struct cork_command *command) +{ + size_t max_length = 0; + struct cork_command **curr; + + /* Calculate the length of the longest command name. */ + for (curr = command->set; *curr != NULL; curr++) { + size_t len = strlen((*curr)->name); + if (len > max_length) { + max_length = len; + } + } + + /* Then print out the available commands. */ + printf("Usage:%s []\n" + "\nAvailable commands:\n", + cork_command_breadcrumbs()); + + for (curr = command->set; *curr != NULL; curr++) { + printf(" %*s", (int) -max_length, (*curr)->name); + if ((*curr)->short_desc != NULL) { + printf(" %s\n", (*curr)->short_desc); + } else { + printf("\n"); + } + } +} + +static void +cork_command_leaf_show_help(struct cork_command *command) +{ + printf("Usage:%s", cork_command_breadcrumbs()); + if (command->usage_suffix != NULL) { + printf(" %s", command->usage_suffix); + } + if (command->full_help != NULL) { + printf("\n\n%s", command->full_help); + } else { + printf("\n"); + } +} + +void +cork_command_show_help(struct cork_command *command, const char *message) +{ + if (message != NULL) { + printf("%s\n", message); + } + + if (command->type == CORK_COMMAND_SET) { + cork_command_set_show_help(command); + } else if (command->type == CORK_LEAF_COMMAND) { + cork_command_leaf_show_help(command); + } +} + +static void +cork_command_set_run_help(struct cork_command *command, int argc, char **argv) +{ + /* When we see the help command when processing a command set, we use any + * remaining arguments to identifity which subcommand the user wants help + * with. */ + + /* Skip over the name of the command set */ + argc--; + argv++; + + while (argc > 0 && command->type == CORK_COMMAND_SET) { + struct cork_command *subcommand = + cork_command_set_get_subcommand(command, argv[0]); + if (subcommand == NULL) { + printf("Unknown command \"%s\".\n" + "Usage:%s []\n", + argv[0], cork_command_breadcrumbs()); + exit(EXIT_FAILURE); + } + + cork_command_add_breadcrumb(subcommand); + command = subcommand; + argc--; + argv++; + } + + cork_command_show_help(command, NULL); +} + +static void +cork_command_set_run(struct cork_command *command, int argc, char **argv) +{ + const char *command_name; + struct cork_command *subcommand; + + if (argc == 0) { + printf("No command given.\n"); + cork_command_set_show_help(command); + exit(EXIT_FAILURE); + } + + command_name = argv[0]; + + /* The "help" command is special. */ + if (streq(command_name, "help")) { + cork_command_set_run_help(command, argc, argv); + return; + } + + /* Otherwise look for a real subcommand with this name. */ + subcommand = cork_command_set_get_subcommand(command, command_name); + if (subcommand == NULL) { + printf("Unknown command \"%s\".\n" + "Usage:%s []\n", + command_name, cork_command_breadcrumbs()); + exit(EXIT_FAILURE); + } else { + cork_command_run(subcommand, argc, argv); + } +} + +static void +cork_command_leaf_run(struct cork_command *command, int argc, char **argv) +{ + command->run(argc, argv); +} + +static void +cork_command_cleanup(void) +{ + cork_buffer_done(&breadcrumbs_buf); +} + +static void +cork_command_run(struct cork_command *command, int argc, char **argv) +{ + cork_command_add_breadcrumb(command); + + /* If the gives the --help option at this point, describe the current + * command. */ + if (argc >= 2 && (streq(argv[1], "--help") || streq(argv[1], "-h"))) { + cork_command_show_help(command, NULL); + return; + } + + /* Otherwise let the command parse any options that occur here. */ + if (command->parse_options != NULL) { + int option_count = command->parse_options(argc, argv); + argc -= option_count; + argv += option_count; + } else { + argc--; + argv++; + } + + switch (command->type) { + case CORK_COMMAND_SET: + cork_command_set_run(command, argc, argv); + return; + + case CORK_LEAF_COMMAND: + cork_command_leaf_run(command, argc, argv); + return; + + default: + cork_unreachable(); + } +} + + +int +cork_command_main(struct cork_command *root, int argc, char **argv) +{ + /* Clean up after ourselves when the command finishes. */ + atexit(cork_command_cleanup); + + /* Run the root command. */ + cork_command_run(root, argc, argv); + return EXIT_SUCCESS; +} diff --git a/3rd/libcork/src/core/allocator.c b/3rd/libcork/src/core/allocator.c new file mode 100644 index 0000000..9a2ad05 --- /dev/null +++ b/3rd/libcork/src/core/allocator.c @@ -0,0 +1,421 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include + +#include "libcork/core/allocator.h" +#include "libcork/core/attributes.h" +#include "libcork/core/error.h" +#include "libcork/core/types.h" +#include "libcork/os/process.h" + + +/*----------------------------------------------------------------------- + * Allocator interface + */ + +struct cork_alloc_priv { + struct cork_alloc public; + struct cork_alloc_priv *next; +}; + +static void * +cork_alloc__default_calloc(const struct cork_alloc *alloc, + size_t count, size_t size) +{ + void *result = cork_alloc_xcalloc(alloc, count, size); + if (CORK_UNLIKELY(result == NULL)) { + abort(); + } + return result; +} + +static void * +cork_alloc__default_malloc(const struct cork_alloc *alloc, size_t size) +{ + void *result = cork_alloc_xmalloc(alloc, size); + if (CORK_UNLIKELY(result == NULL)) { + abort(); + } + return result; +} + +static void * +cork_alloc__default_realloc(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + void *result = cork_alloc_xrealloc(alloc, ptr, old_size, new_size); + if (CORK_UNLIKELY(result == NULL)) { + abort(); + } + return result; +} + +static void * +cork_alloc__default_xcalloc(const struct cork_alloc *alloc, + size_t count, size_t size) +{ + void *result; + assert(count < (SIZE_MAX / size)); + result = cork_alloc_xmalloc(alloc, count * size); + if (result != NULL) { + memset(result, 0, count * size); + } + return result; +} + +static void * +cork_alloc__default_xmalloc(const struct cork_alloc *alloc, size_t size) +{ + cork_abort("%s isn't defined", "cork_alloc:xmalloc"); +} + +static void * +cork_alloc__default_xrealloc(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + void *result = cork_alloc_xmalloc(alloc, new_size); + if (CORK_LIKELY(result != NULL) && ptr != NULL) { + size_t min_size = (new_size < old_size)? new_size: old_size; + memcpy(result, ptr, min_size); + cork_alloc_free(alloc, ptr, old_size); + } + return result; +} + +static void +cork_alloc__default_free(const struct cork_alloc *alloc, void *ptr, size_t size) +{ + cork_abort("%s isn't defined", "cork_alloc:free"); +} + +static bool cleanup_registered = false; +static struct cork_alloc_priv *all_allocs = NULL; + +static void +cork_alloc_free_alloc(struct cork_alloc_priv *alloc) +{ + cork_free_user_data(&alloc->public); + cork_alloc_delete(alloc->public.parent, struct cork_alloc_priv, alloc); +} + +static void +cork_alloc_free_all(void) +{ + struct cork_alloc_priv *curr; + struct cork_alloc_priv *next; + for (curr = all_allocs; curr != NULL; curr = next) { + next = curr->next; + cork_alloc_free_alloc(curr); + } +} + +static void +cork_alloc_register_cleanup(void) +{ + if (CORK_UNLIKELY(!cleanup_registered)) { + /* We don't use cork_cleanup because that requires the allocators to + * have already been set up! (atexit calls its functions in reverse + * order, and this one will be registered before cork_cleanup's, which + * makes it safe for cork_cleanup functions to still use the allocator, + * since the allocator atexit function will be called last.) */ + atexit(cork_alloc_free_all); + cleanup_registered = true; + } +} + +struct cork_alloc * +cork_alloc_new_alloc(const struct cork_alloc *parent) +{ + struct cork_alloc_priv *alloc = + cork_alloc_new(parent, struct cork_alloc_priv); + alloc->public.parent = parent; + alloc->public.user_data = NULL; + alloc->public.free_user_data = NULL; + alloc->public.calloc = cork_alloc__default_calloc; + alloc->public.malloc = cork_alloc__default_malloc; + alloc->public.realloc = cork_alloc__default_realloc; + alloc->public.xcalloc = cork_alloc__default_xcalloc; + alloc->public.xmalloc = cork_alloc__default_xmalloc; + alloc->public.xrealloc = cork_alloc__default_xrealloc; + alloc->public.free = cork_alloc__default_free; + + cork_alloc_register_cleanup(); + alloc->next = all_allocs; + all_allocs = alloc; + + return &alloc->public; +} + + +void +cork_alloc_set_user_data(struct cork_alloc *alloc, + void *user_data, cork_free_f free_user_data) +{ + cork_free_user_data(alloc); + alloc->user_data = user_data; + alloc->free_user_data = free_user_data; +} + +void +cork_alloc_set_calloc(struct cork_alloc *alloc, cork_alloc_calloc_f calloc) +{ + alloc->calloc = calloc; +} + +void +cork_alloc_set_malloc(struct cork_alloc *alloc, cork_alloc_malloc_f malloc) +{ + alloc->malloc = malloc; +} + +void +cork_alloc_set_realloc(struct cork_alloc *alloc, cork_alloc_realloc_f realloc) +{ + alloc->realloc = realloc; +} + +void +cork_alloc_set_xcalloc(struct cork_alloc *alloc, cork_alloc_calloc_f xcalloc) +{ + alloc->xcalloc = xcalloc; +} + +void +cork_alloc_set_xmalloc(struct cork_alloc *alloc, cork_alloc_malloc_f xmalloc) +{ + alloc->xmalloc = xmalloc; +} + +void +cork_alloc_set_xrealloc(struct cork_alloc *alloc, + cork_alloc_realloc_f xrealloc) +{ + alloc->xrealloc = xrealloc; +} + +void +cork_alloc_set_free(struct cork_alloc *alloc, cork_alloc_free_f free) +{ + alloc->free = free; +} + + +/*----------------------------------------------------------------------- + * Allocating strings + */ + +static inline const char * +strndup_internal(const struct cork_alloc *alloc, + const char *str, size_t len) +{ + char *dest; + size_t allocated_size = len + sizeof(size_t) + 1; + size_t *new_str = cork_alloc_malloc(alloc, allocated_size); + *new_str = allocated_size; + dest = (char *) (void *) (new_str + 1); + memcpy(dest, str, len); + dest[len] = '\0'; + return dest; +} + +const char * +cork_alloc_strdup(const struct cork_alloc *alloc, const char *str) +{ + return strndup_internal(alloc, str, strlen(str)); +} + +const char * +cork_alloc_strndup(const struct cork_alloc *alloc, + const char *str, size_t size) +{ + return strndup_internal(alloc, str, size); +} + +static inline const char * +xstrndup_internal(const struct cork_alloc *alloc, + const char *str, size_t len) +{ + size_t allocated_size = len + sizeof(size_t) + 1; + size_t *new_str = cork_alloc_xmalloc(alloc, allocated_size); + if (CORK_UNLIKELY(new_str == NULL)) { + return NULL; + } else { + char *dest; + *new_str = allocated_size; + dest = (char *) (void *) (new_str + 1); + memcpy(dest, str, len); + dest[len] = '\0'; + return dest; + } +} + +const char * +cork_alloc_xstrdup(const struct cork_alloc *alloc, const char *str) +{ + return xstrndup_internal(alloc, str, strlen(str)); +} + +const char * +cork_alloc_xstrndup(const struct cork_alloc *alloc, + const char *str, size_t size) +{ + return xstrndup_internal(alloc, str, size); +} + +void +cork_alloc_strfree(const struct cork_alloc *alloc, const char *str) +{ + size_t *base = ((size_t *) str) - 1; + cork_alloc_free(alloc, base, *base); +} + + +/*----------------------------------------------------------------------- + * stdlib allocator + */ + +static void * +cork_stdlib_alloc__calloc(const struct cork_alloc *alloc, + size_t count, size_t size) +{ + void *result = calloc(count, size); + if (CORK_UNLIKELY(result == NULL)) { + abort(); + } + return result; +} + +static void * +cork_stdlib_alloc__malloc(const struct cork_alloc *alloc, size_t size) +{ + void *result = malloc(size); + if (CORK_UNLIKELY(result == NULL)) { + abort(); + } + return result; +} + +static void * +cork_stdlib_alloc__realloc(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + /* Technically we don't really need to free `ptr` if the reallocation fails, + * since we'll abort the process immediately after. But my sense of + * cleanliness makes me do it anyway. */ + +#if CORK_HAVE_REALLOCF + void *result = reallocf(ptr, new_size); + if (result == NULL) { + abort(); + } + return result; +#else + void *result = realloc(ptr, new_size); + if (result == NULL) { + free(ptr); + abort(); + } + return result; +#endif +} + +static void * +cork_stdlib_alloc__xcalloc(const struct cork_alloc *alloc, + size_t count, size_t size) +{ + return calloc(count, size); +} + +static void * +cork_stdlib_alloc__xmalloc(const struct cork_alloc *alloc, size_t size) +{ + return malloc(size); +} + +static void * +cork_stdlib_alloc__xrealloc(const struct cork_alloc *alloc, void *ptr, + size_t old_size, size_t new_size) +{ + return realloc(ptr, new_size); +} + +static void +cork_stdlib_alloc__free(const struct cork_alloc *alloc, void *ptr, size_t size) +{ + free(ptr); +} + + +static const struct cork_alloc default_allocator = { + NULL, + NULL, + NULL, + cork_stdlib_alloc__calloc, + cork_stdlib_alloc__malloc, + cork_stdlib_alloc__realloc, + cork_stdlib_alloc__xcalloc, + cork_stdlib_alloc__xmalloc, + cork_stdlib_alloc__xrealloc, + cork_stdlib_alloc__free +}; + + +/*----------------------------------------------------------------------- + * Customizing libcork's allocator + */ + +const struct cork_alloc *cork_allocator = &default_allocator; + +void +cork_set_allocator(const struct cork_alloc *alloc) +{ + cork_allocator = alloc; +} + + +/*----------------------------------------------------------------------- + * Debugging allocator + */ + +static void * +cork_debug_alloc__xmalloc(const struct cork_alloc *alloc, size_t size) +{ + size_t real_size = size + sizeof(size_t); + size_t *base = cork_alloc_xmalloc(alloc->parent, real_size); + *base = size; + return base + 1; +} + +static void +cork_debug_alloc__free(const struct cork_alloc *alloc, void *ptr, + size_t expected_size) +{ + size_t *base = ((size_t *) ptr) - 1; + size_t actual_size = *base; + size_t real_size = actual_size + sizeof(size_t); + if (CORK_UNLIKELY(actual_size != expected_size)) { + cork_abort + ("Incorrect size when freeing pointer (got %zu, expected %zu)", + expected_size, actual_size); + } + cork_alloc_free(alloc->parent, base, real_size); +} + +struct cork_alloc * +cork_debug_alloc_new(const struct cork_alloc *parent) +{ + struct cork_alloc *debug = cork_alloc_new_alloc(parent); + cork_alloc_set_xmalloc(debug, cork_debug_alloc__xmalloc); + cork_alloc_set_free(debug, cork_debug_alloc__free); + return debug; +} diff --git a/3rd/libcork/src/core/error.c b/3rd/libcork/src/core/error.c new file mode 100644 index 0000000..114a090 --- /dev/null +++ b/3rd/libcork/src/core/error.c @@ -0,0 +1,246 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include + +#include "libcork/config.h" +#include "libcork/core/allocator.h" +#include "libcork/core/error.h" +#include "libcork/ds/buffer.h" +#include "libcork/os/process.h" +#include "libcork/threads/basics.h" + + +/*----------------------------------------------------------------------- + * Life cycle + */ + +struct cork_error { + cork_error code; + struct cork_buffer *message; + struct cork_buffer *other; + struct cork_buffer buf1; + struct cork_buffer buf2; + struct cork_error *next; +}; + +static struct cork_error * +cork_error_new(void) +{ + struct cork_error *error = cork_new(struct cork_error); + error->code = CORK_ERROR_NONE; + cork_buffer_init(&error->buf1); + cork_buffer_init(&error->buf2); + error->message = &error->buf1; + error->other = &error->buf2; + return error; +} + +static void +cork_error_free(struct cork_error *error) +{ + cork_buffer_done(&error->buf1); + cork_buffer_done(&error->buf2); + cork_delete(struct cork_error, error); +} + + +static struct cork_error * volatile errors; + +cork_once_barrier(cork_error_list); + +static void +cork_error_list_done(void) +{ + struct cork_error *curr; + struct cork_error *next; + for (curr = errors; curr != NULL; curr = next) { + next = curr->next; + cork_error_free(curr); + } +} + +static void +cork_error_list_init(void) +{ + cork_cleanup_at_exit(0, cork_error_list_done); +} + + +cork_tls(struct cork_error *, cork_error_); + +static struct cork_error * +cork_error_get(void) +{ + struct cork_error **error_ptr = cork_error__get(); + if (CORK_UNLIKELY(*error_ptr == NULL)) { + struct cork_error *old_head; + struct cork_error *error = cork_error_new(); + cork_once(cork_error_list, cork_error_list_init()); + do { + old_head = errors; + error->next = old_head; + } while (cork_ptr_cas(&errors, old_head, error) != old_head); + *error_ptr = error; + return error; + } else { + return *error_ptr; + } +} + + +/*----------------------------------------------------------------------- + * Public error API + */ + +bool +cork_error_occurred(void) +{ + struct cork_error *error = cork_error_get(); + return error->code != CORK_ERROR_NONE; +} + +cork_error +cork_error_code(void) +{ + struct cork_error *error = cork_error_get(); + return error->code; +} + +const char * +cork_error_message(void) +{ + struct cork_error *error = cork_error_get(); + return error->message->buf; +} + +void +cork_error_clear(void) +{ + struct cork_error *error = cork_error_get(); + error->code = CORK_ERROR_NONE; + cork_buffer_clear(error->message); +} + +void +cork_error_set_printf(cork_error code, const char *format, ...) +{ + va_list args; + struct cork_error *error = cork_error_get(); + error->code = code; + va_start(args, format); + cork_buffer_vprintf(error->message, format, args); + va_end(args); +} + +void +cork_error_set_string(cork_error code, const char *str) +{ + struct cork_error *error = cork_error_get(); + error->code = code; + cork_buffer_set_string(error->message, str); +} + +void +cork_error_set_vprintf(cork_error code, const char *format, va_list args) +{ + struct cork_error *error = cork_error_get(); + error->code = code; + cork_buffer_vprintf(error->message, format, args); +} + +void +cork_error_prefix_printf(const char *format, ...) +{ + va_list args; + struct cork_error *error = cork_error_get(); + struct cork_buffer *temp; + va_start(args, format); + cork_buffer_vprintf(error->other, format, args); + va_end(args); + cork_buffer_append_copy(error->other, error->message); + temp = error->other; + error->other = error->message; + error->message = temp; +} + +void +cork_error_prefix_string(const char *str) +{ + struct cork_error *error = cork_error_get(); + struct cork_buffer *temp; + cork_buffer_set_string(error->other, str); + cork_buffer_append_copy(error->other, error->message); + temp = error->other; + error->other = error->message; + error->message = temp; +} + +void +cork_error_prefix_vprintf(const char *format, va_list args) +{ + struct cork_error *error = cork_error_get(); + struct cork_buffer *temp; + cork_buffer_vprintf(error->other, format, args); + cork_buffer_append_copy(error->other, error->message); + temp = error->other; + error->other = error->message; + error->message = temp; +} + + +/*----------------------------------------------------------------------- + * Deprecated + */ + +void +cork_error_set(uint32_t error_class, unsigned int error_code, + const char *format, ...) +{ + /* Create a fallback error code that's most likely not very useful. */ + va_list args; + va_start(args, format); + cork_error_set_vprintf(error_class + error_code, format, args); + va_end(args); +} + +void +cork_error_prefix(const char *format, ...) +{ + va_list args; + va_start(args, format); + cork_error_prefix_vprintf(format, args); + va_end(args); +} + + +/*----------------------------------------------------------------------- + * Built-in errors + */ + +void +cork_system_error_set_explicit(int err) +{ + cork_error_set_string(err, strerror(err)); +} + +void +cork_system_error_set(void) +{ + cork_error_set_string(errno, strerror(errno)); +} + +void +cork_unknown_error_set_(const char *location) +{ + cork_error_set_printf(CORK_UNKNOWN_ERROR, "Unknown error in %s", location); +} diff --git a/3rd/libcork/src/core/gc.c b/3rd/libcork/src/core/gc.c new file mode 100644 index 0000000..d16562d --- /dev/null +++ b/3rd/libcork/src/core/gc.c @@ -0,0 +1,406 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "libcork/config/config.h" +#include "libcork/core/allocator.h" +#include "libcork/core/gc.h" +#include "libcork/core/types.h" +#include "libcork/ds/dllist.h" +#include "libcork/threads/basics.h" + + +#if !defined(CORK_DEBUG_GC) +#define CORK_DEBUG_GC 0 +#endif + +#if CORK_DEBUG_GC +#include +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) /* no debug messages */ +#endif + + +/*----------------------------------------------------------------------- + * GC context life cycle + */ + +#define ROOTS_SIZE 1024 + +/* An internal structure allocated with every garbage-collected object. */ +struct cork_gc_header; + +/* A garbage collector context. */ +struct cork_gc { + /* The number of used entries in roots. */ + size_t root_count; + /* The possible roots of garbage cycles */ + struct cork_gc_header *roots[ROOTS_SIZE]; +}; + +cork_tls(struct cork_gc, cork_gc); + +static void +cork_gc_collect_cycles(struct cork_gc *gc); + + +/*----------------------------------------------------------------------- + * Garbage collection functions + */ + +struct cork_gc_header { + /* The current reference count for this object, along with its color + * during the mark/sweep process. */ + volatile int ref_count_color; + + /* The allocated size of this garbage-collected object (including + * the header). */ + size_t allocated_size; + + /* The garbage collection interface for this object. */ + struct cork_gc_obj_iface *iface; +}; + +/* + * Structure of ref_count_color: + * + * +-----+---+---+---+---+---+ + * | ... | 4 | 3 | 2 | 1 | 0 | + * +-----+---+---+---+---+---+ + * ref_count | color + * | + * buffered --/ + */ + +#define cork_gc_ref_count_color(count, buffered, color) \ + (((count) << 3) | ((buffered) << 2) | (color)) + +#define cork_gc_get_ref_count(hdr) \ + ((hdr)->ref_count_color >> 3) + +#define cork_gc_inc_ref_count(hdr) \ + do { \ + (hdr)->ref_count_color += (1 << 3); \ + } while (0) + +#define cork_gc_dec_ref_count(hdr) \ + do { \ + (hdr)->ref_count_color -= (1 << 3); \ + } while (0) + +#define cork_gc_get_color(hdr) \ + ((hdr)->ref_count_color & 0x3) + +#define cork_gc_set_color(hdr, color) \ + do { \ + (hdr)->ref_count_color = \ + ((hdr)->ref_count_color & ~0x3) | (color & 0x3); \ + } while (0) + +#define cork_gc_get_buffered(hdr) \ + (((hdr)->ref_count_color & 0x4) != 0) + +#define cork_gc_set_buffered(hdr, buffered) \ + do { \ + (hdr)->ref_count_color = \ + ((hdr)->ref_count_color & ~0x4) | (((buffered) & 1) << 2); \ + } while (0) + +#define cork_gc_free(hdr) \ + do { \ + if ((hdr)->iface->free != NULL) { \ + (hdr)->iface->free(cork_gc_get_object((hdr))); \ + } \ + cork_free((hdr), (hdr)->allocated_size); \ + } while (0) + +#define cork_gc_recurse(gc, hdr, recurser) \ + do { \ + if ((hdr)->iface->recurse != NULL) { \ + (hdr)->iface->recurse \ + ((gc), cork_gc_get_object((hdr)), (recurser), NULL); \ + } \ + } while (0) + +enum cork_gc_color { + /* In use or free */ + GC_BLACK = 0, + /* Possible member of garbage cycle */ + GC_GRAY = 1, + /* Member of garbage cycle */ + GC_WHITE = 2, + /* Possible root of garbage cycle */ + GC_PURPLE = 3 +}; + +#define cork_gc_get_header(obj) \ + (((struct cork_gc_header *) (obj)) - 1) + +#define cork_gc_get_object(hdr) \ + ((void *) (((struct cork_gc_header *) (hdr)) + 1)) + + +void +cork_gc_init(void) +{ + cork_gc_get(); +} + +void +cork_gc_done(void) +{ + cork_gc_collect_cycles(cork_gc_get()); +} + +void * +cork_gc_alloc(size_t instance_size, struct cork_gc_obj_iface *iface) +{ + size_t full_size = instance_size + sizeof(struct cork_gc_header); + DEBUG("Allocating %zu (%zu) bytes\n", instance_size, full_size); + struct cork_gc_header *header = cork_malloc(full_size); + DEBUG(" Result is %p[%p]\n", cork_gc_get_object(header), header); + header->ref_count_color = cork_gc_ref_count_color(1, false, GC_BLACK); + header->allocated_size = full_size; + header->iface = iface; + return cork_gc_get_object(header); +} + +void * +cork_gc_incref(void *obj) +{ + if (obj != NULL) { + struct cork_gc_header *header = cork_gc_get_header(obj); + cork_gc_inc_ref_count(header); + DEBUG("Incrementing %p -> %d\n", + obj, cork_gc_get_ref_count(header)); + cork_gc_set_color(header, GC_BLACK); + } + return obj; +} + +static void +cork_gc_decref_step(struct cork_gc *gc, void *obj, void *ud); + +static void +cork_gc_release(struct cork_gc *gc, struct cork_gc_header *header) +{ + cork_gc_recurse(gc, header, cork_gc_decref_step); + cork_gc_set_color(header, GC_BLACK); + if (!cork_gc_get_buffered(header)) { + cork_gc_free(header); + } +} + +static void +cork_gc_possible_root(struct cork_gc *gc, struct cork_gc_header *header) +{ + if (cork_gc_get_color(header) != GC_PURPLE) { + DEBUG(" Possible garbage cycle root\n"); + cork_gc_set_color(header, GC_PURPLE); + if (!cork_gc_get_buffered(header)) { + cork_gc_set_buffered(header, true); + if (gc->root_count >= ROOTS_SIZE) { + cork_gc_collect_cycles(gc); + } + gc->roots[gc->root_count++] = header; + } + } else { + DEBUG(" Already marked as possible garbage cycle root\n"); + } +} + +static void +cork_gc_decref_step(struct cork_gc *gc, void *obj, void *ud) +{ + if (obj != NULL) { + struct cork_gc_header *header = cork_gc_get_header(obj); + cork_gc_dec_ref_count(header); + DEBUG("Decrementing %p -> %d\n", + obj, cork_gc_get_ref_count(header)); + if (cork_gc_get_ref_count(header) == 0) { + DEBUG(" Releasing %p\n", header); + cork_gc_release(gc, header); + } else { + cork_gc_possible_root(gc, header); + } + } +} + +void +cork_gc_decref(void *obj) +{ + if (obj != NULL) { + struct cork_gc *gc = cork_gc_get(); + struct cork_gc_header *header = cork_gc_get_header(obj); + cork_gc_dec_ref_count(header); + DEBUG("Decrementing %p -> %d\n", + obj, cork_gc_get_ref_count(header)); + if (cork_gc_get_ref_count(header) == 0) { + DEBUG(" Releasing %p\n", header); + cork_gc_release(gc, header); + } else { + cork_gc_possible_root(gc, header); + } + } +} + + +static void +cork_gc_mark_gray_step(struct cork_gc *gc, void *obj, void *ud); + +static void +cork_gc_mark_gray(struct cork_gc *gc, struct cork_gc_header *header) +{ + if (cork_gc_get_color(header) != GC_GRAY) { + DEBUG(" Setting color to gray\n"); + cork_gc_set_color(header, GC_GRAY); + cork_gc_recurse(gc, header, cork_gc_mark_gray_step); + } +} + +static void +cork_gc_mark_gray_step(struct cork_gc *gc, void *obj, void *ud) +{ + if (obj != NULL) { + DEBUG(" cork_gc_mark_gray(%p)\n", obj); + struct cork_gc_header *header = cork_gc_get_header(obj); + cork_gc_dec_ref_count(header); + DEBUG(" Reference count now %d\n", cork_gc_get_ref_count(header)); + cork_gc_mark_gray(gc, header); + } +} + +static void +cork_gc_mark_roots(struct cork_gc *gc) +{ + size_t i; + for (i = 0; i < gc->root_count; i++) { + struct cork_gc_header *header = gc->roots[i]; + if (cork_gc_get_color(header) == GC_PURPLE) { + DEBUG(" Checking possible garbage cycle root %p\n", + cork_gc_get_object(header)); + DEBUG(" cork_gc_mark_gray(%p)\n", + cork_gc_get_object(header)); + cork_gc_mark_gray(gc, header); + } else { + DEBUG(" Possible garbage cycle root %p already checked\n", + cork_gc_get_object(header)); + cork_gc_set_buffered(header, false); + gc->roots[i] = NULL; + if (cork_gc_get_color(header) == GC_BLACK && + cork_gc_get_ref_count(header) == 0) { + DEBUG(" Freeing %p\n", header); + cork_gc_free(header); + } + } + } +} + +static void +cork_gc_scan_black_step(struct cork_gc *gc, void *obj, void *ud); + +static void +cork_gc_scan_black(struct cork_gc *gc, struct cork_gc_header *header) +{ + DEBUG(" Setting color of %p to BLACK\n", + cork_gc_get_object(header)); + cork_gc_set_color(header, GC_BLACK); + cork_gc_recurse(gc, header, cork_gc_scan_black_step); +} + +static void +cork_gc_scan_black_step(struct cork_gc *gc, void *obj, void *ud) +{ + if (obj != NULL) { + struct cork_gc_header *header = cork_gc_get_header(obj); + cork_gc_inc_ref_count(header); + DEBUG(" Increasing reference count %p -> %d\n", + obj, cork_gc_get_ref_count(header)); + if (cork_gc_get_color(header) != GC_BLACK) { + cork_gc_scan_black(gc, header); + } + } +} + +static void +cork_gc_scan(struct cork_gc *gc, void *obj, void *ud) +{ + if (obj != NULL) { + DEBUG(" Scanning possible garbage cycle entry %p\n", obj); + struct cork_gc_header *header = cork_gc_get_header(obj); + if (cork_gc_get_color(header) == GC_GRAY) { + if (cork_gc_get_ref_count(header) > 0) { + DEBUG(" Remaining references; can't be a cycle\n"); + cork_gc_scan_black(gc, header); + } else { + DEBUG(" Definitely a garbage cycle\n"); + cork_gc_set_color(header, GC_WHITE); + cork_gc_recurse(gc, header, cork_gc_scan); + } + } else { + DEBUG(" Already checked\n"); + } + } +} + +static void +cork_gc_scan_roots(struct cork_gc *gc) +{ + size_t i; + for (i = 0; i < gc->root_count; i++) { + if (gc->roots[i] != NULL) { + void *obj = cork_gc_get_object(gc->roots[i]); + cork_gc_scan(gc, obj, NULL); + } + } +} + +static void +cork_gc_collect_white(struct cork_gc *gc, void *obj, void *ud) +{ + if (obj != NULL) { + struct cork_gc_header *header = cork_gc_get_header(obj); + if (cork_gc_get_color(header) == GC_WHITE && + !cork_gc_get_buffered(header)) { + DEBUG(" Releasing %p\n", obj); + cork_gc_set_color(header, GC_BLACK); + cork_gc_recurse(gc, header, cork_gc_collect_white); + DEBUG(" Freeing %p\n", header); + cork_gc_free(header); + } + } +} + +static void +cork_gc_collect_roots(struct cork_gc *gc) +{ + size_t i; + for (i = 0; i < gc->root_count; i++) { + if (gc->roots[i] != NULL) { + struct cork_gc_header *header = gc->roots[i]; + void *obj = cork_gc_get_object(header); + cork_gc_set_buffered(header, false); + DEBUG("Collecting cycles from garbage root %p\n", obj); + cork_gc_collect_white(gc, obj, NULL); + gc->roots[i] = NULL; + } + } + gc->root_count = 0; +} + +static void +cork_gc_collect_cycles(struct cork_gc *gc) +{ + DEBUG("Collecting garbage cycles\n"); + cork_gc_mark_roots(gc); + cork_gc_scan_roots(gc); + cork_gc_collect_roots(gc); +} diff --git a/3rd/libcork/src/core/hash.c b/3rd/libcork/src/core/hash.c new file mode 100644 index 0000000..79b5824 --- /dev/null +++ b/3rd/libcork/src/core/hash.c @@ -0,0 +1,20 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#define CORK_HASH_ATTRIBUTES CORK_API + +#include "libcork/core/hash.h" +#include "libcork/core/types.h" + +/* All of the following functions will be defined for us by libcork/core/hash.h: + * cork_hash_buffer + * cork_big_hash_buffer + * cork_stable_hash_buffer + */ diff --git a/3rd/libcork/src/core/ip-address.c b/3rd/libcork/src/core/ip-address.c new file mode 100644 index 0000000..6ad95fb --- /dev/null +++ b/3rd/libcork/src/core/ip-address.c @@ -0,0 +1,536 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "libcork/core/byte-order.h" +#include "libcork/core/error.h" +#include "libcork/core/net-addresses.h" +#include "libcork/core/types.h" + +#ifndef CORK_IP_ADDRESS_DEBUG +#define CORK_IP_ADDRESS_DEBUG 0 +#endif + +#if CORK_IP_ADDRESS_DEBUG +#include +#define DEBUG(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) +#else +#define DEBUG(...) /* nothing */ +#endif + + +/*----------------------------------------------------------------------- + * IP addresses + */ + +/*** IPv4 ***/ + +static inline const char * +cork_ipv4_parse(struct cork_ipv4 *addr, const char *str) +{ + const char *ch; + bool seen_digit_in_octet = false; + unsigned int octets = 0; + unsigned int digit = 0; + uint8_t result[4]; + + for (ch = str; *ch != '\0'; ch++) { + DEBUG("%2u: %c\t", (unsigned int) (ch-str), *ch); + switch (*ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + seen_digit_in_octet = true; + digit *= 10; + digit += (*ch - '0'); + DEBUG("digit = %u\n", digit); + if (CORK_UNLIKELY(digit > 255)) { + DEBUG("\t"); + goto parse_error; + } + break; + + case '.': + /* If this would be the fourth octet, it can't have a trailing + * period. */ + if (CORK_UNLIKELY(octets == 3)) { + goto parse_error; + } + DEBUG("octet %u = %u\n", octets, digit); + result[octets] = digit; + digit = 0; + octets++; + seen_digit_in_octet = false; + break; + + default: + /* Any other character is a parse error. */ + goto parse_error; + } + } + + /* If we have a valid octet at the end, and that would be the fourth octet, + * then we've got a valid final parse. */ + DEBUG("%2u:\t", (unsigned int) (ch-str)); + if (CORK_LIKELY(seen_digit_in_octet && octets == 3)) { +#if CORK_IP_ADDRESS_DEBUG + char parsed_ipv4[CORK_IPV4_STRING_LENGTH]; +#endif + DEBUG("octet %u = %u\n", octets, digit); + result[octets] = digit; + cork_ipv4_copy(addr, result); +#if CORK_IP_ADDRESS_DEBUG + cork_ipv4_to_raw_string(addr, parsed_ipv4); + DEBUG("\tParsed address: %s\n", parsed_ipv4); +#endif + return ch; + } + +parse_error: + DEBUG("parse error\n"); + cork_parse_error("Invalid IPv4 address: \"%s\"", str); + return NULL; +} + +int +cork_ipv4_init(struct cork_ipv4 *addr, const char *str) +{ + return cork_ipv4_parse(addr, str) == NULL? -1: 0; +} + +bool +cork_ipv4_equal_(const struct cork_ipv4 *addr1, const struct cork_ipv4 *addr2) +{ + return cork_ipv4_equal(addr1, addr2); +} + +void +cork_ipv4_to_raw_string(const struct cork_ipv4 *addr, char *dest) +{ + snprintf(dest, CORK_IPV4_STRING_LENGTH, "%u.%u.%u.%u", + addr->_.u8[0], addr->_.u8[1], addr->_.u8[2], addr->_.u8[3]); +} + +bool +cork_ipv4_is_valid_network(const struct cork_ipv4 *addr, + unsigned int cidr_prefix) +{ + uint32_t cidr_mask; + + if (cidr_prefix > 32) { + return false; + } else if (cidr_prefix == 32) { + /* This handles undefined behavior for overflow bit shifts. */ + cidr_mask = 0; + } else { + cidr_mask = 0xffffffff >> cidr_prefix; + } + + return (CORK_UINT32_BIG_TO_HOST(addr->_.u32) & cidr_mask) == 0; +} + +/*** IPv6 ***/ + +int +cork_ipv6_init(struct cork_ipv6 *addr, const char *str) +{ + const char *ch; + + uint16_t digit = 0; + unsigned int before_count = 0; + uint16_t before_double_colon[8]; + uint16_t after_double_colon[8]; + uint16_t *dest = before_double_colon; + + unsigned int digits_seen = 0; + unsigned int hextets_seen = 0; + bool another_required = true; + bool digit_allowed = true; + bool colon_allowed = true; + bool double_colon_allowed = true; + bool just_saw_colon = false; + + for (ch = str; *ch != '\0'; ch++) { + DEBUG("%2u: %c\t", (unsigned int) (ch-str), *ch); + switch (*ch) { +#define process_digit(base) \ + /* Make sure a digit is allowed here. */ \ + if (CORK_UNLIKELY(!digit_allowed)) { \ + goto parse_error; \ + } \ + /* If we've already seen 4 digits, it's a parse error. */ \ + if (CORK_UNLIKELY(digits_seen == 4)) { \ + goto parse_error; \ + } \ + \ + digits_seen++; \ + colon_allowed = true; \ + just_saw_colon = false; \ + digit <<= 4; \ + digit |= (*ch - (base)); \ + DEBUG("digit = %04x\n", digit); + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + process_digit('0'); + break; + + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + process_digit('a'-10); + break; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + process_digit('A'-10); + break; + +#undef process_digit + + case ':': + /* We can only see a colon immediately after a hextet or as part + * of a double-colon. */ + if (CORK_UNLIKELY(!colon_allowed)) { + goto parse_error; + } + + /* If this is a double-colon, start parsing hextets into our + * second array. */ + if (just_saw_colon) { + DEBUG("double-colon\n"); + colon_allowed = false; + digit_allowed = true; + another_required = false; + double_colon_allowed = false; + before_count = hextets_seen; + dest = after_double_colon; + continue; + } + + /* If this would end the eighth hextet (regardless of the + * placement of a double-colon), then there can't be a trailing + * colon. */ + if (CORK_UNLIKELY(hextets_seen == 8)) { + goto parse_error; + } + + /* If this is the very beginning of the string, then we can only + * have a double-colon, not a single colon. */ + if (digits_seen == 0 && hextets_seen == 0) { + DEBUG("initial colon\n"); + colon_allowed = true; + digit_allowed = false; + just_saw_colon = true; + another_required = true; + continue; + } + + /* Otherwise this ends the current hextet. */ + DEBUG("hextet %u = %04x\n", hextets_seen, digit); + *(dest++) = CORK_UINT16_HOST_TO_BIG(digit); + digit = 0; + hextets_seen++; + digits_seen = 0; + colon_allowed = double_colon_allowed; + just_saw_colon = true; + another_required = true; + break; + + case '.': + { + /* If we see a period, then we must be in the middle of an IPv4 + * address at the end of the IPv6 address. */ + struct cork_ipv4 *ipv4 = (struct cork_ipv4 *) dest; + DEBUG("Detected IPv4 address %s\n", ch-digits_seen); + + /* Ensure that we have space for the two hextets that the IPv4 + * address will take up. */ + if (CORK_UNLIKELY(hextets_seen >= 7)) { + goto parse_error; + } + + /* Parse the IPv4 address directly into our current hextet + * buffer. */ + ch = cork_ipv4_parse(ipv4, ch - digits_seen); + if (CORK_LIKELY(ch != NULL)) { + hextets_seen += 2; + digits_seen = 0; + another_required = false; + + /* ch now points at the NUL terminator, but we're about to + * increment ch. */ + ch--; + break; + } + + /* The IPv4 parse failed, so we have an IPv6 parse error. */ + goto parse_error; + } + + default: + /* Any other character is a parse error. */ + goto parse_error; + } + } + + /* If we have a valid hextet at the end, and we've either seen a + * double-colon, or we have eight hextets in total, then we've got a valid + * final parse. */ + DEBUG("%2u:\t", (unsigned int) (ch-str)); + if (CORK_LIKELY(digits_seen > 0)) { + /* If there are trailing digits that would form a ninth hextet + * (regardless of the placement of a double-colon), then we have a parse + * error. */ + if (CORK_UNLIKELY(hextets_seen == 8)) { + goto parse_error; + } + + DEBUG("hextet %u = %04x\n\t", hextets_seen, digit); + *(dest++) = CORK_UINT16_HOST_TO_BIG(digit); + hextets_seen++; + } else if (CORK_UNLIKELY(another_required)) { + goto parse_error; + } + + if (!double_colon_allowed) { + /* We've seen a double-colon, so use 0000 for any hextets that weren't + * present. */ +#if CORK_IP_ADDRESS_DEBUG + char parsed_result[CORK_IPV6_STRING_LENGTH]; +#endif + unsigned int after_count = hextets_seen - before_count; + DEBUG("Saw double-colon; %u hextets before, %u after\n", + before_count, after_count); + memset(addr, 0, sizeof(struct cork_ipv6)); + memcpy(addr, before_double_colon, + sizeof(uint16_t) * before_count); + memcpy(&addr->_.u16[8-after_count], after_double_colon, + sizeof(uint16_t) * after_count); +#if CORK_IP_ADDRESS_DEBUG + cork_ipv6_to_raw_string(addr, parsed_result); + DEBUG("\tParsed address: %s\n", parsed_result); +#endif + return 0; + } else if (hextets_seen == 8) { + /* No double-colon, so we must have exactly eight hextets. */ +#if CORK_IP_ADDRESS_DEBUG + char parsed_result[CORK_IPV6_STRING_LENGTH]; +#endif + DEBUG("No double-colon\n"); + cork_ipv6_copy(addr, before_double_colon); +#if CORK_IP_ADDRESS_DEBUG + cork_ipv6_to_raw_string(addr, parsed_result); + DEBUG("\tParsed address: %s\n", parsed_result); +#endif + return 0; + } + +parse_error: + DEBUG("parse error\n"); + cork_parse_error("Invalid IPv6 address: \"%s\"", str); + return -1; +} + +bool +cork_ipv6_equal_(const struct cork_ipv6 *addr1, const struct cork_ipv6 *addr2) +{ + return cork_ipv6_equal(addr1, addr2); +} + +#define NS_IN6ADDRSZ 16 +#define NS_INT16SZ 2 + +void +cork_ipv6_to_raw_string(const struct cork_ipv6 *addr, char *dest) +{ + const uint8_t *src = addr->_.u8; + + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char *tp; + struct { int base, len; } best, cur; + unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ]; + int i; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < NS_IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + best.len = 0; + cur.base = -1; + cur.len = 0; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + tp = dest; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) + *tp++ = ':'; + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) + *tp++ = ':'; + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { + tp += sprintf(tp, "%u.%u.%u.%u", + src[12], src[13], src[14], src[15]); + break; + } + tp += sprintf(tp, "%x", words[i]); + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == + (NS_IN6ADDRSZ / NS_INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; +} + +bool +cork_ipv6_is_valid_network(const struct cork_ipv6 *addr, + unsigned int cidr_prefix) +{ + uint64_t cidr_mask[2]; + + if (cidr_prefix > 128) { + return false; + } else if (cidr_prefix == 128) { + /* This handles undefined behavior for overflow bit shifts. */ + cidr_mask[0] = cidr_mask[1] = 0; + } else if (cidr_prefix == 64) { + /* This handles undefined behavior for overflow bit shifts. */ + cidr_mask[0] = 0; + cidr_mask[1] = UINT64_C(0xffffffffffffffff); + } else if (cidr_prefix > 64) { + cidr_mask[0] = 0; + cidr_mask[1] = UINT64_C(0xffffffffffffffff) >> (cidr_prefix-64); + } else { + cidr_mask[0] = UINT64_C(0xffffffffffffffff) >> cidr_prefix; + cidr_mask[1] = UINT64_C(0xffffffffffffffff); + } + + return (CORK_UINT64_BIG_TO_HOST(addr->_.u64[0] & cidr_mask[0]) == 0) && + (CORK_UINT64_BIG_TO_HOST(addr->_.u64[1] & cidr_mask[1]) == 0); +} + + +/*** IP ***/ + +void +cork_ip_from_ipv4_(struct cork_ip *addr, const void *src) +{ + cork_ip_from_ipv4(addr, src); +} + +void +cork_ip_from_ipv6_(struct cork_ip *addr, const void *src) +{ + cork_ip_from_ipv6(addr, src); +} + +int +cork_ip_init(struct cork_ip *addr, const char *str) +{ + int rc; + + /* Try IPv4 first */ + rc = cork_ipv4_init(&addr->ip.v4, str); + if (rc == 0) { + /* successful parse */ + addr->version = 4; + return 0; + } + + /* Then try IPv6 */ + cork_error_clear(); + rc = cork_ipv6_init(&addr->ip.v6, str); + if (rc == 0) { + /* successful parse */ + addr->version = 6; + return 0; + } + + /* Parse error for both address types */ + cork_parse_error("Invalid IP address: \"%s\"", str); + return -1; +} + +bool +cork_ip_equal_(const struct cork_ip *addr1, const struct cork_ip *addr2) +{ + return cork_ip_equal(addr1, addr2); +} + +void +cork_ip_to_raw_string(const struct cork_ip *addr, char *dest) +{ + switch (addr->version) { + case 4: + cork_ipv4_to_raw_string(&addr->ip.v4, dest); + return; + + case 6: + cork_ipv6_to_raw_string(&addr->ip.v6, dest); + return; + + default: + strncpy(dest, "", CORK_IP_STRING_LENGTH); + return; + } +} + +bool +cork_ip_is_valid_network(const struct cork_ip *addr, unsigned int cidr_prefix) +{ + switch (addr->version) { + case 4: + return cork_ipv4_is_valid_network(&addr->ip.v4, cidr_prefix); + case 6: + return cork_ipv6_is_valid_network(&addr->ip.v6, cidr_prefix); + default: + return false; + } +} diff --git a/3rd/libcork/src/core/mempool.c b/3rd/libcork/src/core/mempool.c new file mode 100644 index 0000000..1937d35 --- /dev/null +++ b/3rd/libcork/src/core/mempool.c @@ -0,0 +1,198 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "libcork/core/callbacks.h" +#include "libcork/core/mempool.h" +#include "libcork/core/types.h" +#include "libcork/helpers/errors.h" + + +#if !defined(CORK_DEBUG_MEMPOOL) +#define CORK_DEBUG_MEMPOOL 0 +#endif + +#if CORK_DEBUG_MEMPOOL +#include +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) /* no debug messages */ +#endif + + + +struct cork_mempool { + size_t element_size; + size_t block_size; + struct cork_mempool_object *free_list; + /* The number of objects that have been given out by + * cork_mempool_new but not returned via cork_mempool_free. */ + size_t allocated_count; + struct cork_mempool_block *blocks; + + void *user_data; + cork_free_f free_user_data; + cork_init_f init_object; + cork_done_f done_object; +}; + +struct cork_mempool_object { + /* When this object is unclaimed, it will be in the cork_mempool + * object's free_list using this pointer. */ + struct cork_mempool_object *next_free; +}; + +struct cork_mempool_block { + struct cork_mempool_block *next_block; +}; + +#define cork_mempool_object_size(mp) \ + (sizeof(struct cork_mempool_object) + (mp)->element_size) + +#define cork_mempool_get_header(obj) \ + (((struct cork_mempool_object *) (obj)) - 1) + +#define cork_mempool_get_object(hdr) \ + ((void *) (((struct cork_mempool_object *) (hdr)) + 1)) + + +struct cork_mempool * +cork_mempool_new_size_ex(size_t element_size, size_t block_size) +{ + struct cork_mempool *mp = cork_new(struct cork_mempool); + mp->element_size = element_size; + mp->block_size = block_size; + mp->free_list = NULL; + mp->allocated_count = 0; + mp->blocks = NULL; + mp->user_data = NULL; + mp->free_user_data = NULL; + mp->init_object = NULL; + mp->done_object = NULL; + return mp; +} + +void +cork_mempool_free(struct cork_mempool *mp) +{ + struct cork_mempool_block *curr; + assert(mp->allocated_count == 0); + + if (mp->done_object != NULL) { + struct cork_mempool_object *obj; + for (obj = mp->free_list; obj != NULL; obj = obj->next_free) { + mp->done_object + (mp->user_data, cork_mempool_get_object(obj)); + } + } + + for (curr = mp->blocks; curr != NULL; ) { + struct cork_mempool_block *next = curr->next_block; + cork_free(curr, mp->block_size); + /* Do this here instead of in the for statement to avoid + * accessing the just-freed block. */ + curr = next; + } + + cork_free_user_data(mp); + cork_delete(struct cork_mempool, mp); +} + + +void +cork_mempool_set_user_data(struct cork_mempool *mp, + void *user_data, cork_free_f free_user_data) +{ + cork_free_user_data(mp); + mp->user_data = user_data; + mp->free_user_data = free_user_data; +} + +void +cork_mempool_set_init_object(struct cork_mempool *mp, cork_init_f init_object) +{ + mp->init_object = init_object; +} + +void +cork_mempool_set_done_object(struct cork_mempool *mp, cork_done_f done_object) +{ + mp->done_object = done_object; +} + +void +cork_mempool_set_callbacks(struct cork_mempool *mp, + void *user_data, cork_free_f free_user_data, + cork_init_f init_object, + cork_done_f done_object) +{ + cork_mempool_set_user_data(mp, user_data, free_user_data); + cork_mempool_set_init_object(mp, init_object); + cork_mempool_set_done_object(mp, done_object); +} + + +/* If this function succeeds, then we guarantee that there will be at + * least one object in mp->free_list. */ +static void +cork_mempool_new_block(struct cork_mempool *mp) +{ + /* Allocate the new block and add it to mp's block list. */ + struct cork_mempool_block *block; + void *vblock; + DEBUG("Allocating new %zu-byte block\n", mp->block_size); + block = cork_malloc(mp->block_size); + block->next_block = mp->blocks; + mp->blocks = block; + vblock = block; + + /* Divide the block's memory region into a bunch of objects. */ + size_t index = sizeof(struct cork_mempool_block); + for (index = sizeof(struct cork_mempool_block); + (index + cork_mempool_object_size(mp)) <= mp->block_size; + index += cork_mempool_object_size(mp)) { + struct cork_mempool_object *obj = vblock + index; + DEBUG(" New object at %p[%p]\n", cork_mempool_get_object(obj), obj); + if (mp->init_object != NULL) { + mp->init_object + (mp->user_data, cork_mempool_get_object(obj)); + } + obj->next_free = mp->free_list; + mp->free_list = obj; + } +} + +void * +cork_mempool_new_object(struct cork_mempool *mp) +{ + struct cork_mempool_object *obj; + void *ptr; + + if (CORK_UNLIKELY(mp->free_list == NULL)) { + cork_mempool_new_block(mp); + } + + obj = mp->free_list; + mp->free_list = obj->next_free; + mp->allocated_count++; + ptr = cork_mempool_get_object(obj); + return ptr; +} + +void +cork_mempool_free_object(struct cork_mempool *mp, void *ptr) +{ + struct cork_mempool_object *obj = cork_mempool_get_header(ptr); + DEBUG("Returning %p[%p] to memory pool\n", ptr, obj); + obj->next_free = mp->free_list; + mp->free_list = obj; + mp->allocated_count--; +} diff --git a/3rd/libcork/src/core/timestamp.c b/3rd/libcork/src/core/timestamp.c new file mode 100644 index 0000000..331bca7 --- /dev/null +++ b/3rd/libcork/src/core/timestamp.c @@ -0,0 +1,162 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "libcork/core/timestamp.h" +#include "libcork/core/types.h" +#include "libcork/helpers/errors.h" + +void +cork_timestamp_init_now(cork_timestamp *ts) +{ + struct timeval tp; + gettimeofday(&tp, NULL); + cork_timestamp_init_usec(ts, tp.tv_sec, tp.tv_usec); +} + + +#define is_digit(ch) ((ch) >= '0' && (ch) <= '9') + +static uint64_t +power_of_10(unsigned int width) +{ + uint64_t accumulator = 10; + uint64_t result = 1; + while (width != 0) { + if ((width % 2) == 1) { + result *= accumulator; + width--; + } + accumulator *= accumulator; + width /= 2; + } + return result; +} + +static int +append_fractional(const cork_timestamp ts, unsigned int width, + struct cork_buffer *dest) +{ + if (CORK_UNLIKELY(width == 0 || width > 9)) { + cork_error_set_printf + (EINVAL, + "Invalid width %u for fractional cork_timestamp", width); + return -1; + } else { + uint64_t denom = power_of_10(width); + uint64_t frac = cork_timestamp_gsec_to_units(ts, denom); + cork_buffer_append_printf(dest, "%0*" PRIu64, width, frac); + return 0; + } +} + +static int +cork_timestamp_format_parts(const cork_timestamp ts, struct tm *tm, + const char *format, struct cork_buffer *dest) +{ + const char *next_percent; + + while ((next_percent = strchr(format, '%')) != NULL) { + const char *spec = next_percent + 1; + unsigned int width = 0; + + /* First append any text in between the previous format specifier and + * this one. */ + cork_buffer_append(dest, format, next_percent - format); + + /* Then parse the format specifier */ + while (is_digit(*spec)) { + width *= 10; + width += (*spec++ - '0'); + } + + switch (*spec) { + case '\0': + cork_error_set_string + (EINVAL, + "Trailing %% at end of cork_timestamp format string"); + return -1; + + case '%': + cork_buffer_append(dest, "%", 1); + break; + + case 'Y': + cork_buffer_append_printf(dest, "%04d", tm->tm_year + 1900); + break; + + case 'm': + cork_buffer_append_printf(dest, "%02d", tm->tm_mon + 1); + break; + + case 'd': + cork_buffer_append_printf(dest, "%02d", tm->tm_mday); + break; + + case 'H': + cork_buffer_append_printf(dest, "%02d", tm->tm_hour); + break; + + case 'M': + cork_buffer_append_printf(dest, "%02d", tm->tm_min); + break; + + case 'S': + cork_buffer_append_printf(dest, "%02d", tm->tm_sec); + break; + + case 's': + cork_buffer_append_printf + (dest, "%" PRIu32, cork_timestamp_sec(ts)); + break; + + case 'f': + rii_check(append_fractional(ts, width, dest)); + break; + + default: + cork_error_set_printf + (EINVAL, + "Unknown cork_timestamp format specifier %%%c", *spec); + return -1; + } + + format = spec + 1; + } + + /* When we fall through, there is some additional content after the final + * format specifier. */ + cork_buffer_append_string(dest, format); + return 0; +} + +int +cork_timestamp_format_utc(const cork_timestamp ts, const char *format, + struct cork_buffer *dest) +{ + time_t clock; + struct tm tm; + clock = cork_timestamp_sec(ts); + gmtime_r(&clock, &tm); + return cork_timestamp_format_parts(ts, &tm, format, dest); +} + +int +cork_timestamp_format_local(const cork_timestamp ts, const char *format, + struct cork_buffer *dest) +{ + time_t clock; + struct tm tm; + clock = cork_timestamp_sec(ts); + localtime_r(&clock, &tm); + return cork_timestamp_format_parts(ts, &tm, format, dest); +} diff --git a/3rd/libcork/src/core/u128.c b/3rd/libcork/src/core/u128.c new file mode 100644 index 0000000..90fd077 --- /dev/null +++ b/3rd/libcork/src/core/u128.c @@ -0,0 +1,85 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "libcork/core/types.h" +#include "libcork/core/u128.h" + + +/* From http://stackoverflow.com/questions/8023414/how-to-convert-a-128-bit-integer-to-a-decimal-ascii-string-in-c */ + +const char * +cork_u128_to_decimal(char *dest, cork_u128 val) +{ + uint32_t n[4]; + char *s = dest; + char *p = dest; + unsigned int i; + + /* This algorithm assumes that n[3] is the MSW. */ + n[3] = cork_u128_be32(val, 0); + n[2] = cork_u128_be32(val, 1); + n[1] = cork_u128_be32(val, 2); + n[0] = cork_u128_be32(val, 3); + + memset(s, '0', CORK_U128_DECIMAL_LENGTH - 1); + s[CORK_U128_DECIMAL_LENGTH - 1] = '\0'; + + for (i = 0; i < 128; i++) { + unsigned int j; + unsigned int carry; + + carry = (n[3] >= 0x80000000); + /* Shift n[] left, doubling it */ + n[3] = ((n[3] << 1) & 0xFFFFFFFF) + (n[2] >= 0x80000000); + n[2] = ((n[2] << 1) & 0xFFFFFFFF) + (n[1] >= 0x80000000); + n[1] = ((n[1] << 1) & 0xFFFFFFFF) + (n[0] >= 0x80000000); + n[0] = ((n[0] << 1) & 0xFFFFFFFF); + + /* Add s[] to itself in decimal, doubling it */ + for (j = CORK_U128_DECIMAL_LENGTH - 1; j-- > 0; ) { + s[j] += s[j] - '0' + carry; + carry = (s[j] > '9'); + if (carry) { + s[j] -= 10; + } + } + } + + while ((p[0] == '0') && (p < &s[CORK_U128_DECIMAL_LENGTH - 2])) { + p++; + } + + return p; +} + + +const char * +cork_u128_to_hex(char *buf, cork_u128 val) +{ + uint64_t hi = val._.be64.hi; + uint64_t lo = val._.be64.lo; + if (hi == 0) { + snprintf(buf, CORK_U128_HEX_LENGTH, "%" PRIx64, lo); + } else { + snprintf(buf, CORK_U128_HEX_LENGTH, "%" PRIx64 "%016" PRIx64, hi, lo); + } + return buf; +} + +const char * +cork_u128_to_padded_hex(char *buf, cork_u128 val) +{ + uint64_t hi = val._.be64.hi; + uint64_t lo = val._.be64.lo; + snprintf(buf, CORK_U128_HEX_LENGTH, "%016" PRIx64 "%016" PRIx64, hi, lo); + return buf; +} diff --git a/3rd/libcork/src/core/version.c b/3rd/libcork/src/core/version.c new file mode 100644 index 0000000..474fc6e --- /dev/null +++ b/3rd/libcork/src/core/version.c @@ -0,0 +1,28 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include "libcork/config.h" +#include "libcork/core/api.h" + + +/*----------------------------------------------------------------------- + * Library version + */ + +const char * +cork_version_string(void) +{ + return CORK_CONFIG_VERSION_STRING; +} + +const char * +cork_revision_string(void) +{ + return CORK_CONFIG_REVISION; +} diff --git a/3rd/libcork/src/ds/array.c b/3rd/libcork/src/ds/array.c new file mode 100644 index 0000000..378b1a0 --- /dev/null +++ b/3rd/libcork/src/ds/array.c @@ -0,0 +1,378 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "libcork/core/types.h" +#include "libcork/ds/array.h" +#include "libcork/helpers/errors.h" + +#ifndef CORK_ARRAY_DEBUG +#define CORK_ARRAY_DEBUG 0 +#endif + +#if CORK_ARRAY_DEBUG +#include +#define DEBUG(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#else +#define DEBUG(...) /* nothing */ +#endif + + +/*----------------------------------------------------------------------- + * Resizable arrays + */ + +struct cork_array_priv { + size_t allocated_count; + size_t allocated_size; + size_t element_size; + size_t initialized_count; + void *user_data; + cork_free_f free_user_data; + cork_init_f init; + cork_done_f done; + cork_init_f reuse; + cork_done_f remove; +}; + +void +cork_raw_array_init(struct cork_raw_array *array, size_t element_size) +{ + array->items = NULL; + array->size = 0; + array->priv = cork_new(struct cork_array_priv); + array->priv->allocated_count = 0; + array->priv->allocated_size = 0; + array->priv->element_size = element_size; + array->priv->initialized_count = 0; + array->priv->user_data = NULL; + array->priv->free_user_data = NULL; + array->priv->init = NULL; + array->priv->done = NULL; + array->priv->reuse = NULL; + array->priv->remove = NULL; +} + +void +cork_raw_array_done(struct cork_raw_array *array) +{ + if (array->priv->done != NULL) { + size_t i; + char *element = array->items; + for (i = 0; i < array->priv->initialized_count; i++) { + array->priv->done(array->priv->user_data, element); + element += array->priv->element_size; + } + } + if (array->items != NULL) { + cork_free(array->items, array->priv->allocated_size); + } + cork_free_user_data(array->priv); + cork_delete(struct cork_array_priv, array->priv); +} + +void +cork_raw_array_set_callback_data(struct cork_raw_array *array, + void *user_data, cork_free_f free_user_data) +{ + array->priv->user_data = user_data; + array->priv->free_user_data = free_user_data; +} + +void +cork_raw_array_set_init(struct cork_raw_array *array, cork_init_f init) +{ + array->priv->init = init; +} + +void +cork_raw_array_set_done(struct cork_raw_array *array, cork_done_f done) +{ + array->priv->done = done; +} + +void +cork_raw_array_set_reuse(struct cork_raw_array *array, cork_init_f reuse) +{ + array->priv->reuse = reuse; +} + +void +cork_raw_array_set_remove(struct cork_raw_array *array, cork_done_f remove) +{ + array->priv->remove = remove; +} + +size_t +cork_raw_array_element_size(const struct cork_raw_array *array) +{ + return array->priv->element_size; +} + +void +cork_raw_array_clear(struct cork_raw_array *array) +{ + if (array->priv->remove != NULL) { + size_t i; + char *element = array->items; + for (i = 0; i < array->priv->initialized_count; i++) { + array->priv->remove(array->priv->user_data, element); + element += array->priv->element_size; + } + } + array->size = 0; +} + +void * +cork_raw_array_elements(const struct cork_raw_array *array) +{ + return array->items; +} + +void * +cork_raw_array_at(const struct cork_raw_array *array, size_t index) +{ + return ((char *) array->items) + (array->priv->element_size * index); +} + +size_t +cork_raw_array_size(const struct cork_raw_array *array) +{ + return array->size; +} + +bool +cork_raw_array_is_empty(const struct cork_raw_array *array) +{ + return (array->size == 0); +} + +void +cork_raw_array_ensure_size(struct cork_raw_array *array, size_t desired_count) +{ + size_t desired_size; + + DEBUG("--- Array %p: Ensure %zu %zu-byte elements", + array, desired_count, array->priv->element_size); + desired_size = desired_count * array->priv->element_size; + + if (desired_size > array->priv->allocated_size) { + size_t new_count = array->priv->allocated_count * 2; + size_t new_size = array->priv->allocated_size * 2; + if (desired_size > new_size) { + new_count = desired_count; + new_size = desired_size; + } + + DEBUG("--- Array %p: Reallocating %zu->%zu bytes", + array, array->priv->allocated_size, new_size); + array->items = + cork_realloc(array->items, array->priv->allocated_size, new_size); + + array->priv->allocated_count = new_count; + array->priv->allocated_size = new_size; + } +} + +void * +cork_raw_array_append(struct cork_raw_array *array) +{ + size_t index; + void *element; + index = array->size++; + cork_raw_array_ensure_size(array, array->size); + element = cork_raw_array_at(array, index); + + /* Call the init or reset callback, depending on whether this entry has been + * initialized before. */ + + /* Since we can currently only add elements by appending them one at a time, + * then this entry is either already initialized, or is the first + * uninitialized entry. */ + assert(index <= array->priv->initialized_count); + + if (index == array->priv->initialized_count) { + /* This element has not been initialized yet. */ + array->priv->initialized_count++; + if (array->priv->init != NULL) { + array->priv->init(array->priv->user_data, element); + } + } else { + /* This element has already been initialized. */ + if (array->priv->reuse != NULL) { + array->priv->reuse(array->priv->user_data, element); + } + } + + return element; +} + +int +cork_raw_array_copy(struct cork_raw_array *dest, + const struct cork_raw_array *src, + cork_copy_f copy, void *user_data) +{ + size_t i; + size_t reuse_count; + char *dest_element; + + DEBUG("--- Copying %zu elements (%zu bytes) from %p to %p", + src->size, src->size * dest->priv->element_size, src, dest); + assert(dest->priv->element_size == src->priv->element_size); + cork_array_clear(dest); + cork_array_ensure_size(dest, src->size); + + /* Initialize enough elements to hold the contents of src */ + reuse_count = dest->priv->initialized_count; + if (src->size < reuse_count) { + reuse_count = src->size; + } + + dest_element = dest->items; + if (dest->priv->reuse != NULL) { + DEBUG(" Calling reuse on elements 0-%zu", reuse_count); + for (i = 0; i < reuse_count; i++) { + dest->priv->reuse(dest->priv->user_data, dest_element); + dest_element += dest->priv->element_size; + } + } else { + dest_element += reuse_count * dest->priv->element_size; + } + + if (dest->priv->init != NULL) { + DEBUG(" Calling init on elements %zu-%zu", reuse_count, src->size); + for (i = reuse_count; i < src->size; i++) { + dest->priv->init(dest->priv->user_data, dest_element); + dest_element += dest->priv->element_size; + } + } + + if (src->size > dest->priv->initialized_count) { + dest->priv->initialized_count = src->size; + } + + /* If the caller provided a copy function, let it copy each element in turn. + * Otherwise, bulk copy everything using memcpy. */ + if (copy == NULL) { + memcpy(dest->items, src->items, src->size * dest->priv->element_size); + } else { + const char *src_element = src->items; + dest_element = dest->items; + for (i = 0; i < src->size; i++) { + rii_check(copy(user_data, dest_element, src_element)); + dest_element += dest->priv->element_size; + src_element += dest->priv->element_size; + } + } + + dest->size = src->size; + return 0; +} + + +/*----------------------------------------------------------------------- + * Pointer arrays + */ + +struct cork_pointer_array { + cork_free_f free; +}; + +static void +pointer__init(void *user_data, void *vvalue) +{ + void **value = vvalue; + *value = NULL; +} + +static void +pointer__done(void *user_data, void *vvalue) +{ + struct cork_pointer_array *ptr_array = user_data; + void **value = vvalue; + if (*value != NULL) { + ptr_array->free(*value); + } +} + +static void +pointer__remove(void *user_data, void *vvalue) +{ + struct cork_pointer_array *ptr_array = user_data; + void **value = vvalue; + if (*value != NULL) { + ptr_array->free(*value); + } + *value = NULL; +} + +static void +pointer__free(void *user_data) +{ + struct cork_pointer_array *ptr_array = user_data; + cork_delete(struct cork_pointer_array, ptr_array); +} + +void +cork_raw_pointer_array_init(struct cork_raw_array *array, cork_free_f free_ptr) +{ + struct cork_pointer_array *ptr_array = cork_new(struct cork_pointer_array); + ptr_array->free = free_ptr; + cork_raw_array_init(array, sizeof(void *)); + cork_array_set_callback_data(array, ptr_array, pointer__free); + cork_array_set_init(array, pointer__init); + cork_array_set_done(array, pointer__done); + cork_array_set_remove(array, pointer__remove); +} + + +/*----------------------------------------------------------------------- + * String arrays + */ + +void +cork_string_array_init(struct cork_string_array *array) +{ + cork_raw_pointer_array_init + ((struct cork_raw_array *) array, (cork_free_f) cork_strfree); +} + +void +cork_string_array_append(struct cork_string_array *array, const char *str) +{ + const char *copy = cork_strdup(str); + cork_array_append(array, copy); +} + +static int +string__copy(void *user_data, void *vdest, const void *vsrc) +{ + const char **dest = vdest; + const char **src = (const char **) vsrc; + *dest = cork_strdup(*src); + return 0; +} + +void +cork_string_array_copy(struct cork_string_array *dest, + const struct cork_string_array *src) +{ + CORK_ATTR_UNUSED int rc; + rc = cork_array_copy(dest, src, string__copy, NULL); + /* cork_array_copy can only fail if the copy callback fails, and ours + * doesn't! */ + assert(rc == 0); +} diff --git a/3rd/libcork/src/ds/bitset.c b/3rd/libcork/src/ds/bitset.c new file mode 100644 index 0000000..ba1e1a6 --- /dev/null +++ b/3rd/libcork/src/ds/bitset.c @@ -0,0 +1,50 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "libcork/core/allocator.h" +#include "libcork/core/api.h" +#include "libcork/core/types.h" +#include "libcork/ds/bitset.h" + + +static size_t +bytes_needed(size_t bit_count) +{ + /* We need one byte for every bit... */ + size_t bytes_needed = bit_count / 8; + /* Plus one extra for the leftovers that don't fit into a whole byte. */ + bytes_needed += ((bit_count % 8) > 0); + return bytes_needed; +} + +struct cork_bitset * +cork_bitset_new(size_t bit_count) +{ + struct cork_bitset *set = cork_new(struct cork_bitset); + set->bit_count = bit_count; + set->byte_count = bytes_needed(bit_count); + set->bits = cork_malloc(set->byte_count); + memset(set->bits, 0, set->byte_count); + return set; +} + +void +cork_bitset_free(struct cork_bitset *set) +{ + cork_free(set->bits, set->byte_count); + cork_delete(struct cork_bitset, set); +} + +void +cork_bitset_clear(struct cork_bitset *set) +{ + memset(set->bits, 0, set->byte_count); +} diff --git a/3rd/libcork/src/ds/buffer.c b/3rd/libcork/src/ds/buffer.c new file mode 100644 index 0000000..2cd8e1c --- /dev/null +++ b/3rd/libcork/src/ds/buffer.c @@ -0,0 +1,471 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "libcork/core/allocator.h" +#include "libcork/core/types.h" +#include "libcork/ds/buffer.h" +#include "libcork/ds/managed-buffer.h" +#include "libcork/ds/stream.h" +#include "libcork/helpers/errors.h" + + +void +cork_buffer_init(struct cork_buffer *buffer) +{ + buffer->buf = NULL; + buffer->size = 0; + buffer->allocated_size = 0; +} + + +struct cork_buffer * +cork_buffer_new(void) +{ + struct cork_buffer *buffer = cork_new(struct cork_buffer); + cork_buffer_init(buffer); + return buffer; +} + + +void +cork_buffer_done(struct cork_buffer *buffer) +{ + if (buffer->buf != NULL) { + cork_free(buffer->buf, buffer->allocated_size); + buffer->buf = NULL; + } + buffer->size = 0; + buffer->allocated_size = 0; +} + + +void +cork_buffer_free(struct cork_buffer *buffer) +{ + cork_buffer_done(buffer); + cork_delete(struct cork_buffer, buffer); +} + + +bool +cork_buffer_equal(const struct cork_buffer *buffer1, + const struct cork_buffer *buffer2) +{ + if (buffer1 == buffer2) { + return true; + } + + if (buffer1->size != buffer2->size) { + return false; + } + + return (memcmp(buffer1->buf, buffer2->buf, buffer1->size) == 0); +} + + +static void +cork_buffer_ensure_size_int(struct cork_buffer *buffer, size_t desired_size) +{ + size_t new_size; + + if (CORK_LIKELY(buffer->allocated_size >= desired_size)) { + return; + } + + /* Make sure we at least double the old size when reallocating. */ + new_size = buffer->allocated_size * 2; + if (desired_size > new_size) { + new_size = desired_size; + } + + buffer->buf = cork_realloc(buffer->buf, buffer->allocated_size, new_size); + buffer->allocated_size = new_size; +} + +void +cork_buffer_ensure_size(struct cork_buffer *buffer, size_t desired_size) +{ + cork_buffer_ensure_size_int(buffer, desired_size); +} + + +void +cork_buffer_clear(struct cork_buffer *buffer) +{ + buffer->size = 0; + if (buffer->buf != NULL) { + ((char *) buffer->buf)[0] = '\0'; + } +} + +void +cork_buffer_truncate(struct cork_buffer *buffer, size_t length) +{ + if (buffer->size > length) { + buffer->size = length; + if (length == 0) { + if (buffer->buf != NULL) { + ((char *) buffer->buf)[0] = '\0'; + } + } else { + ((char *) buffer->buf)[length] = '\0'; + } + } +} + + +void +cork_buffer_set(struct cork_buffer *buffer, const void *src, size_t length) +{ + cork_buffer_ensure_size_int(buffer, length+1); + memcpy(buffer->buf, src, length); + ((char *) buffer->buf)[length] = '\0'; + buffer->size = length; +} + + +void +cork_buffer_append(struct cork_buffer *buffer, const void *src, size_t length) +{ + cork_buffer_ensure_size_int(buffer, buffer->size + length + 1); + memcpy(buffer->buf + buffer->size, src, length); + buffer->size += length; + ((char *) buffer->buf)[buffer->size] = '\0'; +} + + +void +cork_buffer_set_string(struct cork_buffer *buffer, const char *str) +{ + cork_buffer_set(buffer, str, strlen(str)); +} + + +void +cork_buffer_append_string(struct cork_buffer *buffer, const char *str) +{ + cork_buffer_append(buffer, str, strlen(str)); +} + + +void +cork_buffer_append_vprintf(struct cork_buffer *buffer, const char *format, + va_list args) +{ + size_t format_size; + va_list args1; + + va_copy(args1, args); + format_size = vsnprintf(buffer->buf + buffer->size, + buffer->allocated_size - buffer->size, + format, args1); + va_end(args1); + + /* If the first call works, then set buffer->size and return. */ + if (format_size < (buffer->allocated_size - buffer->size)) { + buffer->size += format_size; + return; + } + + /* If the first call fails, resize buffer and try again. */ + cork_buffer_ensure_size_int + (buffer, buffer->allocated_size + format_size + 1); + format_size = vsnprintf(buffer->buf + buffer->size, + buffer->allocated_size - buffer->size, + format, args); + buffer->size += format_size; +} + + +void +cork_buffer_vprintf(struct cork_buffer *buffer, const char *format, + va_list args) +{ + cork_buffer_clear(buffer); + cork_buffer_append_vprintf(buffer, format, args); +} + + +void +cork_buffer_append_printf(struct cork_buffer *buffer, const char *format, ...) +{ + va_list args; + va_start(args, format); + cork_buffer_append_vprintf(buffer, format, args); + va_end(args); +} + + +void +cork_buffer_printf(struct cork_buffer *buffer, const char *format, ...) +{ + va_list args; + va_start(args, format); + cork_buffer_vprintf(buffer, format, args); + va_end(args); +} + + +void +cork_buffer_append_indent(struct cork_buffer *buffer, size_t indent) +{ + cork_buffer_ensure_size_int(buffer, buffer->size + indent + 1); + memset(buffer->buf + buffer->size, ' ', indent); + buffer->size += indent; + ((char *) buffer->buf)[buffer->size] = '\0'; +} + +/* including space */ +#define is_sprint(ch) ((ch) >= 0x20 && (ch) <= 0x7e) + +/* not including space */ +#define is_print(ch) ((ch) > 0x20 && (ch) <= 0x7e) + +#define is_space(ch) \ + ((ch) == ' ' || \ + (ch) == '\f' || \ + (ch) == '\n' || \ + (ch) == '\r' || \ + (ch) == '\t' || \ + (ch) == '\v') + +#define to_hex(nybble) \ + ((nybble) < 10? '0' + (nybble): 'a' - 10 + (nybble)) + +void +cork_buffer_append_c_string(struct cork_buffer *dest, + const char *chars, size_t length) +{ + size_t i; + cork_buffer_append(dest, "\"", 1); + for (i = 0; i < length; i++) { + char ch = chars[i]; + switch (ch) { + case '\"': + cork_buffer_append_literal(dest, "\\\""); + break; + case '\\': + cork_buffer_append_literal(dest, "\\\\"); + break; + case '\f': + cork_buffer_append_literal(dest, "\\f"); + break; + case '\n': + cork_buffer_append_literal(dest, "\\n"); + break; + case '\r': + cork_buffer_append_literal(dest, "\\r"); + break; + case '\t': + cork_buffer_append_literal(dest, "\\t"); + break; + case '\v': + cork_buffer_append_literal(dest, "\\v"); + break; + default: + if (is_sprint(ch)) { + cork_buffer_append(dest, &chars[i], 1); + } else { + uint8_t byte = ch; + cork_buffer_append_printf(dest, "\\x%02" PRIx8, byte); + } + break; + } + } + cork_buffer_append(dest, "\"", 1); +} + +void +cork_buffer_append_hex_dump(struct cork_buffer *dest, size_t indent, + const char *chars, size_t length) +{ + char hex[3 * 16]; + char print[16]; + char *curr_hex = hex; + char *curr_print = print; + size_t i; + size_t column = 0; + for (i = 0; i < length; i++) { + char ch = chars[i]; + uint8_t u8 = ch; + *curr_hex++ = to_hex(u8 >> 4); + *curr_hex++ = to_hex(u8 & 0x0f); + *curr_hex++ = ' '; + *curr_print++ = is_sprint(ch)? ch: '.'; + if (column == 0 && i != 0) { + cork_buffer_append_literal(dest, "\n"); + cork_buffer_append_indent(dest, indent); + column++; + } else if (column == 15) { + cork_buffer_append_printf + (dest, "%-48.*s", (int) (curr_hex - hex), hex); + cork_buffer_append_literal(dest, " |"); + cork_buffer_append(dest, print, curr_print - print); + cork_buffer_append_literal(dest, "|"); + curr_hex = hex; + curr_print = print; + column = 0; + } else { + column++; + } + } + + if (column > 0) { + cork_buffer_append_printf(dest, "%-48.*s", (int) (curr_hex - hex), hex); + cork_buffer_append_literal(dest, " |"); + cork_buffer_append(dest, print, curr_print - print); + cork_buffer_append_literal(dest, "|"); + } +} + +void +cork_buffer_append_multiline(struct cork_buffer *dest, size_t indent, + const char *chars, size_t length) +{ + size_t i; + for (i = 0; i < length; i++) { + char ch = chars[i]; + if (ch == '\n') { + cork_buffer_append_literal(dest, "\n"); + cork_buffer_append_indent(dest, indent); + } else { + cork_buffer_append(dest, &chars[i], 1); + } + } +} + +void +cork_buffer_append_binary(struct cork_buffer *dest, size_t indent, + const char *chars, size_t length) +{ + size_t i; + bool newline = false; + + /* If there are any non-printable characters, print out a hex dump */ + for (i = 0; i < length; i++) { + if (!is_print(chars[i]) && !is_space(chars[i])) { + cork_buffer_append_literal(dest, "(hex)\n"); + cork_buffer_append_indent(dest, indent); + cork_buffer_append_hex_dump(dest, indent, chars, length); + return; + } else if (chars[i] == '\n') { + newline = true; + /* Don't immediately use the multiline format, since there might be + * a non-printable character later on that kicks us over to the hex + * dump format. */ + } + } + + if (newline) { + cork_buffer_append_literal(dest, "(multiline)\n"); + cork_buffer_append_indent(dest, indent); + cork_buffer_append_multiline(dest, indent, chars, length); + } else { + cork_buffer_append(dest, chars, length); + } +} + + +struct cork_buffer__managed_buffer { + struct cork_managed_buffer parent; + struct cork_buffer *buffer; +}; + +static void +cork_buffer__managed_free(struct cork_managed_buffer *vself) +{ + struct cork_buffer__managed_buffer *self = + cork_container_of(vself, struct cork_buffer__managed_buffer, parent); + cork_buffer_free(self->buffer); + cork_delete(struct cork_buffer__managed_buffer, self); +} + +static struct cork_managed_buffer_iface CORK_BUFFER__MANAGED_BUFFER = { + cork_buffer__managed_free +}; + +struct cork_managed_buffer * +cork_buffer_to_managed_buffer(struct cork_buffer *buffer) +{ + struct cork_buffer__managed_buffer *self = + cork_new(struct cork_buffer__managed_buffer); + self->parent.buf = buffer->buf; + self->parent.size = buffer->size; + self->parent.ref_count = 1; + self->parent.iface = &CORK_BUFFER__MANAGED_BUFFER; + self->buffer = buffer; + return &self->parent; +} + + +int +cork_buffer_to_slice(struct cork_buffer *buffer, struct cork_slice *slice) +{ + struct cork_managed_buffer *managed = + cork_buffer_to_managed_buffer(buffer); + + /* We don't have to check for NULL; cork_managed_buffer_slice_offset + * will do that for us. */ + int rc = cork_managed_buffer_slice_offset(slice, managed, 0); + + /* Before returning, drop our reference to the managed buffer. If + * the slicing succeeded, then there will be one remaining reference + * in the slice. If it didn't succeed, this will free the managed + * buffer for us. */ + cork_managed_buffer_unref(managed); + return rc; +} + + +struct cork_buffer__stream_consumer { + struct cork_stream_consumer consumer; + struct cork_buffer *buffer; +}; + +static int +cork_buffer_stream_consumer_data(struct cork_stream_consumer *consumer, + const void *buf, size_t size, + bool is_first_chunk) +{ + struct cork_buffer__stream_consumer *bconsumer = cork_container_of + (consumer, struct cork_buffer__stream_consumer, consumer); + cork_buffer_append(bconsumer->buffer, buf, size); + return 0; +} + +static int +cork_buffer_stream_consumer_eof(struct cork_stream_consumer *consumer) +{ + return 0; +} + +static void +cork_buffer_stream_consumer_free(struct cork_stream_consumer *consumer) +{ + struct cork_buffer__stream_consumer *bconsumer = + cork_container_of + (consumer, struct cork_buffer__stream_consumer, consumer); + cork_delete(struct cork_buffer__stream_consumer, bconsumer); +} + +struct cork_stream_consumer * +cork_buffer_to_stream_consumer(struct cork_buffer *buffer) +{ + struct cork_buffer__stream_consumer *bconsumer = + cork_new(struct cork_buffer__stream_consumer); + bconsumer->consumer.data = cork_buffer_stream_consumer_data; + bconsumer->consumer.eof = cork_buffer_stream_consumer_eof; + bconsumer->consumer.free = cork_buffer_stream_consumer_free; + bconsumer->buffer = buffer; + return &bconsumer->consumer; +} diff --git a/3rd/libcork/src/ds/dllist.c b/3rd/libcork/src/ds/dllist.c new file mode 100644 index 0000000..dbaafbc --- /dev/null +++ b/3rd/libcork/src/ds/dllist.c @@ -0,0 +1,63 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include "libcork/core/api.h" +#include "libcork/core/types.h" +#include "libcork/ds/dllist.h" + + +/* Include a linkable (but deprecated) version of this to maintain ABI + * compatibility. */ +#undef cork_dllist_init +CORK_API void +cork_dllist_init(struct cork_dllist *list) +{ + list->head.next = &list->head; + list->head.prev = &list->head; +} + + +void +cork_dllist_map(struct cork_dllist *list, + cork_dllist_map_func func, void *user_data) +{ + struct cork_dllist_item *curr; + struct cork_dllist_item *next; + cork_dllist_foreach_void(list, curr, next) { + func(curr, user_data); + } +} + +int +cork_dllist_visit(struct cork_dllist *list, void *ud, + cork_dllist_visit_f *visit) +{ + struct cork_dllist_item *curr; + struct cork_dllist_item *next; + cork_dllist_foreach_void(list, curr, next) { + int rc = visit(ud, curr); + if (rc != 0) { + return rc; + } + } + return 0; +} + + +size_t +cork_dllist_size(const struct cork_dllist *list) +{ + size_t size = 0; + struct cork_dllist_item *curr; + struct cork_dllist_item *next; + cork_dllist_foreach_void(list, curr, next) { + size++; + } + return size; +} diff --git a/3rd/libcork/src/ds/file-stream.c b/3rd/libcork/src/ds/file-stream.c new file mode 100644 index 0000000..c9a7c73 --- /dev/null +++ b/3rd/libcork/src/ds/file-stream.c @@ -0,0 +1,214 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include + +#include "libcork/ds/stream.h" +#include "libcork/helpers/errors.h" +#include "libcork/helpers/posix.h" + +#define BUFFER_SIZE 4096 + + +/*----------------------------------------------------------------------- + * Producers + */ + +int +cork_consume_fd(struct cork_stream_consumer *consumer, int fd) +{ + char buf[BUFFER_SIZE]; + ssize_t bytes_read; + bool first = true; + + while (true) { + while ((bytes_read = read(fd, buf, BUFFER_SIZE)) > 0) { + rii_check(cork_stream_consumer_data + (consumer, buf, bytes_read, first)); + first = false; + } + + if (bytes_read == 0) { + return cork_stream_consumer_eof(consumer); + } else if (errno != EINTR) { + cork_system_error_set(); + return -1; + } + } +} + +int +cork_consume_file(struct cork_stream_consumer *consumer, FILE *fp) +{ + char buf[BUFFER_SIZE]; + size_t bytes_read; + bool first = true; + + while (true) { + while ((bytes_read = fread(buf, 1, BUFFER_SIZE, fp)) > 0) { + rii_check(cork_stream_consumer_data + (consumer, buf, bytes_read, first)); + first = false; + } + + if (feof(fp)) { + return cork_stream_consumer_eof(consumer); + } else if (errno != EINTR) { + cork_system_error_set(); + return -1; + } + } +} + +int +cork_consume_file_from_path(struct cork_stream_consumer *consumer, + const char *path, int flags) +{ + int fd; + rii_check_posix(fd = open(path, flags)); + ei_check(cork_consume_fd(consumer, fd)); + rii_check_posix(close(fd)); + return 0; + +error: + rii_check_posix(close(fd)); + return -1; +} + + +/*----------------------------------------------------------------------- + * Consumers + */ + +struct cork_file_consumer { + struct cork_stream_consumer parent; + FILE *fp; +}; + +static int +cork_file_consumer__data(struct cork_stream_consumer *vself, + const void *buf, size_t size, bool is_first) +{ + struct cork_file_consumer *self = + cork_container_of(vself, struct cork_file_consumer, parent); + size_t bytes_written = fwrite(buf, 1, size, self->fp); + /* If there was an error writing to the file, then signal this to + * the producer */ + if (bytes_written == size) { + return 0; + } else { + cork_system_error_set(); + return -1; + } +} + +static int +cork_file_consumer__eof(struct cork_stream_consumer *vself) +{ + /* We never close the file with this version of the consumer, so there's + * nothing special to do at end-of-stream. */ + return 0; +} + +static void +cork_file_consumer__free(struct cork_stream_consumer *vself) +{ + struct cork_file_consumer *self = + cork_container_of(vself, struct cork_file_consumer, parent); + cork_delete(struct cork_file_consumer, self); +} + +struct cork_stream_consumer * +cork_file_consumer_new(FILE *fp) +{ + struct cork_file_consumer *self = cork_new(struct cork_file_consumer); + self->parent.data = cork_file_consumer__data; + self->parent.eof = cork_file_consumer__eof; + self->parent.free = cork_file_consumer__free; + self->fp = fp; + return &self->parent; +} + + +struct cork_fd_consumer { + struct cork_stream_consumer parent; + int fd; +}; + +static int +cork_fd_consumer__data(struct cork_stream_consumer *vself, + const void *buf, size_t size, bool is_first) +{ + struct cork_fd_consumer *self = + cork_container_of(vself, struct cork_fd_consumer, parent); + size_t bytes_left = size; + + while (bytes_left > 0) { + ssize_t rc = write(self->fd, buf, bytes_left); + if (rc == -1 && errno != EINTR) { + cork_system_error_set(); + return -1; + } else { + bytes_left -= rc; + buf += rc; + } + } + + return 0; +} + +static int +cork_fd_consumer__eof_close(struct cork_stream_consumer *vself) +{ + int rc; + struct cork_fd_consumer *self = + cork_container_of(vself, struct cork_fd_consumer, parent); + rii_check_posix(rc = close(self->fd)); + return 0; +} + +static void +cork_fd_consumer__free(struct cork_stream_consumer *vself) +{ + struct cork_fd_consumer *self = + cork_container_of(vself, struct cork_fd_consumer, parent); + cork_delete(struct cork_fd_consumer, self); +} + +struct cork_stream_consumer * +cork_fd_consumer_new(int fd) +{ + struct cork_fd_consumer *self = cork_new(struct cork_fd_consumer); + self->parent.data = cork_fd_consumer__data; + /* We don't want to close fd, so we reuse file_consumer's eof method */ + self->parent.eof = cork_file_consumer__eof; + self->parent.free = cork_fd_consumer__free; + self->fd = fd; + return &self->parent; +} + +struct cork_stream_consumer * +cork_file_from_path_consumer_new(const char *path, int flags) +{ + + int fd; + struct cork_fd_consumer *self; + + rpi_check_posix(fd = open(path, flags)); + self = cork_new(struct cork_fd_consumer); + self->parent.data = cork_fd_consumer__data; + self->parent.eof = cork_fd_consumer__eof_close; + self->parent.free = cork_fd_consumer__free; + self->fd = fd; + return &self->parent; +} diff --git a/3rd/libcork/src/ds/hash-table.c b/3rd/libcork/src/ds/hash-table.c new file mode 100644 index 0000000..280a7bd --- /dev/null +++ b/3rd/libcork/src/ds/hash-table.c @@ -0,0 +1,689 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "libcork/core/callbacks.h" +#include "libcork/core/hash.h" +#include "libcork/core/types.h" +#include "libcork/ds/dllist.h" +#include "libcork/ds/hash-table.h" +#include "libcork/helpers/errors.h" + +#ifndef CORK_HASH_TABLE_DEBUG +#define CORK_HASH_TABLE_DEBUG 0 +#endif + +#if CORK_HASH_TABLE_DEBUG +#include +#define DEBUG(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#else +#define DEBUG(...) /* nothing */ +#endif + + +/*----------------------------------------------------------------------- + * Hash tables + */ + +struct cork_hash_table_entry_priv { + struct cork_hash_table_entry public; + struct cork_dllist_item in_bucket; + struct cork_dllist_item insertion_order; +}; + +struct cork_hash_table { + struct cork_dllist *bins; + struct cork_dllist insertion_order; + size_t bin_count; + size_t bin_mask; + size_t entry_count; + void *user_data; + cork_free_f free_user_data; + cork_hash_f hash; + cork_equals_f equals; + cork_free_f free_key; + cork_free_f free_value; +}; + +static cork_hash +cork_hash_table__default_hash(void *user_data, const void *key) +{ + return (cork_hash) (uintptr_t) key; +} + +static bool +cork_hash_table__default_equals(void *user_data, + const void *key1, const void *key2) +{ + return key1 == key2; +} + + +/* The default initial number of bins to allocate in a new table. */ +#define CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE 8 + +/* The default number of entries per bin to allow before increasing the + * number of bins. */ +#define CORK_HASH_TABLE_MAX_DENSITY 5 + +/* Return a power-of-2 bin count that's at least as big as the given requested + * size. */ +static inline size_t +cork_hash_table_new_size(size_t desired_count) +{ + size_t v = desired_count; + size_t r = 1; + while (v >>= 1) { + r <<= 1; + } + if (r != desired_count) { + r <<= 1; + } + return r; +} + +#define bin_index(table, hash) ((hash) & (table)->bin_mask) + +/* Allocates a new bins array in a hash table. We overwrite the old + * array, so make sure to stash it away somewhere safe first. */ +static void +cork_hash_table_allocate_bins(struct cork_hash_table *table, + size_t desired_count) +{ + size_t i; + + table->bin_count = cork_hash_table_new_size(desired_count); + table->bin_mask = table->bin_count - 1; + DEBUG("Allocate %zu bins", table->bin_count); + table->bins = cork_calloc(table->bin_count, sizeof(struct cork_dllist)); + for (i = 0; i < table->bin_count; i++) { + cork_dllist_init(&table->bins[i]); + } +} + + +static struct cork_hash_table_entry_priv * +cork_hash_table_new_entry(struct cork_hash_table *table, + cork_hash hash, void *key, void *value) +{ + struct cork_hash_table_entry_priv *entry = + cork_new(struct cork_hash_table_entry_priv); + cork_dllist_add(&table->insertion_order, &entry->insertion_order); + entry->public.hash = hash; + entry->public.key = key; + entry->public.value = value; + return entry; +} + +static void +cork_hash_table_free_entry(struct cork_hash_table *table, + struct cork_hash_table_entry_priv *entry) +{ + if (table->free_key != NULL) { + table->free_key(entry->public.key); + } + if (table->free_value != NULL) { + table->free_value(entry->public.value); + } + cork_dllist_remove(&entry->insertion_order); + cork_delete(struct cork_hash_table_entry_priv, entry); +} + + +struct cork_hash_table * +cork_hash_table_new(size_t initial_size, unsigned int flags) +{ + struct cork_hash_table *table = cork_new(struct cork_hash_table); + table->entry_count = 0; + table->user_data = NULL; + table->free_user_data = NULL; + table->hash = cork_hash_table__default_hash; + table->equals = cork_hash_table__default_equals; + table->free_key = NULL; + table->free_value = NULL; + cork_dllist_init(&table->insertion_order); + if (initial_size < CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE) { + initial_size = CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE; + } + cork_hash_table_allocate_bins(table, initial_size); + return table; +} + +void +cork_hash_table_clear(struct cork_hash_table *table) +{ + size_t i; + struct cork_dllist_item *curr; + struct cork_dllist_item *next; + + DEBUG("(clear) Remove all entries"); + for (curr = cork_dllist_start(&table->insertion_order); + !cork_dllist_is_end(&table->insertion_order, curr); + curr = next) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, insertion_order); + next = curr->next; + cork_hash_table_free_entry(table, entry); + } + cork_dllist_init(&table->insertion_order); + + DEBUG("(clear) Clear bins"); + for (i = 0; i < table->bin_count; i++) { + DEBUG(" Bin %zu", i); + cork_dllist_init(&table->bins[i]); + } + + table->entry_count = 0; +} + +void +cork_hash_table_free(struct cork_hash_table *table) +{ + cork_hash_table_clear(table); + cork_cfree(table->bins, table->bin_count, sizeof(struct cork_dllist)); + cork_delete(struct cork_hash_table, table); +} + +size_t +cork_hash_table_size(const struct cork_hash_table *table) +{ + return table->entry_count; +} + +void +cork_hash_table_set_user_data(struct cork_hash_table *table, + void *user_data, cork_free_f free_user_data) +{ + table->user_data = user_data; + table->free_user_data = free_user_data; +} + +void +cork_hash_table_set_hash(struct cork_hash_table *table, cork_hash_f hash) +{ + table->hash = hash; +} + +void +cork_hash_table_set_equals(struct cork_hash_table *table, cork_equals_f equals) +{ + table->equals = equals; +} + +void +cork_hash_table_set_free_key(struct cork_hash_table *table, cork_free_f free) +{ + table->free_key = free; +} + +void +cork_hash_table_set_free_value(struct cork_hash_table *table, cork_free_f free) +{ + table->free_value = free; +} + + +void +cork_hash_table_ensure_size(struct cork_hash_table *table, size_t desired_count) +{ + if (desired_count > table->bin_count) { + struct cork_dllist *old_bins = table->bins; + size_t old_bin_count = table->bin_count; + + cork_hash_table_allocate_bins(table, desired_count); + + if (old_bins != NULL) { + size_t i; + for (i = 0; i < old_bin_count; i++) { + struct cork_dllist *bin = &old_bins[i]; + struct cork_dllist_item *curr = cork_dllist_start(bin); + while (!cork_dllist_is_end(bin, curr)) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, in_bucket); + struct cork_dllist_item *next = curr->next; + + size_t bin_index = bin_index(table, entry->public.hash); + DEBUG(" Rehash %p from bin %zu to bin %zu", + entry, i, bin_index); + cork_dllist_add(&table->bins[bin_index], curr); + + curr = next; + } + } + + cork_cfree(old_bins, old_bin_count, sizeof(struct cork_dllist)); + } + } +} + + +static void +cork_hash_table_rehash(struct cork_hash_table *table) +{ + DEBUG(" Reached maximum density; rehash"); + cork_hash_table_ensure_size(table, table->bin_count + 1); +} + + +struct cork_hash_table_entry * +cork_hash_table_get_entry_hash(const struct cork_hash_table *table, + cork_hash hash, const void *key) +{ + size_t bin_index; + struct cork_dllist *bin; + struct cork_dllist_item *curr; + + if (table->bin_count == 0) { + DEBUG("(get) Empty table when searching for key %p " + "(hash 0x%08" PRIx32 ")", + key, hash); + return NULL; + } + + bin_index = bin_index(table, hash); + DEBUG("(get) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", + key, hash, bin_index); + + bin = &table->bins[bin_index]; + curr = cork_dllist_start(bin); + while (!cork_dllist_is_end(bin, curr)) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, in_bucket); + + DEBUG(" Check entry %p", entry); + if (table->equals(table->user_data, key, entry->public.key)) { + DEBUG(" Match"); + return &entry->public; + } + + curr = curr->next; + } + + DEBUG(" Entry not found"); + return NULL; +} + +struct cork_hash_table_entry * +cork_hash_table_get_entry(const struct cork_hash_table *table, const void *key) +{ + cork_hash hash = table->hash(table->user_data, key); + return cork_hash_table_get_entry_hash(table, hash, key); +} + +void * +cork_hash_table_get_hash(const struct cork_hash_table *table, + cork_hash hash, const void *key) +{ + struct cork_hash_table_entry *entry = + cork_hash_table_get_entry_hash(table, hash, key); + if (entry == NULL) { + return NULL; + } else { + DEBUG(" Extract value pointer %p", entry->value); + return entry->value; + } +} + +void * +cork_hash_table_get(const struct cork_hash_table *table, const void *key) +{ + struct cork_hash_table_entry *entry = + cork_hash_table_get_entry(table, key); + if (entry == NULL) { + return NULL; + } else { + DEBUG(" Extract value pointer %p", entry->value); + return entry->value; + } +} + + +struct cork_hash_table_entry * +cork_hash_table_get_or_create_hash(struct cork_hash_table *table, + cork_hash hash, void *key, bool *is_new) +{ + struct cork_hash_table_entry_priv *entry; + size_t bin_index; + + if (table->bin_count > 0) { + struct cork_dllist *bin; + struct cork_dllist_item *curr; + + bin_index = bin_index(table, hash); + DEBUG("(get_or_create) Search for key %p " + "(hash 0x%08" PRIx32 ", bin %zu)", + key, hash, bin_index); + + bin = &table->bins[bin_index]; + curr = cork_dllist_start(bin); + while (!cork_dllist_is_end(bin, curr)) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, in_bucket); + + DEBUG(" Check entry %p", entry); + if (table->equals(table->user_data, key, entry->public.key)) { + DEBUG(" Match"); + DEBUG(" Return value pointer %p", entry->public.value); + *is_new = false; + return &entry->public; + } + + curr = curr->next; + } + + /* create a new entry */ + DEBUG(" Entry not found"); + + if ((table->entry_count / table->bin_count) > + CORK_HASH_TABLE_MAX_DENSITY) { + cork_hash_table_rehash(table); + bin_index = bin_index(table, hash); + } + } else { + DEBUG("(get_or_create) Search for key %p (hash 0x%08" PRIx32 ")", + key, hash); + DEBUG(" Empty table"); + cork_hash_table_rehash(table); + bin_index = bin_index(table, hash); + } + + DEBUG(" Allocate new entry"); + entry = cork_hash_table_new_entry(table, hash, key, NULL); + DEBUG(" Created new entry %p", entry); + + DEBUG(" Add entry into bin %zu", bin_index); + cork_dllist_add(&table->bins[bin_index], &entry->in_bucket); + + table->entry_count++; + *is_new = true; + return &entry->public; +} + +struct cork_hash_table_entry * +cork_hash_table_get_or_create(struct cork_hash_table *table, + void *key, bool *is_new) +{ + cork_hash hash = table->hash(table->user_data, key); + return cork_hash_table_get_or_create_hash(table, hash, key, is_new); +} + + +void +cork_hash_table_put_hash(struct cork_hash_table *table, + cork_hash hash, void *key, void *value, + bool *is_new, void **old_key, void **old_value) +{ + struct cork_hash_table_entry_priv *entry; + size_t bin_index; + + if (table->bin_count > 0) { + struct cork_dllist *bin; + struct cork_dllist_item *curr; + + bin_index = bin_index(table, hash); + DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", + key, hash, bin_index); + + bin = &table->bins[bin_index]; + curr = cork_dllist_start(bin); + while (!cork_dllist_is_end(bin, curr)) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, in_bucket); + + DEBUG(" Check entry %p", entry); + if (table->equals(table->user_data, key, entry->public.key)) { + DEBUG(" Found existing entry; overwriting"); + DEBUG(" Return old key %p", entry->public.key); + if (old_key != NULL) { + *old_key = entry->public.key; + } + DEBUG(" Return old value %p", entry->public.value); + if (old_value != NULL) { + *old_value = entry->public.value; + } + DEBUG(" Copy key %p into entry", key); + entry->public.key = key; + DEBUG(" Copy value %p into entry", value); + entry->public.value = value; + if (is_new != NULL) { + *is_new = false; + } + return; + } + + curr = curr->next; + } + + /* create a new entry */ + DEBUG(" Entry not found"); + if ((table->entry_count / table->bin_count) > + CORK_HASH_TABLE_MAX_DENSITY) { + cork_hash_table_rehash(table); + bin_index = bin_index(table, hash); + } + } else { + DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ")", + key, hash); + DEBUG(" Empty table"); + cork_hash_table_rehash(table); + bin_index = bin_index(table, hash); + } + + DEBUG(" Allocate new entry"); + entry = cork_hash_table_new_entry(table, hash, key, value); + DEBUG(" Created new entry %p", entry); + + DEBUG(" Add entry into bin %zu", bin_index); + cork_dllist_add(&table->bins[bin_index], &entry->in_bucket); + + table->entry_count++; + if (old_key != NULL) { + *old_key = NULL; + } + if (old_value != NULL) { + *old_value = NULL; + } + if (is_new != NULL) { + *is_new = true; + } +} + +void +cork_hash_table_put(struct cork_hash_table *table, + void *key, void *value, + bool *is_new, void **old_key, void **old_value) +{ + cork_hash hash = table->hash(table->user_data, key); + cork_hash_table_put_hash + (table, hash, key, value, is_new, old_key, old_value); +} + + +void +cork_hash_table_delete_entry(struct cork_hash_table *table, + struct cork_hash_table_entry *ventry) +{ + struct cork_hash_table_entry_priv *entry = + cork_container_of(ventry, struct cork_hash_table_entry_priv, public); + cork_dllist_remove(&entry->in_bucket); + table->entry_count--; + cork_hash_table_free_entry(table, entry); +} + + +bool +cork_hash_table_delete_hash(struct cork_hash_table *table, + cork_hash hash, const void *key, + void **deleted_key, void **deleted_value) +{ + size_t bin_index; + struct cork_dllist *bin; + struct cork_dllist_item *curr; + + if (table->bin_count == 0) { + DEBUG("(delete) Empty table when searching for key %p " + "(hash 0x%08" PRIx32 ")", + key, hash); + return false; + } + + bin_index = bin_index(table, hash); + DEBUG("(delete) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", + key, hash, bin_index); + + bin = &table->bins[bin_index]; + curr = cork_dllist_start(bin); + while (!cork_dllist_is_end(bin, curr)) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, in_bucket); + + DEBUG(" Check entry %p", entry); + if (table->equals(table->user_data, key, entry->public.key)) { + DEBUG(" Match"); + if (deleted_key != NULL) { + *deleted_key = entry->public.key; + } + if (deleted_value != NULL) { + *deleted_value = entry->public.value; + } + + DEBUG(" Remove entry from hash bin %zu", bin_index); + cork_dllist_remove(curr); + table->entry_count--; + + DEBUG(" Free entry %p", entry); + cork_hash_table_free_entry(table, entry); + return true; + } + + curr = curr->next; + } + + DEBUG(" Entry not found"); + return false; +} + +bool +cork_hash_table_delete(struct cork_hash_table *table, const void *key, + void **deleted_key, void **deleted_value) +{ + cork_hash hash = table->hash(table->user_data, key); + return cork_hash_table_delete_hash + (table, hash, key, deleted_key, deleted_value); +} + + +void +cork_hash_table_map(struct cork_hash_table *table, void *user_data, + cork_hash_table_map_f map) +{ + struct cork_dllist_item *curr; + DEBUG("Map across hash table"); + + curr = cork_dllist_start(&table->insertion_order); + while (!cork_dllist_is_end(&table->insertion_order, curr)) { + struct cork_hash_table_entry_priv *entry = + cork_container_of + (curr, struct cork_hash_table_entry_priv, insertion_order); + struct cork_dllist_item *next = curr->next; + enum cork_hash_table_map_result result; + + DEBUG(" Apply function to entry %p", entry); + result = map(user_data, &entry->public); + + if (result == CORK_HASH_TABLE_MAP_ABORT) { + return; + } else if (result == CORK_HASH_TABLE_MAP_DELETE) { + DEBUG(" Delete requested"); + cork_dllist_remove(curr); + cork_dllist_remove(&entry->in_bucket); + table->entry_count--; + cork_hash_table_free_entry(table, entry); + } + + curr = next; + } +} + + +void +cork_hash_table_iterator_init(struct cork_hash_table *table, + struct cork_hash_table_iterator *iterator) +{ + DEBUG("Iterate through hash table"); + iterator->table = table; + iterator->priv = cork_dllist_start(&table->insertion_order); +} + + +struct cork_hash_table_entry * +cork_hash_table_iterator_next(struct cork_hash_table_iterator *iterator) +{ + struct cork_hash_table *table = iterator->table; + struct cork_dllist_item *curr = iterator->priv; + struct cork_hash_table_entry_priv *entry; + + if (cork_dllist_is_end(&table->insertion_order, curr)) { + return NULL; + } + + entry = cork_container_of + (curr, struct cork_hash_table_entry_priv, insertion_order); + DEBUG(" Return entry %p", entry); + iterator->priv = curr->next; + return &entry->public; +} + + +/*----------------------------------------------------------------------- + * Built-in key types + */ + +static cork_hash +string_hash(void *user_data, const void *vk) +{ + const char *k = vk; + size_t len = strlen(k); + return cork_hash_buffer(0, k, len); +} + +static bool +string_equals(void *user_data, const void *vk1, const void *vk2) +{ + const char *k1 = vk1; + const char *k2 = vk2; + return strcmp(k1, k2) == 0; +} + +struct cork_hash_table * +cork_string_hash_table_new(size_t initial_size, unsigned int flags) +{ + struct cork_hash_table *table = cork_hash_table_new(initial_size, flags); + cork_hash_table_set_hash(table, string_hash); + cork_hash_table_set_equals(table, string_equals); + return table; +} + +struct cork_hash_table * +cork_pointer_hash_table_new(size_t initial_size, unsigned int flags) +{ + return cork_hash_table_new(initial_size, flags); +} diff --git a/3rd/libcork/src/ds/managed-buffer.c b/3rd/libcork/src/ds/managed-buffer.c new file mode 100644 index 0000000..830b88f --- /dev/null +++ b/3rd/libcork/src/ds/managed-buffer.c @@ -0,0 +1,240 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "libcork/core/error.h" +#include "libcork/core/types.h" +#include "libcork/ds/managed-buffer.h" +#include "libcork/ds/slice.h" +#include "libcork/helpers/errors.h" + + +/*----------------------------------------------------------------------- + * Error handling + */ + +static void +cork_slice_invalid_slice_set(size_t buf_size, size_t requested_offset, + size_t requested_length) +{ + cork_error_set + (CORK_SLICE_ERROR, CORK_SLICE_INVALID_SLICE, + "Cannot slice %zu-byte buffer at %zu:%zu", + buf_size, requested_offset, requested_length); +} + + +/*----------------------------------------------------------------------- + * Managed buffers + */ + +struct cork_managed_buffer_wrapped { + struct cork_managed_buffer parent; + void *buf; + size_t size; + cork_managed_buffer_freer free; +}; + +static void +cork_managed_buffer_wrapped__free(struct cork_managed_buffer *vself) +{ + struct cork_managed_buffer_wrapped *self = + cork_container_of(vself, struct cork_managed_buffer_wrapped, parent); + self->free(self->buf, self->size); + cork_delete(struct cork_managed_buffer_wrapped, self); +} + +static struct cork_managed_buffer_iface CORK_MANAGED_BUFFER_WRAPPED = { + cork_managed_buffer_wrapped__free +}; + +struct cork_managed_buffer * +cork_managed_buffer_new(const void *buf, size_t size, + cork_managed_buffer_freer free) +{ + /* + DEBUG("Creating new struct cork_managed_buffer [%p:%zu], refcount now 1", + buf, size); + */ + + struct cork_managed_buffer_wrapped *self = + cork_new(struct cork_managed_buffer_wrapped); + self->parent.buf = buf; + self->parent.size = size; + self->parent.ref_count = 1; + self->parent.iface = &CORK_MANAGED_BUFFER_WRAPPED; + self->buf = (void *) buf; + self->size = size; + self->free = free; + return &self->parent; +} + + +struct cork_managed_buffer_copied { + struct cork_managed_buffer parent; +}; + +#define cork_managed_buffer_copied_data(self) \ + (((void *) (self)) + sizeof(struct cork_managed_buffer_copied)) + +#define cork_managed_buffer_copied_sizeof(sz) \ + ((sz) + sizeof(struct cork_managed_buffer_copied)) + +static void +cork_managed_buffer_copied__free(struct cork_managed_buffer *vself) +{ + struct cork_managed_buffer_copied *self = + cork_container_of(vself, struct cork_managed_buffer_copied, parent); + size_t allocated_size = + cork_managed_buffer_copied_sizeof(self->parent.size); + cork_free(self, allocated_size); +} + +static struct cork_managed_buffer_iface CORK_MANAGED_BUFFER_COPIED = { + cork_managed_buffer_copied__free +}; + +struct cork_managed_buffer * +cork_managed_buffer_new_copy(const void *buf, size_t size) +{ + size_t allocated_size = cork_managed_buffer_copied_sizeof(size); + struct cork_managed_buffer_copied *self = cork_malloc(allocated_size); + if (self == NULL) { + return NULL; + } + + self->parent.buf = cork_managed_buffer_copied_data(self); + self->parent.size = size; + self->parent.ref_count = 1; + self->parent.iface = &CORK_MANAGED_BUFFER_COPIED; + memcpy((void *) self->parent.buf, buf, size); + return &self->parent; +} + + +static void +cork_managed_buffer_free(struct cork_managed_buffer *self) +{ + /* + DEBUG("Freeing struct cork_managed_buffer [%p:%zu]", self->buf, self->size); + */ + + self->iface->free(self); +} + + +struct cork_managed_buffer * +cork_managed_buffer_ref(struct cork_managed_buffer *self) +{ + /* + int old_count = self->ref_count++; + DEBUG("Referencing struct cork_managed_buffer [%p:%zu], refcount now %d", + self->buf, self->size, old_count + 1); + */ + + self->ref_count++; + return self; +} + + +void +cork_managed_buffer_unref(struct cork_managed_buffer *self) +{ + /* + int old_count = self->ref_count--; + DEBUG("Dereferencing struct cork_managed_buffer [%p:%zu], refcount now %d", + self->buf, self->size, old_count - 1); + */ + + if (--self->ref_count == 0) { + cork_managed_buffer_free(self); + } +} + + +static struct cork_slice_iface CORK_MANAGED_BUFFER__SLICE; + +static void +cork_managed_buffer__slice_free(struct cork_slice *self) +{ + struct cork_managed_buffer *mbuf = self->user_data; + cork_managed_buffer_unref(mbuf); +} + +static int +cork_managed_buffer__slice_copy(struct cork_slice *dest, + const struct cork_slice *src, + size_t offset, size_t length) +{ + struct cork_managed_buffer *mbuf = src->user_data; + dest->buf = src->buf + offset; + dest->size = length; + dest->iface = &CORK_MANAGED_BUFFER__SLICE; + dest->user_data = cork_managed_buffer_ref(mbuf); + return 0; +} + +static struct cork_slice_iface CORK_MANAGED_BUFFER__SLICE = { + cork_managed_buffer__slice_free, + cork_managed_buffer__slice_copy, + cork_managed_buffer__slice_copy, + NULL +}; + + +int +cork_managed_buffer_slice(struct cork_slice *dest, + struct cork_managed_buffer *buffer, + size_t offset, size_t length) +{ + if ((buffer != NULL) && + (offset <= buffer->size) && + ((offset + length) <= buffer->size)) { + /* + DEBUG("Slicing [%p:%zu] at %zu:%zu, gives <%p:%zu>", + buffer->buf, buffer->size, + offset, length, + buffer->buf + offset, length); + */ + dest->buf = buffer->buf + offset; + dest->size = length; + dest->iface = &CORK_MANAGED_BUFFER__SLICE; + dest->user_data = cork_managed_buffer_ref(buffer); + return 0; + } + + else { + /* + DEBUG("Cannot slice [%p:%zu] at %zu:%zu", + buffer->buf, buffer->size, + offset, length); + */ + cork_slice_clear(dest); + cork_slice_invalid_slice_set(0, offset, 0); + return -1; + } +} + + +int +cork_managed_buffer_slice_offset(struct cork_slice *dest, + struct cork_managed_buffer *buffer, + size_t offset) +{ + if (buffer == NULL) { + cork_slice_clear(dest); + cork_slice_invalid_slice_set(0, offset, 0); + return -1; + } else { + return cork_managed_buffer_slice + (dest, buffer, offset, buffer->size - offset); + } +} diff --git a/3rd/libcork/src/ds/ring-buffer.c b/3rd/libcork/src/ds/ring-buffer.c new file mode 100644 index 0000000..92eb8b9 --- /dev/null +++ b/3rd/libcork/src/ds/ring-buffer.c @@ -0,0 +1,87 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "libcork/core/allocator.h" +#include "libcork/core/types.h" +#include "libcork/ds/ring-buffer.h" + + +int +cork_ring_buffer_init(struct cork_ring_buffer *self, size_t size) +{ + self->elements = cork_calloc(size, sizeof(void *)); + self->allocated_size = size; + self->size = 0; + self->read_index = 0; + self->write_index = 0; + return 0; +} + +struct cork_ring_buffer * +cork_ring_buffer_new(size_t size) +{ + struct cork_ring_buffer *buf = cork_new(struct cork_ring_buffer); + cork_ring_buffer_init(buf, size); + return buf; +} + +void +cork_ring_buffer_done(struct cork_ring_buffer *self) +{ + cork_cfree(self->elements, self->allocated_size, sizeof(void *)); +} + +void +cork_ring_buffer_free(struct cork_ring_buffer *buf) +{ + cork_ring_buffer_done(buf); + cork_delete(struct cork_ring_buffer, buf); +} + +int +cork_ring_buffer_add(struct cork_ring_buffer *self, void *element) +{ + if (cork_ring_buffer_is_full(self)) { + return -1; + } + + self->elements[self->write_index++] = element; + self->size++; + if (self->write_index == self->allocated_size) { + self->write_index = 0; + } + return 0; +} + +void * +cork_ring_buffer_pop(struct cork_ring_buffer *self) +{ + if (cork_ring_buffer_is_empty(self)) { + return NULL; + } else { + void *result = self->elements[self->read_index++]; + self->size--; + if (self->read_index == self->allocated_size) { + self->read_index = 0; + } + return result; + } +} + +void * +cork_ring_buffer_peek(struct cork_ring_buffer *self) +{ + if (cork_ring_buffer_is_empty(self)) { + return NULL; + } else { + return self->elements[self->read_index]; + } +} diff --git a/3rd/libcork/src/ds/slice.c b/3rd/libcork/src/ds/slice.c new file mode 100644 index 0000000..68de06c --- /dev/null +++ b/3rd/libcork/src/ds/slice.c @@ -0,0 +1,296 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2011-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "libcork/core/error.h" +#include "libcork/core/types.h" +#include "libcork/ds/managed-buffer.h" +#include "libcork/ds/slice.h" +#include "libcork/helpers/errors.h" + + +/*----------------------------------------------------------------------- + * Error handling + */ + +static void +cork_slice_invalid_slice_set(size_t buf_size, size_t requested_offset, + size_t requested_length) +{ + cork_error_set + (CORK_SLICE_ERROR, CORK_SLICE_INVALID_SLICE, + "Cannot slice %zu-byte buffer at %zu:%zu", + buf_size, requested_offset, requested_length); +} + + +/*----------------------------------------------------------------------- + * Slices + */ + +void +cork_slice_clear(struct cork_slice *slice) +{ + slice->buf = NULL; + slice->size = 0; + slice->iface = NULL; + slice->user_data = NULL; +} + + +int +cork_slice_copy(struct cork_slice *dest, const struct cork_slice *slice, + size_t offset, size_t length) +{ + if ((slice != NULL) && + (offset <= slice->size) && + ((offset + length) <= slice->size)) { + /* + DEBUG("Slicing <%p:%zu> at %zu:%zu, gives <%p:%zu>", + slice->buf, slice->size, + offset, length, + slice->buf + offset, length); + */ + return slice->iface->copy(dest, slice, offset, length); + } + + else { + /* + DEBUG("Cannot slice <%p:%zu> at %zu:%zu", + slice->buf, slice->size, + offset, length); + */ + cork_slice_clear(dest); + cork_slice_invalid_slice_set + ((slice == NULL)? 0: slice->size, offset, length); + return -1; + } +} + + +int +cork_slice_copy_offset(struct cork_slice *dest, const struct cork_slice *slice, + size_t offset) +{ + if (slice == NULL) { + cork_slice_clear(dest); + cork_slice_invalid_slice_set(0, offset, 0); + return -1; + } else { + return cork_slice_copy + (dest, slice, offset, slice->size - offset); + } +} + + +int +cork_slice_light_copy(struct cork_slice *dest, const struct cork_slice *slice, + size_t offset, size_t length) +{ + if ((slice != NULL) && + (offset <= slice->size) && + ((offset + length) <= slice->size)) { + /* + DEBUG("Slicing <%p:%zu> at %zu:%zu, gives <%p:%zu>", + slice->buf, slice->size, + offset, length, + slice->buf + offset, length); + */ + return slice->iface->light_copy(dest, slice, offset, length); + } + + else { + /* + DEBUG("Cannot slice <%p:%zu> at %zu:%zu", + slice->buf, slice->size, + offset, length); + */ + cork_slice_clear(dest); + cork_slice_invalid_slice_set + ((slice == NULL)? 0: slice->size, offset, length); + return -1; + } +} + + +int +cork_slice_light_copy_offset(struct cork_slice *dest, + const struct cork_slice *slice, size_t offset) +{ + if (slice == NULL) { + cork_slice_clear(dest); + cork_slice_invalid_slice_set(0, offset, 0); + return -1; + } else { + return cork_slice_light_copy + (dest, slice, offset, slice->size - offset); + } +} + + +int +cork_slice_slice(struct cork_slice *slice, size_t offset, size_t length) +{ + if ((slice != NULL) && + (offset <= slice->size) && + ((offset + length) <= slice->size)) { + /* + DEBUG("Slicing <%p:%zu> at %zu:%zu, gives <%p:%zu>", + slice->buf, slice->size, + offset, length, + slice->buf + offset, length); + */ + if (slice->iface->slice == NULL) { + slice->buf += offset; + slice->size = length; + return 0; + } else { + return slice->iface->slice(slice, offset, length); + } + } + + else { + /* + DEBUG("Cannot slice <%p:%zu> at %zu:%zu", + slice->buf, slice->size, + offset, length); + */ + cork_slice_invalid_slice_set(slice->size, offset, length); + return -1; + } +} + + +int +cork_slice_slice_offset(struct cork_slice *slice, size_t offset) +{ + if (slice == NULL) { + cork_slice_invalid_slice_set(0, offset, 0); + return -1; + } else { + return cork_slice_slice + (slice, offset, slice->size - offset); + } +} + + +void +cork_slice_finish(struct cork_slice *slice) +{ + /* + DEBUG("Finalizing <%p:%zu>", dest->buf, dest->size); + */ + + if (slice->iface != NULL && slice->iface->free != NULL) { + slice->iface->free(slice); + } + + cork_slice_clear(slice); +} + + +bool +cork_slice_equal(const struct cork_slice *slice1, + const struct cork_slice *slice2) +{ + if (slice1 == slice2) { + return true; + } + + if (slice1->size != slice2->size) { + return false; + } + + return (memcmp(slice1->buf, slice2->buf, slice1->size) == 0); +} + + +/*----------------------------------------------------------------------- + * Slices of static content + */ + +static struct cork_slice_iface cork_static_slice; + +static int +cork_static_slice_copy(struct cork_slice *dest, const struct cork_slice *src, + size_t offset, size_t length) +{ + dest->buf = src->buf + offset; + dest->size = length; + dest->iface = &cork_static_slice; + dest->user_data = NULL; + return 0; +} + +static struct cork_slice_iface cork_static_slice = { + NULL, + cork_static_slice_copy, + cork_static_slice_copy, + NULL +}; + +void +cork_slice_init_static(struct cork_slice *dest, const void *buf, size_t size) +{ + dest->buf = buf; + dest->size = size; + dest->iface = &cork_static_slice; + dest->user_data = NULL; +} + + +/*----------------------------------------------------------------------- + * Copy-once slices + */ + +static struct cork_slice_iface cork_copy_once_slice; + +static int +cork_copy_once_slice__copy(struct cork_slice *dest, + const struct cork_slice *src, + size_t offset, size_t length) +{ + struct cork_managed_buffer *mbuf = + cork_managed_buffer_new_copy(src->buf, src->size); + rii_check(cork_managed_buffer_slice(dest, mbuf, offset, length)); + rii_check(cork_managed_buffer_slice + ((struct cork_slice *) src, mbuf, 0, src->size)); + cork_managed_buffer_unref(mbuf); + return 0; +} + +static int +cork_copy_once_slice__light_copy(struct cork_slice *dest, + const struct cork_slice *src, + size_t offset, size_t length) +{ + dest->buf = src->buf + offset; + dest->size = length; + dest->iface = &cork_copy_once_slice; + dest->user_data = NULL; + return 0; +} + +static struct cork_slice_iface cork_copy_once_slice = { + NULL, + cork_copy_once_slice__copy, + cork_copy_once_slice__light_copy, + NULL +}; + +void +cork_slice_init_copy_once(struct cork_slice *dest, const void *buf, size_t size) +{ + dest->buf = buf; + dest->size = size; + dest->iface = &cork_copy_once_slice; + dest->user_data = NULL; +} diff --git a/3rd/libcork/src/posix/directory-walker.c b/3rd/libcork/src/posix/directory-walker.c new file mode 100644 index 0000000..c5a25c5 --- /dev/null +++ b/3rd/libcork/src/posix/directory-walker.c @@ -0,0 +1,122 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libcork/core/attributes.h" +#include "libcork/core/error.h" +#include "libcork/core/types.h" +#include "libcork/ds/buffer.h" +#include "libcork/helpers/errors.h" +#include "libcork/helpers/posix.h" +#include "libcork/os/files.h" + + +static int +cork_walk_one_directory(struct cork_dir_walker *w, struct cork_buffer *path, + size_t root_path_size) +{ + DIR *dir = NULL; + struct dirent *entry; + size_t dir_path_size; + + rip_check_posix(dir = opendir(path->buf)); + + cork_buffer_append(path, "/", 1); + dir_path_size = path->size; + errno = 0; + while ((entry = readdir(dir)) != NULL) { + struct stat info; + + /* Skip the "." and ".." entries */ + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) { + continue; + } + + /* Stat the directory entry */ + cork_buffer_append_string(path, entry->d_name); + ei_check_posix(stat(path->buf, &info)); + + /* If the entry is a subdirectory, recurse into it. */ + if (S_ISDIR(info.st_mode)) { + int rc = cork_dir_walker_enter_directory + (w, path->buf, path->buf + root_path_size, + path->buf + dir_path_size); + if (rc != CORK_SKIP_DIRECTORY) { + ei_check(cork_walk_one_directory(w, path, root_path_size)); + ei_check(cork_dir_walker_leave_directory + (w, path->buf, path->buf + root_path_size, + path->buf + dir_path_size)); + } + } else if (S_ISREG(info.st_mode)) { + ei_check(cork_dir_walker_file + (w, path->buf, path->buf + root_path_size, + path->buf + dir_path_size)); + } + + /* Remove this entry name from the path buffer. */ + cork_buffer_truncate(path, dir_path_size); + + /* We have to reset errno to 0 because of the ambiguous way + * readdir uses a return value of NULL. Other functions may + * return normally yet set errno to a non-zero value. dlopen + * on Mac OS X is an ogreish example. Since an error readdir + * is indicated by returning NULL and setting errno to indicate + * the error, then we need to reset it to zero before each call. + * We shall assume, perhaps to our great misery, that functions + * within this loop do proper error checking and act accordingly. + */ + errno = 0; + } + + /* Check errno immediately after the while loop terminates */ + if (CORK_UNLIKELY(errno != 0)) { + cork_system_error_set(); + goto error; + } + + /* Remove the trailing '/' from the path buffer. */ + cork_buffer_truncate(path, dir_path_size - 1); + rii_check_posix(closedir(dir)); + return 0; + +error: + if (dir != NULL) { + rii_check_posix(closedir(dir)); + } + return -1; +} + +int +cork_walk_directory(const char *path, struct cork_dir_walker *w) +{ + int rc; + char *p; + struct cork_buffer buf = CORK_BUFFER_INIT(); + + /* Seed the buffer with the directory's path, ensuring that there's no + * trailing '/' */ + cork_buffer_append_string(&buf, path); + p = buf.buf; + while (p[buf.size-1] == '/') { + buf.size--; + p[buf.size] = '\0'; + } + rc = cork_walk_one_directory(w, &buf, buf.size + 1); + cork_buffer_done(&buf); + return rc; +} diff --git a/3rd/libcork/src/posix/env.c b/3rd/libcork/src/posix/env.c new file mode 100644 index 0000000..16b23ef --- /dev/null +++ b/3rd/libcork/src/posix/env.c @@ -0,0 +1,207 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "libcork/core.h" +#include "libcork/ds.h" +#include "libcork/os/subprocess.h" +#include "libcork/helpers/errors.h" + +#if defined(__APPLE__) +/* Apple doesn't provide access to the "environ" variable from a shared library. + * There's a workaround function to grab the environ pointer described at [1]. + * + * [1] http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man7/environ.7.html + */ +#include +#define environ (*_NSGetEnviron()) + +#else +/* On all other POSIX platforms, we assume that environ is available in shared + * libraries. */ +extern char **environ; + +#endif + + +struct cork_env_var { + const char *name; + const char *value; +}; + +static struct cork_env_var * +cork_env_var_new(const char *name, const char *value) +{ + struct cork_env_var *var = cork_new(struct cork_env_var); + var->name = cork_strdup(name); + var->value = cork_strdup(value); + return var; +} + +static void +cork_env_var_free(void *vvar) +{ + struct cork_env_var *var = vvar; + cork_strfree(var->name); + cork_strfree(var->value); + cork_delete(struct cork_env_var, var); +} + + +struct cork_env { + struct cork_hash_table *variables; + struct cork_buffer buffer; +}; + +struct cork_env * +cork_env_new(void) +{ + struct cork_env *env = cork_new(struct cork_env); + env->variables = cork_string_hash_table_new(0, 0); + cork_hash_table_set_free_value(env->variables, cork_env_var_free); + cork_buffer_init(&env->buffer); + return env; +} + +static void +cork_env_add_internal(struct cork_env *env, const char *name, const char *value) +{ + if (env == NULL) { + setenv(name, value, true); + } else { + struct cork_env_var *var = cork_env_var_new(name, value); + void *old_var; + + cork_hash_table_put + (env->variables, (void *) var->name, var, NULL, NULL, &old_var); + + if (old_var != NULL) { + cork_env_var_free(old_var); + } + } +} + +struct cork_env * +cork_env_clone_current(void) +{ + char **curr; + struct cork_env *env = cork_env_new(); + + for (curr = environ; *curr != NULL; curr++) { + const char *entry = *curr; + const char *equal; + + equal = strchr(entry, '='); + if (CORK_UNLIKELY(equal == NULL)) { + /* This environment entry is malformed; skip it. */ + continue; + } + + /* Make a copy of the name so that it's NUL-terminated rather than + * equal-terminated. */ + cork_buffer_set(&env->buffer, entry, equal - entry); + cork_env_add_internal(env, env->buffer.buf, equal + 1); + } + + return env; +} + + +void +cork_env_free(struct cork_env *env) +{ + cork_hash_table_free(env->variables); + cork_buffer_done(&env->buffer); + cork_delete(struct cork_env, env); +} + +const char * +cork_env_get(struct cork_env *env, const char *name) +{ + if (env == NULL) { + return getenv(name); + } else { + struct cork_env_var *var = + cork_hash_table_get(env->variables, (void *) name); + return (var == NULL)? NULL: var->value; + } +} + +void +cork_env_add(struct cork_env *env, const char *name, const char *value) +{ + cork_env_add_internal(env, name, value); +} + +void +cork_env_add_vprintf(struct cork_env *env, const char *name, + const char *format, va_list args) +{ + cork_buffer_vprintf(&env->buffer, format, args); + cork_env_add_internal(env, name, env->buffer.buf); +} + +void +cork_env_add_printf(struct cork_env *env, const char *name, + const char *format, ...) +{ + va_list args; + va_start(args, format); + cork_env_add_vprintf(env, name, format, args); + va_end(args); +} + +void +cork_env_remove(struct cork_env *env, const char *name) +{ + if (env == NULL) { + unsetenv(name); + } else { + void *old_var; + cork_hash_table_delete(env->variables, (void *) name, NULL, &old_var); + if (old_var != NULL) { + cork_env_var_free(old_var); + } + } +} + + +static enum cork_hash_table_map_result +cork_env_set_vars(void *user_data, struct cork_hash_table_entry *entry) +{ + struct cork_env_var *var = entry->value; + setenv(var->name, var->value, false); + return CORK_HASH_TABLE_MAP_CONTINUE; +} + +#if defined(__APPLE__) || (defined(BSD) && (BSD >= 199103)) +/* A handful of platforms [1] don't provide clearenv(), so we must implement our + * own version that clears the environ array directly. + * + * [1] http://www.gnu.org/software/gnulib/manual/html_node/clearenv.html + */ +static void +clearenv(void) +{ + *environ = NULL; +} + +#else +/* Otherwise assume that we have clearenv available. */ +#endif + +void +cork_env_replace_current(struct cork_env *env) +{ + clearenv(); + cork_hash_table_map(env->variables, NULL, cork_env_set_vars); +} diff --git a/3rd/libcork/src/posix/exec.c b/3rd/libcork/src/posix/exec.c new file mode 100644 index 0000000..0621028 --- /dev/null +++ b/3rd/libcork/src/posix/exec.c @@ -0,0 +1,189 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "libcork/core.h" +#include "libcork/ds.h" +#include "libcork/os/subprocess.h" +#include "libcork/helpers/errors.h" + +#define ri_check_posix(call) \ + do { \ + while (true) { \ + if ((call) == -1) { \ + if (errno == EINTR) { \ + continue; \ + } else { \ + cork_system_error_set(); \ + CORK_PRINT_ERROR(); \ + return -1; \ + } \ + } else { \ + break; \ + } \ + } \ + } while (0) + + +struct cork_exec { + const char *program; + struct cork_string_array params; + struct cork_env *env; + const char *cwd; + struct cork_buffer description; +}; + +struct cork_exec * +cork_exec_new(const char *program) +{ + struct cork_exec *exec = cork_new(struct cork_exec); + exec->program = cork_strdup(program); + cork_string_array_init(&exec->params); + exec->env = NULL; + exec->cwd = NULL; + cork_buffer_init(&exec->description); + cork_buffer_set_string(&exec->description, program); + return exec; +} + +struct cork_exec * +cork_exec_new_with_params(const char *program, ...) +{ + struct cork_exec *exec; + va_list args; + const char *param; + + exec = cork_exec_new(program); + cork_exec_add_param(exec, program); + va_start(args, program); + while ((param = va_arg(args, const char *)) != NULL) { + cork_exec_add_param(exec, param); + } + return exec; +} + +struct cork_exec * +cork_exec_new_with_param_array(const char *program, char * const *params) +{ + char * const *curr; + struct cork_exec *exec = cork_exec_new(program); + for (curr = params; *curr != NULL; curr++) { + cork_exec_add_param(exec, *curr); + } + return exec; +} + +void +cork_exec_free(struct cork_exec *exec) +{ + cork_strfree(exec->program); + cork_array_done(&exec->params); + if (exec->env != NULL) { + cork_env_free(exec->env); + } + if (exec->cwd != NULL) { + cork_strfree(exec->cwd); + } + cork_buffer_done(&exec->description); + cork_delete(struct cork_exec, exec); +} + +const char * +cork_exec_description(struct cork_exec *exec) +{ + return exec->description.buf; +} + +const char * +cork_exec_program(struct cork_exec *exec) +{ + return exec->program; +} + +size_t +cork_exec_param_count(struct cork_exec *exec) +{ + return cork_array_size(&exec->params); +} + +const char * +cork_exec_param(struct cork_exec *exec, size_t index) +{ + return cork_array_at(&exec->params, index); +} + +void +cork_exec_add_param(struct cork_exec *exec, const char *param) +{ + /* Don't add the first parameter to the description; that's a copy of the + * program name, which we've already added. */ + if (!cork_array_is_empty(&exec->params)) { + cork_buffer_append(&exec->description, " ", 1); + cork_buffer_append_string(&exec->description, param); + } + cork_array_append(&exec->params, cork_strdup(param)); +} + +struct cork_env * +cork_exec_env(struct cork_exec *exec) +{ + return exec->env; +} + +void +cork_exec_set_env(struct cork_exec *exec, struct cork_env *env) +{ + if (exec->env != NULL) { + cork_env_free(exec->env); + } + exec->env = env; +} + +const char * +cork_exec_cwd(struct cork_exec *exec) +{ + return exec->cwd; +} + +void +cork_exec_set_cwd(struct cork_exec *exec, const char *directory) +{ + if (exec->cwd != NULL) { + cork_strfree(exec->cwd); + } + exec->cwd = cork_strdup(directory); +} + +int +cork_exec_run(struct cork_exec *exec) +{ + const char **params; + + /* Make sure the parameter array is NULL-terminated. */ + cork_array_append(&exec->params, NULL); + params = cork_array_elements(&exec->params); + + /* Fill in the requested environment */ + if (exec->env != NULL) { + cork_env_replace_current(exec->env); + } + + /* Change the working directory, if requested */ + if (exec->cwd != NULL) { + ri_check_posix(chdir(exec->cwd)); + } + + /* Execute the new program */ + ri_check_posix(execvp(exec->program, (char * const *) params)); + + /* This is unreachable */ + return 0; +} diff --git a/3rd/libcork/src/posix/files.c b/3rd/libcork/src/posix/files.c new file mode 100644 index 0000000..a2fbf3a --- /dev/null +++ b/3rd/libcork/src/posix/files.c @@ -0,0 +1,872 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libcork/core/attributes.h" +#include "libcork/core/error.h" +#include "libcork/core/types.h" +#include "libcork/ds/array.h" +#include "libcork/ds/buffer.h" +#include "libcork/helpers/errors.h" +#include "libcork/helpers/posix.h" +#include "libcork/os/files.h" +#include "libcork/os/subprocess.h" + + +#if !defined(CORK_DEBUG_FILES) +#define CORK_DEBUG_FILES 0 +#endif + +#if CORK_DEBUG_FILES +#include +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) /* no debug messages */ +#endif + + +/*----------------------------------------------------------------------- + * Paths + */ + +struct cork_path { + struct cork_buffer given; +}; + +static struct cork_path * +cork_path_new_internal(const char *str, size_t length) +{ + struct cork_path *path = cork_new(struct cork_path); + cork_buffer_init(&path->given); + if (length == 0) { + cork_buffer_ensure_size(&path->given, 16); + cork_buffer_set(&path->given, "", 0); + } else { + cork_buffer_set(&path->given, str, length); + } + return path; +} + +struct cork_path * +cork_path_new(const char *source) +{ + return cork_path_new_internal(source, source == NULL? 0: strlen(source)); +} + +struct cork_path * +cork_path_clone(const struct cork_path *other) +{ + return cork_path_new_internal(other->given.buf, other->given.size); +} + +void +cork_path_free(struct cork_path *path) +{ + cork_buffer_done(&path->given); + cork_delete(struct cork_path, path); +} + + +void +cork_path_set(struct cork_path *path, const char *content) +{ + if (content == NULL) { + cork_buffer_clear(&path->given); + } else { + cork_buffer_set_string(&path->given, content); + } +} + +const char * +cork_path_get(const struct cork_path *path) +{ + return path->given.buf; +} + +#define cork_path_get(path) ((const char *) (path)->given.buf) +#define cork_path_size(path) ((path)->given.size) +#define cork_path_truncate(path, size) \ + (cork_buffer_truncate(&(path)->given, (size))) + + +int +cork_path_set_cwd(struct cork_path *path) +{ + cork_buffer_ensure_size(&path->given, PATH_MAX); + rip_check_posix(getcwd(path->given.buf, PATH_MAX)); + path->given.size = strlen(path->given.buf); + return 0; +} + +struct cork_path * +cork_path_cwd(void) +{ + struct cork_path *path = cork_path_new(NULL); + ei_check(cork_path_set_cwd(path)); + return path; + +error: + cork_path_free(path); + return NULL; +} + + +int +cork_path_set_absolute(struct cork_path *path) +{ + struct cork_buffer buf; + + if (path->given.size > 0 && + cork_buffer_char(&path->given, 0) == '/') { + /* The path is already absolute. */ + return 0; + } + + cork_buffer_init(&buf); + cork_buffer_ensure_size(&buf, PATH_MAX); + ep_check_posix(getcwd(buf.buf, PATH_MAX)); + buf.size = strlen(buf.buf); + cork_buffer_append(&buf, "/", 1); + cork_buffer_append_copy(&buf, &path->given); + cork_buffer_done(&path->given); + path->given = buf; + return 0; + +error: + cork_buffer_done(&buf); + return -1; +} + +struct cork_path * +cork_path_absolute(const struct cork_path *other) +{ + struct cork_path *path = cork_path_clone(other); + ei_check(cork_path_set_absolute(path)); + return path; + +error: + cork_path_free(path); + return NULL; +} + + +void +cork_path_append(struct cork_path *path, const char *more) +{ + if (more == NULL || more[0] == '\0') { + return; + } + + if (more[0] == '/') { + /* If more starts with a "/", then it's absolute, and should replace + * the contents of the current path. */ + cork_buffer_set_string(&path->given, more); + } else { + /* Otherwise, more is relative, and should be appended to the current + * path. If the current given path doesn't end in a "/", then we need + * to add one to keep the path well-formed. */ + + if (path->given.size > 0 && + cork_buffer_char(&path->given, path->given.size - 1) != '/') { + cork_buffer_append(&path->given, "/", 1); + } + + cork_buffer_append_string(&path->given, more); + } +} + +struct cork_path * +cork_path_join(const struct cork_path *other, const char *more) +{ + struct cork_path *path = cork_path_clone(other); + cork_path_append(path, more); + return path; +} + +void +cork_path_append_path(struct cork_path *path, const struct cork_path *more) +{ + cork_path_append(path, more->given.buf); +} + +struct cork_path * +cork_path_join_path(const struct cork_path *other, const struct cork_path *more) +{ + struct cork_path *path = cork_path_clone(other); + cork_path_append_path(path, more); + return path; +} + + +void +cork_path_set_basename(struct cork_path *path) +{ + char *given = path->given.buf; + const char *last_slash = strrchr(given, '/'); + if (last_slash != NULL) { + size_t offset = last_slash - given; + size_t basename_length = path->given.size - offset - 1; + memmove(given, last_slash + 1, basename_length); + given[basename_length] = '\0'; + path->given.size = basename_length; + } +} + +struct cork_path * +cork_path_basename(const struct cork_path *other) +{ + struct cork_path *path = cork_path_clone(other); + cork_path_set_basename(path); + return path; +} + + +void +cork_path_set_dirname(struct cork_path *path) +{ + const char *given = path->given.buf; + const char *last_slash = strrchr(given, '/'); + if (last_slash == NULL) { + cork_buffer_clear(&path->given); + } else { + size_t offset = last_slash - given; + if (offset == 0) { + /* A special case for the immediate subdirectories of "/" */ + cork_buffer_truncate(&path->given, 1); + } else { + cork_buffer_truncate(&path->given, offset); + } + } +} + +struct cork_path * +cork_path_dirname(const struct cork_path *other) +{ + struct cork_path *path = cork_path_clone(other); + cork_path_set_dirname(path); + return path; +} + + +/*----------------------------------------------------------------------- + * Lists of paths + */ + +struct cork_path_list { + cork_array(struct cork_path *) array; + struct cork_buffer string; +}; + +struct cork_path_list * +cork_path_list_new_empty(void) +{ + struct cork_path_list *list = cork_new(struct cork_path_list); + cork_array_init(&list->array); + cork_buffer_init(&list->string); + return list; +} + +void +cork_path_list_free(struct cork_path_list *list) +{ + size_t i; + for (i = 0; i < cork_array_size(&list->array); i++) { + struct cork_path *path = cork_array_at(&list->array, i); + cork_path_free(path); + } + cork_array_done(&list->array); + cork_buffer_done(&list->string); + cork_delete(struct cork_path_list, list); +} + +const char * +cork_path_list_to_string(const struct cork_path_list *list) +{ + return list->string.buf; +} + +void +cork_path_list_add(struct cork_path_list *list, struct cork_path *path) +{ + cork_array_append(&list->array, path); + if (cork_array_size(&list->array) > 1) { + cork_buffer_append(&list->string, ":", 1); + } + cork_buffer_append_string(&list->string, cork_path_get(path)); +} + +size_t +cork_path_list_size(const struct cork_path_list *list) +{ + return cork_array_size(&list->array); +} + +const struct cork_path * +cork_path_list_get(const struct cork_path_list *list, size_t index) +{ + return cork_array_at(&list->array, index); +} + +static void +cork_path_list_append_string(struct cork_path_list *list, const char *str) +{ + struct cork_path *path; + const char *curr = str; + const char *next; + + while ((next = strchr(curr, ':')) != NULL) { + size_t size = next - curr; + path = cork_path_new_internal(curr, size); + cork_path_list_add(list, path); + curr = next + 1; + } + + path = cork_path_new(curr); + cork_path_list_add(list, path); +} + +struct cork_path_list * +cork_path_list_new(const char *str) +{ + struct cork_path_list *list = cork_path_list_new_empty(); + cork_path_list_append_string(list, str); + return list; +} + + +/*----------------------------------------------------------------------- + * Files + */ + +struct cork_file { + struct cork_path *path; + struct stat stat; + enum cork_file_type type; + bool has_stat; +}; + +static void +cork_file_init(struct cork_file *file, struct cork_path *path) +{ + file->path = path; + file->has_stat = false; +} + +struct cork_file * +cork_file_new(const char *path) +{ + return cork_file_new_from_path(cork_path_new(path)); +} + +struct cork_file * +cork_file_new_from_path(struct cork_path *path) +{ + struct cork_file *file = cork_new(struct cork_file); + cork_file_init(file, path); + return file; +} + +static void +cork_file_reset(struct cork_file *file) +{ + file->has_stat = false; +} + +static void +cork_file_done(struct cork_file *file) +{ + cork_path_free(file->path); +} + +void +cork_file_free(struct cork_file *file) +{ + cork_file_done(file); + cork_delete(struct cork_file, file); +} + +const struct cork_path * +cork_file_path(struct cork_file *file) +{ + return file->path; +} + +static int +cork_file_stat(struct cork_file *file) +{ + if (file->has_stat) { + return 0; + } else { + int rc; + rc = stat(cork_path_get(file->path), &file->stat); + + if (rc == -1) { + if (errno == ENOENT || errno == ENOTDIR) { + file->type = CORK_FILE_MISSING; + file->has_stat = true; + return 0; + } else { + cork_system_error_set(); + return -1; + } + } + + if (S_ISREG(file->stat.st_mode)) { + file->type = CORK_FILE_REGULAR; + } else if (S_ISDIR(file->stat.st_mode)) { + file->type = CORK_FILE_DIRECTORY; + } else if (S_ISLNK(file->stat.st_mode)) { + file->type = CORK_FILE_SYMLINK; + } else { + file->type = CORK_FILE_UNKNOWN; + } + + file->has_stat = true; + return 0; + } +} + +int +cork_file_exists(struct cork_file *file, bool *exists) +{ + rii_check(cork_file_stat(file)); + *exists = (file->type != CORK_FILE_MISSING); + return 0; +} + +int +cork_file_type(struct cork_file *file, enum cork_file_type *type) +{ + rii_check(cork_file_stat(file)); + *type = file->type; + return 0; +} + + +struct cork_file * +cork_path_list_find_file(const struct cork_path_list *list, + const char *rel_path) +{ + size_t i; + size_t count = cork_path_list_size(list); + struct cork_file *file; + + for (i = 0; i < count; i++) { + const struct cork_path *path = cork_path_list_get(list, i); + struct cork_path *joined = cork_path_join(path, rel_path); + bool exists; + file = cork_file_new_from_path(joined); + ei_check(cork_file_exists(file, &exists)); + if (exists) { + return file; + } else { + cork_file_free(file); + } + } + + cork_error_set_printf + (ENOENT, "%s not found in %s", + rel_path, cork_path_list_to_string(list)); + return NULL; + +error: + cork_file_free(file); + return NULL; +} + + +/*----------------------------------------------------------------------- + * Directories + */ + +int +cork_file_iterate_directory(struct cork_file *file, + cork_file_directory_iterator iterator, + void *user_data) +{ + DIR *dir = NULL; + struct dirent *entry; + size_t dir_path_size; + struct cork_path *child_path; + struct cork_file child_file; + + rip_check_posix(dir = opendir(cork_path_get(file->path))); + child_path = cork_path_clone(file->path); + cork_file_init(&child_file, child_path); + dir_path_size = cork_path_size(child_path); + + errno = 0; + while ((entry = readdir(dir)) != NULL) { + /* Skip the "." and ".." entries */ + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) { + continue; + } + + cork_path_append(child_path, entry->d_name); + ei_check(cork_file_stat(&child_file)); + + /* If the entry is a subdirectory, recurse into it. */ + ei_check(iterator(&child_file, entry->d_name, user_data)); + + /* Remove this entry name from the path buffer. */ + cork_path_truncate(child_path, dir_path_size); + cork_file_reset(&child_file); + + /* We have to reset errno to 0 because of the ambiguous way readdir uses + * a return value of NULL. Other functions may return normally yet set + * errno to a non-zero value. dlopen on Mac OS X is an ogreish example. + * Since an error readdir is indicated by returning NULL and setting + * errno to indicate the error, then we need to reset it to zero before + * each call. We shall assume, perhaps to our great misery, that + * functions within this loop do proper error checking and act + * accordingly. */ + errno = 0; + } + + /* Check errno immediately after the while loop terminates */ + if (CORK_UNLIKELY(errno != 0)) { + cork_system_error_set(); + goto error; + } + + cork_file_done(&child_file); + rii_check_posix(closedir(dir)); + return 0; + +error: + cork_file_done(&child_file); + rii_check_posix(closedir(dir)); + return -1; +} + +static int +cork_file_mkdir_one(struct cork_file *file, cork_file_mode mode, + unsigned int flags) +{ + DEBUG("mkdir %s\n", cork_path_get(file->path)); + + /* First check if the directory already exists. */ + rii_check(cork_file_stat(file)); + if (file->type == CORK_FILE_DIRECTORY) { + DEBUG(" Already exists!\n"); + if (!(flags & CORK_FILE_PERMISSIVE)) { + cork_system_error_set_explicit(EEXIST); + return -1; + } else { + return 0; + } + } else if (file->type != CORK_FILE_MISSING) { + DEBUG(" Exists and not a directory!\n"); + cork_system_error_set_explicit(EEXIST); + return -1; + } + + /* If the caller asked for a recursive mkdir, then make sure the parent + * directory exists. */ + if (flags & CORK_FILE_RECURSIVE) { + struct cork_path *parent = cork_path_dirname(file->path); + DEBUG(" Checking parent %s\n", cork_path_get(parent)); + if (parent->given.size == 0) { + /* There is no parent; we're either at the filesystem root (for an + * absolute path) or the current directory (for a relative one). + * Either way, we can assume it already exists. */ + cork_path_free(parent); + } else { + int rc; + struct cork_file parent_file; + cork_file_init(&parent_file, parent); + rc = cork_file_mkdir_one + (&parent_file, mode, flags | CORK_FILE_PERMISSIVE); + cork_file_done(&parent_file); + rii_check(rc); + } + } + + /* Create the directory already! */ + DEBUG(" Creating %s\n", cork_path_get(file->path)); + rii_check_posix(mkdir(cork_path_get(file->path), mode)); + return 0; +} + +int +cork_file_mkdir(struct cork_file *file, cork_file_mode mode, + unsigned int flags) +{ + return cork_file_mkdir_one(file, mode, flags); +} + +static int +cork_file_remove_iterator(struct cork_file *file, const char *rel_name, + void *user_data) +{ + unsigned int *flags = user_data; + return cork_file_remove(file, *flags); +} + +int +cork_file_remove(struct cork_file *file, unsigned int flags) +{ + DEBUG("rm %s\n", cork_path_get(file->path)); + rii_check(cork_file_stat(file)); + + if (file->type == CORK_FILE_MISSING) { + if (flags & CORK_FILE_PERMISSIVE) { + return 0; + } else { + cork_system_error_set_explicit(ENOENT); + return -1; + } + } else if (file->type == CORK_FILE_DIRECTORY) { + if (flags & CORK_FILE_RECURSIVE) { + /* The user asked that we delete the contents of the directory + * first. */ + rii_check(cork_file_iterate_directory + (file, cork_file_remove_iterator, &flags)); + } + + rii_check_posix(rmdir(cork_path_get(file->path))); + return 0; + } else { + rii_check(unlink(cork_path_get(file->path))); + return 0; + } +} + + +/*----------------------------------------------------------------------- + * Lists of files + */ + +struct cork_file_list { + cork_array(struct cork_file *) array; +}; + +struct cork_file_list * +cork_file_list_new_empty(void) +{ + struct cork_file_list *list = cork_new(struct cork_file_list); + cork_array_init(&list->array); + return list; +} + +void +cork_file_list_free(struct cork_file_list *list) +{ + size_t i; + for (i = 0; i < cork_array_size(&list->array); i++) { + struct cork_file *file = cork_array_at(&list->array, i); + cork_file_free(file); + } + cork_array_done(&list->array); + cork_delete(struct cork_file_list, list); +} + +void +cork_file_list_add(struct cork_file_list *list, struct cork_file *file) +{ + cork_array_append(&list->array, file); +} + +size_t +cork_file_list_size(struct cork_file_list *list) +{ + return cork_array_size(&list->array); +} + +struct cork_file * +cork_file_list_get(struct cork_file_list *list, size_t index) +{ + return cork_array_at(&list->array, index); +} + +struct cork_file_list * +cork_file_list_new(struct cork_path_list *path_list) +{ + struct cork_file_list *list = cork_file_list_new_empty(); + size_t count = cork_path_list_size(path_list); + size_t i; + + for (i = 0; i < count; i++) { + const struct cork_path *path = cork_path_list_get(path_list, i); + struct cork_file *file = cork_file_new(cork_path_get(path)); + cork_array_append(&list->array, file); + } + + return list; +} + + +struct cork_file_list * +cork_path_list_find_files(const struct cork_path_list *path_list, + const char *rel_path) +{ + size_t i; + size_t count = cork_path_list_size(path_list); + struct cork_file_list *list = cork_file_list_new_empty(); + struct cork_file *file; + + for (i = 0; i < count; i++) { + const struct cork_path *path = cork_path_list_get(path_list, i); + struct cork_path *joined = cork_path_join(path, rel_path); + bool exists; + file = cork_file_new_from_path(joined); + ei_check(cork_file_exists(file, &exists)); + if (exists) { + cork_file_list_add(list, file); + } else { + cork_file_free(file); + } + } + + return list; + +error: + cork_file_list_free(list); + cork_file_free(file); + return NULL; +} + + +/*----------------------------------------------------------------------- + * Standard paths and path lists + */ + +#define empty_string(str) ((str) == NULL || (str)[0] == '\0') + +struct cork_path * +cork_path_home(void) +{ + const char *path = cork_env_get(NULL, "HOME"); + if (empty_string(path)) { + cork_undefined("Cannot determine home directory"); + return NULL; + } else { + return cork_path_new(path); + } +} + + +struct cork_path_list * +cork_path_config_paths(void) +{ + struct cork_path_list *list = cork_path_list_new_empty(); + const char *var; + struct cork_path *path; + + /* The first entry should be the user's configuration directory. This is + * specified by $XDG_CONFIG_HOME, with $HOME/.config as the default. */ + var = cork_env_get(NULL, "XDG_CONFIG_HOME"); + if (empty_string(var)) { + ep_check(path = cork_path_home()); + cork_path_append(path, ".config"); + cork_path_list_add(list, path); + } else { + path = cork_path_new(var); + cork_path_list_add(list, path); + } + + /* The remaining entries should be the system-wide configuration + * directories. These are specified by $XDG_CONFIG_DIRS, with /etc/xdg as + * the default. */ + var = cork_env_get(NULL, "XDG_CONFIG_DIRS"); + if (empty_string(var)) { + path = cork_path_new("/etc/xdg"); + cork_path_list_add(list, path); + } else { + cork_path_list_append_string(list, var); + } + + return list; + +error: + cork_path_list_free(list); + return NULL; +} + +struct cork_path_list * +cork_path_data_paths(void) +{ + struct cork_path_list *list = cork_path_list_new_empty(); + const char *var; + struct cork_path *path; + + /* The first entry should be the user's data directory. This is specified + * by $XDG_DATA_HOME, with $HOME/.local/share as the default. */ + var = cork_env_get(NULL, "XDG_DATA_HOME"); + if (empty_string(var)) { + ep_check(path = cork_path_home()); + cork_path_append(path, ".local/share"); + cork_path_list_add(list, path); + } else { + path = cork_path_new(var); + cork_path_list_add(list, path); + } + + /* The remaining entries should be the system-wide configuration + * directories. These are specified by $XDG_DATA_DIRS, with + * /usr/local/share:/usr/share as the the default. */ + var = cork_env_get(NULL, "XDG_DATA_DIRS"); + if (empty_string(var)) { + path = cork_path_new("/usr/local/share"); + cork_path_list_add(list, path); + path = cork_path_new("/usr/share"); + cork_path_list_add(list, path); + } else { + cork_path_list_append_string(list, var); + } + + return list; + +error: + cork_path_list_free(list); + return NULL; +} + +struct cork_path * +cork_path_user_cache_path(void) +{ + const char *var; + struct cork_path *path; + + /* The user's cache directory is specified by $XDG_CACHE_HOME, with + * $HOME/.cache as the default. */ + var = cork_env_get(NULL, "XDG_CACHE_HOME"); + if (empty_string(var)) { + rpp_check(path = cork_path_home()); + cork_path_append(path, ".cache"); + return path; + } else { + return cork_path_new(var); + } +} + +struct cork_path * +cork_path_user_runtime_path(void) +{ + const char *var; + + /* The user's cache directory is specified by $XDG_RUNTIME_DIR, with + * no default given by the spec. */ + var = cork_env_get(NULL, "XDG_RUNTIME_DIR"); + if (empty_string(var)) { + cork_undefined("Cannot determine user-specific runtime directory"); + return NULL; + } else { + return cork_path_new(var); + } +} diff --git a/3rd/libcork/src/posix/process.c b/3rd/libcork/src/posix/process.c new file mode 100644 index 0000000..72afdd3 --- /dev/null +++ b/3rd/libcork/src/posix/process.c @@ -0,0 +1,116 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "libcork/core.h" +#include "libcork/ds.h" +#include "libcork/os/process.h" +#include "libcork/helpers/errors.h" + + +#if !defined(CORK_DEBUG_PROCESS) +#define CORK_DEBUG_PROCESS 0 +#endif + +#if CORK_DEBUG_PROCESS +#include +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) /* no debug messages */ +#endif + + +struct cork_cleanup_entry { + struct cork_dllist_item item; + int priority; + const char *name; + cork_cleanup_function function; +}; + +static struct cork_cleanup_entry * +cork_cleanup_entry_new(const char *name, int priority, + cork_cleanup_function function) +{ + struct cork_cleanup_entry *self = cork_new(struct cork_cleanup_entry); + self->priority = priority; + self->name = cork_strdup(name); + self->function = function; + return self; +} + +static void +cork_cleanup_entry_free(struct cork_cleanup_entry *self) +{ + cork_strfree(self->name); + cork_delete(struct cork_cleanup_entry, self); +} + +static struct cork_dllist cleanup_entries = CORK_DLLIST_INIT(cleanup_entries); +static bool cleanup_registered = false; + +static void +cork_cleanup_call_one(struct cork_dllist_item *item, void *user_data) +{ + struct cork_cleanup_entry *entry = + cork_container_of(item, struct cork_cleanup_entry, item); + cork_cleanup_function function = entry->function; + DEBUG("Call cleanup function [%d] %s\n", entry->priority, entry->name); + /* We need to free the entry before calling the entry's function, since one + * of the functions that libcork registers frees the allocator instance that + * we'd use to free the entry. If we called the function first, the + * allocator would be freed before we could use it to free the entry. */ + cork_cleanup_entry_free(entry); + function(); +} + +static void +cork_cleanup_call_all(void) +{ + cork_dllist_map(&cleanup_entries, cork_cleanup_call_one, NULL); +} + +static void +cork_cleanup_entry_add(struct cork_cleanup_entry *entry) +{ + struct cork_dllist_item *curr; + + if (CORK_UNLIKELY(!cleanup_registered)) { + atexit(cork_cleanup_call_all); + cleanup_registered = true; + } + + /* Linear search through the list of existing cleanup functions. When we + * find the first existing function with a higher priority, we've found + * where to insert the new function. */ + for (curr = cork_dllist_start(&cleanup_entries); + !cork_dllist_is_end(&cleanup_entries, curr); curr = curr->next) { + struct cork_cleanup_entry *existing = + cork_container_of(curr, struct cork_cleanup_entry, item); + if (existing->priority > entry->priority) { + cork_dllist_add_before(&existing->item, &entry->item); + return; + } + } + + /* If we fall through the loop, then the new function should be appended to + * the end of the list. */ + cork_dllist_add(&cleanup_entries, &entry->item); +} + + +CORK_API void +cork_cleanup_at_exit_named(const char *name, int priority, + cork_cleanup_function function) +{ + struct cork_cleanup_entry *entry = + cork_cleanup_entry_new(name, priority, function); + DEBUG("Register cleanup function [%d] %s\n", priority, name); + cork_cleanup_entry_add(entry); +} diff --git a/3rd/libcork/src/posix/subprocess.c b/3rd/libcork/src/posix/subprocess.c new file mode 100644 index 0000000..50b28d1 --- /dev/null +++ b/3rd/libcork/src/posix/subprocess.c @@ -0,0 +1,662 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012-2014, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libcork/core.h" +#include "libcork/ds.h" +#include "libcork/os/subprocess.h" +#include "libcork/threads/basics.h" +#include "libcork/helpers/errors.h" +#include "libcork/helpers/posix.h" + + +#if !defined(CORK_DEBUG_SUBPROCESS) +#define CORK_DEBUG_SUBPROCESS 0 +#endif + +#if CORK_DEBUG_SUBPROCESS +#include +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) /* no debug messages */ +#endif + + +/*----------------------------------------------------------------------- + * Subprocess groups + */ + +#define BUF_SIZE 4096 + +struct cork_subprocess_group { + cork_array(struct cork_subprocess *) subprocesses; +}; + +struct cork_subprocess_group * +cork_subprocess_group_new(void) +{ + struct cork_subprocess_group *group = + cork_new(struct cork_subprocess_group); + cork_pointer_array_init + (&group->subprocesses, (cork_free_f) cork_subprocess_free); + return group; +} + +void +cork_subprocess_group_free(struct cork_subprocess_group *group) +{ + cork_array_done(&group->subprocesses); + cork_delete(struct cork_subprocess_group, group); +} + +void +cork_subprocess_group_add(struct cork_subprocess_group *group, + struct cork_subprocess *sub) +{ + cork_array_append(&group->subprocesses, sub); +} + + +/*----------------------------------------------------------------------- + * Pipes (parent reads) + */ + +struct cork_read_pipe { + struct cork_stream_consumer *consumer; + int fds[2]; + bool first; +}; + +static void +cork_read_pipe_init(struct cork_read_pipe *p, struct cork_stream_consumer *consumer) +{ + p->consumer = consumer; + p->fds[0] = -1; + p->fds[1] = -1; +} + +static int +cork_read_pipe_close_read(struct cork_read_pipe *p) +{ + if (p->fds[0] != -1) { + DEBUG("Closing read pipe %d\n", p->fds[0]); + rii_check_posix(close(p->fds[0])); + p->fds[0] = -1; + } + return 0; +} + +static int +cork_read_pipe_close_write(struct cork_read_pipe *p) +{ + if (p->fds[1] != -1) { + DEBUG("Closing write pipe %d\n", p->fds[1]); + rii_check_posix(close(p->fds[1])); + p->fds[1] = -1; + } + return 0; +} + +static void +cork_read_pipe_close(struct cork_read_pipe *p) +{ + cork_read_pipe_close_read(p); + cork_read_pipe_close_write(p); +} + +static void +cork_read_pipe_done(struct cork_read_pipe *p) +{ + cork_read_pipe_close(p); +} + +static int +cork_read_pipe_open(struct cork_read_pipe *p) +{ + if (p->consumer != NULL) { + int flags; + + /* We want the read end of the pipe to be non-blocking. */ + DEBUG("[read] Opening pipe\n"); + rii_check_posix(pipe(p->fds)); + DEBUG("[read] Got read=%d write=%d\n", p->fds[0], p->fds[1]); + DEBUG("[read] Setting non-blocking flag on read pipe\n"); + ei_check_posix(flags = fcntl(p->fds[0], F_GETFD)); + flags |= O_NONBLOCK; + ei_check_posix(fcntl(p->fds[0], F_SETFD, flags)); + } + + p->first = true; + return 0; + +error: + cork_read_pipe_close(p); + return -1; +} + +static int +cork_read_pipe_dup(struct cork_read_pipe *p, int fd) +{ + if (p->fds[1] != -1) { + rii_check_posix(dup2(p->fds[1], fd)); + } + return 0; +} + +static int +cork_read_pipe_read(struct cork_read_pipe *p, char *buf, bool *progress) +{ + if (p->fds[0] == -1) { + return 0; + } + + do { + DEBUG("[read] Reading from pipe %d\n", p->fds[0]); + ssize_t bytes_read = read(p->fds[0], buf, BUF_SIZE); + if (bytes_read == -1) { + if (errno == EAGAIN) { + /* We've exhausted all of the data currently available. */ + DEBUG("[read] No more bytes without blocking\n"); + return 0; + } else if (errno == EINTR) { + /* Interrupted by a signal; return so that our wait loop can + * catch that. */ + DEBUG("[read] Interrupted by signal\n"); + return 0; + } else { + /* An actual error */ + cork_system_error_set(); + DEBUG("[read] Error: %s\n", cork_error_message()); + return -1; + } + } else if (bytes_read == 0) { + DEBUG("[read] End of stream\n"); + *progress = true; + rii_check(cork_stream_consumer_eof(p->consumer)); + rii_check_posix(close(p->fds[0])); + p->fds[0] = -1; + return 0; + } else { + DEBUG("[read] Got %zd bytes\n", bytes_read); + *progress = true; + rii_check(cork_stream_consumer_data + (p->consumer, buf, bytes_read, p->first)); + p->first = false; + } + } while (true); +} + +static bool +cork_read_pipe_is_finished(struct cork_read_pipe *p) +{ + return p->fds[0] == -1; +} + + +/*----------------------------------------------------------------------- + * Pipes (parent writes) + */ + +struct cork_write_pipe { + struct cork_stream_consumer consumer; + int fds[2]; +}; + +static int +cork_write_pipe_close_read(struct cork_write_pipe *p) +{ + if (p->fds[0] != -1) { + DEBUG("[write] Closing read pipe %d\n", p->fds[0]); + rii_check_posix(close(p->fds[0])); + p->fds[0] = -1; + } + return 0; +} + +static int +cork_write_pipe_close_write(struct cork_write_pipe *p) +{ + if (p->fds[1] != -1) { + DEBUG("[write] Closing write pipe %d\n", p->fds[1]); + rii_check_posix(close(p->fds[1])); + p->fds[1] = -1; + } + return 0; +} + +static int +cork_write_pipe__data(struct cork_stream_consumer *consumer, + const void *buf, size_t size, bool is_first_chunk) +{ + struct cork_write_pipe *p = + cork_container_of(consumer, struct cork_write_pipe, consumer); + rii_check_posix(write(p->fds[1], buf, size)); + return 0; +} + +static int +cork_write_pipe__eof(struct cork_stream_consumer *consumer) +{ + struct cork_write_pipe *p = + cork_container_of(consumer, struct cork_write_pipe, consumer); + return cork_write_pipe_close_write(p); +} + +static void +cork_write_pipe__free(struct cork_stream_consumer *consumer) +{ +} + +static void +cork_write_pipe_init(struct cork_write_pipe *p) +{ + p->consumer.data = cork_write_pipe__data; + p->consumer.eof = cork_write_pipe__eof; + p->consumer.free = cork_write_pipe__free; + p->fds[0] = -1; + p->fds[1] = -1; +} + +static void +cork_write_pipe_close(struct cork_write_pipe *p) +{ + cork_write_pipe_close_read(p); + cork_write_pipe_close_write(p); +} + +static void +cork_write_pipe_done(struct cork_write_pipe *p) +{ + cork_write_pipe_close(p); +} + +static int +cork_write_pipe_open(struct cork_write_pipe *p) +{ + DEBUG("[write] Opening writer pipe\n"); + rii_check_posix(pipe(p->fds)); + DEBUG("[write] Got read=%d write=%d\n", p->fds[0], p->fds[1]); + return 0; +} + +static int +cork_write_pipe_dup(struct cork_write_pipe *p, int fd) +{ + if (p->fds[0] != -1) { + rii_check_posix(dup2(p->fds[0], fd)); + } + return 0; +} + + +/*----------------------------------------------------------------------- + * Subprocesses + */ + +struct cork_subprocess { + pid_t pid; + struct cork_write_pipe stdin_pipe; + struct cork_read_pipe stdout_pipe; + struct cork_read_pipe stderr_pipe; + void *user_data; + cork_free_f free_user_data; + cork_run_f run; + int *exit_code; + char buf[BUF_SIZE]; +}; + +struct cork_subprocess * +cork_subprocess_new(void *user_data, cork_free_f free_user_data, + cork_run_f run, + struct cork_stream_consumer *stdout_consumer, + struct cork_stream_consumer *stderr_consumer, + int *exit_code) +{ + struct cork_subprocess *self = cork_new(struct cork_subprocess); + cork_write_pipe_init(&self->stdin_pipe); + cork_read_pipe_init(&self->stdout_pipe, stdout_consumer); + cork_read_pipe_init(&self->stderr_pipe, stderr_consumer); + self->pid = 0; + self->user_data = user_data; + self->free_user_data = free_user_data; + self->run = run; + self->exit_code = exit_code; + return self; +} + +void +cork_subprocess_free(struct cork_subprocess *self) +{ + cork_free_user_data(self); + cork_write_pipe_done(&self->stdin_pipe); + cork_read_pipe_done(&self->stdout_pipe); + cork_read_pipe_done(&self->stderr_pipe); + cork_delete(struct cork_subprocess, self); +} + +struct cork_stream_consumer * +cork_subprocess_stdin(struct cork_subprocess *self) +{ + return &self->stdin_pipe.consumer; +} + + +/*----------------------------------------------------------------------- + * Executing another program + */ + +static int +cork_exec__run(void *vself) +{ + struct cork_exec *exec = vself; + return cork_exec_run(exec); +} + +static void +cork_exec__free(void *vself) +{ + struct cork_exec *exec = vself; + cork_exec_free(exec); +} + +struct cork_subprocess * +cork_subprocess_new_exec(struct cork_exec *exec, + struct cork_stream_consumer *out, + struct cork_stream_consumer *err, + int *exit_code) +{ + return cork_subprocess_new + (exec, cork_exec__free, + cork_exec__run, + out, err, exit_code); +} + + +/*----------------------------------------------------------------------- + * Running subprocesses + */ + +int +cork_subprocess_start(struct cork_subprocess *self) +{ + pid_t pid; + + /* Create the stdout and stderr pipes. */ + if (cork_write_pipe_open(&self->stdin_pipe) == -1) { + return -1; + } + if (cork_read_pipe_open(&self->stdout_pipe) == -1) { + cork_write_pipe_close(&self->stdin_pipe); + return -1; + } + if (cork_read_pipe_open(&self->stderr_pipe) == -1) { + cork_write_pipe_close(&self->stdin_pipe); + cork_read_pipe_close(&self->stdout_pipe); + return -1; + } + + /* Fork the child process. */ + DEBUG("Forking child process\n"); + pid = fork(); + if (pid == 0) { + /* Child process */ + int rc; + + /* Close the parent's end of the pipes */ + DEBUG("[child] "); + cork_write_pipe_close_write(&self->stdin_pipe); + DEBUG("[child] "); + cork_read_pipe_close_read(&self->stdout_pipe); + DEBUG("[child] "); + cork_read_pipe_close_read(&self->stderr_pipe); + + /* Bind the stdout and stderr pipes */ + if (cork_write_pipe_dup(&self->stdin_pipe, STDIN_FILENO) == -1) { + _exit(EXIT_FAILURE); + } + if (cork_read_pipe_dup(&self->stdout_pipe, STDOUT_FILENO) == -1) { + _exit(EXIT_FAILURE); + } + if (cork_read_pipe_dup(&self->stderr_pipe, STDERR_FILENO) == -1) { + _exit(EXIT_FAILURE); + } + + /* Run the subprocess */ + rc = self->run(self->user_data); + if (CORK_LIKELY(rc == 0)) { + _exit(EXIT_SUCCESS); + } else { + fprintf(stderr, "%s\n", cork_error_message()); + _exit(EXIT_FAILURE); + } + } else if (pid < 0) { + /* Error forking */ + cork_system_error_set(); + return -1; + } else { + /* Parent process */ + DEBUG(" Child PID=%d\n", (int) pid); + self->pid = pid; + cork_write_pipe_close_read(&self->stdin_pipe); + cork_read_pipe_close_write(&self->stdout_pipe); + cork_read_pipe_close_write(&self->stderr_pipe); + return 0; + } +} + +static int +cork_subprocess_reap(struct cork_subprocess *self, int flags, bool *progress) +{ + int pid; + int status; + rii_check_posix(pid = waitpid(self->pid, &status, flags)); + if (pid == self->pid) { + *progress = true; + self->pid = 0; + if (self->exit_code != NULL) { + *self->exit_code = WEXITSTATUS(status); + } + } + return 0; +} + +int +cork_subprocess_abort(struct cork_subprocess *self) +{ + if (self->pid > 0) { + CORK_ATTR_UNUSED bool progress; + DEBUG("Terminating child process %d\n", (int) self->pid); + kill(self->pid, SIGTERM); + return cork_subprocess_reap(self, 0, &progress); + } else { + return 0; + } +} + +bool +cork_subprocess_is_finished(struct cork_subprocess *self) +{ + return (self->pid == 0) + && cork_read_pipe_is_finished(&self->stdout_pipe) + && cork_read_pipe_is_finished(&self->stderr_pipe); +} + +#if defined(__APPLE__) +#include +#define THREAD_YIELD pthread_yield_np +#elif defined(__linux__) || defined(BSD) +#include +#define THREAD_YIELD sched_yield +#else +#error "Unknown thread yield implementation" +#endif + +static void +cork_subprocess_yield(unsigned int *spin_count) +{ + /* Adapted from + * http://www.1024cores.net/home/lock-free-algorithms/tricks/spinning */ + + if (*spin_count < 10) { + /* Spin-wait */ + cork_pause(); + } else if (*spin_count < 20) { + /* A more intense spin-wait */ + int i; + for (i = 0; i < 50; i++) { + cork_pause(); + } + } else if (*spin_count < 22) { + THREAD_YIELD(); + } else if (*spin_count < 24) { + usleep(0); + } else if (*spin_count < 50) { + usleep(1); + } else if (*spin_count < 75) { + usleep((*spin_count - 49) * 1000); + } else { + usleep(25000); + } + + (*spin_count)++; +} + +static int +cork_subprocess_drain_(struct cork_subprocess *self, bool *progress) +{ + rii_check(cork_read_pipe_read(&self->stdout_pipe, self->buf, progress)); + rii_check(cork_read_pipe_read(&self->stderr_pipe, self->buf, progress)); + if (self->pid > 0) { + return cork_subprocess_reap(self, WNOHANG, progress); + } else { + return 0; + } +} + +bool +cork_subprocess_drain(struct cork_subprocess *self) +{ + bool progress; + cork_subprocess_drain_(self, &progress); + return progress; +} + +int +cork_subprocess_wait(struct cork_subprocess *self) +{ + unsigned int spin_count = 0; + bool progress; + while (!cork_subprocess_is_finished(self)) { + progress = false; + rii_check(cork_subprocess_drain_(self, &progress)); + if (!progress) { + cork_subprocess_yield(&spin_count); + } + } + return 0; +} + + +/*----------------------------------------------------------------------- + * Running subprocess groups + */ + +static int +cork_subprocess_group_terminate(struct cork_subprocess_group *group) +{ + size_t i; + for (i = 0; i < cork_array_size(&group->subprocesses); i++) { + struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i); + rii_check(cork_subprocess_abort(sub)); + } + return 0; +} + +int +cork_subprocess_group_start(struct cork_subprocess_group *group) +{ + size_t i; + DEBUG("Starting subprocess group\n"); + /* Start each subprocess. */ + for (i = 0; i < cork_array_size(&group->subprocesses); i++) { + struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i); + ei_check(cork_subprocess_start(sub)); + } + return 0; + +error: + cork_subprocess_group_terminate(group); + return -1; +} + + +int +cork_subprocess_group_abort(struct cork_subprocess_group *group) +{ + DEBUG("Aborting subprocess group\n"); + return cork_subprocess_group_terminate(group); +} + + +bool +cork_subprocess_group_is_finished(struct cork_subprocess_group *group) +{ + size_t i; + for (i = 0; i < cork_array_size(&group->subprocesses); i++) { + struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i); + bool sub_finished = cork_subprocess_is_finished(sub); + if (!sub_finished) { + return false; + } + } + return true; +} + +static int +cork_subprocess_group_drain_(struct cork_subprocess_group *group, + bool *progress) +{ + size_t i; + for (i = 0; i < cork_array_size(&group->subprocesses); i++) { + struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i); + rii_check(cork_subprocess_drain_(sub, progress)); + } + return 0; +} + +bool +cork_subprocess_group_drain(struct cork_subprocess_group *group) +{ + bool progress = false; + cork_subprocess_group_drain_(group, &progress); + return progress; +} + +int +cork_subprocess_group_wait(struct cork_subprocess_group *group) +{ + unsigned int spin_count = 0; + bool progress; + DEBUG("Waiting for subprocess group to finish\n"); + while (!cork_subprocess_group_is_finished(group)) { + progress = false; + rii_check(cork_subprocess_group_drain_(group, &progress)); + if (!progress) { + cork_subprocess_yield(&spin_count); + } + } + return 0; +} diff --git a/3rd/libcork/src/pthreads/thread.c b/3rd/libcork/src/pthreads/thread.c new file mode 100644 index 0000000..002416e --- /dev/null +++ b/3rd/libcork/src/pthreads/thread.c @@ -0,0 +1,220 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2013-2015, RedJack, LLC. + * All rights reserved. + * + * Please see the COPYING file in this distribution for license details. + * ---------------------------------------------------------------------- + */ + +#if defined(__linux) +/* This is needed on Linux to get the pthread_setname_np function. */ +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE 1 +#endif +#endif + +#include +#include + +#include + +#include "libcork/core/allocator.h" +#include "libcork/core/error.h" +#include "libcork/core/types.h" +#include "libcork/ds/buffer.h" +#include "libcork/threads/basics.h" + + +/*----------------------------------------------------------------------- + * Current thread + */ + +static volatile cork_thread_id last_thread_descriptor = 0; + +struct cork_thread { + const char *name; + cork_thread_id id; + pthread_t thread_id; + void *user_data; + cork_free_f free_user_data; + cork_run_f run; + cork_error error_code; + struct cork_buffer error_message; + bool started; + bool joined; +}; + +struct cork_thread_descriptor { + struct cork_thread *current_thread; + cork_thread_id id; +}; + +cork_tls(struct cork_thread_descriptor, cork_thread_descriptor); + +struct cork_thread * +cork_current_thread_get(void) +{ + struct cork_thread_descriptor *desc = cork_thread_descriptor_get(); + return desc->current_thread; +} + +cork_thread_id +cork_current_thread_get_id(void) +{ + struct cork_thread_descriptor *desc = cork_thread_descriptor_get(); + if (CORK_UNLIKELY(desc->id == 0)) { + if (desc->current_thread == NULL) { + desc->id = cork_uint_atomic_add(&last_thread_descriptor, 1); + } else { + desc->id = desc->current_thread->id; + } + } + return desc->id; +} + + +/*----------------------------------------------------------------------- + * Threads + */ + +struct cork_thread * +cork_thread_new(const char *name, + void *user_data, cork_free_f free_user_data, + cork_run_f run) +{ + struct cork_thread *self = cork_new(struct cork_thread); + self->name = cork_strdup(name); + self->id = cork_uint_atomic_add(&last_thread_descriptor, 1); + self->user_data = user_data; + self->free_user_data = free_user_data; + self->run = run; + self->error_code = CORK_ERROR_NONE; + cork_buffer_init(&self->error_message); + self->started = false; + self->joined = false; + return self; +} + +static void +cork_thread_free_private(struct cork_thread *self) +{ + cork_strfree(self->name); + cork_free_user_data(self); + cork_buffer_done(&self->error_message); + cork_delete(struct cork_thread, self); +} + +void +cork_thread_free(struct cork_thread *self) +{ + assert(!self->started); + cork_thread_free_private(self); +} + +const char * +cork_thread_get_name(struct cork_thread *self) +{ + return self->name; +} + +cork_thread_id +cork_thread_get_id(struct cork_thread *self) +{ + return self->id; +} + +#define PTHREADS_MAX_THREAD_NAME_LENGTH 16 + +static void * +cork_thread_pthread_run(void *vself) +{ + int rc; + struct cork_thread *self = vself; + struct cork_thread_descriptor *desc = cork_thread_descriptor_get(); +#if defined(__APPLE__) && defined(__MACH__) + char thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH]; +#endif + + desc->current_thread = self; + desc->id = self->id; + rc = self->run(self->user_data); + +#if defined(__APPLE__) && defined(__MACH__) + /* On Mac OS X, we set the name of the current thread, not of an arbitrary + * thread of our choosing. */ + strncpy(thread_name, self->name, PTHREADS_MAX_THREAD_NAME_LENGTH); + thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH - 1] = '\0'; + pthread_setname_np(thread_name); +#endif + + /* If an error occurred in the body of the thread, save the error into the + * cork_thread object so that we can propagate that error when some calls + * cork_thread_join. */ + if (CORK_UNLIKELY(rc != 0)) { + if (CORK_LIKELY(cork_error_occurred())) { + self->error_code = cork_error_code(); + cork_buffer_set_string(&self->error_message, cork_error_message()); + } else { + self->error_code = CORK_UNKNOWN_ERROR; + cork_buffer_set_string(&self->error_message, "Unknown error"); + } + } + + return NULL; +} + +int +cork_thread_start(struct cork_thread *self) +{ + int rc; + pthread_t thread_id; +#if defined(__linux) + char thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH]; +#endif + + assert(!self->started); + + rc = pthread_create(&thread_id, NULL, cork_thread_pthread_run, self); + if (CORK_UNLIKELY(rc != 0)) { + cork_system_error_set_explicit(rc); + return -1; + } + +#if defined(__linux) + /* On Linux we choose which thread to name via an explicit thread ID. */ + strncpy(thread_name, self->name, PTHREADS_MAX_THREAD_NAME_LENGTH); + thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH - 1] = '\0'; + pthread_setname_np(thread_id, thread_name); +#endif + + self->thread_id = thread_id; + self->started = true; + return 0; +} + +int +cork_thread_join(struct cork_thread *self) +{ + int rc; + + assert(self->started && !self->joined); + + rc = pthread_join(self->thread_id, NULL); + if (CORK_UNLIKELY(rc != 0)) { + cork_system_error_set_explicit(rc); + cork_thread_free_private(self); + return -1; + } + + if (CORK_UNLIKELY(self->error_code != CORK_ERROR_NONE)) { + cork_error_set_printf + (self->error_code, "Error from thread %s: %s", + self->name, (char *) self->error_message.buf); + cork_thread_free_private(self); + return -1; + } + + cork_thread_free_private(self); + return 0; +} diff --git a/3rd/libipset/include/ipset/bdd/nodes.h b/3rd/libipset/include/ipset/bdd/nodes.h new file mode 100644 index 0000000..1854828 --- /dev/null +++ b/3rd/libipset/include/ipset/bdd/nodes.h @@ -0,0 +1,536 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef IPSET_BDD_NODES_H +#define IPSET_BDD_NODES_H + + +#include + +#include +#include + + +/*----------------------------------------------------------------------- + * Preliminaries + */ + +/** + * Each variable in a BDD is referred to by number. + */ +typedef unsigned int ipset_variable; + + +/** + * Each BDD terminal represents an integer value. The integer must be + * non-negative, but must be within the range of the signed + * integer type. + */ +typedef unsigned int ipset_value; + + +/** + * An identifier for each distinct node in a BDD. + * + * Internal implementation note. Since pointers are aligned to at + * least two bytes, the ID of a terminal node has its LSB set to 1, + * and has the terminal value stored in the remaining bits. The ID of + * a nonterminal node is simply a pointer to the node struct. + */ +typedef unsigned int ipset_node_id; + + +/** + * Nodes can either be terminal or nonterminal. + */ +enum ipset_node_type { + IPSET_NONTERMINAL_NODE = 0, + IPSET_TERMINAL_NODE = 1 +}; + + +/** + * Return the type of node represented by a particular node ID. + */ +#define ipset_node_get_type(node_id) ((node_id) & 0x01) + +#define IPSET_NODE_ID_FORMAT "%s%u" +#define IPSET_NODE_ID_VALUES(node_id) \ + (ipset_node_get_type((node_id)) == IPSET_NONTERMINAL_NODE? "s": ""), \ + ((node_id) >> 1) + + +/*----------------------------------------------------------------------- + * Terminal nodes + */ + +/** + * Return the value of a terminal node. The result is undefined if + * the node ID represents a nonterminal. + */ +#define ipset_terminal_value(node_id) ((node_id) >> 1) + +/** + * Creates a terminal node ID from a terminal value. + */ +#define ipset_terminal_node_id(value) \ + (((value) << 1) | IPSET_TERMINAL_NODE) + + +/*----------------------------------------------------------------------- + * Nonterminal nodes + */ + +/** + * A nonterminal BDD node. This is an inner node of the BDD tree. + * The node represents one variable in an overall variable assignment. + * The node has two children: a “low” child and a “high” child. The + * low child is the subtree that applies when the node's variable is + * false or 0; the high child is the subtree that applies when it's + * true or 1. + * + * This type does not take care of ensuring that all BDD nodes are + * reduced; that is handled by the node_cache class. + */ +struct ipset_node { + /** The reference count for this node. */ + unsigned int refcount; + /** The variable that this node represents. */ + ipset_variable variable; + /** The subtree node for when the variable is false. */ + ipset_node_id low; + /** The subtree node for when the variable is true. */ + ipset_node_id high; +}; + +/** + * Return the "value" of a nonterminal node. The value of a nonterminal + * is the index into the node array of the cache that the node belongs + * to. + */ +#define ipset_nonterminal_value(node_id) ((node_id) >> 1) + +/** + * Creates a nonterminal node ID from a nonterminal value. + */ +#define ipset_nonterminal_node_id(value) \ + (((value) << 1) | IPSET_NONTERMINAL_NODE) + +/** + * Print out a node object. + */ +void +ipset_node_fprint(FILE *stream, struct ipset_node *node); + + +/*----------------------------------------------------------------------- + * Node caches + */ + +/** + * The log2 of the size of each chunk of BDD nodes. + */ +/* 16K elements per cache */ +#define IPSET_BDD_NODE_CACHE_BIT_SIZE 6 +#define IPSET_BDD_NODE_CACHE_SIZE (1 << IPSET_BDD_NODE_CACHE_BIT_SIZE) +#define IPSET_BDD_NODE_CACHE_MASK (IPSET_BDD_NODE_CACHE_SIZE - 1) + +/** + * A cache for BDD nodes. By creating and retrieving nodes through + * the cache, we ensure that a BDD is reduced. + */ +struct ipset_node_cache { + /** The storage for the nodes managed by this cache. */ + cork_array(struct ipset_node *) chunks; + /** The largest nonterminal index that has been handed out. */ + ipset_value largest_index; + /** The index of the first node in the free list. */ + ipset_value free_list; + /** A cache of the nonterminal nodes, keyed by their contents. */ + struct cork_hash_table *node_cache; +}; + +/** + * Returns the index of the chunk that the given nonterminal lives in. + */ +#define ipset_nonterminal_chunk_index(index) \ + ((index) >> IPSET_BDD_NODE_CACHE_BIT_SIZE) + +/** + * Returns the offset of the given nonterminal within its chunk. + */ +#define ipset_nonterminal_chunk_offset(index) \ + ((index) & IPSET_BDD_NODE_CACHE_MASK) + +/** + * Returns a pointer to the ipset_node for a given nonterminal index. + */ +#define ipset_node_cache_get_nonterminal_by_index(cache, index) \ + (&cork_array_at(&(cache)->chunks, ipset_nonterminal_chunk_index((index))) \ + [ipset_nonterminal_chunk_offset((index))]) + +/** + * Returns the ipset_node for a given nonterminal node ID. + */ +#define ipset_node_cache_get_nonterminal(cache, node_id) \ + (ipset_node_cache_get_nonterminal_by_index \ + ((cache), ipset_nonterminal_value((node_id)))) + +/** + * Create a new node cache. + */ +struct ipset_node_cache * +ipset_node_cache_new(void); + +/** + * Free a node cache. + */ +void +ipset_node_cache_free(struct ipset_node_cache *cache); + +/** + * Create a new nonterminal node with the given contents, returning + * its ID. This function ensures that there is only one node with the + * given contents in this cache. + * + * Steals references to low and high. + */ +ipset_node_id +ipset_node_cache_nonterminal(struct ipset_node_cache *cache, + ipset_variable variable, + ipset_node_id low, ipset_node_id high); + + +/** + * Increment the reference count of a nonterminal node. (This is a + * no-op for terminal nodes.) + */ +ipset_node_id +ipset_node_incref(struct ipset_node_cache *cache, ipset_node_id node); + +/** + * Decrement the reference count of a nonterminal node. If the + * reference count reaches 0, the storage for the node will be + * reclaimed. (This is a no-op for terminal nodes.) + */ +void +ipset_node_decref(struct ipset_node_cache *cache, ipset_node_id node); + + +/** + * Return the number of nodes that are reachable from the given node. + * This does not include duplicates if a node is reachable via more + * than one path. + */ +size_t +ipset_node_reachable_count(const struct ipset_node_cache *cache, + ipset_node_id node); + + +/** + * Return the amount of memory used by the nodes in the given BDD. + */ +size_t +ipset_node_memory_size(const struct ipset_node_cache *cache, + ipset_node_id node); + + +/** + * Load a BDD from an input stream. The error field is filled in with + * an error condition is the BDD can't be read for any reason. + */ +ipset_node_id +ipset_node_cache_load(FILE *stream, struct ipset_node_cache *cache); + + +/** + * Save a BDD to an output stream. This encodes the set using only + * those nodes that are reachable from the BDD's root node. + */ +int +ipset_node_cache_save(struct cork_stream_consumer *stream, + struct ipset_node_cache *cache, ipset_node_id node); + + +/** + * Compare two BDD nodes, possibly from different caches, for equality. + */ +bool +ipset_node_cache_nodes_equal(const struct ipset_node_cache *cache1, + ipset_node_id node1, + const struct ipset_node_cache *cache2, + ipset_node_id node2); + + +/** + * Save a GraphViz dot graph for a BDD. The graph script is written + * to the given output stream. This graph only includes those nodes + * that are reachable from the BDD's root node. + */ +int +ipset_node_cache_save_dot(struct cork_stream_consumer *stream, + struct ipset_node_cache *cache, ipset_node_id node); + + +/*----------------------------------------------------------------------- + * BDD operators + */ + +/** + * A function that provides the value for each variable in a BDD. + */ +typedef bool +(*ipset_assignment_func)(const void *user_data, + ipset_variable variable); + +/** + * An assignment function that gets the variable values from an array + * of gbooleans. + */ +bool +ipset_bool_array_assignment(const void *user_data, + ipset_variable variable); + +/** + * An assignment function that gets the variable values from an array + * of bits. + */ +bool +ipset_bit_array_assignment(const void *user_data, + ipset_variable variable); + +/** + * Evaluate a BDD given a particular assignment of variables. + */ +ipset_value +ipset_node_evaluate(const struct ipset_node_cache *cache, ipset_node_id node, + ipset_assignment_func assignment, + const void *user_data); + +/** + * Add an assignment to the BDD. + */ +ipset_node_id +ipset_node_insert(struct ipset_node_cache *cache, ipset_node_id node, + ipset_assignment_func assignment, + const void *user_data, ipset_variable variable_count, + ipset_value value); + + +/*----------------------------------------------------------------------- + * Variable assignments + */ + +/** + * Each variable in the input to a Boolean function can be true or + * false; it can also be EITHER, which means that the variable can be + * either true or false in a particular assignment without affecting + * the result of the function. + */ +enum ipset_tribool { + IPSET_FALSE = 0, + IPSET_TRUE = 1, + IPSET_EITHER = 2 +}; + + +/** + * An assignment is a mapping of variable numbers to Boolean values. + * It represents an input to a Boolean function that maps to a + * particular output value. Each variable in the input to a Boolean + * function can be true or false; it can also be EITHER, which means + * that the variable can be either true or false in a particular + * assignment without affecting the result of the function. + */ + +struct ipset_assignment { + /** + * The underlying variable assignments are stored in a vector of + * tribools. Every variable that has a true or false value must + * appear in the vector. Variables that are EITHER only have to + * appear to prevent gaps in the vector. Any variables outside + * the range of the vector are assumed to be EITHER. + */ + cork_array(enum ipset_tribool) values; +}; + + +/** + * Create a new assignment where all variables are indeterminite. + */ +struct ipset_assignment * +ipset_assignment_new(); + + +/** + * Free an assignment. + */ +void +ipset_assignment_free(struct ipset_assignment *assignment); + + +/** + * Compare two assignments for equality. + */ +bool +ipset_assignment_equal(const struct ipset_assignment *assignment1, + const struct ipset_assignment *assignment2); + + +/** + * Set the given variable, and all higher variables, to the EITHER + * value. + */ +void +ipset_assignment_cut(struct ipset_assignment *assignment, ipset_variable var); + + +/** + * Clear the assignment, setting all variables to the EITHER value. + */ +void +ipset_assignment_clear(struct ipset_assignment *assignment); + + +/** + * Return the value assigned to a particular variable. + */ +enum ipset_tribool +ipset_assignment_get(struct ipset_assignment *assignment, ipset_variable var); + + +/** + * Set the value assigned to a particular variable. + */ +void +ipset_assignment_set(struct ipset_assignment *assignment, + ipset_variable var, enum ipset_tribool value); + + +/*----------------------------------------------------------------------- + * Expanded assignments + */ + +/** + * An iterator for expanding a variable assignment. For each EITHER + * variable in the assignment, the iterator yields a result with both + * values. + */ +struct ipset_expanded_assignment { + /** Whether there are any more assignments in this iterator. */ + bool finished; + + /** + * The variable values in the current expanded assignment. Since + * there won't be any EITHERs in the expanded assignment, we can + * use a byte array, and represent each variable by a single bit. + */ + struct cork_buffer values; + + /** + * An array containing all of the variables that are EITHER in the + * original assignment. + */ + cork_array(ipset_variable) eithers; +}; + + +/** + * Return an iterator that expands a variable assignment. For each + * variable that's EITHER in the assignment, the iterator yields a + * result with both values. The iterator will ensure that the + * specified number of variables are given concrete values. + */ +struct ipset_expanded_assignment * +ipset_assignment_expand(const struct ipset_assignment *assignment, + ipset_variable var_count); + + +/** + * Free an expanded assignment iterator. + */ +void +ipset_expanded_assignment_free(struct ipset_expanded_assignment *exp); + + +/** + * Advance the iterator to the next assignment. + */ +void +ipset_expanded_assignment_advance(struct ipset_expanded_assignment *exp); + + +/*----------------------------------------------------------------------- + * BDD iterators + */ + +/** + * An iterator for walking through the assignments for a given BDD + * node. + * + * The iterator walks through each path in the BDD tree, stopping at + * each terminal node. Each time we reach a terminal node, we yield a + * new ipset_assignment object representing the assignment of variables + * along the current path. + * + * We maintain a stack of nodes leading to the current terminal, which + * allows us to backtrack up the path to find the next terminal when + * we increment the iterator. + */ +struct ipset_bdd_iterator { + /** Whether there are any more assignments in this iterator. */ + bool finished; + + /** The node cache that we're iterating through. */ + struct ipset_node_cache *cache; + + /** + * The sequence of nonterminal nodes leading to the current + * terminal. + */ + cork_array(ipset_node_id) stack; + + /** The current assignment. */ + struct ipset_assignment *assignment; + + /** + * The value of the BDD's function when applied to the current + * assignment. + */ + ipset_value value; +}; + + +/** + * Return an iterator that yields all of the assignments in the given + * BDD. The iterator contains two items of interest. The first is an + * ipset_assignment providing the value that each variable takes, while + * the second is the terminal value that is the result of the BDD's + * function when applied to that variable assignment. + */ +struct ipset_bdd_iterator * +ipset_node_iterate(struct ipset_node_cache *cache, ipset_node_id root); + + +/** + * Free a BDD iterator. + */ +void +ipset_bdd_iterator_free(struct ipset_bdd_iterator *iterator); + + +/** + * Advance the iterator to the next assignment. + */ +void +ipset_bdd_iterator_advance(struct ipset_bdd_iterator *iterator); + + +#endif /* IPSET_BDD_NODES_H */ diff --git a/3rd/libipset/include/ipset/bits.h b/3rd/libipset/include/ipset/bits.h new file mode 100644 index 0000000..a534605 --- /dev/null +++ b/3rd/libipset/include/ipset/bits.h @@ -0,0 +1,58 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef IPSET_BITS_H +#define IPSET_BITS_H + +#include + +/*----------------------------------------------------------------------- + * Bit arrays + */ + +/** + * Extract the byte that contains a particular bit in an array. + */ +#define IPSET_BIT_GET_BYTE(array, i) \ + (((uint8_t *) (array))[(i) / 8]) + +/** + * Create a bit mask that extracts a particular bit from the byte that + * it lives in. + */ +#define IPSET_BIT_ON_MASK(i) \ + (0x80 >> ((i) % 8)) + +/** + * Create a bit mask that extracts everything except for a particular + * bit from the byte that it lives in. + */ +#define IPSET_BIT_NEG_MASK(i) \ + (~IPSET_BIT_ON_MASK(i)) + +/** + * Return whether a particular bit is set in a byte array. Bits are + * numbered from 0, in a big-endian order. + */ +#define IPSET_BIT_GET(array, i) \ + ((IPSET_BIT_GET_BYTE(array, i) & \ + IPSET_BIT_ON_MASK(i)) != 0) + +/** + * Set (or unset) a particular bit is set in a byte array. Bits are + * numbered from 0, in a big-endian order. + */ +#define IPSET_BIT_SET(array, i, val) \ + (IPSET_BIT_GET_BYTE(array, i) = \ + (IPSET_BIT_GET_BYTE(array, i) & IPSET_BIT_NEG_MASK(i)) \ + | ((val)? IPSET_BIT_ON_MASK(i): 0)) + + +#endif /* IPSET_BITS_H */ diff --git a/3rd/libipset/include/ipset/errors.h b/3rd/libipset/include/ipset/errors.h new file mode 100644 index 0000000..ed178b4 --- /dev/null +++ b/3rd/libipset/include/ipset/errors.h @@ -0,0 +1,31 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef IPSET_ERRORS_H +#define IPSET_ERRORS_H + + +#include + + +/*----------------------------------------------------------------------- + * Error reporting + */ + +/* Hash of "ipset.h" */ +#define IPSET_ERROR 0xf2000181 + +enum ipset_error { + IPSET_IO_ERROR, + IPSET_PARSE_ERROR +}; + + +#endif /* IPSET_ERRORS_H */ diff --git a/3rd/libipset/include/ipset/ipset.h b/3rd/libipset/include/ipset/ipset.h new file mode 100644 index 0000000..cbb435f --- /dev/null +++ b/3rd/libipset/include/ipset/ipset.h @@ -0,0 +1,265 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef IPSET_IPSET_H +#define IPSET_IPSET_H + +#include + +#include +#include + +#include + + +struct ip_set { + struct ipset_node_cache *cache; + ipset_node_id set_bdd; +}; + + +struct ip_map { + struct ipset_node_cache *cache; + ipset_node_id map_bdd; + ipset_node_id default_bdd; +}; + + +/*--------------------------------------------------------------------- + * General functions + */ + +int +ipset_init_library(void); + + +/*--------------------------------------------------------------------- + * IP set functions + */ + +void +ipset_init(struct ip_set *set); + +void +ipset_done(struct ip_set *set); + +struct ip_set * +ipset_new(void); + +void +ipset_free(struct ip_set *set); + +bool +ipset_is_empty(const struct ip_set *set); + +bool +ipset_is_equal(const struct ip_set *set1, const struct ip_set *set2); + +size_t +ipset_memory_size(const struct ip_set *set); + +int +ipset_save(FILE *stream, const struct ip_set *set); + +int +ipset_save_to_stream(struct cork_stream_consumer *stream, + const struct ip_set *set); + +int +ipset_save_dot(FILE *stream, const struct ip_set *set); + +struct ip_set * +ipset_load(FILE *stream); + +bool +ipset_ipv4_add(struct ip_set *set, struct cork_ipv4 *elem); + +bool +ipset_ipv4_add_network(struct ip_set *set, struct cork_ipv4 *elem, + unsigned int cidr_prefix); + +bool +ipset_ipv4_remove(struct ip_set *set, struct cork_ipv4 *elem); + +bool +ipset_ipv4_remove_network(struct ip_set *set, struct cork_ipv4 *elem, + unsigned int cidr_prefix); + +bool +ipset_contains_ipv4(const struct ip_set *set, struct cork_ipv4 *elem); + +bool +ipset_ipv6_add(struct ip_set *set, struct cork_ipv6 *elem); + +bool +ipset_ipv6_add_network(struct ip_set *set, struct cork_ipv6 *elem, + unsigned int cidr_prefix); + +bool +ipset_ipv6_remove(struct ip_set *set, struct cork_ipv6 *elem); + +bool +ipset_ipv6_remove_network(struct ip_set *set, struct cork_ipv6 *elem, + unsigned int cidr_prefix); + +bool +ipset_contains_ipv6(const struct ip_set *set, struct cork_ipv6 *elem); + +bool +ipset_ip_add(struct ip_set *set, struct cork_ip *addr); + +bool +ipset_ip_add_network(struct ip_set *set, struct cork_ip *addr, + unsigned int cidr_prefix); + +bool +ipset_ip_remove(struct ip_set *set, struct cork_ip *addr); + +bool +ipset_ip_remove_network(struct ip_set *set, struct cork_ip *addr, + unsigned int cidr_prefix); + +bool +ipset_contains_ip(const struct ip_set *set, struct cork_ip *elem); + + +/* An internal state type used by the ipset_iterator_multiple_expansion_state + * field. */ +enum ipset_iterator_state { + IPSET_ITERATOR_NORMAL = 0, + IPSET_ITERATOR_MULTIPLE_IPV4, + IPSET_ITERATOR_MULTIPLE_IPV6 +}; + + +/* An iterator that returns all of the IP addresses that have a given value in + * an IP set or map. */ +struct ipset_iterator { + /* The address of the current IP network in the iterator. */ + struct cork_ip addr; + + /* The netmask of the current IP network in the iterator, given as a + * CIDR prefix. For a single IP address, this will be 32 or 128. */ + unsigned int cidr_prefix; + + /* Whether the current assignment needs to be expanded a second + * time. + * + * We have to expand IPv4 and IPv6 assignments separately, since the + * set of variables to turn into address bits is different. + * Unfortunately, a BDD assignment can contain both IPv4 and IPv6 + * addresses, if variable 0 is EITHER. (This is trivially true for + * the empty set, for instance.) In this case, we have to + * explicitly set variable 0 to TRUE, expand it as IPv4, and then + * set it to FALSE, and expand it as IPv6. This variable tells us + * whether we're in an assignment that needs to be expanded twice, + * and if so, which expansion we're currently in. + */ + enum ipset_iterator_state multiple_expansion_state; + + /* An iterator for retrieving each assignment in the set's BDD. */ + struct ipset_bdd_iterator *bdd_iterator; + + /* An iterator for expanding each assignment into individual IP + * addresses. */ + struct ipset_expanded_assignment *assignment_iterator; + + /* Whether there are any more IP addresses in this iterator. */ + bool finished; + + /* The desired value for each IP address. */ + bool desired_value; + + /* Whether to summarize the contents of the IP set as networks, + * where possible. */ + bool summarize; +}; + + +struct ipset_iterator * +ipset_iterate(struct ip_set *set, bool desired_value); + +struct ipset_iterator * +ipset_iterate_networks(struct ip_set *set, bool desired_value); + +void +ipset_iterator_free(struct ipset_iterator *iterator); + +void +ipset_iterator_advance(struct ipset_iterator *iterator); + + +/*--------------------------------------------------------------------- + * IP map functions + */ + +void +ipmap_init(struct ip_map *map, int default_value); + +void +ipmap_done(struct ip_map *map); + +struct ip_map * +ipmap_new(int default_value); + +void +ipmap_free(struct ip_map *map); + +bool +ipmap_is_empty(const struct ip_map *map); + +bool +ipmap_is_equal(const struct ip_map *map1, const struct ip_map *map2); + +size_t +ipmap_memory_size(const struct ip_map *map); + +int +ipmap_save(FILE *stream, const struct ip_map *map); + +int +ipmap_save_to_stream(struct cork_stream_consumer *stream, + const struct ip_map *map); + +struct ip_map * +ipmap_load(FILE *stream); + +void +ipmap_ipv4_set(struct ip_map *map, struct cork_ipv4 *elem, int value); + +void +ipmap_ipv4_set_network(struct ip_map *map, struct cork_ipv4 *elem, + unsigned int cidr_prefix, int value); + +int +ipmap_ipv4_get(struct ip_map *map, struct cork_ipv4 *elem); + +void +ipmap_ipv6_set(struct ip_map *map, struct cork_ipv6 *elem, int value); + +void +ipmap_ipv6_set_network(struct ip_map *map, struct cork_ipv6 *elem, + unsigned int cidr_prefix, int value); + +int +ipmap_ipv6_get(struct ip_map *map, struct cork_ipv6 *elem); + +void +ipmap_ip_set(struct ip_map *map, struct cork_ip *addr, int value); + +void +ipmap_ip_set_network(struct ip_map *map, struct cork_ip *addr, + unsigned int cidr_prefix, int value); + +int +ipmap_ip_get(struct ip_map *map, struct cork_ip *addr); + + +#endif /* IPSET_IPSET_H */ diff --git a/3rd/libipset/include/ipset/logging.h b/3rd/libipset/include/ipset/logging.h new file mode 100644 index 0000000..c4fb0d6 --- /dev/null +++ b/3rd/libipset/include/ipset/logging.h @@ -0,0 +1,31 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#ifndef IPSET_LOGGING_H +#define IPSET_LOGGING_H + + +#if !defined(IPSET_DEBUG) +#define IPSET_DEBUG 0 +#endif + +#if IPSET_DEBUG +#include +#define DEBUG(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#else +#define DEBUG(...) /* no debug messages */ +#endif + + +#endif /* IPSET_LOGGING_H */ diff --git a/3rd/libipset/src/bdd/assignments.c b/3rd/libipset/src/bdd/assignments.c new file mode 100644 index 0000000..41d5702 --- /dev/null +++ b/3rd/libipset/src/bdd/assignments.c @@ -0,0 +1,134 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" + + +struct ipset_assignment * +ipset_assignment_new() +{ + struct ipset_assignment *assignment = cork_new(struct ipset_assignment); + cork_array_init(&assignment->values); + return assignment; +} + + +void +ipset_assignment_free(struct ipset_assignment *assignment) +{ + cork_array_done(&assignment->values); + free(assignment); +} + + +bool +ipset_assignment_equal(const struct ipset_assignment *assignment1, + const struct ipset_assignment *assignment2) +{ + /* Identical pointers are trivially equal. */ + if (assignment1 == assignment2) { + return true; + } + + /* Otherwise we compare the assignments piecewise up through the end + * of the smaller vector. */ + unsigned int size1 = cork_array_size(&assignment1->values); + unsigned int size2 = cork_array_size(&assignment2->values); + unsigned int smaller_size = (size1 < size2)? size1: size2; + + unsigned int i; + for (i = 0; i < smaller_size; i++) { + if (cork_array_at(&assignment1->values, i) != + cork_array_at(&assignment2->values, i)) { + return false; + } + } + + /* If one of the assignment vectors is longer, any remaining + * elements must be indeterminate. */ + if (size1 > smaller_size) { + for (i = smaller_size; i < size1; i++) { + if (cork_array_at(&assignment1->values, i) != IPSET_EITHER) { + return false; + } + } + } + + if (size2 > smaller_size) { + for (i = smaller_size; i < size2; i++) { + if (cork_array_at(&assignment2->values, i) != IPSET_EITHER) { + return false; + } + } + } + + /* If we make it through all of that, the two assignments are equal. */ + return true; +} + + +void +ipset_assignment_cut(struct ipset_assignment *assignment, + ipset_variable var) +{ + if (var < cork_array_size(&assignment->values)) { + assignment->values.size = var; + } +} + + +void +ipset_assignment_clear(struct ipset_assignment *assignment) +{ + ipset_assignment_cut(assignment, 0); +} + + +enum ipset_tribool +ipset_assignment_get(struct ipset_assignment *assignment, ipset_variable var) +{ + if (var < cork_array_size(&assignment->values)) { + /* If the requested variable is in the range of the values + * array, return whatever is stored there. */ + return cork_array_at(&assignment->values, var); + } else { + /* Variables htat aren't in the values array are always EITHER. */ + return IPSET_EITHER; + } +} + + +void +ipset_assignment_set(struct ipset_assignment *assignment, + ipset_variable var, enum ipset_tribool value) +{ + /* Ensure that the vector is big enough to hold this variable + * assignment, inserting new EITHERs if needed. */ + if (var >= cork_array_size(&assignment->values)) { + unsigned int old_len = cork_array_size(&assignment->values); + + /* Expand the array. */ + cork_array_ensure_size(&assignment->values, var+1); + assignment->values.size = var+1; + + /* Fill in EITHERs in the newly allocated elements. */ + if (var != old_len) { + unsigned int i; + for (i = old_len; i < var; i++) { + cork_array_at(&assignment->values, i) = IPSET_EITHER; + } + } + } + + /* Assign the desired value. */ + cork_array_at(&assignment->values, var) = value; +} diff --git a/3rd/libipset/src/bdd/basics.c b/3rd/libipset/src/bdd/basics.c new file mode 100644 index 0000000..3db6f17 --- /dev/null +++ b/3rd/libipset/src/bdd/basics.c @@ -0,0 +1,467 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/bits.h" +#include "ipset/logging.h" + + +void +ipset_node_fprint(FILE *stream, struct ipset_node *node) +{ + fprintf(stream, + "nonterminal(x%u? " IPSET_NODE_ID_FORMAT + ": " IPSET_NODE_ID_FORMAT ")", + node->variable, + IPSET_NODE_ID_VALUES(node->high), + IPSET_NODE_ID_VALUES(node->low)); +} + + +static cork_hash +ipset_node_hash(void *user_data, const void *key) +{ + const struct ipset_node *node = key; + /* Hash of "ipset_node" */ + cork_hash hash = 0xf3b7dc44; + hash = cork_hash_variable(hash, node->variable); + hash = cork_hash_variable(hash, node->low); + hash = cork_hash_variable(hash, node->high); + return hash; +} + +static bool +ipset_node_equals(void *user_data, const void *key1, const void *key2) +{ + const struct ipset_node *node1 = key1; + const struct ipset_node *node2 = key2; + + if (node1 == node2) { + return true; + } + + return + (node1->variable == node2->variable) && + (node1->low == node2->low) && + (node1->high == node2->high); +} + + +/* The free list in an ipset_node_cache is represented by a + * singly-linked list of indices into the chunk array. Since the + * ipset_node instance is unused for nodes in the free list, we reuse + * the refcount field to store the "next" index. */ + +#define IPSET_NULL_INDEX ((ipset_variable) -1) + +struct ipset_node_cache * +ipset_node_cache_new() +{ + struct ipset_node_cache *cache = cork_new(struct ipset_node_cache); + cork_array_init(&cache->chunks); + cache->largest_index = 0; + cache->free_list = IPSET_NULL_INDEX; + cache->node_cache = cork_hash_table_new(0, 0); + cork_hash_table_set_hash + (cache->node_cache, (cork_hash_f) ipset_node_hash); + cork_hash_table_set_equals + (cache->node_cache, (cork_equals_f) ipset_node_equals); + return cache; +} + +void +ipset_node_cache_free(struct ipset_node_cache *cache) +{ + size_t i; + for (i = 0; i < cork_array_size(&cache->chunks); i++) { + free(cork_array_at(&cache->chunks, i)); + } + cork_array_done(&cache->chunks); + cork_hash_table_free(cache->node_cache); + free(cache); +} + + +/** + * Returns the index of a new ipset_node instance. + */ +static ipset_value +ipset_node_cache_alloc_node(struct ipset_node_cache *cache) +{ + if (cache->free_list == IPSET_NULL_INDEX) { + /* Nothing in the free list; need to allocate a new node. */ + ipset_value next_index = cache->largest_index++; + ipset_value chunk_index = next_index >> IPSET_BDD_NODE_CACHE_BIT_SIZE; + if (chunk_index >= cork_array_size(&cache->chunks)) { + /* We've filled up all of the existing chunks, and need to + * create a new one. */ + DEBUG(" (allocating chunk %zu)", + cork_array_size(&cache->chunks)); + struct ipset_node *new_chunk = cork_calloc + (IPSET_BDD_NODE_CACHE_SIZE, sizeof(struct ipset_node)); + cork_array_append(&cache->chunks, new_chunk); + } + return next_index; + } else { + /* Reuse a recently freed node. */ + ipset_value next_index = cache->free_list; + struct ipset_node *node = + ipset_node_cache_get_nonterminal_by_index(cache, next_index); + cache->free_list = node->refcount; + return next_index; + } +} + +ipset_node_id +ipset_node_incref(struct ipset_node_cache *cache, ipset_node_id node_id) +{ + if (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) { + struct ipset_node *node = + ipset_node_cache_get_nonterminal(cache, node_id); + DEBUG(" [incref " IPSET_NODE_ID_FORMAT "]", + IPSET_NODE_ID_VALUES(node_id)); + node->refcount++; + } + return node_id; +} + +void +ipset_node_decref(struct ipset_node_cache *cache, ipset_node_id node_id) +{ + if (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) { + struct ipset_node *node = + ipset_node_cache_get_nonterminal(cache, node_id); + DEBUG(" [decref " IPSET_NODE_ID_FORMAT "]", + IPSET_NODE_ID_VALUES(node_id)); + if (--node->refcount == 0) { + DEBUG(" [free " IPSET_NODE_ID_FORMAT "]", + IPSET_NODE_ID_VALUES(node_id)); + ipset_node_decref(cache, node->low); + ipset_node_decref(cache, node->high); + cork_hash_table_delete(cache->node_cache, node, NULL, NULL); + + /* Add the node to the free list */ + node->refcount = cache->free_list; + cache->free_list = ipset_nonterminal_value(node_id); + } + } +} + +bool +ipset_node_cache_nodes_equal(const struct ipset_node_cache *cache1, + ipset_node_id node_id1, + const struct ipset_node_cache *cache2, + ipset_node_id node_id2) +{ + struct ipset_node *node1; + struct ipset_node *node2; + + if (ipset_node_get_type(node_id1) != ipset_node_get_type(node_id2)) { + return false; + } + + if (ipset_node_get_type(node_id1) == IPSET_TERMINAL_NODE) { + return node_id1 == node_id2; + } + + node1 = ipset_node_cache_get_nonterminal(cache1, node_id1); + node2 = ipset_node_cache_get_nonterminal(cache2, node_id2); + return + (node1->variable == node2->variable) && + ipset_node_cache_nodes_equal(cache1, node1->low, cache2, node2->low) && + ipset_node_cache_nodes_equal(cache1, node1->high, cache2, node2->high); +} + +ipset_node_id +ipset_node_cache_nonterminal(struct ipset_node_cache *cache, + ipset_variable variable, + ipset_node_id low, ipset_node_id high) +{ + /* Don't allow any nonterminals whose low and high subtrees are the + * same, since the nonterminal would be redundant. */ + if (CORK_UNLIKELY(low == high)) { + DEBUG(" [ SKIP nonterminal(x%u? " + IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")]", + variable, IPSET_NODE_ID_VALUES(high), IPSET_NODE_ID_VALUES(low)); + ipset_node_decref(cache, high); + return low; + } + + /* Check to see if there's already a nonterminal with these contents + * in the cache. */ + DEBUG(" [search nonterminal(x%u? " + IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")]", + variable, IPSET_NODE_ID_VALUES(high), IPSET_NODE_ID_VALUES(low)); + + struct ipset_node search_node; + search_node.variable = variable; + search_node.low = low; + search_node.high = high; + + bool is_new; + struct cork_hash_table_entry *entry = + cork_hash_table_get_or_create + (cache->node_cache, &search_node, &is_new); + + if (!is_new) { + /* There's already a node with these contents, so return its ID. */ + ipset_node_id node_id = (uintptr_t) entry->value; + DEBUG(" [reuse " IPSET_NODE_ID_FORMAT "]", + IPSET_NODE_ID_VALUES(node_id)); + ipset_node_incref(cache, node_id); + ipset_node_decref(cache, low); + ipset_node_decref(cache, high); + return node_id; + } else { + /* This node doesn't exist yet. Allocate a permanent copy of + * the node, add it to the cache, and then return its ID. */ + ipset_value new_index = ipset_node_cache_alloc_node(cache); + ipset_node_id new_node_id = ipset_nonterminal_node_id(new_index); + struct ipset_node *real_node = + ipset_node_cache_get_nonterminal_by_index(cache, new_index); + real_node->refcount = 1; + real_node->variable = variable; + real_node->low = low; + real_node->high = high; + entry->key = real_node; + entry->value = (void *) (uintptr_t) new_node_id; + DEBUG(" [new " IPSET_NODE_ID_FORMAT "]", + IPSET_NODE_ID_VALUES(new_node_id)); + return new_node_id; + } +} + + +bool +ipset_bool_array_assignment(const void *user_data, ipset_variable variable) +{ + const bool *bool_array = (const bool *) user_data; + return bool_array[variable]; +} + + +bool +ipset_bit_array_assignment(const void *user_data, ipset_variable variable) +{ + return IPSET_BIT_GET(user_data, variable); +} + + +ipset_value +ipset_node_evaluate(const struct ipset_node_cache *cache, ipset_node_id node_id, + ipset_assignment_func assignment, const void *user_data) +{ + ipset_node_id curr_node_id = node_id; + DEBUG("Evaluating BDD node " IPSET_NODE_ID_FORMAT, + IPSET_NODE_ID_VALUES(node_id)); + + /* As long as the current node is a nonterminal, we have to check + * the value of the current variable. */ + while (ipset_node_get_type(curr_node_id) == IPSET_NONTERMINAL_NODE) { + /* We have to look up this variable in the assignment. */ + struct ipset_node *node = + ipset_node_cache_get_nonterminal(cache, curr_node_id); + bool this_value = assignment(user_data, node->variable); + DEBUG("[%3u] Nonterminal " IPSET_NODE_ID_FORMAT, + node->variable, IPSET_NODE_ID_VALUES(curr_node_id)); + DEBUG("[%3u] x%u = %s", + node->variable, node->variable, this_value? "TRUE": "FALSE"); + + if (this_value) { + /* This node's variable is true in the assignment vector, so + * trace down the high subtree. */ + curr_node_id = node->high; + } else { + /* This node's variable is false in the assignment vector, + * so trace down the low subtree. */ + curr_node_id = node->low; + } + } + + /* Once we find a terminal node, we've got the final result. */ + DEBUG("Evaluated result is %u", ipset_terminal_value(curr_node_id)); + return ipset_terminal_value(curr_node_id); +} + + +/* A “fake” BDD node given by an assignment. */ +struct ipset_fake_node { + ipset_variable current_var; + ipset_variable var_count; + ipset_assignment_func assignment; + const void *user_data; + ipset_value value; +}; + +/* A fake BDD node representing the terminal 0 value. */ +static struct ipset_fake_node fake_terminal_0 = { 0, 0, NULL, 0, 0 }; + +/* We set elements in a map using the if-then-else (ITE) operator: + * + * new_set = new_element? new_value: old_set + * + * The below is a straight copy of the standard trinary APPLY from the BDD + * literature, but without the caching of the results. And also with the + * wrinkle that the F argument to ITE (i.e., new_element) is given by an + * assignment, and not by a BDD node. (This lets us skip constructing the BDD + * for the assignment, saving us a few cycles.) + */ + +static ipset_node_id +ipset_apply_ite(struct ipset_node_cache *cache, struct ipset_fake_node *f, + ipset_value g, ipset_node_id h) +{ + ipset_node_id h_low; + ipset_node_id h_high; + ipset_node_id result_low; + ipset_node_id result_high; + + /* If F is a terminal, then we're in one of the following two + * cases: + * + * 1? G: H == G + * 0? G: H == H + */ + if (f->current_var == f->var_count) { + ipset_node_id result; + DEBUG("[%3u] F is terminal (value %u)", f->current_var, f->value); + + if (f->value == 0) { + DEBUG("[%3u] 0? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT + " = " IPSET_NODE_ID_FORMAT, + f->current_var, + IPSET_NODE_ID_VALUES(ipset_terminal_node_id(g)), + IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h)); + result = ipset_node_incref(cache, h); + } else { + result = ipset_terminal_node_id(g); + DEBUG("[%3u] 1? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT + " = " IPSET_NODE_ID_FORMAT, + f->current_var, IPSET_NODE_ID_VALUES(result), + IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(result)); + } + + return result; + } + + /* F? G: G == G */ + if (h == ipset_terminal_node_id(g)) { + DEBUG("[%3u] F? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT + " = " IPSET_NODE_ID_FORMAT, + f->current_var, IPSET_NODE_ID_VALUES(h), + IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h)); + return h; + } + + /* From here to the end of the function, we know that F is a + * nonterminal. */ + DEBUG("[%3u] F is nonterminal", f->current_var); + + /* We're going to do two recursive calls, a “low” one and a “high” one. For + * each nonterminal that has the minimum variable number, we use its low and + * high pointers in the respective recursive call. For all other + * nonterminals, and for all terminals, we use the operand itself. */ + + if (ipset_node_get_type(h) == IPSET_NONTERMINAL_NODE) { + struct ipset_node *h_node = + ipset_node_cache_get_nonterminal(cache, h); + + DEBUG("[%3u] H is nonterminal (variable %u)", + f->current_var, h_node->variable); + + if (h_node->variable < f->current_var) { + /* var(F) > var(H), so we only recurse down the H branches. */ + DEBUG("[%3u] Recursing only down H", f->current_var); + DEBUG("[%3u] Recursing high", f->current_var); + result_high = ipset_apply_ite(cache, f, g, h_node->high); + DEBUG("[%3u] Back from high recursion", f->current_var); + DEBUG("[%3u] Recursing low", f->current_var); + result_low = ipset_apply_ite(cache, f, g, h_node->low); + DEBUG("[%3u] Back from low recursion", f->current_var); + return ipset_node_cache_nonterminal + (cache, h_node->variable, result_low, result_high); + } else if (h_node->variable == f->current_var) { + /* var(F) == var(H), so we recurse down both branches. */ + DEBUG("[%3u] Recursing down both F and H", f->current_var); + h_low = h_node->low; + h_high = h_node->high; + } else { + /* var(F) < var(H), so we only recurse down the F branches. */ + DEBUG("[%3u] Recursing only down F", f->current_var); + h_low = h; + h_high = h; + } + } else { + /* H in nonterminal, so we only recurse down the F branches. */ + DEBUG("[%3u] H is terminal (value %u)", + f->current_var, ipset_terminal_value(h)); + DEBUG("[%3u] Recursing only down F", f->current_var); + h_low = h; + h_high = h; + } + + /* F is a “fake” nonterminal node, since it comes from our assignment. One + * of its branches will be the 0 terminal, and the other will be the fake + * nonterminal for the next variable in the assignment. (Which one is low + * and which one is high depends on the value of the current variable in the + * assignment.) */ + + if (f->assignment(f->user_data, f->current_var)) { + /* The current variable is set in F. The low branch is terminal 0; the + * high branch is the next variable in F. */ + DEBUG("[%3u] x[%u] is set", f->current_var, f->current_var); + DEBUG("[%3u] Recursing high", f->current_var); + f->current_var++; + result_high = ipset_apply_ite(cache, f, g, h_high); + f->current_var--; + DEBUG("[%3u] Back from high recursion: " IPSET_NODE_ID_FORMAT, + f->current_var, IPSET_NODE_ID_VALUES(result_high)); + DEBUG("[%3u] Recursing low", f->current_var); + fake_terminal_0.current_var = f->var_count; + fake_terminal_0.var_count = f->var_count; + result_low = ipset_apply_ite(cache, &fake_terminal_0, g, h_low); + DEBUG("[%3u] Back from low recursion: " IPSET_NODE_ID_FORMAT, + f->current_var, IPSET_NODE_ID_VALUES(result_low)); + } else { + /* The current variable is NOT set in F. The high branch is terminal 0; + * the low branch is the next variable in F. */ + DEBUG("[%3u] x[%u] is NOT set", f->current_var, f->current_var); + DEBUG("[%3u] Recursing high", f->current_var); + fake_terminal_0.current_var = f->var_count; + fake_terminal_0.var_count = f->var_count; + result_high = ipset_apply_ite(cache, &fake_terminal_0, g, h_high); + DEBUG("[%3u] Back from high recursion: " IPSET_NODE_ID_FORMAT, + f->current_var, IPSET_NODE_ID_VALUES(result_high)); + DEBUG("[%3u] Recursing low", f->current_var); + f->current_var++; + result_low = ipset_apply_ite(cache, f, g, h_low); + f->current_var--; + DEBUG("[%3u] Back from low recursion: " IPSET_NODE_ID_FORMAT, + f->current_var, IPSET_NODE_ID_VALUES(result_low)); + } + + return ipset_node_cache_nonterminal + (cache, f->current_var, result_low, result_high); +} + +ipset_node_id +ipset_node_insert(struct ipset_node_cache *cache, ipset_node_id node, + ipset_assignment_func assignment, const void *user_data, + ipset_variable var_count, ipset_value value) +{ + struct ipset_fake_node f = { 0, var_count, assignment, user_data, 1 }; + DEBUG("Inserting new element"); + return ipset_apply_ite(cache, &f, value, node); +} diff --git a/3rd/libipset/src/bdd/bdd-iterator.c b/3rd/libipset/src/bdd/bdd-iterator.c new file mode 100644 index 0000000..c246245 --- /dev/null +++ b/3rd/libipset/src/bdd/bdd-iterator.c @@ -0,0 +1,129 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/logging.h" + + +/** + * Add the given node ID to the node stack, and trace down from it + * until we find a terminal node. Assign values to the variables for + * each nonterminal that encounter along the way. We check low edges + * first, so each new variable we encounter will be assigned FALSE. + * (The high edges will be checked eventually by a call to the + * ipset_bdd_iterator_advance() function.) + */ +static void +add_node(struct ipset_bdd_iterator *iterator, ipset_node_id node_id) +{ + /* Keep tracing down low edges until we reach a terminal. */ + while (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) { + /* Add this nonterminal node to the stack, and trace down + * further into the tree. We check low edges first, so set the + * node's variable to FALSE in the assignment. */ + struct ipset_node *node = + ipset_node_cache_get_nonterminal(iterator->cache, node_id); + + cork_array_append(&iterator->stack, node_id); + ipset_assignment_set(iterator->assignment, node->variable, false); + + node_id = node->low; + } + + /* Once we find a terminal node, save it away in the iterator result + * and return. */ + iterator->value = ipset_terminal_value(node_id); +} + + +struct ipset_bdd_iterator * +ipset_node_iterate(struct ipset_node_cache *cache, ipset_node_id root) +{ + /* First allocate the iterator itself, and all of its contained + * fields. */ + + struct ipset_bdd_iterator *iterator = + cork_new(struct ipset_bdd_iterator); + iterator->finished = false; + iterator->cache = cache; + cork_array_init(&iterator->stack); + iterator->assignment = ipset_assignment_new(); + + /* Then add the root node to the iterator, tracing down until we + * find the first terminal node. */ + add_node(iterator, root); + return iterator; +} + + +void +ipset_bdd_iterator_free(struct ipset_bdd_iterator *iterator) +{ + cork_array_done(&iterator->stack); + ipset_assignment_free(iterator->assignment); + free(iterator); +} + + +void +ipset_bdd_iterator_advance(struct ipset_bdd_iterator *iterator) +{ + /* If we're already at the end of the iterator, don't do anything. */ + if (CORK_UNLIKELY(iterator->finished)) { + return; + } + + /* We look at the last node in the stack. If it's currently + * assigned a false value, then we track down its true branch. If + * it's got a true branch, then we pop it off and check the next to + * last node. */ + + DEBUG("Advancing BDD iterator"); + + while (cork_array_size(&iterator->stack) > 0) { + ipset_node_id last_node_id = + cork_array_at + (&iterator->stack, cork_array_size(&iterator->stack) - 1); + + struct ipset_node *last_node = + ipset_node_cache_get_nonterminal(iterator->cache, last_node_id); + + enum ipset_tribool current_value = + ipset_assignment_get(iterator->assignment, last_node->variable); + + /* The current value can't be EITHER, because we definitely + * assign a TRUE or FALSE to the variables of the nodes that we + * encounter. */ + if (current_value == IPSET_TRUE) { + /* We've checked both outgoing edges for this node, so pop + * it off and look at its parent. */ + iterator->stack.size--; + + /* Before continuing, reset this node's variable to + * indeterminate in the assignment. */ + ipset_assignment_set + (iterator->assignment, last_node->variable, IPSET_EITHER); + } else { + /* We've checked this node's low edge, but not its high + * edge. Set the variable to TRUE in the assignment, and + * add the high edge's node to the node stack. */ + ipset_assignment_set + (iterator->assignment, last_node->variable, IPSET_TRUE); + add_node(iterator, last_node->high); + return; + } + } + + /* If we fall through then we ran out of nodes to check. That means + * the iterator is done! */ + iterator->finished = true; +} diff --git a/3rd/libipset/src/bdd/expanded.c b/3rd/libipset/src/bdd/expanded.c new file mode 100644 index 0000000..6a97fd7 --- /dev/null +++ b/3rd/libipset/src/bdd/expanded.c @@ -0,0 +1,138 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/bits.h" +#include "ipset/logging.h" + + +static void +initialize(struct ipset_expanded_assignment *exp, + const struct ipset_assignment *assignment, + ipset_variable var_count) +{ + /* First loop through all of the variables in the assignment vector, + * making sure not to go further than the caller requested. */ + + ipset_variable last_assignment = cork_array_size(&assignment->values); + if (var_count < last_assignment) { + last_assignment = var_count; + } + + ipset_variable var; + for (var = 0; var < last_assignment; var++) { + enum ipset_tribool curr_value = + cork_array_at(&assignment->values, var); + + if (curr_value == IPSET_EITHER) { + /* If this variable is EITHER, start it off as FALSE, and + * add it to the eithers list. */ + DEBUG("Variable %u is EITHER", var); + + IPSET_BIT_SET(exp->values.buf, var, false); + cork_array_append(&exp->eithers, var); + } else { + /* Otherwise set the variable to the same value in the + * expanded assignment as it is in the non-expanded one. */ + + DEBUG("Variable %u is %s", var, curr_value? "true": "false"); + IPSET_BIT_SET(exp->values.buf, var, curr_value); + } + } + + /* If the caller requested more variables than there are in the + * assignment vector, add them to the eithers list. */ + for (var = last_assignment; var < var_count; var++) { + DEBUG("Variable %u is implicitly EITHER", var); + cork_array_append(&exp->eithers, var); + } +} + + +struct ipset_expanded_assignment * +ipset_assignment_expand(const struct ipset_assignment *assignment, + ipset_variable var_count) +{ + /* First allocate the iterator itself, and all of its contained + * fields. */ + + struct ipset_expanded_assignment *exp; + unsigned int values_size = (var_count / 8) + ((var_count % 8) != 0); + + exp = cork_new(struct ipset_expanded_assignment); + exp->finished = false; + cork_buffer_init(&exp->values); + cork_buffer_ensure_size(&exp->values, values_size); + memset(exp->values.buf, 0, values_size); + cork_array_init(&exp->eithers); + + /* Then initialize the values and eithers fields. */ + initialize(exp, assignment, var_count); + return exp; +} + + +void +ipset_expanded_assignment_free(struct ipset_expanded_assignment *exp) +{ + if (exp == NULL) { + return; + } + + cork_buffer_done(&exp->values); + cork_array_done(&exp->eithers); + free(exp); +} + + +void +ipset_expanded_assignment_advance(struct ipset_expanded_assignment *exp) +{ + /* If we're already at the end of the iterator, don't do anything. */ + if (CORK_UNLIKELY(exp->finished)) { + return; + } + + DEBUG("Advancing iterator"); + + /* Look at the last EITHER bit in the assignment. If it's 0, then + * set it to 1 and return. Otherwise we set it to 0 and carry up to + * the previous indeterminate bit. */ + + size_t i; + for (i = cork_array_size(&exp->eithers); i > 0; i--) { + size_t idx = i - 1; + ipset_variable either_var = cork_array_at(&exp->eithers, idx); + DEBUG("Checking EITHER variable %u", either_var); + + if (IPSET_BIT_GET(exp->values.buf, either_var)) { + /* This variable is currently true, so set it back to false + * and carry. */ + DEBUG(" Variable %u is true, changing to false and carrying", + either_var); + IPSET_BIT_SET(exp->values.buf, either_var, false); + } else { + /* This variable is currently false, so set it to true and + * return. */ + DEBUG(" Variable %u is false, changing to true", + either_var); + IPSET_BIT_SET(exp->values.buf, either_var, true); + return; + } + } + + /* If we fall through then we've made it through all of the expanded + * assignments. */ + exp->finished = true; +} diff --git a/3rd/libipset/src/bdd/reachable.c b/3rd/libipset/src/bdd/reachable.c new file mode 100644 index 0000000..8af8624 --- /dev/null +++ b/3rd/libipset/src/bdd/reachable.c @@ -0,0 +1,83 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/logging.h" + + +size_t +ipset_node_reachable_count(const struct ipset_node_cache *cache, + ipset_node_id node) +{ + /* Create a set to track when we've visited a given node. */ + struct cork_hash_table *visited = cork_pointer_hash_table_new(0, 0); + + /* And a queue of nodes to check. */ + cork_array(ipset_node_id) queue; + cork_array_init(&queue); + + if (ipset_node_get_type(node) == IPSET_NONTERMINAL_NODE) { + DEBUG("Adding node %u to queue", node); + cork_array_append(&queue, node); + } + + /* And somewhere to store the result. */ + size_t node_count = 0; + + /* Check each node in turn. */ + while (!cork_array_is_empty(&queue)) { + ipset_node_id curr = cork_array_at(&queue, --queue.size); + + /* We don't have to do anything if this node is already in the + * visited set. */ + if (cork_hash_table_get(visited, (void *) (uintptr_t) curr) == NULL) { + DEBUG("Visiting node %u for the first time", curr); + + /* Add the node to the visited set. */ + cork_hash_table_put + (visited, (void *) (uintptr_t) curr, + (void *) (uintptr_t) true, NULL, NULL, NULL); + + /* Increase the node count. */ + node_count++; + + /* And add the node's nonterminal children to the visit + * queue. */ + struct ipset_node *node = + ipset_node_cache_get_nonterminal(cache, curr); + + if (ipset_node_get_type(node->low) == IPSET_NONTERMINAL_NODE) { + DEBUG("Adding node %u to queue", node->low); + cork_array_append(&queue, node->low); + } + + if (ipset_node_get_type(node->high) == IPSET_NONTERMINAL_NODE) { + DEBUG("Adding node %u to queue", node->high); + cork_array_append(&queue, node->high); + } + } + } + + /* Return the result, freeing everything before we go. */ + cork_hash_table_free(visited); + cork_array_done(&queue); + return node_count; +} + + +size_t +ipset_node_memory_size(const struct ipset_node_cache *cache, + ipset_node_id node) +{ + return ipset_node_reachable_count(cache, node) * sizeof(struct ipset_node); +} diff --git a/3rd/libipset/src/bdd/read.c b/3rd/libipset/src/bdd/read.c new file mode 100644 index 0000000..9c59a79 --- /dev/null +++ b/3rd/libipset/src/bdd/read.c @@ -0,0 +1,350 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include + +#include +#include +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/errors.h" +#include "ipset/logging.h" + + +static const char MAGIC_NUMBER[] = "IP set"; +static const size_t MAGIC_NUMBER_LENGTH = sizeof(MAGIC_NUMBER) - 1; + + +/** + * On disk, we use a different node ID scheme than we do in memory. + * Terminal node IDs are non-negative, and are equal to the terminal + * value. Nonterminal node IDs are negative, starting with -1. + * Nonterminal -1 appears first on disk, then nonterminal -2, and so on. + */ + +typedef int serialized_id; + + +/** + * Sets a libcork error based on the contents of errno. + */ +static void +create_errno_error(FILE *stream) +{ + if (ferror(stream)) { + cork_error_set(IPSET_ERROR, IPSET_IO_ERROR, "%s", strerror(errno)); + } else { + cork_unknown_error(); + } +} + + +/** + * Read in a big-endian uint8 from a stream. If we can't read the + * integer for some reason, return an error. + */ +static int +read_uint8(FILE *stream, uint8_t *dest) +{ + size_t num_read = fread(dest, sizeof(uint8_t), 1, stream); + if (num_read != 1) { + create_errno_error(stream); + return -1; + } + + /* for a byte, we don't need to endian-swap */ + return 0; +} + + +/** + * Read in a big-endian uint16 from a stream. If we can't read the + * integer for some reason, return an error. + */ +static uint16_t +read_uint16(FILE *stream, uint16_t *dest) +{ + size_t num_read = fread(dest, sizeof(uint16_t), 1, stream); + if (num_read != 1) { + create_errno_error(stream); + return -1; + } + + CORK_UINT16_BIG_TO_HOST_IN_PLACE(*dest); + return 0; +} + + +/** + * Read in a big-endian uint32 from a stream. If we can't read the + * integer for some reason, return an error. + */ +static uint32_t +read_uint32(FILE *stream, uint32_t *dest) +{ + size_t num_read = fread(dest, sizeof(uint32_t), 1, stream); + if (num_read != 1) { + create_errno_error(stream); + return -1; + } + + CORK_UINT32_BIG_TO_HOST_IN_PLACE(*dest); + return 0; +} + + +/** + * Read in a big-endian uint64 from a stream. If we can't read the + * integer for some reason, return an error. + */ +static uint64_t +read_uint64(FILE *stream, uint64_t *dest) +{ + size_t num_read = fread(dest, sizeof(uint64_t), 1, stream); + if (num_read != 1) { + create_errno_error(stream); + return -1; + } + + CORK_UINT64_BIG_TO_HOST_IN_PLACE(*dest); + return 0; +} + + +/** + * A helper function that verifies that we've read exactly as many bytes + * as we should, returning an error otherwise. + */ +static int +verify_cap(size_t bytes_read, size_t cap) +{ + if (bytes_read < cap) { + /* There's extra data at the end of the stream. */ + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "Malformed set: extra data at end of stream."); + return -1; + } else if (bytes_read > cap) { + /* We read more data than we were supposed to. */ + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "Malformed set: read too much data."); + return -1; + } + + return 0; +} + +/** + * A helper function for reading a version 1 BDD stream. + */ +static ipset_node_id +load_v1(FILE *stream, struct ipset_node_cache *cache) +{ + DEBUG("Stream contains v1 IP set"); + ipset_node_id result; + struct cork_hash_table *cache_ids = cork_pointer_hash_table_new(0, 0); + + /* We've already read in the magic number and version. Next should + * be the length of the encoded set. */ + uint64_t length; + DEBUG("Reading encoded length"); + ei_check(read_uint64(stream, &length)); + + /* The length includes the magic number, version number, and the + * length field itself. Remove those to get the cap on the + * remaining stream. */ + + size_t bytes_read = 0; + size_t cap = length - + MAGIC_NUMBER_LENGTH - + sizeof(uint16_t) - + sizeof(uint64_t); + + DEBUG("Length cap is %zu bytes.", cap); + + /* Read in the number of nonterminals. */ + + uint32_t nonterminal_count; + DEBUG("Reading number of nonterminals"); + ei_check(read_uint32(stream, &nonterminal_count)); + bytes_read += sizeof(uint32_t); + + /* If there are no nonterminals, then there's only a single terminal + * left to read. */ + + if (nonterminal_count == 0) { + uint32_t value; + DEBUG("Reading single terminal value"); + ei_check(read_uint32(stream, &value)); + bytes_read += sizeof(uint32_t); + + /* We should have reached the end of the encoded set. */ + ei_check(verify_cap(bytes_read, cap)); + + /* Create a terminal node for this value and return it. */ + cork_hash_table_free(cache_ids); + return ipset_terminal_node_id(value); + } + + /* Otherwise, read in each nonterminal. We need to keep track of a + * mapping between each nonterminal's ID in the stream (which are + * number consecutively from -1), and its ID in the node cache + * (which could be anything). */ + + size_t i; + for (i = 0; i < nonterminal_count; i++) { + serialized_id serialized_id = -(i+1); + + /* Each serialized node consists of a variable index, a low + * pointer, and a high pointer. */ + + uint8_t variable; + ei_check(read_uint8(stream, &variable)); + bytes_read += sizeof(uint8_t); + + int32_t low; + ei_check(read_uint32(stream, (uint32_t *) &low)); + bytes_read += sizeof(int32_t); + + int32_t high; + ei_check(read_uint32(stream, (uint32_t *) &high)); + bytes_read += sizeof(int32_t); + + DEBUG("Read serialized node %d = (x%d? %" PRId32 ": %" PRId32 ")", + serialized_id, variable, high, low); + + /* Turn the low pointer into a node ID. If the pointer is >= 0, + * it's a terminal value. Otherwise, its a nonterminal ID, + * indexing into the serialized nonterminal array.*/ + + ipset_node_id low_id; + + if (low >= 0) { + low_id = ipset_terminal_node_id(low); + } else { + /* The file format guarantees that any node reference points + * to a node earlier in the serialized array. That means we + * can assume that cache_ids has already been filled in for + * this node. */ + + low_id = (ipset_node_id) (uintptr_t) + cork_hash_table_get(cache_ids, (void *) (intptr_t) low); + + DEBUG(" Serialized ID %" PRId32 " is internal ID %u", + low, low_id); + } + + /* Do the same for the high pointer. */ + + ipset_node_id high_id; + + if (high >= 0) { + high_id = ipset_terminal_node_id(high); + } else { + /* The file format guarantees that any node reference points + * to a node earlier in the serialized array. That means we + * can assume that cache_ids has already been filled in for + * this node. */ + + high_id = (ipset_node_id) (uintptr_t) + cork_hash_table_get(cache_ids, (void *) (intptr_t) high); + + DEBUG(" Serialized ID %" PRId32 " is internal ID %u", + high, high_id); + } + + /* Create a nonterminal node in the node cache. */ + result = ipset_node_cache_nonterminal + (cache, variable, low_id, high_id); + + DEBUG("Internal node %u = nonterminal(x%d? %u: %u)", + result, (int) variable, high_id, low_id); + + /* Remember the internal node ID for this new node, in case any + * later serialized nodes point to it. */ + + cork_hash_table_put + (cache_ids, (void *) (intptr_t) serialized_id, + (void *) (uintptr_t) result, NULL, NULL, NULL); + } + + /* We should have reached the end of the encoded set. */ + ei_check(verify_cap(bytes_read, cap)); + + /* The last node is the nonterminal for the entire set. */ + cork_hash_table_free(cache_ids); + return result; + + error: + /* If there's an error, clean up the objects that we've created + * before returning. */ + + cork_hash_table_free(cache_ids); + return 0; +} + + +ipset_node_id +ipset_node_cache_load(FILE *stream, struct ipset_node_cache *cache) +{ + size_t bytes_read; + + /* First, read in the magic number from the stream to ensure that + * this is an IP set. */ + + uint8_t magic[MAGIC_NUMBER_LENGTH]; + + DEBUG("Reading IP set magic number"); + bytes_read = fread(magic, 1, MAGIC_NUMBER_LENGTH, stream); + + if (ferror(stream)) { + create_errno_error(stream); + return 0; + } + + if (bytes_read != MAGIC_NUMBER_LENGTH) { + /* We reached EOF before reading the entire magic number. */ + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "Unexpected end of file"); + return 0; + } + + if (memcmp(magic, MAGIC_NUMBER, MAGIC_NUMBER_LENGTH) != 0) { + /* The magic number doesn't match, so this isn't a BDD. */ + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "Magic number doesn't match; this isn't an IP set."); + return 0; + } + + /* Read in the version number and dispatch to the right reading + * function. */ + + uint16_t version; + DEBUG("Reading IP set version"); + xi_check(0, read_uint16(stream, &version)); + + switch (version) { + case 0x0001: + return load_v1(stream, cache); + + default: + /* We don't know how to read this version number. */ + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "Unknown version number %" PRIu16, version); + return 0; + } +} diff --git a/3rd/libipset/src/bdd/write.c b/3rd/libipset/src/bdd/write.c new file mode 100644 index 0000000..0b5633e --- /dev/null +++ b/3rd/libipset/src/bdd/write.c @@ -0,0 +1,556 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2013, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/logging.h" + + +/*----------------------------------------------------------------------- + * Generic saving logic + */ + +/** + * On disk, we use a different node ID scheme than we do in memory. + * Terminal node IDs are non-negative, and are equal to the terminal + * value. Nonterminal node IDs are negative, starting with -1. + * Nonterminal -1 appears first on disk, then nonterminal -2, and so + * on. + */ + +typedef int serialized_id; + + +/* forward declaration */ +struct save_data; + + +/** + * A callback that outputs any necessary header. Should return an int + * status code indicating whether the write was successful. + */ + +typedef int +(*write_header_func)(struct save_data *save_data, + struct ipset_node_cache *cache, + ipset_node_id root); + + +/** + * A callback that outputs any necessary footer. Should return an int + * status code indicating whether the write was successful. + */ + +typedef int +(*write_footer_func)(struct save_data *save_data, + struct ipset_node_cache *cache, + ipset_node_id root); + + +/** + * A callback that actually outputs a terminal node to disk. Should + * return an int status code indicating whether the write was successful. + */ + +typedef int +(*write_terminal_func)(struct save_data *save_data, + ipset_value terminal_value); + + +/** + * A callback that actually outputs a nonterminal node to disk. + * Should return an int status code indicating whether the write was + * successful. + */ + +typedef int +(*write_nonterminal_func)(struct save_data *save_data, + serialized_id serialized_node, + ipset_variable variable, + serialized_id serialized_low, + serialized_id serialized_high); + + +/** + * A helper struct containing all of the persistent data items needed + * during the execution of a save. + */ + +struct save_data { + /* The node cache that we're saving nodes from. */ + struct ipset_node_cache *cache; + + /* The output stream to save the data to. */ + struct cork_stream_consumer *stream; + + /* The cache of serialized IDs for any nonterminals that we've + * encountered so far. */ + struct cork_hash_table *serialized_ids; + + /* The serialized ID to use for the next nonterminal that we + * encounter. */ + serialized_id next_serialized_id; + + /* The callback used to write the file header to the stream. */ + write_header_func write_header; + + /* The callback used to write the file footer to the stream. */ + write_footer_func write_footer; + + /* The callback used to write terminals to the stream. */ + write_terminal_func write_terminal; + + /* The callback used to write nonterminals to the stream. */ + write_nonterminal_func write_nonterminal; + + /* A pointer to any additional data needed by the callbacks. */ + void *user_data; +}; + + +/** + * A helper function for ipset_node_save(). Outputs a nonterminal + * node in a BDD tree, if we haven't done so already. Ensures that + * the children of the nonterminal are output before the nonterminal + * is. Returns the serialized ID of this node. + */ + +static int +save_visit_node(struct save_data *save_data, + ipset_node_id node_id, serialized_id *dest) +{ + /* Check whether we've already serialized this node. */ + + struct cork_hash_table_entry *entry; + bool is_new; + entry = cork_hash_table_get_or_create + (save_data->serialized_ids, (void *) (uintptr_t) node_id, &is_new); + + if (!is_new) { + *dest = (intptr_t) entry->value; + return 0; + } else { + if (ipset_node_get_type(node_id) == IPSET_TERMINAL_NODE) { + /* For terminals, there isn't really anything to do — we + * just output the terminal node and use its value as the + * serialized ID. */ + + ipset_value value = ipset_terminal_value(node_id); + + DEBUG("Writing terminal(%d)", value); + rii_check(save_data->write_terminal(save_data, value)); + entry->value = (void *) (intptr_t) value; + *dest = value; + return 0; + } else { + /* For nonterminals, we drill down into the node's children + * first, then output the nonterminal node. */ + + struct ipset_node *node = + ipset_node_cache_get_nonterminal(save_data->cache, node_id); + DEBUG("Visiting node %u nonterminal(x%u? %u: %u)", + node_id, node->variable, node->high, node->low); + + /* Output the node's nonterminal children before we output + * the node itself. */ + serialized_id serialized_low; + serialized_id serialized_high; + rii_check(save_visit_node(save_data, node->low, &serialized_low)); + rii_check(save_visit_node(save_data, node->high, &serialized_high)); + + /* Output the nonterminal */ + serialized_id result = save_data->next_serialized_id--; + DEBUG("Writing node %u as serialized node %d = (x%u? %d: %d)", + node_id, result, + node->variable, serialized_low, serialized_high); + + entry->value = (void *) (intptr_t) result; + *dest = result; + return save_data->write_nonterminal + (save_data, result, node->variable, + serialized_low, serialized_high); + } + } +} + + +static int +save_bdd(struct save_data *save_data, + struct ipset_node_cache *cache, ipset_node_id root) +{ + /* First, output the file header. */ + + DEBUG("Writing file header"); + rii_check(save_data->write_header(save_data, cache, root)); + + /* The serialized node IDs are different than the in-memory node + * IDs. This means that, for our nonterminal nodes, we need a + * mapping from internal node ID to serialized node ID. */ + + DEBUG("Creating file caches"); + save_data->serialized_ids = cork_pointer_hash_table_new(0, 0); + save_data->next_serialized_id = -1; + + /* Trace down through the BDD tree, outputting each terminal and + * nonterminal node as they're encountered. */ + + DEBUG("Writing nodes"); + + serialized_id last_serialized_id; + ei_check(save_visit_node(save_data, root, &last_serialized_id)); + + /* Finally, output the file footer and cleanup. */ + + DEBUG("Writing file footer"); + ei_check(save_data->write_footer(save_data, cache, root)); + + DEBUG("Freeing file caches"); + cork_hash_table_free(save_data->serialized_ids); + return 0; + + error: + /* If there's an error, clean up the objects that we've created + * before returning. */ + cork_hash_table_free(save_data->serialized_ids); + return -1; +} + + +/*----------------------------------------------------------------------- + * Helper functions + */ + +/** + * Write a NUL-terminated string to a stream. If we can't write the + * string for some reason, return an error. + */ +static int +write_string(struct cork_stream_consumer *stream, const char *str) +{ + size_t len = strlen(str); + return cork_stream_consumer_data(stream, str, len, false); +} + + +/** + * Write a big-endian uint8 to a stream. If we can't write the + * integer for some reason, return an error. + */ +static int +write_uint8(struct cork_stream_consumer *stream, uint8_t val) +{ + /* for a byte, we don't need to endian-swap */ + return cork_stream_consumer_data(stream, &val, sizeof(uint8_t), false); +} + + +/** + * Write a big-endian uint16 to a stream. If we can't write the + * integer for some reason, return an error. + */ +static int +write_uint16(struct cork_stream_consumer *stream, uint16_t val) +{ + CORK_UINT16_HOST_TO_BIG_IN_PLACE(val); + return cork_stream_consumer_data(stream, &val, sizeof(uint16_t), false); +} + + +/** + * Write a big-endian uint32 to a stream. If we can't write the + * integer for some reason, return an error. + */ + +static int +write_uint32(struct cork_stream_consumer *stream, uint32_t val) +{ + CORK_UINT32_HOST_TO_BIG_IN_PLACE(val); + return cork_stream_consumer_data(stream, &val, sizeof(uint32_t), false); +} + + +/** + * Write a big-endian uint64 to a stream. If we can't write the + * integer for some reason, return an error. + */ + +static int +write_uint64(struct cork_stream_consumer *stream, uint64_t val) +{ + CORK_UINT64_HOST_TO_BIG_IN_PLACE(val); + return cork_stream_consumer_data(stream, &val, sizeof(uint64_t), false); +} + + +/*----------------------------------------------------------------------- + * V1 BDD file + */ + +static const char MAGIC_NUMBER[] = "IP set"; +static const size_t MAGIC_NUMBER_LENGTH = sizeof(MAGIC_NUMBER) - 1; + + +static int +write_header_v1(struct save_data *save_data, + struct ipset_node_cache *cache, ipset_node_id root) +{ + /* Output the magic number for an IP set, and the file format + * version that we're going to write. */ + rii_check(cork_stream_consumer_data(save_data->stream, NULL, 0, true)); + rii_check(write_string(save_data->stream, MAGIC_NUMBER)); + rii_check(write_uint16(save_data->stream, 0x0001)); + + /* Determine how many reachable nodes there are, to calculate the + * size of the set. */ + size_t nonterminal_count = ipset_node_reachable_count(cache, root); + size_t set_size = + MAGIC_NUMBER_LENGTH + /* magic number */ + sizeof(uint16_t) + /* version number */ + sizeof(uint64_t) + /* length of set */ + sizeof(uint32_t) + /* number of nonterminals */ + (nonterminal_count * /* for each nonterminal: */ + (sizeof(uint8_t) + /* variable number */ + sizeof(uint32_t) + /* low pointer */ + sizeof(uint32_t) /* high pointer */ + )); + + /* If the root is a terminal, we need to add 4 bytes to the set + * size, for storing the terminal value. */ + if (ipset_node_get_type(root) == IPSET_TERMINAL_NODE) { + set_size += sizeof(uint32_t); + } + + rii_check(write_uint64(save_data->stream, set_size)); + rii_check(write_uint32(save_data->stream, nonterminal_count)); + return 0; +} + + +static int +write_footer_v1(struct save_data *save_data, + struct ipset_node_cache *cache, ipset_node_id root) +{ + /* If the root is a terminal node, then we output the terminal value + * in place of the (nonexistent) list of nonterminal nodes. */ + + if (ipset_node_get_type(root) == IPSET_TERMINAL_NODE) { + ipset_value value = ipset_terminal_value(root); + return write_uint32(save_data->stream, value); + } + + return 0; +} + + +static int +write_terminal_v1(struct save_data *save_data, ipset_value terminal_value) +{ + /* We don't have to write anything out for a terminal in a V1 file, + * since the terminal's value will be encoded into the node ID + * wherever it's used. */ + return 0; +} + + +static int +write_nonterminal_v1(struct save_data *save_data, + serialized_id serialized_node, + ipset_variable variable, + serialized_id serialized_low, + serialized_id serialized_high) +{ + rii_check(write_uint8(save_data->stream, variable)); + rii_check(write_uint32(save_data->stream, serialized_low)); + rii_check(write_uint32(save_data->stream, serialized_high)); + return 0; +} + + +int +ipset_node_cache_save(struct cork_stream_consumer *stream, struct ipset_node_cache *cache, + ipset_node_id node) +{ + struct save_data save_data; + save_data.cache = cache; + save_data.stream = stream; + save_data.write_header = write_header_v1; + save_data.write_footer = write_footer_v1; + save_data.write_terminal = write_terminal_v1; + save_data.write_nonterminal = write_nonterminal_v1; + return save_bdd(&save_data, cache, node); +} + + +/*----------------------------------------------------------------------- + * GraphViz dot file + */ + +static const char *GRAPHVIZ_HEADER = + "strict digraph bdd {\n"; + +static const char *GRAPHVIZ_FOOTER = + "}\n"; + + +struct dot_data { + /* The terminal value to leave out of the dot file. This should be + * the default value of the set or map. */ + ipset_value default_value; + + /* A scratch buffer */ + struct cork_buffer scratch; +}; + + +static int +write_header_dot(struct save_data *save_data, + struct ipset_node_cache *cache, ipset_node_id root) +{ + /* Output the opening clause of the GraphViz script. */ + rii_check(cork_stream_consumer_data(save_data->stream, NULL, 0, true)); + return write_string(save_data->stream, GRAPHVIZ_HEADER); +} + + +static int +write_footer_dot(struct save_data *save_data, + struct ipset_node_cache *cache, ipset_node_id root) +{ + /* Output the closing clause of the GraphViz script. */ + return write_string(save_data->stream, GRAPHVIZ_FOOTER); +} + + +static int +write_terminal_dot(struct save_data *save_data, ipset_value terminal_value) +{ + struct dot_data *dot_data = save_data->user_data; + + /* If this terminal has the default value, skip it. */ + if (terminal_value == dot_data->default_value) { + return 0; + } + + /* Output a node for the terminal value. */ + cork_buffer_printf + (&dot_data->scratch, + " t%d [shape=box, label=%d];\n", + terminal_value, terminal_value); + return write_string(save_data->stream, dot_data->scratch.buf); +} + + +static int +write_nonterminal_dot(struct save_data *save_data, + serialized_id serialized_node, + ipset_variable variable, + serialized_id serialized_low, + serialized_id serialized_high) +{ + struct dot_data *dot_data = save_data->user_data; + + /* Include a node for the nonterminal value. */ + cork_buffer_printf + (&dot_data->scratch, + " n%d [shape=circle,label=%u];\n", + (-serialized_node), variable); + + /* Include an edge for the low pointer. */ + if (serialized_low < 0) { + /* The low pointer is a nonterminal. */ + cork_buffer_append_printf + (&dot_data->scratch, + " n%d -> n%d", + (-serialized_node), (-serialized_low)); + } else { + /* The low pointer is a terminal. */ + ipset_value low_value = (ipset_value) serialized_low; + + if (low_value == dot_data->default_value) { + /* The terminal is the default value, so instead of a real + * terminal, connect this pointer to a dummy circle node. */ + cork_buffer_append_printf + (&dot_data->scratch, + " low%d [shape=circle,label=\"\"]\n" + " n%d -> low%d", + (-serialized_node), (-serialized_node), (-serialized_node)); + } else { + /* The terminal isn't a default, so go ahead and output it. */ + cork_buffer_append_printf + (&dot_data->scratch, + " n%d -> t%d", + (-serialized_node), serialized_low); + } + } + + cork_buffer_append_printf + (&dot_data->scratch, " [style=dashed,color=red]\n"); + + /* Include an edge for the high pointer. */ + if (serialized_high < 0) { + /* The high pointer is a nonterminal. */ + cork_buffer_append_printf + (&dot_data->scratch, + " n%d -> n%d", + (-serialized_node), (-serialized_high)); + } else { + /* The high pointer is a terminal. */ + ipset_value high_value = (ipset_value) serialized_high; + + if (high_value == dot_data->default_value) { + /* The terminal is the default value, so instead of a real + * terminal, connect this pointer to a dummy circle node. */ + cork_buffer_append_printf + (&dot_data->scratch, + " high%d " + "[shape=circle," + "fixedsize=true," + "height=0.25," + "width=0.25," + "label=\"\"]\n" + " n%d -> high%d", + (-serialized_node), (-serialized_node), (-serialized_node)); + } else { + /* The terminal isn't a default, so go ahead and output it. */ + cork_buffer_append_printf + (&dot_data->scratch, + " n%d -> t%d", + (-serialized_node), serialized_high); + } + } + + cork_buffer_append_printf + (&dot_data->scratch, " [style=solid,color=black]\n"); + + /* Output the clauses to the stream. */ + return write_string(save_data->stream, dot_data->scratch.buf); +} + + +int +ipset_node_cache_save_dot(struct cork_stream_consumer *stream, + struct ipset_node_cache *cache, ipset_node_id node) +{ + struct dot_data dot_data = { + 0 /* default value */ + }; + + struct save_data save_data; + save_data.cache = cache; + save_data.stream = stream; + save_data.write_header = write_header_dot; + save_data.write_footer = write_footer_dot; + save_data.write_terminal = write_terminal_dot; + save_data.write_nonterminal = write_nonterminal_dot; + save_data.user_data = &dot_data; + return save_bdd(&save_data, cache, node); +} diff --git a/3rd/libipset/src/general.c b/3rd/libipset/src/general.c new file mode 100644 index 0000000..82d3f92 --- /dev/null +++ b/3rd/libipset/src/general.c @@ -0,0 +1,21 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/ipset.h" + + +int +ipset_init_library() +{ + return 0; +} diff --git a/3rd/libipset/src/map/allocation.c b/3rd/libipset/src/map/allocation.c new file mode 100644 index 0000000..0dee17b --- /dev/null +++ b/3rd/libipset/src/map/allocation.c @@ -0,0 +1,50 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/ipset.h" + + +void +ipmap_init(struct ip_map *map, int default_value) +{ + /* The map starts empty, so every value assignment should yield the + * default. */ + map->cache = ipset_node_cache_new(); + map->default_bdd = ipset_terminal_node_id(default_value); + map->map_bdd = map->default_bdd; +} + + +struct ip_map * +ipmap_new(int default_value) +{ + struct ip_map *result = cork_new(struct ip_map); + ipmap_init(result, default_value); + return result; +} + + +void +ipmap_done(struct ip_map *map) +{ + ipset_node_decref(map->cache, map->map_bdd); + ipset_node_cache_free(map->cache); +} + + +void +ipmap_free(struct ip_map *map) +{ + ipmap_done(map); + free(map); +} diff --git a/3rd/libipset/src/map/inspection-template.c.in b/3rd/libipset/src/map/inspection-template.c.in new file mode 100644 index 0000000..e6447d5 --- /dev/null +++ b/3rd/libipset/src/map/inspection-template.c.in @@ -0,0 +1,88 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/bits.h" +#include "ipset/errors.h" +#include "ipset/ipset.h" + + +/** + * Given a BDD variable number, return the index of the corresponding + * bit in an IP address. IPv4 addresses use variables 1-32; IPv6 + * addresses use 1-128. (Variable 0 is used to identify the kind of + * address — TRUE for IPv4, FALSE for IPv6.) + */ + +static unsigned int +IPMAP_NAME(bit_for_var)(ipset_variable var) +{ + return (var - 1); +} + + +/** + * An assignment function that can be used to evaluate an IP map BDD. + */ + +static bool +IPMAP_NAME(assignment)(const void *addr, ipset_variable var) +{ + if (var == 0) { + return IP_DISCRIMINATOR_VALUE; + } else { + unsigned int bit = IPMAP_NAME(bit_for_var)(var); + return IPSET_BIT_GET(addr, bit); + } +} + + +int +IPMAP_NAME(get)(struct ip_map *map, CORK_IP *elem) +{ + return ipset_node_evaluate + (map->cache, map->map_bdd, IPMAP_NAME(assignment), elem); +} + + +void +IPMAP_NAME(set_network)(struct ip_map *map, CORK_IP *elem, + unsigned int cidr_prefix, int value) +{ + /* Special case — the BDD for a netmask that's out of range never + * evaluates to true. */ + if (cidr_prefix > IP_BIT_SIZE) { + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "CIDR block %u out of range [0..%u]", cidr_prefix, IP_BIT_SIZE); + return; + } + + ipset_node_id new_bdd = + ipset_node_insert + (map->cache, map->map_bdd, + IPMAP_NAME(assignment), elem, cidr_prefix + 1, value); + ipset_node_decref(map->cache, map->map_bdd); + map->map_bdd = new_bdd; +} + + +void +IPMAP_NAME(set)(struct ip_map *map, CORK_IP *elem, int value) +{ + ipset_node_id new_bdd = + ipset_node_insert + (map->cache, map->map_bdd, + IPMAP_NAME(assignment), elem, IP_BIT_SIZE + 1, value); + ipset_node_decref(map->cache, map->map_bdd); + map->map_bdd = new_bdd; +} diff --git a/3rd/libipset/src/map/inspection.c b/3rd/libipset/src/map/inspection.c new file mode 100644 index 0000000..e23253b --- /dev/null +++ b/3rd/libipset/src/map/inspection.c @@ -0,0 +1,69 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/ipset.h" + +bool +ipmap_is_empty(const struct ip_map *map) +{ + /* Since BDDs are unique, any map that maps all addresses to the + * default value is “empty”. */ + return (map->map_bdd == map->default_bdd); +} + +bool +ipmap_is_equal(const struct ip_map *map1, const struct ip_map *map2) +{ + return ipset_node_cache_nodes_equal + (map1->cache, map1->map_bdd, map2->cache, map2->map_bdd); +} + +size_t +ipmap_memory_size(const struct ip_map *map) +{ + return ipset_node_memory_size(map->cache, map->map_bdd); +} + + +void +ipmap_ip_set(struct ip_map *map, struct cork_ip *addr, int value) +{ + if (addr->version == 4) { + ipmap_ipv4_set(map, &addr->ip.v4, value); + } else { + ipmap_ipv6_set(map, &addr->ip.v6, value); + } +} + + +void +ipmap_ip_set_network(struct ip_map *map, struct cork_ip *addr, + unsigned int cidr_prefix, int value) +{ + if (addr->version == 4) { + ipmap_ipv4_set_network(map, &addr->ip.v4, cidr_prefix, value); + } else { + ipmap_ipv6_set_network(map, &addr->ip.v6, cidr_prefix, value); + } +} + + +int +ipmap_ip_get(struct ip_map *map, struct cork_ip *addr) +{ + if (addr->version == 4) { + return ipmap_ipv4_get(map, &addr->ip.v4); + } else { + return ipmap_ipv6_get(map, &addr->ip.v6); + } +} diff --git a/3rd/libipset/src/map/ipv4_map.c b/3rd/libipset/src/map/ipv4_map.c new file mode 100644 index 0000000..dcb8877 --- /dev/null +++ b/3rd/libipset/src/map/ipv4_map.c @@ -0,0 +1,37 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +/* + * The IPv4 and IPv6 map types are basically identical, except for the + * names of the functions, and the size of the values that are being + * stored. Rather than having two mostly duplicate definitions of each + * function, we define “template functions” where anything that depends + * on the size of the IP address is defined using the following macros. + */ + + +/* The name of the cork_ipvX type. */ +#define CORK_IP struct cork_ipv4 + +/* The number of bits in an IPvX address. */ +#define IP_BIT_SIZE 32 + +/* The value of the discriminator variable for an IPvX address. */ +#define IP_DISCRIMINATOR_VALUE true + +/* Creates a identifier of the form “ipset_ipv4_”. */ +#define IPSET_NAME(basename) ipset_ipv4_##basename + +/* Creates a identifier of the form “ipmap_ipv4_”. */ +#define IPMAP_NAME(basename) ipmap_ipv4_##basename + + +/* Now include all of the templates. */ +#include "inspection-template.c.in" diff --git a/3rd/libipset/src/map/ipv6_map.c b/3rd/libipset/src/map/ipv6_map.c new file mode 100644 index 0000000..5821141 --- /dev/null +++ b/3rd/libipset/src/map/ipv6_map.c @@ -0,0 +1,37 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +/* + * The IPv4 and IPv6 map types are basically identical, except for the + * names of the functions, and the size of the values that are being + * stored. Rather than having two mostly duplicate definitions of each + * function, we define “template functions” where anything that depends + * on the size of the IP address is defined using the following macros. + */ + + +/* The name of the cork_ipvX type. */ +#define CORK_IP struct cork_ipv6 + +/* The number of bits in an IPvX address. */ +#define IP_BIT_SIZE 128 + +/* The value of the discriminator variable for an IPvX address. */ +#define IP_DISCRIMINATOR_VALUE false + +/* Creates a identifier of the form “ipset_ipv6_”. */ +#define IPSET_NAME(basename) ipset_ipv6_##basename + +/* Creates a identifier of the form “ipmap_ipv6_”. */ +#define IPMAP_NAME(basename) ipmap_ipv6_##basename + + +/* Now include all of the templates. */ +#include "inspection-template.c.in" diff --git a/3rd/libipset/src/map/storage.c b/3rd/libipset/src/map/storage.c new file mode 100644 index 0000000..79de233 --- /dev/null +++ b/3rd/libipset/src/map/storage.c @@ -0,0 +1,101 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/errors.h" +#include "ipset/ipset.h" + + +static void +create_errno_error(FILE *stream) +{ + if (ferror(stream)) { + cork_error_set(IPSET_ERROR, IPSET_IO_ERROR, "%s", strerror(errno)); + } else { + cork_unknown_error(); + } +} + +struct file_consumer { + /* file_consumer is a subclass of cork_stream_consumer */ + struct cork_stream_consumer parent; + /* the file to write the data into */ + FILE *fp; +}; + +static int +file_consumer_data(struct cork_stream_consumer *vself, + const void *buf, size_t size, bool is_first) +{ + struct file_consumer *self = + cork_container_of(vself, struct file_consumer, parent); + size_t bytes_written = fwrite(buf, 1, size, self->fp); + /* If there was an error writing to the file, then signal this to + * the producer */ + if (bytes_written == size) { + return 0; + } else { + create_errno_error(self->fp); + return -1; + } +} + +static int +file_consumer_eof(struct cork_stream_consumer *vself) +{ + /* We don't close the file, so there's nothing special to do at + * end-of-stream. */ + return 0; +} + + +int +ipmap_save_to_stream(struct cork_stream_consumer *stream, + const struct ip_map *map) +{ + return ipset_node_cache_save(stream, map->cache, map->map_bdd); +} + +int +ipmap_save(FILE *fp, const struct ip_map *map) +{ + struct file_consumer stream = { + { file_consumer_data, file_consumer_eof, NULL }, fp + }; + return ipmap_save_to_stream(&stream.parent, map); +} + + +struct ip_map * +ipmap_load(FILE *stream) +{ + struct ip_map *map; + ipset_node_id new_bdd; + + /* It doesn't matter what default value we use here, because we're + * going to replace it with the default BDD we load in from the + * file. */ + map = ipmap_new(0); + new_bdd = ipset_node_cache_load(stream, map->cache); + if (cork_error_occurred()) { + ipmap_free(map); + return NULL; + } + + map->map_bdd = new_bdd; + return map; +} diff --git a/3rd/libipset/src/set/allocation.c b/3rd/libipset/src/set/allocation.c new file mode 100644 index 0000000..a1e9392 --- /dev/null +++ b/3rd/libipset/src/set/allocation.c @@ -0,0 +1,49 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/ipset.h" + + +void +ipset_init(struct ip_set *set) +{ + /* The set starts empty, so every value assignment should yield + * false. */ + set->cache = ipset_node_cache_new(); + set->set_bdd = ipset_terminal_node_id(false); +} + + +struct ip_set * +ipset_new(void) +{ + struct ip_set *result = cork_new(struct ip_set); + ipset_init(result); + return result; +} + + +void +ipset_done(struct ip_set *set) +{ + ipset_node_decref(set->cache, set->set_bdd); + ipset_node_cache_free(set->cache); +} + + +void +ipset_free(struct ip_set *set) +{ + ipset_done(set); + free(set); +} diff --git a/3rd/libipset/src/set/inspection-template.c.in b/3rd/libipset/src/set/inspection-template.c.in new file mode 100644 index 0000000..6e0142c --- /dev/null +++ b/3rd/libipset/src/set/inspection-template.c.in @@ -0,0 +1,130 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/bits.h" +#include "ipset/errors.h" +#include "ipset/ipset.h" + + +/** + * Given a BDD variable number, return the index of the corresponding + * bit in an IP address. IPv4 addresses use variables 1-32; IPv6 + * addresses use 1-128. (Variable 0 is used to identify the kind of + * address — TRUE for IPv4, FALSE for IPv6.) + */ + +static unsigned int +IPSET_NAME(bit_for_var)(ipset_variable var) +{ + return (var - 1); +} + + +/** + * An assignment function that can be used to evaluate an IP set BDD. + */ + +static bool +IPSET_NAME(assignment)(const void *addr, ipset_variable var) +{ + if (var == 0) { + return IP_DISCRIMINATOR_VALUE; + } else { + unsigned int bit = IPSET_NAME(bit_for_var)(var); + return IPSET_BIT_GET(addr, bit); + } +} + + +bool +IPSET_PRENAME(contains)(const struct ip_set *set, CORK_IP *elem) +{ + return ipset_node_evaluate + (set->cache, set->set_bdd, IPSET_NAME(assignment), elem); +} + + +bool +IPSET_NAME(add_network)(struct ip_set *set, CORK_IP *elem, + unsigned int cidr_prefix) +{ + /* Special case — the BDD for a netmask that's out of range never + * evaluates to true. */ + if (cidr_prefix > IP_BIT_SIZE) { + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "CIDR block %u out of range [0..%u]", cidr_prefix, IP_BIT_SIZE); + return false; + } + + ipset_node_id new_bdd = + ipset_node_insert + (set->cache, set->set_bdd, + IPSET_NAME(assignment), elem, cidr_prefix + 1, 1); + bool result = (new_bdd == set->set_bdd); + ipset_node_decref(set->cache, set->set_bdd); + set->set_bdd = new_bdd; + return result; +} + + +bool +IPSET_NAME(add)(struct ip_set *set, CORK_IP *elem) +{ + ipset_node_id new_bdd = + ipset_node_insert + (set->cache, set->set_bdd, + IPSET_NAME(assignment), elem, IP_BIT_SIZE + 1, 1); + bool result = (new_bdd == set->set_bdd); + ipset_node_decref(set->cache, set->set_bdd); + set->set_bdd = new_bdd; + return result; +} + + +bool +IPSET_NAME(remove)(struct ip_set *set, CORK_IP *elem) +{ + ipset_node_id new_bdd = + ipset_node_insert + (set->cache, set->set_bdd, + IPSET_NAME(assignment), elem, IP_BIT_SIZE + 1, 0); + bool result = (new_bdd == set->set_bdd); + ipset_node_decref(set->cache, set->set_bdd); + set->set_bdd = new_bdd; + return result; +} + + +bool +IPSET_NAME(remove_network)(struct ip_set *set, CORK_IP *elem, + unsigned int cidr_prefix) +{ + /* Special case — the BDD for a netmask that's out of range never + * evaluates to true. */ + if (cidr_prefix > IP_BIT_SIZE) { + cork_error_set + (IPSET_ERROR, IPSET_PARSE_ERROR, + "CIDR block %u out of range [0..%u]", cidr_prefix, IP_BIT_SIZE); + return false; + } + + ipset_node_id new_bdd = + ipset_node_insert + (set->cache, set->set_bdd, + IPSET_NAME(assignment), elem, cidr_prefix + 1, 0); + bool result = (new_bdd == set->set_bdd); + ipset_node_decref(set->cache, set->set_bdd); + set->set_bdd = new_bdd; + return result; +} diff --git a/3rd/libipset/src/set/inspection.c b/3rd/libipset/src/set/inspection.c new file mode 100644 index 0000000..2cb17ba --- /dev/null +++ b/3rd/libipset/src/set/inspection.c @@ -0,0 +1,91 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/ipset.h" + +bool +ipset_is_empty(const struct ip_set *set) +{ + /* Since BDDs are unique, the only empty set is the “false” BDD. */ + return (set->set_bdd == ipset_terminal_node_id(false)); +} + +bool +ipset_is_equal(const struct ip_set *set1, const struct ip_set *set2) +{ + return ipset_node_cache_nodes_equal + (set1->cache, set1->set_bdd, set2->cache, set2->set_bdd); +} + +size_t +ipset_memory_size(const struct ip_set *set) +{ + return ipset_node_memory_size(set->cache, set->set_bdd); +} + + +bool +ipset_ip_add(struct ip_set *set, struct cork_ip *addr) +{ + if (addr->version == 4) { + return ipset_ipv4_add(set, &addr->ip.v4); + } else { + return ipset_ipv6_add(set, &addr->ip.v6); + } +} + + +bool +ipset_ip_add_network(struct ip_set *set, struct cork_ip *addr, + unsigned int cidr_prefix) +{ + if (addr->version == 4) { + return ipset_ipv4_add_network(set, &addr->ip.v4, cidr_prefix); + } else { + return ipset_ipv6_add_network(set, &addr->ip.v6, cidr_prefix); + } +} + + +bool +ipset_ip_remove(struct ip_set *set, struct cork_ip *addr) +{ + if (addr->version == 4) { + return ipset_ipv4_remove(set, &addr->ip.v4); + } else { + return ipset_ipv6_remove(set, &addr->ip.v6); + } +} + + +bool +ipset_ip_remove_network(struct ip_set *set, struct cork_ip *addr, + unsigned int cidr_prefix) +{ + if (addr->version == 4) { + return ipset_ipv4_remove_network(set, &addr->ip.v4, cidr_prefix); + } else { + return ipset_ipv6_remove_network(set, &addr->ip.v6, cidr_prefix); + } +} + + +bool +ipset_contains_ip(const struct ip_set *set, struct cork_ip *addr) +{ + if (addr->version == 4) { + return ipset_contains_ipv4(set, &addr->ip.v4); + } else { + return ipset_contains_ipv6(set, &addr->ip.v6); + } +} diff --git a/3rd/libipset/src/set/ipv4_set.c b/3rd/libipset/src/set/ipv4_set.c new file mode 100644 index 0000000..2a33a67 --- /dev/null +++ b/3rd/libipset/src/set/ipv4_set.c @@ -0,0 +1,37 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +/* + * The IPv4 and IPv6 set types are basically identical, except for the + * names of the functions, and the size of the values that are being + * stored. Rather than having two mostly duplicate definitions of each + * function, we define “template functions” where anything that depends + * on the size of the IP address is defined using the following macros. + */ + + +/* The name of the cork_ipvX type. */ +#define CORK_IP struct cork_ipv4 + +/* The number of bits in an IPvX address. */ +#define IP_BIT_SIZE 32 + +/* The value of the discriminator variable for an IPvX address. */ +#define IP_DISCRIMINATOR_VALUE true + +/* Creates a identifier of the form “ipset_ipv4_”. */ +#define IPSET_NAME(basename) ipset_ipv4_##basename + +/* Creates a identifier of the form “ipset__ipv4”. */ +#define IPSET_PRENAME(basename) ipset_##basename##_ipv4 + + +/* Now include all of the templates. */ +#include "inspection-template.c.in" diff --git a/3rd/libipset/src/set/ipv6_set.c b/3rd/libipset/src/set/ipv6_set.c new file mode 100644 index 0000000..fac72cc --- /dev/null +++ b/3rd/libipset/src/set/ipv6_set.c @@ -0,0 +1,38 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2009-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +/* + * The IPv4 and IPv6 set types are basically identical, except for the + * names of the functions, and the size of the values that are being + * stored. Rather than having two mostly duplicate definitions of + * each function, we define “template functions” where anything that + * depends on the size of the IP address is defined using the + * following macros. + */ + + +/* The name of the cork_ipvX type. */ +#define CORK_IP struct cork_ipv6 + +/* The number of bits in an IPvX address. */ +#define IP_BIT_SIZE 128 + +/* The value of the discriminator variable for an IPvX address. */ +#define IP_DISCRIMINATOR_VALUE false + +/* Creates a identifier of the form “ipset_ipv6_”. */ +#define IPSET_NAME(basename) ipset_ipv6_##basename + +/* Creates a identifier of the form “ipset__ipv6”. */ +#define IPSET_PRENAME(basename) ipset_##basename##_ipv6 + + +/* Now include all of the templates. */ +#include "inspection-template.c.in" diff --git a/3rd/libipset/src/set/iterator.c b/3rd/libipset/src/set/iterator.c new file mode 100644 index 0000000..e601caa --- /dev/null +++ b/3rd/libipset/src/set/iterator.c @@ -0,0 +1,342 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/bits.h" +#include "ipset/ipset.h" +#include "ipset/logging.h" + + +#define IPV4_BIT_SIZE 32 +#define IPV6_BIT_SIZE 128 + + +/* Forward declarations */ + +static void +process_assignment(struct ipset_iterator *iterator); + +static void +expand_ipv6(struct ipset_iterator *iterator); + + +/** + * Find the highest non-EITHER bit in an assignment, starting from the + * given bit index. + */ +static unsigned int +find_last_non_either_bit(struct ipset_assignment *assignment, + unsigned int starting_bit) +{ + unsigned int i; + + for (i = starting_bit; i >= 1; i--) { + enum ipset_tribool value = ipset_assignment_get(assignment, i); + if (value != IPSET_EITHER) { + return i; + } + } + + return 0; +} + + +/** + * Create a generic IP address object from the current expanded + * assignment. + */ +static void +create_ip_address(struct ipset_iterator *iterator) +{ + struct cork_ip *addr = &iterator->addr; + struct ipset_expanded_assignment *exp = iterator->assignment_iterator; + + /* Initialize the address to all 0 bits. */ + memset(addr, 0, sizeof(struct cork_ip)); + + /* Check variable 0 to see if this is an IPv4 or IPv6 address. */ + addr->version = IPSET_BIT_GET(exp->values.buf, 0)? 4: 6; + + /* Copy bits from the expanded assignment. The number of bits to + * copy is given as the current netmask. We'll have calculated that + * already based on the non-expanded assignment. */ + unsigned int i; + for (i = 0; i < iterator->cidr_prefix; i++) { + IPSET_BIT_SET(&addr->ip, i, IPSET_BIT_GET(exp->values.buf, i+1)); + } + +#if IPSET_DEBUG + char buf[CORK_IP_STRING_LENGTH]; + cork_ip_to_raw_string(addr, buf); + DEBUG("Current IP address is %s/%u", buf, iterator->cidr_prefix); +#endif +} + + +/** + * Advance the BDD iterator, taking into account that some assignments + * need to be expanded twice. + */ +static void +advance_assignment(struct ipset_iterator *iterator) +{ + /* Check the current state of the iterator to determine how to + * advance. */ + + /* In most cases, the assignment we just finished only needed to be + * expanded once. So we move on to the next assignment and process + * it. */ + if (CORK_LIKELY(iterator->multiple_expansion_state == + IPSET_ITERATOR_NORMAL)) + { + ipset_bdd_iterator_advance(iterator->bdd_iterator); + process_assignment(iterator); + return; + } + + /* If the assignment needs to be expanded twice, we'll do the IPv4 + * expansion first. If that's what we've just finished, do the IPv6 + * expansion next. */ + + if (iterator->multiple_expansion_state == IPSET_ITERATOR_MULTIPLE_IPV4) { + DEBUG("Expanding IPv6 second"); + + iterator->multiple_expansion_state = IPSET_ITERATOR_MULTIPLE_IPV6; + ipset_assignment_set + (iterator->bdd_iterator->assignment, 0, IPSET_FALSE); + expand_ipv6(iterator); + return; + } + + /* If we've just finished the IPv6 expansion, then we've finished + * with this assignment. Before moving on to the next one, we have + * to reset variable 0 to EITHER (which it was before we started + * this whole mess). */ + + if (iterator->multiple_expansion_state == IPSET_ITERATOR_MULTIPLE_IPV6) { + DEBUG("Finished both expansions"); + + ipset_assignment_set + (iterator->bdd_iterator->assignment, 0, IPSET_EITHER); + ipset_bdd_iterator_advance(iterator->bdd_iterator); + process_assignment(iterator); + return; + } +} + + +/** + * Process the current expanded assignment in the current BDD + * assignment. + */ +static void +process_expanded_assignment(struct ipset_iterator *iterator) +{ + if (iterator->assignment_iterator->finished) { + /* If there isn't anything in the expanded assignment, advance + * to the next BDD assignment. */ + + DEBUG("Expanded assignment is finished"); + ipset_expanded_assignment_free(iterator->assignment_iterator); + iterator->assignment_iterator = NULL; + advance_assignment(iterator); + } else { + /* Otherwise, we've found a fully expanded assignment, so create + * an IP address for it and return. */ + create_ip_address(iterator); + } +} + + +/** + * Expand the current assignment as IPv4 addresses. + */ +static void +expand_ipv4(struct ipset_iterator *iterator) +{ + unsigned int last_bit; + + if (iterator->summarize) { + last_bit = find_last_non_either_bit + (iterator->bdd_iterator->assignment, IPV4_BIT_SIZE); + DEBUG("Last non-either bit is %u", last_bit); + } else { + last_bit = IPV4_BIT_SIZE; + } + + iterator->assignment_iterator = + ipset_assignment_expand + (iterator->bdd_iterator->assignment, last_bit + 1); + iterator->cidr_prefix = last_bit; + + process_expanded_assignment(iterator); +} + + +/** + * Expand the current assignment as IPv4 addresses. + */ +static void +expand_ipv6(struct ipset_iterator *iterator) +{ + unsigned int last_bit; + + if (iterator->summarize) { + last_bit = find_last_non_either_bit + (iterator->bdd_iterator->assignment, IPV6_BIT_SIZE); + DEBUG("Last non-either bit is %u", last_bit); + } else { + last_bit = IPV6_BIT_SIZE; + } + + iterator->assignment_iterator = + ipset_assignment_expand + (iterator->bdd_iterator->assignment, last_bit + 1); + iterator->cidr_prefix = last_bit; + + process_expanded_assignment(iterator); +} + + +/** + * Process the current assignment in the BDD iterator. + */ + +static void +process_assignment(struct ipset_iterator *iterator) +{ + while (!iterator->bdd_iterator->finished) { + if (iterator->bdd_iterator->value == iterator->desired_value) { + /* If the BDD iterator hasn't finished, and the result of + * the function with this assignment matches what the caller + * wants, then we've found an assignment to generate IP + * addresses from. + * + * Try to expand this assignment, and process the first + * expanded assignment. We want 32 + 1 variables if the + * current address is IPv4; 128 + 1 if it's IPv6. */ + + DEBUG("Got a matching BDD assignment"); + enum ipset_tribool address_type = ipset_assignment_get + (iterator->bdd_iterator->assignment, 0); + + if (address_type == IPSET_FALSE) { + /* FALSE means IPv6*/ + DEBUG("Assignment is IPv6"); + iterator->multiple_expansion_state = IPSET_ITERATOR_NORMAL; + expand_ipv6(iterator); + return; + } else if (address_type == IPSET_TRUE) { + /* TRUE means IPv4*/ + DEBUG("Assignment is IPv4"); + iterator->multiple_expansion_state = IPSET_ITERATOR_NORMAL; + expand_ipv4(iterator); + return; + } else { + /* EITHER means that this assignment contains both IPv4 + * and IPv6 addresses. Expand it as IPv4 first. */ + DEBUG("Assignment is both IPv4 and IPv6"); + DEBUG("Expanding IPv4 first"); + iterator->multiple_expansion_state = + IPSET_ITERATOR_MULTIPLE_IPV4; + ipset_assignment_set + (iterator->bdd_iterator->assignment, 0, IPSET_TRUE); + expand_ipv4(iterator); + return; + } + } + + /* The BDD iterator has a value, but it doesn't match the one we + * want. Advance the BDD iterator and try again. */ + DEBUG("Value is %d, skipping", iterator->bdd_iterator->value); + ipset_bdd_iterator_advance(iterator->bdd_iterator); + } + + /* If we fall through, then the BDD iterator has finished. That + * means there's nothing left for the set iterator. */ + + DEBUG("Set iterator is finished"); + ipset_expanded_assignment_free(iterator->assignment_iterator); + iterator->assignment_iterator = NULL; + + ipset_bdd_iterator_free(iterator->bdd_iterator); + iterator->bdd_iterator = NULL; + iterator->finished = true; +} + + +static struct ipset_iterator * +create_iterator(struct ip_set *set, bool desired_value, bool summarize) +{ + /* First allocate the iterator itself. */ + struct ipset_iterator *iterator = cork_new(struct ipset_iterator); + iterator->finished = false; + iterator->assignment_iterator = NULL; + iterator->desired_value = desired_value; + iterator->summarize = summarize; + + /* Then create the iterator that returns each BDD assignment. */ + DEBUG("Iterating set"); + iterator->bdd_iterator = ipset_node_iterate(set->cache, set->set_bdd); + + /* Then drill down from the current BDD assignment, creating an + * expanded assignment for it. */ + process_assignment(iterator); + return iterator; +} + + +struct ipset_iterator * +ipset_iterate(struct ip_set *set, bool desired_value) +{ + return create_iterator(set, desired_value, false); +} + + +struct ipset_iterator * +ipset_iterate_networks(struct ip_set *set, bool desired_value) +{ + return create_iterator(set, desired_value, true); +} + + +void +ipset_iterator_free(struct ipset_iterator *iterator) +{ + if (iterator->bdd_iterator != NULL) { + ipset_bdd_iterator_free(iterator->bdd_iterator); + } + if (iterator->assignment_iterator != NULL) { + ipset_expanded_assignment_free(iterator->assignment_iterator); + } + free(iterator); +} + + +void +ipset_iterator_advance(struct ipset_iterator *iterator) +{ + /* If we're already at the end of the iterator, don't do anything. */ + + if (CORK_UNLIKELY(iterator->finished)) { + return; + } + + /* Otherwise, advance the expanded assignment iterator to the next + * assignment, and then drill down into it. */ + + DEBUG("Advancing set iterator"); + ipset_expanded_assignment_advance(iterator->assignment_iterator); + process_expanded_assignment(iterator); +} diff --git a/3rd/libipset/src/set/storage.c b/3rd/libipset/src/set/storage.c new file mode 100644 index 0000000..7dc0927 --- /dev/null +++ b/3rd/libipset/src/set/storage.c @@ -0,0 +1,107 @@ +/* -*- coding: utf-8 -*- + * ---------------------------------------------------------------------- + * Copyright © 2010-2012, RedJack, LLC. + * All rights reserved. + * + * Please see the LICENSE.txt file in this distribution for license + * details. + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include + +#include + +#include "ipset/bdd/nodes.h" +#include "ipset/errors.h" +#include "ipset/ipset.h" + + +static void +create_errno_error(FILE *stream) +{ + if (ferror(stream)) { + cork_error_set(IPSET_ERROR, IPSET_IO_ERROR, "%s", strerror(errno)); + } else { + cork_unknown_error(); + } +} + +struct file_consumer { + /* file_consumer is a subclass of cork_stream_consumer */ + struct cork_stream_consumer parent; + /* the file to write the data into */ + FILE *fp; +}; + +static int +file_consumer_data(struct cork_stream_consumer *vself, + const void *buf, size_t size, bool is_first) +{ + struct file_consumer *self = + cork_container_of(vself, struct file_consumer, parent); + size_t bytes_written = fwrite(buf, 1, size, self->fp); + /* If there was an error writing to the file, then signal this to + * the producer */ + if (bytes_written == size) { + return 0; + } else { + create_errno_error(self->fp); + return -1; + } +} + +static int +file_consumer_eof(struct cork_stream_consumer *vself) +{ + /* We don't close the file, so there's nothing special to do at + * end-of-stream. */ + return 0; +} + +int +ipset_save_to_stream(struct cork_stream_consumer *stream, + const struct ip_set *set) +{ + return ipset_node_cache_save(stream, set->cache, set->set_bdd); +} + +int +ipset_save(FILE *fp, const struct ip_set *set) +{ + struct file_consumer stream = { + { file_consumer_data, file_consumer_eof, NULL }, fp + }; + return ipset_save_to_stream(&stream.parent, set); +} + + +int +ipset_save_dot(FILE *fp, const struct ip_set *set) +{ + struct file_consumer stream = { + { file_consumer_data, file_consumer_eof, NULL }, fp + }; + return ipset_node_cache_save_dot(&stream.parent, set->cache, set->set_bdd); +} + + +struct ip_set * +ipset_load(FILE *stream) +{ + struct ip_set *set; + ipset_node_id new_bdd; + + set = ipset_new(); + new_bdd = ipset_node_cache_load(stream, set->cache); + if (cork_error_occurred()) { + ipset_free(set); + return NULL; + } + + set->set_bdd = new_bdd; + return set; +} diff --git a/3rd/libsodium b/3rd/libsodium index c1f749e..4705c0a 160000 --- a/3rd/libsodium +++ b/3rd/libsodium @@ -1 +1 @@ -Subproject commit c1f749e68a6c2370ad77d514dd90dc7b4933f340 +Subproject commit 4705c0a06681d48ceec5ecbb66d496fd70c59bba diff --git a/CHANGES.md b/CHANGES.md index fe98a9b..ceaa91b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +v0.3.0 (2015-10-1) +----------- +* Feature: Support ACL + + v0.2.6 (2015-9-16) ----------- * Hotfix: Crypto init once only diff --git a/Makefile b/Makefile index 4a708ed..49a5487 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MAJOR = 0 -MINOR = 2 -PATCH = 6 +MINOR = 3 +PATCH = 0 NAME = xsocks ifdef O @@ -68,7 +68,7 @@ EXTRA_CFLAGS = ######################################################################### CPPFLAGS += -Isrc -CPPFLAGS += -I3rd/libuv/include -I3rd/libsodium/src/libsodium/include -I3rd/c-ares/ +CPPFLAGS += -I3rd/libuv/include -I3rd/libsodium/src/libsodium/include -I3rd/c-ares -I3rd/libcork/include -I3rd/libipset/include LDFLAGS = -Wl,--gc-sections @@ -80,6 +80,8 @@ else endif endif +LIBCORK = 3rd/libcork/libcork.a +LIBIPSET = 3rd/libipset/libipset.a LIBS += 3rd/libuv/.libs/libuv.a 3rd/libsodium/src/libsodium/.libs/libsodium.a ifdef MINGW32 @@ -127,6 +129,43 @@ libsodium: 3rd/libsodium/Makefile c-ares: 3rd/c-ares/Makefile +$(LIBCORK): \ + 3rd/libcork/src/core/allocator.o \ + 3rd/libcork/src/core/error.o \ + 3rd/libcork/src/core/ip-address.o \ + 3rd/libcork/src/ds/array.o \ + 3rd/libcork/src/ds/hash-table.o \ + 3rd/libcork/src/ds/buffer.o \ + 3rd/libcork/src/ds/dllist.o \ + 3rd/libcork/src/posix/process.o + $(BUILD_AR) rcu $@ $^ + $(BUILD_RANLIB) $@ + +$(LIBIPSET): \ + 3rd/libipset/src/bdd/bdd-iterator.o \ + 3rd/libipset/src/bdd/read.o \ + 3rd/libipset/src/bdd/assignments.o \ + 3rd/libipset/src/bdd/write.o \ + 3rd/libipset/src/bdd/basics.o \ + 3rd/libipset/src/bdd/reachable.o \ + 3rd/libipset/src/bdd/expanded.o \ + 3rd/libipset/src/general.o \ + 3rd/libipset/src/map/inspection.o \ + 3rd/libipset/src/map/storage.o \ + 3rd/libipset/src/map/allocation.o \ + 3rd/libipset/src/map/ipv6_map.o \ + 3rd/libipset/src/map/ipv4_map.o \ + 3rd/libipset/src/set/ipv4_set.o \ + 3rd/libipset/src/set/inspection.o \ + 3rd/libipset/src/set/iterator.o \ + 3rd/libipset/src/set/storage.o \ + 3rd/libipset/src/set/ipv6_set.o \ + 3rd/libipset/src/set/allocation.o + $(BUILD_AR) rcu $@ $^ + $(BUILD_RANLIB) $@ + +lib3rd: $(LIBCORK) $(LIBIPSET) + ifndef MINGW32 xsocksd: \ src/util.o \ @@ -163,6 +202,7 @@ endif ifndef MINGW32 xsocks: \ + src/acl.o \ src/util.o \ src/logger.o \ src/common.o \ @@ -175,8 +215,9 @@ xsocks: \ src/xsocks_udprelay.o \ src/xsocks_client.o \ src/xsocks_remote.o \ - src/xsocks.o - $(LINK) $^ -o $(OBJTREE)/$@ $(LDFLAGS) + src/xsocks.o \ + | lib3rd + $(LINK) $^ -o $(OBJTREE)/$@ $(LDFLAGS) $(LIBIPSET) $(LIBCORK) else xsocks.exe: \ src/util.o \ @@ -265,7 +306,7 @@ xtunnel.exe: \ endif clean: - @find $(OBJTREE)/src -type f \ + @find $(OBJTREE)/src 3rd/libcork 3rd/libipset -type f \ \( -name '*.bak' -o -name '*~' \ -o -name '*.o' -o -name '*.tmp' \) -print \ | xargs rm -f diff --git a/README.md b/README.md index 6803d0a..32e8ebd 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ xsocksd --signal stop ```bash xsocks -s SERVER:PORT -k PASSWORD -xforwarder -s SERVER:PORT -k PASSWORD -t TARGET:PORT +xforwarder -s SERVER:PORT -k PASSWORD -d DESTINATION:PORT xtunnel -m client -k PASSWORD -t TARGET:PORT ``` diff --git a/config.mk b/config.mk index 64dc8ed..91c39de 100644 --- a/config.mk +++ b/config.mk @@ -48,6 +48,8 @@ FINAL_LDFLAGS = CCC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) LINK=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) +BUILD_AR=$(QUIET_AR)$(AR) +BUILD_RANLIB=$(QUIET_RANLIB)$(RANLIB) INSTALL=$(QUIET_INSTALL) CCCOLOR="\033[34m" @@ -61,6 +63,8 @@ ENDCOLOR="\033[0m" ifndef V QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; +QUIET_AR = @printf ' %b %b\n' $(LINKCOLOR)AR$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; +QUIET_RANLIB = @printf ' %b %b\n' $(LINKCOLOR)RANLIB$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_STRIP_OPTION = > /dev/null endif diff --git a/openwrt/Makefile b/openwrt/Makefile index c7375ee..ae6bc9b 100644 --- a/openwrt/Makefile +++ b/openwrt/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=xsocks -PKG_VERSION:=0.2.6 +PKG_VERSION:=0.3.0 PKG_RELEASE= PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 0000000..a34ab7f --- /dev/null +++ b/src/acl.c @@ -0,0 +1,104 @@ +#include +#include + +#include "util.h" +#include "logger.h" +#include "ipset/ipset.h" + +static struct ip_set acl_ipv4_set; +static struct ip_set acl_ipv6_set; + +static void +parse_addr_cidr(const char *str, char *host, int *cidr) { + int ret = -1, n = 0; + char *pch; + + pch = strchr(str, '/'); + while (pch != NULL) { + n++; + ret = pch - str; + pch = strchr(pch + 1, '/'); + } + if (ret == -1) { + strcpy(host, str); + *cidr = -1; + } else { + memcpy(host, str, ret); + host[ret] = '\0'; + *cidr = atoi(str + ret + 1); + } +} + +int +acl_init(const char *path) { + // initialize ipset + ipset_init_library(); + ipset_init(&acl_ipv4_set); + ipset_init(&acl_ipv6_set); + + FILE *f = fopen(path, "r"); + if (f == NULL) { + logger_stderr("Invalid acl path"); + return -1; + } + + char line[256]; + while (!feof(f)) { + if (fgets(line, 256, f)) { + // Trim the newline + int len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + char host[256]; + int cidr; + parse_addr_cidr(line, host, &cidr); + + struct cork_ip addr; + int err = cork_ip_init(&addr, host); + if (!err) { + if (addr.version == 4) { + if (cidr >= 0) { + ipset_ipv4_add_network(&acl_ipv4_set, &(addr.ip.v4), cidr); + } else { + ipset_ipv4_add(&acl_ipv4_set, &(addr.ip.v4)); + } + } else if (addr.version == 6) { + if (cidr >= 0) { + ipset_ipv6_add_network(&acl_ipv6_set, &(addr.ip.v6), cidr); + } else { + ipset_ipv6_add(&acl_ipv6_set, &(addr.ip.v6)); + } + } + } + } + } + + fclose(f); + + return 0; +} + +void +acl_free(void) { + ipset_done(&acl_ipv4_set); + ipset_done(&acl_ipv6_set); +} + +int +acl_contains_ip(const char * host) { + struct cork_ip addr; + int err = cork_ip_init(&addr, host); + if (err) { + return 0; + } + + if (addr.version == 4) { + return ipset_contains_ipv4(&acl_ipv4_set, &(addr.ip.v4)); + } else if (addr.version == 6) { + return ipset_contains_ipv6(&acl_ipv6_set, &(addr.ip.v6)); + } + + return 0; +} diff --git a/src/acl.h b/src/acl.h new file mode 100644 index 0000000..0bde869 --- /dev/null +++ b/src/acl.h @@ -0,0 +1,8 @@ +#ifndef ACL_H +#define ACL_H + +int acl_init(const char *path); +void acl_free(void); +int acl_contains_ip(const char * ip); + +#endif // for #ifndef ACL_H diff --git a/src/common.h b/src/common.h index 9bfea5f..007ef9e 100644 --- a/src/common.h +++ b/src/common.h @@ -4,7 +4,7 @@ #include "uv.h" #include "socks.h" -#define XSOCKS_VERSION "0.2.6" +#define XSOCKS_VERSION "0.3.0" struct server_context { int index; diff --git a/src/logger.c b/src/logger.c index 77dc580..79faeb0 100644 --- a/src/logger.c +++ b/src/logger.c @@ -26,7 +26,7 @@ static uv_tty_t _ttyerr; #endif static const char *levels[] = { - "EMERG", "ALERT", "CRIT", "ERR", "WARNING", "NOTICE", "INFO", "DEBUG" + "EMERG", "ALERT", "CRIT", "ERR", "WARN", "NOTICE", "INFO", "DEBUG" }; static const char *colors[] = { diff --git a/src/xsocks.c b/src/xsocks.c index 0895c8e..50721b7 100644 --- a/src/xsocks.c +++ b/src/xsocks.c @@ -9,6 +9,7 @@ #include "uv.h" +#include "acl.h" #include "util.h" #include "logger.h" #include "crypto.h" @@ -22,6 +23,7 @@ static char *local_addr = "0.0.0.0:1080"; static char *server_addr_buf; static char *pidfile = "/var/run/xsocks/xsocks.pid"; static char *password = NULL; +static char *acl_file; static char *xsignal; #ifndef _WIN32 @@ -38,6 +40,7 @@ static const struct option _lopts[] = { { "", required_argument, NULL, 't' }, { "", required_argument, NULL, 's' }, { "", no_argument, NULL, 'n' }, + { "acl", required_argument, NULL, 0 }, { "signal", required_argument, NULL, 0 }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, @@ -61,6 +64,9 @@ print_usage(const char *prog) { #ifndef _WIN32 " [-c ]\t : worker threads\n" " [-p ]\t : pid file path (default: /var/run/xsocks/xsocks.pid)\n" +#endif + " [--acl ]\t : ACL (Access Control List) file path\n" +#ifndef _WIN32 " [--signal ]\t : send signal to xsocks: quit, stop\n" " [-n]\t\t\t : non daemon mode\n" #endif @@ -118,6 +124,9 @@ parse_opts(int argc, char *argv[]) { } fprintf(stderr, "invalid option: --signal %s\n", xsignal); print_usage(argv[0]); + } + if (strcmp("acl", _lopts[longindex].name) == 0) { + acl_file = optarg; } break; default: @@ -214,6 +223,10 @@ init(void) { if (idle_timeout == 0) { idle_timeout = 60; } + + if (acl_file != NULL) { + acl = !acl_init(acl_file); + } } int @@ -337,6 +350,9 @@ main(int argc, char *argv[]) { } #endif + if (acl_file != NULL) { + acl_free(); + } logger_exit(); return 0; diff --git a/src/xsocks.h b/src/xsocks.h index e43b31d..b2aa553 100644 --- a/src/xsocks.h +++ b/src/xsocks.h @@ -18,15 +18,17 @@ struct client_context { uv_udp_t udp; } handle; uv_write_t write_req; + struct sockaddr addr; struct remote_context *remote; uint8_t buf[MAX_PACKET_SIZE]; - struct sockaddr addr; + uint8_t buflen; uint8_t cmd; char target_addr[256]; }; struct remote_context { int stage; + int direct; union { uv_handle_t handle; uv_stream_t stream; @@ -36,6 +38,7 @@ struct remote_context { uv_write_t write_req; uv_timer_t *timer; uv_connect_t connect_req; + struct sockaddr addr; struct client_context *client; struct packet packet; uint16_t idle_timeout; @@ -48,13 +51,14 @@ void forward_to_client(struct client_context *client, uint8_t *buf, int buflen); void client_accept_cb(uv_stream_t *server, int status); void request_ack(struct client_context *client, enum s5_rep rep); -struct remote_context * new_remote(uint16_t timeout); +struct remote_context * new_remote(uint16_t timeout, struct sockaddr *addr); void close_remote(struct remote_context *remote); void connect_to_remote(struct remote_context *remote); void receive_from_remote(struct remote_context *remote); void forward_to_remote(struct remote_context *remote, uint8_t *buf, int buflen); void reset_timer(struct remote_context *remote); +int acl; int verbose; uint16_t idle_timeout; struct sockaddr bind_addr; diff --git a/src/xsocks_client.c b/src/xsocks_client.c index f4fa783..b37af07 100644 --- a/src/xsocks_client.c +++ b/src/xsocks_client.c @@ -5,6 +5,7 @@ #include "uv.h" +#include "acl.h" #include "util.h" #include "logger.h" #include "crypto.h" @@ -98,23 +99,18 @@ forward_to_client(struct client_context *client, uint8_t *buf, int buflen) { */ void request_ack(struct client_context *client, enum s5_rep rep) { - struct remote_context *remote = client->remote; struct sockaddr addr; int addrlen = sizeof(addr); int buflen; uint8_t *buf; - buf = remote->packet.buf; + buf = client->buf; buf[0] = 0x05; // VER buf[1] = rep; // REP buf[2] = 0x00; // RSV memset(&addr, 0, sizeof(addr)); - if (client->cmd == S5_CMD_UDP_ASSOCIATE) { - uv_tcp_getsockname(&client->handle.tcp, (struct sockaddr *) &addr, &addrlen); - } else { - uv_tcp_getsockname(&remote->handle.tcp, (struct sockaddr *) &addr, &addrlen); - } + uv_tcp_getsockname(&client->handle.tcp, (struct sockaddr *) &addr, &addrlen); if (addr.sa_family == AF_INET6) { buf[3] = 0x04; /* ATYP - IPv6. */ const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)&addr; @@ -151,10 +147,6 @@ handshake(struct client_context *client) { static void request_start(struct client_context *client, char *req_buf) { struct socks5_request *req = (struct socks5_request *)req_buf; - struct remote_context *remote = client->remote; - - assert(remote->stage == XSTAGE_FORWARD); - client->cmd = req->cmd; if (req->cmd != S5_CMD_CONNECT && req->cmd != S5_CMD_UDP_ASSOCIATE) { @@ -168,6 +160,7 @@ request_start(struct client_context *client, char *req_buf) { return; } + char host[256] = {0}; char buf[260] = {0}; size_t buflen; uint16_t portlen = 2; @@ -189,9 +182,9 @@ request_start(struct client_context *client, char *req_buf) { memcpy(buf + 1, req->addr, in_addr_len); memcpy(buf + 1 + in_addr_len, req->addr + in_addr_len, portlen); - uv_inet_ntop(AF_INET, (const void *)(req->addr), client->target_addr, INET_ADDRSTRLEN); + uv_inet_ntop(AF_INET, (const void *)(req->addr), host, INET_ADDRSTRLEN); uint16_t port = read_size((uint8_t*)(req->addr + in_addr_len)); - sprintf(client->target_addr, "%s:%u", client->target_addr, port); + sprintf(client->target_addr, "%s:%u", host, port); } else if (req->atyp == ATYP_HOST) { uint8_t namelen = *(uint8_t *)(req->addr); @@ -216,9 +209,9 @@ request_start(struct client_context *client, char *req_buf) { memcpy(buf + 1, req->addr, in6_addr_len); memcpy(buf + 1 + in6_addr_len, req->addr + in6_addr_len, portlen); - uv_inet_ntop(AF_INET6, (const void *)(req->addr), client->target_addr, INET_ADDRSTRLEN); + uv_inet_ntop(AF_INET6, (const void *)(req->addr), host, INET6_ADDRSTRLEN); uint16_t port = read_size((uint8_t*)(req->addr + in6_addr_len)); - sprintf(client->target_addr, "%s:%u", client->target_addr, port); + sprintf(client->target_addr, "%s:%u", host, port); } else { logger_log(LOG_ERR, "unsupported address type: 0x%02x", req->atyp); @@ -228,18 +221,44 @@ request_start(struct client_context *client, char *req_buf) { request_ack(client, S5_REP_SUCCESSED); - // TODO: handle UDP ASSOCIATE - if (req->cmd == S5_CMD_CONNECT) { + if (req->cmd == S5_CMD_UDP_ASSOCIATE) { + return; + } + + int direct = 0; + struct sockaddr addr; + memset(&addr, 0, sizeof addr); + + if ((acl && (req->atyp == 1 || req->atyp == 4) && acl_contains_ip(host))) { if (verbose) { - logger_log(LOG_INFO, "connect to %s", client->target_addr); + logger_log(LOG_WARNING, "bypass %s", client->target_addr); } - int clen = buflen + PRIMITIVE_BYTES; - uint8_t *c = client->buf + HEADER_BYTES; - int rc = crypto_encrypt(c, (uint8_t *)buf, buflen); - if (!rc) { - forward_to_remote(remote, c, clen); + direct = 1; + int rc = resolve_addr(client->target_addr, &addr); + if (rc) { + logger_stderr("invalid target address"); + request_ack(client, S5_REP_ADDRESS_TYPE_NOT_SUPPORTED); + return; } } + + client->buflen = buflen; + memcpy(req_buf, buf, buflen); + + struct remote_context *remote = new_remote(idle_timeout, direct ? &addr : NULL); + remote->direct = direct; + remote->client = client; + client->remote = remote; + + uv_timer_init(client->handle.stream.loop, remote->timer); + uv_tcp_init(client->handle.stream.loop, &remote->handle.tcp); + + if (verbose && !direct) { + logger_log(LOG_INFO, "connect to %s", client->target_addr); + } + + reset_timer(remote); + connect_to_remote(remote); } static void @@ -286,7 +305,6 @@ client_recv_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { int clen; if (nread > 0) { - reset_timer(remote); uv_read_stop(&client->handle.stream); switch (client->stage) { @@ -296,7 +314,6 @@ client_recv_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { } else { logger_log(LOG_ERR, "invalid method packet"); close_client(client); - close_remote(remote); } break; @@ -307,21 +324,27 @@ client_recv_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { } else { logger_log(LOG_ERR, "invalid request packet"); close_client(client); - close_remote(remote); } break; case XSTAGE_FORWARD: - clen = nread + PRIMITIVE_BYTES; - uint8_t *c = client->buf + HEADER_BYTES; - int rc = crypto_encrypt(c, (uint8_t*)buf->base, nread); - if (rc) { - logger_log(LOG_ERR, "encrypt failed"); - close_client(client); - close_remote(remote); + reset_timer(remote); + + if (remote->direct) { + forward_to_remote(remote, (uint8_t*)buf->base, nread); + + } else { + clen = nread + PRIMITIVE_BYTES; + uint8_t *c = client->buf + HEADER_BYTES; + int rc = crypto_encrypt(c, (uint8_t*)buf->base, nread); + if (rc) { + logger_log(LOG_ERR, "encrypt failed"); + close_client(client); + close_remote(remote); + } + forward_to_remote(remote, c, clen); } - forward_to_remote(remote, c, clen); break; @@ -343,26 +366,14 @@ client_recv_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { void client_accept_cb(uv_stream_t *server, int status) { struct client_context *client = new_client(); - struct remote_context *remote = new_remote(idle_timeout); - - client->remote = remote; - remote->client = client; - - uv_timer_init(server->loop, remote->timer); - uv_tcp_init(server->loop, &client->handle.tcp); - uv_tcp_init(server->loop, &remote->handle.tcp); - - int rc = uv_accept(server, &client->handle.stream); if (rc == 0) { int namelen = sizeof client->addr; uv_tcp_getpeername(&client->handle.tcp, &client->addr, &namelen); - reset_timer(remote); // start timer - connect_to_remote(remote); + receive_from_client(client); } else { logger_log(LOG_ERR, "accept error: %s", uv_strerror(rc)); close_client(client); - close_remote(remote); } } diff --git a/src/xsocks_remote.c b/src/xsocks_remote.c index 6f55140..1a87d3d 100644 --- a/src/xsocks_remote.c +++ b/src/xsocks_remote.c @@ -51,12 +51,13 @@ timer_close_cb(uv_handle_t *handle) { } struct remote_context * -new_remote(uint16_t timeout) { +new_remote(uint16_t timeout, struct sockaddr *addr) { struct remote_context *remote = malloc(sizeof(*remote)); memset(remote, 0, sizeof(*remote)); remote->stage = XSTAGE_HANDSHAKE; remote->timer = malloc(sizeof(uv_timer_t)); remote->idle_timeout = timeout; + remote->addr = addr ? *addr : server_addr; return remote; } @@ -101,13 +102,26 @@ close_remote(struct remote_context *remote) { uv_close(&remote->handle.handle, remote_close_cb); } +static void +forward_client_request_packet(struct remote_context *remote, struct client_context *client) { + if (!remote->direct) { + int clen = client->buflen + PRIMITIVE_BYTES; + uint8_t *c = client->buf + HEADER_BYTES; + int rc = crypto_encrypt(c, client->buf + OVERHEAD_BYTES, client->buflen); + if (!rc) { + forward_to_remote(remote, c, clen); + } + } +} + static void remote_connect_cb(uv_connect_t *req, int status) { struct remote_context *remote = (struct remote_context *)req->data; struct client_context *client = remote->client; - if (status == 0) { + forward_client_request_packet(remote, client); + remote->stage = XSTAGE_FORWARD; reset_timer(remote); receive_from_client(client); @@ -115,7 +129,7 @@ remote_connect_cb(uv_connect_t *req, int status) { } else { if (status != UV_ECANCELED) { - logger_log(LOG_ERR, "connect to server failed: %s", uv_strerror(status)); + logger_log(LOG_ERR, "connect to remote failed: %s", uv_strerror(status)); request_ack(client, S5_REP_HOST_UNREACHABLE); } } @@ -130,21 +144,30 @@ receive_from_remote(struct remote_context *remote) { void forward_to_remote(struct remote_context *remote, uint8_t *buf, int buflen) { - buf -= HEADER_BYTES; - write_size(buf, buflen); - buflen += HEADER_BYTES; - uv_buf_t data = uv_buf_init((char*)buf, buflen); - remote->write_req.data = remote; - uv_write(&remote->write_req, &remote->handle.stream, &data, 1, remote_send_cb); + uv_buf_t data; + + if (remote->direct) { + data = uv_buf_init((char*)buf, buflen); + remote->write_req.data = remote; + uv_write(&remote->write_req, &remote->handle.stream, &data, 1, remote_send_cb); + + } else { + buf -= HEADER_BYTES; + write_size(buf, buflen); + buflen += HEADER_BYTES; + data = uv_buf_init((char*)buf, buflen); + remote->write_req.data = remote; + uv_write(&remote->write_req, &remote->handle.stream, &data, 1, remote_send_cb); + } } void connect_to_remote(struct remote_context *remote) { remote->stage = XSTAGE_CONNECT; remote->connect_req.data = remote; - int rc = uv_tcp_connect(&remote->connect_req, &remote->handle.tcp, &server_addr, remote_connect_cb); + int rc = uv_tcp_connect(&remote->connect_req, &remote->handle.tcp, &remote->addr, remote_connect_cb); if (rc) { - logger_log(LOG_ERR, "connect to server error: %s", uv_strerror(rc)); + logger_log(LOG_ERR, "connect to remote error: %s", uv_strerror(rc)); request_ack(remote->client, S5_REP_NETWORK_UNREACHABLE); } } @@ -171,22 +194,28 @@ remote_recv_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { if (nread > 0) { reset_timer(remote); - struct packet *packet = &remote->packet; - int rc = packet_filter(packet, buf->base, nread); - if (rc == PACKET_COMPLETED) { - uint8_t *m = packet->buf; - int mlen = packet->size - PRIMITIVE_BYTES; - - int err = crypto_decrypt(m, packet->buf, packet->size); - if (err) { - goto error; - } - + if (remote->direct) { uv_read_stop(&remote->handle.stream); - forward_to_client(client, m, mlen); + forward_to_client(client, (uint8_t*)buf->base, nread); + + } else { + struct packet *packet = &remote->packet; + int rc = packet_filter(packet, buf->base, nread); + if (rc == PACKET_COMPLETED) { + uint8_t *m = packet->buf; + int mlen = packet->size - PRIMITIVE_BYTES; + + int err = crypto_decrypt(m, packet->buf, packet->size); + if (err) { + goto error; + } + + uv_read_stop(&remote->handle.stream); + forward_to_client(client, m, mlen); - } else if (rc == PACKET_INVALID) { - goto error; + } else if (rc == PACKET_INVALID) { + goto error; + } } } else if (nread < 0){