diff --git a/Package.swift b/Package.swift index f354a85..0f68cf1 100644 --- a/Package.swift +++ b/Package.swift @@ -7,11 +7,10 @@ let package = Package( products: [ .library(name: "Venice", targets: ["Venice"]) ], - dependencies: [ - .package(url: "https://github.com/Zewo/CLibdill.git", .branch("swift-4")) - ], targets: [ - .target(name: "Venice"), + .target(name: "CLibdill"), + .target(name: "Venice", dependencies: ["CLibdill"]), .testTarget(name: "VeniceTests", dependencies: ["Venice"]), ] ) + diff --git a/Sources/CLibdill/bsock.c b/Sources/CLibdill/bsock.c new file mode 100755 index 0000000..1dea08f --- /dev/null +++ b/Sources/CLibdill/bsock.c @@ -0,0 +1,61 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include + +#include "libdillimpl.h" +#include "utils.h" + +dill_unique_id(bsock_type); + +int bsend(int s, const void *buf, size_t len, int64_t deadline) { + struct bsock_vfs *b = hquery(s, bsock_type); + if(dill_slow(!b)) return -1; + struct iolist iol = {(void*)buf, len, NULL, 0}; + return b->bsendl(b, &iol, &iol, deadline); +} + +ssize_t brecv(int s, void *buf, size_t len, int64_t deadline) { + struct bsock_vfs *b = hquery(s, bsock_type); + if(dill_slow(!b)) return -1; + struct iolist iol = {buf, len, NULL, 0}; + return b->brecvl(b, &iol, &iol, deadline); +} + +int bsendl(int s, struct iolist *first, struct iolist *last, int64_t deadline) { + struct bsock_vfs *b = hquery(s, bsock_type); + if(dill_slow(!b)) return -1; + if(dill_slow(!first || !last || last->iol_next)) { + errno = EINVAL; return -1;} + return b->bsendl(b, first, last, deadline); +} + +ssize_t brecvl(int s, struct iolist *first, struct iolist *last, int64_t deadline) { + struct bsock_vfs *b = hquery(s, bsock_type); + if(dill_slow(!b)) return -1; + if(dill_slow((first && !last) || (!first && last) || last->iol_next)) { + errno = EINVAL; return -1;} + return b->brecvl(b, first, last, deadline); +} diff --git a/Sources/CLibdill/chan.c b/Sources/CLibdill/chan.c new file mode 100755 index 0000000..8464529 --- /dev/null +++ b/Sources/CLibdill/chan.c @@ -0,0 +1,283 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include + +#include "cr.h" +#include "libdillimpl.h" +#include "list.h" +#include "utils.h" + +struct dill_chan { + /* Table of virtual functions. */ + struct hvfs vfs; + /* The size of one element stored in the channel, in bytes. */ + size_t sz; + /* List of clauses wanting to receive from the channel. */ + struct dill_list in; + /* List of clauses wanting to send to the channel. */ + struct dill_list out; + /* 1 if hdone() has been called on this channel. 0 otherwise. */ + unsigned int done : 1; + /* 1 if the object was created with chmake_mem(). */ + unsigned int mem : 1; +}; + +/* Channel clause. */ +struct dill_chclause { + struct dill_clause cl; + /* An item in either the dill_chan::in or dill_chan::out list. */ + struct dill_list item; + void *val; +}; + +DILL_CT_ASSERT(sizeof(struct chmem) >= sizeof(struct dill_chan)); + +/******************************************************************************/ +/* Handle implementation. */ +/******************************************************************************/ + +static const int dill_chan_type_placeholder = 0; +static const void *dill_chan_type = &dill_chan_type_placeholder; +static void *dill_chan_query(struct hvfs *vfs, const void *type); +static void dill_chan_close(struct hvfs *vfs); +static int dill_chan_done(struct hvfs *vfs, int64_t deadline); + +/******************************************************************************/ +/* Channel creation and deallocation. */ +/******************************************************************************/ + +int chmake_mem(size_t itemsz, struct chmem *mem) { + if(dill_slow(!mem)) {errno = EINVAL; return -1;} + /* Returns ECANCELED if the coroutine is shutting down. */ + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + struct dill_chan *ch = (struct dill_chan*)mem; + ch->vfs.query = dill_chan_query; + ch->vfs.close = dill_chan_close; + ch->vfs.done = dill_chan_done; + ch->sz = itemsz; + dill_list_init(&ch->in); + dill_list_init(&ch->out); + ch->done = 0; + ch->mem = 1; + /* Allocate a handle to point to the channel. */ + return hmake(&ch->vfs); +} + +int chmake(size_t itemsz) { + struct dill_chan *ch = malloc(sizeof(struct dill_chan)); + if(dill_slow(!ch)) {errno = ENOMEM; return -1;} + int h = chmake_mem(itemsz, (struct chmem*)ch); + if(dill_slow(h < 0)) { + int err = errno; + free(ch); + errno = err; + return -1; + } + ch->mem = 0; + return h; +} + +static void *dill_chan_query(struct hvfs *vfs, const void *type) { + if(dill_fast(type == dill_chan_type)) return vfs; + errno = ENOTSUP; + return NULL; +} + +static void dill_chan_close(struct hvfs *vfs) { + struct dill_chan *ch = (struct dill_chan*)vfs; + dill_assert(ch); + /* Resume any remaining senders and receivers on the channel + with the EPIPE error. */ + while(!dill_list_empty(&ch->in)) { + struct dill_chclause *chcl = dill_cont(dill_list_next(&ch->in), + struct dill_chclause, item); + dill_trigger(&chcl->cl, EPIPE); + } + while(!dill_list_empty(&ch->out)) { + struct dill_chclause *chcl = dill_cont(dill_list_next(&ch->out), + struct dill_chclause, item); + dill_trigger(&chcl->cl, EPIPE); + } + if(!ch->mem) free(ch); +} + +/******************************************************************************/ +/* Sending and receiving. */ +/******************************************************************************/ + +static void dill_chcancel(struct dill_clause *cl) { + struct dill_chclause *chcl = dill_cont(cl, struct dill_chclause, cl); + dill_list_erase(&chcl->item); +} + +int chsend(int h, const void *val, size_t len, int64_t deadline) { + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* Get the channel interface. */ + struct dill_chan *ch = hquery(h, dill_chan_type); + if(dill_slow(!ch)) return -1; + /* Check that the length provided matches the channel length */ + if(dill_slow(len != ch->sz)) {errno = EINVAL; return -1;} + /* Check if the channel is done. */ + if(dill_slow(ch->done)) {errno = EPIPE; return -1;} + /* Copy the message directly to the waiting receiver, if any. */ + if(!dill_list_empty(&ch->in)) { + struct dill_chclause *chcl = dill_cont(dill_list_next(&ch->in), + struct dill_chclause, item); + memcpy(chcl->val, val, len); + dill_trigger(&chcl->cl, 0); + return 0; + } + /* The clause is not available immediately. */ + if(dill_slow(deadline == 0)) {errno = ETIMEDOUT; return -1;} + /* Let's wait. */ + struct dill_chclause chcl; + dill_list_insert(&chcl.item, &ch->out); + chcl.val = (void*)val; + dill_waitfor(&chcl.cl, 0, dill_chcancel); + struct dill_tmclause tmcl; + dill_timer(&tmcl, 1, deadline); + int id = dill_wait(); + if(dill_slow(id < 0)) return -1; + if(dill_slow(id == 1)) {errno = ETIMEDOUT; return -1;} + if(dill_slow(errno != 0)) return -1; + return 0; +} + +int chrecv(int h, void *val, size_t len, int64_t deadline) { + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* Get the channel interface. */ + struct dill_chan *ch = hquery(h, dill_chan_type); + if(dill_slow(!ch)) return -1; + /* Check that the length provided matches the channel length */ + if(dill_slow(len != ch->sz)) {errno = EINVAL; return -1;} + /* Check whether the channel is done. */ + if(dill_slow(ch->done)) {errno = EPIPE; return -1;} + /* If there's a sender waiting, copy the message directly from the sender. */ + if(!dill_list_empty(&ch->out)) { + struct dill_chclause *chcl = dill_cont(dill_list_next(&ch->out), + struct dill_chclause, item); + memcpy(val, chcl->val, len); + dill_trigger(&chcl->cl, 0); + return 0; + } + /* The clause is not immediately available. */ + if(dill_slow(deadline == 0)) {errno = ETIMEDOUT; return -1;} + /* Let's wait. */ + struct dill_chclause chcl; + dill_list_insert(&chcl.item, &ch->in); + chcl.val = val; + dill_waitfor(&chcl.cl, 0, dill_chcancel); + struct dill_tmclause tmcl; + dill_timer(&tmcl, 1, deadline); + int id = dill_wait(); + if(dill_slow(id < 0)) return -1; + if(dill_slow(id == 1)) {errno = ETIMEDOUT; return -1;} + if(dill_slow(errno != 0)) return -1; + return 0; +} + +static int dill_chan_done(struct hvfs *vfs, int64_t deadline) { + struct dill_chan *ch = (struct dill_chan*)vfs; + dill_assert(ch); + if(ch->done) {errno = EPIPE; return -1;} + ch->done = 1; + /* Resume any remaining senders and receivers on the channel + with the EPIPE error. */ + while(!dill_list_empty(&ch->in)) { + struct dill_chclause *chcl = dill_cont(dill_list_next(&ch->in), + struct dill_chclause, item); + dill_trigger(&chcl->cl, EPIPE); + } + while(!dill_list_empty(&ch->out)) { + struct dill_chclause *chcl = dill_cont(dill_list_next(&ch->out), + struct dill_chclause, item); + dill_trigger(&chcl->cl, EPIPE); + } + return 0; +} + +int choose(struct chclause *clauses, int nclauses, int64_t deadline) { + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + if(dill_slow(nclauses < 0 || (nclauses != 0 && !clauses))) { + errno = EINVAL; return -1;} + int i; + for(i = 0; i != nclauses; ++i) { + struct chclause *cl = &clauses[i]; + struct dill_chan *ch = hquery(cl->ch, dill_chan_type); + if(dill_slow(!ch)) return i; + if(dill_slow(cl->len != ch->sz || (cl->len > 0 && !cl->val))) { + errno = EINVAL; return i;} + struct dill_chclause *chcl; + switch(cl->op) { + case CHSEND: + if(dill_slow(ch->done)) {errno = EPIPE; return i;} + if(dill_list_empty(&ch->in)) break; + chcl = dill_cont(dill_list_next(&ch->in), + struct dill_chclause, item); + memcpy(chcl->val, cl->val, cl->len); + dill_trigger(&chcl->cl, 0); + errno = 0; + return i; + case CHRECV: + if(dill_slow(ch->done)) {errno = EPIPE; return i;} + if(dill_list_empty(&ch->out)) break; + chcl = dill_cont(dill_list_next(&ch->out), + struct dill_chclause, item); + memcpy(cl->val, chcl->val, ch->sz); + dill_trigger(&chcl->cl, 0); + errno = 0; + return i; + default: + errno = EINVAL; + return i; + } + } + /* There are no clauses immediately available. */ + if(dill_slow(deadline == 0)) {errno = ETIMEDOUT; return -1;} + /* Let's wait. */ + struct dill_chclause chcls[nclauses]; + for(i = 0; i != nclauses; ++i) { + struct dill_chan *ch = hquery(clauses[i].ch, dill_chan_type); + dill_assert(ch); + dill_list_insert(&chcls[i].item, + clauses[i].op == CHRECV ? &ch->in : &ch->out); + chcls[i].val = clauses[i].val; + dill_waitfor(&chcls[i].cl, i, dill_chcancel); + } + struct dill_tmclause tmcl; + dill_timer(&tmcl, nclauses, deadline); + int id = dill_wait(); + if(dill_slow(id < 0)) return -1; + if(dill_slow(id == nclauses)) {errno = ETIMEDOUT; return -1;} + return id; +} + diff --git a/Sources/CLibdill/cr.c b/Sources/CLibdill/cr.c new file mode 100755 index 0000000..870512a --- /dev/null +++ b/Sources/CLibdill/cr.c @@ -0,0 +1,556 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include + +#if defined DILL_VALGRIND +#include +#endif + +#include "cr.h" +#include "pollset.h" +#include "stack.h" +#include "utils.h" +#include "ctx.h" + +/******************************************************************************/ +/* Autorelease pool fix. */ +/******************************************************************************/ + +/* On darwin most foundation reference types leak memory due to a lack of a autorelease + pool. Because of this we need to push and pop the pool frequently during libmill's + context switches. This dynamically finds the objc runtime functions and inserts + the push/pop calls appropriately. If any part of the rest of the application adds its own + autorelease pool it must do so that it does not span across a libmill context switch + otherwise the objc runtime will throw a fatal error. */ +#ifdef __APPLE__ +#include +#include +#include +#include +#include + +static int runningTests = 0; + +static pthread_once_t darwin_prepare_once = PTHREAD_ONCE_INIT; +static pthread_key_t darwin_autoreleasepool_key; + +static Class (*objc_getClass_fptr)(const char *name); +static id (*objc_msgSend_fptr)(id self, SEL, ...); +static SEL (*sel_getUid_fptr)(const char *str); + +static void *(*objc_autoreleasePoolPush_fptr)(void); +static void (*objc_autoreleasePoolPop_fptr)(void *ctx); + +/* Prepare the darwin environment, this mainly looks up Objective-C runtime + functions and also determines whether or not the process is running from + XCTest. The autorelease pools will only be added if not running in XCTest + because XCTest runs it's own autorelease pool around each test and if we + insert our own we corrupt the runtime. It's hard to fix because of how + libdill switches contexts. */ + +static void darwin_prepare_once_routine(void) { + void *handle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL); + + if (handle) { + /* Using the dlsym find the objc runtime functions we need */ + objc_getClass_fptr = dlsym(handle, "objc_getClass"); + objc_msgSend_fptr = dlsym(handle, "objc_msgSend"); + sel_getUid_fptr = dlsym(handle, "sel_getUid"); + objc_autoreleasePoolPush_fptr = dlsym(handle, "objc_autoreleasePoolPush"); + objc_autoreleasePoolPop_fptr = dlsym(handle, "objc_autoreleasePoolPop"); + } + + /* This is a really elogated way to determine if we're running inside of XCTest. + Because some users may accidentally import XCTest into their binary we don't + just want to do class detection. + + This grabs [[[[NSProcessInfo processInfo] arguments] description] UTF8String] + and searches it for /Xcode/Agents/xctest OR /usr/bin/xctest OR PackageTests.xctest + in the future these searches may not be valid and need to be tweaked. */ + Class processInfoClass = objc_getClass_fptr("NSProcessInfo"); + + if (processInfoClass) { + id processInfo = objc_msgSend_fptr((id)processInfoClass, sel_getUid_fptr("processInfo")); + id arguments = objc_msgSend_fptr(processInfo, sel_getUid_fptr("arguments")); + id description = objc_msgSend_fptr(arguments, sel_getUid_fptr("description")); + const char *chars = (const char *)objc_msgSend_fptr(description, sel_getUid_fptr("UTF8String")); + + runningTests = (strstr(chars, "/Xcode/Agents/xctest") || + strstr(chars, "/usr/bin/xctest") || + strstr(chars, "PackageTests.xctest")); + } + + pthread_key_create(&darwin_autoreleasepool_key, NULL); +} + +void darwin_prepare() { + pthread_once(&darwin_prepare_once, darwin_prepare_once_routine); +} + +static inline void darwin_pool_pop_push() { + darwin_prepare(); + + if (runningTests) { + return; + } + + void *pool = pthread_getspecific(darwin_autoreleasepool_key); + if (pool) { + objc_autoreleasePoolPop_fptr(pool); + pthread_setspecific(darwin_autoreleasepool_key, NULL); + } + + pthread_setspecific(darwin_autoreleasepool_key, objc_autoreleasePoolPush_fptr()); +} +#endif + + +#if defined DILL_CENSUS + +/* When taking the stack size census, we will keep the maximum stack size in a list + indexed by the go() call, i.e., by file name and line number. */ +struct dill_census_item { + struct dill_slist crs; + const char *file; + int line; + size_t max_stack; +}; + +#endif + +/* Storage for the constant used by the go() macro. */ +volatile void *dill_unoptimisable = NULL; + +/******************************************************************************/ +/* Helpers. */ +/******************************************************************************/ + +static void dill_resume(struct dill_cr *cr, int id, int err) { +#ifdef __APPLE__ + darwin_pool_pop_push(); +#endif + + struct dill_ctx_cr *ctx = &dill_getctx->cr; + cr->id = id; + cr->err = err; + dill_qlist_push(&ctx->ready, &cr->ready); +} + +int dill_canblock(void) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + if(dill_slow(ctx->r->no_blocking1 || ctx->r->no_blocking2)) { + errno = ECANCELED; return -1;} + return 0; +} + +int dill_no_blocking(int val) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + int old = ctx->r->no_blocking2; + ctx->r->no_blocking2 = val; + return old; +} + +/******************************************************************************/ +/* Context. */ +/******************************************************************************/ + +int dill_ctx_cr_init(struct dill_ctx_cr *ctx) { + /* This function is definitely called from the main coroutine, given that + it's called only once and you can't even create a different coroutine + without calling it. */ + ctx->r = &ctx->main; + dill_qlist_init(&ctx->ready); + dill_rbtree_init(&ctx->timers); + /* We can't use now() here as the context is still being intialized. */ + ctx->last_poll = mnow(); + /* Initialize the main coroutine. */ + memset(&ctx->main, 0, sizeof(ctx->main)); + ctx->main.ready.next = NULL; + dill_slist_init(&ctx->main.clauses); +#if defined DILL_CENSUS + dill_slist_init(&ctx->census); +#endif + return 0; +} + +void dill_ctx_cr_term(struct dill_ctx_cr *ctx) { +#if defined DILL_CENSUS + struct dill_slist *it; + for(it = dill_slist_next(&ctx->census); it != &ctx->census; + it = dill_slist_next(it)) { + struct dill_census_item *ci = + dill_cont(it, struct dill_census_item, crs); + fprintf(stderr, "%s:%d - maximum stack size %zu B\n", + ci->file, ci->line, ci->max_stack); + } +#endif +} + +/******************************************************************************/ +/* Timers. */ +/******************************************************************************/ + +static void dill_timer_cancel(struct dill_clause *cl) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + struct dill_tmclause *tmcl = dill_cont(cl, struct dill_tmclause, cl); + dill_rbtree_erase(&ctx->timers, &tmcl->item); + /* This is a safeguard. If an item isn't properly removed from the rb-tree, + we can spot the fact by seeing that the cr has been set to NULL. */ + tmcl->cl.cr = NULL; +} + +/* Adds a timer clause to the list of clauses being waited on. */ +void dill_timer(struct dill_tmclause *tmcl, int id, int64_t deadline) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + /* If the deadline is infinite, there's nothing to wait for. */ + if(deadline < 0) return; + dill_rbtree_insert(&ctx->timers, deadline, &tmcl->item); + dill_waitfor(&tmcl->cl, id, dill_timer_cancel); +} + +/******************************************************************************/ +/* Handle implementation. */ +/******************************************************************************/ + +static const int dill_cr_type_placeholder = 0; +static const void *dill_cr_type = &dill_cr_type_placeholder; +static void *dill_cr_query(struct hvfs *vfs, const void *type); +static void dill_cr_close(struct hvfs *vfs); + +/******************************************************************************/ +/* Coroutine creation and termination */ +/******************************************************************************/ + +static void dill_cancel(struct dill_cr *cr, int err); + +/* The initial part of go(). Allocates a new stack and handle. */ +int dill_prologue(sigjmp_buf **jb, void **ptr, size_t len, + const char *file, int line) { +#ifdef __APPLE__ + darwin_pool_pop_push(); +#endif + + struct dill_ctx_cr *ctx = &dill_getctx->cr; + /* Return ECANCELED if shutting down. */ + int rc = dill_canblock(); + if(dill_slow(rc < 0)) {errno = ECANCELED; return -1;} + struct dill_cr *cr; + size_t stacksz; + if(!*ptr) { + /* Allocate a new stack. */ + cr = (struct dill_cr*)dill_allocstack(&stacksz); + if(dill_slow(!cr)) return -1; + } + else { + /* The stack is supplied by the user. + Align the top of the stack to a 16-byte boundary. */ + uintptr_t top = (uintptr_t)*ptr; + top += len; + top &= ~(uintptr_t)15; + stacksz = top - (uintptr_t)*ptr; + cr = (struct dill_cr*)top; + if(dill_slow(stacksz < sizeof(struct dill_cr))) { + errno = ENOMEM; return -1;} + } +#if defined DILL_CENSUS + /* Mark the bytes in the stack as unused. */ + uint8_t *bottom = ((char*)cr) - stacksz; + int i; + for(i = 0; i != stacksz; ++i) + bottom[i] = 0xa0 + (i % 13); +#endif + --cr; + cr->vfs.query = dill_cr_query; + cr->vfs.close = dill_cr_close; + cr->vfs.done = NULL; + int hndl = hmake(&cr->vfs); + if(dill_slow(hndl < 0)) { + int err = errno; dill_freestack(cr + 1); errno = err; return -1;} + cr->cls = NULL; + cr->ready.next = NULL; + dill_slist_init(&cr->clauses); + cr->closer = NULL; + cr->no_blocking1 = 0; + cr->no_blocking2 = 0; + cr->done = 0; + cr->mem = *ptr ? 1 : 0; +#if defined DILL_VALGRIND + cr->sid = VALGRIND_STACK_REGISTER((char*)(cr + 1) - stacksz, cr); +#endif +#if defined DILL_CENSUS + /* Find the appropriate census item if it exists. It's O(n) but meh. */ + cr->census = NULL; + struct dill_slist *it; + for(it = dill_slist_next(&ctx->census); it != &ctx->census; + it = dill_slist_next(it)) { + cr->census = dill_cont(it, struct dill_census_item, crs); + if(cr->census->line == line && strcmp(cr->census->file, file) == 0) + break; + } + /* Allocate it if it does not exist. */ + if(it == &ctx->census) { + cr->census = malloc(sizeof(struct dill_census_item)); + dill_assert(cr->census); + dill_slist_push(&ctx->census, &cr->census->crs); + cr->census->file = file; + cr->census->line = line; + cr->census->max_stack = 0; + } + cr->stacksz = stacksz - sizeof(struct dill_cr); +#endif + /* Return the context of the parent coroutine to the caller so that it can + store its current state. It can't be done here because we are at the + wrong stack frame here. */ + *jb = &ctx->r->ctx; + /* Add parent coroutine to the list of coroutines ready for execution. */ + dill_resume(ctx->r, 0, 0); + /* Mark the new coroutine as running. */ + *ptr = ctx->r = cr; + return hndl; +} + +/* The final part of go(). Gets called when the coroutine is finished. */ +void dill_epilogue(void) { +#ifdef __APPLE__ + darwin_pool_pop_push(); +#endif + + struct dill_ctx_cr *ctx = &dill_getctx->cr; + /* Mark the coroutine as finished. */ + ctx->r->done = 1; + /* If there's a coroutine waiting for us to finish, unblock it now. */ + if(ctx->r->closer) + dill_cancel(ctx->r->closer, 0); + /* With no clauses added, this call will never return. */ + dill_assert(dill_slist_empty(&ctx->r->clauses)); + dill_wait(); +} +static void *dill_cr_query(struct hvfs *vfs, const void *type) { + if(dill_slow(type != dill_cr_type)) {errno = ENOTSUP; return NULL;} + struct dill_cr *cr = dill_cont(vfs, struct dill_cr, vfs); + return cr; +} + +/* Gets called when coroutine handle is closed. */ +static void dill_cr_close(struct hvfs *vfs) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + struct dill_cr *cr = dill_cont(vfs, struct dill_cr, vfs); + /* If the coroutine has already finished, we are done. */ + if(!cr->done) { + /* No blocking calls from this point on. */ + cr->no_blocking1 = 1; + /* Resume the coroutine if it was blocked. */ + if(!cr->ready.next) + dill_cancel(cr, ECANCELED); + /* Wait for the coroutine to stop executing. With no clauses added, + the only mechanism to resume is through dill_cancel(). This is not really + a blocking call, although it looks like one. Given that the coroutine + that is being shut down is not permitted to block, we should get + control back pretty quickly. */ + cr->closer = ctx->r; + int rc = dill_wait(); + dill_assert(rc == -1 && errno == 0); + } +#if defined DILL_CENSUS + /* Find the first overwritten byte on the stack. + Determine stack usage based on that. */ + uint8_t *bottom = ((uint8_t*)cr) - cr->stacksz; + int i; + for(i = 0; i != cr->stacksz; ++i) { + if(bottom[i] != 0xa0 + (i % 13)) { + /* dill_cr is located on the stack so we have to take that into account. + Also, it may be necessary to align the top of the stack to + a 16-byte boundary, so add 16 bytes to account for that. */ + size_t used = cr->stacksz - i - sizeof(struct dill_cr) + 16; + if(used > cr->census->max_stack) + cr->census->max_stack = used; + break; + } + } +#endif +#if defined DILL_VALGRIND + VALGRIND_STACK_DEREGISTER(cr->sid); +#endif + /* Now that the coroutine is finished, deallocate it. */ + if(!cr->mem) dill_freestack(cr + 1); +} + +/******************************************************************************/ +/* Suspend/resume functionality. */ +/******************************************************************************/ + +void dill_waitfor(struct dill_clause *cl, int id, + void (*cancel)(struct dill_clause *cl)) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + /* Add a clause to the coroutine list of active clauses. */ + cl->cr = ctx->r; + dill_slist_push(&ctx->r->clauses, &cl->item); + cl->id = id; + cl->cancel = cancel; +} + +int dill_wait(void) { +#ifdef __APPLE__ + darwin_pool_pop_push(); +#endif + + struct dill_ctx_cr *ctx = &dill_getctx->cr; + /* Store the context of the current coroutine, if any. */ + if(dill_setjmp(ctx->r->ctx)) { + /* We get here once the coroutine is resumed. */ + dill_slist_init(&ctx->r->clauses); + errno = ctx->r->err; + return ctx->r->id; + } + /* For performance reasons, we want to avoid excessive checking of current + time, so we cache the value here. It will be recomputed only after + a blocking call. */ + int64_t nw = now(); + /* Wait for timeouts and external events. However, if there are ready + coroutines there's no need to poll for external events every time. + Still, we'll do it at least once a second. The external signal may + very well be a deadline or a user-issued command that cancels the CPU + intensive operation. */ + if(dill_qlist_empty(&ctx->ready) || nw > ctx->last_poll + 1000) { + int block = dill_qlist_empty(&ctx->ready); + while(1) { + /* Compute the timeout for the subsequent poll. */ + int timeout = 0; + if(block) { + if(dill_rbtree_empty(&ctx->timers)) + timeout = -1; + else { + int64_t deadline = dill_cont( + dill_rbtree_first(&ctx->timers), + struct dill_tmclause, item)->item.val; + timeout = (int) (nw >= deadline ? 0 : deadline - nw); + } + } + /* Wait for events. */ + int fired = dill_pollset_poll(timeout); + if(timeout != 0) nw = now(); + if(dill_slow(fired < 0)) continue; + /* Fire all expired timers. */ + if(!dill_rbtree_empty(&ctx->timers)) { + while(!dill_rbtree_empty(&ctx->timers)) { + struct dill_tmclause *tmcl = dill_cont( + dill_rbtree_first(&ctx->timers), + struct dill_tmclause, item); + if(tmcl->item.val > nw) + break; + dill_trigger(&tmcl->cl, ETIMEDOUT); + fired = 1; + } + } + /* Never retry the poll when in non-blocking mode. */ + if(!block || fired) + break; + /* If the timeout was hit but there were no expired timers, do the poll + again. It can happen if the timers were canceled in the + meantime. */ + } + ctx->last_poll = nw; + } + /* There's a coroutine ready to be executed so jump to it. */ + struct dill_slist *it = dill_qlist_pop(&ctx->ready); + it->next = NULL; + ctx->r = dill_cont(it, struct dill_cr, ready); + /* dill_longjmp has to be at the end of a function body, otherwise stack + unwinding information will be trimmed if a crash occurs in this + function. */ + dill_longjmp(ctx->r->ctx); + +#ifdef __APPLE__ + darwin_pool_pop_push(); +#endif + + return 0; +} + +static void dill_docancel(struct dill_cr *cr, int id, int err) { + /* Sanity check: Make sure that the coroutine was really suspended. */ + dill_assert(!cr->ready.next); + /* Remove the clauses from endpoints' lists of waiting coroutines. */ + struct dill_slist *it; + for(it = dill_slist_next(&cr->clauses); it != &cr->clauses; + it = dill_slist_next(it)) { + struct dill_clause *cl = dill_cont(it, struct dill_clause, item); + if(cl->cancel) cl->cancel(cl); + } + /* Schedule the newly unblocked coroutine for execution. */ + dill_resume(cr, id, err); +} + +void dill_trigger(struct dill_clause *cl, int err) { + dill_docancel(cl->cr, cl->id, err); +} + +static void dill_cancel(struct dill_cr *cr, int err) { + dill_docancel(cr, -1, err); +} + +int yield(void) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* Put the current coroutine into the ready queue. */ + dill_resume(ctx->r, 0, 0); + /* Suspend. */ + return dill_wait(); +} + +void* clsget(void) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + struct dill_cr *cr = ctx->r; + return cr->cls; +} + +void clsset(void *cls) { + struct dill_ctx_cr *ctx = &dill_getctx->cr; + struct dill_cr *cr = ctx->r; + cr->cls = cls; +} + +int co(void **ptr, + size_t len, + void *fn, + const char *file, + int line, + void (*routine)(int, void *)) { + sigjmp_buf *ctx; + void *stk = (ptr); + int h = dill_prologue(&ctx, &stk, len, file, line); + + if(h >= 0) { + if(!dill_setjmp(*ctx)) { + DILL_SETSP(stk); + routine(h, fn); + dill_epilogue(); + } + } + + return h; +} diff --git a/Sources/CLibdill/cr.h b/Sources/CLibdill/cr.h new file mode 100755 index 0000000..9d198cb --- /dev/null +++ b/Sources/CLibdill/cr.h @@ -0,0 +1,164 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_CR_INCLUDED +#define DILL_CR_INCLUDED + +#include + +#include "libdillimpl.h" +#include "qlist.h" +#include "rbtree.h" +#include "slist.h" + +/* The coroutine. The memory layout looks like this: + +-------------------------------------------------------------+---------+ + | stack | dill_cr | + +-------------------------------------------------------------+---------+ + - dill_cr contains generic book-keeping info about the coroutine + - the stack is a standard C stack; it grows downwards (at the moment, libdill + doesn't support microarchitectures where stacks grow upwards) +*/ +struct dill_cr { + /* When the coroutine is ready for execution but not running yet, + it lives on this list (ctx->ready). 'id' is the result value to return + from dill_wait() when the coroutine is resumed. Additionally, errno + will be set to 'err'. */ + struct dill_slist ready; + /* Virtual function table. */ + struct hvfs vfs; + int id; + int err; + /* When the coroutine is suspended 'ctx' holds the context + (registers and such).*/ + sigjmp_buf ctx; + /* If the coroutine is blocked, here's the list of the clauses it's waiting on. */ + struct dill_slist clauses; + /* There are two possible reasons to disable blocking calls. + 1. The coroutine is being closed by its owner. + 2. The execution is happening within the context of an hclose() call. */ + unsigned int no_blocking1 : 1; + unsigned int no_blocking2 : 1; + /* Set when the coroutine has finished its execution. */ + unsigned int done : 1; + /* If true, the coroutine was launched with go_mem. */ + unsigned int mem : 1; + /* When the coroutine handle is being closed, this points to the + coroutine that is doing the hclose() call. */ + struct dill_cr *closer; +#if defined DILL_VALGRIND + /* Valgrind stack identifier. This way, valgrind knows which areas of + memory are used as stacks, and so it doesn't produce spurious warnings. + Well, sort of. The mechanism is not perfect, but it's still better + than nothing. */ + int sid; +#endif +#if defined DILL_CENSUS + /* Census record corresponding to this coroutine. */ + struct dill_census_item *census; + size_t stacksz; +#endif + /* Local storage pointer */ + void *cls; +/* Clang assumes that the client stack is aligned to 16-bytes on x86-64 + architectures. To achieve this, we align this structure (with the added + benefit of a minor optimization). */ +} __attribute__((aligned(16))); + +struct dill_ctx_cr { + /* Currently running coroutine. */ + struct dill_cr *r; + /* List of coroutines ready for execution. */ + struct dill_qlist ready; + /* All active timers. */ + struct dill_rbtree timers; + /* Last time poll was performed. */ + int64_t last_poll; + /* The main coroutine. We don't control the creation of the main coroutine's stack, + so we have to store this info here instead of the top of the stack. */ + struct dill_cr main; +#if defined DILL_CENSUS + struct dill_slist census; +#endif +}; + +struct dill_clause { + /* The coroutine that owns this clause. */ + struct dill_cr *cr; + /* List of the clauses the coroutine is waiting on. See dill_cr::clauses. */ + struct dill_slist item; + /* Number to return from dill_wait() if this clause triggers. */ + int id; + /* Function to call when this clause is canceled. */ + void (*cancel)(struct dill_clause *cl); +}; + +/* Timer clause. */ +struct dill_tmclause { + struct dill_clause cl; + /* An item in dill_ctx_cr::timers. */ + struct dill_rbtree_item item; +}; + +/* File descriptor clause. */ +struct dill_fdclause; + +int dill_ctx_cr_init(struct dill_ctx_cr *ctx); +void dill_ctx_cr_term(struct dill_ctx_cr *ctx); + +/* When dill_wait() is called next time, the coroutine will wait + (among other clauses) on this clause. 'id' must not be negative. + 'cancel' is a function to be called when the clause is canceled + without being triggered. */ +void dill_waitfor(struct dill_clause *cl, int id, + void (*cancel)(struct dill_clause *cl)); + +/* Suspend running coroutine. Move to executing different coroutines. + The coroutine will be resumed once one of the clauses previously added by + dill_waitfor() is triggered. When that happens, all the clauses, whether + triggered or not, will be canceled. The function returns the ID of the triggered + clause or -1 on error. In either case, it sets errno to 0 indicate + success or non-zero value to indicate error. */ +int dill_wait(void); + +/* Schedule a previously suspended coroutine for execution. Keep in mind that this + doesn't immediately run it, it just puts it into the coroutine ready queue. + It will cause dill_wait() to return the id supplied in dill_waitfor(). */ +void dill_trigger(struct dill_clause *cl, int err); + +/* Add a timer to the list of active clauses. */ +void dill_timer(struct dill_tmclause *tmcl, int id, int64_t deadline); + +/* Returns 0 if blocking functions are allowed. + Returns -1 and sets errno to ECANCELED otherwise. */ +int dill_canblock(void); + +/* When set to 1, blocking calls return ECANCELED. + Returns the old value of the flag */ +int dill_no_blocking(int val); + +/* Cleans cached info about the fd. */ +int dill_clean(int fd); + +#endif + + diff --git a/Sources/CLibdill/ctx.c b/Sources/CLibdill/ctx.c new file mode 100755 index 0000000..3ef7e37 --- /dev/null +++ b/Sources/CLibdill/ctx.c @@ -0,0 +1,194 @@ +/* + + Copyright (c) 2016 Tai Chi Minh Ralph Eastwood + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include "ctx.h" + +#if !defined DILL_THREADS + +struct dill_ctx dill_ctx_ = {0}; + +static void dill_ctx_atexit(void) { + dill_ctx_pollset_term(&dill_ctx_.pollset); + dill_ctx_stack_term(&dill_ctx_.stack); + dill_ctx_handle_term(&dill_ctx_.handle); + dill_ctx_cr_term(&dill_ctx_.cr); + dill_ctx_now_term(&dill_ctx_.now); +} + +struct dill_ctx *dill_ctx_init(void) { + int rc = dill_ctx_now_init(&dill_ctx_.now); + dill_assert(rc == 0); + rc = dill_ctx_cr_init(&dill_ctx_.cr); + dill_assert(rc == 0); + rc = dill_ctx_handle_init(&dill_ctx_.handle); + dill_assert(rc == 0); + rc = dill_ctx_stack_init(&dill_ctx_.stack); + dill_assert(rc == 0); + rc = dill_ctx_pollset_init(&dill_ctx_.pollset); + dill_assert(rc == 0); + rc = atexit(dill_ctx_atexit); + dill_assert(rc == 0); + dill_ctx_.initialized = 1; + return &dill_ctx_; +} + +#else + +#include + +/* Determine whether current thread is the main thread. */ +#if defined __linux__ +#define _GNU_SOURCE +#include +#include +static int dill_ismain() { + return syscall(SYS_gettid) == getpid(); +} +#elif defined __OpenBSD__ || defined __FreeBSD__ || \ + defined __APPLE__ || defined __DragonFly__ +#if defined __FreeBSD__ +#include +#endif +static int dill_ismain() { + return pthread_main_np(); +} +#elif defined __NetBSD__ +#include +static int dill_ismain() { + return _lwp_self() == 1; +} +#elif defined __sun +static int dill_ismain() { + return pthread_self() == 1; +} +#else +#error "Cannot determine which thread is the main thread." +#endif + +#if defined __GNUC__ && !defined DILL_THREAD_FALLBACK + +__thread struct dill_ctx dill_ctx_ = {0}; + +static pthread_key_t dill_key; +static pthread_once_t dill_keyonce = PTHREAD_ONCE_INIT; +static void *dill_main = NULL; + +static void dill_ctx_term(void *ptr) { + struct dill_ctx *ctx = ptr; + dill_ctx_pollset_term(&ctx->pollset); + dill_ctx_stack_term(&ctx->stack); + dill_ctx_handle_term(&ctx->handle); + dill_ctx_cr_term(&ctx->cr); + dill_ctx_now_term(&ctx->now); + if(dill_ismain()) dill_main = NULL; +} + +static void dill_ctx_atexit(void) { + if(dill_main) dill_ctx_term(dill_main); +} + +static void dill_makekey(void) { + int rc = pthread_key_create(&dill_key, dill_ctx_term); + dill_assert(!rc); +} + +struct dill_ctx *dill_ctx_init(void) { + int rc = dill_ctx_now_init(&dill_ctx_.now); + dill_assert(rc == 0); + rc = dill_ctx_cr_init(&dill_ctx_.cr); + dill_assert(rc == 0); + rc = dill_ctx_handle_init(&dill_ctx_.handle); + dill_assert(rc == 0); + rc = dill_ctx_stack_init(&dill_ctx_.stack); + dill_assert(rc == 0); + rc = dill_ctx_pollset_init(&dill_ctx_.pollset); + dill_assert(rc == 0); + rc = pthread_once(&dill_keyonce, dill_makekey); + dill_assert(rc == 0); + if(dill_ismain()) { + dill_main = &dill_ctx_; + rc = atexit(dill_ctx_atexit); + dill_assert(rc == 0); + } + rc = pthread_setspecific(dill_key, &dill_ctx_); + dill_assert(rc == 0); + dill_ctx_.initialized = 1; + return &dill_ctx_; +} + +#else + +static pthread_key_t dill_key; +static pthread_once_t dill_keyonce = PTHREAD_ONCE_INIT; +static void *dill_main = NULL; + +static void dill_ctx_term(void *ptr) { + struct dill_ctx *ctx = ptr; + dill_ctx_pollset_term(&ctx->pollset); + dill_ctx_stack_term(&ctx->stack); + dill_ctx_handle_term(&ctx->handle); + dill_ctx_cr_term(&ctx->cr); + dill_ctx_now_term(&ctx->now); + free(ctx); + if(dill_ismain()) dill_main = NULL; +} + +static void dill_ctx_atexit(void) { + if(dill_main) dill_ctx_term(dill_main); +} + +static void dill_makekey(void) { + int rc = pthread_key_create(&dill_key, dill_ctx_term); + dill_assert(!rc); +} + +struct dill_ctx *dill_getctx_(void) { + int rc = pthread_once(&dill_keyonce, dill_makekey); + dill_assert(rc == 0); + struct dill_ctx *ctx = pthread_getspecific(dill_key); + if(dill_fast(ctx)) return ctx; + ctx = malloc(sizeof(struct dill_ctx)); + dill_assert(ctx); + rc = dill_ctx_now_init(&ctx->now); + dill_assert(rc == 0); + rc = dill_ctx_cr_init(&ctx->cr); + dill_assert(rc == 0); + rc = dill_ctx_handle_init(&ctx->handle); + dill_assert(rc == 0); + rc = dill_ctx_stack_init(&ctx->stack); + dill_assert(rc == 0); + rc = dill_ctx_pollset_init(&ctx->pollset); + dill_assert(rc == 0); + if(dill_ismain()) { + dill_main = ctx; + rc = atexit(dill_ctx_atexit); + dill_assert(rc == 0); + } + rc = pthread_setspecific(dill_key, ctx); + dill_assert(rc == 0); + return ctx; +} + +#endif + +#endif + diff --git a/Sources/CLibdill/ctx.h b/Sources/CLibdill/ctx.h new file mode 100755 index 0000000..4824c80 --- /dev/null +++ b/Sources/CLibdill/ctx.h @@ -0,0 +1,66 @@ +/* + + Copyright (c) 2016 Tai Chi Minh Ralph Eastwood + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_CTX_INCLUDED +#define DILL_CTX_INCLUDED + +#include "libdill.h" +#include "cr.h" +#include "handle.h" +#include "now.h" +#include "pollset.h" +#include "stack.h" + +struct dill_ctx { +#if !defined DILL_THREAD_FALLBACK + int initialized; +#endif + struct dill_ctx_now now; + struct dill_ctx_cr cr; + struct dill_ctx_handle handle; + struct dill_ctx_stack stack; + struct dill_ctx_pollset pollset; +}; + +struct dill_ctx *dill_ctx_init(void); + +#if !defined DILL_THREADS + +extern struct dill_ctx dill_ctx_; +#define dill_getctx \ + (dill_fast(dill_ctx_.initialized) ? &dill_ctx_ : dill_ctx_init()) + +#elif defined __GNUC__ && !defined DILL_THREAD_FALLBACK + +extern __thread struct dill_ctx dill_ctx_; +#define dill_getctx \ + (dill_fast(dill_ctx_.initialized) ? &dill_ctx_ : dill_ctx_init()) + +#else + +struct dill_ctx *dill_getctx_(void); +#define dill_getctx (dill_getctx_()) + +#endif + +#endif + diff --git a/Sources/CLibdill/dns/dns.c b/Sources/CLibdill/dns/dns.c new file mode 100755 index 0000000..630dcef --- /dev/null +++ b/Sources/CLibdill/dns/dns.c @@ -0,0 +1,9131 @@ +/* ========================================================================== + * dns.c - Recursive, Reentrant DNS Resolver. + * -------------------------------------------------------------------------- + * Copyright (c) 2008, 2009, 2010, 2012, 2013, 2014, 2015 William Ahern + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * ========================================================================== + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if !defined(__FreeBSD__) && !defined(__sun) +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#if defined(__FreeBSD__) +#define HAVE_STRUCT_SOCKADDR_SA_LEN +#endif + +#undef _BSD_SOURCE +#define _BSD_SOURCE + +#undef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE + +#undef _NETBSD_SOURCE +#define _NETBSD_SOURCE +#endif + +#include /* INT_MAX */ +#include /* offsetof() */ +#ifdef _WIN32 +#define uint32_t unsigned int +#else +#include /* uint32_t */ +#endif +#include /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */ +#include /* FILE fopen(3) fclose(3) getc(3) rewind(3) */ +#include /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */ +#include /* strcasecmp(3) strncasecmp(3) */ +#include /* isspace(3) isdigit(3) */ +#include /* time_t time(2) difftime(3) */ +#include /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */ +#include /* errno EINVAL ENOENT */ +#undef NDEBUG +#include /* assert(3) */ + +#if _WIN32 +#ifndef FD_SETSIZE +#define FD_SETSIZE 256 +#endif +#include +#include +#else +#include /* FD_SETSIZE socklen_t */ +#include /* FD_ZERO FD_SET fd_set select(2) */ +#include /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */ +#if defined(AF_UNIX) +#include /* struct sockaddr_un */ +#endif +#include /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */ +#include /* _POSIX_THREADS gethostname(3) close(2) */ +#include /* POLLIN POLLOUT */ +#include /* struct sockaddr_in struct sockaddr_in6 */ +#include /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */ +#include /* struct addrinfo */ +#endif + +#include "dns.h" + + +/* + * C O M P I L E R A N N O T A T I O N S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_GNUC_PREREQ(M, m) \ + (defined __GNUC__ && ((__GNUC__ > M) || (__GNUC__ == M && __GNUC_MINOR__ >= m))) + +#ifndef HAVE_PRAGMA_MESSAGE +#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4, 4) || __clang__ || _MSC_VER) +#endif + +#if __GNUC__ +#define DNS_NOTUSED __attribute__((unused)) +#define DNS_NORETURN __attribute__((noreturn)) +#else +#define DNS_NOTUSED +#define DNS_NORETURN +#endif + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#elif DNS_GNUC_PREREQ(4, 6) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + +/* + * S T A N D A R D M A C R O S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef lengthof +#define lengthof(a) (sizeof (a) / sizeof (a)[0]) +#endif + +#ifndef endof +#define endof(a) (&(a)[lengthof((a))]) +#endif + + +/* + * M I S C E L L A N E O U S C O M P A T + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if _WIN32 || _WIN64 +#define PRIuZ "Iu" +#else +#define PRIuZ "zu" +#endif + +#ifndef DNS_THREAD_SAFE +#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0 +#define DNS_THREAD_SAFE 1 +#else +#define DNS_THREAD_SAFE 0 +#endif +#endif + + +/* + * D E B U G M A C R O S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +int dns_debug = 0; + +#if DNS_DEBUG + +#undef DNS_DEBUG +#define DNS_DEBUG dns_debug + +#define DNS_SAY_(fmt, ...) \ + do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0) +#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n") +#define DNS_HAI DNS_SAY("HAI") + +#define DNS_SHOW_(P, fmt, ...) do { \ + if (DNS_DEBUG > 1) { \ + fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \ + fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \ + dns_p_dump((P), stderr); \ + fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \ + } \ +} while (0) + +#define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "") + +#else /* !DNS_DEBUG */ + +#undef DNS_DEBUG +#define DNS_DEBUG 0 + +#define DNS_SAY(...) +#define DNS_HAI +#define DNS_SHOW(...) + +#endif /* DNS_DEBUG */ + + +/* + * V E R S I O N R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +const char *dns_vendor(void) { + return DNS_VENDOR; +} /* dns_vendor() */ + + +int dns_v_rel(void) { + return DNS_V_REL; +} /* dns_v_rel() */ + + +int dns_v_abi(void) { + return DNS_V_ABI; +} /* dns_v_abi() */ + + +int dns_v_api(void) { + return DNS_V_API; +} /* dns_v_api() */ + + +/* + * E R R O R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if _WIN32 + +#define DNS_EINTR WSAEINTR +#define DNS_EINPROGRESS WSAEINPROGRESS +#define DNS_EISCONN WSAEISCONN +#define DNS_EWOULDBLOCK WSAEWOULDBLOCK +#define DNS_EALREADY WSAEALREADY +#define DNS_EAGAIN EAGAIN +#define DNS_ETIMEDOUT WSAETIMEDOUT + +#define dns_syerr() ((int)GetLastError()) +#define dns_soerr() ((int)WSAGetLastError()) + +#else + +#define DNS_EINTR EINTR +#define DNS_EINPROGRESS EINPROGRESS +#define DNS_EISCONN EISCONN +#define DNS_EWOULDBLOCK EWOULDBLOCK +#define DNS_EALREADY EALREADY +#define DNS_EAGAIN EAGAIN +#define DNS_ETIMEDOUT ETIMEDOUT + +#define dns_syerr() errno +#define dns_soerr() errno + +#endif + + +const char *dns_strerror(int error) { + switch (error) { + case DNS_ENOBUFS: + return "DNS packet buffer too small"; + case DNS_EILLEGAL: + return "Illegal DNS RR name or data"; + case DNS_EORDER: + return "Attempt to push RR out of section order"; + case DNS_ESECTION: + return "Invalid section specified"; + case DNS_EUNKNOWN: + return "Unknown DNS error"; + case DNS_EADDRESS: + return "Invalid textual address form"; + case DNS_ENOQUERY: + return "Bad execution state (missing query packet)"; + case DNS_ENOANSWER: + return "Bad execution state (missing answer packet)"; + case DNS_EFETCHED: + return "Answer already fetched"; + case DNS_ESERVICE: + return "The service passed was not recognized for the specified socket type"; + case DNS_ENONAME: + return "The name does not resolve for the supplied parameters"; + case DNS_EFAIL: + return "A non-recoverable error occurred when attempting to resolve the name"; + default: + return strerror(error); + } /* switch() */ +} /* dns_strerror() */ + + +/* + * A T O M I C R O U T I N E S + * + * Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we + * can use the preprocessor to detect API and, more importantly, ISA + * support. We want to avoid linking headaches where the API depends on an + * external library if the ISA (e.g. i386) doesn't support lockless + * operation. + * + * TODO: Support C11's atomic API. Although that may require some finesse + * with how we define some public types, such as dns_atomic_t and struct + * dns_resolv_conf. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HAVE___ATOMIC_FETCH_ADD +#define HAVE___ATOMIC_FETCH_ADD (defined __ATOMIC_RELAXED) +#endif + +#ifndef HAVE___ATOMIC_FETCH_SUB +#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD +#endif + +#ifndef DNS_ATOMIC_FETCH_ADD +#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2 +#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED) +#else +#pragma message("no atomic_fetch_add available") +#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++) +#endif +#endif + +#ifndef DNS_ATOMIC_FETCH_SUB +#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2 +#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED) +#else +#pragma message("no atomic_fetch_sub available") +#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--) +#endif +#endif + +static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) { + return DNS_ATOMIC_FETCH_ADD(i); +} /* dns_atomic_fetch_add() */ + + +static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) { + return DNS_ATOMIC_FETCH_SUB(i); +} /* dns_atomic_fetch_sub() */ + + +/* + * C R Y P T O R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * P R N G + */ + +#ifndef DNS_RANDOM +#if defined(HAVE_ARC4RANDOM) \ + || defined(__OpenBSD__) \ + || defined(__FreeBSD__) \ + || defined(__NetBSD__) \ + || defined(__APPLE__) +#define DNS_RANDOM arc4random +#elif __linux +#define DNS_RANDOM random +#else +#define DNS_RANDOM rand +#endif +#endif + +#define DNS_RANDOM_arc4random 1 +#define DNS_RANDOM_random 2 +#define DNS_RANDOM_rand 3 +#define DNS_RANDOM_RAND_bytes 4 + +#define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM)) + +#if DNS_RANDOM_OPENSSL +#include +#endif + +static unsigned dns_random_(void) { +#if DNS_RANDOM_OPENSSL + unsigned r; + + assert(1 == RAND_bytes((unsigned char *)&r, sizeof r)); + + return r; +#else + return DNS_RANDOM(); +#endif +} /* dns_random_() */ + +unsigned (*dns_random)(void) __attribute__((weak)) = &dns_random_; + + +/* + * P E R M U T A T I O N G E N E R A T O R + */ + +#define DNS_K_TEA_KEY_SIZE 16 +#define DNS_K_TEA_BLOCK_SIZE 8 +#define DNS_K_TEA_CYCLES 32 +#define DNS_K_TEA_MAGIC 0x9E3779B9U + +struct dns_k_tea { + uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; + unsigned cycles; +}; /* struct dns_k_tea */ + + +static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) { + memcpy(tea->key, key, sizeof tea->key); + + tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES; +} /* dns_k_tea_init() */ + + +static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) { + uint32_t y, z, sum, n; + + y = v[0]; + z = v[1]; + sum = 0; + + for (n = 0; n < tea->cycles; n++) { + sum += DNS_K_TEA_MAGIC; + y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]); + z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]); + } + + w[0] = y; + w[1] = z; + + return /* void */; +} /* dns_k_tea_encrypt() */ + + +/* + * Permutation generator, based on a Luby-Rackoff Feistel construction. + * + * Specifically, this is a generic balanced Feistel block cipher using TEA + * (another block cipher) as the pseudo-random function, F. At best it's as + * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or + * perhaps Bernstein's Salsa20 core; I am naively trying to keep things + * simple. + * + * The generator can create a permutation of any set of numbers, as long as + * the size of the set is an even power of 2. This limitation arises either + * out of an inherent property of balanced Feistel constructions, or by my + * own ignorance. I'll tackle an unbalanced construction after I wrap my + * head around Schneier and Kelsey's paper. + * + * CAVEAT EMPTOR. IANAC. + */ +#define DNS_K_PERMUTOR_ROUNDS 8 + +struct dns_k_permutor { + unsigned stepi, length, limit; + unsigned shift, mask, rounds; + + struct dns_k_tea tea; +}; /* struct dns_k_permutor */ + + +static inline unsigned dns_k_permutor_powof(unsigned n) { + unsigned m, i = 0; + + for (m = 1; m < n; m <<= 1, i++) + ;; + + return i; +} /* dns_k_permutor_powof() */ + +static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) { + uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; + unsigned width, i; + + p->stepi = 0; + + p->length = (high - low) + 1; + p->limit = high; + + width = dns_k_permutor_powof(p->length); + width += width % 2; + + p->shift = width / 2; + p->mask = (1U << p->shift) - 1; + p->rounds = DNS_K_PERMUTOR_ROUNDS; + + for (i = 0; i < lengthof(key); i++) + key[i] = dns_random(); + + dns_k_tea_init(&p->tea, key, 0); + + return /* void */; +} /* dns_k_permutor_init() */ + + +static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) { + uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)]; + + memset(in, '\0', sizeof in); + + in[0] = k; + in[1] = x; + + dns_k_tea_encrypt(&p->tea, in, out); + + return p->mask & out[0]; +} /* dns_k_permutor_F() */ + + +static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) { + unsigned l[2], r[2]; + unsigned i; + + i = 0; + l[i] = p->mask & (n >> p->shift); + r[i] = p->mask & (n >> 0); + + do { + l[(i + 1) % 2] = r[i % 2]; + r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]); + + i++; + } while (i < p->rounds - 1); + + return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); +} /* dns_k_permutor_E() */ + + +DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) { + unsigned l[2], r[2]; + unsigned i; + + i = p->rounds - 1; + l[i % 2] = p->mask & (n >> p->shift); + r[i % 2] = p->mask & (n >> 0); + + do { + i--; + + r[i % 2] = l[(i + 1) % 2]; + l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]); + } while (i > 0); + + return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); +} /* dns_k_permutor_D() */ + + +static unsigned dns_k_permutor_step(struct dns_k_permutor *p) { + unsigned n; + + do { + n = dns_k_permutor_E(p, p->stepi++); + } while (n >= p->length); + + return n + (p->limit + 1 - p->length); +} /* dns_k_permutor_step() */ + + +/* + * Simple permutation box. Useful for shuffling rrsets from an iterator. + * Uses AES s-box to provide good diffusion. + * + * Seems to pass muster under runs test. + * + * $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done + * $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }' + * library(lawstat) + * runs.test(scan(file="/tmp/out")) + * EOF + */ +static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) { + static const unsigned char sbox[256] = + { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, + 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, + 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, + 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, + 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, + 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, + 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, + 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, + 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, + 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + unsigned char a, b; + unsigned i; + + a = 0xff & (n >> 0); + b = 0xff & (n >> 8); + + for (i = 0; i < 4; i++) { + a ^= 0xff & s; + a = sbox[a] ^ b; + b = sbox[b] ^ a; + s >>= 8; + } + + return ((0xff00 & (a << 8)) | (0x00ff & (b << 0))); +} /* dns_k_shuffle16() */ + + +/* + * U T I L I T Y R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_MAXINTERVAL 300 + +struct dns_clock { + time_t sample, elapsed; +}; /* struct dns_clock */ + +static void dns_begin(struct dns_clock *clk) { + clk->sample = time(0); + clk->elapsed = 0; +} /* dns_begin() */ + +static time_t dns_elapsed(struct dns_clock *clk) { + time_t curtime; + + if ((time_t)-1 == time(&curtime)) + return clk->elapsed; + + if (curtime > clk->sample) + clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL); + + clk->sample = curtime; + + return clk->elapsed; +} /* dns_elapsed() */ + + +DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) { + size_t n = 0; + + while (*src++ && n < m) + ++n; + + return n; +} /* dns_strnlen() */ + + +DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) { + size_t len = dns_strnlen(src, max), n; + + if (lim > 0) { + n = DNS_PP_MIN(lim - 1, len); + memcpy(dst, src, n); + dst[n] = '\0'; + } + + return len; +} /* dns_strnlcpy() */ + + +#define DNS_HAVE_SOCKADDR_UN (defined AF_UNIX && !defined _WIN32) + +static size_t dns_af_len(int af) { + static const size_t table[AF_MAX] = { + [AF_INET6] = sizeof (struct sockaddr_in6), + [AF_INET] = sizeof (struct sockaddr_in), +#if DNS_HAVE_SOCKADDR_UN + [AF_UNIX] = sizeof (struct sockaddr_un), +#endif + }; + + return table[af]; +} /* dns_af_len() */ + +#define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family) + +#define dns_sa_len(sa) dns_af_len(dns_sa_family(sa)) + + +#define DNS_SA_NOPORT &dns_sa_noport +static unsigned short dns_sa_noport; + +static unsigned short *dns_sa_port(int af, void *sa) { + switch (af) { + case AF_INET6: + return &((struct sockaddr_in6 *)sa)->sin6_port; + case AF_INET: + return &((struct sockaddr_in *)sa)->sin_port; + default: + return DNS_SA_NOPORT; + } +} /* dns_sa_port() */ + + +static void *dns_sa_addr(int af, void *sa, socklen_t *size) { + switch (af) { + case AF_INET6: { + struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr; + + if (size) + *size = sizeof *in6; + + return in6; + } + case AF_INET: { + struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr; + + if (size) + *size = sizeof *in; + + return in; + } + default: + if (size) + *size = 0; + + return 0; + } +} /* dns_sa_addr() */ + + +#if DNS_HAVE_SOCKADDR_UN +#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path) +#endif + +DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) { + switch (dns_sa_family(sa)) { +#if DNS_HAVE_SOCKADDR_UN + case AF_UNIX: { + char *path = ((struct sockaddr_un *)sa)->sun_path; + + if (size) + *size = dns_strnlen(path, DNS_SUNPATHMAX); + + return path; + } +#endif + default: + if (size) + *size = 0; + + return NULL; + } +} /* dns_sa_path() */ + + +static int dns_sa_cmp(void *a, void *b) { + int cmp, af; + + if ((cmp = dns_sa_family(a) - dns_sa_family(b))) + return cmp; + + switch ((af = dns_sa_family(a))) { + case AF_INET: { + struct in_addr *a4, *b4; + + if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) + return cmp; + + a4 = dns_sa_addr(af, a, NULL); + b4 = dns_sa_addr(af, b, NULL); + + if (ntohl(a4->s_addr) < ntohl(b4->s_addr)) + return -1; + if (ntohl(a4->s_addr) > ntohl(b4->s_addr)) + return 1; + + return 0; + } + case AF_INET6: { + struct in6_addr *a6, *b6; + size_t i; + + if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) + return cmp; + + a6 = dns_sa_addr(af, a, NULL); + b6 = dns_sa_addr(af, b, NULL); + + /* XXX: do we need to use in6_clearscope()? */ + for (i = 0; i < sizeof a6->s6_addr; i++) { + if ((cmp = a6->s6_addr[i] - b6->s6_addr[i])) + return cmp; + } + + return 0; + } +#if DNS_HAVE_SOCKADDR_UN + case AF_UNIX: { + char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path]; + + dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX); + dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX); + + return strcmp(a_path, b_path); + } +#endif + default: + return -1; + } +} /* dns_sa_cmp() */ + + +#if _WIN32 +static int dns_inet_pton(int af, const void *src, void *dst) { + union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; + + u.sin.sin_family = af; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + u.sin.sin_len = dns_af_len(af); +#endif + + if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u })) + return -1; + + switch (af) { + case AF_INET6: + *(struct in6_addr *)dst = u.sin6.sin6_addr; + + return 1; + case AF_INET: + *(struct in_addr *)dst = u.sin.sin_addr; + + return 1; + default: + return 0; + } +} /* dns_inet_pton() */ + +static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) { + union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; + + /* NOTE: WSAAddressToString will print .sin_port unless zeroed. */ + memset(&u, 0, sizeof u); + + u.sin.sin_family = af; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + u.sin.sin_len = dns_af_len(af); +#endif + + switch (af) { + case AF_INET6: + u.sin6.sin6_addr = *(struct in6_addr *)src; + break; + case AF_INET: + u.sin.sin_addr = *(struct in_addr *)src; + + break; + default: + return 0; + } + + if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim)) + return 0; + + return dst; +} /* dns_inet_ntop() */ +#else +#define dns_inet_pton(...) inet_pton(__VA_ARGS__) +#define dns_inet_ntop(...) inet_ntop(__VA_ARGS__) +#endif + + +static dns_error_t dns_pton(int af, const void *src, void *dst) { + switch (dns_inet_pton(af, src, dst)) { + case 1: + return 0; + case -1: + return dns_soerr(); + default: + return DNS_EADDRESS; + } +} /* dns_pton() */ + + +static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) { + return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr(); +} /* dns_ntop() */ + + +size_t dns_strlcpy(char *dst, const char *src, size_t lim) { + char *d = dst; + char *e = &dst[lim]; + const char *s = src; + + if (d < e) { + do { + if ('\0' == (*d++ = *s++)) + return s - src - 1; + } while (d < e); + + d[-1] = '\0'; + } + + while (*s++ != '\0') + ;; + + return s - src - 1; +} /* dns_strlcpy() */ + + +size_t dns_strlcat(char *dst, const char *src, size_t lim) { + char *d = memchr(dst, '\0', lim); + char *e = &dst[lim]; + const char *s = src; + const char *p; + + if (d && d < e) { + do { + if ('\0' == (*d++ = *s++)) + return d - dst - 1; + } while (d < e); + + d[-1] = '\0'; + } + + p = s; + + while (*s++ != '\0') + ;; + + return lim + (s - p - 1); +} /* dns_strlcat() */ + + +#if _WIN32 + +static char *dns_strsep(char **sp, const char *delim) { + char *p; + + if (!(p = *sp)) + return 0; + + *sp += strcspn(p, delim); + + if (**sp != '\0') { + **sp = '\0'; + ++*sp; + } else + *sp = NULL; + + return p; +} /* dns_strsep() */ + +#else +#define dns_strsep(...) strsep(__VA_ARGS__) +#endif + + +#if _WIN32 +#define strcasecmp(...) _stricmp(__VA_ARGS__) +#define strncasecmp(...) _strnicmp(__VA_ARGS__) +#endif + + +static int dns_poll(int fd, short events, int timeout) { + fd_set rset, wset; + + if (!events) + return 0; + + assert(fd >= 0 && (unsigned)fd < FD_SETSIZE); + + FD_ZERO(&rset); + FD_ZERO(&wset); + + if (events & DNS_POLLIN) + FD_SET(fd, &rset); + + if (events & DNS_POLLOUT) + FD_SET(fd, &wset); + + select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL); + + return 0; +} /* dns_poll() */ + + +#if !_WIN32 +DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) { +#if DNS_THREAD_SAFE + return pthread_sigmask(how, set, oset); +#else + return (0 == sigprocmask(how, set, oset))? 0 : errno; +#endif +} /* dns_sigmask() */ +#endif + + +static long dns_send(int fd, const void *src, size_t lim, int flags) { +#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE + return send(fd, src, lim, flags); +#elif defined MSG_NOSIGNAL + return send(fd, src, lim, flags|MSG_NOSIGNAL); +#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */ + /* + * SIGPIPE handling similar to the approach described in + * http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html + */ + sigset_t pending, blocked, piped; + long count; + int saved, error; + + sigemptyset(&pending); + sigpending(&pending); + + if (!sigismember(&pending, SIGPIPE)) { + sigemptyset(&piped); + sigaddset(&piped, SIGPIPE); + sigemptyset(&blocked); + + if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked))) + goto error; + } + + count = send(fd, src, lim, flags); + + if (!sigismember(&pending, SIGPIPE)) { + saved = errno; + + if (count == -1 && errno == EPIPE) { + while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR) + ;; + } + + if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL))) + goto error; + + errno = saved; + } + + return count; +error: + errno = error; + + return -1; +#else +#error "unable to suppress SIGPIPE" + return send(fd, src, lim, flags); +#endif +} /* dns_send() */ + + +/* + * P A C K E T R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +unsigned dns_p_count(struct dns_packet *P, enum dns_section section) { + unsigned count; + + switch (section) { + case DNS_S_QD: + return ntohs(dns_header(P)->qdcount); + case DNS_S_AN: + return ntohs(dns_header(P)->ancount); + case DNS_S_NS: + return ntohs(dns_header(P)->nscount); + case DNS_S_AR: + return ntohs(dns_header(P)->arcount); + default: + count = 0; + + if (section & DNS_S_QD) + count += ntohs(dns_header(P)->qdcount); + if (section & DNS_S_AN) + count += ntohs(dns_header(P)->ancount); + if (section & DNS_S_NS) + count += ntohs(dns_header(P)->nscount); + if (section & DNS_S_AR) + count += ntohs(dns_header(P)->arcount); + + return count; + } +} /* dns_p_count() */ + + +struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) { + if (!P) + return 0; + + assert(size >= offsetof(struct dns_packet, data) + 12); + + memset(P, 0, sizeof *P); + P->size = size - offsetof(struct dns_packet, data); + P->end = 12; + + memset(P->data, '\0', 12); + + return P; +} /* dns_p_init() */ + + +static unsigned short dns_p_qend(struct dns_packet *P) { + unsigned short qend = 12; + unsigned i, count = dns_p_count(P, DNS_S_QD); + + for (i = 0; i < count && qend < P->end; i++) { + if (P->end == (qend = dns_d_skip(qend, P))) + goto invalid; + + if (P->end - qend < 4) + goto invalid; + + qend += 4; + } + + return DNS_PP_MIN(qend, P->end); +invalid: + return P->end; +} /* dns_p_qend() */ + + +struct dns_packet *dns_p_make(size_t len, int *error) { + struct dns_packet *P; + size_t size = dns_p_calcsize(len); + + if (!(P = dns_p_init(malloc(size), size))) + *error = dns_syerr(); + + return P; +} /* dns_p_make() */ + + +static void dns_p_free(struct dns_packet *P) { + free(P); +} /* dns_p_free() */ + + +/* convience routine to free any existing packet before storing new packet */ +static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { + dns_p_free(*dst); + + *dst = src; + + return src; +} /* dns_p_setptr() */ + + +static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) { + dns_p_setptr(dst, *src); + + *src = NULL; + + return *dst; +} /* dns_p_movptr() */ + + +int dns_p_grow(struct dns_packet **P) { + struct dns_packet *tmp; + size_t size; + int error; + + if (!*P) { + if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error))) + return error; + + return 0; + } + + size = dns_p_sizeof(*P); + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size++; + + if (size > 65536) + return DNS_ENOBUFS; + + if (!(tmp = realloc(*P, dns_p_calcsize(size)))) + return dns_syerr(); + + tmp->size = size; + *P = tmp; + + return 0; +} /* dns_p_grow() */ + + +struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) { + if (!P) + return 0; + + P->end = DNS_PP_MIN(P->size, P0->end); + + memcpy(P->data, P0->data, P->end); + + return P; +} /* dns_p_copy() */ + + +struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) { + size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0)); + struct dns_packet *M; + enum dns_section section; + struct dns_rr rr, mr; + int error, copy; + + if (!A && B) { + A = B; + Amask = Bmask; + B = 0; + } + +merge: + if (!(M = dns_p_make(bufsiz, &error))) + goto error; + + for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) { + if (A && (section & Amask)) { + dns_rr_foreach(&rr, A, .section = section) { + if ((error = dns_rr_copy(M, &rr, A))) + goto error; + } + } + + if (B && (section & Bmask)) { + dns_rr_foreach(&rr, B, .section = section) { + copy = 1; + + dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) { + if (!(copy = dns_rr_cmp(&rr, B, &mr, M))) + break; + } + + if (copy && (error = dns_rr_copy(M, &rr, B))) + goto error; + } + } + } + + return M; +error: + dns_p_setptr(&M, NULL); + + if (error == DNS_ENOBUFS && bufsiz < 65535) { + bufsiz = DNS_PP_MIN(65535, bufsiz * 2); + + goto merge; + } + + *error_ = error; + + return 0; +} /* dns_p_merge() */ + + +static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t); + +void dns_p_dictadd(struct dns_packet *P, unsigned short dn) { + unsigned short lp, lptr, i; + + lp = dn; + + while (lp < P->end) { + if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) { + lptr = ((0x3f & P->data[lp + 0]) << 8) + | ((0xff & P->data[lp + 1]) << 0); + + for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { + if (P->dict[i] == lptr) { + P->dict[i] = dn; + + return; + } + } + } + + lp = dns_l_skip(lp, P->data, P->end); + } + + for (i = 0; i < lengthof(P->dict); i++) { + if (!P->dict[i]) { + P->dict[i] = dn; + + break; + } + } +} /* dns_p_dictadd() */ + + +int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) { + size_t end = P->end; + int error; + + if ((error = dns_d_push(P, dn, dnlen))) + goto error; + + if (P->size - P->end < 4) + goto nobufs; + + P->data[P->end++] = 0xff & (type >> 8); + P->data[P->end++] = 0xff & (type >> 0); + + P->data[P->end++] = 0xff & (class >> 8); + P->data[P->end++] = 0xff & (class >> 0); + + if (section == DNS_S_QD) + goto update; + + if (P->size - P->end < 6) + goto nobufs; + + P->data[P->end++] = 0x7f & (ttl >> 24); + P->data[P->end++] = 0xff & (ttl >> 16); + P->data[P->end++] = 0xff & (ttl >> 8); + P->data[P->end++] = 0xff & (ttl >> 0); + + if ((error = dns_any_push(P, (union dns_any *)any, type))) + goto error; + +update: + switch (section) { + case DNS_S_QD: + if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR)) + goto order; + + if (!P->qd.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->qdcount = htons(ntohs(dns_header(P)->qdcount) + 1); + + P->qd.end = P->end; + P->an.base = P->end; + P->an.end = P->end; + P->ns.base = P->end; + P->ns.end = P->end; + P->ar.base = P->end; + P->ar.end = P->end; + + break; + case DNS_S_AN: + if (dns_p_count(P, DNS_S_NS|DNS_S_AR)) + goto order; + + if (!P->an.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->ancount = htons(ntohs(dns_header(P)->ancount) + 1); + + P->an.end = P->end; + P->ns.base = P->end; + P->ns.end = P->end; + P->ar.base = P->end; + P->ar.end = P->end; + + break; + case DNS_S_NS: + if (dns_p_count(P, DNS_S_AR)) + goto order; + + if (!P->ns.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->nscount = htons(ntohs(dns_header(P)->nscount) + 1); + + P->ns.end = P->end; + P->ar.base = P->end; + P->ar.end = P->end; + + break; + case DNS_S_AR: + if (!P->ar.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->arcount = htons(ntohs(dns_header(P)->arcount) + 1); + + P->ar.end = P->end; + + break; + default: + error = DNS_ESECTION; + + goto error; + } /* switch() */ + + return 0; +nobufs: + error = DNS_ENOBUFS; + + goto error; +order: + error = DNS_EORDER; + + goto error; +error: + P->end = end; + + return error; +} /* dns_p_push() */ + + +static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { + enum dns_section section; + struct dns_rr rr; + int error; + union dns_any any; + char pretty[sizeof any * 2]; + size_t len; + + fputs(";; [HEADER]\n", fp); + fprintf(fp, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); + fprintf(fp, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); + fprintf(fp, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); + fprintf(fp, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); + fprintf(fp, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); + fprintf(fp, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); + fprintf(fp, ";; rcode : %s(%d)\n", dns_strrcode(dns_header(P)->rcode), dns_header(P)->rcode); + + section = 0; + + while (dns_rr_grep(&rr, 1, I, P, &error)) { + if (section != rr.section) + fprintf(fp, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section)); + + if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error))) + fprintf(fp, "%s\n", pretty); + + section = rr.section; + } +} /* dns_p_dump3() */ + + +void dns_p_dump(struct dns_packet *P, FILE *fp) { + dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp); +} /* dns_p_dump() */ + + +static void dns_s_unstudy(struct dns_s_memo *m) + { m->base = 0; m->end = 0; } + +static void dns_p_unstudy(struct dns_packet *P) { + dns_s_unstudy(&P->qd); + dns_s_unstudy(&P->an); + dns_s_unstudy(&P->ns); + dns_s_unstudy(&P->ar); +} /* dns_p_unstudy() */ + +static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned base, struct dns_packet *P) { + unsigned short count, rp; + + count = dns_p_count(P, section); + + for (rp = base; count && rp < P->end; count--) + rp = dns_rr_skip(rp, P); + + m->base = base; + m->end = rp; + + return 0; +} /* dns_s_study() */ + +int dns_p_study(struct dns_packet *P) { + int error; + + if ((error = dns_s_study(&P->qd, DNS_S_QD, 12, P))) + goto error; + + if ((error = dns_s_study(&P->an, DNS_S_AN, P->qd.end, P))) + goto error; + + if ((error = dns_s_study(&P->ns, DNS_S_NS, P->an.end, P))) + goto error; + + if ((error = dns_s_study(&P->ar, DNS_S_AR, P->ns.end, P))) + goto error; + + return 0; +error: + dns_p_unstudy(P); + + return error; +} /* dns_p_study() */ + + +/* + * D O M A I N N A M E R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DNS_D_MAXPTRS +#define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */ +#endif + +static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) { + unsigned short len; + unsigned nptrs = 0; + +retry: + if (src >= end) + goto invalid; + + switch (0x03 & (data[src] >> 6)) { + case 0x00: + len = (0x3f & (data[src++])); + + if (end - src < len) + goto invalid; + + if (lim > 0) { + memcpy(dst, &data[src], DNS_PP_MIN(lim, len)); + + dst[DNS_PP_MIN(lim - 1, len)] = '\0'; + } + + *nxt = src + len; + + return len; + case 0x01: + goto invalid; + case 0x02: + goto invalid; + case 0x03: + if (++nptrs > DNS_D_MAXPTRS) + goto invalid; + + if (end - src < 2) + goto invalid; + + src = ((0x3f & data[src + 0]) << 8) + | ((0xff & data[src + 1]) << 0); + + goto retry; + } /* switch() */ + + /* NOT REACHED */ +invalid: + *nxt = end; + + return 0; +} /* dns_l_expand() */ + + +static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) { + unsigned short len; + + if (src >= end) + goto invalid; + + switch (0x03 & (data[src] >> 6)) { + case 0x00: + len = (0x3f & (data[src++])); + + if (end - src < len) + goto invalid; + + return (len)? src + len : end; + case 0x01: + goto invalid; + case 0x02: + goto invalid; + case 0x03: + return end; + } /* switch() */ + + /* NOT REACHED */ +invalid: + return end; +} /* dns_l_skip() */ + + +size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) { + unsigned char *dst = dst_; + const unsigned char *src = src_; + size_t dp = 0, sp = 0; + int lc; + + /* trim any leading dot(s) */ + while (sp < len && src[sp] == '.') + sp++; + + for (lc = 0; sp < len; lc = src[sp++]) { + /* trim extra dot(s) */ + if (src[sp] == '.' && lc == '.') + continue; + + if (dp < lim) + dst[dp] = src[sp]; + + dp++; + } + + if ((flags & DNS_D_ANCHOR) && lc != '.') { + if (dp < lim) + dst[dp] = '.'; + + dp++; + } + + if (lim > 0) + dst[DNS_PP_MIN(dp, lim - 1)] = '\0'; + + return dp; +} /* dns_d_trim() */ + + +char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) { + if (flags & DNS_D_TRIM) { + dns_d_trim(dst, lim, src, len, flags); + } if (flags & DNS_D_ANCHOR) { + dns_d_anchor(dst, lim, src, len); + } else { + memmove(dst, src, DNS_PP_MIN(lim, len)); + + if (lim > 0) + ((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0'; + } + + return dst; +} /* dns_d_init() */ + + +size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) { + if (len == 0) + return 0; + + memmove(dst, src, DNS_PP_MIN(lim, len)); + + if (((const char *)src)[len - 1] != '.') { + if (len < lim) + ((char *)dst)[len] = '.'; + len++; + } + + if (lim > 0) + ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; + + return len; +} /* dns_d_anchor() */ + + +size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) { + const char *dot; + + /* XXX: Skip any leading dot. Handles cleaving root ".". */ + if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1))) + return 0; + + len -= dot - (const char *)src; + + /* XXX: Unless root, skip the label's trailing dot. */ + if (len > 1) { + src = ++dot; + len--; + } else + src = dot; + + memmove(dst, src, DNS_PP_MIN(lim, len)); + + if (lim > 0) + ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; + + return len; +} /* dns_d_cleave() */ + + +size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) { + struct { unsigned char *b; size_t p, x; } dst, src; + unsigned char ch = '.'; + + dst.b = dst_; + dst.p = 0; + dst.x = 1; + + src.b = (unsigned char *)src_; + src.p = 0; + src.x = 0; + + while (src.x < len) { + ch = src.b[src.x]; + + if (ch == '.') { + if (dst.p < lim) + dst.b[dst.p] = (0x3f & (src.x - src.p)); + + dst.p = dst.x++; + src.p = ++src.x; + } else { + if (dst.x < lim) + dst.b[dst.x] = ch; + + dst.x++; + src.x++; + } + } /* while() */ + + if (src.x > src.p) { + if (dst.p < lim) + dst.b[dst.p] = (0x3f & (src.x - src.p)); + + dst.p = dst.x; + } + + if (dst.p > 1) { + if (dst.p < lim) + dst.b[dst.p] = 0x00; + + dst.p++; + } + +#if 1 + if (dst.p < lim) { + struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b; + unsigned i; + + a.p = 0; + + while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) { + for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { + b.p = P->dict[i]; + + while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) { + a.y = a.x; + b.y = b.x; + + while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) { + a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim); + b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end); + } + + if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) { + dst.b[a.p++] = 0xc0 + | (0x3f & (b.p >> 8)); + dst.b[a.p++] = (0xff & (b.p >> 0)); + + return a.p; + } + + b.p = b.x; + } /* while() */ + } /* for() */ + + a.p = a.x; + } /* while() */ + } /* if () */ +#endif + + if (!dst.p) + *error = DNS_EILLEGAL; + + return dst.p; +} /* dns_d_comp() */ + + +unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) { + unsigned short len; + + while (src < P->end) { + switch (0x03 & (P->data[src] >> 6)) { + case 0x00: /* FOLLOWS */ + len = (0x3f & P->data[src++]); + + if (0 == len) { +/* success ==> */ return src; + } else if (P->end - src > len) { + src += len; + + break; + } else + goto invalid; + + /* NOT REACHED */ + case 0x01: /* RESERVED */ + goto invalid; + case 0x02: /* RESERVED */ + goto invalid; + case 0x03: /* POINTER */ + if (P->end - src < 2) + goto invalid; + + src += 2; + +/* success ==> */ return src; + } /* switch() */ + } /* while() */ + +invalid: + return P->end; +} /* dns_d_skip() */ + + +#include + +size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) { + size_t dstp = 0; + unsigned nptrs = 0; + unsigned char len; + + while (src < P->end) { + switch ((0x03 & (P->data[src] >> 6))) { + case 0x00: /* FOLLOWS */ + len = (0x3f & P->data[src]); + + if (0 == len) { + if (dstp == 0) { + if (dstp < lim) + ((unsigned char *)dst)[dstp] = '.'; + + dstp++; + } + + /* NUL terminate */ + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; + +/* success ==> */ return dstp; + } + + src++; + + if (P->end - src < len) + goto toolong; + + if (dstp < lim) + memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp)); + + src += len; + dstp += len; + + if (dstp < lim) + ((unsigned char *)dst)[dstp] = '.'; + + dstp++; + + nptrs = 0; + + continue; + case 0x01: /* RESERVED */ + goto reserved; + case 0x02: /* RESERVED */ + goto reserved; + case 0x03: /* POINTER */ + if (++nptrs > DNS_D_MAXPTRS) + goto toolong; + + if (P->end - src < 2) + goto toolong; + + src = ((0x3f & P->data[src + 0]) << 8) + | ((0xff & P->data[src + 1]) << 0); + + continue; + } /* switch() */ + } /* while() */ + +toolong: + *error = DNS_EILLEGAL; + + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; + + return 0; +reserved: + *error = DNS_EILLEGAL; + + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; + + return 0; +} /* dns_d_expand() */ + + +int dns_d_push(struct dns_packet *P, const void *dn, size_t len) { + size_t lim = P->size - P->end; + unsigned dp = P->end; + int error = DNS_EILLEGAL; /* silence compiler */ + + len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error); + + if (len == 0) + return error; + if (len > lim) + return DNS_ENOBUFS; + + P->end += len; + + dns_p_dictadd(P, dp); + + return 0; +} /* dns_d_push() */ + + +size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) { + char host[DNS_D_MAXNAME + 1]; + struct dns_rr_i i; + struct dns_rr rr; + unsigned depth; + int error; + + if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len)) + { error = ENAMETOOLONG; goto error; } + + for (depth = 0; depth < 7; depth++) { + dns_rr_i_init(memset(&i, 0, sizeof i), P); + + i.section = DNS_S_ALL & ~DNS_S_QD; + i.name = host; + i.type = DNS_T_CNAME; + + if (!dns_rr_grep(&rr, 1, &i, P, &error)) + break; + + if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P))) + goto error; + } + + return dns_strlcpy(dst, host, lim); +error: + *error_ = error; + + return 0; +} /* dns_d_cname() */ + + +/* + * R E S O U R C E R E C O R D R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) { + unsigned char dn[DNS_D_MAXNAME + 1]; + union dns_any any; + size_t len; + int error; + + if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error))) + return error; + else if (len >= sizeof dn) + return DNS_EILLEGAL; + + if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q))) + return error; + + return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any); +} /* dns_rr_copy() */ + + +int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) { + unsigned short p = src; + + if (src >= P->end) + goto invalid; + + rr->dn.p = p; + rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p; + + if (P->end - p < 4) + goto invalid; + + rr->type = ((0xff & P->data[p + 0]) << 8) + | ((0xff & P->data[p + 1]) << 0); + + rr->class = ((0xff & P->data[p + 2]) << 8) + | ((0xff & P->data[p + 3]) << 0); + + p += 4; + + if (src < dns_p_qend(P)) { + rr->section = DNS_S_QUESTION; + + rr->ttl = 0; + rr->rd.p = 0; + rr->rd.len = 0; + + return 0; + } + + if (P->end - p < 4) + goto invalid; + + rr->ttl = ((0x7f & P->data[p + 0]) << 24) + | ((0xff & P->data[p + 1]) << 16) + | ((0xff & P->data[p + 2]) << 8) + | ((0xff & P->data[p + 3]) << 0); + + p += 4; + + if (P->end - p < 2) + goto invalid; + + rr->rd.len = ((0xff & P->data[p + 0]) << 8) + | ((0xff & P->data[p + 1]) << 0); + rr->rd.p = p + 2; + + p += 2; + + if (P->end - p < rr->rd.len) + goto invalid; + + return 0; +invalid: + return DNS_EILLEGAL; +} /* dns_rr_parse() */ + + +static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) { + unsigned short rp, rdlen; + + rp = dns_d_skip(src, P); + + if (P->end - rp < 4) + return P->end - src; + + rp += 4; /* TYPE, CLASS */ + + if (rp <= dns_p_qend(P)) + return rp - src; + + if (P->end - rp < 6) + return P->end - src; + + rp += 6; /* TTL, RDLEN */ + + rdlen = ((0xff & P->data[rp - 2]) << 8) + | ((0xff & P->data[rp - 1]) << 0); + + if (P->end - rp < rdlen) + return P->end - src; + + rp += rdlen; + + return rp - src; +} /* dns_rr_len() */ + + +unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) { + return src + dns_rr_len(src, P); +} /* dns_rr_skip() */ + + +static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) { + enum dns_section section; + unsigned count, index; + unsigned short rp; + + if (src >= P->qd.base && src < P->qd.end) + return DNS_S_QD; + if (src >= P->an.base && src < P->an.end) + return DNS_S_AN; + if (src >= P->ns.base && src < P->ns.end) + return DNS_S_NS; + if (src >= P->ar.base && src < P->ar.end) + return DNS_S_AR; + + /* NOTE: Possibly bad memoization. Try it the hard-way. */ + + for (rp = 12, index = 0; rp < src && rp < P->end; index++) + rp = dns_rr_skip(rp, P); + + section = DNS_S_QD; + count = dns_p_count(P, section); + + while (index >= count && section <= DNS_S_AR) { + section <<= 1; + count += dns_p_count(P, section); + } + + return DNS_S_ALL & section; +} /* dns_rr_section() */ + + +static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) { + struct dns_rr rr; + int error; + + if ((error = dns_rr_parse(&rr, src, P))) + return 0; + + return rr.type; +} /* dns_rr_type() */ + + +int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) { + char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1]; + union dns_any any0, any1; + int cmp, error; + size_t len; + + if ((cmp = r0->type - r1->type)) + return cmp; + + if ((cmp = r0->class - r1->class)) + return cmp; + + /* + * FIXME: Do label-by-label comparison to handle illegally long names? + */ + + if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error)) + || len >= sizeof host0) + return -1; + + if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error)) + || len >= sizeof host1) + return 1; + + if ((cmp = strcasecmp(host0, host1))) + return cmp; + + if (DNS_S_QD & (r0->section | r1->section)) { + if (r0->section == r1->section) + return 0; + + return (r0->section == DNS_S_QD)? -1 : 1; + } + + if ((error = dns_any_parse(&any0, r0, P0))) + return -1; + + if ((error = dns_any_parse(&any1, r1, P1))) + return 1; + + return dns_any_cmp(&any0, r0->type, &any1, r1->type); +} /* dns_rr_cmp() */ + + +static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) { + struct dns_rr rr1; + + dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) { + if (0 == dns_rr_cmp(rr0, P0, &rr1, P1)) + return 1; + } + + return 0; +} /* dns_rr_exists() */ + + +static unsigned short dns_rr_offset(struct dns_rr *rr) { + return rr->dn.p; +} /* dns_rr_offset() */ + + +static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) { + if (i->section && !(rr->section & i->section)) + return 0; + + if (i->type && rr->type != i->type && i->type != DNS_T_ALL) + return 0; + + if (i->class && rr->class != i->class && i->class != DNS_C_ANY) + return 0; + + if (i->name) { + char dn[DNS_D_MAXNAME + 1]; + size_t len; + int error; + + if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error)) + || len >= sizeof dn) + return 0; + + if (0 != strcasecmp(dn, i->name)) + return 0; + } + + if (i->data && i->type && rr->section > DNS_S_QD) { + union dns_any rd; + int error; + + if ((error = dns_any_parse(&rd, rr, P))) + return 0; + + if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type)) + return 0; + } + + return 1; +} /* dns_rr_i_match() */ + + +static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) { + unsigned short rp; + struct dns_rr r0, rr; + int error; + + if ((i->section & DNS_S_QD) && P->qd.base) + rp = P->qd.base; + else if ((i->section & DNS_S_AN) && P->an.base) + rp = P->an.base; + else if ((i->section & DNS_S_NS) && P->ns.base) + rp = P->ns.base; + else if ((i->section & DNS_S_AR) && P->ar.base) + rp = P->ar.base; + else + rp = 12; + + for (; rp < P->end; rp = dns_rr_skip(rp, P)) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + r0 = rr; + + goto lower; + } + + return P->end; +lower: + if (i->sort == &dns_rr_i_packet) + return dns_rr_offset(&r0); + + while ((rp = dns_rr_skip(rp, P)) < P->end) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + if (i->sort(&rr, &r0, i, P) < 0) + r0 = rr; + } + + return dns_rr_offset(&r0); +} /* dns_rr_i_start() */ + + +static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) { + struct dns_rr r0, r1, rr; + int error; + + if ((error = dns_rr_parse(&r0, rp, P))) + return P->end; + + r0.section = dns_rr_section(rp, P); + + rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12; + + for (; rp < P->end; rp = dns_rr_skip(rp, P)) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + if (i->sort(&rr, &r0, i, P) <= 0) + continue; + + r1 = rr; + + goto lower; + } + + return P->end; +lower: + if (i->sort == &dns_rr_i_packet) + return dns_rr_offset(&r1); + + while ((rp = dns_rr_skip(rp, P)) < P->end) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + if (i->sort(&rr, &r0, i, P) <= 0) + continue; + + if (i->sort(&rr, &r1, i, P) >= 0) + continue; + + r1 = rr; + } + + return dns_rr_offset(&r1); +} /* dns_rr_i_skip() */ + + +int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + (void)i; + (void)P; + + return (int)a->dn.p - (int)b->dn.p; +} /* dns_rr_i_packet() */ + + +int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + int cmp; + + (void)i; + + if ((cmp = a->section - b->section)) + return cmp; + + if (a->type != b->type) + return (int)a->dn.p - (int)b->dn.p; + + return dns_rr_cmp(a, P, b, P); +} /* dns_rr_i_order() */ + + +int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + int cmp; + + (void)i; + (void)P; + + while (!i->state.regs[0]) + i->state.regs[0] = dns_random(); + + if ((cmp = a->section - b->section)) + return cmp; + + return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]); +} /* dns_rr_i_shuffle() */ + + +struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *i, struct dns_packet *P) { + static const struct dns_rr_i i_initializer; + + (void)P; + + i->state = i_initializer.state; + i->saved = i->state; + + return i; +} /* dns_rr_i_init() */ + + +unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) { + unsigned count = 0; + int error; + + switch (i->state.exec) { + case 0: + if (!i->sort) + i->sort = &dns_rr_i_packet; + + i->state.next = dns_rr_i_start(i, P); + i->state.exec++; + + /* FALL THROUGH */ + case 1: + while (count < lim && i->state.next < P->end) { + if ((error = dns_rr_parse(rr, i->state.next, P))) + goto error; + + rr->section = dns_rr_section(i->state.next, P); + + rr++; + count++; + i->state.count++; + + i->state.next = dns_rr_i_skip(i->state.next, i, P); + } /* while() */ + + break; + } /* switch() */ + + return count; +error: + *error_ = error; + + return count; +} /* dns_rr_grep() */ + + +static size_t dns__printchar(void *dst, size_t lim, size_t cp, unsigned char ch) { + if (cp < lim) + ((unsigned char *)dst)[cp] = ch; + + return 1; +} /* dns__printchar() */ + + +static size_t dns__printstring(void *dst, size_t lim, size_t cp, const void *src, size_t len) { + if (cp < lim) + memcpy(&((unsigned char *)dst)[cp], src, DNS_PP_MIN(len, lim - cp)); + + return len; +} /* dns__printstring() */ + +#define dns__printstring5(a, b, c, d, e) dns__printstring((a), (b), (c), (d), (e)) +#define dns__printstring4(a, b, c, d) dns__printstring((a), (b), (c), (d), strlen((d))) +#define dns__printstring(...) DNS_PP_CALL(DNS_PP_XPASTE(dns__printstring, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + + +static void dns__printnul(void *dst, size_t lim, size_t off) { + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(off, lim - 1)] = '\0'; +} /* dns__printnul() */ + + +static size_t dns__print10(void *dst, size_t lim, size_t off, unsigned n, unsigned pad) { + unsigned char tmp[32]; + unsigned dp = off; + unsigned cp = 0; + unsigned d = 1000000000; + unsigned ch; + + pad = DNS_PP_MAX(1, pad); + + while (d) { + if ((ch = n / d) || cp > 0) { + n -= ch * d; + + tmp[cp] = '0' + ch; + + cp++; + } + + d /= 10; + } + + while (cp < pad) { + dp += dns__printchar(dst, lim, dp, '0'); + pad--; + } + + dp += dns__printstring(dst, lim, dp, tmp, cp); + + return dp - off; +} /* dns__print10() */ + + +size_t dns_rr_print(void *dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *error_) { + union dns_any any; + size_t cp, n, rdlen; + void *rd; + int error; + + cp = 0; + + if (rr->section == DNS_S_QD) + cp += dns__printchar(dst, lim, cp, ';'); + + if (!(n = dns_d_expand(&((unsigned char *)dst)[cp], (cp < lim)? lim - cp : 0, rr->dn.p, P, &error))) + goto error; + + cp += n; + + if (rr->section != DNS_S_QD) { + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, rr->ttl, 0); + } + + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__printstring(dst, lim, cp, dns_strclass(rr->class), strlen(dns_strclass(rr->class))); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__printstring(dst, lim, cp, dns_strtype(rr->type), strlen(dns_strtype(rr->type))); + + if (rr->section == DNS_S_QD) + goto epilog; + + cp += dns__printchar(dst, lim, cp, ' '); + + if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P))) + goto error; + + if (cp < lim) { + rd = &((unsigned char *)dst)[cp]; + rdlen = lim - cp; + } else { + rd = 0; + rdlen = 0; + } + + cp += dns_any_print(rd, rdlen, &any, rr->type); + +epilog: + dns__printnul(dst, lim, cp); + + return cp; +error: + *error_ = error; + + return 0; +} /* dns_rr_print() */ + + +int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) { + unsigned long addr; + + if (rr->rd.len != 4) + return DNS_EILLEGAL; + + addr = ((0xff & P->data[rr->rd.p + 0]) << 24) + | ((0xff & P->data[rr->rd.p + 1]) << 16) + | ((0xff & P->data[rr->rd.p + 2]) << 8) + | ((0xff & P->data[rr->rd.p + 3]) << 0); + + a->addr.s_addr = htonl(addr); + + return 0; +} /* dns_a_parse() */ + + +int dns_a_push(struct dns_packet *P, struct dns_a *a) { + unsigned long addr; + + if (P->size - P->end < 6) + return DNS_ENOBUFS; + + P->data[P->end++] = 0x00; + P->data[P->end++] = 0x04; + + addr = ntohl(a->addr.s_addr); + + P->data[P->end++] = 0xff & (addr >> 24); + P->data[P->end++] = 0xff & (addr >> 16); + P->data[P->end++] = 0xff & (addr >> 8); + P->data[P->end++] = 0xff & (addr >> 0); + + return 0; +} /* dns_a_push() */ + + +size_t dns_a_arpa(void *dst, size_t lim, const struct dns_a *a) { + unsigned long a4 = ntohl(a->addr.s_addr); + size_t cp = 0; + unsigned i; + + for (i = 4; i > 0; i--) { + cp += dns__print10(dst, lim, cp, (0xff & a4), 0); + cp += dns__printchar(dst, lim, cp, '.'); + a4 >>= 8; + } + + cp += dns__printstring(dst, lim, cp, "in-addr.arpa."); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_a_arpa() */ + + +int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) { + if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr)) + return -1; + if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr)) + return 1; + + return 0; +} /* dns_a_cmp() */ + + +size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) { + char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0"; + size_t len; + + dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr); + + dns__printnul(dst, lim, (len = dns__printstring(dst, lim, 0, addr))); + + return len; +} /* dns_a_print() */ + + +int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) { + if (rr->rd.len != sizeof aaaa->addr.s6_addr) + return DNS_EILLEGAL; + + memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr); + + return 0; +} /* dns_aaaa_parse() */ + + +int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) { + if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr) + return DNS_ENOBUFS; + + P->data[P->end++] = 0x00; + P->data[P->end++] = 0x10; + + memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr); + + P->end += sizeof aaaa->addr.s6_addr; + + return 0; +} /* dns_aaaa_push() */ + + +int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) { + unsigned i; + int cmp; + + for (i = 0; i < lengthof(a->addr.s6_addr); i++) { + if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i]))) + return cmp; + } + + return 0; +} /* dns_aaaa_cmp() */ + + +size_t dns_aaaa_arpa(void *dst, size_t lim, const struct dns_aaaa *aaaa) { + static const unsigned char hex[16] = "0123456789abcdef"; + size_t cp = 0; + unsigned nyble; + int i, j; + + for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) { + nyble = aaaa->addr.s6_addr[i]; + + for (j = 0; j < 2; j++) { + cp += dns__printchar(dst, lim, cp, hex[0x0f & nyble]); + cp += dns__printchar(dst, lim, cp, '.'); + nyble >>= 4; + } + } + + cp += dns__printstring(dst, lim, cp, "ip6.arpa."); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_aaaa_arpa() */ + + +size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) { + char addr[INET6_ADDRSTRLEN + 1] = "::"; + size_t len; + + dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr); + + dns__printnul(dst, lim, (len = dns__printstring(dst, lim, 0, addr))); + + return len; +} /* dns_aaaa_print() */ + + +int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) { + size_t len; + int error; + + if (rr->rd.len < 3) + return DNS_EILLEGAL; + + mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8)) + | (0x00ff & (P->data[rr->rd.p + 1] << 0)); + + if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error))) + return error; + else if (len >= sizeof mx->host) + return DNS_EILLEGAL; + + return 0; +} /* dns_mx_parse() */ + + +int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) { + size_t end, len; + int error; + + if (P->size - P->end < 5) + return DNS_ENOBUFS; + + end = P->end; + P->end += 2; + + P->data[P->end++] = 0xff & (mx->preference >> 8); + P->data[P->end++] = 0xff & (mx->preference >> 0); + + if ((error = dns_d_push(P, mx->host, strlen(mx->host)))) + goto error; + + len = P->end - end - 2; + + P->data[end + 0] = 0xff & (len >> 8); + P->data[end + 1] = 0xff & (len >> 0); + + return 0; +error: + P->end = end; + + return error; +} /* dns_mx_push() */ + + +int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) { + int cmp; + + if ((cmp = a->preference - b->preference)) + return cmp; + + return strcasecmp(a->host, b->host); +} /* dns_mx_cmp() */ + + +size_t dns_mx_print(void *dst, size_t lim, struct dns_mx *mx) { + size_t cp = 0; + + cp += dns__print10(dst, lim, cp, mx->preference, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__printstring(dst, lim, cp, mx->host, strlen(mx->host)); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_mx_print() */ + + +size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) { + return dns_strlcpy(dst, mx->host, lim); +} /* dns_mx_cname() */ + + +int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) { + size_t len; + int error; + + if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error))) + return error; + else if (len >= sizeof ns->host) + return DNS_EILLEGAL; + + return 0; +} /* dns_ns_parse() */ + + +int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) { + size_t end, len; + int error; + + if (P->size - P->end < 3) + return DNS_ENOBUFS; + + end = P->end; + P->end += 2; + + if ((error = dns_d_push(P, ns->host, strlen(ns->host)))) + goto error; + + len = P->end - end - 2; + + P->data[end + 0] = 0xff & (len >> 8); + P->data[end + 1] = 0xff & (len >> 0); + + return 0; +error: + P->end = end; + + return error; +} /* dns_ns_push() */ + + +int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) { + return strcasecmp(a->host, b->host); +} /* dns_ns_cmp() */ + + +size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) { + size_t cp; + + cp = dns__printstring(dst, lim, 0, ns->host, strlen(ns->host)); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_ns_print() */ + + +size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) { + return dns_strlcpy(dst, ns->host, lim); +} /* dns_ns_cname() */ + + +int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) { + return dns_ns_parse((struct dns_ns *)cname, rr, P); +} /* dns_cname_parse() */ + + +int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) { + return dns_ns_push(P, (struct dns_ns *)cname); +} /* dns_cname_push() */ + + +int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) { + return strcasecmp(a->host, b->host); +} /* dns_cname_cmp() */ + + +size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) { + return dns_ns_print(dst, lim, (struct dns_ns *)cname); +} /* dns_cname_print() */ + + +size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) { + return dns_strlcpy(dst, cname->host, lim); +} /* dns_cname_cname() */ + + +int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) { + struct { void *dst; size_t lim; } dn[] = + { { soa->mname, sizeof soa->mname }, + { soa->rname, sizeof soa->rname } }; + unsigned *ts[] = + { &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum }; + unsigned short rp; + unsigned i, j, n; + int error; + + /* MNAME / RNAME */ + if ((rp = rr->rd.p) >= P->end) + return DNS_EILLEGAL; + + for (i = 0; i < lengthof(dn); i++) { + if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error))) + return error; + else if (n >= dn[i].lim) + return DNS_EILLEGAL; + + if ((rp = dns_d_skip(rp, P)) >= P->end) + return DNS_EILLEGAL; + } + + /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ + for (i = 0; i < lengthof(ts); i++) { + for (j = 0; j < 4; j++, rp++) { + if (rp >= P->end) + return DNS_EILLEGAL; + + *ts[i] <<= 8; + *ts[i] |= (0xff & P->data[rp]); + } + } + + return 0; +} /* dns_soa_parse() */ + + +int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) { + void *dn[] = { soa->mname, soa->rname }; + unsigned ts[] = { (0xffffffff & soa->serial), + (0x7fffffff & soa->refresh), + (0x7fffffff & soa->retry), + (0x7fffffff & soa->expire), + (0xffffffff & soa->minimum) }; + unsigned i, j; + size_t end, len; + int error; + + end = P->end; + + if ((P->end += 2) >= P->size) + goto toolong; + + /* MNAME / RNAME */ + for (i = 0; i < lengthof(dn); i++) { + if ((error = dns_d_push(P, dn[i], strlen(dn[i])))) + goto error; + } + + /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ + for (i = 0; i < lengthof(ts); i++) { + if ((P->end += 4) >= P->size) + goto toolong; + + for (j = 1; j <= 4; j++) { + P->data[P->end - j] = (0xff & ts[i]); + ts[i] >>= 8; + } + } + + len = P->end - end - 2; + P->data[end + 0] = (0xff & (len >> 8)); + P->data[end + 1] = (0xff & (len >> 0)); + + return 0; +toolong: + error = DNS_ENOBUFS; + + /* FALL THROUGH */ +error: + P->end = end; + + return error; +} /* dns_soa_push() */ + + +int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) { + int cmp; + + if ((cmp = strcasecmp(a->mname, b->mname))) + return cmp; + + if ((cmp = strcasecmp(a->rname, b->rname))) + return cmp; + + if (a->serial > b->serial) + return -1; + else if (a->serial < b->serial) + return 1; + + if (a->refresh > b->refresh) + return -1; + else if (a->refresh < b->refresh) + return 1; + + if (a->retry > b->retry) + return -1; + else if (a->retry < b->retry) + return 1; + + if (a->expire > b->expire) + return -1; + else if (a->expire < b->expire) + return 1; + + if (a->minimum > b->minimum) + return -1; + else if (a->minimum < b->minimum) + return 1; + + return 0; +} /* dns_soa_cmp() */ + + +size_t dns_soa_print(void *dst, size_t lim, struct dns_soa *soa) { + size_t cp = 0; + + cp += dns__printstring(dst, lim, cp, soa->mname, strlen(soa->mname)); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__printstring(dst, lim, cp, soa->rname, strlen(soa->rname)); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, soa->serial, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, soa->refresh, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, soa->retry, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, soa->expire, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, soa->minimum, 0); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_soa_print() */ + + +int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) { + unsigned short rp; + unsigned i; + size_t n; + int error; + + memset(srv, '\0', sizeof *srv); + + rp = rr->rd.p; + + if (rr->rd.len < 7) + return DNS_EILLEGAL; + + for (i = 0; i < 2; i++, rp++) { + srv->priority <<= 8; + srv->priority |= (0xff & P->data[rp]); + } + + for (i = 0; i < 2; i++, rp++) { + srv->weight <<= 8; + srv->weight |= (0xff & P->data[rp]); + } + + for (i = 0; i < 2; i++, rp++) { + srv->port <<= 8; + srv->port |= (0xff & P->data[rp]); + } + + if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error))) + return error; + else if (n >= sizeof srv->target) + return DNS_EILLEGAL; + + return 0; +} /* dns_srv_parse() */ + + +int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) { + size_t end, len; + int error; + + end = P->end; + + if (P->size - P->end < 2) + goto toolong; + + P->end += 2; + + if (P->size - P->end < 6) + goto toolong; + + P->data[P->end++] = 0xff & (srv->priority >> 8); + P->data[P->end++] = 0xff & (srv->priority >> 0); + + P->data[P->end++] = 0xff & (srv->weight >> 8); + P->data[P->end++] = 0xff & (srv->weight >> 0); + + P->data[P->end++] = 0xff & (srv->port >> 8); + P->data[P->end++] = 0xff & (srv->port >> 0); + + if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error))) + goto error; + else if (P->size - P->end < len) + goto toolong; + + P->end += len; + + if (P->end > 65535) + goto toolong; + + len = P->end - end - 2; + + P->data[end + 0] = 0xff & (len >> 8); + P->data[end + 1] = 0xff & (len >> 0); + + return 0; +toolong: + error = DNS_ENOBUFS; + + /* FALL THROUGH */ +error: + P->end = end; + + return error; +} /* dns_srv_push() */ + + +int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) { + int cmp; + + if ((cmp = a->priority - b->priority)) + return cmp; + + /* + * FIXME: We need some sort of random seed to implement the dynamic + * weighting required by RFC 2782. + */ + if ((cmp = a->weight - b->weight)) + return cmp; + + if ((cmp = a->port - b->port)) + return cmp; + + return strcasecmp(a->target, b->target); +} /* dns_srv_cmp() */ + + +size_t dns_srv_print(void *dst, size_t lim, struct dns_srv *srv) { + size_t cp = 0; + + cp += dns__print10(dst, lim, cp, srv->priority, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, srv->weight, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__print10(dst, lim, cp, srv->port, 0); + cp += dns__printchar(dst, lim, cp, ' '); + cp += dns__printstring(dst, lim, cp, srv->target, strlen(srv->target)); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_srv_print() */ + + +size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) { + return dns_strlcpy(dst, srv->target, lim); +} /* dns_srv_cname() */ + + +unsigned int dns_opt_ttl(const struct dns_opt *opt) { + unsigned int ttl = 0; + + ttl |= (0xffU & opt->rcode) << 24U; + ttl |= (0xffU & opt->version) << 16U; + + return ttl; +} /* dns_opt_ttl() */ + + +unsigned short dns_opt_class(const struct dns_opt *opt) { + return opt->maxsize; +} /* dns_opt_class() */ + + +struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) { + assert(size >= offsetof(struct dns_opt, data)); + + opt->size = size - offsetof(struct dns_opt, data); + opt->len = 0; + + opt->rcode = 0; + opt->version = 0; + opt->maxsize = 512; + + return opt; +} /* dns_opt_init() */ + + +int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) { + (void)rr; + (void)P; + + opt->len = 0; + + return 0; +} /* dns_opt_parse() */ + + +int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) { + (void)P; + (void)opt; + + return 0; +} /* dns_opt_push() */ + + +int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) { + (void)a; + (void)b; + + return 0; +} /* dns_opt_cmp() */ + + +size_t dns_opt_print(void *dst, size_t lim, struct dns_opt *opt) { + size_t p = 0, src; + + p += dns__printchar(dst, lim, p, '"'); + + for (src = 0; src < opt->len; src++) { + p += dns__printchar(dst, lim, p, '\\'); + p += dns__print10(dst, lim, p, opt->data[src], 3); + } + + p += dns__printchar(dst, lim, p, '"'); + + dns__printnul(dst, lim, p); + + return p; +} /* dns_opt_print() */ + + +int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) { + return dns_ns_parse((struct dns_ns *)ptr, rr, P); +} /* dns_ptr_parse() */ + + +int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) { + return dns_ns_push(P, (struct dns_ns *)ptr); +} /* dns_ptr_push() */ + + +size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) { + unsigned len = (af == AF_INET6) + ? dns_aaaa_arpa(dst, lim, addr) + : dns_a_arpa(dst, lim, addr); + + dns__printnul(dst, lim, len); + + return len; +} /* dns_ptr_qname() */ + + +int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) { + return strcasecmp(a->host, b->host); +} /* dns_ptr_cmp() */ + + +size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) { + return dns_ns_print(dst, lim, (struct dns_ns *)ptr); +} /* dns_ptr_print() */ + + +size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) { + return dns_strlcpy(dst, ptr->host, lim); +} /* dns_ptr_cname() */ + + +int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) { + unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len; + + if (pe - p < 2) + return DNS_EILLEGAL; + + fp->algo = P->data[p++]; + fp->type = P->data[p++]; + + switch (fp->type) { + case DNS_SSHFP_SHA1: + if (pe - p < sizeof fp->digest.sha1) + return DNS_EILLEGAL; + + memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1); + + break; + default: + break; + } /* switch() */ + + return 0; +} /* dns_sshfp_parse() */ + + +int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) { + unsigned p = P->end, pe = P->size, n; + + if (pe - p < 4) + return DNS_ENOBUFS; + + p += 2; + P->data[p++] = 0xff & fp->algo; + P->data[p++] = 0xff & fp->type; + + switch (fp->type) { + case DNS_SSHFP_SHA1: + if (pe - p < sizeof fp->digest.sha1) + return DNS_ENOBUFS; + + memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1); + p += sizeof fp->digest.sha1; + + break; + default: + return DNS_EILLEGAL; + } /* switch() */ + + n = p - P->end - 2; + P->data[P->end++] = 0xff & (n >> 8); + P->data[P->end++] = 0xff & (n >> 0); + P->end = p; + + return 0; +} /* dns_sshfp_push() */ + + +int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) { + int cmp; + + if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type)) + return cmp; + + switch (a->type) { + case DNS_SSHFP_SHA1: + return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1); + default: + return 0; + } /* switch() */ + + /* NOT REACHED */ +} /* dns_sshfp_cmp() */ + + +size_t dns_sshfp_print(void *dst, size_t lim, struct dns_sshfp *fp) { + static const unsigned char hex[16] = "0123456789abcdef"; + size_t i, p = 0; + + p += dns__print10(dst, lim, p, fp->algo, 0); + p += dns__printchar(dst, lim, p, ' '); + p += dns__print10(dst, lim, p, fp->type, 0); + p += dns__printchar(dst, lim, p, ' '); + + switch (fp->type) { + case DNS_SSHFP_SHA1: + for (i = 0; i < sizeof fp->digest.sha1; i++) { + p += dns__printchar(dst, lim, p, hex[0x0f & (fp->digest.sha1[i] >> 4)]); + p += dns__printchar(dst, lim, p, hex[0x0f & (fp->digest.sha1[i] >> 0)]); + } + + break; + default: + p += dns__printchar(dst, lim, p, '0'); + + break; + } /* switch() */ + + dns__printnul(dst, lim, p); + + return p; +} /* dns_sshfp_print() */ + + +struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) { + assert(size > offsetof(struct dns_txt, data)); + + txt->size = size - offsetof(struct dns_txt, data); + txt->len = 0; + + return txt; +} /* dns_txt_init() */ + + +int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) { + struct { unsigned char *b; size_t p, end; } dst, src; + unsigned n; + + dst.b = txt->data; + dst.p = 0; + dst.end = txt->size; + + src.b = P->data; + src.p = rr->rd.p; + src.end = src.p + rr->rd.len; + + while (src.p < src.end) { + n = 0xff & P->data[src.p++]; + + if (src.end - src.p < n || dst.end - dst.p < n) + return DNS_EILLEGAL; + + memcpy(&dst.b[dst.p], &src.b[src.p], n); + + dst.p += n; + src.p += n; + } + + txt->len = dst.p; + + return 0; +} /* dns_txt_parse() */ + + +int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) { + struct { unsigned char *b; size_t p, end; } dst, src; + unsigned n; + + dst.b = P->data; + dst.p = P->end; + dst.end = P->size; + + src.b = txt->data; + src.p = 0; + src.end = txt->len; + + if (dst.end - dst.p < 2) + return DNS_ENOBUFS; + + n = txt->len + ((txt->len + 254) / 255); + + dst.b[dst.p++] = 0xff & (n >> 8); + dst.b[dst.p++] = 0xff & (n >> 0); + + while (src.p < src.end) { + n = DNS_PP_MIN(255, src.end - src.p); + + if (dst.p >= dst.end) + return DNS_ENOBUFS; + + dst.b[dst.p++] = n; + + if (dst.end - dst.p < n) + return DNS_ENOBUFS; + + memcpy(&dst.b[dst.p], &src.b[src.p], n); + + dst.p += n; + src.p += n; + } + + P->end = dst.p; + + return 0; +} /* dns_txt_push() */ + + +int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) { + (void)a; + (void)b; + + return -1; +} /* dns_txt_cmp() */ + + +size_t dns_txt_print(void *dst_, size_t lim, struct dns_txt *txt) { + struct { unsigned char *b; size_t p, end; } dst, src; + unsigned ch; + + dst.b = dst_; + dst.end = lim; + dst.p = 0; + + src.b = txt->data; + src.end = txt->len; + src.p = 0; + + dst.p += dns__printchar(dst.b, dst.end, dst.p, '"'); + + while (src.p < src.end) { + ch = src.b[src.p]; + + if (0 == (src.p++ % 255) && src.p != 1) { + dst.p += dns__printchar(dst.b, dst.end, dst.p, '"'); + dst.p += dns__printchar(dst.b, dst.end, dst.p, ' '); + dst.p += dns__printchar(dst.b, dst.end, dst.p, '"'); + } + + if (ch < 32 || ch > 126 || ch == '"' || ch == '\\') { + dst.p += dns__printchar(dst.b, dst.end, dst.p, '\\'); + dst.p += dns__print10(dst.b, dst.end, dst.p, ch, 3); + } else { + dst.p += dns__printchar(dst.b, dst.end, dst.p, ch); + } + } + + dst.p += dns__printchar(dst.b, dst.end, dst.p, '"'); + + dns__printnul(dst.b, dst.end, dst.p); + + return dst.p; +} /* dns_txt_print() */ + + +static const struct { + enum dns_type type; + const char *name; + int (*parse)(); + int (*push)(); + int (*cmp)(); + size_t (*print)(); + size_t (*cname)(); +} dns_rrtypes[] = { + { DNS_T_A, "A", &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0 }, + { DNS_T_AAAA, "AAAA", &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0 }, + { DNS_T_MX, "MX", &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname }, + { DNS_T_NS, "NS", &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname }, + { DNS_T_CNAME, "CNAME", &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname }, + { DNS_T_SOA, "SOA", &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0 }, + { DNS_T_SRV, "SRV", &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname }, + { DNS_T_OPT, "OPT", &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0 }, + { DNS_T_PTR, "PTR", &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname }, + { DNS_T_TXT, "TXT", &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0 }, + { DNS_T_SPF, "SPF", &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0 }, + { DNS_T_SSHFP, "SSHFP", &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0 }, +}; /* dns_rrtypes[] */ + + +union dns_any *dns_any_init(union dns_any *any, size_t size) { + return (union dns_any *)dns_txt_init(&any->rdata, size); +} /* dns_any_init() */ + + +int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) { + unsigned i; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == rr->type) + return dns_rrtypes[i].parse(any, rr, P); + } + + if (rr->rd.len > any->rdata.size) + return DNS_EILLEGAL; + + memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len); + any->rdata.len = rr->rd.len; + + return 0; +} /* dns_any_parse() */ + + +int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) { + unsigned i; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == type) + return dns_rrtypes[i].push(P, any); + } + + if (P->size - P->end < any->rdata.len + 2) + return DNS_ENOBUFS; + + P->data[P->end++] = 0xff & (any->rdata.len >> 8); + P->data[P->end++] = 0xff & (any->rdata.len >> 0); + + memcpy(&P->data[P->end], any->rdata.data, any->rdata.len); + P->end += any->rdata.len; + + return 0; +} /* dns_any_push() */ + + +int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) { + unsigned i; + int cmp; + + if ((cmp = x - y)) + return cmp; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == x) + return dns_rrtypes[i].cmp(a, b); + } + + return -1; +} /* dns_any_cmp() */ + + +size_t dns_any_print(void *dst_, size_t lim, union dns_any *any, enum dns_type type) { + struct { unsigned char *b; size_t p, end; } dst, src; + unsigned i, ch; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == type) + return dns_rrtypes[i].print(dst_, lim, any); + } + + dst.b = dst_; + dst.end = lim; + dst.p = 0; + + src.b = any->rdata.data; + src.end = any->rdata.len; + src.p = 0; + + dst.p += dns__printchar(dst.b, dst.end, dst.p, '"'); + + while (src.p < src.end) { + ch = src.b[src.p++]; + + dst.p += dns__printchar(dst.b, dst.end, dst.p, '\\'); + dst.p += dns__print10(dst.b, dst.end, dst.p, ch, 3); + } + + dst.p += dns__printchar(dst.b, dst.end, dst.p, '"'); + + dns__printnul(dst.b, dst.end, dst.p); + + return dst.p; +} /* dns_any_print() */ + + +size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) { + unsigned i; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == type) + return (dns_rrtypes[i].cname)? dns_rrtypes[i].cname(dst, lim, any) : 0; + } + + return 0; +} /* dns_any_cname() */ + + +/* + * H O S T S R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hosts { + struct dns_hosts_entry { + char host[DNS_D_MAXNAME + 1]; + char arpa[73 + 1]; + + int af; + + union { + struct in_addr a4; + struct in6_addr a6; + } addr; + + _Bool alias; + + struct dns_hosts_entry *next; + } *head, **tail; + + dns_atomic_t refcount; +}; /* struct dns_hosts */ + + +struct dns_hosts *dns_hosts_open(int *error) { + static const struct dns_hosts hosts_initializer = { .refcount = 1 }; + struct dns_hosts *hosts; + + if (!(hosts = malloc(sizeof *hosts))) + goto syerr; + + *hosts = hosts_initializer; + + hosts->tail = &hosts->head; + + return hosts; +syerr: + *error = dns_syerr(); + + free(hosts); + + return 0; +} /* dns_hosts_open() */ + + +void dns_hosts_close(struct dns_hosts *hosts) { + struct dns_hosts_entry *ent, *xnt; + + if (!hosts || 1 != dns_hosts_release(hosts)) + return; + + for (ent = hosts->head; ent; ent = xnt) { + xnt = ent->next; + + free(ent); + } + + free(hosts); + + return; +} /* dns_hosts_close() */ + + +dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) { + return dns_atomic_fetch_add(&hosts->refcount); +} /* dns_hosts_acquire() */ + + +dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) { + return dns_atomic_fetch_sub(&hosts->refcount); +} /* dns_hosts_release() */ + + +struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) { + if (hosts) + dns_hosts_release(hosts); + + return hosts; +} /* dns_hosts_mortal() */ + + +struct dns_hosts *dns_hosts_local(int *error_) { + struct dns_hosts *hosts; + int error; + + if (!(hosts = dns_hosts_open(&error))) + goto error; + + if ((error = dns_hosts_loadpath(hosts, "/etc/hosts"))) + goto error; + + return hosts; +error: + *error_ = error; + + dns_hosts_close(hosts); + + return 0; +} /* dns_hosts_local() */ + + +#define dns_hosts_issep(ch) (isspace(ch)) +#define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';') + +int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) { + struct dns_hosts_entry ent; + char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1]; + unsigned wp, wc, skip; + int ch, error; + + rewind(fp); + + do { + memset(&ent, '\0', sizeof ent); + wc = 0; + skip = 0; + + do { + memset(word, '\0', sizeof word); + wp = 0; + + while (EOF != (ch = fgetc(fp)) && ch != '\n') { + skip |= !!dns_hosts_iscom(ch); + + if (skip) + continue; + + if (dns_hosts_issep(ch)) + break; + + if (wp < sizeof word - 1) + word[wp] = ch; + wp++; + } + + if (!wp) + continue; + + wc++; + + switch (wc) { + case 0: + break; + case 1: + ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET; + skip = (1 != dns_inet_pton(ent.af, word, &ent.addr)); + + break; + default: + if (!wp) + break; + + dns_d_anchor(ent.host, sizeof ent.host, word, wp); + + if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2)))) + return error; + + break; + } /* switch() */ + } while (ch != EOF && ch != '\n'); + } while (ch != EOF); + + return 0; +} /* dns_hosts_loadfile() */ + + +int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) { + FILE *fp; + int error; + + if (!(fp = fopen(path, "r"))) + return dns_syerr(); + + error = dns_hosts_loadfile(hosts, fp); + + fclose(fp); + + return error; +} /* dns_hosts_loadpath() */ + + +int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) { + struct dns_hosts_entry *ent, *xnt; + char addr[INET6_ADDRSTRLEN + 1]; + unsigned i; + + for (ent = hosts->head; ent; ent = xnt) { + xnt = ent->next; + + dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr); + + fputs(addr, fp); + + for (i = strlen(addr); i < INET_ADDRSTRLEN; i++) + fputc(' ', fp); + + fputc(' ', fp); + + fputs(ent->host, fp); + fputc('\n', fp); + } + + return 0; +} /* dns_hosts_dump() */ + + +int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) { + struct dns_hosts_entry *ent; + int error; + + if (!(ent = malloc(sizeof *ent))) + goto syerr; + + dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host)); + + switch ((ent->af = af)) { + case AF_INET6: + memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6); + + dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr); + + break; + case AF_INET: + memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4); + + dns_a_arpa(ent->arpa, sizeof ent->arpa, addr); + + break; + default: + error = EINVAL; + + goto error; + } /* switch() */ + + ent->alias = alias; + + ent->next = 0; + *hosts->tail = ent; + hosts->tail = &ent->next; + + return 0; +syerr: + error = dns_syerr(); +error: + free(ent); + + return error; +} /* dns_hosts_insert() */ + + +struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { + struct dns_packet *P = dns_p_new(512); + struct dns_packet *A = 0; + struct dns_rr rr; + struct dns_hosts_entry *ent; + int error, af; + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + + if ((error = dns_rr_parse(&rr, 12, Q))) + goto error; + + if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error))) + goto error; + else if (qlen >= sizeof qname) + goto toolong; + + if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0))) + goto error; + + switch (rr.type) { + case DNS_T_PTR: + for (ent = hosts->head; ent; ent = ent->next) { + if (ent->alias || 0 != strcasecmp(qname, ent->arpa)) + continue; + + if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host))) + goto error; + } + + break; + case DNS_T_AAAA: + af = AF_INET6; + + goto loop; + case DNS_T_A: + af = AF_INET; + +loop: for (ent = hosts->head; ent; ent = ent->next) { + if (ent->af != af || 0 != strcasecmp(qname, ent->host)) + continue; + + if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr))) + goto error; + } + + break; + default: + break; + } /* switch() */ + + + if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) + goto error; + + return A; +toolong: + error = DNS_EILLEGAL; +error: + *error_ = error; + + dns_p_free(A); + + return 0; +} /* dns_hosts_query() */ + + +/* + * R E S O L V . C O N F R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_resolv_conf *dns_resconf_open(int *error) { + static const struct dns_resolv_conf resconf_initializer + = { .lookup = "bf", .options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, }, + .iface = { .ss_family = AF_INET }, }; + struct dns_resolv_conf *resconf; + struct sockaddr_in *sin; + + if (!(resconf = malloc(sizeof *resconf))) + goto syerr; + + *resconf = resconf_initializer; + + sin = (struct sockaddr_in *)&resconf->nameserver[0]; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = INADDR_ANY; + sin->sin_port = htons(53); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin->sin_len = dns_af_len(sin->sin_family); +#endif + + if (0 != gethostname(resconf->search[0], sizeof resconf->search[0])) + goto syerr; + + dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); + dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); + + /* + * XXX: If gethostname() returned a string without any label + * separator, then search[0][0] should be NUL. + */ + + dns_resconf_acquire(resconf); + + return resconf; +syerr: + *error = dns_syerr(); + + free(resconf); + + return 0; +} /* dns_resconf_open() */ + + +void dns_resconf_close(struct dns_resolv_conf *resconf) { + if (!resconf || 1 != dns_resconf_release(resconf)) + return /* void */; + + free(resconf); +} /* dns_resconf_close() */ + + +dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) { + return dns_atomic_fetch_add(&resconf->_.refcount); +} /* dns_resconf_acquire() */ + + +dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) { + return dns_atomic_fetch_sub(&resconf->_.refcount); +} /* dns_resconf_release() */ + + +struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) { + if (resconf) + dns_resconf_release(resconf); + + return resconf; +} /* dns_resconf_mortal() */ + + +struct dns_resolv_conf *dns_resconf_local(int *error_) { + struct dns_resolv_conf *resconf; + int error; + + if (!(resconf = dns_resconf_open(&error))) + goto error; + + if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) + goto error; + + if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) { + if (error != ENOENT) + goto error; + } + + return resconf; +error: + *error_ = error; + + dns_resconf_close(resconf); + + return 0; +} /* dns_resconf_local() */ + + +struct dns_resolv_conf *dns_resconf_root(int *error) { + struct dns_resolv_conf *resconf; + + if ((resconf = dns_resconf_local(error))) + resconf->options.recurse = 1; + + return resconf; +} /* dns_resconf_root() */ + + +static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) { + return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout); +} /* dns_resconf_timeout() */ + + +enum dns_resconf_keyword { + DNS_RESCONF_NAMESERVER, + DNS_RESCONF_DOMAIN, + DNS_RESCONF_SEARCH, + DNS_RESCONF_LOOKUP, + DNS_RESCONF_FILE, + DNS_RESCONF_BIND, + DNS_RESCONF_CACHE, + DNS_RESCONF_OPTIONS, + DNS_RESCONF_EDNS0, + DNS_RESCONF_NDOTS, + DNS_RESCONF_TIMEOUT, + DNS_RESCONF_ATTEMPTS, + DNS_RESCONF_ROTATE, + DNS_RESCONF_RECURSE, + DNS_RESCONF_SMART, + DNS_RESCONF_TCP, + DNS_RESCONF_TCPx, + DNS_RESCONF_INTERFACE, + DNS_RESCONF_ZERO, + DNS_RESCONF_ONE, + DNS_RESCONF_ENABLE, + DNS_RESCONF_ONLY, + DNS_RESCONF_DISABLE, +}; /* enum dns_resconf_keyword */ + +static enum dns_resconf_keyword dns_resconf_keyword(const char *word) { + static const char *words[] = { + [DNS_RESCONF_NAMESERVER] = "nameserver", + [DNS_RESCONF_DOMAIN] = "domain", + [DNS_RESCONF_SEARCH] = "search", + [DNS_RESCONF_LOOKUP] = "lookup", + [DNS_RESCONF_FILE] = "file", + [DNS_RESCONF_BIND] = "bind", + [DNS_RESCONF_CACHE] = "cache", + [DNS_RESCONF_OPTIONS] = "options", + [DNS_RESCONF_EDNS0] = "edns0", + [DNS_RESCONF_ROTATE] = "rotate", + [DNS_RESCONF_RECURSE] = "recurse", + [DNS_RESCONF_SMART] = "smart", + [DNS_RESCONF_TCP] = "tcp", + [DNS_RESCONF_INTERFACE] = "interface", + [DNS_RESCONF_ZERO] = "0", + [DNS_RESCONF_ONE] = "1", + [DNS_RESCONF_ENABLE] = "enable", + [DNS_RESCONF_ONLY] = "only", + [DNS_RESCONF_DISABLE] = "disable", + }; + unsigned i; + + for (i = 0; i < lengthof(words); i++) { + if (words[i] && 0 == strcasecmp(words[i], word)) + return i; + } + + if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1)) + return DNS_RESCONF_NDOTS; + + if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1)) + return DNS_RESCONF_TIMEOUT; + + if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1)) + return DNS_RESCONF_ATTEMPTS; + + if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1)) + return DNS_RESCONF_TCPx; + + return -1; +} /* dns_resconf_keyword() */ + + +/** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */ +int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) { + struct { char buf[128], *p; } addr = { "", addr.buf }; + unsigned short port = 0; + int ch, af = AF_INET, error; + + while ((ch = *src++)) { + switch (ch) { + case ' ': + /* FALL THROUGH */ + case '\t': + break; + case '[': + break; + case ']': + while ((ch = *src++)) { + if (isdigit((unsigned char)ch)) { + port *= 10; + port += ch - '0'; + } + } + + goto inet; + case ':': + af = AF_INET6; + + /* FALL THROUGH */ + default: + if (addr.p < endof(addr.buf) - 1) + *addr.p++ = ch; + + break; + } /* switch() */ + } /* while() */ +inet: + + if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL)))) + return error; + + port = (!port)? 53 : port; + *dns_sa_port(af, ss) = htons(port); + dns_sa_family(ss) = af; + + return 0; +} /* dns_resconf_pton() */ + +#define dns_resconf_issep(ch) (isspace(ch) || (ch) == ',') +#define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';') + +int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { + unsigned sa_count = 0; + char words[6][DNS_D_MAXNAME + 1]; + unsigned wp, wc, i, j, n; + int ch, error; + + rewind(fp); + + do { + memset(words, '\0', sizeof words); + wp = 0; + wc = 0; + + while (EOF != (ch = getc(fp)) && ch != '\n') { + if (dns_resconf_issep(ch)) { + if (wp > 0) { + wp = 0; + + if (++wc >= lengthof(words)) + goto skip; + } + } else if (dns_resconf_iscom(ch)) { +skip: + do { + ch = getc(fp); + } while (ch != EOF && ch != '\n'); + + break; + } else { + dns__printchar(words[wc], sizeof words[wc], wp, ch); + wp++; + } + } + + if (wp > 0) + wc++; + + if (wc < 2) + continue; + + switch (dns_resconf_keyword(words[0])) { + case DNS_RESCONF_NAMESERVER: + if (sa_count >= lengthof(resconf->nameserver)) + continue; + + if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1]))) + continue; + + sa_count++; + + break; + case DNS_RESCONF_DOMAIN: + case DNS_RESCONF_SEARCH: + memset(resconf->search, '\0', sizeof resconf->search); + + for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++) + dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i])); + + break; + case DNS_RESCONF_LOOKUP: + for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) { + switch (dns_resconf_keyword(words[i])) { + case DNS_RESCONF_FILE: + resconf->lookup[j++] = 'f'; + + break; + case DNS_RESCONF_BIND: + resconf->lookup[j++] = 'b'; + + break; + case DNS_RESCONF_CACHE: + resconf->lookup[j++] = 'c'; + + break; + default: + break; + } /* switch() */ + } /* for() */ + + break; + case DNS_RESCONF_OPTIONS: + for (i = 1; i < wc; i++) { + switch (dns_resconf_keyword(words[i])) { + case DNS_RESCONF_EDNS0: + resconf->options.edns0 = 1; + + break; + case DNS_RESCONF_NDOTS: + for (j = sizeof "ndots:" - 1, n = 0; isdigit((int)words[i][j]); j++) { + n *= 10; + n += words[i][j] - '0'; + } /* for() */ + + resconf->options.ndots = n; + + break; + case DNS_RESCONF_TIMEOUT: + for (j = sizeof "timeout:" - 1, n = 0; isdigit((int)words[i][j]); j++) { + n *= 10; + n += words[i][j] - '0'; + } /* for() */ + + resconf->options.timeout = n; + + break; + case DNS_RESCONF_ATTEMPTS: + for (j = sizeof "attempts:" - 1, n = 0; isdigit((int)words[i][j]); j++) { + n *= 10; + n += words[i][j] - '0'; + } /* for() */ + + resconf->options.attempts = n; + + break; + case DNS_RESCONF_ROTATE: + resconf->options.rotate = 1; + + break; + case DNS_RESCONF_RECURSE: + resconf->options.recurse = 1; + + break; + case DNS_RESCONF_SMART: + resconf->options.smart = 1; + + break; + case DNS_RESCONF_TCP: + resconf->options.tcp = DNS_RESCONF_TCP_ONLY; + + break; + case DNS_RESCONF_TCPx: + switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) { + case DNS_RESCONF_ENABLE: + resconf->options.tcp = DNS_RESCONF_TCP_ENABLE; + + break; + case DNS_RESCONF_ONE: + case DNS_RESCONF_ONLY: + resconf->options.tcp = DNS_RESCONF_TCP_ONLY; + + break; + case DNS_RESCONF_ZERO: + case DNS_RESCONF_DISABLE: + resconf->options.tcp = DNS_RESCONF_TCP_DISABLE; + + break; + default: + break; + } /* switch() */ + + break; + default: + break; + } /* switch() */ + } /* for() */ + + break; + case DNS_RESCONF_INTERFACE: + for (i = 0, n = 0; isdigit((int)words[2][i]); i++) { + n *= 10; + n += words[2][i] - '0'; + } + + dns_resconf_setiface(resconf, words[1], n); + + break; + default: + break; + } /* switch() */ + } while (ch != EOF); + + return 0; +} /* dns_resconf_loadfile() */ + + +int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { + FILE *fp; + int error; + + if (!(fp = fopen(path, "r"))) + return dns_syerr(); + + error = dns_resconf_loadfile(resconf, fp); + + fclose(fp); + + return error; +} /* dns_resconf_loadpath() */ + + +struct dns_anyconf { + char *token[16]; + unsigned count; + char buffer[1024], *tp, *cp; +}; /* struct dns_anyconf */ + + +static void dns_anyconf_reset(struct dns_anyconf *cf) { + cf->count = 0; + cf->tp = cf->cp = cf->buffer; +} /* dns_anyconf_reset() */ + + +static int dns_anyconf_push(struct dns_anyconf *cf) { + if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token))) + return ENOMEM; + + *cf->cp++ = '\0'; + cf->token[cf->count++] = cf->tp; + cf->tp = cf->cp; + + return 0; +} /* dns_anyconf_push() */ + + +static void dns_anyconf_pop(struct dns_anyconf *cf) { + if (cf->count > 0) { + --cf->count; + cf->tp = cf->cp = cf->token[cf->count]; + cf->token[cf->count] = 0; + } +} /* dns_anyconf_pop() */ + + +static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) { + if (!(cf->cp < endof(cf->buffer))) + return ENOMEM; + + *cf->cp++ = ch; + + return 0; +} /* dns_anyconf_addc() */ + + +static _Bool dns_anyconf_match(const char *pat, int mc) { + _Bool match; + int pc; + + if (*pat == '^') { + match = 0; + ++pat; + } else { + match = 1; + } + + while ((pc = *(const unsigned char *)pat++)) { + switch (pc) { + case '%': + if (!(pc = *(const unsigned char *)pat++)) + return !match; + + switch (pc) { + case 'a': + if (isalpha(mc)) + return match; + break; + case 'd': + if (isdigit(mc)) + return match; + break; + case 'w': + if (isalnum(mc)) + return match; + break; + case 's': + if (isspace(mc)) + return match; + break; + default: + if (mc == pc) + return match; + break; + } /* switch() */ + + break; + default: + if (mc == pc) + return match; + break; + } /* switch() */ + } /* while() */ + + return !match; +} /* dns_anyconf_match() */ + + +static int dns_anyconf_peek(FILE *fp) { + int ch; + ch = getc(fp); + ungetc(ch, fp); + return ch; +} /* dns_anyconf_peek() */ + + +static size_t dns_anyconf_skip(const char *pat, FILE *fp) { + size_t count = 0; + int ch; + + while (EOF != (ch = getc(fp))) { + if (dns_anyconf_match(pat, ch)) { + count++; + continue; + } + + ungetc(ch, fp); + + break; + } + + return count; +} /* dns_anyconf_skip() */ + + +static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) { + size_t len; + int ch; + + while (EOF != (ch = getc(fp))) { + if (dns_anyconf_match(pat, ch)) { + if ((*error = dns_anyconf_addc(cf, ch))) + return 0; + + continue; + } else { + ungetc(ch, fp); + + break; + } + } + + if ((len = cf->cp - cf->tp)) { + if ((*error = dns_anyconf_push(cf))) + return 0; + + return len; + } else { + *error = 0; + + return 0; + } +} /* dns_anyconf_scan() */ + + +DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) { + unsigned i; + + fprintf(fp, "tokens:"); + + for (i = 0; i < cf->count; i++) { + fprintf(fp, " %s", cf->token[i]); + } + + fputc('\n', fp); +} /* dns_anyconf_dump() */ + + +enum dns_nssconf_keyword { + DNS_NSSCONF_INVALID = 0, + DNS_NSSCONF_HOSTS = 1, + DNS_NSSCONF_SUCCESS, + DNS_NSSCONF_NOTFOUND, + DNS_NSSCONF_UNAVAIL, + DNS_NSSCONF_TRYAGAIN, + DNS_NSSCONF_CONTINUE, + DNS_NSSCONF_RETURN, + DNS_NSSCONF_FILES, + DNS_NSSCONF_DNS, + DNS_NSSCONF_MDNS, + + DNS_NSSCONF_LAST, +}; /* enum dns_nssconf_keyword */ + +static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) { + static const char *list[] = { + [DNS_NSSCONF_HOSTS] = "hosts", + [DNS_NSSCONF_SUCCESS] = "success", + [DNS_NSSCONF_NOTFOUND] = "notfound", + [DNS_NSSCONF_UNAVAIL] = "unavail", + [DNS_NSSCONF_TRYAGAIN] = "tryagain", + [DNS_NSSCONF_CONTINUE] = "continue", + [DNS_NSSCONF_RETURN] = "return", + [DNS_NSSCONF_FILES] = "files", + [DNS_NSSCONF_DNS] = "dns", + [DNS_NSSCONF_MDNS] = "mdns", + }; + unsigned i; + + for (i = 1; i < lengthof(list); i++) { + if (list[i] && 0 == strcasecmp(list[i], word)) + return i; + } + + return DNS_NSSCONF_INVALID; +} /* dns_nssconf_keyword() */ + + +static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) { + static const char map[] = { + ['S'] = DNS_NSSCONF_SUCCESS, + ['N'] = DNS_NSSCONF_NOTFOUND, + ['U'] = DNS_NSSCONF_UNAVAIL, + ['T'] = DNS_NSSCONF_TRYAGAIN, + ['C'] = DNS_NSSCONF_CONTINUE, + ['R'] = DNS_NSSCONF_RETURN, + ['f'] = DNS_NSSCONF_FILES, + ['F'] = DNS_NSSCONF_FILES, + ['d'] = DNS_NSSCONF_DNS, + ['D'] = DNS_NSSCONF_DNS, + ['b'] = DNS_NSSCONF_DNS, + ['B'] = DNS_NSSCONF_DNS, + ['m'] = DNS_NSSCONF_MDNS, + ['M'] = DNS_NSSCONF_MDNS, + }; + + return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID; +} /* dns_nssconf_c2k() */ + + +DNS_PRAGMA_PUSH +DNS_PRAGMA_QUIET + +static int dns_nssconf_k2c(int k) { + static const char map[DNS_NSSCONF_LAST] = { + [DNS_NSSCONF_SUCCESS] = 'S', + [DNS_NSSCONF_NOTFOUND] = 'N', + [DNS_NSSCONF_UNAVAIL] = 'U', + [DNS_NSSCONF_TRYAGAIN] = 'T', + [DNS_NSSCONF_CONTINUE] = 'C', + [DNS_NSSCONF_RETURN] = 'R', + [DNS_NSSCONF_FILES] = 'f', + [DNS_NSSCONF_DNS] = 'b', + [DNS_NSSCONF_MDNS] = 'm', + }; + + return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?'; +} /* dns_nssconf_k2c() */ + +static const char *dns_nssconf_k2s(int k) { + static const char *const map[DNS_NSSCONF_LAST] = { + [DNS_NSSCONF_SUCCESS] = "SUCCESS", + [DNS_NSSCONF_NOTFOUND] = "NOTFOUND", + [DNS_NSSCONF_UNAVAIL] = "UNAVAIL", + [DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN", + [DNS_NSSCONF_CONTINUE] = "continue", + [DNS_NSSCONF_RETURN] = "return", + [DNS_NSSCONF_FILES] = "files", + [DNS_NSSCONF_DNS] = "dns", + [DNS_NSSCONF_MDNS] = "mdns", + }; + + return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : ""; +} /* dns_nssconf_k2s() */ + +DNS_PRAGMA_POP + + +int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { + enum dns_nssconf_keyword source, status, action; + char lookup[sizeof resconf->lookup] = "", *lp; + struct dns_anyconf cf; + size_t i; + int error; + + while (!feof(fp) && !ferror(fp)) { + dns_anyconf_reset(&cf); + + dns_anyconf_skip("%s", fp); + + if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) + goto nextent; + + if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0])) + goto nextent; + + dns_anyconf_pop(&cf); + + if (!dns_anyconf_skip(": \t", fp)) + goto nextent; + + *(lp = lookup) = '\0'; + + while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { + dns_anyconf_skip(" \t", fp); + + if ('[' == dns_anyconf_peek(fp)) { + dns_anyconf_skip("[ \t", fp); + + while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { + dns_anyconf_skip("= \t", fp); + if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) { + dns_anyconf_pop(&cf); /* discard status */ + dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ + break; + } + dns_anyconf_skip(" \t", fp); + } + + dns_anyconf_skip("] \t", fp); + } + + if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */ + goto nextsrc; + + source = dns_nssconf_keyword(cf.token[0]); + + switch (source) { + case DNS_NSSCONF_DNS: + case DNS_NSSCONF_MDNS: + case DNS_NSSCONF_FILES: + *lp++ = dns_nssconf_k2c(source); + break; + default: + goto nextsrc; + } + + for (i = 1; i + 1 < cf.count; i += 2) { + status = dns_nssconf_keyword(cf.token[i]); + action = dns_nssconf_keyword(cf.token[i + 1]); + + switch (status) { + case DNS_NSSCONF_SUCCESS: + case DNS_NSSCONF_NOTFOUND: + case DNS_NSSCONF_UNAVAIL: + case DNS_NSSCONF_TRYAGAIN: + *lp++ = dns_nssconf_k2c(status); + break; + default: + continue; + } + + switch (action) { + case DNS_NSSCONF_CONTINUE: + case DNS_NSSCONF_RETURN: + break; + default: + action = (status == DNS_NSSCONF_SUCCESS) + ? DNS_NSSCONF_RETURN + : DNS_NSSCONF_CONTINUE; + break; + } + + *lp++ = dns_nssconf_k2c(action); + } +nextsrc: + *lp = '\0'; + dns_anyconf_reset(&cf); + } +nextent: + dns_anyconf_skip("^\n", fp); + } + + if (*lookup) + strncpy(resconf->lookup, lookup, sizeof resconf->lookup); + + return 0; +} /* dns_nssconf_loadfile() */ + + +int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { + FILE *fp; + int error; + + if (!(fp = fopen(path, "r"))) + return dns_syerr(); + + error = dns_nssconf_loadfile(resconf, fp); + + fclose(fp); + + return error; +} /* dns_nssconf_loadpath() */ + + +struct dns_nssconf_source { + enum dns_nssconf_keyword source, success, notfound, unavail, tryagain; +}; /* struct dns_nssconf_source */ + +typedef unsigned dns_nssconf_i; + +static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) { + return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0; +} /* dns_nssconf_peek() */ + +static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) { + int source, status, action; + + src->source = DNS_NSSCONF_INVALID; + src->success = DNS_NSSCONF_RETURN; + src->notfound = DNS_NSSCONF_CONTINUE; + src->unavail = DNS_NSSCONF_CONTINUE; + src->tryagain = DNS_NSSCONF_CONTINUE; + + while ((source = dns_nssconf_peek(resconf, *state))) { + source = dns_nssconf_c2k(source); + ++*state; + + switch (source) { + case DNS_NSSCONF_FILES: + case DNS_NSSCONF_DNS: + case DNS_NSSCONF_MDNS: + src->source = source; + break; + default: + continue; + } + + while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) { + status = dns_nssconf_c2k(status); + action = dns_nssconf_c2k(action); + + switch (action) { + case DNS_NSSCONF_RETURN: + case DNS_NSSCONF_CONTINUE: + break; + default: + goto done; + } + + switch (status) { + case DNS_NSSCONF_SUCCESS: + src->success = action; + break; + case DNS_NSSCONF_NOTFOUND: + src->notfound = action; + break; + case DNS_NSSCONF_UNAVAIL: + src->unavail = action; + break; + case DNS_NSSCONF_TRYAGAIN: + src->tryagain = action; + break; + default: + goto done; + } + + *state += 2; + } + + break; + } +done: + return src->source != DNS_NSSCONF_INVALID; +} /* dns_nssconf_next() */ + + +static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) { + switch (status) { + case DNS_NSSCONF_SUCCESS: + if (action == DNS_NSSCONF_RETURN) + return 0; + break; + default: + if (action == DNS_NSSCONF_CONTINUE) + return 0; + break; + } + + fputc(' ', fp); + + if (!*count) + fputc('[', fp); + + fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action)); + + ++*count; + + return 0; +} /* dns_nssconf_dump_status() */ + + +int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { + struct dns_nssconf_source src; + dns_nssconf_i i = 0; + + fputs("hosts:", fp); + + while (dns_nssconf_next(&src, resconf, &i)) { + unsigned n = 0; + + fprintf(fp, " %s", dns_nssconf_k2s(src.source)); + + dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp); + dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp); + dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp); + dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp); + + if (n) + fputc(']', fp); + } + + fputc('\n', fp); + + return 0; +} /* dns_nssconf_dump() */ + + +int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) { + int af = (strchr(addr, ':'))? AF_INET6 : AF_INET; + int error; + + if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL)))) + return error; + + *dns_sa_port(af, &resconf->iface) = htons(port); + resconf->iface.ss_family = af; + + return 0; +} /* dns_resconf_setiface() */ + + +size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) { + unsigned srchi = 0xff & (*state >> 8); + unsigned ndots = 0xff & (*state >> 16); + unsigned slen, len = 0; + const char *qp, *qe; + +// assert(0xff > lengthof(resconf->search)); + + switch (0xff & *state) { + case 0: + qp = qname; + qe = qp + qlen; + + while ((qp = memchr(qp, '.', qe - qp))) + { ndots++; qp++; } + + ++*state; + + if (ndots >= resconf->options.ndots) { + len = dns_d_anchor(dst, lim, qname, qlen); + + break; + } + + /* FALL THROUGH */ + case 1: + if (srchi < lengthof(resconf->search) && (slen = strlen(resconf->search[srchi]))) { + len = dns__printstring(dst, lim, 0, qname, qlen); + len = dns_d_anchor(dst, lim, dst, len); + len += dns__printstring(dst, lim, len, resconf->search[srchi], slen); + + srchi++; + + break; + } + + ++*state; + + /* FALL THROUGH */ + case 2: + ++*state; + + if (ndots < resconf->options.ndots) { + len = dns_d_anchor(dst, lim, qname, qlen); + + break; + } + + /* FALL THROUGH */ + default: + break; + } /* switch() */ + + dns__printnul(dst, lim, len); + + *state = ((0xff & *state) << 0) + | ((0xff & srchi) << 8) + | ((0xff & ndots) << 16); + + return len; +} /* dns_resconf_search() */ + + +int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { + unsigned i; + int af; + + for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) { + char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; + unsigned short port; + + dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr); + port = ntohs(*dns_sa_port(af, &resconf->nameserver[i])); + + if (port == 53) + fprintf(fp, "nameserver %s\n", addr); + else + fprintf(fp, "nameserver [%s]:%hu\n", addr, port); + } + + + fprintf(fp, "search"); + + for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++) + fprintf(fp, " %s", resconf->search[i]); + + fputc('\n', fp); + + + fputs("; ", fp); + dns_nssconf_dump(resconf, fp); + + fprintf(fp, "lookup"); + + for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) { + switch (resconf->lookup[i]) { + case 'b': + fprintf(fp, " bind"); break; + case 'f': + fprintf(fp, " file"); break; + case 'c': + fprintf(fp, " cache"); break; + } + } + + fputc('\n', fp); + + + fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts); + + if (resconf->options.edns0) + fprintf(fp, " edns0"); + if (resconf->options.rotate) + fprintf(fp, " rotate"); + if (resconf->options.recurse) + fprintf(fp, " recurse"); + if (resconf->options.smart) + fprintf(fp, " smart"); + + switch (resconf->options.tcp) { + case DNS_RESCONF_TCP_ENABLE: + break; + case DNS_RESCONF_TCP_ONLY: + fprintf(fp, " tcp"); + break; + case DNS_RESCONF_TCP_DISABLE: + fprintf(fp, " tcp:disable"); + break; + } + + fputc('\n', fp); + + + if ((af = resconf->iface.ss_family) != AF_UNSPEC) { + char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; + + dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr); + + fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface))); + } + + return 0; +} /* dns_resconf_dump() */ + + +/* + * H I N T S E R V E R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hints_soa { + unsigned char zone[DNS_D_MAXNAME + 1]; + + struct { + struct sockaddr_storage ss; + unsigned priority; + } addrs[16]; + + unsigned count; + + struct dns_hints_soa *next; +}; /* struct dns_hints_soa */ + + +struct dns_hints { + dns_atomic_t refcount; + + struct dns_hints_soa *head; +}; /* struct dns_hints */ + + +struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) { + static const struct dns_hints H_initializer; + struct dns_hints *H; + + (void)resconf; + + if (!(H = malloc(sizeof *H))) + goto syerr; + + *H = H_initializer; + + dns_hints_acquire(H); + + return H; +syerr: + *error = dns_syerr(); + + free(H); + + return 0; +} /* dns_hints_open() */ + + +void dns_hints_close(struct dns_hints *H) { + struct dns_hints_soa *soa, *nxt; + + if (!H || 1 != dns_hints_release(H)) + return /* void */; + + for (soa = H->head; soa; soa = nxt) { + nxt = soa->next; + + free(soa); + } + + free(H); + + return /* void */; +} /* dns_hints_close() */ + + +dns_refcount_t dns_hints_acquire(struct dns_hints *H) { + return dns_atomic_fetch_add(&H->refcount); +} /* dns_hints_acquire() */ + + +dns_refcount_t dns_hints_release(struct dns_hints *H) { + return dns_atomic_fetch_sub(&H->refcount); +} /* dns_hints_release() */ + + +struct dns_hints *dns_hints_mortal(struct dns_hints *hints) { + if (hints) + dns_hints_release(hints); + + return hints; +} /* dns_hints_mortal() */ + + +struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) { + struct dns_hints *hints = 0; + int error; + + if (resconf) + dns_resconf_acquire(resconf); + else if (!(resconf = dns_resconf_local(&error))) + goto error; + + if (!(hints = dns_hints_open(resconf, &error))) + goto error; + + error = 0; + + if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error) + goto error; + + dns_resconf_close(resconf); + + return hints; +error: + *error_ = error; + + dns_resconf_close(resconf); + dns_hints_close(hints); + + return 0; +} /* dns_hints_local() */ + + +struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) { + static const struct { + int af; + char addr[INET6_ADDRSTRLEN]; + } root_hints[] = { + { AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */ + { AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */ + { AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */ + { AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */ + { AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */ + { AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */ + { AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */ + { AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */ + { AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */ + { AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */ + { AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */ + { AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */ + { AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */ + }; + struct dns_hints *hints = 0; + struct sockaddr_storage ss; + unsigned i; + int error, af; + + if (!(hints = dns_hints_open(resconf, &error))) + goto error; + + for (i = 0; i < lengthof(root_hints); i++) { + af = root_hints[i].af; + + if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL)))) + goto error; + + *dns_sa_port(af, &ss) = htons(53); + ss.ss_family = af; + + if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1))) + goto error; + } + + return hints; +error: + *error_ = error; + + dns_hints_close(hints); + + return 0; +} /* dns_hints_root() */ + + +static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) { + struct dns_hints_soa *soa; + + for (soa = H->head; soa; soa = soa->next) { + if (0 == strcasecmp(zone, (char *)soa->zone)) + return soa; + } + + return 0; +} /* dns_hints_fetch() */ + + +int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) { + static const struct dns_hints_soa soa_initializer; + struct dns_hints_soa *soa; + unsigned i; + + if (!(soa = dns_hints_fetch(H, zone))) { + if (!(soa = malloc(sizeof *soa))) + return dns_syerr(); + + *soa = soa_initializer; + + dns__printstring(soa->zone, sizeof soa->zone, 0, zone); + + soa->next = H->head; + H->head = soa; + } + + i = soa->count % lengthof(soa->addrs); + + memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa)); + + soa->addrs[i].priority = DNS_PP_MAX(1, priority); + + if (soa->count < lengthof(soa->addrs)) + soa->count++; + + return 0; +} /* dns_hints_insert() */ + + +unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) { + unsigned i, n, p; + int error; + + for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) { + if ((error = dns_hints_insert(H, zone, (struct sockaddr *)&resconf->nameserver[i], p))) + goto error; + + p += !resconf->options.rotate; + } + + return n; +error: + *error_ = error; + + return n; +} /* dns_hints_insert_resconf() */ + + +static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) { + int cmp; + + if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority)) + return cmp; + + return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed); +} /* dns_hints_i_cmp() */ + + +static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) { + unsigned p0, p; + + p0 = 0; + + for (p = 1; p < soa->count; p++) { + if (dns_hints_i_cmp(p, p0, i, soa) < 0) + p0 = p; + } + + return p0; +} /* dns_hints_i_start() */ + + +static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) { + unsigned pZ, p; + + for (pZ = 0; pZ < soa->count; pZ++) { + if (dns_hints_i_cmp(pZ, p0, i, soa) > 0) + goto cont; + } + + return soa->count; +cont: + for (p = pZ + 1; p < soa->count; p++) { + if (dns_hints_i_cmp(p, p0, i, soa) <= 0) + continue; + + if (dns_hints_i_cmp(p, pZ, i, soa) >= 0) + continue; + + pZ = p; + } + + + return pZ; +} /* dns_hints_i_skip() */ + + +struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) { + static const struct dns_hints_i i_initializer; + struct dns_hints_soa *soa; + + i->state = i_initializer.state; + + do { + i->state.seed = dns_random(); + } while (0 == i->state.seed); + + if ((soa = dns_hints_fetch(hints, i->zone))) { + i->state.next = dns_hints_i_start(i, soa); + } + + return i; +} /* dns_hints_i_init() */ + + +unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) { + struct dns_hints_soa *soa; + unsigned n; + + if (!(soa = dns_hints_fetch(H, i->zone))) + return 0; + + n = 0; + + while (i->state.next < soa->count && n < lim) { + *sa = (struct sockaddr *)&soa->addrs[i->state.next].ss; + *sa_len = dns_sa_len(*sa); + + sa++; + sa_len++; + n++; + + i->state.next = dns_hints_i_skip(i->state.next, i, soa); + } + + return n; +} /* dns_hints_grep() */ + + +struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { + struct dns_packet *A, *P; + struct dns_rr rr; + char zone[DNS_D_MAXNAME + 1]; + size_t zlen; + struct dns_hints_i i; + struct sockaddr *sa; + socklen_t slen; + int error; + + if (!dns_rr_grep(&rr, 1, dns_rr_i_new(Q, .section = DNS_S_QUESTION), Q, &error)) + goto error; + + if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) + goto error; + else if (zlen >= sizeof zone) + goto toolong; + + P = dns_p_new(512); + dns_header(P)->qr = 1; + + if ((error = dns_rr_copy(P, &rr, Q))) + goto error; + + if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local."))) + goto error; + + do { + i.zone = zone; + + dns_hints_i_init(&i, hints); + + while (dns_hints_grep(&sa, &slen, 1, &i, hints)) { + int af = sa->sa_family; + int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A; + + if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL)))) + goto error; + } + } while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen))); + + if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) + goto error; + + return A; +toolong: + error = DNS_EILLEGAL; +error: + *error_ = error; + + return 0; +} /* dns_hints_query() */ + + +/** ugly hack to support specifying ports other than 53 in resolv.conf. */ +static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) { + struct dns_hints_soa *soa; + void *addrsoa; + socklen_t addrlen; + unsigned short port; + unsigned i; + + for (soa = hints->head; soa; soa = soa->next) { + for (i = 0; i < soa->count; i++) { + if (af != soa->addrs[i].ss.ss_family) + continue; + + if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen))) + continue; + + if (memcmp(addr, addrsoa, addrlen)) + continue; + + port = *dns_sa_port(af, &soa->addrs[i].ss); + + return (port)? port : htons(53); + } + } + + return htons(53); +} /* dns_hints_port() */ + + +int dns_hints_dump(struct dns_hints *hints, FILE *fp) { + struct dns_hints_soa *soa; + char addr[INET6_ADDRSTRLEN]; + unsigned i; + int af, error; + + for (soa = hints->head; soa; soa = soa->next) { + fprintf(fp, "ZONE \"%s\"\n", soa->zone); + + for (i = 0; i < soa->count; i++) { + af = soa->addrs[i].ss.ss_family; + + if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr))) + return error; + + fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss))); + } + } + + return 0; +} /* dns_hints_dump() */ + + +/* + * C A C H E R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) { + return dns_atomic_fetch_add(&cache->_.refcount); +} /* dns_cache_acquire() */ + + +static dns_refcount_t dns_cache_release(struct dns_cache *cache) { + return dns_atomic_fetch_sub(&cache->_.refcount); +} /* dns_cache_release() */ + + +static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) { + (void)query; + (void)cache; + (void)error; + + return NULL; +} /* dns_cache_submit() */ + + +static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) { + (void)query; + (void)cache; + + return 0; +} /* dns_cache_submit() */ + + +static int dns_cache_check(struct dns_cache *cache) { + (void)cache; + + return 0; +} /* dns_cache_check() */ + + +static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) { + (void)cache; + (void)error; + + return NULL; +} /* dns_cache_fetch() */ + + +static int dns_cache_pollfd(struct dns_cache *cache) { + (void)cache; + + return -1; +} /* dns_cache_pollfd() */ + + +static short dns_cache_events(struct dns_cache *cache) { + (void)cache; + + return 0; +} /* dns_cache_events() */ + + +static void dns_cache_clear(struct dns_cache *cache) { + (void)cache; + + return; +} /* dns_cache_clear() */ + + +struct dns_cache *dns_cache_init(struct dns_cache *cache) { + static const struct dns_cache c_init = { + .acquire = &dns_cache_acquire, + .release = &dns_cache_release, + .query = &dns_cache_query, + .submit = &dns_cache_submit, + .check = &dns_cache_check, + .fetch = &dns_cache_fetch, + .pollfd = &dns_cache_pollfd, + .events = &dns_cache_events, + .clear = &dns_cache_clear, + ._ = { .refcount = 1, }, + }; + + *cache = c_init; + + return cache; +} /* dns_cache_init() */ + + +void dns_cache_close(struct dns_cache *cache) { + if (cache) + cache->release(cache); +} /* dns_cache_close() */ + + +/* + * S O C K E T R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static void dns_socketclose(int *fd, const struct dns_options *opts) { + if (opts && opts->closefd.cb) + opts->closefd.cb(fd, opts->closefd.arg); + + if (*fd != -1) { +#if _WIN32 + closesocket(*fd); +#else + close(*fd); +#endif + *fd = -1; + } +} /* dns_socketclose() */ + + +#define DNS_SO_MAXTRY 7 + +static int dns_socket(struct sockaddr *local, int type, int *error_) { + int error, fd = -1; +#if defined(O_NONBLOCK) + int flags; +#elif defined(FIONBIO) + unsigned long opt; +#endif + + if (-1 == (fd = socket(local->sa_family, type, 0))) + goto soerr; + +#if defined(F_SETFD) + if (-1 == fcntl(fd, F_SETFD, 1)) + goto syerr; +#endif + +#if defined(O_NONBLOCK) + if (-1 == (flags = fcntl(fd, F_GETFL))) + goto syerr; + + if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) + goto syerr; +#elif defined(FIONBIO) + opt = 1; + + if (0 != ioctlsocket(fd, FIONBIO, &opt)) + goto soerr; +#endif + +#if defined(SO_NOSIGPIPE) + if (type != SOCK_DGRAM) { + if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int))) + goto soerr; + } +#endif + + if (local->sa_family != AF_INET && local->sa_family != AF_INET6) + return fd; + + if (type != SOCK_DGRAM) + return fd; + + if (*dns_sa_port(local->sa_family, local) == 0) { + struct sockaddr_storage tmp; + unsigned i, port; + + memcpy(&tmp, local, dns_sa_len(local)); + + for (i = 0; i < DNS_SO_MAXTRY; i++) { + port = 1025 + (dns_random() % 64510); + + *dns_sa_port(tmp.ss_family, &tmp) = htons(port); + + if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp))) + return fd; + } + } + + if (0 == bind(fd, local, dns_sa_len(local))) + return fd; + + /* FALL THROUGH */ +soerr: + error = dns_soerr(); + + goto error; +#if defined(F_SETFD) || defined(O_NONBLOCK) +syerr: + error = dns_syerr(); + + goto error; +#endif +error: + *error_ = error; + + dns_socketclose(&fd, NULL); + + return -1; +} /* dns_socket() */ + + +enum { + DNS_SO_UDP_INIT = 1, + DNS_SO_UDP_CONN, + DNS_SO_UDP_SEND, + DNS_SO_UDP_RECV, + DNS_SO_UDP_DONE, + + DNS_SO_TCP_INIT, + DNS_SO_TCP_CONN, + DNS_SO_TCP_SEND, + DNS_SO_TCP_RECV, + DNS_SO_TCP_DONE, +}; + +struct dns_socket { + struct dns_options opts; + + int udp; + int tcp; + + int *old; + unsigned onum, olim; + + int type; + + struct sockaddr_storage local, remote; + + struct dns_k_permutor qids; + + struct dns_stat stat; + + /* + * NOTE: dns_so_reset() zeroes everything from here down. + */ + int state; + + unsigned short qid; + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + enum dns_type qtype; + enum dns_class qclass; + + struct dns_packet *query; + size_t qout; + + struct dns_clock elapsed; + + struct dns_packet *answer; + size_t alen, apos; +}; /* struct dns_socket */ + + +/* + * NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have + * a chance to recognize a state change after installing a persistent event + * and where sequential descriptors with the same integer value returned + * from _pollfd() would be ambiguous. See dns_so_closefds(). + */ +static int dns_so_closefd(struct dns_socket *so, int *fd) { + int error; + + if (*fd == -1) + return 0; + + if (so->opts.closefd.cb) { + if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) { + return error; + } else if (*fd == -1) + return 0; + } + + if (!(so->onum < so->olim)) { + unsigned olim = DNS_PP_MAX(4, so->olim * 2); + void *old; + + if (!(old = realloc(so->old, sizeof so->old[0] * olim))) + return dns_syerr(); + + so->old = old; + so->olim = olim; + } + + so->old[so->onum++] = *fd; + *fd = -1; + + return 0; +} /* dns_so_closefd() */ + + +#define DNS_SO_CLOSE_UDP 0x01 +#define DNS_SO_CLOSE_TCP 0x02 +#define DNS_SO_CLOSE_OLD 0x04 +#define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD) + +static void dns_so_closefds(struct dns_socket *so, int which) { + if (DNS_SO_CLOSE_UDP & which) + dns_socketclose(&so->udp, &so->opts); + if (DNS_SO_CLOSE_TCP & which) + dns_socketclose(&so->tcp, &so->opts); + if (DNS_SO_CLOSE_OLD & which) { + unsigned i; + for (i = 0; i < so->onum; i++) + dns_socketclose(&so->old[i], &so->opts); + so->onum = 0; + free(so->old); + so->old = 0; + so->olim = 0; + } +} /* dns_so_closefds() */ + + +static void dns_so_destroy(struct dns_socket *); + +static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { + static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, }; + + *so = so_initializer; + so->type = type; + + if (opts) + so->opts = *opts; + + if (local) + memcpy(&so->local, local, dns_sa_len(local)); + + if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error))) + goto error; + + dns_k_permutor_init(&so->qids, 1, 65535); + + return so; +error: + dns_so_destroy(so); + + return 0; +} /* dns_so_init() */ + + +struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { + struct dns_socket *so; + + if (!(so = malloc(sizeof *so))) + goto syerr; + + if (!dns_so_init(so, local, type, opts, error)) + goto error; + + return so; +syerr: + *error = dns_syerr(); +error: + dns_so_close(so); + + return 0; +} /* dns_so_open() */ + + +static void dns_so_destroy(struct dns_socket *so) { + dns_so_reset(so); + dns_so_closefds(so, DNS_SO_CLOSE_ALL); +} /* dns_so_destroy() */ + + +void dns_so_close(struct dns_socket *so) { + if (!so) + return; + + dns_so_destroy(so); + + free(so); +} /* dns_so_close() */ + + +void dns_so_reset(struct dns_socket *so) { + dns_p_setptr(&so->answer, NULL); + + memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state)); +} /* dns_so_reset() */ + + +unsigned short dns_so_mkqid(struct dns_socket *so) { + return dns_k_permutor_step(&so->qids); +} /* dns_so_mkqid() */ + + +#define DNS_SO_MINBUF 768 + +static int dns_so_newanswer(struct dns_socket *so, size_t len) { + size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF); + void *p; + + if (!(p = realloc(so->answer, size))) + return dns_syerr(); + + so->answer = dns_p_init(p, size); + + return 0; +} /* dns_so_newanswer() */ + + +int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) { + struct dns_rr rr; + int error = -1; + + dns_so_reset(so); + + if ((error = dns_rr_parse(&rr, 12, Q))) + goto error; + + if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error))) + goto error; + /* + * NOTE: don't bail if expansion is too long; caller may be + * intentionally sending long names. However, we won't be able to + * verify it on return. + */ + + so->qtype = rr.type; + so->qclass = rr.class; + + if ((error = dns_so_newanswer(so, DNS_SO_MINBUF))) + goto syerr; + + memcpy(&so->remote, host, dns_sa_len(host)); + + so->query = Q; + so->qout = 0; + + dns_begin(&so->elapsed); + + if (dns_header(so->query)->qid == 0) + dns_header(so->query)->qid = dns_so_mkqid(so); + + so->qid = dns_header(so->query)->qid; + so->state = (so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT; + + so->stat.queries++; + + return 0; +syerr: + error = dns_syerr(); +error: + dns_so_reset(so); + + return error; +} /* dns_so_submit() */ + + +static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) { + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + struct dns_rr rr; + int error = -1; + + if (so->qid != dns_header(so->answer)->qid) + return DNS_EUNKNOWN; + + if (!dns_p_count(so->answer, DNS_S_QD)) + return DNS_EUNKNOWN; + + if (0 != dns_rr_parse(&rr, 12, so->answer)) + return DNS_EUNKNOWN; + + if (rr.type != so->qtype || rr.class != so->qclass) + return DNS_EUNKNOWN; + + if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error))) + return error; + else if (qlen >= sizeof qname || qlen != so->qlen) + return DNS_EUNKNOWN; + + if (0 != strcasecmp(so->qname, qname)) + return DNS_EUNKNOWN; + + return 0; +} /* dns_so_verify() */ + + +static _Bool dns_so_tcp_keep(struct dns_socket *so) { + struct sockaddr_storage remote; + + if (so->tcp == -1) + return 0; + + if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &(socklen_t){ sizeof remote })) + return 0; + + return 0 == dns_sa_cmp(&remote, &so->remote); +} /* dns_so_tcp_keep() */ + + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warray-bounds" +#endif + +static int dns_so_tcp_send(struct dns_socket *so) { + unsigned char *qsrc; + size_t qend; + long n; + + so->query->data[-2] = 0xff & (so->query->end >> 8); + so->query->data[-1] = 0xff & (so->query->end >> 0); + + qsrc = &so->query->data[-2] + so->qout; + qend = so->query->end + 2; + + while (so->qout < qend) { + if (0 > (n = dns_send(so->tcp, (void *)&qsrc[so->qout], qend - so->qout, 0))) + return dns_soerr(); + + so->qout += n; + so->stat.tcp.sent.bytes += n; + } + + so->stat.tcp.sent.count++; + + return 0; +} /* dns_so_tcp_send() */ + + +static int dns_so_tcp_recv(struct dns_socket *so) { + unsigned char *asrc; + size_t aend, alen; + int error; + long n; + + aend = so->alen + 2; + + while (so->apos < aend) { + asrc = &so->answer->data[-2]; + + if (0 > (n = recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0))) + return dns_soerr(); + else if (n == 0) + return DNS_EUNKNOWN; /* FIXME */ + + so->apos += n; + so->stat.tcp.rcvd.bytes += n; + + if (so->alen == 0 && so->apos >= 2) { + alen = ((0xff & so->answer->data[-2]) << 8) + | ((0xff & so->answer->data[-1]) << 0); + + if ((error = dns_so_newanswer(so, alen))) + return error; + + so->alen = alen; + aend = alen + 2; + } + } + + so->answer->end = so->alen; + so->stat.tcp.rcvd.count++; + + return 0; +} /* dns_so_tcp_recv() */ + +#if __clang__ +#pragma clang diagnostic pop +#endif + + +int dns_so_check(struct dns_socket *so) { + int error; + long n; + +retry: + switch (so->state) { + case DNS_SO_UDP_INIT: + so->state++; + case DNS_SO_UDP_CONN: + if (0 != connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote))) + goto soerr; + + so->state++; + case DNS_SO_UDP_SEND: + if (0 > (n = send(so->udp, (void *)so->query->data, so->query->end, 0))) + goto soerr; + + so->stat.udp.sent.bytes += n; + so->stat.udp.sent.count++; + + so->state++; + case DNS_SO_UDP_RECV: + if (0 > (n = recv(so->udp, (void *)so->answer->data, so->answer->size, 0))) + goto soerr; + + so->stat.udp.rcvd.bytes += n; + so->stat.udp.rcvd.count++; + + if ((so->answer->end = n) < 12) + goto trash; + + if ((error = dns_so_verify(so, so->answer))) + goto trash; + + so->state++; + case DNS_SO_UDP_DONE: + if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM) + return 0; + + so->state++; + case DNS_SO_TCP_INIT: + if (dns_so_tcp_keep(so)) { + so->state = DNS_SO_TCP_SEND; + + goto retry; + } + + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + + if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) + goto error; + + so->state++; + case DNS_SO_TCP_CONN: + if (0 != connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote))) { + if (dns_soerr() != DNS_EISCONN) + goto soerr; + } + + so->state++; + case DNS_SO_TCP_SEND: + if ((error = dns_so_tcp_send(so))) + goto error; + + so->state++; + case DNS_SO_TCP_RECV: + if ((error = dns_so_tcp_recv(so))) + goto error; + + so->state++; + case DNS_SO_TCP_DONE: + /* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */ + if (so->type != SOCK_STREAM) { + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + } + + if (so->answer->end < 12) + return DNS_EILLEGAL; + + if ((error = dns_so_verify(so, so->answer))) + goto error; + + return 0; + default: + error = DNS_EUNKNOWN; + + goto error; + } /* switch() */ + +trash: + goto retry; +soerr: + error = dns_soerr(); + + goto error; +error: + switch (error) { + case DNS_EINTR: + goto retry; + case DNS_EINPROGRESS: + /* FALL THROUGH */ + case DNS_EALREADY: + /* FALL THROUGH */ +#if DNS_EWOULDBLOCK != DNS_EAGAIN + case DNS_EWOULDBLOCK: + /* FALL THROUGH */ +#endif + error = DNS_EAGAIN; + + break; + } /* switch() */ + + return error; +} /* dns_so_check() */ + + +struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) { + struct dns_packet *answer; + + switch (so->state) { + case DNS_SO_UDP_DONE: + case DNS_SO_TCP_DONE: + answer = so->answer; + so->answer = 0; + + return answer; + default: + *error = DNS_EUNKNOWN; + + return 0; + } +} /* dns_so_fetch() */ + + +struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) { + struct dns_packet *A; + int error; + + if (!so->state) { + if ((error = dns_so_submit(so, Q, host))) + goto error; + } + + if ((error = dns_so_check(so))) + goto error; + + if (!(A = dns_so_fetch(so, &error))) + goto error; + + dns_so_reset(so); + + return A; +error: + *error_ = error; + + return 0; +} /* dns_so_query() */ + + +time_t dns_so_elapsed(struct dns_socket *so) { + return dns_elapsed(&so->elapsed); +} /* dns_so_elapsed() */ + + +void dns_so_clear(struct dns_socket *so) { + dns_so_closefds(so, DNS_SO_CLOSE_OLD); +} /* dns_so_clear() */ + + +static int dns_so_events2(struct dns_socket *so, enum dns_events type) { + int events = 0; + + switch (so->state) { + case DNS_SO_UDP_CONN: + case DNS_SO_UDP_SEND: + events |= DNS_POLLOUT; + + break; + case DNS_SO_UDP_RECV: + events |= DNS_POLLIN; + + break; + case DNS_SO_TCP_CONN: + case DNS_SO_TCP_SEND: + events |= DNS_POLLOUT; + + break; + case DNS_SO_TCP_RECV: + events |= DNS_POLLIN; + + break; + } /* switch() */ + + switch (type) { + case DNS_LIBEVENT: + return DNS_POLL2EV(events); + default: + return events; + } /* switch() */ +} /* dns_so_events2() */ + + +int dns_so_events(struct dns_socket *so) { + return dns_so_events2(so, so->opts.events); +} /* dns_so_events() */ + + +int dns_so_pollfd(struct dns_socket *so) { + switch (so->state) { + case DNS_SO_UDP_CONN: + case DNS_SO_UDP_SEND: + case DNS_SO_UDP_RECV: + return so->udp; + case DNS_SO_TCP_CONN: + case DNS_SO_TCP_SEND: + case DNS_SO_TCP_RECV: + return so->tcp; + } /* switch() */ + + return -1; +} /* dns_so_pollfd() */ + + +int dns_so_poll(struct dns_socket *so, int timeout) { + return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout); +} /* dns_so_poll() */ + + +const struct dns_stat *dns_so_stat(struct dns_socket *so) { + return &so->stat; +} /* dns_so_stat() */ + + +/* + * R E S O L V E R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +enum dns_res_state { + DNS_R_INIT, + DNS_R_GLUE, + DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */ + + DNS_R_FILE, /* Lookup in local hosts database */ + + DNS_R_CACHE, /* Lookup in application cache */ + DNS_R_SUBMIT, + DNS_R_CHECK, + DNS_R_FETCH, + + DNS_R_BIND, /* Lookup in the network */ + DNS_R_SEARCH, + DNS_R_HINTS, + DNS_R_ITERATE, + DNS_R_FOREACH_NS, + DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */ + DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */ + DNS_R_FOREACH_A, + DNS_R_QUERY_A, + DNS_R_CNAME0_A, + DNS_R_CNAME1_A, + + DNS_R_FINISH, + DNS_R_SMART0_A, + DNS_R_SMART1_A, + DNS_R_DONE, + DNS_R_SERVFAIL, +}; /* enum dns_res_state */ + + +#define DNS_R_MAXDEPTH 8 +#define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1) + +struct dns_resolver { + struct dns_socket so; + + struct dns_resolv_conf *resconf; + struct dns_hosts *hosts; + struct dns_hints *hints; + struct dns_cache *cache; + + dns_atomic_t refcount; + + /* Reset zeroes everything below here. */ + + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + + enum dns_type qtype; + enum dns_class qclass; + + struct dns_clock elapsed; + + dns_resconf_i_t search; + + struct dns_rr_i smart; + + struct dns_packet *nodata; /* answer if nothing better */ + + unsigned sp; + + struct dns_res_frame { + enum dns_res_state state; + + int error; + int which; /* (B)IND, (F)ILE; index into resconf->lookup */ + + unsigned attempts; + + struct dns_packet *query, *answer, *hints; + + struct dns_rr_i hints_i, hints_j; + struct dns_rr hints_ns, ans_cname; + } stack[DNS_R_MAXDEPTH]; +}; /* struct dns_resolver */ + + +static int dns_res_tcp2type(int tcp) { + switch (tcp) { + case DNS_RESCONF_TCP_ONLY: + return SOCK_STREAM; + case DNS_RESCONF_TCP_DISABLE: + return SOCK_DGRAM; + default: + return 0; + } +} /* dns_res_tcp2type() */ + +struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) { + static const struct dns_resolver R_initializer + = { .refcount = 1, }; + struct dns_resolver *R = 0; + int type, error; + + /* + * Grab ref count early because the caller may have passed us a mortal + * reference, and we want to do the right thing if we return early + * from an error. + */ + if (resconf) + dns_resconf_acquire(resconf); + if (hosts) + dns_hosts_acquire(hosts); + if (hints) + dns_hints_acquire(hints); + if (cache) + dns_cache_acquire(cache); + + /* + * Don't try to load it ourselves because a NULL object might be an + * error from, say, dns_resconf_root(), and loading + * dns_resconf_local() by default would create undesirable surpises. + */ + if (!resconf || !hosts || !hints) + goto _error; + + if (!(R = malloc(sizeof *R))) + goto syerr; + + *R = R_initializer; + type = dns_res_tcp2type(resconf->options.tcp); + + if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error)) + goto error; + + R->resconf = resconf; + R->hosts = hosts; + R->hints = hints; + R->cache = cache; + + return R; +syerr: + error = dns_syerr(); +error: + *_error = error; +_error: + dns_res_close(R); + + dns_resconf_close(resconf); + dns_hosts_close(hosts); + dns_hints_close(hints); + dns_cache_close(cache); + + return 0; +} /* dns_res_open() */ + + +struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) { + struct dns_resolv_conf *resconf = 0; + struct dns_hosts *hosts = 0; + struct dns_hints *hints = 0; + struct dns_resolver *res = 0; + + if (!(resconf = dns_resconf_local(error))) + goto epilog; + + if (!(hosts = dns_hosts_local(error))) + goto epilog; + + if (!(hints = dns_hints_local(resconf, error))) + goto epilog; + + if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error))) + goto epilog; + +epilog: + dns_resconf_close(resconf); + dns_hosts_close(hosts); + dns_hints_close(hints); + + return res; +} /* dns_res_stub() */ + + +static void dns_res_reset_frame(struct dns_resolver *R, struct dns_res_frame *frame) { + (void)R; + + dns_p_setptr(&frame->query, NULL); + dns_p_setptr(&frame->answer, NULL); + dns_p_setptr(&frame->hints, NULL); + + memset(frame, '\0', sizeof *frame); +} /* dns_res_reset_frame() */ + + +void dns_res_reset(struct dns_resolver *R) { + unsigned i; + + dns_so_reset(&R->so); + + dns_p_setptr(&R->nodata, NULL); + + for (i = 0; i < lengthof(R->stack); i++) + dns_res_reset_frame(R, &R->stack[i]); + + memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname)); +} /* dns_res_reset() */ + + +void dns_res_close(struct dns_resolver *R) { + if (!R || 1 < dns_res_release(R)) + return; + + dns_res_reset(R); + + dns_so_destroy(&R->so); + + dns_hints_close(R->hints); + dns_hosts_close(R->hosts); + dns_resconf_close(R->resconf); + dns_cache_close(R->cache); + + free(R); +} /* dns_res_close() */ + + +dns_refcount_t dns_res_acquire(struct dns_resolver *R) { + return dns_atomic_fetch_add(&R->refcount); +} /* dns_res_acquire() */ + + +dns_refcount_t dns_res_release(struct dns_resolver *R) { + return dns_atomic_fetch_sub(&R->refcount); +} /* dns_res_release() */ + + +struct dns_resolver *dns_res_mortal(struct dns_resolver *res) { + if (res) + dns_res_release(res); + return res; +} /* dns_res_mortal() */ + + +static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) { + size_t bufsiz = P0->end + P1->end; + struct dns_packet *P[3] = { P0, P1, 0 }; + struct dns_rr rr[3]; + int error, copy, i; + enum dns_section section; + +retry: + if (!(P[2] = dns_p_make(bufsiz, &error))) + goto error; + + dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) { + if ((error = dns_rr_copy(P[2], &rr[0], P[0]))) + goto error; + } + + for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) { + for (i = 0; i < 2; i++) { + dns_rr_foreach(&rr[i], P[i], .section = section) { + copy = 1; + + dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) { + if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) { + copy = 0; + + break; + } + } + + if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) { + if (error == DNS_ENOBUFS && bufsiz < 65535) { + dns_p_setptr(&P[2], NULL); + + bufsiz = DNS_PP_MAX(65535, bufsiz * 2); + + goto retry; + } + + goto error; + } + } /* foreach(rr) */ + } /* foreach(packet) */ + } /* foreach(section) */ + + return P[2]; +error: + *error_ = error; + + dns_p_free(P[2]); + + return 0; +} /* dns_res_merge() */ + + +static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { + struct dns_packet *P = dns_p_new(512); + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + enum dns_type qtype; + struct dns_rr rr; + unsigned sp; + int error; + + if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error)) + || qlen >= sizeof qname) + return 0; + + if (!(qtype = dns_rr_type(12, Q))) + return 0; + + if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0))) + return 0; + + for (sp = 0; sp <= R->sp; sp++) { + if (!R->stack[sp].answer) + continue; + + dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) { + rr.section = DNS_S_AN; + + if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) + return 0; + } + } + + if (dns_p_count(P, DNS_S_AN) > 0) + goto copy; + + /* Otherwise, look for a CNAME */ + for (sp = 0; sp <= R->sp; sp++) { + if (!R->stack[sp].answer) + continue; + + dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) { + rr.section = DNS_S_AN; + + if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) + return 0; + } + } + + if (!dns_p_count(P, DNS_S_AN)) + return 0; + +copy: + return dns_p_copy(dns_p_make(P->end, &error), P); +} /* dns_res_glue() */ + + +static struct dns_packet *dns_res_mkquery(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass, int *error_) { + struct dns_packet *Q = 0; + int error; + + if (!(Q = dns_p_init(malloc(DNS_P_QBUFSIZ), DNS_P_QBUFSIZ))) + goto syerr; + + if ((error = dns_p_push(Q, DNS_S_QD, qname, strlen(qname), qtype, qclass, 0, 0))) + goto error; + + dns_header(Q)->rd = !R->resconf->options.recurse; + + return Q; +syerr: + error = dns_syerr(); +error: + dns_p_free(Q); + + *error_ = error; + + return 0; +} /* dns_res_mkquery() */ + + +/* + * Sort NS records by three criteria: + * + * 1) Whether glue is present. + * 2) Whether glue record is original or of recursive lookup. + * 3) Randomly shuffle records which share the above criteria. + * + * NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will + * be added during an iteration. + * + * FIXME: Only groks A glue, not AAAA glue. + */ +static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + _Bool glued[2] = { 0 }; + struct dns_rr x = { 0 }, y = { 0 }; + struct dns_ns ns; + int cmp, error; + + if (!(error = dns_ns_parse(&ns, a, P))) + glued[0] = !!dns_rr_grep(&x, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); + + if (!(error = dns_ns_parse(&ns, b, P))) + glued[1] = !!dns_rr_grep(&y, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); + + if ((cmp = glued[1] - glued[0])) { + return cmp; + } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { + return cmp; + } else { + return dns_rr_i_shuffle(a, b, i, P); + } +} /* dns_res_nameserv_cmp() */ + + +#define dgoto(sp, i) \ + do { R->stack[(sp)].state = (i); goto exec; } while (0) + +static int dns_res_exec(struct dns_resolver *R) { + struct dns_res_frame *F; + struct dns_packet *P; + char host[DNS_D_MAXNAME + 1]; + size_t len; + struct dns_rr rr; + struct sockaddr_in sin; + int error; + +exec: + + F = &R->stack[R->sp]; + + switch (F->state) { + case DNS_R_INIT: + F->state++; + case DNS_R_GLUE: + if (R->sp == 0) + dgoto(R->sp, DNS_R_SWITCH); + + if (!F->query) + goto noquery; + + if (!(F->answer = dns_res_glue(R, F->query))) + dgoto(R->sp, DNS_R_SWITCH); + + if (!(len = dns_d_expand(host, sizeof host, 12, F->query, &error))) + goto error; + else if (len >= sizeof host) + goto toolong; + + dns_rr_foreach(&rr, F->answer, .name = host, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) { + dgoto(R->sp, DNS_R_FINISH); + } + + dns_rr_foreach(&rr, F->answer, .name = host, .type = DNS_T_CNAME, .section = DNS_S_AN) { + F->ans_cname = rr; + + dgoto(R->sp, DNS_R_CNAME0_A); + } + + F->state++; + case DNS_R_SWITCH: + while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) { + switch (R->resconf->lookup[F->which++]) { + case 'b': case 'B': + dgoto(R->sp, DNS_R_BIND); + case 'f': case 'F': + dgoto(R->sp, DNS_R_FILE); + case 'c': case 'C': + if (R->cache) + dgoto(R->sp, DNS_R_CACHE); + + break; + default: + break; + } + } + + /* + * FIXME: Examine more closely whether our logic is correct + * and DNS_R_SERVFAIL is the correct default response. + * + * Case 1: We got here because we never got an answer on the + * wire. All queries timed-out and we reached maximum + * attempts count. See DNS_R_FOREACH_NS. In that case + * DNS_R_SERVFAIL is the correct state, unless we want to + * return DNS_ETIMEDOUT. + * + * Case 2: We were a stub resolver and got an unsatisfactory + * answer (empty ANSWER section) which caused us to jump + * back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We + * return the answer returned from the wire, which we + * stashed in R->nodata. + * + * Case 3: We reached maximum attempts count as in case #1, + * but never got an authoritative response which caused us + * to short-circuit. See end of DNS_R_QUERY_A case. We + * should probably prepare R->nodata as in case #2. + */ + if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */ + dns_p_movptr(&F->answer, &R->nodata); + dgoto(R->sp, DNS_R_FINISH); + } + + dgoto(R->sp, DNS_R_SERVFAIL); + case DNS_R_FILE: + if (R->sp > 0) { + if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) + goto error; + + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + } else { + R->search = 0; + + while ((len = dns_resconf_search(host, sizeof host, R->qname, R->qlen, R->resconf, &R->search))) { +/* + * FIXME: Some sort of bug, either with this code or with GCC 3.3.5 on + * OpenBSD 4.4, overwites the stack guard. If the bug is in this file, it + * appears to be localized somewhere around here. It can also be mitigated + * in dns_hosts_query(). In any event, the bug manifests only when using + * compound literals. alloca(), malloc(), calloc(), etc, all work fine. + * Valgrind (tested on Linux) cannot detect any issues, but stack issues are + * not Valgrind's forte. Neither can I spot anything in the assembly, but + * that's not my forte. + */ +#if __OpenBSD__ && __GNUC__ + struct dns_packet *query = __builtin_alloca(DNS_P_QBUFSIZ); + + dns_p_init(query, DNS_P_QBUFSIZ); +#else + struct dns_packet *query = dns_p_new(DNS_P_QBUFSIZ); +#endif + + if ((error = dns_p_push(query, DNS_S_QD, host, len, R->qtype, R->qclass, 0, 0))) + goto error; + + if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, query, &error))) + goto error; + + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + } + } + + dgoto(R->sp, DNS_R_SWITCH); + case DNS_R_CACHE: + error = 0; + + if (!F->query && !(F->query = dns_res_mkquery(R, R->qname, R->qtype, R->qclass, &error))) + goto error; + + if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) { + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + + dgoto(R->sp, DNS_R_SWITCH); + } else if (error) + goto error; + + F->state++; + case DNS_R_SUBMIT: + if ((error = R->cache->submit(F->query, R->cache))) + goto error; + + F->state++; + case DNS_R_CHECK: + if ((error = R->cache->check(R->cache))) + goto error; + + F->state++; + case DNS_R_FETCH: + error = 0; + + if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) { + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + + dgoto(R->sp, DNS_R_SWITCH); + } else if (error) + goto error; + + dgoto(R->sp, DNS_R_SWITCH); + case DNS_R_BIND: + if (R->sp > 0) { + if (!F->query) + goto noquery; + + dgoto(R->sp, DNS_R_HINTS); + } + + R->search = 0; + + F->state++; + case DNS_R_SEARCH: + /* + * XXX: We probably should only apply the domain search + * algorithm if R->sp == 0. + */ + if (!(len = dns_resconf_search(host, sizeof host, R->qname, R->qlen, R->resconf, &R->search))) + dgoto(R->sp, DNS_R_SWITCH); + + if (!(P = dns_p_make(DNS_P_QBUFSIZ, &error))) + goto error; + + dns_header(P)->rd = !R->resconf->options.recurse; + + dns_p_setptr(&F->query, P); + + if ((error = dns_p_push(F->query, DNS_S_QD, host, len, R->qtype, R->qclass, 0, 0))) + goto error; + + F->state++; + case DNS_R_HINTS: + if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error))) + goto error; + + F->state++; + case DNS_R_ITERATE: + dns_rr_i_init(&F->hints_i, F->hints); + + F->hints_i.section = DNS_S_AUTHORITY; + F->hints_i.type = DNS_T_NS; + F->hints_i.sort = &dns_res_nameserv_cmp; + F->hints_i.args[0] = F->hints->end; + + F->state++; + case DNS_R_FOREACH_NS: + dns_rr_i_save(&F->hints_i); + + /* Load our next nameserver host. */ + if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) { + if (++F->attempts < R->resconf->options.attempts) + dgoto(R->sp, DNS_R_ITERATE); + + dgoto(R->sp, DNS_R_SWITCH); + } + + dns_rr_i_init(&F->hints_j, F->hints); + + /* Assume there are glue records */ + dgoto(R->sp, DNS_R_FOREACH_A); + case DNS_R_RESOLV0_NS: + /* Have we reached our max depth? */ + if (&F[1] >= endof(R->stack)) + dgoto(R->sp, DNS_R_FOREACH_NS); + + dns_res_reset_frame(R, &F[1]); + + if (!(F[1].query = dns_p_make(DNS_P_QBUFSIZ, &error))) + goto error; + + if ((error = dns_ns_parse((struct dns_ns *)host, &F->hints_ns, F->hints))) + goto error; + + if ((error = dns_p_push(F[1].query, DNS_S_QD, host, strlen(host), DNS_T_A, DNS_C_IN, 0, 0))) + goto error; + + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + case DNS_R_RESOLV1_NS: + if (!(len = dns_d_expand(host, sizeof host, 12, F[1].query, &error))) + goto error; + else if (len >= sizeof host) + goto toolong; + + dns_rr_foreach(&rr, F[1].answer, .name = host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) { + rr.section = DNS_S_AR; + + if ((error = dns_rr_copy(F->hints, &rr, F[1].answer))) + goto error; + + dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */ + } + + dgoto(R->sp, DNS_R_FOREACH_NS); + case DNS_R_FOREACH_A: + /* + * NOTE: Iterator initialized in DNS_R_FOREACH_NS because + * this state is re-entrant, but we need to reset + * .name to a valid pointer each time. + */ + if ((error = dns_ns_parse((struct dns_ns *)host, &F->hints_ns, F->hints))) + goto error; + + F->hints_j.name = host; + F->hints_j.type = DNS_T_A; + F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; + + if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { + if (!dns_rr_i_count(&F->hints_j)) + dgoto(R->sp, DNS_R_RESOLV0_NS); + + dgoto(R->sp, DNS_R_FOREACH_NS); + } + + sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin.sin_len = dns_af_len(sin.sin_family); +#endif + + if ((error = dns_a_parse((struct dns_a *)&sin.sin_addr, &rr, F->hints))) + goto error; + + if (R->sp == 0) + sin.sin_port = dns_hints_port(R->hints, AF_INET, (struct sockaddr *)&sin.sin_addr); + else + sin.sin_port = htons(53); + + if (DNS_DEBUG) { + char addr[INET_ADDRSTRLEN + 1]; + dns_a_print(addr, sizeof addr, (struct dns_a *)&sin.sin_addr); + DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", host, addr, R->sp); + } + + if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin))) + goto error; + + F->state++; + case DNS_R_QUERY_A: + if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) + dgoto(R->sp, DNS_R_FOREACH_A); + + if ((error = dns_so_check(&R->so))) + goto error; + + if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) + goto error; + + if (DNS_DEBUG) { + DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); + } + + if ((error = dns_rr_parse(&rr, 12, F->query))) + goto error; + + if (!(len = dns_d_expand(host, sizeof host, rr.dn.p, F->query, &error))) + goto error; + else if (len >= sizeof host) + goto toolong; + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = host, .type = rr.type) { + dgoto(R->sp, DNS_R_FINISH); /* Found */ + } + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = host, .type = DNS_T_CNAME) { + F->ans_cname = rr; + + dgoto(R->sp, DNS_R_CNAME0_A); + } + + /* + * XXX: The condition here should probably check whether + * R->sp == 0, because DNS_R_SEARCH runs regardless of + * options.recurse. See DNS_R_BIND. + */ + if (!R->resconf->options.recurse) { + /* Make first answer our tentative answer */ + if (!R->nodata) + dns_p_movptr(&R->nodata, &F->answer); + + dgoto(R->sp, DNS_R_SEARCH); + } + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { + dns_p_movptr(&F->hints, &F->answer); + + dgoto(R->sp, DNS_R_ITERATE); + } + + /* XXX: Should this go further up? */ + if (dns_header(F->answer)->aa) + dgoto(R->sp, DNS_R_FINISH); + + /* XXX: Should we copy F->answer to R->nodata? */ + + dgoto(R->sp, DNS_R_FOREACH_A); + case DNS_R_CNAME0_A: + if (&F[1] >= endof(R->stack)) + dgoto(R->sp, DNS_R_FINISH); + + if ((error = dns_cname_parse((struct dns_cname *)host, &F->ans_cname, F->answer))) + goto error; + + dns_res_reset_frame(R, &F[1]); + + if (!(F[1].query = dns_p_make(DNS_P_QBUFSIZ, &error))) + goto error; + + if ((error = dns_p_push(F[1].query, DNS_S_QD, host, strlen(host), dns_rr_type(12, F->query), DNS_C_IN, 0, 0))) + goto error; + + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + case DNS_R_CNAME1_A: + if (!(P = dns_res_merge(F->answer, F[1].answer, &error))) + goto error; + + dns_p_setptr(&F->answer, P); + + dgoto(R->sp, DNS_R_FINISH); + case DNS_R_FINISH: + if (!F->answer) + goto noanswer; + + if (!R->resconf->options.smart || R->sp > 0) + dgoto(R->sp, DNS_R_DONE); + + R->smart.section = DNS_S_AN; + R->smart.type = R->qtype; + + dns_rr_i_init(&R->smart, F->answer); + + F->state++; + case DNS_R_SMART0_A: + if (&F[1] >= endof(R->stack)) + dgoto(R->sp, DNS_R_DONE); + + while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) { + union { + struct dns_ns ns; + struct dns_mx mx; + struct dns_srv srv; + } rd; + const char *qname; + enum dns_type qtype; + enum dns_class qclass; + + switch (rr.type) { + case DNS_T_NS: + if ((error = dns_ns_parse(&rd.ns, &rr, F->answer))) + goto error; + + qname = rd.ns.host; + qtype = DNS_T_A; + qclass = DNS_C_IN; + + break; + case DNS_T_MX: + if ((error = dns_mx_parse(&rd.mx, &rr, F->answer))) + goto error; + + qname = rd.mx.host; + qtype = DNS_T_A; + qclass = DNS_C_IN; + + break; + case DNS_T_SRV: + if ((error = dns_srv_parse(&rd.srv, &rr, F->answer))) + goto error; + + qname = rd.srv.target; + qtype = DNS_T_A; + qclass = DNS_C_IN; + + break; + default: + continue; + } /* switch() */ + + dns_res_reset_frame(R, &F[1]); + + if (!(F[1].query = dns_res_mkquery(R, qname, qtype, qclass, &error))) + goto error; + + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + } /* while() */ + + /* + * NOTE: SMTP specification says to fallback to A record. + * + * XXX: Should we add a mock MX answer? + */ + if (R->qtype == DNS_T_MX && R->smart.state.count == 0) { + dns_res_reset_frame(R, &F[1]); + + if (!(F[1].query = dns_res_mkquery(R, R->qname, DNS_T_A, DNS_C_IN, &error))) + goto error; + + R->smart.state.count++; + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + } + + dgoto(R->sp, DNS_R_DONE); + case DNS_R_SMART1_A: + if (!F[1].answer) + goto noanswer; + + /* + * FIXME: For CNAME chains (which are typically illegal in + * this context), we should rewrite the record host name + * to the original smart qname. All the user cares about + * is locating that A/AAAA record. + */ + dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) { + rr.section = DNS_S_AR; + + if (dns_rr_exists(&rr, F[1].answer, F->answer)) + continue; + + while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) { + if (error != DNS_ENOBUFS) + goto error; + if ((error = dns_p_grow(&F->answer))) + goto error; + } + } + + dgoto(R->sp, DNS_R_SMART0_A); + case DNS_R_DONE: + if (!F->answer) + goto noanswer; + + if (R->sp > 0) + dgoto(--R->sp, F[-1].state); + + break; + case DNS_R_SERVFAIL: + if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error))) + goto error; + + dns_header(F->answer)->qr = 1; + dns_header(F->answer)->rcode = DNS_RC_SERVFAIL; + + if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0))) + goto error; + + dgoto(R->sp, DNS_R_DONE); + default: + error = EINVAL; + + goto error; + } /* switch () */ + + return 0; +noquery: + error = DNS_ENOQUERY; + + goto error; +noanswer: + error = DNS_ENOANSWER; + + goto error; +toolong: + error = DNS_EILLEGAL; + + /* FALL THROUGH */ +error: + return error; +} /* dns_res_exec() */ + +#undef goto + + +void dns_res_clear(struct dns_resolver *R) { + switch (R->stack[R->sp].state) { + case DNS_R_CHECK: + R->cache->clear(R->cache); + break; + default: + dns_so_clear(&R->so); + break; + } +} /* dns_res_clear() */ + + +static int dns_res_events2(struct dns_resolver *R, enum dns_events type) { + int events; + + switch (R->stack[R->sp].state) { + case DNS_R_CHECK: + events = R->cache->events(R->cache); + + return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events; + default: + return dns_so_events2(&R->so, type); + } +} /* dns_res_events2() */ + + +int dns_res_events(struct dns_resolver *R) { + return dns_res_events2(R, R->so.opts.events); +} /* dns_res_events() */ + + +int dns_res_pollfd(struct dns_resolver *R) { + switch (R->stack[R->sp].state) { + case DNS_R_CHECK: + return R->cache->pollfd(R->cache); + default: + return dns_so_pollfd(&R->so); + } +} /* dns_res_pollfd() */ + + +time_t dns_res_timeout(struct dns_resolver *R) { + time_t elapsed; + + switch (R->stack[R->sp].state) { +#if 0 + case DNS_R_QUERY_AAAA: +#endif + case DNS_R_QUERY_A: + elapsed = dns_so_elapsed(&R->so); + + if (elapsed <= dns_resconf_timeout(R->resconf)) + return R->resconf->options.timeout - elapsed; + + break; + default: + break; + } /* switch() */ + + /* + * NOTE: We're not in a pollable state, or the user code hasn't + * called dns_res_check properly. The calling code is probably + * broken. Put them into a slow-burn pattern. + */ + return 1; +} /* dns_res_timeout() */ + + +time_t dns_res_elapsed(struct dns_resolver *R) { + return dns_elapsed(&R->elapsed); +} /* dns_res_elapsed() */ + + +int dns_res_poll(struct dns_resolver *R, int timeout) { + return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout); +} /* dns_res_poll() */ + + +int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) { + dns_res_reset(R); + + /* Don't anchor; that can conflict with searchlist generation. */ + dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0); + + R->qtype = qtype; + R->qclass = qclass; + + dns_begin(&R->elapsed); + + return 0; +} /* dns_res_submit2() */ + + +int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) { + return dns_res_submit2(R, qname, strlen(qname), qtype, qclass); +} /* dns_res_submit() */ + + +int dns_res_check(struct dns_resolver *R) { + int error; + + if (R->stack[0].state != DNS_R_DONE) { + if ((error = dns_res_exec(R))) + return error; + } + + return 0; +} /* dns_res_check() */ + + +struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *error) { + struct dns_packet *answer; + + if (R->stack[0].state != DNS_R_DONE) { + *error = DNS_EUNKNOWN; + + return 0; + } + + if (!(answer = R->stack[0].answer)) { + *error = DNS_EFETCHED; + + return 0; + } + + R->stack[0].answer = 0; + + return answer; +} /* dns_res_fetch() */ + + +struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) { + int error; + + if ((error = dns_res_submit(res, qname, qtype, qclass))) + goto error; + + while ((error = dns_res_check(res))) { + if (dns_res_elapsed(res) > timeout) + error = DNS_ETIMEDOUT; + + if (error != DNS_EAGAIN) + goto error; + + if ((error = dns_res_poll(res, 1))) + goto error; + } + + return dns_res_fetch(res, error_); +error: + *error_ = error; + + return 0; +} /* dns_res_query() */ + + +const struct dns_stat *dns_res_stat(struct dns_resolver *res) { + return dns_so_stat(&res->so); +} /* dns_res_stat() */ + + +void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) { + dns_hints_acquire(hints); /* acquire first in case same hints object */ + dns_hints_close(res->hints); + res->hints = hints; +} /* dns_res_sethints() */ + + +/* + * A D D R I N F O R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_addrinfo { + struct addrinfo hints; + struct dns_resolver *res; + + char qname[DNS_D_MAXNAME + 1]; + enum dns_type qtype; + unsigned short qport, port; + + struct dns_packet *answer; + struct dns_packet *glue; + + struct dns_rr_i i, g; + struct dns_rr rr; + + char cname[DNS_D_MAXNAME + 1]; + + int state; + int found; + + struct dns_stat st; +}; /* struct dns_addrinfo */ + + +static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) { + const char *cp = serv; + unsigned long n = 0; + + while (*cp >= '0' && *cp <= '9' && n < 65536) { + n *= 10; + n += *cp++ - '0'; + } + + if (*cp == '\0') { + if (cp == serv || n >= 65536) + return DNS_ESERVICE; + + *port = n; + + return 0; + } + + if (hints->ai_flags & AI_NUMERICSERV) + return DNS_ESERVICE; + + /* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */ + + return DNS_ESERVICE; +} /* dns_ai_parseport() */ + + +struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *error_) { + static const struct dns_addrinfo ai_initializer; + struct dns_addrinfo *ai; + int error; + + if (res) { + dns_res_acquire(res); + } else if (!(hints->ai_flags & AI_NUMERICHOST)) { + /* + * NOTE: it's assumed that *_error is set from a previous + * API function call, such as dns_res_stub(). Should change + * this semantic, but it's applied elsewhere, too. + */ + return NULL; + } + + if (!(ai = malloc(sizeof *ai))) + goto syerr; + + *ai = ai_initializer; + ai->hints = *hints; + + ai->res = res; + res = NULL; + + if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname)) + { error = ENAMETOOLONG; goto error; } + + ai->qtype = qtype; + ai->qport = 0; + + if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints))) + goto error; + + ai->port = ai->qport; + + return ai; +syerr: + error = dns_syerr(); +error: + *error_ = error; + + dns_ai_close(ai); + dns_res_close(res); + + return NULL; +} /* dns_ai_open() */ + + +void dns_ai_close(struct dns_addrinfo *ai) { + if (!ai) + return; + + dns_res_close(ai->res); + + if (ai->answer != ai->glue) + dns_p_free(ai->glue); + + dns_p_free(ai->answer); + free(ai); +} /* dns_ai_close() */ + + +static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) { + struct sockaddr *saddr; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + const char *cname; + size_t clen; + + switch (type) { + case DNS_T_A: + saddr = memset(&sin, '\0', sizeof sin); + + sin.sin_family = AF_INET; + sin.sin_port = htons(ai->port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin.sin_len = dns_af_len(sin.sin_family); +#endif + + memcpy(&sin.sin_addr, any, sizeof sin.sin_addr); + + break; + case DNS_T_AAAA: + saddr = memset(&sin6, '\0', sizeof sin6); + + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(ai->port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = dns_af_len(sin6.sin6_family); +#endif + + memcpy(&sin6.sin6_addr, any, sizeof sin6.sin6_addr); + + break; + default: + return EINVAL; + } /* switch() */ + + if (ai->hints.ai_flags & AI_CANONNAME) { + cname = (*ai->cname)? ai->cname : ai->qname; + clen = strlen(cname); + } else { + cname = NULL; + clen = 0; + } + + if (!(*ent = malloc(sizeof **ent + dns_sa_len(saddr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0)))) + return dns_syerr(); + + memset(*ent, '\0', sizeof **ent); + + (*ent)->ai_family = saddr->sa_family; + (*ent)->ai_socktype = ai->hints.ai_socktype; + (*ent)->ai_protocol = ai->hints.ai_protocol; + + (*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, saddr, dns_sa_len(saddr)); + (*ent)->ai_addrlen = dns_sa_len(saddr); + + if (ai->hints.ai_flags & AI_CANONNAME) + (*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(saddr), cname, clen + 1); + + ai->found++; + + return 0; +} /* dns_ai_setent() */ + + +enum dns_ai_state { + DNS_AI_S_INIT, + DNS_AI_S_NUMERIC, + DNS_AI_S_SUBMIT, + DNS_AI_S_CHECK, + DNS_AI_S_FETCH, + DNS_AI_S_FOREACH_I, + DNS_AI_S_FOREACH_G, + DNS_AI_S_SUBMIT_G, + DNS_AI_S_CHECK_G, + DNS_AI_S_FETCH_G, + DNS_AI_S_DONE, +}; /* enum dns_ai_state */ + +#define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0) + +int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) { + struct dns_packet *ans, *glue; + struct dns_rr rr; + char qname[DNS_D_MAXNAME + 1]; + union dns_any any; + size_t len; + int error; + + *ent = 0; + +exec: + + switch (ai->state) { + case DNS_AI_S_INIT: + ai->state++; + case DNS_AI_S_NUMERIC: + if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) { + ai->state = DNS_AI_S_DONE; + + return dns_ai_setent(ent, &any, DNS_T_A, ai); + } + + if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) { + ai->state = DNS_AI_S_DONE; + + return dns_ai_setent(ent, &any, DNS_T_AAAA, ai); + } + + if (ai->hints.ai_flags & AI_NUMERICHOST) + dns_ai_goto(DNS_AI_S_DONE); + + ai->state++; + case DNS_AI_S_SUBMIT: + assert(ai->res); + + if ((error = dns_res_submit(ai->res, ai->qname, ai->qtype, DNS_C_IN))) + return error; + + ai->state++; + case DNS_AI_S_CHECK: + if ((error = dns_res_check(ai->res))) + return error; + + ai->state++; + case DNS_AI_S_FETCH: + if (!(ai->answer = dns_res_fetch(ai->res, &error))) + return error; + + if ((error = dns_p_study(ai->answer))) + return error; + + ai->glue = ai->answer; + + dns_rr_i_init(&ai->i, ai->answer); + + ai->i.section = DNS_S_AN; + ai->i.type = ai->qtype; + ai->i.sort = &dns_rr_i_order; + + ai->state++; + case DNS_AI_S_FOREACH_I: + /* Search generator may have changed our qname. */ + if (!(len = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error))) + return error; + else if (len >= sizeof qname) + return DNS_EILLEGAL; + + if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, strlen(qname), ai->answer, &error)) + return error; + + ai->i.name = ai->cname; + + if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error)) + dns_ai_goto(DNS_AI_S_DONE); + + if ((error = dns_any_parse(&any, &rr, ai->answer))) + return error; + + ai->port = ai->qport; + + switch (rr.type) { + case DNS_T_A: + case DNS_T_AAAA: + return dns_ai_setent(ent, &any, rr.type, ai); + default: + if (!dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type)) + dns_ai_goto(DNS_AI_S_FOREACH_I); + + /* + * Find the "real" canonical name. Some authorities + * publish aliases where an RFC defines a canonical + * name. We trust that the resolver followed any + * CNAME chains on it's own, regardless of whether + * the "smart" option is enabled. + */ + if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, strlen(ai->cname), ai->answer, &error)) + return error; + + if (rr.type == DNS_T_SRV) + ai->port = any.srv.port; + + break; + } /* switch() */ + + dns_rr_i_init(&ai->g, ai->glue); + + ai->g.section = DNS_S_ALL & ~DNS_S_QD; + ai->g.name = ai->cname; + ai->g.type = (ai->hints.ai_family == AF_INET6)? DNS_T_AAAA : DNS_T_A; + + ai->state++; + case DNS_AI_S_FOREACH_G: + if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) { + if (dns_rr_i_count(&ai->g) > 0) + dns_ai_goto(DNS_AI_S_FOREACH_I); + else + dns_ai_goto(DNS_AI_S_SUBMIT_G); + } + + if ((error = dns_any_parse(&any, &rr, ai->glue))) + return error; + + return dns_ai_setent(ent, &any, rr.type, ai); + case DNS_AI_S_SUBMIT_G: + if (dns_rr_grep(&rr, 1, dns_rr_i_new(ai->glue, .section = DNS_S_QD, .name = ai->g.name, .type = ai->g.type), ai->glue, &error)) + dns_ai_goto(DNS_AI_S_FOREACH_I); + + if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) + return error; + + ai->state++; + case DNS_AI_S_CHECK_G: + if ((error = dns_res_check(ai->res))) + return error; + + ai->state++; + case DNS_AI_S_FETCH_G: + if (!(ans = dns_res_fetch(ai->res, &error))) + return error; + + dns_p_study(ans); + + glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error); + + dns_p_free(ans); + + if (!glue) + return error; + + if (ai->glue != ai->answer) + dns_p_free(ai->glue); + + ai->glue = glue; + + dns_rr_i_init(&ai->g, ai->glue); + + /* ai->g.name should already point to ai->cname */ + if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, strlen(ai->cname), ai->glue, &error)) + dns_ai_goto(DNS_AI_S_FOREACH_I); + + /* NOTE: Keep all the other iterator filters */ + + dns_ai_goto(DNS_AI_S_FOREACH_G); + case DNS_AI_S_DONE: + if (ai->found) { + return ENOENT; /* TODO: Just return 0 */ + } else if (ai->answer) { + switch (dns_header(ai->answer)->rcode) { + case DNS_RC_NOERROR: + /* FALL THROUGH */ + case DNS_RC_NXDOMAIN: + return DNS_ENONAME; + default: + return DNS_EFAIL; + } + } else { + return DNS_EFAIL; + } + default: + return EINVAL; + } /* switch() */ +} /* dns_ai_nextent() */ + + +time_t dns_ai_elapsed(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_elapsed(ai->res) : 0; +} /* dns_ai_elapsed() */ + + +void dns_ai_clear(struct dns_addrinfo *ai) { + if (ai->res) + dns_res_clear(ai->res); +} /* dns_ai_clear() */ + + +int dns_ai_events(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_events(ai->res) : 0; +} /* dns_ai_events() */ + + +int dns_ai_pollfd(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_pollfd(ai->res) : -1; +} /* dns_ai_pollfd() */ + + +time_t dns_ai_timeout(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_timeout(ai->res) : 0; +} /* dns_ai_timeout() */ + + +int dns_ai_poll(struct dns_addrinfo *ai, int timeout) { + return (ai->res)? dns_res_poll(ai->res, timeout) : 0; +} /* dns_ai_poll() */ + + +size_t dns_ai_print(void *dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) { + char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1]; + size_t cp = 0; + + cp += dns__printstring(dst, lim, cp, "[ "); + cp += dns__printstring(dst, lim, cp, ai->qname); + cp += dns__printstring(dst, lim, cp, " IN "); + cp += dns__printstring(dst, lim, cp, dns_strtype(ai->qtype)); + cp += dns__printstring(dst, lim, cp, " ]\n"); + + cp += dns__printstring(dst, lim, cp, ".ai_family = "); + + switch (ent->ai_family) { + case AF_INET: + cp += dns__printstring(dst, lim, cp, "AF_INET"); + break; + case AF_INET6: + cp += dns__printstring(dst, lim, cp, "AF_INET6"); + break; + default: + cp += dns__print10(dst, lim, cp, ent->ai_family, 0); + break; + } + + cp += dns__printchar(dst, lim, cp, '\n'); + + cp += dns__printstring(dst, lim, cp, ".ai_socktype = "); + + switch (ent->ai_socktype) { + case SOCK_STREAM: + cp += dns__printstring(dst, lim, cp, "SOCK_STREAM"); + break; + case SOCK_DGRAM: + cp += dns__printstring(dst, lim, cp, "SOCK_DGRAM"); + break; + default: + cp += dns__print10(dst, lim, cp, ent->ai_socktype, 0); + break; + } + + cp += dns__printchar(dst, lim, cp, '\n'); + + cp += dns__printstring(dst, lim, cp, ".ai_addr = ["); + + dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr); + + cp += dns__printstring(dst, lim, cp, addr); + cp += dns__printstring(dst, lim, cp, "]:"); + + cp += dns__print10(dst, lim, cp, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0); + cp += dns__printchar(dst, lim, cp, '\n'); + + cp += dns__printstring(dst, lim, cp, ".ai_canonname = "); + cp += dns__printstring(dst, lim, cp, (ent->ai_canonname)? ent->ai_canonname : "[NULL]"); + cp += dns__printchar(dst, lim, cp, '\n'); + + dns__printnul(dst, lim, cp); + + return cp; +} /* dns_ai_print() */ + + +const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_stat(ai->res) : &ai->st; +} /* dns_ai_stat() */ + + +/* + * M I S C E L L A N E O U S R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static const struct { + char name[16]; + enum dns_section type; +} dns_sections[] = { + { "QUESTION", DNS_S_QUESTION }, + { "QD", DNS_S_QUESTION }, + { "ANSWER", DNS_S_ANSWER }, + { "AN", DNS_S_ANSWER }, + { "AUTHORITY", DNS_S_AUTHORITY }, + { "NS", DNS_S_AUTHORITY }, + { "ADDITIONAL", DNS_S_ADDITIONAL }, + { "AR", DNS_S_ADDITIONAL }, +}; + +const char *(dns_strsection)(enum dns_section section, void *dst, size_t lim) { + unsigned i, p = 0; + + for (i = 0; i < lengthof(dns_sections); i++) { + if (dns_sections[i].type & section) { + if (p > 0) + p += dns__printchar(dst, lim, p, '|'); + + p += dns__printstring(dst, lim, p, dns_sections[i].name); + + section &= ~dns_sections[i].type; + } + } + + if (!p) + p += dns__print10(dst, lim, 0, (0xffff & section), 0); + + dns__printnul(dst, lim, p); + + return dst; +} /* dns_strsection() */ + + +enum dns_section dns_isection(const char *src) { + enum dns_section section = 0; + char sbuf[128]; + char *name, *next; + unsigned i; + + dns_strlcpy(sbuf, src, sizeof sbuf); + next = sbuf; + + while ((name = dns_strsep(&next, "|+, \t"))) { + for (i = 0; i < lengthof(dns_sections); i++) { + if (!strcasecmp(dns_sections[i].name, name)) { + section |= dns_sections[i].type; + break; + } + } + } + + return section; +} /* dns_isection() */ + + +static const struct { + char name[8]; + enum dns_class type; +} dns_classes[] = { + { "IN", DNS_C_IN }, +}; + +const char *(dns_strclass)(enum dns_class type, void *dst, size_t lim) { + unsigned i; + + for (i = 0; i < lengthof(dns_classes); i++) { + if (dns_classes[i].type == type) { + dns__printnul(dst, lim, dns__printstring(dst, lim, 0, dns_classes[i].name)); + + return dst; + } + } + + dns__printnul(dst, lim, dns__print10(dst, lim, 0, (0xffff & type), 0)); + + return dst; +} /* dns_strclass() */ + + +enum dns_class dns_iclass(const char *name) { + unsigned i; + + for (i = 0; i < lengthof(dns_classes); i++) { + if (!strcasecmp(dns_classes[i].name, name)) + return dns_classes[i].type; + } + + return 0; +} /* dns_iclass() */ + + +const char *(dns_strtype)(enum dns_type type, void *dst, size_t lim) { + unsigned i; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == type) { + dns__printnul(dst, lim, dns__printstring(dst, lim, 0, dns_rrtypes[i].name)); + + return dst; + } + } + + dns__printnul(dst, lim, dns__print10(dst, lim, 0, (0xffff & type), 0)); + + return dst; +} /* dns_strtype() */ + + +enum dns_type dns_itype(const char *type) { + unsigned i; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (!strcasecmp(dns_rrtypes[i].name, type)) + return dns_rrtypes[i].type; + } + + return 0; +} /* dns_itype() */ + + +static char dns_opcodes[16][16] = { + [DNS_OP_QUERY] = "QUERY", + [DNS_OP_IQUERY] = "IQUERY", + [DNS_OP_STATUS] = "STATUS", + [DNS_OP_NOTIFY] = "NOTIFY", + [DNS_OP_UPDATE] = "UPDATE", +}; + +const char *dns_stropcode(enum dns_opcode opcode) { + opcode &= 0xf; + + if ('\0' == dns_opcodes[opcode][0]) + dns__printnul(dns_opcodes[opcode], sizeof dns_opcodes[opcode], dns__print10(dns_opcodes[opcode], sizeof dns_opcodes[opcode], 0, opcode, 0)); + + return dns_opcodes[opcode]; +} /* dns_stropcode() */ + + +enum dns_opcode dns_iopcode(const char *name) { + unsigned opcode; + + for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) { + if (!strcasecmp(name, dns_opcodes[opcode])) + return opcode; + } + + return lengthof(dns_opcodes) - 1; +} /* dns_iopcode() */ + + +static char dns_rcodes[16][16] = { + [DNS_RC_NOERROR] = "NOERROR", + [DNS_RC_FORMERR] = "FORMERR", + [DNS_RC_SERVFAIL] = "SERVFAIL", + [DNS_RC_NXDOMAIN] = "NXDOMAIN", + [DNS_RC_NOTIMP] = "NOTIMP", + [DNS_RC_REFUSED] = "REFUSED", + [DNS_RC_YXDOMAIN] = "YXDOMAIN", + [DNS_RC_YXRRSET] = "YXRRSET", + [DNS_RC_NXRRSET] = "NXRRSET", + [DNS_RC_NOTAUTH] = "NOTAUTH", + [DNS_RC_NOTZONE] = "NOTZONE", +}; + +const char *dns_strrcode(enum dns_rcode rcode) { + rcode &= 0xf; + + if ('\0' == dns_rcodes[rcode][0]) + dns__printnul(dns_rcodes[rcode], sizeof dns_rcodes[rcode], dns__print10(dns_rcodes[rcode], sizeof dns_rcodes[rcode], 0, rcode, 0)); + + return dns_rcodes[rcode]; +} /* dns_strrcode() */ + + +enum dns_rcode dns_ircode(const char *name) { + unsigned rcode; + + for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) { + if (!strcasecmp(name, dns_rcodes[rcode])) + return rcode; + } + + return lengthof(dns_rcodes) - 1; +} /* dns_ircode() */ + + + +/* + * C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#if DNS_MAIN + +#include +#include +#include + +#include + +#if _WIN32 +#include +#endif + +#if !_WIN32 +#include +#endif + + +struct { + struct { + const char *path[8]; + unsigned count; + } resconf, nssconf, hosts, cache; + + const char *qname; + enum dns_type qtype; + + int (*sort)(); + + int verbose; +} MAIN = { + .sort = &dns_rr_i_packet, +}; + + +void hexdump(const unsigned char *src, size_t len, FILE *fp) { + static const unsigned char hex[] = "0123456789abcdef"; + static const unsigned char tmpl[] = " | |\n"; + unsigned char ln[sizeof tmpl]; + const unsigned char *sp, *se; + unsigned char *h, *g; + unsigned i, n; + + sp = src; + se = sp + len; + + while (sp < se) { + memcpy(ln, tmpl, sizeof ln); + + h = &ln[2]; + g = &ln[53]; + + for (n = 0; n < 2; n++) { + for (i = 0; i < 8 && se - sp > 0; i++, sp++) { + h[0] = hex[0x0f & (*sp >> 4)]; + h[1] = hex[0x0f & (*sp >> 0)]; + h += 3; + + *g++ = (isgraph(*sp))? *sp : '.'; + } + + h++; + } + + fputs((char *)ln, fp); + } + + return /* void */; +} /* hexdump() */ + + +DNS_NORETURN static void panic(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + +#if _WIN32 + vfprintf(stderr, fmt, ap); + + exit(EXIT_FAILURE); +#else + verrx(EXIT_FAILURE, fmt, ap); +#endif +} /* panic() */ + +#define panic_(fn, ln, fmt, ...) \ + panic(fmt "%0s", (fn), (ln), __VA_ARGS__) +#define panic(...) \ + panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "") + + +static void *grow(unsigned char *p, size_t size) { + void *tmp; + + if (!(tmp = realloc(p, size))) + panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno)); + + return tmp; +} /* grow() */ + + +static size_t add(size_t a, size_t b) { + if (~a < b) + panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b); + + return a + b; +} /* add() */ + + +static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) { + size_t size = add(osize, len); + + *dst = grow(*dst, size); + memcpy(*dst + osize, src, len); + + return size; +} /* append() */ + + +static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) { + size_t size = osize; + unsigned char buf[1024]; + size_t count; + + while ((count = fread(buf, 1, sizeof buf, fp))) + size = append(dst, size, buf, count); + + if (ferror(fp)) + panic("%s: %s", path, dns_strerror(errno)); + + return size; +} /* slurp() */ + + +static struct dns_resolv_conf *resconf(void) { + static struct dns_resolv_conf *resconf; + const char *path; + unsigned i; + int error; + + if (resconf) + return resconf; + + if (!(resconf = dns_resconf_open(&error))) + panic("dns_resconf_open: %s", dns_strerror(error)); + + if (!MAIN.resconf.count) + MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf"; + + for (i = 0; i < MAIN.resconf.count; i++) { + path = MAIN.resconf.path[i]; + + if (0 == strcmp(path, "-")) + error = dns_resconf_loadfile(resconf, stdin); + else + error = dns_resconf_loadpath(resconf, path); + + if (error) + panic("%s: %s", path, dns_strerror(error)); + } + + for (i = 0; i < MAIN.nssconf.count; i++) { + path = MAIN.nssconf.path[i]; + + if (0 == strcmp(path, "-")) + error = dns_nssconf_loadfile(resconf, stdin); + else + error = dns_nssconf_loadpath(resconf, path); + + if (error) + panic("%s: %s", path, dns_strerror(error)); + } + + if (!MAIN.nssconf.count) { + path = "/etc/nsswitch.conf"; + + if (!(error = dns_nssconf_loadpath(resconf, path))) + MAIN.nssconf.path[MAIN.nssconf.count++] = path; + else if (error != ENOENT) + panic("%s: %s", path, dns_strerror(error)); + } + + return resconf; +} /* resconf() */ + + +static struct dns_hosts *hosts(void) { + static struct dns_hosts *hosts; + const char *path; + unsigned i; + int error; + + if (hosts) + return hosts; + + if (!MAIN.hosts.count) { + MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts"; + + /* Explicitly test dns_hosts_local() */ + if (!(hosts = dns_hosts_local(&error))) + panic("%s: %s", "/etc/hosts", dns_strerror(error)); + + return hosts; + } + + if (!(hosts = dns_hosts_open(&error))) + panic("dns_hosts_open: %s", dns_strerror(error)); + + for (i = 0; i < MAIN.hosts.count; i++) { + path = MAIN.hosts.path[i]; + + if (0 == strcmp(path, "-")) + error = dns_hosts_loadfile(hosts, stdin); + else + error = dns_hosts_loadpath(hosts, path); + + if (error) + panic("%s: %s", path, dns_strerror(error)); + } + + return hosts; +} /* hosts() */ + + +#if DNS_CACHE +#include "cache.h" + +struct dns_cache *cache(void) { + static struct cache *cache; + const char *path; + unsigned i; + int error; + + if (cache) + return cache_resi(cache); + if (!MAIN.cache.count) + return NULL; + + if (!(cache = cache_open(&error))) + panic("%s: %s", MAIN.cache.path[0], dns_strerror(error)); + + for (i = 0; i < MAIN.cache.count; i++) { + path = MAIN.cache.path[i]; + + if (!strcmp(path, "-")) { + if ((error = cache_loadfile(cache, stdin, NULL, 0))) + panic("%s: %s", path, dns_strerror(error)); + } else if ((error = cache_loadpath(cache, path, NULL, 0))) + panic("%s: %s", path, dns_strerror(error)); + } + + return cache_resi(cache); +} /* cache() */ +#else +struct dns_cache *cache(void) { return NULL; } +#endif + + +static void print_packet(struct dns_packet *P, FILE *fp) { + dns_p_dump3(P, dns_rr_i_new(P, .sort = MAIN.sort), fp); + + if (MAIN.verbose > 2) + hexdump(P->data, P->end, fp); +} /* print_packet() */ + + +static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + struct dns_packet *P = dns_p_new(512); + struct dns_packet *Q = dns_p_new(512); + enum dns_section section; + struct dns_rr rr; + int error; + union dns_any any; + char pretty[sizeof any * 2]; + size_t len; + + P->end = fread(P->data, 1, P->size, stdin); + + fputs(";; [HEADER]\n", stdout); + fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); + fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); + fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); + fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); + fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); + fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); + fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_header(P)->rcode), dns_header(P)->rcode); + + section = 0; + + dns_rr_foreach(&rr, P, .sort = MAIN.sort) { + if (section != rr.section) + fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section)); + + if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error))) + fprintf(stdout, "%s\n", pretty); + + dns_rr_copy(Q, &rr, P); + + section = rr.section; + } + + fputs("; ; ; ; ; ; ; ;\n\n", stdout); + + section = 0; + +#if 0 + dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { +#else + struct dns_rr rrset[32]; + struct dns_rr_i *rri = dns_rr_i_new(Q, .name = dns_d_new("ns8.yahoo.com", DNS_D_ANCHOR), .sort = MAIN.sort); + unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); + + for (unsigned i = 0; i < rrcount; i++) { + rr = rrset[i]; +#endif + if (section != rr.section) + fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(Q, rr.section)); + + if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error))) + fprintf(stdout, "%s\n", pretty); + + section = rr.section; + } + + if (MAIN.verbose > 1) { + fprintf(stderr, "orig:%"PRIuZ"\n", P->end); + hexdump(P->data, P->end, stdout); + + fprintf(stderr, "copy:%"PRIuZ"\n", Q->end); + hexdump(Q->data, Q->end, stdout); + } + + return 0; +} /* parse_packet() */ + + +static int parse_domain(int argc, char *argv[]) { + char *dn; + + dn = (argc > 1)? argv[1] : "f.l.google.com"; + + printf("[%s]\n", dn); + + dn = dns_d_new(dn); + + do { + puts(dn); + } while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn))); + + return 0; +} /* parse_domain() */ + + +static int trim_domain(int argc, char **argv) { + for (argc--, argv++; argc > 0; argc--, argv++) { + char name[DNS_D_MAXNAME + 1]; + + dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR); + + puts(name); + } + + return 0; +} /* trim_domain() */ + + +static int expand_domain(int argc, char *argv[]) { + unsigned short rp = 0; + unsigned char *src = NULL; + unsigned char *dst; + struct dns_packet *pkt; + size_t lim = 0, len; + int error; + + if (argc > 1) + rp = atoi(argv[1]); + + len = slurp(&src, 0, stdin, "-"); + + if (!(pkt = dns_p_make(len, &error))) + panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error)); + + memcpy(pkt->data, src, len); + pkt->end = len; + + lim = 1; + dst = grow(NULL, lim); + + while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) { + lim = add(len, 1); + dst = grow(dst, lim); + } + + if (!len) + panic("expand: %s", dns_strerror(error)); + + fwrite(dst, 1, len, stdout); + fflush(stdout); + + free(src); + free(dst); + free(pkt); + + return 0; +} /* expand_domain() */ + + +static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + unsigned i; + + resconf(); /* load it */ + + fputs("; SOURCES\n", stdout); + + for (i = 0; i < MAIN.resconf.count; i++) + fprintf(stdout, "; %s\n", MAIN.resconf.path[i]); + + for (i = 0; i < MAIN.nssconf.count; i++) + fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]); + + fputs(";\n", stdout); + + dns_resconf_dump(resconf(), stdout); + + return 0; +} /* show_resconf() */ + + +static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + unsigned i; + + resconf(); + + fputs("# SOURCES\n", stdout); + + for (i = 0; i < MAIN.resconf.count; i++) + fprintf(stdout, "# %s\n", MAIN.resconf.path[i]); + + for (i = 0; i < MAIN.nssconf.count; i++) + fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]); + + fputs("#\n", stdout); + + dns_nssconf_dump(resconf(), stdout); + + return 0; +} /* show_nssconf() */ + + +static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + unsigned i; + + hosts(); + + fputs("# SOURCES\n", stdout); + + for (i = 0; i < MAIN.hosts.count; i++) + fprintf(stdout, "# %s\n", MAIN.hosts.path[i]); + + fputs("#\n", stdout); + + dns_hosts_dump(hosts(), stdout); + + return 0; +} /* show_hosts() */ + + +static int query_hosts(int argc, char *argv[]) { + struct dns_packet *Q = dns_p_new(512); + struct dns_packet *A; + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + int error; + + if (!MAIN.qname) + MAIN.qname = (argc > 1)? argv[1] : "localhost"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_A; + + hosts(); + + if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) { + union { struct in_addr a; struct in6_addr a6; } addr; + int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET; + + if ((error = dns_pton(af, MAIN.qname, &addr))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + qlen = dns_ptr_qname(qname, sizeof qname, af, &addr); + } else + qlen = dns__printstring(qname, sizeof qname, 0, MAIN.qname); + + if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0))) + panic("%s: %s", qname, dns_strerror(error)); + + if (!(A = dns_hosts_query(hosts(), Q, &error))) + panic("%s: %s", qname, dns_strerror(error)); + + print_packet(A, stdout); + + free(A); + + return 0; +} /* query_hosts() */ + + +static int search_list(int argc, char *argv[]) { + const char *qname = (argc > 1)? argv[1] : "f.l.google.com"; + unsigned long i = 0; + char name[DNS_D_MAXNAME + 1]; + + printf("[%s]\n", qname); + + while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i)) + puts(name); + + return 0; +} /* search_list() */ + + +int permute_set(int argc, char *argv[]) { + unsigned lo, hi, i; + struct dns_k_permutor p; + + hi = (--argc > 0)? atoi(argv[argc]) : 8; + lo = (--argc > 0)? atoi(argv[argc]) : 0; + + fprintf(stderr, "[%u .. %u]\n", lo, hi); + + dns_k_permutor_init(&p, lo, hi); + + for (i = lo; i <= hi; i++) + fprintf(stdout, "%u\n", dns_k_permutor_step(&p)); +// printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i))); + + return 0; +} /* permute_set() */ + + +int shuffle_16(int argc, char *argv[]) { + unsigned n, r; + + if (--argc > 0) { + n = 0xffff & atoi(argv[argc]); + r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random(); + + fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); + } else { + r = dns_random(); + + for (n = 0; n < 65536; n++) + fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); + } + + return 0; +} /* shuffle_16() */ + + +int dump_random(int argc, char *argv[]) { + unsigned char b[32]; + unsigned i, j, n, r; + + n = (argc > 1)? atoi(argv[1]) : 32; + + while (n) { + i = 0; + + do { + r = dns_random(); + + for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) { + b[i] = 0xff & r; + r >>= 8; + } + } while (i < n && i < sizeof b); + + hexdump(b, i, stdout); + + n -= i; + } + + return 0; +} /* dump_random() */ + + +static int send_query(int argc, char *argv[]) { + struct dns_packet *A, *Q = dns_p_new(512); + char host[INET6_ADDRSTRLEN + 1]; + struct sockaddr_storage ss; + struct dns_socket *so; + int error, type; + + if (argc > 1) { + ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET; + + if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL)))) + panic("%s: %s", argv[1], dns_strerror(error)); + + *dns_sa_port(ss.ss_family, &ss) = htons(53); + } else + memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0])); + + if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host)) + panic("bad host address, or none provided"); + + if (!MAIN.qname) + MAIN.qname = "ipv6.google.com"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_AAAA; + + if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0))) + panic("dns_p_push: %s", dns_strerror(error)); + + dns_header(Q)->rd = 1; + + if (strstr(argv[0], "udp")) + type = SOCK_DGRAM; + else if (strstr(argv[0], "tcp")) + type = SOCK_STREAM; + else + type = dns_res_tcp2type(resconf()->options.tcp); + + fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype)); + + if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, dns_opts(), &error))) + panic("dns_so_open: %s", dns_strerror(error)); + + while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { + if (error != DNS_EAGAIN) + panic("dns_so_query: %s (%d)", dns_strerror(error), error); + if (dns_so_elapsed(so) > 10) + panic("query timed-out"); + + dns_so_poll(so, 1); + } + + print_packet(A, stdout); + + dns_so_close(so); + + return 0; +} /* send_query() */ + + +static int print_arpa(int argc, char *argv[]) { + const char *ip = (argc > 1)? argv[1] : "::1"; + int af = (strchr(ip, ':'))? AF_INET6 : AF_INET; + union { struct in_addr a4; struct in6_addr a6; } addr; + char host[DNS_D_MAXNAME + 1]; + + if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr)) + panic("%s: invalid address", ip); + + fprintf(stdout, "%s\n", host); + + return 0; +} /* print_arpa() */ + + +static int show_hints(int argc, char *argv[]) { + struct dns_hints *(*load)(struct dns_resolv_conf *, int *); + const char *which, *how, *who; + struct dns_hints *hints; + int error; + + which = (argc > 1)? argv[1] : "local"; + how = (argc > 2)? argv[2] : "plain"; + who = (argc > 3)? argv[3] : "google.com"; + + load = (0 == strcmp(which, "local")) + ? &dns_hints_local + : &dns_hints_root; + + if (!(hints = load(resconf(), &error))) + panic("%s: %s", argv[0], dns_strerror(error)); + + if (0 == strcmp(how, "plain")) { + dns_hints_dump(hints, stdout); + } else { + struct dns_packet *query, *answer; + + query = dns_p_new(512); + + if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) + panic("%s: %s", who, dns_strerror(error)); + + if (!(answer = dns_hints_query(hints, query, &error))) + panic("%s: %s", who, dns_strerror(error)); + + print_packet(answer, stdout); + + free(answer); + } + + dns_hints_close(hints); + + return 0; +} /* show_hints() */ + + +static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { + _Bool recurse = !!strstr(argv[0], "recurse"); + struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; + struct dns_resolver *R; + struct dns_packet *ans; + const struct dns_stat *st; + int error; + + if (!MAIN.qname) + MAIN.qname = "www.google.com"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_A; + + resconf()->options.recurse = recurse; + + if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + while ((error = dns_res_check(R))) { + if (error != DNS_EAGAIN) + panic("dns_res_check: %s (%d)", dns_strerror(error), error); + if (dns_res_elapsed(R) > 30) + panic("query timed-out"); + + dns_res_poll(R, 1); + } + + ans = dns_res_fetch(R, &error); + print_packet(ans, stdout); + free(ans); + + st = dns_res_stat(R); + putchar('\n'); + printf(";; queries: %"PRIuZ"\n", st->queries); + printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes); + printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes); + printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes); + printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes); + + dns_res_close(R); + + return 0; +} /* resolve_query() */ + + +static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { + _Bool recurse = !!strstr(argv[0], "recurse"); + struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; + struct dns_resolver *res = NULL; + struct dns_addrinfo *ai = NULL; + struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME }; + struct addrinfo *ent; + char pretty[512]; + int error; + + if (!MAIN.qname) + MAIN.qname = "www.google.com"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_A; + + resconf()->options.recurse = recurse; + + if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + do { + switch (error = dns_ai_nextent(&ent, ai)) { + case 0: + dns_ai_print(pretty, sizeof pretty, ent, ai); + + fputs(pretty, stdout); + + free(ent); + + break; + case ENOENT: + break; + case DNS_EAGAIN: + if (dns_ai_elapsed(ai) > 30) + panic("query timed-out"); + + dns_ai_poll(ai, 1); + + break; + default: + panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error); + } + } while (error != ENOENT); + + dns_res_close(res); + dns_ai_close(ai); + + return 0; +} /* resolve_addrinfo() */ + + +static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + union { + struct sockaddr sa; + struct sockaddr_in sin; + } port; + int fd; + + memset(&port, 0, sizeof port); + port.sin.sin_family = AF_INET; + port.sin.sin_port = htons(5354); + port.sin.sin_addr.s_addr = inet_addr("127.0.0.1"); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + port.sin.sin_len = dns_af_len(port.sin.sin_family); +#endif + + if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0))) + panic("socket: %s", strerror(errno)); + + if (0 != bind(fd, &port.sa, sizeof port.sa)) + panic("127.0.0.1:5353: %s", dns_strerror(errno)); + + for (;;) { + struct dns_packet *pkt = dns_p_new(512); + struct sockaddr_storage ss; + socklen_t slen = sizeof ss; + ssize_t count; +#if defined(MSG_WAITALL) /* MinGW issue */ + int rflags = MSG_WAITALL; +#else + int rflags = 0; +#endif + + count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen); + + if (!count || count < 0) + panic("recvfrom: %s", strerror(errno)); + + pkt->end = count; + + dns_p_dump(pkt, stdout); + + (void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen); + } + + return 0; +} /* echo_port() */ + + +static int isection(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_isection(name); + name = dns_strsection(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* isection() */ + + +static int iclass(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_iclass(name); + name = dns_strclass(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* iclass() */ + + +static int itype(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_itype(name); + name = dns_strtype(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* itype() */ + + +static int iopcode(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_iopcode(name); + name = dns_stropcode(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* iopcode() */ + + +static int ircode(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_ircode(name); + name = dns_strrcode(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* ircode() */ + + +#define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) } +#define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__) +#define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__) +#define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__) +#define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + +static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + static const struct { const char *name; size_t size; } type[] = { + SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i), + SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns), + SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv), + SIZE(struct dns_sshfp, struct dns_txt, union dns_any), + SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i), + SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo), + SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long) + }; + unsigned i, max; + + for (i = 0, max = 0; i < lengthof(type); i++) + max = DNS_PP_MAX(max, strlen(type[i].name)); + + for (i = 0; i < lengthof(type); i++) + printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size); + + return 0; +} /* sizes() */ + + +static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = { + { "parse-packet", &parse_packet, "parse binary packet from stdin" }, + { "parse-domain", &parse_domain, "anchor and iteratively cleave domain" }, + { "trim-domain", &trim_domain, "trim and anchor domain name" }, + { "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" }, + { "show-resconf", &show_resconf, "show resolv.conf data" }, + { "show-hosts", &show_hosts, "show hosts data" }, + { "show-nssconf", &show_nssconf, "show nsswitch.conf data" }, + { "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" }, + { "search-list", &search_list, "generate query search list from domain" }, + { "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" }, + { "shuffle-16", &shuffle_16, "simple 16-bit permutation" }, + { "dump-random", &dump_random, "generate random bytes" }, + { "send-query", &send_query, "send query to host" }, + { "send-query-udp", &send_query, "send udp query to host" }, + { "send-query-tcp", &send_query, "send tcp query to host" }, + { "print-arpa", &print_arpa, "print arpa. zone name of address" }, + { "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" }, + { "resolve-stub", &resolve_query, "resolve as stub resolver" }, + { "resolve-recurse", &resolve_query, "resolve as recursive resolver" }, + { "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" }, + { "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" }, +/* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */ + { "echo", &echo_port, "server echo mode, for nmap fuzzing" }, + { "isection", &isection, "parse section string" }, + { "iclass", &iclass, "parse class string" }, + { "itype", &itype, "parse type string" }, + { "iopcode", &iopcode, "parse opcode string" }, + { "ircode", &ircode, "parse rcode string" }, + { "sizes", &sizes, "print data structure sizes" }, +}; + + +static void print_usage(const char *progname, FILE *fp) { + static const char *usage = + " [OPTIONS] COMMAND [ARGS]\n" + " -c PATH Path to resolv.conf\n" + " -n PATH Path to nsswitch.conf\n" + " -l PATH Path to local hosts\n" + " -z PATH Path to zone cache\n" + " -q QNAME Query name\n" + " -t QTYPE Query type\n" + " -s HOW Sort records\n" + " -v Be more verbose (-vv show packets; -vvv hexdump packets)\n" + " -V Print version info\n" + " -h Print this usage message\n" + "\n"; + unsigned i, n, m; + + fputs(progname, fp); + fputs(usage, fp); + + for (i = 0, m = 0; i < lengthof(cmds); i++) { + if (strlen(cmds[i].cmd) > m) + m = strlen(cmds[i].cmd); + } + + for (i = 0; i < lengthof(cmds); i++) { + fprintf(fp, " %s ", cmds[i].cmd); + + for (n = strlen(cmds[i].cmd); n < m; n++) + putc(' ', fp); + + fputs(cmds[i].help, fp); + putc('\n', fp); + } + + fputs("\nReport bugs to William Ahern \n", fp); +} /* print_usage() */ + + +static void print_version(const char *progname, FILE *fp) { + fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel()); + fprintf(fp, "vendor %s\n", dns_vendor()); + fprintf(fp, "release %.8X\n", dns_v_rel()); + fprintf(fp, "abi %.8X\n", dns_v_abi()); + fprintf(fp, "api %.8X\n", dns_v_api()); +} /* print_version() */ + + +int main(int argc, char **argv) { + extern int optind; + extern char *optarg; + const char *progname = argv[0]; + unsigned i; + int ch; + + while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:vVh"))) { + switch (ch) { + case 'c': + assert(MAIN.resconf.count < lengthof(MAIN.resconf.path)); + + MAIN.resconf.path[MAIN.resconf.count++] = optarg; + + break; + case 'n': + assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path)); + + MAIN.nssconf.path[MAIN.nssconf.count++] = optarg; + + break; + case 'l': + assert(MAIN.hosts.count < lengthof(MAIN.hosts.path)); + + MAIN.hosts.path[MAIN.hosts.count++] = optarg; + + break; + case 'z': + assert(MAIN.cache.count < lengthof(MAIN.cache.path)); + + MAIN.cache.path[MAIN.cache.count++] = optarg; + + break; + case 'q': + MAIN.qname = optarg; + + break; + case 't': + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (0 == strcasecmp(dns_rrtypes[i].name, optarg)) + { MAIN.qtype = dns_rrtypes[i].type; break; } + } + + if (MAIN.qtype) + break; + + for (i = 0; isdigit((int)optarg[i]); i++) { + MAIN.qtype *= 10; + MAIN.qtype += optarg[i] - '0'; + } + + if (!MAIN.qtype) + panic("%s: invalid query type", optarg); + + break; + case 's': + if (0 == strcasecmp(optarg, "packet")) + MAIN.sort = &dns_rr_i_packet; + else if (0 == strcasecmp(optarg, "shuffle")) + MAIN.sort = &dns_rr_i_shuffle; + else if (0 == strcasecmp(optarg, "order")) + MAIN.sort = &dns_rr_i_order; + else + panic("%s: invalid sort method", optarg); + + break; + case 'v': + dns_debug = ++MAIN.verbose; + + break; + case 'V': + print_version(progname, stdout); + + return 0; + case 'h': + print_usage(progname, stdout); + + return 0; + default: + print_usage(progname, stderr); + + return EXIT_FAILURE; + } /* switch() */ + } /* while() */ + + argc -= optind; + argv += optind; + + for (i = 0; i < lengthof(cmds) && argv[0]; i++) { + if (0 == strcmp(cmds[i].cmd, argv[0])) + return cmds[i].run(argc, argv); + } + + print_usage(progname, stderr); + + return EXIT_FAILURE; +} /* main() */ + + +#endif /* DNS_MAIN */ + + +/* + * pop file-scoped compiler annotations + */ +#if __clang__ +#pragma clang diagnostic pop +#elif DNS_GNUC_PREREQ(4, 6) +#pragma GCC diagnostic pop +#endif + diff --git a/Sources/CLibdill/dns/dns.h b/Sources/CLibdill/dns/dns.h new file mode 100755 index 0000000..7eb3e7d --- /dev/null +++ b/Sources/CLibdill/dns/dns.h @@ -0,0 +1,1198 @@ +/* ========================================================================== + * dns.h - Recursive, Reentrant DNS Resolver. + * -------------------------------------------------------------------------- + * Copyright (c) 2009, 2010, 2012, 2013, 2014, 2015 William Ahern + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * ========================================================================== + */ +#ifndef DNS_H +#define DNS_H + +#include /* size_t offsetof() */ +#include /* FILE */ + +#include /* strlen(3) */ + +#include /* time_t */ + +#if _WIN32 +#include +#include +#else +#include /* BYTE_ORDER BIG_ENDIAN _BIG_ENDIAN */ +#include /* socklen_t */ +#include /* struct socket */ + +#include /* POLLIN POLLOUT */ + +#include /* struct in_addr struct in6_addr */ + +#include /* struct addrinfo */ +#endif + + +/* + * V E R S I O N + * + * Vendor: Entity for which versions numbers are relevant. (If forking + * change DNS_VENDOR to avoid confusion.) + * + * Three versions: + * + * REL Official "release"--bug fixes, new features, etc. + * ABI Changes to existing object sizes or parameter types. + * API Changes that might effect application source. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_VENDOR "william@25thandClement.com" + +#define DNS_V_REL 0x20150630 +#define DNS_V_ABI 0x20150612 +#define DNS_V_API 0x20150612 + + +const char *dns_vendor(void); + +int dns_v_rel(void); +int dns_v_abi(void); +int dns_v_api(void); + + +/* + * E R R O R S + * + * Errors and exceptions are always returned through an int. This should + * hopefully make integration easier in the majority of circumstances, and + * also cut down on useless compiler warnings. + * + * System and library errors are returned together. POSIX guarantees that + * all system errors are positive integers. Library errors are always + * negative integers in the range DNS_EBASE to DNS_ELAST, with the high bits + * set to the three magic ASCII characters "dns". + * + * dns_strerror() returns static English string descriptions of all known + * errors, and punts the remainder to strerror(3). + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_EBASE -(('d' << 24) | ('n' << 16) | ('s' << 8) | 64) + +#define dns_error_t int /* for documentation only */ + +enum dns_errno { + DNS_ENOBUFS = DNS_EBASE, + DNS_EILLEGAL, + DNS_EORDER, + DNS_ESECTION, + DNS_EUNKNOWN, + DNS_EADDRESS, + DNS_ENOQUERY, + DNS_ENOANSWER, + DNS_EFETCHED, + DNS_ESERVICE, /* EAI_SERVICE */ + DNS_ENONAME, /* EAI_NONAME */ + DNS_EFAIL, /* EAI_FAIL */ + DNS_ELAST, +}; /* dns_errno */ + +const char *dns_strerror(dns_error_t); + +extern int dns_debug; + + +/* + * C O M P I L E R A N N O T A T I O N S + * + * GCC with -Wextra, and clang by default, complain about overrides in + * initializer lists. Overriding previous member initializers is well + * defined behavior in C. dns.c relies on this behavior to define default, + * overrideable member values when instantiating configuration objects. + * + * dns_quietinit() guards a compound literal expression with pragmas to + * silence these shrill warnings. This alleviates the burden of requiring + * third-party projects to adjust their compiler flags. + * + * NOTE: If you take the address of the compound literal, take the address + * of the transformed expression, otherwise the compound literal lifetime is + * tied to the scope of the GCC statement expression. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if defined __clang__ +#define DNS_PRAGMA_PUSH _Pragma("clang diagnostic push") +#define DNS_PRAGMA_QUIET _Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") +#define DNS_PRAGMA_POP _Pragma("clang diagnostic pop") + +#define dns_quietinit(...) \ + DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__ DNS_PRAGMA_POP +#elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4 +#define DNS_PRAGMA_PUSH _Pragma("GCC diagnostic push") +#define DNS_PRAGMA_QUIET _Pragma("GCC diagnostic ignored \"-Woverride-init\"") +#define DNS_PRAGMA_POP _Pragma("GCC diagnostic pop") + +/* GCC parses the _Pragma operator less elegantly than clang. */ +#define dns_quietinit(...) \ + __extension__ ({ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__; DNS_PRAGMA_POP }) +#else +#define DNS_PRAGMA_PUSH +#define DNS_PRAGMA_QUIET +#define DNS_PRAGMA_POP +#define dns_quietinit(...) __VA_ARGS__ +#endif + +#if defined __GNUC__ +#define DNS_PRAGMA_EXTENSION __extension__ +#else +#define DNS_PRAGMA_EXTENSION +#endif + + +/* + * E V E N T S I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if defined(POLLIN) +#define DNS_POLLIN POLLIN +#else +#define DNS_POLLIN 1 +#endif + +#if defined(POLLOUT) +#define DNS_POLLOUT POLLOUT +#else +#define DNS_POLLOUT 2 +#endif + + +/* + * See Application Interface below for configuring libevent bitmasks instead + * of poll(2) bitmasks. + */ +#define DNS_EVREAD 2 +#define DNS_EVWRITE 4 + + +#define DNS_POLL2EV(set) \ + (((set) & DNS_POLLIN)? DNS_EVREAD : 0) | (((set) & DNS_POLLOUT)? DNS_EVWRITE : 0) + +#define DNS_EV2POLL(set) \ + (((set) & DNS_EVREAD)? DNS_POLLIN : 0) | (((set) & DNS_EVWRITE)? DNS_POLLOUT : 0) + + +/* + * E N U M E R A T I O N I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +enum dns_section { + DNS_S_QD = 0x01, +#define DNS_S_QUESTION DNS_S_QD + + DNS_S_AN = 0x02, +#define DNS_S_ANSWER DNS_S_AN + + DNS_S_NS = 0x04, +#define DNS_S_AUTHORITY DNS_S_NS + + DNS_S_AR = 0x08, +#define DNS_S_ADDITIONAL DNS_S_AR + + DNS_S_ALL = 0x0f +}; /* enum dns_section */ + + +enum dns_class { + DNS_C_IN = 1, + + DNS_C_ANY = 255 +}; /* enum dns_class */ + + +enum dns_type { + DNS_T_A = 1, + DNS_T_NS = 2, + DNS_T_CNAME = 5, + DNS_T_SOA = 6, + DNS_T_PTR = 12, + DNS_T_MX = 15, + DNS_T_TXT = 16, + DNS_T_AAAA = 28, + DNS_T_SRV = 33, + DNS_T_OPT = 41, + DNS_T_SSHFP = 44, + DNS_T_SPF = 99, + + DNS_T_ALL = 255 +}; /* enum dns_type */ + + +enum dns_opcode { + DNS_OP_QUERY = 0, + DNS_OP_IQUERY = 1, + DNS_OP_STATUS = 2, + DNS_OP_NOTIFY = 4, + DNS_OP_UPDATE = 5, +}; /* dns_opcode */ + + +enum dns_rcode { + DNS_RC_NOERROR = 0, + DNS_RC_FORMERR = 1, + DNS_RC_SERVFAIL = 2, + DNS_RC_NXDOMAIN = 3, + DNS_RC_NOTIMP = 4, + DNS_RC_REFUSED = 5, + DNS_RC_YXDOMAIN = 6, + DNS_RC_YXRRSET = 7, + DNS_RC_NXRRSET = 8, + DNS_RC_NOTAUTH = 9, + DNS_RC_NOTZONE = 10, +}; /* dns_rcode */ + + +/* + * NOTE: These string functions need a small buffer in case the literal + * integer value needs to be printed and returned. UNLESS this buffer is + * SPECIFIED, the returned string has ONLY BLOCK SCOPE. + */ +#define DNS_STRMAXLEN 47 /* "QUESTION|ANSWER|AUTHORITY|ADDITIONAL" */ + +const char *dns_strsection(enum dns_section, void *, size_t); +#define dns_strsection3(a, b, c) \ + dns_strsection((a), (b), (c)) +#define dns_strsection1(a) dns_strsection((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) +#define dns_strsection(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strsection, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + +enum dns_section dns_isection(const char *); + +const char *dns_strclass(enum dns_class, void *, size_t); +#define dns_strclass3(a, b, c) dns_strclass((a), (b), (c)) +#define dns_strclass1(a) dns_strclass((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) +#define dns_strclass(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strclass, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + +enum dns_class dns_iclass(const char *); + +const char *dns_strtype(enum dns_type, void *, size_t); +#define dns_strtype3(a, b, c) dns_strtype((a), (b), (c)) +#define dns_strtype1(a) dns_strtype((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) +#define dns_strtype(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strtype, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + +enum dns_type dns_itype(const char *); + +const char *dns_stropcode(enum dns_opcode); + +enum dns_opcode dns_iopcode(const char *); + +const char *dns_strrcode(enum dns_rcode); + +enum dns_rcode dns_ircode(const char *); + + +/* + * A T O M I C I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef unsigned long dns_atomic_t; + +typedef unsigned long dns_refcount_t; /* must be same value type as dns_atomic_t */ + + +/* + * C R Y P T O I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +extern unsigned (*dns_random)(void); + + +/* + * P A C K E T I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_header { + unsigned qid:16; + +#if (defined BYTE_ORDER && BYTE_ORDER == BIG_ENDIAN) || (defined __sun && defined _BIG_ENDIAN) + unsigned qr:1; + unsigned opcode:4; + unsigned aa:1; + unsigned tc:1; + unsigned rd:1; + + unsigned ra:1; + unsigned unused:3; + unsigned rcode:4; +#else + unsigned rd:1; + unsigned tc:1; + unsigned aa:1; + unsigned opcode:4; + unsigned qr:1; + + unsigned rcode:4; + unsigned unused:3; + unsigned ra:1; +#endif + + unsigned qdcount:16; + unsigned ancount:16; + unsigned nscount:16; + unsigned arcount:16; +}; /* struct dns_header */ + +#define dns_header(p) (&(p)->header) + + +#ifndef DNS_P_QBUFSIZ +#define DNS_P_QBUFSIZ dns_p_calcsize(256 + 4) +#endif + +#ifndef DNS_P_DICTSIZE +#define DNS_P_DICTSIZE 16 +#endif + +struct dns_packet { + unsigned short dict[DNS_P_DICTSIZE]; + + struct dns_s_memo { + unsigned short base, end; + } qd, an, ns, ar; + + struct { struct dns_packet *cqe_next, *cqe_prev; } cqe; + + size_t size, end; + + int:16; /* tcp padding */ + + DNS_PRAGMA_EXTENSION union { + struct dns_header header; + unsigned char data[1]; + }; +}; /* struct dns_packet */ + +#define dns_p_calcsize(n) (offsetof(struct dns_packet, data) + DNS_PP_MAX(12, (n))) + +#define dns_p_sizeof(P) dns_p_calcsize((P)->end) + +/** takes size of maximum desired payload */ +#define dns_p_new(n) (dns_p_init((struct dns_packet *)&(union { unsigned char b[dns_p_calcsize((n))]; struct dns_packet p; }){ { 0 } }, dns_p_calcsize((n)))) + +/** takes size of entire packet structure as allocated */ +struct dns_packet *dns_p_init(struct dns_packet *, size_t); + +/** takes size of maximum desired payload */ +struct dns_packet *dns_p_make(size_t, int *); + +int dns_p_grow(struct dns_packet **); + +struct dns_packet *dns_p_copy(struct dns_packet *, const struct dns_packet *); + +#define dns_p_opcode(P) (dns_header(P)->opcode) + +#define dns_p_rcode(P) (dns_header(P)->rcode) + +unsigned dns_p_count(struct dns_packet *, enum dns_section); + +int dns_p_push(struct dns_packet *, enum dns_section, const void *, size_t, enum dns_type, enum dns_class, unsigned, const void *); + +void dns_p_dictadd(struct dns_packet *, unsigned short); + +struct dns_packet *dns_p_merge(struct dns_packet *, enum dns_section, struct dns_packet *, enum dns_section, int *); + +void dns_p_dump(struct dns_packet *, FILE *); + +int dns_p_study(struct dns_packet *); + + +/* + * D O M A I N N A M E I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_D_MAXLABEL 63 /* + 1 '\0' */ +#define DNS_D_MAXNAME 255 /* + 1 '\0' */ + +#define DNS_D_ANCHOR 1 /* anchor domain w/ root "." */ +#define DNS_D_CLEAVE 2 /* cleave sub-domain */ +#define DNS_D_TRIM 4 /* remove superfluous dots */ + +#define dns_d_new3(a, b, f) dns_d_init(&(char[DNS_D_MAXNAME + 1]){ 0 }, DNS_D_MAXNAME + 1, (a), (b), (f)) +#define dns_d_new2(a, f) dns_d_new3((a), strlen((a)), (f)) +#define dns_d_new1(a) dns_d_new3((a), strlen((a)), DNS_D_ANCHOR) +#define dns_d_new(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_d_new, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + +char *dns_d_init(void *, size_t, const void *, size_t, int); + +size_t dns_d_anchor(void *, size_t, const void *, size_t); + +size_t dns_d_cleave(void *, size_t, const void *, size_t); + +size_t dns_d_comp(void *, size_t, const void *, size_t, struct dns_packet *, int *); + +size_t dns_d_expand(void *, size_t, unsigned short, struct dns_packet *, int *); + +unsigned short dns_d_skip(unsigned short, struct dns_packet *); + +int dns_d_push(struct dns_packet *, const void *, size_t); + +size_t dns_d_cname(void *, size_t, const void *, size_t, struct dns_packet *, int *error); + + +/* + * R E S O U R C E R E C O R D I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_rr { + enum dns_section section; + + struct { + unsigned short p; + unsigned short len; + } dn; + + enum dns_type type; + enum dns_class class; + unsigned ttl; + + struct { + unsigned short p; + unsigned short len; + } rd; +}; /* struct dns_rr */ + + +int dns_rr_copy(struct dns_packet *, struct dns_rr *, struct dns_packet *); + +int dns_rr_parse(struct dns_rr *, unsigned short, struct dns_packet *); + +unsigned short dns_rr_skip(unsigned short, struct dns_packet *); + +int dns_rr_cmp(struct dns_rr *, struct dns_packet *, struct dns_rr *, struct dns_packet *); + +size_t dns_rr_print(void *, size_t, struct dns_rr *, struct dns_packet *, int *); + + +#define dns_rr_i_new(P, ...) \ + dns_rr_i_init(&dns_quietinit((struct dns_rr_i){ 0, __VA_ARGS__ }), (P)) + +struct dns_rr_i { + enum dns_section section; + const void *name; + enum dns_type type; + enum dns_class class; + const void *data; + + int follow; + + int (*sort)(); + unsigned args[2]; + + struct { + unsigned short next; + unsigned short count; + + unsigned exec; + unsigned regs[2]; + } state, saved; +}; /* struct dns_rr_i */ + +int dns_rr_i_packet(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + +int dns_rr_i_order(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + +int dns_rr_i_shuffle(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + +struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *, struct dns_packet *); + +#define dns_rr_i_save(i) ((i)->saved = (i)->state) +#define dns_rr_i_rewind(i) ((i)->state = (i)->saved) +#define dns_rr_i_count(i) ((i)->state.count) + +unsigned dns_rr_grep(struct dns_rr *, unsigned, struct dns_rr_i *, struct dns_packet *, int *); + +#define dns_rr_foreach_(rr, P, ...) \ + for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = *dns_rr_i_new((P), __VA_ARGS__); dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), &(int){ 0 }); ) + +#define dns_rr_foreach(...) dns_rr_foreach_(__VA_ARGS__) + + +/* + * A R E S O U R C E R E C O R D + */ + +struct dns_a { + struct in_addr addr; +}; /* struct dns_a */ + +int dns_a_parse(struct dns_a *, struct dns_rr *, struct dns_packet *); + +int dns_a_push(struct dns_packet *, struct dns_a *); + +int dns_a_cmp(const struct dns_a *, const struct dns_a *); + +size_t dns_a_print(void *, size_t, struct dns_a *); + + +/* + * AAAA R E S O U R C E R E C O R D + */ + +struct dns_aaaa { + struct in6_addr addr; +}; /* struct dns_aaaa */ + +int dns_aaaa_parse(struct dns_aaaa *, struct dns_rr *, struct dns_packet *); + +int dns_aaaa_push(struct dns_packet *, struct dns_aaaa *); + +int dns_aaaa_cmp(const struct dns_aaaa *, const struct dns_aaaa *); + +size_t dns_aaaa_print(void *, size_t, struct dns_aaaa *); + + +/* + * MX R E S O U R C E R E C O R D + */ + +struct dns_mx { + unsigned short preference; + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_mx */ + +int dns_mx_parse(struct dns_mx *, struct dns_rr *, struct dns_packet *); + +int dns_mx_push(struct dns_packet *, struct dns_mx *); + +int dns_mx_cmp(const struct dns_mx *, const struct dns_mx *); + +size_t dns_mx_print(void *, size_t, struct dns_mx *); + +size_t dns_mx_cname(void *, size_t, struct dns_mx *); + + +/* + * NS R E S O U R C E R E C O R D + */ + +struct dns_ns { + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_ns */ + +int dns_ns_parse(struct dns_ns *, struct dns_rr *, struct dns_packet *); + +int dns_ns_push(struct dns_packet *, struct dns_ns *); + +int dns_ns_cmp(const struct dns_ns *, const struct dns_ns *); + +size_t dns_ns_print(void *, size_t, struct dns_ns *); + +size_t dns_ns_cname(void *, size_t, struct dns_ns *); + + +/* + * CNAME R E S O U R C E R E C O R D + */ + +struct dns_cname { + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_cname */ + +int dns_cname_parse(struct dns_cname *, struct dns_rr *, struct dns_packet *); + +int dns_cname_push(struct dns_packet *, struct dns_cname *); + +int dns_cname_cmp(const struct dns_cname *, const struct dns_cname *); + +size_t dns_cname_print(void *, size_t, struct dns_cname *); + +size_t dns_cname_cname(void *, size_t, struct dns_cname *); + + +/* + * SOA R E S O U R C E R E C O R D + */ + +struct dns_soa { + char mname[DNS_D_MAXNAME + 1]; + char rname[DNS_D_MAXNAME + 1]; + unsigned serial, refresh, retry, expire, minimum; +}; /* struct dns_soa */ + +int dns_soa_parse(struct dns_soa *, struct dns_rr *, struct dns_packet *); + +int dns_soa_push(struct dns_packet *, struct dns_soa *); + +int dns_soa_cmp(const struct dns_soa *, const struct dns_soa *); + +size_t dns_soa_print(void *, size_t, struct dns_soa *); + + +/* + * PTR R E S O U R C E R E C O R D + */ + +struct dns_ptr { + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_ptr */ + +int dns_ptr_parse(struct dns_ptr *, struct dns_rr *, struct dns_packet *); + +int dns_ptr_push(struct dns_packet *, struct dns_ptr *); + +int dns_ptr_cmp(const struct dns_ptr *, const struct dns_ptr *); + +size_t dns_ptr_print(void *, size_t, struct dns_ptr *); + +size_t dns_ptr_cname(void *, size_t, struct dns_ptr *); + + +/* + * SRV R E S O U R C E R E C O R D + */ + +struct dns_srv { + unsigned short priority; + unsigned short weight; + unsigned short port; + char target[DNS_D_MAXNAME + 1]; +}; /* struct dns_srv */ + +int dns_srv_parse(struct dns_srv *, struct dns_rr *, struct dns_packet *); + +int dns_srv_push(struct dns_packet *, struct dns_srv *); + +int dns_srv_cmp(const struct dns_srv *, const struct dns_srv *); + +size_t dns_srv_print(void *, size_t, struct dns_srv *); + +size_t dns_srv_cname(void *, size_t, struct dns_srv *); + + +/* + * OPT R E S O U R C E R E C O R D + */ + +#define DNS_OPT_MINDATA 512 + +#define DNS_OPT_BADVERS 16 + +struct dns_opt { + size_t size, len; + + unsigned char rcode, version; + unsigned short maxsize; + + unsigned char data[DNS_OPT_MINDATA]; +}; /* struct dns_opt */ + +unsigned int dns_opt_ttl(const struct dns_opt *); + +unsigned short dns_opt_class(const struct dns_opt *); + +struct dns_opt *dns_opt_init(struct dns_opt *, size_t); + +int dns_opt_parse(struct dns_opt *, struct dns_rr *, struct dns_packet *); + +int dns_opt_push(struct dns_packet *, struct dns_opt *); + +int dns_opt_cmp(const struct dns_opt *, const struct dns_opt *); + +size_t dns_opt_print(void *, size_t, struct dns_opt *); + + +/* + * SSHFP R E S O U R C E R E C O R D + */ + +struct dns_sshfp { + enum dns_sshfp_key { + DNS_SSHFP_RSA = 1, + DNS_SSHFP_DSA = 2, + } algo; + + enum dns_sshfp_digest { + DNS_SSHFP_SHA1 = 1, + } type; + + union { + unsigned char sha1[20]; + } digest; +}; /* struct dns_sshfp */ + +int dns_sshfp_parse(struct dns_sshfp *, struct dns_rr *, struct dns_packet *); + +int dns_sshfp_push(struct dns_packet *, struct dns_sshfp *); + +int dns_sshfp_cmp(const struct dns_sshfp *, const struct dns_sshfp *); + +size_t dns_sshfp_print(void *, size_t, struct dns_sshfp *); + + +/* + * TXT R E S O U R C E R E C O R D + */ + +#ifndef DNS_TXT_MINDATA +#define DNS_TXT_MINDATA 1024 +#endif + +struct dns_txt { + size_t size, len; + unsigned char data[DNS_TXT_MINDATA]; +}; /* struct dns_txt */ + +struct dns_txt *dns_txt_init(struct dns_txt *, size_t); + +int dns_txt_parse(struct dns_txt *, struct dns_rr *, struct dns_packet *); + +int dns_txt_push(struct dns_packet *, struct dns_txt *); + +int dns_txt_cmp(const struct dns_txt *, const struct dns_txt *); + +size_t dns_txt_print(void *, size_t, struct dns_txt *); + + +/* + * ANY R E S O U R C E R E C O R D + */ + +union dns_any { + struct dns_a a; + struct dns_aaaa aaaa; + struct dns_mx mx; + struct dns_ns ns; + struct dns_cname cname; + struct dns_soa soa; + struct dns_ptr ptr; + struct dns_srv srv; + struct dns_opt opt; + struct dns_sshfp sshfp; + struct dns_txt txt, spf, rdata; +}; /* union dns_any */ + +#define DNS_ANY_INIT(any) { .rdata = { .size = sizeof *(any) } } + +union dns_any *dns_any_init(union dns_any *, size_t); + +int dns_any_parse(union dns_any *, struct dns_rr *, struct dns_packet *); + +int dns_any_push(struct dns_packet *, union dns_any *, enum dns_type); + +int dns_any_cmp(const union dns_any *, enum dns_type, const union dns_any *, enum dns_type); + +size_t dns_any_print(void *, size_t, union dns_any *, enum dns_type); + +size_t dns_any_cname(void *, size_t, union dns_any *, enum dns_type); + + +/* + * H O S T S I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hosts; + +struct dns_hosts *dns_hosts_open(int *); + +void dns_hosts_close(struct dns_hosts *); + +dns_refcount_t dns_hosts_acquire(struct dns_hosts *); + +dns_refcount_t dns_hosts_release(struct dns_hosts *); + +struct dns_hosts *dns_hosts_mortal(struct dns_hosts *); + +struct dns_hosts *dns_hosts_local(int *); + +int dns_hosts_loadfile(struct dns_hosts *, FILE *); + +int dns_hosts_loadpath(struct dns_hosts *, const char *); + +int dns_hosts_dump(struct dns_hosts *, FILE *); + +int dns_hosts_insert(struct dns_hosts *, int, const void *, const void *, _Bool); + +struct dns_packet *dns_hosts_query(struct dns_hosts *, struct dns_packet *, int *); + + +/* + * R E S O L V . C O N F I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_resolv_conf { + struct sockaddr_storage nameserver[3]; + + char search[4][DNS_D_MAXNAME + 1]; + + /* (f)ile, (b)ind, (c)ache */ + char lookup[4 * (1 + (4 * 2))]; + + struct { + _Bool edns0; + + unsigned ndots; + + unsigned timeout; + + unsigned attempts; + + _Bool rotate; + + _Bool recurse; + + _Bool smart; + + enum { + DNS_RESCONF_TCP_ENABLE, + DNS_RESCONF_TCP_ONLY, + DNS_RESCONF_TCP_DISABLE, + } tcp; + } options; + + struct sockaddr_storage iface; + + struct { /* PRIVATE */ + dns_atomic_t refcount; + } _; +}; /* struct dns_resolv_conf */ + +struct dns_resolv_conf *dns_resconf_open(int *); + +void dns_resconf_close(struct dns_resolv_conf *); + +dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *); + +dns_refcount_t dns_resconf_release(struct dns_resolv_conf *); + +struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *); + +struct dns_resolv_conf *dns_resconf_local(int *); + +struct dns_resolv_conf *dns_resconf_root(int *); + +int dns_resconf_pton(struct sockaddr_storage *, const char *); + +int dns_resconf_loadfile(struct dns_resolv_conf *, FILE *); + +int dns_resconf_loadpath(struct dns_resolv_conf *, const char *); + +int dns_nssconf_loadfile(struct dns_resolv_conf *, FILE *); + +int dns_nssconf_loadpath(struct dns_resolv_conf *, const char *); + +int dns_resconf_dump(struct dns_resolv_conf *, FILE *); + +int dns_nssconf_dump(struct dns_resolv_conf *, FILE *); + +int dns_resconf_setiface(struct dns_resolv_conf *, const char *, unsigned short); + +typedef unsigned long dns_resconf_i_t; + +size_t dns_resconf_search(void *, size_t, const void *, size_t, struct dns_resolv_conf *, dns_resconf_i_t *); + + +/* + * H I N T S E R V E R I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hints; + +struct dns_hints *dns_hints_open(struct dns_resolv_conf *, int *); + +void dns_hints_close(struct dns_hints *); + +dns_refcount_t dns_hints_acquire(struct dns_hints *); + +dns_refcount_t dns_hints_release(struct dns_hints *); + +struct dns_hints *dns_hints_mortal(struct dns_hints *); + +int dns_hints_insert(struct dns_hints *, const char *, const struct sockaddr *, unsigned); + +unsigned dns_hints_insert_resconf(struct dns_hints *, const char *, const struct dns_resolv_conf *, int *); + +struct dns_hints *dns_hints_local(struct dns_resolv_conf *, int *); + +struct dns_hints *dns_hints_root(struct dns_resolv_conf *, int *); + +int dns_hints_dump(struct dns_hints *, FILE *); + + +struct dns_hints_i { + const char *zone; + + struct { + unsigned next; + unsigned seed; + } state; +}; /* struct dns_hints_i */ + +#define dns_hints_i_new(...) (&(struct dns_hints_i){ __VA_ARGS__ }) + +unsigned dns_hints_grep(struct sockaddr **, socklen_t *, unsigned, struct dns_hints_i *, struct dns_hints *); + + +/* + * C A C H E I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_cache { + void *state; + + dns_refcount_t (*acquire)(struct dns_cache *); + dns_refcount_t (*release)(struct dns_cache *); + + struct dns_packet *(*query)(struct dns_packet *, struct dns_cache *, int *); + + int (*submit)(struct dns_packet *, struct dns_cache *); + int (*check)(struct dns_cache *); + struct dns_packet *(*fetch)(struct dns_cache *, int *); + + int (*pollfd)(struct dns_cache *); + short (*events)(struct dns_cache *); + void (*clear)(struct dns_cache *); + + union { + long i; + void *p; + } arg[3]; + + struct { /* PRIVATE */ + dns_atomic_t refcount; + } _; +}; /* struct dns_cache */ + + +struct dns_cache *dns_cache_init(struct dns_cache *); + +void dns_cache_close(struct dns_cache *); + + +/* + * A P P L I C A T I O N I N T E R F A C E + * + * Options to change the behavior of the API. Applies across all the + * different components. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_OPTS_INITIALIZER_ { 0, 0 }, 0 +#define DNS_OPTS_INITIALIZER { DNS_OPTS_INITIALIZER_ } +#define DNS_OPTS_INIT(...) { DNS_OPTS_INITIALIZER_, __VA_ARGS__ } + +#define dns_opts(...) (&dns_quietinit((struct dns_options)DNS_OPTS_INIT(__VA_ARGS__))) + +struct dns_options { + /* + * If the callback closes *fd, it must set it to -1. Otherwise, the + * descriptor is queued and lazily closed at object destruction or + * by an explicit call to _clear(). This allows safe use of + * kqueue(2), epoll(2), et al -style persistent events. + */ + struct { + void *arg; + int (*cb)(int *fd, void *arg); + } closefd; + + /* bitmask for _events() routines */ + enum dns_events { + DNS_SYSPOLL, + DNS_LIBEVENT, + } events; +}; /* struct dns_options */ + + +/* + * S T A T S I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_stat { + size_t queries; + + struct { + struct { + size_t count, bytes; + } sent, rcvd; + } udp, tcp; +}; /* struct dns_stat */ + + +/* + * S O C K E T I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_socket; + +struct dns_socket *dns_so_open(const struct sockaddr *, int, const struct dns_options *, int *error); + +void dns_so_close(struct dns_socket *); + +void dns_so_reset(struct dns_socket *); + +unsigned short dns_so_mkqid(struct dns_socket *so); + +struct dns_packet *dns_so_query(struct dns_socket *, struct dns_packet *, struct sockaddr *, int *); + +int dns_so_submit(struct dns_socket *, struct dns_packet *, struct sockaddr *); + +int dns_so_check(struct dns_socket *); + +struct dns_packet *dns_so_fetch(struct dns_socket *, int *); + +time_t dns_so_elapsed(struct dns_socket *); + +void dns_so_clear(struct dns_socket *); + +int dns_so_events(struct dns_socket *); + +int dns_so_pollfd(struct dns_socket *); + +int dns_so_poll(struct dns_socket *, int); + +const struct dns_stat *dns_so_stat(struct dns_socket *); + + +/* + * R E S O L V E R I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_resolver; + +struct dns_resolver *dns_res_open(struct dns_resolv_conf *, struct dns_hosts *hosts, struct dns_hints *, struct dns_cache *, const struct dns_options *, int *); + +struct dns_resolver *dns_res_stub(const struct dns_options *, int *); + +void dns_res_reset(struct dns_resolver *); + +void dns_res_close(struct dns_resolver *); + +dns_refcount_t dns_res_acquire(struct dns_resolver *); + +dns_refcount_t dns_res_release(struct dns_resolver *); + +struct dns_resolver *dns_res_mortal(struct dns_resolver *); + +int dns_res_submit(struct dns_resolver *, const char *, enum dns_type, enum dns_class); + +int dns_res_submit2(struct dns_resolver *, const char *, size_t, enum dns_type, enum dns_class); + +int dns_res_check(struct dns_resolver *); + +struct dns_packet *dns_res_fetch(struct dns_resolver *, int *); + +time_t dns_res_elapsed(struct dns_resolver *); + +void dns_res_clear(struct dns_resolver *); + +int dns_res_events(struct dns_resolver *); + +int dns_res_pollfd(struct dns_resolver *); + +time_t dns_res_timeout(struct dns_resolver *); + +int dns_res_poll(struct dns_resolver *, int); + +struct dns_packet *dns_res_query(struct dns_resolver *, const char *, enum dns_type, enum dns_class, int, int *); + +const struct dns_stat *dns_res_stat(struct dns_resolver *); + +void dns_res_sethints(struct dns_resolver *, struct dns_hints *); + + +/* + * A D D R I N F O I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_addrinfo; + +struct dns_addrinfo *dns_ai_open(const char *, const char *, enum dns_type, const struct addrinfo *, struct dns_resolver *, int *); + +void dns_ai_close(struct dns_addrinfo *); + +int dns_ai_nextent(struct addrinfo **, struct dns_addrinfo *); + +size_t dns_ai_print(void *, size_t, struct addrinfo *, struct dns_addrinfo *); + +time_t dns_ai_elapsed(struct dns_addrinfo *); + +void dns_ai_clear(struct dns_addrinfo *); + +int dns_ai_events(struct dns_addrinfo *); + +int dns_ai_pollfd(struct dns_addrinfo *); + +time_t dns_ai_timeout(struct dns_addrinfo *); + +int dns_ai_poll(struct dns_addrinfo *, int); + +const struct dns_stat *dns_ai_stat(struct dns_addrinfo *); + + +/* + * U T I L I T Y I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +size_t dns_strlcpy(char *, const char *, size_t); + +size_t dns_strlcat(char *, const char *, size_t); + + +/* + * M A C R O M A G I C S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_PP_MIN(a, b) (((a) < (b))? (a) : (b)) +#define DNS_PP_MAX(a, b) (((a) > (b))? (a) : (b)) +#define DNS_PP_NARG_(a, b, c, d, e, f, g, h, i, j, k, N,...) N +#define DNS_PP_NARG(...) DNS_PP_NARG_(__VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define DNS_PP_CALL(F, ...) F(__VA_ARGS__) +#define DNS_PP_PASTE(x, y) x##y +#define DNS_PP_XPASTE(x, y) DNS_PP_PASTE(x, y) +#define DNS_PP_STRINGIFY_(s) #s +#define DNS_PP_STRINGIFY(s) DNS_PP_STRINGIFY_(s) +#define DNS_PP_D1 0 +#define DNS_PP_D2 1 +#define DNS_PP_D3 2 +#define DNS_PP_D4 3 +#define DNS_PP_D5 4 +#define DNS_PP_D6 5 +#define DNS_PP_D7 6 +#define DNS_PP_D8 7 +#define DNS_PP_D9 8 +#define DNS_PP_D10 9 +#define DNS_PP_D11 10 +#define DNS_PP_DEC(N) DNS_PP_XPASTE(DNS_PP_D, N) + +#endif /* DNS_H */ diff --git a/Sources/CLibdill/epoll.c.inc b/Sources/CLibdill/epoll.c.inc new file mode 100644 index 0000000..7bc5afd --- /dev/null +++ b/Sources/CLibdill/epoll.c.inc @@ -0,0 +1,269 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include "cr.h" +#include "list.h" +#include "pollset.h" +#include "utils.h" +#include "ctx.h" + +#define DILL_ENDLIST 0xffffffff + +#define DILL_EPOLLSETSIZE 128 + +/* One of these is associated with each file descriptor. */ +struct dill_fdinfo { + /* A coroutines waiting to read from the fd or NULL. */ + struct dill_fdclause *in; + /* A coroutines waiting to write to the fd or NULL. */ + struct dill_fdclause *out; + /* Cached current state of epollset. */ + uint32_t currevs; + /* 1-based index, 0 stands for "not part of the list", DILL_ENDLIST + stands for "no more elements in the list. */ + uint32_t next; + /* 1 if the file descriptor is cached. 0 otherwise. */ + unsigned int cached : 1; +}; + +int dill_ctx_pollset_init(struct dill_ctx_pollset *ctx) { + int err; + /* Allocate one info per fd. */ + ctx->nfdinfos = dill_maxfds(); + ctx->fdinfos = calloc(ctx->nfdinfos, sizeof(struct dill_fdinfo)); + if(dill_slow(!ctx->fdinfos)) {err = ENOMEM; goto error1;} + /* Changelist is empty. */ + ctx->changelist = DILL_ENDLIST; + /* Create the kernel-side pollset. */ + ctx->efd = epoll_create(1); + if(dill_slow(ctx->efd < 0)) {err = errno; goto error2;} + return 0; +error2: + free(ctx->fdinfos); + ctx->fdinfos = NULL; +error1: + errno = err; + return -1; +} + +void dill_ctx_pollset_term(struct dill_ctx_pollset *ctx) { + int rc = close(ctx->efd); + dill_assert(rc == 0); + free(ctx->fdinfos); +} + +static void dill_fdcancelin(struct dill_clause *cl) { + struct dill_fdinfo *fdinfo = + dill_cont(cl, struct dill_fdclause, cl)->fdinfo; + fdinfo->in = NULL; + if(!fdinfo->next) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + fdinfo->next = ctx->changelist; + ctx->changelist = fdinfo - ctx->fdinfos + 1; + } +} + +static void dill_fdcancelout(struct dill_clause *cl) { + struct dill_fdinfo *fdinfo = + dill_cont(cl, struct dill_fdclause, cl)->fdinfo; + fdinfo->out = NULL; + if(!fdinfo->next) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + fdinfo->next = ctx->changelist; + ctx->changelist = fdinfo - ctx->fdinfos + 1; + } +} + +int dill_pollset_in(struct dill_fdclause *fdcl, int id, int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + if(dill_slow(fd < 0 || fd >= ctx->nfdinfos)) {errno = EBADF; return -1;} + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + /* If not yet cached, check whether the fd exists and if it does, + add it to the pollset. */ + if(dill_slow(!fdi->cached)) { + struct epoll_event ev; +#ifdef DILL_VALGRIND + memset(&ev.data, 0, sizeof(ev.data)); //Keep Valgrind happy +#endif + ev.data.fd = fd; + ev.events = EPOLLIN; + int rc = epoll_ctl(ctx->efd, EPOLL_CTL_ADD, fd, &ev); + if(dill_slow(rc < 0)) { + if(errno == ELOOP || errno == EPERM) {errno = ENOTSUP; return -1;} + return -1; + } + fdi->in = NULL; + fdi->out = NULL; + fdi->currevs = EPOLLIN; + fdi->next = 0; + fdi->cached = 1; + } + if(dill_slow(fdi->in)) {errno = EBUSY; return -1;} + /* If the fd is not yet in the pollset, add it there. */ + else if(!fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + fdcl->fdinfo = fdi; + fdi->in = fdcl; + dill_waitfor(&fdcl->cl, id, dill_fdcancelin); + return 0; +} + +int dill_pollset_out(struct dill_fdclause *fdcl, int id, int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + if(dill_slow(fd < 0 || fd >= ctx->nfdinfos)) {errno = EBADF; return -1;} + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + /* If not yet cached, check whether the fd exists and if it does, + add it to pollset. */ + if(dill_slow(!fdi->cached)) { + struct epoll_event ev; +#ifdef DILL_VALGRIND + memset(&ev.data, 0, sizeof(ev.data)); //Keep Valgrind happy +#endif + ev.data.fd = fd; + ev.events = EPOLLOUT; + int rc = epoll_ctl(ctx->efd, EPOLL_CTL_ADD, fd, &ev); + if(dill_slow(rc < 0)) { + if(errno == ELOOP || errno == EPERM) {errno = ENOTSUP; return -1;} + return -1; + } + fdi->in = NULL; + fdi->out = NULL; + fdi->currevs = EPOLLOUT; + fdi->next = 0; + fdi->cached = 1; + } + if(dill_slow(fdi->out)) {errno = EBUSY; return -1;} + /* If the fd is not yet in the pollset, add it there. */ + else if(!fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + fdcl->fdinfo = fdi; + fdi->out = fdcl; + dill_waitfor(&fdcl->cl, id, dill_fdcancelout); + return 0; +} + +int dill_pollset_clean(int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(!fdi->cached) return 0; + /* We cannot clean an fd that someone is waiting for. */ + if(dill_slow(fdi->in || fdi->out)) {errno = EBUSY; return -1;} + /* Remove the file descriptor from the pollset if it is still there. */ + if(fdi->currevs) { + struct epoll_event ev; +#ifdef DILL_VALGRIND + memset(&ev.data, 0, sizeof(ev.data)); //Keep Valgrind happy +#endif + ev.data.fd = fd; + ev.events = 0; + int rc = epoll_ctl(ctx->efd, EPOLL_CTL_DEL, fd, &ev); + dill_assert(rc == 0 || errno == ENOENT); + fdi->currevs = 0; + } + /* If needed, remove the fd from the changelist. */ + if(fdi->next) { + uint32_t *pidx = &ctx->changelist; + while(1) { + dill_assert(*pidx != 0 && *pidx != DILL_ENDLIST); + if(*pidx - 1 == fd) break; + pidx = &ctx->fdinfos[*pidx - 1].next; + } + *pidx = fdi->next; + fdi->next = 0; + } + /* Mark the fd as not used. */ + fdi->cached = 0; + return 0; +} + +int dill_pollset_poll(int timeout) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + /* Apply any changes to the pollset. + TODO: Use epoll_ctl_batch once available. */ + while(ctx->changelist != DILL_ENDLIST) { + int fd = ctx->changelist - 1; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + struct epoll_event ev; + ev.data.u64 = 0; //Keep Valgrind happy + ev.data.fd = fd; + ev.events = 0; + if(fdi->in) + ev.events |= EPOLLIN; + if(fdi->out) + ev.events |= EPOLLOUT; + if(fdi->currevs != ev.events) { + int op; + if(!ev.events) + op = EPOLL_CTL_DEL; + else if(!fdi->currevs) + op = EPOLL_CTL_ADD; + else + op = EPOLL_CTL_MOD; + fdi->currevs = ev.events; + int rc = epoll_ctl(ctx->efd, op, fd, &ev); + dill_assert(rc == 0); + } + ctx->changelist = fdi->next; + fdi->next = 0; + } + /* Wait for events. */ + struct epoll_event evs[DILL_EPOLLSETSIZE]; + int numevs = epoll_wait(ctx->efd, evs, DILL_EPOLLSETSIZE, timeout); + if(numevs < 0 && errno == EINTR) return -1; + dill_assert(numevs >= 0); + /* Fire file descriptor events. */ + int i; + for(i = 0; i != numevs; ++i) { + int fd = evs[i].data.fd; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + /* Resume blocked coroutines. */ + if(fdi->in && (evs[i].events & (EPOLLIN | EPOLLERR | EPOLLHUP))) { + dill_trigger(&fdi->in->cl, 0); + /* Remove the fd from the pollset if needed. */ + if(!fdi->in && !fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + } + if(fdi->out && (evs[i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP))) { + dill_trigger(&fdi->out->cl, 0); + /* Remove the fd from the pollset if needed. */ + if(!fdi->out && !fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + } + } + /* Return 0 on timeout or 1 if at least one coroutine was resumed. */ + return numevs > 0 ? 1 : 0; +} diff --git a/Sources/CLibdill/epoll.h.inc b/Sources/CLibdill/epoll.h.inc new file mode 100644 index 0000000..892cc54 --- /dev/null +++ b/Sources/CLibdill/epoll.h.inc @@ -0,0 +1,46 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_EPOLL_INCLUDED +#define DILL_EPOLL_INCLUDED + +#include + +#include "cr.h" +#include "list.h" + +struct dill_fdinfo; + +struct dill_fdclause { + struct dill_clause cl; + struct dill_fdinfo *fdinfo; + +}; + +struct dill_ctx_pollset { + int efd; + struct dill_fdinfo *fdinfos; + size_t nfdinfos; + uint32_t changelist; +}; + +#endif diff --git a/Sources/CLibdill/fd.c b/Sources/CLibdill/fd.c new file mode 100755 index 0000000..2f65fd0 --- /dev/null +++ b/Sources/CLibdill/fd.c @@ -0,0 +1,299 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include + +#include "libdill.h" +#include "fd.h" +#include "iol.h" +#include "utils.h" + +#if defined MSG_NOSIGNAL +#define FD_NOSIGNAL MSG_NOSIGNAL +#else +#define FD_NOSIGNAL 0 +#endif + +void fd_initrxbuf(struct fd_rxbuf *rxbuf) { + dill_assert(rxbuf); + rxbuf->len = 0; + rxbuf->pos = 0; +} + +int fd_unblock(int s) { + /* Switch to non-blocking mode. */ + int opt = fcntl(s, F_GETFL, 0); + if (opt == -1) + opt = 0; + int rc = fcntl(s, F_SETFL, opt | O_NONBLOCK); + if(dill_slow(rc < 0)) return -1; + /* Allow re-using the same local address rapidly. */ + opt = 1; + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)); + if(dill_slow(rc < 0)) return -1; + /* If possible, prevent SIGPIPE signal when writing to the connection + already closed by the peer. */ +#ifdef SO_NOSIGPIPE + opt = 1; + rc = setsockopt (s, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof (opt)); + if(dill_slow(rc < 0 && errno != EINVAL)) return -1; +#endif + return 0; +} + +int fd_connect(int s, const struct sockaddr *addr, socklen_t addrlen, + int64_t deadline) { + /* Initiate connect. */ + int rc = connect(s, addr, addrlen); + if(rc == 0) return 0; + if(dill_slow(errno != EINPROGRESS)) return -1; + /* Connect is in progress. Let's wait till it's done. */ + rc = fdout(s, deadline); + if(dill_slow(rc == -1)) return -1; + /* Retrieve the error from the socket, if any. */ + int err = 0; + socklen_t errsz = sizeof(err); + rc = getsockopt(s, SOL_SOCKET, SO_ERROR, (void*)&err, &errsz); + if(dill_slow(rc != 0)) return -1; + if(dill_slow(err != 0)) {errno = err; return -1;} + return 0; +} + +int fd_accept(int s, struct sockaddr *addr, socklen_t *addrlen, + int64_t deadline) { + int as; + while(1) { + /* Try to accept new connection synchronously. */ + as = accept(s, addr, addrlen); + if(dill_fast(as >= 0)) + break; + /* If connection was aborted by the peer grab the next one. */ + if(dill_slow(errno == ECONNABORTED)) continue; + /* Propagate other errors to the caller. */ + if(dill_slow(errno != EAGAIN && errno != EWOULDBLOCK)) return -1; + /* Operation is in progress. Wait till new connection is available. */ + int rc = fdin(s, deadline); + if(dill_slow(rc < 0)) return -1; + } + int rc = fd_unblock(as); + if(dill_slow(rc < 0)) return -1; + return as; +} + +int fd_send(int s, struct iolist *first, struct iolist *last, + int64_t deadline) { + /* Make a local iovec array. */ + /* TODO: This is dangerous, it may cause stack overflow. + There should probably be a on-heap per-socket buffer for that. */ + size_t niov; + int rc = iol_check(first, last, &niov, NULL); + if(dill_slow(rc < 0)) return -1; + struct iovec iov[niov]; + iol_toiov(first, iov); + /* Message header will act as an iterator in the following loop. */ + struct msghdr hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.msg_iov = iov; + hdr.msg_iovlen = niov; + /* It is very likely that at least one byte can be sent. Therefore, + to improve efficiency, try to send and resort to fdout() only after + send failed. */ + while(1) { + while(hdr.msg_iovlen && !hdr.msg_iov[0].iov_len) { + hdr.msg_iov++; + hdr.msg_iovlen--; + } + if(!hdr.msg_iovlen) return 0; + ssize_t sz = sendmsg(s, &hdr, FD_NOSIGNAL); + dill_assert(sz != 0); + if(sz < 0) { + if(dill_slow(errno != EWOULDBLOCK && errno != EAGAIN)) { + if(errno == EPIPE) errno = ECONNRESET; + return -1; + } + sz = 0; + } + /* Adjust the iovec array so that it doesn't contain data + that was already sent. */ + while(sz) { + struct iovec *head = &hdr.msg_iov[0]; + if(head->iov_len > sz) { + head->iov_base += sz; + head->iov_len -= sz; + break; + } + sz -= head->iov_len; + hdr.msg_iov++; + hdr.msg_iovlen--; + if(!hdr.msg_iovlen) return 0; + } + /* Wait till more data can be sent. */ + int rc = fdout(s, deadline); + if(dill_slow(rc < 0)) return -1; + } +} + +/* Same as fd_recv() but with no rx buffering. */ +static ssize_t fd_recv_(int s, struct iolist *first, struct iolist *last, + int64_t deadline) { + /* Make a local iovec array. */ + /* TODO: This is dangerous, it may cause stack overflow. + There should probably be a on-heap per-socket buffer for that. */ + size_t niov; + int rc = iol_check(first, last, &niov, NULL); + if(dill_slow(rc < 0)) return -1; + struct iovec iov[niov]; + iol_toiov(first, iov); + /* Message header will act as an iterator in the following loop. */ + struct msghdr hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.msg_iov = iov; + hdr.msg_iovlen = niov; + while(1) { + ssize_t sz = recvmsg(s, &hdr, 0); + if(dill_slow(sz == 0)) {errno = EPIPE; return -1;} + if(sz < 0) { + if(dill_slow(errno != EWOULDBLOCK && errno != EAGAIN)) { + if(errno == EPIPE) errno = ECONNRESET; + return -1; + } + /* Wait for more data. */ + int rc = fdin(s, deadline); + if(dill_slow(rc < 0)) return -1; + continue; + } + return sz; + } +} + +/* Skip len bytes. If len is negative skip until error occurs. */ +static ssize_t fd_skip(int s, ssize_t len, int64_t deadline) { + uint8_t buf[512]; + while(len) { + size_t to_recv = len < 0 || len > sizeof(buf) ? sizeof(buf) : len; + struct iolist iol = {buf, to_recv, NULL, 0}; + ssize_t sz = fd_recv_(s, &iol, &iol, deadline); + if(dill_slow(sz < 0)) return -1; + if(len >= 0) len -= sz; + } + return 0; +} + +/* Copy data from rxbuf to one iolist structure. + Returns number of bytes copied. */ +static size_t fd_copy(struct fd_rxbuf *rxbuf, struct iolist *iol) { + size_t rmn = rxbuf->len - rxbuf->pos; + if(rmn < iol->iol_len) { + if(dill_fast(iol->iol_base)) + memcpy(iol->iol_base, rxbuf->data + rxbuf->pos, rmn); + rxbuf->len = 0; + rxbuf->pos = 0; + return rmn; + } + else { + if(dill_fast(iol->iol_base)) + memcpy(iol->iol_base, rxbuf->data + rxbuf->pos, iol->iol_len); + rxbuf->pos += iol->iol_len; + return iol->iol_len; + } +} + +ssize_t fd_recv(int s, struct fd_rxbuf *rxbuf, struct iolist *first, + struct iolist *last, int64_t deadline) { + /* Skip all data until error occurs. */ + if(dill_slow(!first && !last)) return fd_skip(s, -1, deadline); + /* Fill in data from the rxbuf. */ + size_t sz; + ssize_t read = 0; + while(1) { + sz = fd_copy(rxbuf, first); + read += sz; + if(sz < first->iol_len) break; + first = first->iol_next; + if(!first) return read; + } + /* Copy the current iolist element so that we can modify it without + changing the original list. */ + struct iolist curr; + curr.iol_base = first->iol_base ? first->iol_base + sz : NULL; + curr.iol_len = first->iol_len - sz; + curr.iol_next = first->iol_next; + curr.iol_rsvd = 0; + /* Find out how much data is still missing. */ + size_t miss = 0; + struct iolist *it = &curr; + while(it) { + miss += it->iol_len; + it = it->iol_next; + } + /* If requested amount of data is larger than rx buffer avoid the copy + and read it directly into user's buffer. */ + if(miss > sizeof(rxbuf->data)) + return read + fd_recv_(s, &curr, curr.iol_next ? last : &curr, deadline); + /* If small amount of data is requested use rx buffer. */ + while(1) { + /* Read as much data as possible to the buffer to avoid extra + syscalls. Do the speculative recv() first to avoid extra + polling. Do fdin() only after recv() fails to get data. */ + ssize_t sz = recv(s, rxbuf->data, sizeof(rxbuf->data), 0); + if(dill_slow(sz == 0)) {errno = EPIPE; return -1;} + if(sz < 0) { + if(dill_slow(errno != EWOULDBLOCK && errno != EAGAIN)) { + if(errno == EPIPE) errno = ECONNRESET; + return -1; + } + /* Wait for more data. */ + int rc = fdin(s, deadline); + if(dill_slow(rc < 0)) return -1; + continue; + } + rxbuf->len = sz; + rxbuf->pos = 0; + /* Copy the data from rxbuffer to the iolist. */ + while(1) { + sz = fd_copy(rxbuf, &curr); + read += sz; + if(sz < curr.iol_len) return read; + if(!curr.iol_next) return read; + curr = *curr.iol_next; + } + } +} + +void fd_close(int s) { + fdclean(s); + /* Discard any pending outbound data. If SO_LINGER option cannot + be set, never mind and continue anyway. */ + struct linger lng; + lng.l_onoff=1; + lng.l_linger=0; + setsockopt(s, SOL_SOCKET, SO_LINGER, (void*)&lng, sizeof(lng)); + /* We are not checking the error here. close() has inconsistent behaviour + and leaking a file descriptor is better than crashing the entire + program. */ + close(s); +} + diff --git a/Sources/CLibdill/fd.h b/Sources/CLibdill/fd.h new file mode 100755 index 0000000..25ca61d --- /dev/null +++ b/Sources/CLibdill/fd.h @@ -0,0 +1,69 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_FD_INCLUDED +#define DILL_FD_INCLUDED + +#include +#include +#include + +#include "libdill.h" + +struct fd_rxbuf { + size_t len; + size_t pos; + uint8_t data[2000]; +}; + +void fd_initrxbuf( + struct fd_rxbuf *rxbuf); +int fd_unblock( + int s); +int fd_connect( + int s, + const struct sockaddr *addr, + socklen_t addrlen, + int64_t deadline); +int fd_accept( + int s, + struct sockaddr *addr, + socklen_t *addrlen, + int64_t deadline); +int fd_send( + int s, + struct iolist *first, + struct iolist *last, + int64_t deadline); +ssize_t fd_recv( + int s, + struct fd_rxbuf *rxbuf, + struct iolist *first, + struct iolist *last, + int64_t deadline); +void fd_close( + int s); + +#endif + diff --git a/Sources/CLibdill/handle.c b/Sources/CLibdill/handle.c new file mode 100755 index 0000000..afa73de --- /dev/null +++ b/Sources/CLibdill/handle.c @@ -0,0 +1,156 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include + +#include "cr.h" +#include "handle.h" +#include "libdillimpl.h" +#include "utils.h" +#include "ctx.h" + +struct dill_handle { + /* Table of virtual functions. */ + struct hvfs *vfs; + /* Index of the next handle in the linked list of unused handles. -1 means + 'the end of the list'. -2 means 'handle is in use'. */ + int next; + /* Cache the last call to hquery. */ + const void *type; + void *ptr; +}; + +#define CHECKHANDLE(h, err) \ + if(dill_slow((h) < 0 || (h) >= ctx->nhandles ||\ + ctx->handles[(h)].next != -2)) {\ + errno = EBADF; return (err);}\ + struct dill_handle *hndl = &ctx->handles[(h)]; + +int dill_ctx_handle_init(struct dill_ctx_handle *ctx) { + ctx->handles = NULL; + ctx->nhandles = 0; + ctx->unused = -1; + return 0; +} + +void dill_ctx_handle_term(struct dill_ctx_handle *ctx) { + free(ctx->handles); +} + +int hmake(struct hvfs *vfs) { + struct dill_ctx_handle *ctx = &dill_getctx->handle; + if(dill_slow(!vfs || !vfs->query || !vfs->close)) { + errno = EINVAL; return -1;} + /* Returns ECANCELED if shutting down. */ + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* If there's no space for the new handle, expand the array. */ + if(dill_slow(ctx->unused == -1)) { + /* Start with 256 handles, double the size when needed. */ + int sz = ctx->nhandles ? ctx->nhandles * 2 : 256; + struct dill_handle *hndls = + realloc(ctx->handles, sz * sizeof(struct dill_handle)); + if(dill_slow(!hndls)) {errno = ENOMEM; return -1;} + /* Add newly allocated handles to the list of unused handles. */ + int i; + for(i = ctx->nhandles; i != sz - 1; ++i) + hndls[i].next = i + 1; + hndls[sz - 1].next = -1; + ctx->unused = ctx->nhandles; + /* Adjust the array. */ + ctx->handles = hndls; + ctx->nhandles = sz; + } + /* Return first handle from the list of unused handles. */ + int h = ctx->unused; + ctx->unused = ctx->handles[h].next; + vfs->refcount = 1; + ctx->handles[h].vfs = vfs; + ctx->handles[h].next = -2; + ctx->handles[h].type = NULL; + ctx->handles[h].ptr = NULL; + return h; +} + +int hdup(int h) { + struct dill_ctx_handle *ctx = &dill_getctx->handle; + CHECKHANDLE(h, -1); + int refcount = hndl->vfs->refcount; + int res = hmake(hndl->vfs); + if(dill_slow(res < 0)) return -1; + ctx->handles[res].vfs->refcount = refcount + 1; + return res; +} + +void *hquery(int h, const void *type) { + struct dill_ctx_handle *ctx = &dill_getctx->handle; + CHECKHANDLE(h, NULL); + /* Try and use the cached pointer first; otherwise do the expensive virtual call.*/ + if(dill_fast(hndl->ptr != NULL && hndl->type == type)) + return hndl->ptr; + else { + void *ptr = hndl->vfs->query(hndl->vfs, type); + if(dill_slow(!ptr)) return NULL; + /* Update cache. */ + hndl->type = type; + hndl->ptr = ptr; + return ptr; + } +} + +int hclose(int h) { + struct dill_ctx_handle *ctx = &dill_getctx->handle; + CHECKHANDLE(h, -1); + /* If there are multiple duplicates of this handle, just remove one reference. */ + if(hndl->vfs->refcount > 1) { + --hndl->vfs->refcount; + return 0; + } + /* This will guarantee that blocking functions cannot be called anywhere + inside the context of the close. */ + int old = dill_no_blocking(1); + /* Send the stop signal to the handle. */ + hndl->vfs->close(hndl->vfs); + /* Restore the previous state. */ + dill_no_blocking(old); + /* Mark the cache as invalid. */ + hndl->ptr = NULL; + /* Return a handle to the shared pool. */ + hndl->next = ctx->unused; + ctx->unused = h; + return 0; +} + +int hdone(int h, int64_t deadline) { + struct dill_ctx_handle *ctx = &dill_getctx->handle; + CHECKHANDLE(h, -1); + if(dill_slow(!hndl->vfs->done)) {errno = ENOTSUP; return -1;} + return hndl->vfs->done(hndl->vfs, deadline); +} + diff --git a/Sources/CLibdill/handle.h b/Sources/CLibdill/handle.h new file mode 100755 index 0000000..719f605 --- /dev/null +++ b/Sources/CLibdill/handle.h @@ -0,0 +1,40 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_HANDLE_INCLUDED +#define DILL_HANDLE_INCLUDED + +struct dill_handle; + +struct dill_ctx_handle { + /* Array of handles. The size of the array is stored in 'nhandles'. */ + struct dill_handle *handles; + int nhandles; + /* Points to the first item in the list of unused handles. */ + int unused; +}; + +int dill_ctx_handle_init(struct dill_ctx_handle *ctx); +void dill_ctx_handle_term(struct dill_ctx_handle *ctx); + +#endif + diff --git a/Sources/CLibdill/include/CLibdill.h b/Sources/CLibdill/include/CLibdill.h new file mode 100644 index 0000000..d54a9a9 --- /dev/null +++ b/Sources/CLibdill/include/CLibdill.h @@ -0,0 +1,7 @@ +#ifndef CLibdill_h +#define CLibdill_h + +#include "libdill.h" +#include "libdillimpl.h" + +#endif diff --git a/Sources/CLibdill/include/libdill.h b/Sources/CLibdill/include/libdill.h new file mode 120000 index 0000000..0153301 --- /dev/null +++ b/Sources/CLibdill/include/libdill.h @@ -0,0 +1 @@ +../libdill.h \ No newline at end of file diff --git a/Sources/CLibdill/include/libdillimpl.h b/Sources/CLibdill/include/libdillimpl.h new file mode 120000 index 0000000..e150960 --- /dev/null +++ b/Sources/CLibdill/include/libdillimpl.h @@ -0,0 +1 @@ +../libdillimpl.h \ No newline at end of file diff --git a/Sources/CLibdill/iol.c b/Sources/CLibdill/iol.c new file mode 100755 index 0000000..51c89f6 --- /dev/null +++ b/Sources/CLibdill/iol.c @@ -0,0 +1,62 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include + +#include "iol.h" +#include "utils.h" + +int iol_check(struct iolist *first, struct iolist *last, + size_t *nbufs, size_t *nbytes) { + if(dill_slow(!first || !last || last->iol_next)) { + errno = EINVAL; return -1;} + size_t nbf = 0, nbt = 0, res = 0; + struct iolist *it; + for(it = first; it; it = it->iol_next) { + if(dill_slow(it->iol_rsvd || (!it->iol_next && it != last))) + goto error; + it->iol_rsvd = 1; + nbf++; + nbt += it->iol_len; + } + for(it = first; it; it = it->iol_next) it->iol_rsvd = 0; + if(nbufs) *nbufs = nbf; + if(nbytes) *nbytes = nbt; + return 0; +error:; + struct iolist *it2; + for(it2 = first; it2 != it; it2 = it2->iol_next) it->iol_rsvd = 0; + errno = EINVAL; + return -1; +} + +void iol_toiov(struct iolist *first, struct iovec *iov) { + while(first) { + iov->iov_base = first->iol_base; + iov->iov_len = first->iol_len; + ++iov; + first = first->iol_next; + } +} + diff --git a/Sources/CLibdill/iol.h b/Sources/CLibdill/iol.h new file mode 100755 index 0000000..74b8fa4 --- /dev/null +++ b/Sources/CLibdill/iol.h @@ -0,0 +1,44 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_IOL_INCLUDED +#define DILL_IOL_INCLUDED + +#include + +#include "libdill.h" + +/* Checks whether iolist is valid. Returns 0 in case of success or -1 in case + of error. Fills in number of buffers in the list and overall number of bytes + if requested. */ +int iol_check(struct iolist *first, struct iolist *last, + size_t *nbufs, size_t *nbytes); + +/* Copy the iolist into an iovec. Iovec must have at least as much elements + as the iolist, otherwise undefined behaviour ensues. The data buffers + as such are not affected by this operation .*/ +void iol_toiov(struct iolist *first, struct iovec *iov); + +#endif + diff --git a/Sources/CLibdill/ipaddr.c b/Sources/CLibdill/ipaddr.c new file mode 100755 index 0000000..ba437dd --- /dev/null +++ b/Sources/CLibdill/ipaddr.c @@ -0,0 +1,380 @@ +/* + + Copyright (c) 2015 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#if defined __linux__ +#define _GNU_SOURCE +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#if !defined __sun +#include +#endif +#include +#include +#include + +#include "dns/dns.h" + +#include "libdill.h" +#include "utils.h" + +/* Make sure that both IPv4 and IPv6 address fits into ipaddr. */ +DILL_CT_ASSERT(sizeof(struct ipaddr) >= sizeof(struct sockaddr_in)); +DILL_CT_ASSERT(sizeof(struct ipaddr) >= sizeof(struct sockaddr_in6)); + +static struct dns_resolv_conf *dill_dns_conf = NULL; +static struct dns_hosts *dill_dns_hosts = NULL; +static struct dns_hints *dill_dns_hints = NULL; + +static int ipaddr_ipany(struct ipaddr *addr, int port, int mode) +{ + if(dill_slow(port < 0 || port > 0xffff)) {errno = EINVAL; return -1;} + if (mode == 0 || mode == IPADDR_IPV4 || mode == IPADDR_PREF_IPV4) { + struct sockaddr_in *ipv4 = (struct sockaddr_in*)addr; + ipv4->sin_family = AF_INET; + ipv4->sin_addr.s_addr = htonl(INADDR_ANY); + ipv4->sin_port = htons((uint16_t)port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + ipv4->sin_len = sizeof(struct sockaddr_in); +#endif + return 0; + } + else { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6*)addr; + ipv6->sin6_family = AF_INET6; + memcpy(&ipv6->sin6_addr, &in6addr_any, sizeof(in6addr_any)); + ipv6->sin6_port = htons((uint16_t)port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + ipv6->sin6_len = sizeof(struct sockaddr_in6); +#endif + return 0; + } +} + +/* Convert literal IPv4 address to a binary one. */ +static int ipaddr_ipv4_literal(struct ipaddr *addr, const char *name, + int port) { + struct sockaddr_in *ipv4 = (struct sockaddr_in*)addr; + int rc = inet_pton(AF_INET, name, &ipv4->sin_addr); + dill_assert(rc >= 0); + if(dill_slow(rc != 1)) {errno = EINVAL; return -1;} + ipv4->sin_family = AF_INET; + ipv4->sin_port = htons((uint16_t)port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + ipv4->sin_len = sizeof(struct sockaddr_in); +#endif + return 0; +} + +/* Convert literal IPv6 address to a binary one. */ +static int ipaddr_ipv6_literal(struct ipaddr *addr, const char *name, + int port) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6*)addr; + int rc = inet_pton(AF_INET6, name, &ipv6->sin6_addr); + dill_assert(rc >= 0); + if(dill_slow(rc != 1)) {errno = EINVAL; return -1;} + ipv6->sin6_family = AF_INET6; + ipv6->sin6_port = htons((uint16_t)port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + ipv6->sin6_len = sizeof(struct sockaddr_in6); +#endif + return 0; +} + +/* Convert literal IPv4 or IPv6 address to a binary one. */ +static int ipaddr_literal(struct ipaddr *addr, const char *name, int port, + int mode) { + if(dill_slow(!addr || port < 0 || port > 0xffff)) { + errno = EINVAL; + return -1; + } + int rc; + switch(mode) { + case IPADDR_IPV4: + return ipaddr_ipv4_literal(addr, name, port); + case IPADDR_IPV6: + return ipaddr_ipv6_literal(addr, name, port); + case 0: + case IPADDR_PREF_IPV4: + rc = ipaddr_ipv4_literal(addr, name, port); + if(rc == 0) + return 0; + return ipaddr_ipv6_literal(addr, name, port); + case IPADDR_PREF_IPV6: + rc = ipaddr_ipv6_literal(addr, name, port); + if(rc == 0) + return 0; + return ipaddr_ipv4_literal(addr, name, port); + default: + dill_assert(0); + } +} + +int ipaddr_family(const struct ipaddr *addr) { + return ((struct sockaddr*)addr)->sa_family; +} + +int ipaddr_len(const struct ipaddr *addr) { + return ipaddr_family(addr) == AF_INET ? + sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); +} + +const struct sockaddr *ipaddr_sockaddr(const struct ipaddr *addr) { + return (const struct sockaddr*)addr; +} + +int ipaddr_port(const struct ipaddr *addr) { + return ntohs(ipaddr_family(addr) == AF_INET ? + ((struct sockaddr_in*)addr)->sin_port : + ((struct sockaddr_in6*)addr)->sin6_port); +} + +void ipaddr_setport(struct ipaddr *addr, int port) { + if(ipaddr_family(addr) == AF_INET) + ((struct sockaddr_in*)addr)->sin_port = htons(port); + else + ((struct sockaddr_in6*)addr)->sin6_port = htons(port); +} + +/* Convert IP address from network format to ASCII dot notation. */ +const char *ipaddr_str(const struct ipaddr *addr, char *ipstr) { + if(ipaddr_family(addr) == AF_INET) { + return inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), + ipstr, INET_ADDRSTRLEN); + } + else { + return inet_ntop(AF_INET6, &(((struct sockaddr_in6*)addr)->sin6_addr), + ipstr, INET6_ADDRSTRLEN); + } +} + +int ipaddr_local(struct ipaddr *addr, const char *name, int port, int mode) { + if(!name) + return ipaddr_ipany(addr, port, mode); + int rc = ipaddr_literal(addr, name, port, mode); +#if defined __sun + return rc; +#else + if(rc == 0) + return 0; + /* Address is not a literal. It must be an interface name then. */ + struct ifaddrs *ifaces = NULL; + rc = getifaddrs (&ifaces); + dill_assert (rc == 0); + dill_assert (ifaces); + /* Find first IPv4 and first IPv6 address. */ + struct ifaddrs *ipv4 = NULL; + struct ifaddrs *ipv6 = NULL; + struct ifaddrs *it; + for(it = ifaces; it != NULL; it = it->ifa_next) { + if(!it->ifa_addr) + continue; + if(strcmp(it->ifa_name, name) != 0) + continue; + switch(it->ifa_addr->sa_family) { + case AF_INET: + dill_assert(!ipv4); + ipv4 = it; + break; + case AF_INET6: + dill_assert(!ipv6); + ipv6 = it; + break; + } + if(ipv4 && ipv6) + break; + } + /* Choose the correct address family based on mode. */ + switch(mode) { + case IPADDR_IPV4: + ipv6 = NULL; + break; + case IPADDR_IPV6: + ipv4 = NULL; + break; + case 0: + case IPADDR_PREF_IPV4: + if(ipv4) + ipv6 = NULL; + break; + case IPADDR_PREF_IPV6: + if(ipv6) + ipv4 = NULL; + break; + default: + dill_assert(0); + } + if(ipv4) { + struct sockaddr_in *inaddr = (struct sockaddr_in*)addr; + memcpy(inaddr, ipv4->ifa_addr, sizeof (struct sockaddr_in)); + inaddr->sin_port = htons(port); + freeifaddrs(ifaces); + return 0; + } + if(ipv6) { + struct sockaddr_in6 *inaddr = (struct sockaddr_in6*)addr; + memcpy(inaddr, ipv6->ifa_addr, sizeof (struct sockaddr_in6)); + inaddr->sin6_port = htons(port); + freeifaddrs(ifaces); + return 0; + } + freeifaddrs(ifaces); + errno = ENODEV; + return -1; +#endif +} + +static void dns_freeaddrinfo(struct addrinfo *ent) { + free(ent); +} + +int ipaddr_remote(struct ipaddr *addr, const char *name, int port, int mode, + int64_t deadline) { + int rc = ipaddr_literal(addr, name, port, mode); + if(rc == 0) + return 0; + /* Load DNS config files, unless they are already chached. */ + if(dill_slow(!dill_dns_conf)) { + /* TODO: Maybe re-read the configuration once in a while? */ + dill_dns_conf = dns_resconf_local(&rc); + if(!dill_dns_conf) { + errno = ENOENT; + return -1; + } + dill_dns_hosts = dns_hosts_local(&rc); + dill_assert(dill_dns_hosts); + dill_dns_hints = dns_hints_local(dill_dns_conf, &rc); + dill_assert(dill_dns_hints); + } + /* Let's do asynchronous DNS query here. */ + struct dns_resolver *resolver = dns_res_open(dill_dns_conf, + dill_dns_hosts, dill_dns_hints, NULL, dns_opts(), &rc); + if(!resolver) { + if(errno == ENFILE || errno == EMFILE) { + return -1; + } + dill_assert(resolver); + } + dill_assert(port >= 0 && port <= 0xffff); + char portstr[8]; + snprintf(portstr, sizeof(portstr), "%d", port); + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + struct dns_addrinfo *ai = dns_ai_open(name, portstr, DNS_T_A, &hints, + resolver, &rc); + dill_assert(ai); + dns_res_close(resolver); + struct addrinfo *ipv4 = NULL; + struct addrinfo *ipv6 = NULL; + struct addrinfo *it = NULL; + while(1) { + rc = dns_ai_nextent(&it, ai); + if(rc == EAGAIN) { + int fd = dns_ai_pollfd(ai); + int events = dns_ai_events(ai); + dill_assert(fd >= 0); + int rc = (events & DNS_POLLOUT) + ? fdout(fd, deadline) : fdin(fd, deadline); + /* There's no guarantee that the file descriptor will be reused + in next iteration. We have to clean the fdwait cache here + to be on the safe side. */ + int err = errno; + fdclean(fd); + errno = err; + if(dill_slow(rc < 0)) { + dns_ai_close(ai); + return -1; + } + continue; + } + if(rc == ENOENT || (rc >= DNS_EBASE && rc <= DNS_ELAST)) + break; + if(!ipv4 && it && it->ai_family == AF_INET) { + ipv4 = it; + it = NULL; + } + if(!ipv6 && it && it->ai_family == AF_INET6) { + ipv6 = it; + it = NULL; + } + dns_freeaddrinfo(it); /* Ended up useless */ + if(ipv4 && ipv6) + break; + } + switch(mode) { + case IPADDR_IPV4: + dns_freeaddrinfo(ipv6); + ipv6 = NULL; + break; + case IPADDR_IPV6: + dns_freeaddrinfo(ipv4); + ipv4 = NULL; + break; + case 0: + case IPADDR_PREF_IPV4: + if(ipv4) { + dns_freeaddrinfo(ipv6); + ipv6 = NULL; + } + break; + case IPADDR_PREF_IPV6: + if(ipv6) { + dns_freeaddrinfo(ipv4); + ipv4 = NULL; + } + break; + default: + dill_assert(0); + } + if(ipv4) { + struct sockaddr_in *inaddr = (struct sockaddr_in*)addr; + memcpy(inaddr, ipv4->ai_addr, sizeof (struct sockaddr_in)); + inaddr->sin_port = htons(port); + dns_freeaddrinfo(ipv4); + dns_freeaddrinfo(ipv6); + dns_ai_close(ai); + return 0; + } + if(ipv6) { + struct sockaddr_in6 *inaddr = (struct sockaddr_in6*)addr; + memcpy(inaddr, ipv6->ai_addr, sizeof (struct sockaddr_in6)); + inaddr->sin6_port = htons(port); + dns_freeaddrinfo(ipv4); + dns_freeaddrinfo(ipv6); + dns_ai_close(ai); + return 0; + } + dns_ai_close(ai); + errno = EADDRNOTAVAIL; + return -1; +} + diff --git a/Sources/CLibdill/ipc.c b/Sources/CLibdill/ipc.c new file mode 100755 index 0000000..9ff6fb4 --- /dev/null +++ b/Sources/CLibdill/ipc.c @@ -0,0 +1,323 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include + +#include "libdillimpl.h" +#include "fd.h" +#include "utils.h" + +static int ipc_resolve(const char *addr, struct sockaddr_un *su); +static int ipc_makeconn(int fd); + +/******************************************************************************/ +/* UNIX connection socket */ +/******************************************************************************/ + +dill_unique_id(ipc_type); + +static void *ipc_hquery(struct hvfs *hvfs, const void *type); +static void ipc_hclose(struct hvfs *hvfs); +static int ipc_hdone(struct hvfs *hvfs, int64_t deadline); +static int ipc_bsendl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline); +static ssize_t ipc_brecvl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline); + +struct ipc_conn { + struct hvfs hvfs; + struct bsock_vfs bvfs; + int fd; + struct fd_rxbuf rxbuf; + unsigned int indone : 1; + unsigned int outdone : 1; + unsigned int inerr : 1; + unsigned int outerr : 1; +}; + +static void *ipc_hquery(struct hvfs *hvfs, const void *type) { + struct ipc_conn *self = (struct ipc_conn*)hvfs; + if(type == bsock_type) return &self->bvfs; + if(type == ipc_type) return self; + errno = ENOTSUP; + return NULL; +} + +int ipc_connect(const char *addr, int64_t deadline) { + int err; + /* Create a UNIX address out of the address string. */ + struct sockaddr_un su; + int rc = ipc_resolve(addr, &su); + if(rc < 0) {err = errno; goto error1;} + /* Open a socket. */ + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if(dill_slow(s < 0)) {err = errno; goto error1;} + /* Set it to non-blocking mode. */ + rc = fd_unblock(s); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Connect to the remote endpoint. */ + rc = fd_connect(s, (struct sockaddr*)&su, sizeof(su), deadline); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Create the handle. */ + int h = ipc_makeconn(s); + if(dill_slow(h < 0)) {err = errno; goto error2;} + return h; +error2: + fd_close(s); +error1: + errno = err; + return -1; +} + +static int ipc_bsendl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline) { + struct ipc_conn *self = dill_cont(bvfs, struct ipc_conn, bvfs); + if(dill_slow(self->outdone)) {errno = EPIPE; return -1;} + if(dill_slow(self->outerr)) {errno = ECONNRESET; return -1;} + ssize_t sz = fd_send(self->fd, first, last, deadline); + if(dill_fast(sz >= 0)) return sz; + self->outerr = 1; + return -1; +} + +static ssize_t ipc_brecvl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline) { + struct ipc_conn *self = dill_cont(bvfs, struct ipc_conn, bvfs); + if(dill_slow(self->indone)) {errno = EPIPE; return -1;} + if(dill_slow(self->inerr)) {errno = ECONNRESET; return -1;} + int sz = fd_recv(self->fd, &self->rxbuf, first, last, deadline); + if(dill_fast(sz > 0)) return sz; + if(errno == EPIPE) self->indone = 1; + else self->inerr = 1; + return -1; +} + +static int ipc_hdone(struct hvfs *hvfs, int64_t deadline) { + struct ipc_conn *self = (struct ipc_conn*)hvfs; + if(dill_slow(self->outdone)) {errno = EPIPE; return -1;} + if(dill_slow(self->outerr)) {errno = ECONNRESET; return -1;} + /* Shutdown is done asynchronously on kernel level. + No need to use the deadline. */ + int rc = shutdown(self->fd, SHUT_WR); + if(dill_slow(rc < 0)) { + if(errno == ENOTCONN) {self->outerr = 1; errno = ECONNRESET; return -1;} + if(errno == ENOBUFS) {self->outerr = 1; errno = ENOMEM; return -1;} + dill_assert(0); + } + self->outdone = 1; + return 0; +} + +int ipc_close(int s, int64_t deadline) { + int err; + struct ipc_conn *self = hquery(s, ipc_type); + if(dill_slow(!self)) return -1; + if(dill_slow(self->inerr || self->outerr)) {err = ECONNRESET; goto error;} + /* If not done already, flush the outbound data and start the terminal + handshake. */ + if(!self->outdone) { + int rc = ipc_hdone(&self->hvfs, deadline); + if(dill_slow(rc < 0)) {err = errno; goto error;} + } + /* Now we are going to read all the inbound data until we reach end of the + stream. That way we can be sure that the peer either received all our + data or consciously closed the connection without reading all of it. */ + int rc = ipc_brecvl(&self->bvfs, NULL, NULL, deadline); + dill_assert(rc < 0); + if(dill_slow(errno != EPIPE)) {err = errno; goto error;} + return 0; +error: + ipc_hclose(&self->hvfs); + errno = err; + return -1; +} + +static void ipc_hclose(struct hvfs *hvfs) { + struct ipc_conn *self = (struct ipc_conn*)hvfs; + fd_close(self->fd); + free(self); +} + +/******************************************************************************/ +/* UNIX listener socket */ +/******************************************************************************/ + +dill_unique_id(ipc_listener_type); + +static void *ipc_listener_hquery(struct hvfs *hvfs, const void *type); +static void ipc_listener_hclose(struct hvfs *hvfs); + +struct ipc_listener { + struct hvfs hvfs; + int fd; +}; + +static void *ipc_listener_hquery(struct hvfs *hvfs, const void *type) { + struct ipc_listener *self = (struct ipc_listener*)hvfs; + if(type == ipc_listener_type) return self; + errno = ENOTSUP; + return NULL; +} + +int ipc_listen(const char *addr, int backlog) { + int err; + /* Create a UNIX address out of the address string. */ + struct sockaddr_un su; + int rc = ipc_resolve(addr, &su); + if(rc < 0) {err = errno; goto error1;} + /* Open the listening socket. */ + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if(dill_slow(s < 0)) {err = errno; goto error1;} + /* Set it to non-blocking mode. */ + rc = fd_unblock(s); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Start listening for incoming connections. */ + rc = bind(s, (struct sockaddr*)&su, sizeof(su)); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + rc = listen(s, backlog); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Create the object. */ + struct ipc_listener *self = malloc(sizeof(struct ipc_listener)); + if(dill_slow(!self)) {err = ENOMEM; goto error2;} + self->hvfs.query = ipc_listener_hquery; + self->hvfs.close = ipc_listener_hclose; + self->hvfs.done = NULL; + self->fd = s; + /* Create handle. */ + int h = hmake(&self->hvfs); + if(dill_slow(h < 0)) {err = errno; goto error3;} + return h; +error3: + free(self); +error2: + close(s); +error1: + errno = err; + return -1; +} + +int ipc_accept(int s, int64_t deadline) { + int err; + /* Retrieve the listener object. */ + struct ipc_listener *lst = hquery(s, ipc_listener_type); + if(dill_slow(!lst)) {err = errno; goto error1;} + /* Try to get new connection in a non-blocking way. */ + int as = fd_accept(lst->fd, NULL, NULL, deadline); + if(dill_slow(as < 0)) {err = errno; goto error1;} + /* Set it to non-blocking mode. */ + int rc = fd_unblock(as); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Create the handle. */ + int h = ipc_makeconn(as); + if(dill_slow(h < 0)) {err = errno; goto error2;} + return h; +error2: + fd_close(as); +error1: + errno = err; + return -1; +} + +static void ipc_listener_hclose(struct hvfs *hvfs) { + struct ipc_listener *self = (struct ipc_listener*)hvfs; + fd_close(self->fd); + free(self); +} + +/******************************************************************************/ +/* UNIX pair */ +/******************************************************************************/ + +int ipc_pair(int s[2]) { + int err; + /* Create the pair. */ + int fds[2]; + int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + if(rc < 0) {err = errno; goto error1;} + /* Set the sockets to non-blocking mode. */ + rc = fd_unblock(fds[0]); + if(dill_slow(rc < 0)) {err = errno; goto error3;} + rc = fd_unblock(fds[1]); + if(dill_slow(rc < 0)) {err = errno; goto error3;} + /* Create the handles. */ + s[0] = ipc_makeconn(fds[0]); + if(dill_slow(s[0] < 0)) {err = errno; goto error3;} + s[1] = ipc_makeconn(fds[1]); + if(dill_slow(s[1] < 0)) {err = errno; goto error4;} + return 0; +error4: + rc = hclose(s[0]); + goto error2; +error3: + fd_close(fds[0]); +error2: + fd_close(fds[1]); +error1: + errno = err; + return -1; +} + +/******************************************************************************/ +/* Helpers */ +/******************************************************************************/ + +static int ipc_resolve(const char *addr, struct sockaddr_un *su) { + dill_assert(su); + if(strlen(addr) >= sizeof(su->sun_path)) {errno = ENAMETOOLONG; return -1;} + su->sun_family = AF_UNIX; + strncpy(su->sun_path, addr, sizeof(su->sun_path)); + return 0; +} + +static int ipc_makeconn(int fd) { + int err; + /* Create the object. */ + struct ipc_conn *self = malloc(sizeof(struct ipc_conn)); + if(dill_slow(!self)) {err = ENOMEM; goto error1;} + self->hvfs.query = ipc_hquery; + self->hvfs.close = ipc_hclose; + self->hvfs.done = ipc_hdone; + self->bvfs.bsendl = ipc_bsendl; + self->bvfs.brecvl = ipc_brecvl; + self->fd = fd; + fd_initrxbuf(&self->rxbuf); + self->indone = 0; + self->outdone = 0; + self->inerr = 0; + self->outerr = 0; + /* Create the handle. */ + int h = hmake(&self->hvfs); + if(dill_slow(h < 0)) {err = errno; goto error2;} + return h; +error2: + free(self); +error1: + errno = err; + return -1; +} + diff --git a/Sources/CLibdill/kqueue.c.inc b/Sources/CLibdill/kqueue.c.inc new file mode 100644 index 0000000..92a0674 --- /dev/null +++ b/Sources/CLibdill/kqueue.c.inc @@ -0,0 +1,307 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cr.h" +#include "list.h" +#include "pollset.h" +#include "utils.h" +#include "ctx.h" + +#define DILL_ENDLIST 0xffffffff + +#define DILL_CHNGSSIZE 128 +#define DILL_EVSSIZE 128 + +#define FDW_IN 1 +#define FDW_OUT 2 + +struct dill_fdinfo { + struct dill_fdclause *in; + struct dill_fdclause *out; + uint16_t currevs; + uint16_t firing; + /* 1-based index, 0 stands for "not part of the list", DILL_ENDLIST + stands for "no more elements in the list. */ + uint32_t next; + /* 1 if the file descriptor is cached. 0 otherwise. */ + unsigned int cached : 1; +}; + +int dill_ctx_pollset_init(struct dill_ctx_pollset *ctx) { + int err; + /* Allocate one info per fd. */ + ctx->nfdinfos = dill_maxfds(); + ctx->fdinfos = calloc(ctx->nfdinfos, sizeof(struct dill_fdinfo)); + if(dill_slow(!ctx->fdinfos)) {err = ENOMEM; goto error1;} + /* Changelist is empty. */ + ctx->changelist = DILL_ENDLIST; + /* Create kernel-side pollset. */ + ctx->kfd = kqueue(); + if(dill_slow(ctx->kfd < 0)) {err = errno; goto error2;} + return 0; +error2: + free(ctx->fdinfos); + ctx->fdinfos = NULL; +error1: + errno = err; + return -1; +} + +void dill_ctx_pollset_term(struct dill_ctx_pollset *ctx) { + /* Kqueue documentation says that a kqueue descriptor won't + survive a fork. However, implementations seem to disagree. + On FreeBSD the following function succeeds. On OSX it returns + EACCESS. Therefore we ignore the return value. */ + close(ctx->kfd); + free(ctx->fdinfos); +} + +static void dill_fdcancelin(struct dill_clause *cl) { + struct dill_fdinfo *fdinfo = + dill_cont(cl, struct dill_fdclause, cl)->fdinfo; + fdinfo->in = NULL; + if(!fdinfo->next) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + fdinfo->next = ctx->changelist; + ctx->changelist = fdinfo - ctx->fdinfos + 1; + } +} + +static void dill_fdcancelout(struct dill_clause *cl) { + struct dill_fdinfo *fdinfo = + dill_cont(cl, struct dill_fdclause, cl)->fdinfo; + fdinfo->out = NULL; + if(!fdinfo->next) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + fdinfo->next = ctx->changelist; + ctx->changelist = fdinfo - ctx->fdinfos + 1; + } +} + +int dill_pollset_in(struct dill_fdclause *fdcl, int id, int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + if(dill_slow(fd < 0 || fd >= ctx->nfdinfos)) {errno = EBADF; return -1;} + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + /* If not yet cached, check whether fd exists and if so add it + to pollset. */ + if(dill_slow(!fdi->cached)) { + struct kevent ev; + EV_SET(&ev, fd, EVFILT_READ, EV_ADD, 0, 0, 0); + int rc = kevent(ctx->kfd, &ev, 1, NULL, 0, NULL); + if(dill_slow(rc < 0 && errno == EBADF)) return -1; + dill_assert(rc >= 0); + fdi->in = NULL; + fdi->out = NULL; + fdi->currevs = FDW_IN; + fdi->firing = 0; + fdi->next = 0; + fdi->cached = 1; + } + if(dill_slow(fdi->in)) {errno = EBUSY; return -1;} + /* If fd is not yet in the pollset, add it there. */ + else if(!fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + fdcl->fdinfo = fdi; + fdi->in = fdcl; + dill_waitfor(&fdcl->cl, id, dill_fdcancelin); + return 0; +} + +int dill_pollset_out(struct dill_fdclause *fdcl, int id, int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + if(dill_slow(fd < 0 || fd >= ctx->nfdinfos)) {errno = EBADF; return -1;} + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + /* If not yet cached, check whether the fd exists and if it does, + add it to the pollset. */ + if(dill_slow(!fdi->cached)) { + struct kevent ev; + EV_SET(&ev, fd, EVFILT_WRITE, EV_ADD, 0, 0, 0); + int rc = kevent(ctx->kfd, &ev, 1, NULL, 0, NULL); + if(dill_slow(rc < 0 && errno == EBADF)) return -1; + dill_assert(rc >= 0); + fdi->in = NULL; + fdi->out = NULL; + fdi->currevs = FDW_OUT; + fdi->firing = 0; + fdi->next = 0; + fdi->cached = 1; + } + if(dill_slow(fdi->out)) {errno = EBUSY; return -1;} + /* If the fd is not yet in the pollset, add it there. */ + else if(!fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + fdcl->fdinfo = fdi; + fdi->out = fdcl; + dill_waitfor(&fdcl->cl, id, dill_fdcancelout); + return 0; +} + +int dill_pollset_clean(int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(!fdi->cached) return 0; + /* We cannot clean an fd that someone is waiting for. */ + if(dill_slow(fdi->in || fdi->out)) {errno = EBUSY; return -1;} + /* Remove the file descriptor from the pollset if it is still there. */ + int nevs = 0; + struct kevent evs[2]; + if(fdi->currevs & FDW_IN) { + EV_SET(&evs[nevs], fd, EVFILT_READ, EV_DELETE, 0, 0, 0); + ++nevs; + } + if(fdi->currevs & FDW_OUT) { + EV_SET(&evs[nevs], fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0); + ++nevs; + } + if(nevs) { + int rc = kevent(ctx->kfd, evs, nevs, NULL, 0, NULL); + dill_assert(rc != -1); + } + fdi->currevs = 0; + /* If needed, remove the fd from the changelist. */ + if(fdi->next) { + uint32_t *pidx = &ctx->changelist; + while(1) { + dill_assert(*pidx != 0 && *pidx != DILL_ENDLIST); + if(*pidx - 1 == fd) break; + pidx = &ctx->fdinfos[*pidx - 1].next; + } + *pidx = fdi->next; + fdi->next = 0; + } + /* Mark the fd as not used. */ + fdi->cached = 0; + return 0; +} + +int dill_pollset_poll(int timeout) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + /* Apply any changes to the pollset. */ + struct kevent chngs[DILL_CHNGSSIZE]; + int nchngs = 0; + while(ctx->changelist != DILL_ENDLIST) { + /* Flush the changes to the pollset even if there is one empty entry + left in the changeset. That way, we make sure that both in & out + associated with the next file descriptor can be filled in if we + choose not to flush the changes yet. */ + if(nchngs >= DILL_CHNGSSIZE - 1) { + int rc = kevent(ctx->kfd, chngs, nchngs, NULL, 0, NULL); + dill_assert(rc != -1); + nchngs = 0; + } + int fd = ctx->changelist - 1; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(fdi->in) { + if(!(fdi->currevs & FDW_IN)) { + EV_SET(&chngs[nchngs], fd, EVFILT_READ, EV_ADD, 0, 0, 0); + fdi->currevs |= FDW_IN; + ++nchngs; + } + } + else { + if(fdi->currevs & FDW_IN) { + EV_SET(&chngs[nchngs], fd, EVFILT_READ, EV_DELETE, 0, 0, 0); + fdi->currevs &= ~FDW_IN; + ++nchngs; + } + } + if(fdi->out) { + if(!(fdi->currevs & FDW_OUT)) { + EV_SET(&chngs[nchngs], fd, EVFILT_WRITE, EV_ADD, 0, 0, 0); + fdi->currevs |= FDW_OUT; + ++nchngs; + } + } + else { + if(fdi->currevs & FDW_OUT) { + EV_SET(&chngs[nchngs], fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0); + fdi->currevs &= ~FDW_OUT; + ++nchngs; + } + } + fdi->firing = 0; + ctx->changelist = fdi->next; + fdi->next = 0; + } + /* Wait for events. */ + struct kevent evs[DILL_EVSSIZE]; + struct timespec ts; + if(timeout >= 0) { + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (((long)timeout) % 1000) * 1000000; + } + int nevs = kevent(ctx->kfd, chngs, nchngs, evs, DILL_EVSSIZE, + timeout < 0 ? NULL : &ts); + if(nevs < 0 && errno == EINTR) return -1; + dill_assert(nevs >= 0); + /* Join events on file descriptor basis. + Put all the firing fds into the changelist. */ + int i; + for(i = 0; i != nevs; ++i) { + dill_assert(evs[i].flags != EV_ERROR); + int fd = (int)evs[i].ident; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + /* Add firing event to the result list. */ + if(evs[i].flags == EV_EOF) + fdi->firing |= (FDW_IN | FDW_OUT); + else { + if(evs[i].filter == EVFILT_READ) + fdi->firing |= FDW_IN; + if(evs[i].filter == EVFILT_WRITE) + fdi->firing |= FDW_OUT; + } + if(!fdi->next) { + fdi->next = ctx->changelist; + ctx->changelist = fd + 1; + } + } + /* Resume blocked coroutines. */ + uint32_t chl = ctx->changelist; + while(chl != DILL_ENDLIST) { + int fd = chl - 1; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(fdi->in && (fdi->firing & FDW_IN)) + dill_trigger(&fdi->in->cl, 0); + if(fdi->out && (fdi->firing & FDW_OUT)) + dill_trigger(&fdi->out->cl, 0); + fdi->firing = 0; + chl = fdi->next; + } + /* Return 0 on timeout or 1 if at least one coroutine was resumed. */ + return nevs > 0 ? 1 : 0; +} + diff --git a/Sources/CLibdill/kqueue.h.inc b/Sources/CLibdill/kqueue.h.inc new file mode 100644 index 0000000..869b0b2 --- /dev/null +++ b/Sources/CLibdill/kqueue.h.inc @@ -0,0 +1,43 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_KQUEUE_INCLUDED +#define DILL_KQUEUE_INCLUDED + +#include "cr.h" +#include "list.h" + +struct dill_fdinfo; + +struct dill_fdclause { + struct dill_clause cl; + struct dill_fdinfo *fdinfo; +}; + +struct dill_ctx_pollset { + int kfd; + int nfdinfos; + struct dill_fdinfo *fdinfos; + uint32_t changelist; +}; + +#endif diff --git a/Sources/CLibdill/libdill.c b/Sources/CLibdill/libdill.c new file mode 100755 index 0000000..a7cd3ae --- /dev/null +++ b/Sources/CLibdill/libdill.c @@ -0,0 +1,81 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include + +#include "cr.h" +#include "libdill.h" +#include "pollset.h" +#include "utils.h" + +int msleep(int64_t deadline) { + /* Return ECANCELED if shutting down. */ + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* Actual waiting. */ + struct dill_tmclause tmcl; + dill_timer(&tmcl, 1, deadline); + int id = dill_wait(); + if(dill_slow(id < 0)) return -1; + return 0; +} + +int fdin(int fd, int64_t deadline) { + /* Return ECANCELED if shutting down. */ + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* Start waiting for the fd. */ + struct dill_fdclause fdcl; + rc = dill_pollset_in(&fdcl, 1, fd); + if(dill_slow(rc < 0)) return -1; + /* Optionally, start waiting for a timer. */ + struct dill_tmclause tmcl; + dill_timer(&tmcl, 2, deadline); + /* Block. */ + int id = dill_wait(); + if(dill_slow(id < 0)) return -1; + if(dill_slow(id == 2)) {errno = ETIMEDOUT; return -1;} + return 0; +} + +int fdout(int fd, int64_t deadline) { + /* Return ECANCELED if shutting down. */ + int rc = dill_canblock(); + if(dill_slow(rc < 0)) return -1; + /* Start waiting for the fd. */ + struct dill_fdclause fdcl; + rc = dill_pollset_out(&fdcl, 1, fd); + if(dill_slow(rc < 0)) return -1; + /* Optionally, start waiting for a timer. */ + struct dill_tmclause tmcl; + dill_timer(&tmcl, 2, deadline); + /* Block. */ + int id = dill_wait(); + if(dill_slow(id < 0)) return -1; + if(dill_slow(id == 2)) {errno = ETIMEDOUT; return -1;} + return 0; +} + +int fdclean(int fd) { + return dill_pollset_clean(fd); +} + diff --git a/Sources/CLibdill/libdill.h b/Sources/CLibdill/libdill.h new file mode 100755 index 0000000..73b382f --- /dev/null +++ b/Sources/CLibdill/libdill.h @@ -0,0 +1,483 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef LIBDILL_H_INCLUDED +#define LIBDILL_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#if defined __linux__ +#include +#endif + +#if defined __APPLE__ +#define HAVE_POSIX_MEMALIGN 1 +#define HAVE_MPROTECT 1 +#define HAVE_CLOCK_GETTIME 1 +#define HAVE_KQUEUE 1 +#define HAVE_KQUEUE 1 +#define HAVE_STRUCT_SOCKADDR_SA_LEN 1 +#define STDC_HEADERS 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_DLFCN_H 1 +#define DILL_THREADS 1 +#define DILL_PTHREAD 1 +#define DILL_SHARED 1 +#define DILL_KQUEUE 1 +#elif defined __linux__ +#define HAVE_POSIX_MEMALIGN 1 +#define HAVE_MPROTECT 1 +#define HAVE_LIBRT 1 +#define HAVE_CLOCK_GETTIME 1 +#define HAVE_EPOLL_CREATE 1 +#define HAVE_EPOLL 1 +#define STDC_HEADERS 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_DLFCN_H 1 +#define DILL_THREADS 1 +#define DILL_PTHREAD 1 +#define DILL_SHARED 1 +#define DILL_EPOLL 1 +#endif + +/******************************************************************************/ +/* ABI versioning support */ +/******************************************************************************/ + +/* Don't change this unless you know exactly what you're doing and have */ +/* read and understood the following documents: */ +/* www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html */ +/* www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html */ + +/* The current interface version. */ +#define DILL_VERSION_CURRENT 14 + +/* The latest revision of the current interface. */ +#define DILL_VERSION_REVISION 0 + +/* How many past interface versions are still supported. */ +#define DILL_VERSION_AGE 1 + +/******************************************************************************/ +/* Symbol visibility */ +/******************************************************************************/ + +#if !defined __GNUC__ && !defined __clang__ +#error "Unsupported compiler!" +#endif + +#if DILL_NO_EXPORTS +#define DILL_EXPORT +#else +#define DILL_EXPORT __attribute__ ((visibility("default"))) +#endif + +/* Old versions of GCC don't support visibility attribute. */ +#if defined __GNUC__ && __GNUC__ < 4 +#undef DILL_EXPORT +#define DILL_EXPORT +#endif + +/******************************************************************************/ +/* Helpers */ +/******************************************************************************/ + +DILL_EXPORT int64_t now(void); + +/******************************************************************************/ +/* Handles */ +/******************************************************************************/ + +DILL_EXPORT int hdup(int h); +DILL_EXPORT int hclose(int h); +DILL_EXPORT int hdone(int h, int64_t deadline); + +/******************************************************************************/ +/* Coroutines */ +/******************************************************************************/ + +#define coroutine __attribute__((noinline)) + +DILL_EXPORT extern volatile void *dill_unoptimisable; + +DILL_EXPORT __attribute__((noinline)) int dill_prologue(sigjmp_buf **ctx, + void **ptr, size_t len, const char *file, int line); +DILL_EXPORT __attribute__((noinline)) void dill_epilogue(void); + +/* The following macros use alloca(sizeof(size_t)) because clang + doesn't support alloca with size zero. */ + +/* This assembly setjmp/longjmp mechanism is in the same order as glibc and + musl, but glibc implements pointer mangling, which is hard to support. + This should be binary-compatible with musl, though. */ + +/* Stack-switching on X86-64. */ +#if defined(__x86_64__) && !defined DILL_ARCH_FALLBACK +#define dill_setjmp(ctx) ({\ + int ret;\ + asm("lea LJMPRET%=(%%rip), %%rcx\n\t"\ + "xor %%rax, %%rax\n\t"\ + "mov %%rbx, (%%rdx)\n\t"\ + "mov %%rbp, 8(%%rdx)\n\t"\ + "mov %%r12, 16(%%rdx)\n\t"\ + "mov %%r13, 24(%%rdx)\n\t"\ + "mov %%r14, 32(%%rdx)\n\t"\ + "mov %%r15, 40(%%rdx)\n\t"\ + "mov %%rsp, 48(%%rdx)\n\t"\ + "mov %%rcx, 56(%%rdx)\n\t"\ + "LJMPRET%=:\n\t"\ + : "=a" (ret)\ + : "d" (ctx)\ + : "memory", "rcx", "rsi", "rdi", "r8", "r9", "r10", "r11", "cc");\ + ret;\ +}) +#define dill_longjmp(ctx) \ + asm("movq 56(%%rdx), %%rcx\n\t"\ + "movq 48(%%rdx), %%rsp\n\t"\ + "movq 40(%%rdx), %%r15\n\t"\ + "movq 32(%%rdx), %%r14\n\t"\ + "movq 24(%%rdx), %%r13\n\t"\ + "movq 16(%%rdx), %%r12\n\t"\ + "movq 8(%%rdx), %%rbp\n\t"\ + "movq (%%rdx), %%rbx\n\t"\ + ".cfi_def_cfa %%rdx, 0 \n\t"\ + ".cfi_offset %%rbx, 0 \n\t"\ + ".cfi_offset %%rbp, 8 \n\t"\ + ".cfi_offset %%r12, 16 \n\t"\ + ".cfi_offset %%r13, 24 \n\t"\ + ".cfi_offset %%r14, 32 \n\t"\ + ".cfi_offset %%r15, 40 \n\t"\ + ".cfi_offset %%rsp, 48 \n\t"\ + ".cfi_offset %%rip, 56 \n\t"\ + "jmp *%%rcx\n\t"\ + : : "d" (ctx), "a" (1)) +#define DILL_SETSP(x) \ + asm(""::"r"(alloca(sizeof(size_t))));\ + asm volatile("leaq (%%rax), %%rsp"::"rax"(x)); + +/* Stack switching on X86. */ +#elif defined(__i386__) && !defined DILL_ARCH_FALLBACK +#define dill_setjmp(ctx) ({\ + int ret;\ + asm("movl $LJMPRET%=, %%ecx\n\t"\ + "movl %%ebx, (%%edx)\n\t"\ + "movl %%esi, 4(%%edx)\n\t"\ + "movl %%edi, 8(%%edx)\n\t"\ + "movl %%ebp, 12(%%edx)\n\t"\ + "movl %%esp, 16(%%edx)\n\t"\ + "movl %%ecx, 20(%%edx)\n\t"\ + "xorl %%eax, %%eax\n\t"\ + "LJMPRET%=:\n\t"\ + : "=a" (ret) : "d" (ctx) : "memory");\ + ret;\ +}) +#define dill_longjmp(ctx) \ + asm("movl (%%edx), %%ebx\n\t"\ + "movl 4(%%edx), %%esi\n\t"\ + "movl 8(%%edx), %%edi\n\t"\ + "movl 12(%%edx), %%ebp\n\t"\ + "movl 16(%%edx), %%esp\n\t"\ + "movl 20(%%edx), %%ecx\n\t"\ + ".cfi_def_cfa %%edx, 0 \n\t"\ + ".cfi_offset %%ebx, 0 \n\t"\ + ".cfi_offset %%esi, 4 \n\t"\ + ".cfi_offset %%edi, 8 \n\t"\ + ".cfi_offset %%ebp, 12 \n\t"\ + ".cfi_offset %%esp, 16 \n\t"\ + ".cfi_offset %%eip, 20 \n\t"\ + "jmp *%%ecx\n\t"\ + : : "d" (ctx), "a" (1)) +#define DILL_SETSP(x) \ + asm(""::"r"(alloca(sizeof(size_t))));\ + asm volatile("leal (%%eax), %%esp"::"eax"(x)); + +/* Stack-switching on other microarchitectures. */ +#else +#define dill_setjmp(ctx) sigsetjmp(ctx, 0) +#define dill_longjmp(ctx) siglongjmp(ctx, 1) +/* For newer GCCs, -fstack-protector breaks on this; use -fno-stack-protector. + Alternatively, implement a custom DILL_SETSP for your microarchitecture. */ +#define DILL_SETSP(x) \ + dill_unoptimisable = alloca((char*)alloca(sizeof(size_t)) - (char*)(x)); +#endif + +/* Statement expressions are a gcc-ism but they are also supported by clang. + Given that there's no other way to do this, screw other compilers for now. + See https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Statement-Exprs.html */ + +/* A bug in gcc have been observed where name clash between variable in the + outer scope and a local variable in this macro causes the variable to + get weird values. To avoid that, we use fancy names (dill_*__). */ + +#define go_mem(fn, ptr, len) \ + ({\ + sigjmp_buf *dill_ctx__;\ + void *dill_stk__ = (ptr);\ + int dill_handle__ = dill_prologue(&dill_ctx__, &dill_stk__, (len),\ + __FILE__, __LINE__);\ + if(dill_handle__ >= 0) {\ + if(!dill_setjmp(*dill_ctx__)) {\ + DILL_SETSP(dill_stk__);\ + fn;\ + dill_epilogue();\ + }\ + }\ + dill_handle__;\ + }) + +#define go(fn) go_mem(fn, NULL, 0) + +DILL_EXPORT int co(void **ptr, size_t len, + void *fn, const char *file, int line, + void (*routine)(int, void *)); +DILL_EXPORT void* clsget(void); +DILL_EXPORT void clsset(void *cls); +DILL_EXPORT int yield(void); +DILL_EXPORT int msleep(int64_t deadline); +DILL_EXPORT int fdclean(int fd); +DILL_EXPORT int fdin(int fd, int64_t deadline); +DILL_EXPORT int fdout(int fd, int64_t deadline); + +/******************************************************************************/ +/* Channels */ +/******************************************************************************/ + +#define CHSEND 1 +#define CHRECV 2 + +struct chclause { + int op; + int ch; + void *val; + size_t len; +}; + +struct chmem { +#if defined(__i386__) + char reserved[40]; +#else + char reserved[80]; +#endif +}; + +DILL_EXPORT int chmake(size_t itemsz); +DILL_EXPORT int chmake_mem(size_t itemsz, struct chmem *mem); +DILL_EXPORT int chsend(int ch, const void *val, size_t len, int64_t deadline); +DILL_EXPORT int chrecv(int ch, void *val, size_t len, int64_t deadline); +DILL_EXPORT int choose(struct chclause *clauses, int nclauses, + int64_t deadline); + +#define chdone(ch) hdone((ch), -1) + +#if !defined DILL_DISABLE_SOCKETS + +/******************************************************************************/ +/* Gather/scatter list. */ +/******************************************************************************/ + +struct iolist { + void *iol_base; + size_t iol_len; + struct iolist *iol_next; + int iol_rsvd; +}; + +/******************************************************************************/ +/* Bytestream sockets. */ +/******************************************************************************/ + +DILL_EXPORT int bsend( + int s, + const void *buf, + size_t len, + int64_t deadline); +DILL_EXPORT ssize_t brecv( + int s, + void *buf, + size_t len, + int64_t deadline); +DILL_EXPORT int bsendl( + int s, + struct iolist *first, + struct iolist *last, + int64_t deadline); +DILL_EXPORT ssize_t brecvl( + int s, + struct iolist *first, + struct iolist *last, + int64_t deadline); + +/******************************************************************************/ +/* Message sockets. */ +/******************************************************************************/ + +DILL_EXPORT int msend( + int s, + const void *buf, + size_t len, + int64_t deadline); +DILL_EXPORT ssize_t mrecv( + int s, + void *buf, + size_t len, + int64_t deadline); +DILL_EXPORT int msendl( + int s, + struct iolist *first, + struct iolist *last, + int64_t deadline); +DILL_EXPORT ssize_t mrecvl( + int s, + struct iolist *first, + struct iolist *last, + int64_t deadline); + +/******************************************************************************/ +/* IP address resolution. */ +/******************************************************************************/ + +struct sockaddr; + +#define IPADDR_IPV4 1 +#define IPADDR_IPV6 2 +#define IPADDR_PREF_IPV4 3 +#define IPADDR_PREF_IPV6 4 +#define IPADDR_MAXSTRLEN 46 + +struct ipaddr { + char data[32]; +}; + +DILL_EXPORT int ipaddr_local( + struct ipaddr *addr, + const char *name, + int port, + int mode); +DILL_EXPORT int ipaddr_remote( + struct ipaddr *addr, + const char *name, + int port, + int mode, + int64_t deadline); +DILL_EXPORT const char *ipaddr_str( + const struct ipaddr *addr, + char *ipstr); +DILL_EXPORT int ipaddr_family( + const struct ipaddr *addr); +DILL_EXPORT const struct sockaddr *ipaddr_sockaddr( + const struct ipaddr *addr); +DILL_EXPORT int ipaddr_len( + const struct ipaddr *addr); +DILL_EXPORT int ipaddr_port( + const struct ipaddr *addr); +DILL_EXPORT void ipaddr_setport( + struct ipaddr *addr, + int port); + +/******************************************************************************/ +/* TCP protocol. */ +/******************************************************************************/ + +DILL_EXPORT int tcp_listen( + struct ipaddr *addr, + int backlog); +DILL_EXPORT int tcp_accept( + int s, + struct ipaddr *addr, + int64_t deadline); +DILL_EXPORT int tcp_connect( + const struct ipaddr *addr, + int64_t deadline); +DILL_EXPORT int tcp_close( + int s, + int64_t deadline); + +/******************************************************************************/ +/* IPC protocol. */ +/******************************************************************************/ + +DILL_EXPORT int ipc_listen( + const char *addr, + int backlog); +DILL_EXPORT int ipc_accept( + int s, + int64_t deadline); +DILL_EXPORT int ipc_connect( + const char *addr, + int64_t deadline); +DILL_EXPORT int ipc_close( + int s, + int64_t deadline); +DILL_EXPORT int ipc_pair( + int s[2]); + +/******************************************************************************/ +/* PFX protocol. */ +/* Messages are prefixed by 8-byte size in network byte order. */ +/* The protocol is terminated by 0xffffffffffffffff. */ +/******************************************************************************/ + +DILL_EXPORT int pfx_attach( + int s); +DILL_EXPORT int pfx_detach( + int s, + int64_t deadline); + +/******************************************************************************/ +/* CRLF protocol. */ +/* Messages are delimited by CRLF (0x0d 0x0a) sequences. */ +/* The protocol is terminated by an empty line. */ +/******************************************************************************/ + +DILL_EXPORT int crlf_attach( + int s); +DILL_EXPORT int crlf_detach( + int s, + int64_t deadline); + +#endif + +#endif + diff --git a/Sources/CLibdill/libdillimpl.h b/Sources/CLibdill/libdillimpl.h new file mode 100755 index 0000000..ab547c5 --- /dev/null +++ b/Sources/CLibdill/libdillimpl.h @@ -0,0 +1,76 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef LIBDILLIMPL_H_INCLUDED +#define LIBDILLIMPL_H_INCLUDED + +#include "libdill.h" + +/******************************************************************************/ +/* Handles */ +/******************************************************************************/ + +struct hvfs { + void *(*query)(struct hvfs *vfs, const void *type); + void (*close)(struct hvfs *vfs); + int (*done)(struct hvfs *vfs, int64_t deadline); + /* Reserved. Do not use directly! */ + unsigned int refcount; +}; + +DILL_EXPORT int hmake(struct hvfs *vfs); +DILL_EXPORT void *hquery(int h, const void *type); + +#if !defined DILL_DISABLE_SOCKETS + +/******************************************************************************/ +/* Bytestream sockets. */ +/******************************************************************************/ + +DILL_EXPORT extern const void *bsock_type; + +struct bsock_vfs { + int (*bsendl)(struct bsock_vfs *vfs, + struct iolist *first, struct iolist *last, int64_t deadline); + ssize_t (*brecvl)(struct bsock_vfs *vfs, + struct iolist *first, struct iolist *last, int64_t deadline); +}; + +/******************************************************************************/ +/* Message sockets. */ +/******************************************************************************/ + +DILL_EXPORT extern const void *msock_type; + +struct msock_vfs { + int (*msendl)(struct msock_vfs *vfs, + struct iolist *first, struct iolist *last, int64_t deadline); + ssize_t (*mrecvl)(struct msock_vfs *vfs, + struct iolist *first, struct iolist *last, int64_t deadline); +}; + +#endif + +#endif + diff --git a/Sources/CLibdill/list.h b/Sources/CLibdill/list.h new file mode 100755 index 0000000..5166422 --- /dev/null +++ b/Sources/CLibdill/list.h @@ -0,0 +1,69 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_LIST_INCLUDED +#define DILL_LIST_INCLUDED + +#include "utils.h" + +/* Doubly-linked list. */ +struct dill_list { + struct dill_list *next; + struct dill_list *prev; +}; + +/* Initialize the list. */ +static inline void dill_list_init(struct dill_list *self) { + self->next = self; + self->prev = self; +} + +/* True if the list has no items. */ +static inline int dill_list_empty(struct dill_list *self) { + return self->next == self; +} + +/* Returns an iterator to one past the item pointed to by 'it'. If 'it' is the + list itself it returns the first item of the list. At the end of + the list, it returns the list itself. */ +#define dill_list_next(it) ((it)->next) + +/* Adds the item to the list before the item pointed to by 'before'. If 'before' + is the list itself the item is inserted to the end of the list. */ +static inline void dill_list_insert(struct dill_list *item, + struct dill_list *before) { + item->next = before; + item->prev = before->prev; + before->prev->next = item; + before->prev = item; +} + +/* Removes the item from the list. */ +static void dill_list_erase(struct dill_list *item) { + item->prev->next = item->next; + item->next->prev = item->prev; +} + +#endif + diff --git a/Sources/CLibdill/msock.c b/Sources/CLibdill/msock.c new file mode 100755 index 0000000..ce8d36c --- /dev/null +++ b/Sources/CLibdill/msock.c @@ -0,0 +1,65 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include + +#include "libdillimpl.h" +#include "utils.h" + +dill_unique_id(msock_type); + +int msend(int s, const void *buf, size_t len, int64_t deadline) { + struct msock_vfs *m = hquery(s, msock_type); + if(dill_slow(!m)) return -1; + struct iolist iol = {(void*)buf, len, NULL, 0}; + return m->msendl(m, &iol, &iol, deadline); +} + +ssize_t mrecv(int s, void *buf, size_t len, int64_t deadline) { + struct msock_vfs *m = hquery(s, msock_type); + if(dill_slow(!m)) return -1; + struct iolist iol = {buf, len, NULL, 0}; + return m->mrecvl(m, &iol, &iol, deadline); +} + +int msendl(int s, struct iolist *first, struct iolist *last, int64_t deadline) { + struct msock_vfs *m = hquery(s, msock_type); + if(dill_slow(!m)) return -1; + if(dill_slow(!first || !last || last->iol_next)) { + errno = EINVAL; return -1;} + return m->msendl(m, first, last, deadline); +} + +ssize_t mrecvl(int s, struct iolist *first, struct iolist *last, + int64_t deadline) { + struct msock_vfs *m = hquery(s, msock_type); + if(dill_slow(!m)) return -1; + if(dill_slow((last && last->iol_next) || + (!first && last) || + (first && !last))) { + errno = EINVAL; return -1;} + return m->mrecvl(m, first, last, deadline); +} + diff --git a/Sources/CLibdill/now.c b/Sources/CLibdill/now.c new file mode 100755 index 0000000..878dab4 --- /dev/null +++ b/Sources/CLibdill/now.c @@ -0,0 +1,135 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include + +#if !defined(__APPLE__) +#if defined(__x86_64__) || defined(__i386__) +#if defined __clang__ +static inline uint64_t __rdtsc() { +#if defined __i386__ + uint64_t x; + asm volatile ("rdtsc" : "=A" (x)); + return x; +#else + uint64_t a, d; + asm volatile ("rdtsc" : "=a" (a), "=d" (d)); + return (d << 32) | a; +#endif +} +#endif +#endif +#endif + +#include "ctx.h" + +int64_t mnow(void) { + +/* Implementation using Mach timers. */ +#if defined __APPLE__ + static mach_timebase_info_data_t dill_mtid = {0}; + if (dill_slow(!dill_mtid.denom)) + mach_timebase_info(&dill_mtid); + uint64_t ticks = mach_absolute_time(); + return (int64_t)(ticks * dill_mtid.numer / dill_mtid.denom / 1000000); +#else + +/* Implementation using clock_gettime(). */ +#if defined CLOCK_MONOTONIC_COARSE + clock_t id = CLOCK_MONOTONIC_COARSE; +#elif defined CLOCK_MONOTONIC_FAST + clock_t id = CLOCK_MONOTONIC_FAST; +#elif defined CLOCK_MONOTONIC + clock_t id = CLOCK_MONOTONIC; +#else +#define DILL_NOW_FALLBACK +#endif +#if !defined DILL_NOW_FALLBACK + struct timespec ts; + int rc = clock_gettime(id, &ts); + dill_assert (rc == 0); + return ((int64_t)ts.tv_sec) * 1000 + (((int64_t)ts.tv_nsec) / 1000000); + +/* Implementation using gettimeofday(). This is slow and error-prone + (the time can jump backwards!), but it's just a last resort option. */ +#else + struct timeval tv; + int rc = gettimeofday(&tv, NULL); + dill_assert (rc == 0); + return ((int64_t)tv.tv_sec) * 1000 + (((int64_t)tv.tv_usec) / 1000); +#endif + +#endif +} + +/* Like now(), this function can be called only after context is initialized + but unlike now() it doesn't do time caching. */ +int64_t now_(void) { +#if defined __APPLE__ + struct dill_ctx_now *ctx = &dill_getctx->now; + uint64_t ticks = mach_absolute_time(); + return (int64_t)(ticks * ctx->mtid.numer / ctx->mtid.denom / 1000000); +#else + return mnow(); +#endif +} + +int64_t now(void) { +#if defined(__x86_64__) || defined(__i386__) + /* On x86 platforms, rdtsc instruction can be used to quickly check time + in form of CPU cycles. If less than 1M cycles have elapsed since the + last now_() call we assume it's still the same millisecond and return + cached time. This optimization can give a huge speedup with old systems. + 1M number is chosen is such a way that it results in getting time every + millisecond on 1GHz processors. On faster processors we'll query time + somewhat more often but the number of queries should still be + statistically insignificant. On slower processors we'll start losing + precision, e.g. on 500MHz processor we can diverge by 1ms. */ + struct dill_ctx_now *ctx = &dill_getctx->now; + uint64_t tsc = __rdtsc(); + int64_t diff = tsc - ctx->last_tsc; + if(diff < 0) diff = -diff; + if(dill_fast(diff < 1000000ULL)) return ctx->last_time; + ctx->last_tsc = tsc; + ctx->last_time = now_(); + return ctx->last_time; +#else + return now_(); +#endif +} + +int dill_ctx_now_init(struct dill_ctx_now *ctx) { +#if defined __APPLE__ + mach_timebase_info(&ctx->mtid); +#endif +#if defined(__x86_64__) || defined(__i386__) + ctx->last_time = mnow(); + ctx->last_tsc = __rdtsc(); +#endif + return 0; +} + +void dill_ctx_now_term(struct dill_ctx_now *ctx) { +} + diff --git a/Sources/CLibdill/now.h b/Sources/CLibdill/now.h new file mode 100755 index 0000000..7ccec4b --- /dev/null +++ b/Sources/CLibdill/now.h @@ -0,0 +1,52 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_NOW_INCLUDED +#define DILL_NOW_INCLUDED + +#include + +#if defined __APPLE__ +#include +#endif + +struct dill_ctx_now { +#if defined __APPLE__ + mach_timebase_info_data_t mtid; +#endif +#if defined(__x86_64__) || defined(__i386__) + int64_t last_time; + uint64_t last_tsc; +#endif +}; + +int dill_ctx_now_init(struct dill_ctx_now *ctx); +void dill_ctx_now_term(struct dill_ctx_now *ctx); + +/* Same as now() except that it doesn't use the context. + I.e. it can be called before calling dill_ctx_now_init(). */ +int64_t mnow(void); + +#endif + diff --git a/Sources/CLibdill/poll.c.inc b/Sources/CLibdill/poll.c.inc new file mode 100755 index 0000000..a575799 --- /dev/null +++ b/Sources/CLibdill/poll.c.inc @@ -0,0 +1,227 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include + +#include "cr.h" +#include "list.h" +#include "pollset.h" +#include "utils.h" +#include "ctx.h" + +/* + + ctx->pollset_size + | + ctx->pollset V + +-------+-------+-------+-----+-------+--------------------------------+ + | pfd 0 | pfd 1 | pfd 2 | ... | pfd N | empty | + +-------+-------+-------+-----+-------+--------------------------------+ + ^ ^ ^ + | | | + idx +------idx------+ | + | | | + +------+------+------+----------------------------------------+--------+ + | fd=0 | fd=1 | fd=2 | ... | fd=max | + +------+------+------+----------------------------------------+--------+ + ctx->fdinfos ^ + | + ctx->nfdinfos + +*/ + +/* Additional info about file descriptor. */ +struct dill_fdinfo { + /* Index of the file descriptor in the pollset. + -1 means the fd is not in the pollset. */ + int idx; + /* Clause waiting for in. NULL if none. */ + struct dill_fdclause *in; + /* Clause waiting for out. NULL if none. */ + struct dill_fdclause *out; + /* 1 is the file descriptor was used before, 0 otherwise. */ + unsigned int cached : 1; +}; + +int dill_ctx_pollset_init(struct dill_ctx_pollset *ctx) { + int err; + ctx->nfdinfos = dill_maxfds(); + /* Allocate largest possible pollset. */ + ctx->pollset_size = 0; + ctx->pollset = malloc(sizeof(struct pollfd) * ctx->nfdinfos); + if(dill_slow(!ctx->pollset)) {err = ENOMEM; goto error1;} + ctx->fdinfos = malloc(sizeof(struct dill_fdinfo) * ctx->nfdinfos); + if(dill_slow(!ctx->fdinfos)) {err = ENOMEM; goto error2;} + /* Intialise fd infos. There's no fd in the pollset, + so set all indices to -1. */ + int i; + for(i = 0; i != ctx->nfdinfos; ++i) { + ctx->fdinfos[i].idx = -1; + ctx->fdinfos[i].in = NULL; + ctx->fdinfos[i].out = NULL; + ctx->fdinfos[i].cached = 0; + } + return 0; +error2: + free(ctx->pollset); + ctx->pollset = NULL; +error1: + errno = err; + return -1; +} + +void dill_ctx_pollset_term(struct dill_ctx_pollset *ctx) { + free(ctx->pollset); + free(ctx->fdinfos); +} + +static void dill_fdcancelin(struct dill_clause *cl) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + struct dill_fdinfo *fdi = dill_cont(cl, struct dill_fdclause, cl)->fdinfo; + fdi->in = NULL; + ctx->pollset[fdi->idx].events &= ~POLLIN; + /* fd is left in the pollset. It will be purged once the event loop + iterates once more. */ +} + +static void dill_fdcancelout(struct dill_clause *cl) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + struct dill_fdinfo *fdi = dill_cont(cl, struct dill_fdclause, cl)->fdinfo; + fdi->out = NULL; + ctx->pollset[fdi->idx].events &= ~POLLOUT; + /* fd is left in the pollset. It will be purged once the event loop + iterates once more. */ +} + +int dill_pollset_in(struct dill_fdclause *fdcl, int id, int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + if(dill_slow(fd < 0 || fd >= ctx->nfdinfos)) {errno = EBADF; return -1;} + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(dill_slow(!fdi->cached)) { + int flags = fcntl(fd, F_GETFD); + if(flags < 0 && errno == EBADF) return -1; + dill_assert(flags >= 0); + fdi->cached = 1; + } + if(fdi->idx < 0) { + fdi->idx = ctx->pollset_size; + ++ctx->pollset_size; + ctx->pollset[fdi->idx].fd = fd; + } + if(dill_slow(fdi->in)) {errno = EBUSY; return -1;} + ctx->pollset[fdi->idx].events |= POLLIN; + fdcl->fdinfo = fdi; + fdi->in = fdcl; + dill_waitfor(&fdcl->cl, id, dill_fdcancelin); + return 0; +} + +int dill_pollset_out(struct dill_fdclause *fdcl, int id, int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + if(dill_slow(fd < 0 || fd >= ctx->nfdinfos)) {errno = EBADF; return -1;} + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(dill_slow(!fdi->cached)) { + int flags = fcntl(fd, F_GETFD); + if(flags < 0 && errno == EBADF) return -1; + dill_assert(flags >= 0); + fdi->cached = 1; + } + if(fdi->idx < 0) { + fdi->idx = ctx->pollset_size; + ++ctx->pollset_size; + ctx->pollset[fdi->idx].fd = fd; + } + if(dill_slow(fdi->out)) {errno = EBUSY; return -1;} + ctx->pollset[fdi->idx].events |= POLLOUT; + fdcl->fdinfo = fdi; + fdi->out = fdcl; + dill_waitfor(&fdcl->cl, id, dill_fdcancelout); + return 0; +} + +int dill_pollset_clean(int fd) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + struct dill_fdinfo *fdi = &ctx->fdinfos[fd]; + if(!fdi->cached) return 0; + if(dill_slow(fdi->in || fdi->out)) {errno = EBUSY; return -1;} + /* If the fd happens to still be in the pollset remove it. */ + if(fdi->idx >= 0) { + --ctx->pollset_size; + if(fdi->idx != ctx->pollset_size) { + struct pollfd *pfd = &ctx->pollset[fdi->idx]; + struct pollfd *lastpfd = &ctx->pollset[ctx->pollset_size]; + *pfd = *lastpfd; + ctx->fdinfos[pfd->fd].idx = fdi->idx; + } + fdi->idx = -1; + } + fdi->cached = 0; + return 0; +} + +int dill_pollset_poll(int timeout) { + struct dill_ctx_pollset *ctx = &dill_getctx->pollset; + /* Wait for events. */ + int numevs = poll(ctx->pollset, ctx->pollset_size, timeout); + if(numevs < 0 && errno == EINTR) return -1; + dill_assert(numevs >= 0); + /* Fire file descriptor events as needed. */ + int i; + for(i = 0; i != ctx->pollset_size; ++i) { + struct pollfd *pfd = &ctx->pollset[i]; + struct dill_fdinfo *fdi = &ctx->fdinfos[pfd->fd]; + /* Resume the blocked coroutines. */ + if(fdi->in && + pfd->revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) { + pfd->events &= ~POLLIN; + dill_trigger(&fdi->in->cl, 0); + } + if(fdi->out && + pfd->revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)) { + pfd->events &= ~POLLOUT; + dill_trigger(&fdi->out->cl, 0); + } + /* If nobody is polling for the fd remove it from the pollset. */ + if(!pfd->events) { + fdi->idx = -1; + dill_assert(!fdi->in && !fdi->out); + --ctx->pollset_size; + /* Pollset has to be compact. Thus, unless we are removing the + last item from the pollset we want to move the last item + to the vacant slot left by the removed fd. */ + if(i != ctx->pollset_size) { + struct pollfd *lastpfd = &ctx->pollset[ctx->pollset_size]; + *pfd = *lastpfd; + ctx->fdinfos[pfd->fd].idx = i; + } + --i; + } + } + return numevs > 0; +} + diff --git a/Sources/CLibdill/poll.h.inc b/Sources/CLibdill/poll.h.inc new file mode 100755 index 0000000..5a26b70 --- /dev/null +++ b/Sources/CLibdill/poll.h.inc @@ -0,0 +1,48 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_POLL_INCLUDED +#define DILL_POLL_INCLUDED + +#include + +#include "cr.h" +#include "list.h" + +struct dill_fdinfo; + +struct dill_fdclause { + struct dill_clause cl; + struct dill_fdinfo *fdinfo; +}; + +struct dill_ctx_pollset { + /* Pollset, as used by poll(2). */ + int pollset_size; + struct pollfd *pollset; + /* Info about all file descriptors. + File descriptors are used as indices in this array. */ + int nfdinfos; + struct dill_fdinfo *fdinfos; +}; + +#endif diff --git a/Sources/CLibdill/pollset.c b/Sources/CLibdill/pollset.c new file mode 100644 index 0000000..3b5aa46 --- /dev/null +++ b/Sources/CLibdill/pollset.c @@ -0,0 +1,43 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +/* Include the poll-mechanism-specific stuff. */ + +#include "pollset.h" + +/* User overloads. */ +#if defined DILL_EPOLL +#include "epoll.c.inc" +#elif defined DILL_KQUEUE +#include "kqueue.c.inc" +#elif defined DILL_POLL +#include "poll.c.inc" +/* Defaults. */ +#elif defined HAVE_EPOLL +#include "epoll.c.inc" +#elif defined HAVE_KQUEUE +#include "kqueue.c.inc" +#else +#include "poll.c.inc" +#endif diff --git a/Sources/CLibdill/pollset.h b/Sources/CLibdill/pollset.h new file mode 100644 index 0000000..d5f6abc --- /dev/null +++ b/Sources/CLibdill/pollset.h @@ -0,0 +1,63 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_POLLSET_INCLUDED +#define DILL_POLLSET_INCLUDED + +#include "libdill.h" + +/* User overloads. */ +#if defined DILL_EPOLL +#include "epoll.h.inc" +#elif defined DILL_KQUEUE +#include "kqueue.h.inc" +#elif defined DILL_POLL +#include "poll.h.inc" +/* Defaults. */ +#elif defined HAVE_EPOLL +#include "epoll.h.inc" +#elif defined HAVE_KQUEUE +#include "kqueue.h.inc" +#else +#include "poll.h.inc" +#endif + +int dill_ctx_pollset_init(struct dill_ctx_pollset *ctx); +void dill_ctx_pollset_term(struct dill_ctx_pollset *ctx); + +/* Add waiting for an in event on the fd to the list of current clauses. */ +int dill_pollset_in(struct dill_fdclause *fdcl, int id, int fd); + +/* Add waiting for an out event on the fd to the list of current clauses. */ +int dill_pollset_out(struct dill_fdclause *fdcl, int id, int fd); + +/* Drop any cached info about the file descriptor. */ +int dill_pollset_clean(int fd); + +/* Wait for events. 'timeout' is in milliseconds. Return 0 if the timeout expired or + 1 if at least one clause was triggered. */ +int dill_pollset_poll(int timeout); + +#endif + diff --git a/Sources/CLibdill/qlist.h b/Sources/CLibdill/qlist.h new file mode 100755 index 0000000..4192175 --- /dev/null +++ b/Sources/CLibdill/qlist.h @@ -0,0 +1,67 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_QLIST_INCLUDED +#define DILL_QLIST_INCLUDED + +#include "slist.h" +#include "utils.h" + +/* Singly-linked list that's first-in-first-out, so it's actually a queue. + To iterate over the items, use the underlying slist. */ + +struct dill_qlist { + struct dill_slist slist; + struct dill_slist *last; +}; + +/* Initialize the list. */ +static inline void dill_qlist_init(struct dill_qlist *self) { + dill_slist_init(&self->slist); + self->last = &self->slist; +} + +/* True if the list has no items. */ +static inline int dill_qlist_empty(struct dill_qlist *self) { + return self->slist.next == &self->slist; +} + +/* Push an item to the end of the list. */ +static inline void dill_qlist_push(struct dill_qlist *self, + struct dill_slist *item) { + item->next = &self->slist; + self->last->next = item; + self->last = item; +} + +/* Pop an item from the beginning of the list. */ +static inline struct dill_slist *dill_qlist_pop(struct dill_qlist *self) { + struct dill_slist *item = self->slist.next; + self->slist.next = item->next; + if(item == self->last) self->last = &self->slist; + return item; +} + +#endif + diff --git a/Sources/CLibdill/rbtree.c b/Sources/CLibdill/rbtree.c new file mode 100755 index 0000000..efdfe74 --- /dev/null +++ b/Sources/CLibdill/rbtree.c @@ -0,0 +1,279 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + + +/* This implementation is based on Emin Martinian's implementation of the algorithm: + http://web.mit.edu/~emin/Desktop/ref_to_emin/www.old/source_code/red_black_tree/index.html */ + +#include + +#include "rbtree.h" + +void dill_rbtree_init(struct dill_rbtree *self) { + struct dill_rbtree_item *temp; + temp = &self->nil; + temp->up = temp->left = temp->right=temp; + temp->red = 0; + temp->val = 0; + temp = &self->root; + temp->up = temp->left=temp->right = &self->nil; + temp->val = 0; + temp->red = 0; +} + +static void dill_rbtree_lrotate(struct dill_rbtree* self, + struct dill_rbtree_item* x) { + struct dill_rbtree_item *y; + struct dill_rbtree_item *nil = &self->nil; + y = x->right; + x->right = y->left; + if(y->left != nil) y->left->up=x; + y->up = x->up; + if(x == x->up->left) { + x->up->left = y; + } else { + x->up->right = y; + } + y->left = x; + x->up = y; +} + +static void dill_rbtree_rrotate(struct dill_rbtree *self, + struct dill_rbtree_item *y) { + struct dill_rbtree_item *x; + struct dill_rbtree_item *nil = &self->nil; + x = y->left; + y->left = x->right; + if(nil != x->right) x->right->up = y; + x->up = y->up; + if(y == y->up->left) { + y->up->left = x; + } else { + y->up->right = x; + } + x->right = y; + y->up = x; +} + +static void dill_rbtree_insert_help(struct dill_rbtree *self, + struct dill_rbtree_item *z) { + struct dill_rbtree_item *x; + struct dill_rbtree_item *y; + struct dill_rbtree_item *nil = &self->nil; + z->left = z->right = nil; + y = &self->root; + x = self->root.left; + while(x != nil) { + y=x; + if(x->val > z->val) { + x = x->left; + } else { + x = x->right; + } + } + z->up = y; + if((y == &self->root) || (y->val > z->val)) { + y->left = z; + } else { + y->right = z; + } +} + +void dill_rbtree_insert(struct dill_rbtree *tree, int64_t val, + struct dill_rbtree_item *item) { + struct dill_rbtree_item *y; + struct dill_rbtree_item *x; + x = item; + x->val = val; + dill_rbtree_insert_help(tree, x); + x->red = 1; + while(x->up->red) { + if(x->up == x->up->up->left) { + y = x->up->up->right; + if(y->red) { + x->up->red = 0; + y->red = 0; + x->up->up->red = 1; + x = x->up->up; + } else { + if(x == x->up->right) { + x = x->up; + dill_rbtree_lrotate(tree, x); + } + x->up->red = 0; + x->up->up->red = 1; + dill_rbtree_rrotate(tree, x->up->up); + } + } else { + y = x->up->up->left; + if(y->red) { + x->up->red = 0; + y->red = 0; + x->up->up->red = 1; + x = x->up->up; + } else { + if(x == x->up->left) { + x = x->up; + dill_rbtree_rrotate(tree, x); + } + x->up->red = 0; + x->up->up->red = 1; + dill_rbtree_lrotate(tree, x->up->up); + } + } + } + tree->root.left->red = 0; +} + +int dill_rbtree_empty(struct dill_rbtree *self) { + struct dill_rbtree_item* nil = &self->nil; + return self->root.left == nil; +} + +struct dill_rbtree_item *dill_rbtree_first(struct dill_rbtree *self) { + struct dill_rbtree_item* nil = &self->nil; + struct dill_rbtree_item *x = self->root.left; + if(x == nil) return NULL; + while(x->left != nil) x = x->left; + return x; +} + +static struct dill_rbtree_item *dill_rbtree_next_help(struct dill_rbtree *tree, + struct dill_rbtree_item *x) { + struct dill_rbtree_item *y; + struct dill_rbtree_item *nil = &tree->nil; + struct dill_rbtree_item *root = &tree->root; + if(nil != (y = x->right)) { + while(y->left != nil) { + y = y->left; + } + return y; + } else { + y = x->up; + while(x == y->right) { + x = y; + y = y->up; + } + if(y == root) return nil; + return y; + } +} + +struct dill_rbtree_item *dill_rbtree_next(struct dill_rbtree *tree, + struct dill_rbtree_item *x) { + struct dill_rbtree_item *it = dill_rbtree_next_help(tree, x); + if(it == &tree->nil) return NULL; + return it; +} + +static void dill_rbtree_fixup(struct dill_rbtree *tree, + struct dill_rbtree_item *x) { + struct dill_rbtree_item *root = tree->root.left; + struct dill_rbtree_item *w; + while((!x->red) && (root != x)) { + if (x == x->up->left) { + w = x->up->right; + if(w->red) { + w->red = 0; + x->up->red = 1; + dill_rbtree_lrotate(tree, x->up); + w = x->up->right; + } + if((!w->right->red) && (!w->left->red)) { + w->red = 1; + x = x->up; + } else { + if(!w->right->red) { + w->left->red = 0; + w->red = 1; + dill_rbtree_rrotate(tree, w); + w = x->up->right; + } + w->red = x->up->red; + x->up->red = 0; + w->right->red = 0; + dill_rbtree_lrotate(tree, x->up); + x = root; + } + } else { + w = x->up->left; + if(w->red) { + w->red = 0; + x->up->red = 1; + dill_rbtree_rrotate(tree, x->up); + w = x->up->left; + } + if((!w->right->red) && (!w->left->red)) { + w->red = 1; + x = x->up; + } else { + if(!w->left->red) { + w->right->red = 0; + w->red = 1; + dill_rbtree_lrotate(tree, w); + w = x->up->left; + } + w->red = x->up->red; + x->up->red = 0; + w->left->red = 0; + dill_rbtree_rrotate(tree, x->up); + x = root; + } + } + } + x->red = 0; +} + +void dill_rbtree_erase(struct dill_rbtree *tree, struct dill_rbtree_item *z) { + struct dill_rbtree_item *y; + struct dill_rbtree_item *x; + struct dill_rbtree_item *nil=&tree->nil; + struct dill_rbtree_item *root=&tree->root; + y = ((z->left == nil) || (z->right == nil)) ? z : + dill_rbtree_next_help(tree, z); + x = (y->left == nil) ? y->right : y->left; + if(root == (x->up = y->up)) { + root->left = x; + } else { + if(y == y->up->left) { + y->up->left = x; + } else { + y->up->right = x; + } + } + if(y != z) { + if(!(y->red)) dill_rbtree_fixup(tree, x); + y->left = z->left; + y->right = z->right; + y->up = z->up; + y->red = z->red; + z->left->up = z->right->up=y; + if(z == z->up->left) { + z->up->left=y; + } else { + z->up->right=y; + } + } else { + if(!(y->red)) dill_rbtree_fixup(tree, x); + } +} + diff --git a/Sources/CLibdill/rbtree.h b/Sources/CLibdill/rbtree.h new file mode 100755 index 0000000..42f9b5f --- /dev/null +++ b/Sources/CLibdill/rbtree.h @@ -0,0 +1,66 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_RBTREE_INCLUDED +#define DILL_RBTREE_INCLUDED + +#include +#include + +struct dill_rbtree_item { + int red; + struct dill_rbtree_item *left; + struct dill_rbtree_item *right; + struct dill_rbtree_item *up; + int64_t val; +}; + +struct dill_rbtree { + struct dill_rbtree_item root; + struct dill_rbtree_item nil; +}; + +/* Initialize the tree. */ +void dill_rbtree_init(struct dill_rbtree *self); + +/* Returns 1 if there are no items in the tree. 0 otherwise. */ +int dill_rbtree_empty(struct dill_rbtree *self); + +/* Insert an item into the tree & set its value to 'val' */ +void dill_rbtree_insert(struct dill_rbtree *self, int64_t val, + struct dill_rbtree_item *item); + +/* Remove an item from a tree. */ +void dill_rbtree_erase(struct dill_rbtree *self, struct dill_rbtree_item *item); + +/* Return an item with the lowest value. If there are no items in the tree, NULL + is returned. */ +struct dill_rbtree_item *dill_rbtree_first(struct dill_rbtree *self); + +/* Iterate through the tree. Items are returned starting with those with + the lowest values and ending with those with the highest values. Items with + equal values are returned in no particular order. If 'it' points to the + last item, NULL is returned. */ +struct dill_rbtree_item *dill_rbtree_next(struct dill_rbtree *self, + struct dill_rbtree_item *it); + +#endif diff --git a/Sources/CLibdill/slist.h b/Sources/CLibdill/slist.h new file mode 100755 index 0000000..8408ef8 --- /dev/null +++ b/Sources/CLibdill/slist.h @@ -0,0 +1,67 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_SLIST_INCLUDED +#define DILL_SLIST_INCLUDED + +#include "utils.h" + +/* A Singly-linked list that's last-in-first-out, so it's actually a stack. + To prevent confusion with C's call stack, we'll call it slist. */ + +struct dill_slist { + struct dill_slist *next; +}; + +/* Initialize the list. */ +static inline void dill_slist_init(struct dill_slist *self) { + self->next = self; +} + +/* True if the list has no items. */ +static inline int dill_slist_empty(struct dill_slist *self) { + return self->next == self; +} + +/* Returns the next item in the list. If 'it' is the list itself, it returns the + first element in the list. If there are no more elements in the list, + returns a pointer to the list itself. */ +#define dill_slist_next(it) ((it)->next) + +/* Push the item to the beginning of the list. */ +static inline void dill_slist_push(struct dill_slist *self, + struct dill_slist *item) { + item->next = self->next; + self->next = item; +} + +/* Pop an item from the beginning of the list. */ +static inline struct dill_slist *dill_slist_pop(struct dill_slist *self) { + struct dill_slist *item = self->next; + self->next = item->next; + return item; +} + +#endif + diff --git a/Sources/CLibdill/stack.c b/Sources/CLibdill/stack.c new file mode 100755 index 0000000..75497a0 --- /dev/null +++ b/Sources/CLibdill/stack.c @@ -0,0 +1,147 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "stack.h" +#include "utils.h" +#include "ctx.h" + +/* The stacks are cached. The advantage of this is twofold. First, caching is + faster than malloc(). Second, it results in fewer calls to + mprotect(). */ + +/* Stack size in bytes. */ +static size_t dill_stack_size = 256 * 1024; +/* Maximum number of unused cached stacks. */ +static int dill_max_cached_stacks = 64; + +/* Returns the smallest value that's greater than val and is a multiple of unit. */ +static size_t dill_align(size_t val, size_t unit) { + return val % unit ? val + unit - val % unit : val; +} + +/* Get memory page size. The query is done once. The value is cached. */ +static size_t dill_page_size(void) { + static long pgsz = 0; + if(dill_fast(pgsz)) + return (size_t)pgsz; + pgsz = sysconf(_SC_PAGE_SIZE); + dill_assert(pgsz > 0); + return (size_t)pgsz; +} + +int dill_ctx_stack_init(struct dill_ctx_stack *ctx) { + ctx->count = 0; + dill_slist_init(&ctx->cache); + return 0; +} + +void dill_ctx_stack_term(struct dill_ctx_stack *ctx) { + /* Deallocate leftover coroutines. */ + struct dill_slist *it; + while((it = dill_slist_pop(&ctx->cache)) != &ctx->cache) { +#if (HAVE_POSIX_MEMALIGN && HAVE_MPROTECT) & !defined DILL_NOGUARD + void *ptr = ((uint8_t*)(it + 1)) - dill_stack_size - dill_page_size(); + int rc = mprotect(ptr, dill_page_size(), PROT_READ|PROT_WRITE); + dill_assert(rc == 0); + free(ptr); +#else + void *ptr = ((uint8_t*)(it + 1)) - dill_stack_size; + free(ptr); +#endif + } +} + +void *dill_allocstack(size_t *stack_size) { + struct dill_ctx_stack *ctx = &dill_getctx->stack; + if(stack_size) + *stack_size = dill_stack_size; + /* If there's a cached stack, use it. */ + if(!dill_slist_empty(&ctx->cache)) { + --ctx->count; + return (void*)(dill_slist_pop(&ctx->cache) + 1); + } + /* Allocate a new stack. */ + uint8_t *top; +#if (HAVE_POSIX_MEMALIGN && HAVE_MPROTECT) & !defined DILL_NOGUARD + /* Allocate the stack so that it's memory-page-aligned. + Add one page as a stack overflow guard. */ + size_t sz = dill_align(dill_stack_size, dill_page_size()) + + dill_page_size(); + uint8_t *ptr; + int rc = posix_memalign((void**)&ptr, dill_page_size(), sz); + if(dill_slow(rc != 0)) { + errno = rc; + return NULL; + } + /* The bottom page is used as a stack guard. This way a stack overflow will + cause a segfault instead of randomly overwriting the heap. */ + rc = mprotect(ptr, dill_page_size(), PROT_NONE); + if(dill_slow(rc != 0)) { + int err = errno; + free(ptr); + errno = err; + return NULL; + } + top = ptr + dill_page_size() + dill_stack_size; +#else + /* Simple allocation without a guard page. */ + uint8_t *ptr = malloc(dill_stack_size); + if(dill_slow(!ptr)) { + errno = ENOMEM; + return NULL; + } + top = ptr + dill_stack_size; +#endif + return top; +} + +void dill_freestack(void *stack) { + struct dill_ctx_stack *ctx = &dill_getctx->stack; + struct dill_slist *item = ((struct dill_slist*)stack) - 1; + /* If there are free slots in the cache, put the stack into the cache. */ + if(ctx->count < dill_max_cached_stacks) { + dill_slist_push(&ctx->cache, item); + ++ctx->count; + return; + } + /* If the stack cache is full, deallocate the stack. */ +#if (HAVE_POSIX_MEMALIGN && HAVE_MPROTECT) & !defined DILL_NOGUARD + void *ptr = ((uint8_t*)(item + 1)) - dill_stack_size - dill_page_size(); + int rc = mprotect(ptr, dill_page_size(), PROT_READ|PROT_WRITE); + dill_assert(rc == 0); + free(ptr); +#else + void *ptr = ((uint8_t*)(item + 1)) - dill_stack_size; + free(ptr); +#endif +} + diff --git a/Sources/CLibdill/stack.h b/Sources/CLibdill/stack.h new file mode 100755 index 0000000..d0010a4 --- /dev/null +++ b/Sources/CLibdill/stack.h @@ -0,0 +1,51 @@ +/* + + Copyright (c) 2016 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_STACK_INCLUDED +#define DILL_STACK_INCLUDED + +#include + +#include "slist.h" + +/* A stack of unused coroutine stacks. This allows for extra-fast allocation + of a new stack. The LIFO nature of this structure minimises cache misses. + When the stack is cached its dill_qlist_item is placed on its top rather + then on the bottom. That way we minimise page misses. */ +struct dill_ctx_stack { + int count; + struct dill_slist cache; +}; + +int dill_ctx_stack_init(struct dill_ctx_stack *ctx); +void dill_ctx_stack_term(struct dill_ctx_stack *ctx); + +/* Allocates new stack. Returns pointer to the *top* of the stack. + For now we assume that the stack grows downwards. */ +void *dill_allocstack(size_t *stack_size); + +/* Deallocates a stack. The argument is pointer to the top of the stack. */ +void dill_freestack(void *stack); + +#endif diff --git a/Sources/CLibdill/tcp.c b/Sources/CLibdill/tcp.c new file mode 100755 index 0000000..bb3a594 --- /dev/null +++ b/Sources/CLibdill/tcp.c @@ -0,0 +1,295 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include +#include + +#include "libdillimpl.h" +#include "fd.h" +#include "utils.h" + +/* Secretly export the symbols. More thinking should be done about how + to do this cleanly without breaking encapsulation. */ +DILL_EXPORT extern const void *tcp_type; +DILL_EXPORT extern const void *tcp_listener_type; +DILL_EXPORT int tcp_fd(int s); + +static int tcp_makeconn(int fd); + +/******************************************************************************/ +/* TCP connection socket */ +/******************************************************************************/ + +dill_unique_id(tcp_type); + +static void *tcp_hquery(struct hvfs *hvfs, const void *type); +static void tcp_hclose(struct hvfs *hvfs); +static int tcp_hdone(struct hvfs *hvfs, int64_t deadline); +static int tcp_bsendl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline); +static ssize_t tcp_brecvl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline); + +struct tcp_conn { + struct hvfs hvfs; + struct bsock_vfs bvfs; + int fd; + struct fd_rxbuf rxbuf; + unsigned int indone : 1; + unsigned int outdone: 1; + unsigned int inerr : 1; + unsigned int outerr : 1; +}; + +static void *tcp_hquery(struct hvfs *hvfs, const void *type) { + struct tcp_conn *self = (struct tcp_conn*)hvfs; + if(type == bsock_type) return &self->bvfs; + if(type == tcp_type) return self; + errno = ENOTSUP; + return NULL; +} + +int tcp_connect(const struct ipaddr *addr, int64_t deadline) { + int err; + /* Open a socket. */ + int s = socket(ipaddr_family(addr), SOCK_STREAM, 0); + if(dill_slow(s < 0)) {err = errno; goto error1;} + /* Set it to non-blocking mode. */ + int rc = fd_unblock(s); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Connect to the remote endpoint. */ + rc = fd_connect(s, ipaddr_sockaddr(addr), ipaddr_len(addr), deadline); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Create the handle. */ + int h = tcp_makeconn(s); + if(dill_slow(h < 0)) {err = errno; goto error2;} + return h; +error2: + fd_close(s); +error1: + errno = err; + return -1; +} + +static int tcp_bsendl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline) { + struct tcp_conn *self = dill_cont(bvfs, struct tcp_conn, bvfs); + if(dill_slow(self->outdone)) {errno = EPIPE; return -1;} + if(dill_slow(self->outerr)) {errno = ECONNRESET; return -1;} + ssize_t sz = fd_send(self->fd, first, last, deadline); + if(dill_fast(sz >= 0)) return sz; + self->outerr = 1; + return -1; +} + +static ssize_t tcp_brecvl(struct bsock_vfs *bvfs, + struct iolist *first, struct iolist *last, int64_t deadline) { + struct tcp_conn *self = dill_cont(bvfs, struct tcp_conn, bvfs); + if(dill_slow(self->indone)) {errno = EPIPE; return -1;} + if(dill_slow(self->inerr)) {errno = ECONNRESET; return -1;} + int sz = fd_recv(self->fd, &self->rxbuf, first, last, deadline); + if(dill_fast(sz > 0)) return sz; + if(errno == EPIPE) self->indone = 1; + else self->inerr = 1; + return -1; +} + +static int tcp_hdone(struct hvfs *hvfs, int64_t deadline) { + struct tcp_conn *self = (struct tcp_conn*)hvfs; + if(dill_slow(self->outdone)) {errno = EPIPE; return -1;} + if(dill_slow(self->outerr)) {errno = ECONNRESET; return -1;} + /* Flushing the tx buffer is done asynchronously on kernel level. */ + int rc = shutdown(self->fd, SHUT_WR); + if(dill_slow(rc < 0)) { + if(errno == ENOTCONN) {self->outerr = 1; errno = ECONNRESET; return -1;} + if(errno == ENOBUFS) {self->outerr = 1; errno = ENOMEM; return -1;} + dill_assert(rc == 0); + } + self->outdone = 1; + return 0; +} + +int tcp_close(int s, int64_t deadline) { + int err; + struct tcp_conn *self = hquery(s, tcp_type); + if(dill_slow(!self)) return -1; + if(dill_slow(self->inerr || self->outerr)) {err = ECONNRESET; goto error;} + /* If not done already, flush the outbound data and start the terminal + handshake. */ + if(!self->outdone) { + int rc = tcp_hdone(&self->hvfs, deadline); + if(dill_slow(rc < 0)) {err = errno; goto error;} + } + /* Now we are going to read all the inbound data until we reach end of the + stream. That way we can be sure that the peer either received all our + data or consciously closed the connection without reading all of it. */ + int rc = tcp_brecvl(&self->bvfs, NULL, NULL, deadline); + dill_assert(rc < 0); + if(dill_slow(errno != EPIPE)) {err = errno; goto error;} + return 0; +error: + tcp_hclose(&self->hvfs); + errno = err; + return -1; +} + +static void tcp_hclose(struct hvfs *hvfs) { + struct tcp_conn *self = (struct tcp_conn*)hvfs; + fd_close(self->fd); + free(self); +} + +/******************************************************************************/ +/* TCP listener socket */ +/******************************************************************************/ + +dill_unique_id(tcp_listener_type); + +static void *tcp_listener_hquery(struct hvfs *hvfs, const void *type); +static void tcp_listener_hclose(struct hvfs *hvfs); + +struct tcp_listener { + struct hvfs hvfs; + int fd; + struct ipaddr addr; +}; + +static void *tcp_listener_hquery(struct hvfs *hvfs, const void *type) { + struct tcp_listener *self = (struct tcp_listener*)hvfs; + if(type == tcp_listener_type) return self; + errno = ENOTSUP; + return NULL; +} + +int tcp_listen(struct ipaddr *addr, int backlog) { + int err; + /* Open the listening socket. */ + int s = socket(ipaddr_family(addr), SOCK_STREAM, 0); + if(dill_slow(s < 0)) {err = errno; goto error1;} + /* Set it to non-blocking mode. */ + int rc = fd_unblock(s); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Start listening for incoming connections. */ + rc = bind(s, ipaddr_sockaddr(addr), ipaddr_len(addr)); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + rc = listen(s, backlog); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* If the user requested an ephemeral port, + retrieve the port number assigned by the OS. */ + if(ipaddr_port(addr) == 0) { + struct ipaddr baddr; + socklen_t len = sizeof(struct ipaddr); + rc = getsockname(s, (struct sockaddr*)&baddr, &len); + if(rc < 0) {err = errno; goto error2;} + ipaddr_setport(addr, ipaddr_port(&baddr)); + } + /* Create the object. */ + struct tcp_listener *self = malloc(sizeof(struct tcp_listener)); + if(dill_slow(!self)) {err = ENOMEM; goto error2;} + self->hvfs.query = tcp_listener_hquery; + self->hvfs.close = tcp_listener_hclose; + self->hvfs.done = NULL; + self->fd = s; + /* Create handle. */ + int h = hmake(&self->hvfs); + if(dill_slow(h < 0)) {err = errno; goto error3;} + return h; +error3: + free(self); +error2: + close(s); +error1: + errno = err; + return -1; +} + +int tcp_accept(int s, struct ipaddr *addr, int64_t deadline) { + int err; + /* Retrieve the listener object. */ + struct tcp_listener *lst = hquery(s, tcp_listener_type); + if(dill_slow(!lst)) {err = errno; goto error1;} + /* Try to get new connection in a non-blocking way. */ + socklen_t addrlen = sizeof(struct ipaddr); + int as = fd_accept(lst->fd, (struct sockaddr*)addr, &addrlen, deadline); + if(dill_slow(as < 0)) {err = errno; goto error1;} + /* Set it to non-blocking mode. */ + int rc = fd_unblock(as); + if(dill_slow(rc < 0)) {err = errno; goto error2;} + /* Create the handle. */ + int h = tcp_makeconn(as); + if(dill_slow(h < 0)) {err = errno; goto error2;} + return h; +error2: + fd_close(as); +error1: + errno = err; + return -1; +} + +int tcp_fd(int s) { + struct tcp_listener *lst = hquery(s, tcp_listener_type); + if(lst) return lst->fd; + struct tcp_conn *conn = hquery(s, tcp_type); + if(conn) return conn->fd; + return -1; +} + +static void tcp_listener_hclose(struct hvfs *hvfs) { + struct tcp_listener *self = (struct tcp_listener*)hvfs; + fd_close(self->fd); + free(self); +} + +/******************************************************************************/ +/* Helpers */ +/******************************************************************************/ + +static int tcp_makeconn(int fd) { + int err; + /* Create the object. */ + struct tcp_conn *self = malloc(sizeof(struct tcp_conn)); + if(dill_slow(!self)) {err = ENOMEM; goto error1;} + self->hvfs.query = tcp_hquery; + self->hvfs.close = tcp_hclose; + self->hvfs.done = tcp_hdone; + self->bvfs.bsendl = tcp_bsendl; + self->bvfs.brecvl = tcp_brecvl; + self->fd = fd; + fd_initrxbuf(&self->rxbuf); + self->indone = 0; + self->outdone = 0; + self->inerr = 0; + self->outerr = 0; + /* Create the handle. */ + int h = hmake(&self->hvfs); + if(dill_slow(h < 0)) {err = errno; goto error2;} + return h; +error2: + free(self); +error1: + errno = err; + return -1; +} + diff --git a/Sources/CLibdill/utils.c b/Sources/CLibdill/utils.c new file mode 100755 index 0000000..16a4d1c --- /dev/null +++ b/Sources/CLibdill/utils.c @@ -0,0 +1,89 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#include +#include + +#include "utils.h" + +int dill_maxfds(void) { + /* Get the maximum number of file descriptors. */ + struct rlimit rlim; + int rc = getrlimit(RLIMIT_NOFILE, &rlim); + dill_assert(rc == 0); + int maxfds = rlim.rlim_max; +#if defined BSD + /* On newer versions of OSX, the above behaves weirdly and returns -1, + so use OPEN_MAX instead. */ + if(maxfds < 0) return OPEN_MAX; +#endif + return maxfds; +} + +uint16_t dill_gets(const uint8_t *buf) { + return (((uint16_t)buf[0]) << 8) | + ((uint16_t)buf[1]); +} + +void dill_puts(uint8_t *buf, uint16_t val) { + buf[0] = (uint8_t)(((val) >> 8) & 0xff); + buf[1] = (uint8_t)(val & 0xff); +} + +uint32_t dill_getl(const uint8_t *buf) { + return (((uint32_t)buf[0]) << 24) | + (((uint32_t)buf[1]) << 16) | + (((uint32_t)buf[2]) << 8) | + ((uint32_t)buf[3]); +} + +void dill_putl(uint8_t *buf, uint32_t val) { + buf[0] = (uint8_t)(((val) >> 24) & 0xff); + buf[1] = (uint8_t)(((val) >> 16) & 0xff); + buf[2] = (uint8_t)(((val) >> 8) & 0xff); + buf[3] = (uint8_t)(val & 0xff); +} + +uint64_t dill_getll(const uint8_t *buf) { + return (((uint64_t)buf[0]) << 56) | + (((uint64_t)buf[1]) << 48) | + (((uint64_t)buf[2]) << 40) | + (((uint64_t)buf[3]) << 32) | + (((uint64_t)buf[4]) << 24) | + (((uint64_t)buf[5]) << 16) | + (((uint64_t)buf[6]) << 8) | + (((uint64_t)buf[7] << 0)); +} + +void dill_putll(uint8_t *buf, uint64_t val) { + buf[0] = (uint8_t)((val >> 56) & 0xff); + buf[1] = (uint8_t)((val >> 48) & 0xff); + buf[2] = (uint8_t)((val >> 40) & 0xff); + buf[3] = (uint8_t)((val >> 32) & 0xff); + buf[4] = (uint8_t)((val >> 24) & 0xff); + buf[5] = (uint8_t)((val >> 16) & 0xff); + buf[6] = (uint8_t)((val >> 8) & 0xff); + buf[7] = (uint8_t)(val & 0xff); +} + diff --git a/Sources/CLibdill/utils.h b/Sources/CLibdill/utils.h new file mode 100755 index 0000000..d69dde6 --- /dev/null +++ b/Sources/CLibdill/utils.h @@ -0,0 +1,87 @@ +/* + + Copyright (c) 2017 Martin Sustrik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom + the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + +*/ + +#ifndef DILL_UTILS_H_INCLUDED +#define DILL_UTILS_H_INCLUDED + +#include +#include +#include +#include + +#define dill_concat(x,y) x##y + +/* Defines a unique identifier of type const void*. */ +#define dill_unique_id(name) \ + static const int dill_concat(name, ___) = 0;\ + const void *name = & dill_concat(name, ___); + +/* Takes a pointer to a member variable and computes pointer to the structure + that contains it. 'type' is type of the structure, not the member. */ +#define dill_cont(ptr, type, member) \ + (ptr ? ((type*) (((char*) ptr) - offsetof(type, member))) : NULL) + +/* Compile-time assert. */ +#define DILL_CT_ASSERT_HELPER2(prefix, line) \ + prefix##line +#define DILL_CT_ASSERT_HELPER1(prefix, line) \ + DILL_CT_ASSERT_HELPER2(prefix, line) +#define DILL_CT_ASSERT(x) \ + typedef int DILL_CT_ASSERT_HELPER1(ct_assert_,__COUNTER__) [(x) ? 1 : -1] + +/* Optimisation hints. */ +#if defined __GNUC__ || defined __llvm__ +#define dill_fast(x) __builtin_expect(!!(x), 1) +#define dill_slow(x) __builtin_expect(!!(x), 0) +#else +#define dill_fast(x) (x) +#define dill_slow(x) (x) +#endif + +/* Define our own assert. This way we are sure that it stays in place even + if the standard C assert would be thrown away by the compiler. It also + allows us to overload it as needed. */ +#define dill_assert(x) \ + do {\ + if (dill_slow(!(x))) {\ + fprintf(stderr, "Assert failed: " #x " (%s:%d)\n",\ + __FILE__, __LINE__);\ + fflush(stderr);\ + abort();\ + }\ + } while (0) + +/* Returns the maximum possible file descriptor number */ +int dill_maxfds(void); + +/* Encoding and decoding integers from network byte order. */ +uint16_t dill_gets(const uint8_t *buf); +void dill_puts(uint8_t *buf, uint16_t val); +uint32_t dill_getl(const uint8_t *buf); +void dill_putl(uint8_t *buf, uint32_t val); +uint64_t dill_getll(const uint8_t *buf); +void dill_putll(uint8_t *buf, uint64_t val); + +#endif + diff --git a/Sources/Venice/Channel.swift b/Sources/Venice/Channel.swift index 3609315..6cd03e7 100644 --- a/Sources/Venice/Channel.swift +++ b/Sources/Venice/Channel.swift @@ -19,56 +19,32 @@ import CLibdill /// /// let theAnswer = try channel.receive(deadline: 1.second.fromNow()) /// ``` -public final class Channel { - private typealias Handle = Int32 - - private enum ChannelResult { - case value(Type) - case error(Error) - fileprivate func getValue() throws -> Type { - switch self { - case .value(let value): - return value - case .error(let error): - throw error - } - } - } - - private let handle: Handle - private var buffer = List>() +public final class Channel { + private let handle: Int32 /// Creates a channel /// /// - Warning: /// A channel is a synchronization primitive, not a container. /// It doesn't store any items. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceledCoroutine - /// Thrown when the operation is performed within a canceled coroutine. - /// #### VeniceError.outOfMemory - /// Thrown when the system doesn't have enough memory to create a new channel. - public init() throws { - let result = chmake(0) + public init() { + let result = chmake(MemoryLayout.size) guard result != -1 else { switch errno { - case ECANCELED: - throw VeniceError.canceledCoroutine case ENOMEM: - throw VeniceError.outOfMemory + fatalError("Out of memory while creating channel.") default: - throw VeniceError.unexpectedError + fatalError("Unexpected error \(errno) while creating channel.") } } - handle = result + self.handle = result } - + deinit { - hclose(handle) + hclose(self.handle) } /// Reference to the channel which can only send. @@ -76,29 +52,35 @@ public final class Channel { /// Reference to the channel which can only receive. public lazy var receiving: Receiving = Receiving(self) + + /// Whether or not this channel is closed + public private(set) var isClosed: Bool = false /// Sends a value to the channel. public func send(_ value: Type, deadline: Deadline) throws { - try send(.value(value), deadline: deadline) + try self.send(.value(value), deadline: deadline) } /// Sends an error to the channel. public func send(_ error: Error, deadline: Deadline) throws { - try send(.error(error), deadline: deadline) + try self.send(.error(error), deadline: deadline) } - private func send(_ channelResult: ChannelResult, deadline: Deadline) throws { - let node = buffer.append(channelResult) - let result = chsend(handle, nil, 0, deadline.value) + private func send(_ value: Result, deadline: Deadline) throws { + guard !self.isClosed else { + throw VeniceError.closedChannel + } + + let boxed = Box(value) + var pointer = Unmanaged.passRetained(boxed).toOpaque() + let result = chsend(self.handle, &pointer, MemoryLayout.size, deadline.value) guard result == 0 else { + _ = Unmanaged>>.fromOpaque(pointer).release() switch errno { - case ECANCELED: - throw VeniceError.canceledCoroutine case EPIPE: - throw VeniceError.doneChannel + throw VeniceError.closedChannel case ETIMEDOUT: - buffer.remove(node) throw VeniceError.deadlineReached default: throw VeniceError.unexpectedError @@ -108,14 +90,18 @@ public final class Channel { /// Receives a value from channel. @discardableResult public func receive(deadline: Deadline) throws -> Type { - let result = chrecv(handle, nil, 0, deadline.value) + guard !self.isClosed else { + throw VeniceError.closedChannel + } + + var pointer: UnsafeMutableRawPointer? = nil + + let result = chrecv(self.handle, &pointer, MemoryLayout.size, deadline.value) guard result == 0 else { switch errno { - case ECANCELED: - throw VeniceError.canceledCoroutine case EPIPE: - throw VeniceError.doneChannel + throw VeniceError.closedChannel case ETIMEDOUT: throw VeniceError.deadlineReached default: @@ -123,17 +109,23 @@ public final class Channel { } } - return try buffer.removeFirst().getValue() + let boxed = Unmanaged>>.fromOpaque(pointer!).takeRetainedValue() + + return try boxed.value.dematerialize() } /// This function is used to inform the channel that no more `send` or `receive` should be /// performed on the channel. /// /// - Warning: - /// After `done` is called on a channel, any attempts to `send` or `receive` - /// will result in a `VeniceError.doneChannel` error. - public func done() { - hdone(handle, 0) + /// After `close` is called on a channel, any attempts to `send` or `receive` + /// will result in a `VeniceError.closedChannel` error. + public func close() { + guard !self.isClosed else { + return + } + self.isClosed = true + hdone(self.handle, 0) } /// Send-only reference to an existing channel. @@ -150,7 +142,11 @@ public final class Channel { /// try send(to: channel.sending) /// ``` public final class Sending { - private let channel: Channel + fileprivate let channel: Channel + + public var isClosed: Bool { + return self.channel.isClosed + } fileprivate init(_ channel: Channel) { self.channel = channel @@ -158,17 +154,17 @@ public final class Channel { /// :nodoc: public func send(_ value: Type, deadline: Deadline) throws { - try channel.send(value, deadline: deadline) + try self.channel.send(value, deadline: deadline) } /// :nodoc: public func send(_ error: Error, deadline: Deadline) throws { - try channel.send(error, deadline: deadline) + try self.channel.send(error, deadline: deadline) } /// :nodoc: - public func done() { - channel.done() + public func close() { + self.channel.close() } } @@ -186,7 +182,11 @@ public final class Channel { /// try receive(from: channel.receiving) /// ``` public final class Receiving { - private let channel: Channel + fileprivate let channel: Channel + + public var isClosed: Bool { + return self.channel.isClosed + } fileprivate init(_ channel: Channel) { self.channel = channel @@ -194,12 +194,12 @@ public final class Channel { /// :nodoc: @discardableResult public func receive(deadline: Deadline) throws -> Type { - return try channel.receive(deadline: deadline) + return try self.channel.receive(deadline: deadline) } /// :nodoc: - public func done() { - channel.done() + public func close() { + self.channel.close() } } } @@ -207,72 +207,36 @@ public final class Channel { extension Channel where Type == Void { /// :nodoc: public func send(deadline: Deadline) throws { - try send((), deadline: deadline) + try self.send((), deadline: deadline) } } extension Channel.Sending where Type == Void { /// :nodoc: public func send(deadline: Deadline) throws { - try send((), deadline: deadline) + try self.send((), deadline: deadline) } } -class Node { - var value: T - var next: Node? - weak var previous: Node? - - init(value: T) { +fileprivate class Box { + let value: T + + init(_ value: T) { self.value = value } } -fileprivate class List { - private var head: Node? - private var tail: Node? - - @discardableResult fileprivate func append(_ value: T) -> Node { - let newNode = Node(value: value) - - if let tailNode = tail { - newNode.previous = tailNode - tailNode.next = newNode - } else { - head = newNode - } - - tail = newNode - return newNode - } - - @discardableResult fileprivate func remove(_ node: Node) -> T { - let prev = node.previous - let next = node.next - - if let prev = prev { - prev.next = next - } else { - head = next - } - - next?.previous = prev - - if next == nil { - tail = prev - } - - node.previous = nil - node.next = nil - - return node.value - } - - @discardableResult fileprivate func removeFirst() throws -> T { - guard let head = head else { - throw VeniceError.unexpectedError +fileprivate enum Result { + case value(Type) + case error(Error) + + public func dematerialize() throws -> Type { + switch self { + case .value(let value): + return value + case .error(let error): + throw error } - - return remove(head) } } + diff --git a/Sources/Venice/Coroutine.swift b/Sources/Venice/Coroutine.swift index 9ab6e48..9e764f2 100644 --- a/Sources/Venice/Coroutine.swift +++ b/Sources/Venice/Coroutine.swift @@ -2,9 +2,10 @@ import Glibc #else import Darwin.C - import Foundation #endif +import Foundation +import Dispatch import CLibdill /// Lightweight coroutine. @@ -31,16 +32,11 @@ import CLibdill /// ## Example: /// /// ```swift -/// let coroutine = try Coroutine { +/// Coroutine.run { /// ... /// } -/// -/// coroutine.cancel() /// ``` -public final class Coroutine { - private typealias Handle = Int32 - private let handle: Handle - +public final class Coroutine : CustomStringConvertible { /// Launches a coroutine that executes the closure passed as argument. /// The coroutine is executed concurrently, and its lifetime may exceed the lifetime /// of the caller. @@ -48,64 +44,175 @@ public final class Coroutine { /// ## Example: /// /// ```swift - /// let coroutine = try Coroutine { + /// Coroutine.run { /// ... /// } - /// - /// coroutine.cancel() /// ``` /// /// - Parameters: - /// - body: Body of the newly created coroutine. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceledCoroutine - /// Thrown when the operation is performed within a canceled coroutine. - /// #### VeniceError.outOfMemory - /// Thrown when the system doesn't have enough memory to create a new coroutine. - public init(body: @escaping () throws -> Void) throws { - var coroutine = { - do { - try body() - } catch VeniceError.canceledCoroutine { - return - } catch { - print(error) - } + /// - routine: The routine to execute + public static func run(label: String = "anonymous", file: String = #file, line: Int = #line, routine: @escaping () -> Void) { + Coroutine.reaper.reap() + + var _routine = { + Coroutine.current = Coroutine(label: label) + routine() + Coroutine.current = Coroutine.main } - let result = co(nil, 0, &coroutine, nil, 0) { pointer in + let result = co(nil, 0, &_routine, file, Int32(line)) { handle, pointer in pointer?.assumingMemoryBound(to: (() -> Void).self).pointee() + Coroutine.reaper.push(handle: handle) } guard result != -1 else { switch errno { - case ECANCELED: - throw VeniceError.canceledCoroutine case ENOMEM: - throw VeniceError.outOfMemory + fatalError("Out of memory while creating coroutine.") default: + fatalError("Unexpected error \(errno) while creating coroutine.") + } + } + } + + /// Launches a coroutine on a background thread that executes the closure passed + /// as an argument. The coroutine is executed concurrently but the caller is + /// blocked cooperatively until a result is available. + /// + /// ## Example: + /// + /// ```swift + /// let result: Bool = Coroutine.worker { + /// ... some heavy processing work + /// return true + /// } + /// ``` + /// + /// - Parameters: + /// - routine: the routine to execute + /// - Warning: + /// Worker coroutines should be treated mostly like individual processes. Care must be taken not + /// to share any coroutine compatible primitives such as channels or sockets as they are not threadsafe. + public static func worker(routine: @escaping () throws -> T) throws -> T { + var resultingValue: T? = nil + var resultingError: Error? = nil + + var fds: [Int32] = [0, 0] + var rc: Int32 = fds.withUnsafeMutableBufferPointer { + #if os(Linux) + return socketpair(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0, $0.baseAddress!) + #else + return socketpair(AF_UNIX, SOCK_STREAM, 0, $0.baseAddress!) + #endif + } + guard rc == 0 else { + throw VeniceError.systemError(number: errno) + } + defer { + #if os(Linux) + Glibc.close(fds[0]) + Glibc.close(fds[1]) + #else + Darwin.close(fds[0]) + Darwin.close(fds[1]) + #endif + } + + let local = try FileDescriptor(fds[0]) + let remote = try FileDescriptor(fds[1]) + + DispatchQueue.global().async { + do { + resultingValue = try routine() + } catch { + resultingError = error + } + try? [UInt8(1)].withUnsafeBufferPointer { + try remote.write(UnsafeRawBufferPointer($0), deadline: .never) + return + } + try? remote.close() + } + + var buffer = UnsafeMutableRawBufferPointer.allocate(count: 1) + defer { + buffer.deallocate() + } + _ = try? local.read(buffer, deadline: .never) + + guard let value = resultingValue else { + guard let error = resultingError else { throw VeniceError.unexpectedError } + throw error } - handle = result + return value } - - deinit { - cancel() + + /// Gets a reference to the current running coroutine + public private(set) static var current: Coroutine { + get { + guard let raw = clsget() else { + return Coroutine.main + } + return Unmanaged.fromOpaque(raw).takeUnretainedValue() + } + set { + if let raw = clsget() { + _ = Unmanaged.fromOpaque(raw).takeRetainedValue() + clsset(nil) + } + clsset(Unmanaged.passRetained(newValue).toOpaque()) + } } - - /// Cancels the coroutine. - /// - /// - Warning: - /// Once a coroutine is canceled any coroutine-blocking operation within the coroutine - /// will throw `VeniceError.canceledCoroutine`. - public func cancel() { - hclose(handle) + + private static var main: Coroutine { + let coroutine: Coroutine + if let existing = Thread.current.threadDictionary["Venice.Coroutine.main"] as? Coroutine { + coroutine = existing + } else { + coroutine = Coroutine() + Thread.current.threadDictionary["Venice.Coroutine.main"] = coroutine + } + return coroutine + } + + private final class Reaper { + private var handles: [Int32] = [] + + func push(handle: Int32) { + handles.append(handle) + } + + func reap() { + guard !handles.isEmpty else { + return + } + + for handle in handles { + hclose(handle) + } + handles = [] + } + + deinit { + reap() + } } - - /// Explicitly passes control to other coroutines. + + private static var reaper: Reaper { + let reaper: Reaper + if let existing = Thread.current.threadDictionary["Venice.Coroutine.reaper"] as? Reaper { + reaper = existing + } else { + reaper = Reaper() + Thread.current.threadDictionary["Venice.Coroutine.reaper"] = reaper + } + return reaper + } + + /// Explicitly passes control to other coroutines. /// By calling this function, you give other coroutines a chance to run. /// /// You should consider using `Coroutiner.yield()` when doing lengthy computations @@ -119,24 +226,11 @@ public final class Coroutine { /// try Coroutine.yield() // Give other coroutines a chance to run. /// } /// ``` - /// - /// - Warning: - /// Once a coroutine is canceled calling `Couroutine.yield` - /// will throw `VeniceError.canceledCoroutine`. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceledCoroutine - /// Thrown when the operation is performed within a canceled coroutine. - public static func yield() throws { + public static func yield() { let result = CLibdill.yield() - - guard result == 0 else { - switch errno { - case ECANCELED: - throw VeniceError.canceledCoroutine - default: - throw VeniceError.unexpectedError - } + + guard result != -1 else { + fatalError("Unexpected error while yielding coroutine.") } } @@ -145,172 +239,77 @@ public final class Coroutine { /// ## Example: /// /// ```swift - /// func execute(_ deadline: Deadline, body: (Void) throws -> R) throws -> R { - /// try Coroutine.wakeUp(deadline) + /// func execute(at deadline: Deadline, body: (Void) throws -> R) throws -> R { + /// Coroutine.wakeUp(at: deadline) /// try body() /// } /// - /// try execute(1.second.fromNow()) { + /// try execute(at: 1.second.fromNow()) { /// print("Hey! Ho! Let's go!") /// } /// ``` - /// - /// - Warning: - /// Once a coroutine is canceled calling `Couroutine.wakeUp` - /// will throw `VeniceError.canceledCoroutine`. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceledCoroutine - /// Thrown when the operation is performed within a canceled coroutine. - public static func wakeUp(_ deadline: Deadline) throws { - let result = msleep(deadline.value) - - guard result == 0 else { - switch errno { - case ECANCELED: - throw VeniceError.canceledCoroutine - default: - throw VeniceError.unexpectedError - } + public static func wakeUp(at deadline: Deadline) { + let result = CLibdill.msleep(deadline.value) + + guard result != -1 else { + fatalError("Unexpected error while sleeping coroutine.") } } - - /// Coroutine groups are useful for canceling multiple coroutines at the - /// same time. + + /// Sleeps for duration. /// /// ## Example: - /// ```swift - /// let group = Coroutine.Group(minimumCapacity: 2) /// - /// try group.addCoroutine { - /// ... + /// ```swift + /// func execute(after duration: Duration, body: (Void) throws -> R) throws -> R { + /// Coroutine.sleep(for: duration) + /// try body() /// } /// - /// try group.addCoroutine { - /// ... + /// try execute(after 1.second) { + /// print("Hey! Ho! Let's go!") /// } - /// - /// // all coroutines in the group will be canceled - /// group.cancel() /// ``` - public class Group { - private var coroutines: [Int: Coroutine] - private var finishedCoroutines: Set = [] - - private static var id = 0 - - private static func getNextID() -> Int { - defer { - if id == Int.max { - id = -1 - } - - id += 1 - } - - return id - } - - /// Creates a new, empty coroutine group with at least the specified number - /// of elements' worth of buffer. - /// - /// Use this initializer to avoid repeated reallocations of a group's buffer - /// if you know you'll be adding elements to the group after creation. The - /// actual capacity of the created group will be the smallest power of 2 that - /// is greater than or equal to `minimumCapacity`. - /// - /// ## Example: - /// - /// ```swift - /// let group = CoroutineGroup(minimumCapacity: 2) - /// - /// try group.addCoroutine { - /// ... - /// } - /// - /// try group.addCoroutine { - /// ... - /// } - /// - /// // all coroutines in the group will be canceled - /// group.cancel() - /// ``` - /// - /// - Parameter minimumCapacity: The minimum number of elements that the - /// newly created group should be able to store without reallocating its - /// buffer. - public init(minimumCapacity: Int = 0) { - coroutines = [Int: Coroutine](minimumCapacity: minimumCapacity) - } - - deinit { - cancel() + public static func sleep(for duration: Duration) { + wakeUp(at: duration.fromNow()) + } + + /// Coroutine label + public let label: String + + /// Coroutine description + public var description: String { + return label + } + + /// Coroutine local storage subscripting + public subscript(key: String) -> Any? { + get { + return storage[key] } - - /// Creates a lightweight coroutine and adds it to the group. - /// - /// ## Example: - /// - /// ```swift - /// let coroutine = try group.addCoroutine { - /// ... - /// } - /// ``` - /// - /// - Parameters: - /// - body: Body of the newly created coroutine. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceledCoroutine - /// Thrown when the operation is performed within a canceled coroutine. - /// #### VeniceError.outOfMemory - /// Thrown when the system doesn't have enough memory to create a new coroutine. - /// - Returns: Newly created coroutine - @discardableResult public func addCoroutine(body: @escaping () throws -> Void) throws -> Coroutine { - removeFinishedCoroutines() - - var finished = false - let id = Group.getNextID() - - let coroutine = try Coroutine { [unowned self] in - defer { - finished = true - self.finishedCoroutines.insert(id) - } - - try body() - } - - if !finished { - coroutines[id] = coroutine + set { + if let value = newValue { + storage[key] = value + } else { + storage.removeValue(forKey: key) } - - return coroutine } - - /// Cancels all coroutines in the group. - /// - /// - Warning: - /// Once a coroutine is canceled any coroutine-blocking operation within the coroutine - /// will throw `VeniceError.canceledCoroutine`. - public func cancel() { - removeFinishedCoroutines() - - for (id, coroutine) in coroutines { - defer { - coroutines[id] = nil - } - - coroutine.cancel() - } + } + + /// Coroutine local storage subscripting + public subscript(key: String, default: Any) -> Any? { + get { + return self[key] ?? `default` } - - private func removeFinishedCoroutines() { - for id in finishedCoroutines { - coroutines[id] = nil - } - - finishedCoroutines.removeAll() + set { + self[key] = newValue } } + + private var storage: [String: Any] = [:] + + private init(label: String = "anonymous") { + self.label = label + } } + diff --git a/Sources/Venice/Error.swift b/Sources/Venice/Error.swift index ab81f1c..b6bac9c 100644 --- a/Sources/Venice/Error.swift +++ b/Sources/Venice/Error.swift @@ -1,21 +1,20 @@ /// Venice operation error public enum VeniceError : Error, Equatable { - /// Thrown when the operation is performed within a canceled coroutine. - case canceledCoroutine /// Thrown when the operation is performed on an invalid file descriptor. case invalidFileDescriptor /// Thrown when another coroutine is already blocked on `poll` with this file descriptor. case fileDescriptorBlockedInAnotherCoroutine /// Thrown when the operation reaches the deadline. case deadlineReached - /// Thrown when the system doesn't have enough memory to perform the operation. - case outOfMemory - /// Thrown when the operation is performed on an done channel. - case doneChannel + /// Thrown when the operation is performed on a closed channel. + case closedChannel /// Thrown when a read operation fails. case readFailed /// Thrown when a write operation fails. case writeFailed + + /// Thrown when a system error occurs. + case systemError(number: Int32) /// Thrown when an unexpected error occurs. /// This should never happen in the regular flow of an application. @@ -24,17 +23,13 @@ public enum VeniceError : Error, Equatable { /// :nodoc: public static func == (lhs: VeniceError, rhs: VeniceError) -> Bool { switch (lhs, rhs) { - case (.canceledCoroutine, .canceledCoroutine): - return true case (.invalidFileDescriptor, .invalidFileDescriptor): return true case (.fileDescriptorBlockedInAnotherCoroutine, .fileDescriptorBlockedInAnotherCoroutine): return true case (.deadlineReached, .deadlineReached): return true - case (.outOfMemory, .outOfMemory): - return true - case (.doneChannel, .doneChannel): + case (.closedChannel, .closedChannel): return true case (.unexpectedError, .unexpectedError): return true diff --git a/Sources/Venice/FileDescriptor.swift b/Sources/Venice/FileDescriptor.swift index 5964a7d..ec8e187 100644 --- a/Sources/Venice/FileDescriptor.swift +++ b/Sources/Venice/FileDescriptor.swift @@ -228,8 +228,6 @@ public final class FileDescriptor { switch errno { case EBADF: throw VeniceError.invalidFileDescriptor - case ECANCELED: - throw VeniceError.canceledCoroutine case EBUSY: throw VeniceError.fileDescriptorBlockedInAnotherCoroutine case ETIMEDOUT: diff --git a/Tests/VeniceTests/Venice/Assert.swift b/Tests/VeniceTests/Venice/Assert.swift index a3a20f6..7be5f24 100644 --- a/Tests/VeniceTests/Venice/Assert.swift +++ b/Tests/VeniceTests/Venice/Assert.swift @@ -1,10 +1,18 @@ import XCTest -public func XCTAssertThrowsError( - _ expression: @autoclosure () throws -> T, - error: U -) { - XCTAssertThrowsError(expression) { e in - XCTAssertEqual(e as? U, error) +public func XCTAssertThrowsError(_ expression: () throws -> T, error expectedError: U) -> Void { + do { + _ = try expression() + } catch { + XCTAssertEqual(error as? U, expectedError) + } +} + + +public func XCTAssertThrowsNoError(_ expression: () throws -> T) -> Void { + do { + _ = try expression() + } catch { + XCTFail("Expected no error, caught \(error).") } } diff --git a/Tests/VeniceTests/Venice/ChannelTests.swift b/Tests/VeniceTests/Venice/ChannelTests.swift index 7d41dc8..42f410a 100644 --- a/Tests/VeniceTests/Venice/ChannelTests.swift +++ b/Tests/VeniceTests/Venice/ChannelTests.swift @@ -7,316 +7,263 @@ struct Fou { } public class ChannelTests : XCTestCase { - func testCreationOnCanceledCoroutine() throws { - let coroutine = try Coroutine { - try Coroutine.yield() - XCTAssertThrowsError(try Channel(), error: VeniceError.canceledCoroutine) - } + func testSendOnCloseChannel() throws { + let channel = Channel() + channel.close() - coroutine.cancel() - } - - func testSendOnCanceledCoroutine() throws { - let channel = try Channel() - - let coroutine = try Coroutine { - XCTAssertThrowsError( - try channel.send(deadline: .never), - error: VeniceError.canceledCoroutine - ) - } - - coroutine.cancel() - } - - func testSendOnDoneChannel() throws { - let channel = try Channel() - channel.done() - - XCTAssertThrowsError(try channel.send(deadline: .never), error: VeniceError.doneChannel) + XCTAssertThrowsError({ try channel.send(deadline: .never) }, error: VeniceError.closedChannel) } func testSendTimeout() throws { - let channel = try Channel() + let channel = Channel() - XCTAssertThrowsError(try channel.send(111, deadline: .immediately), error: VeniceError.deadlineReached) + XCTAssertThrowsError({ try channel.send(111, deadline: .immediately) }, error: VeniceError.deadlineReached) - let coroutine = try Coroutine { - try channel.send(222, deadline: .never) + Coroutine.run { + do { + try channel.send(222, deadline: .never) + } catch { + XCTFail("\(error)") + } } XCTAssertEqual(try channel.receive(deadline: .never), 222) - coroutine.cancel() } func testDoubleSendTimeout() throws { - let channel = try Channel() + let channel = Channel() - let coroutine1 = try Coroutine { + Coroutine.run { XCTAssertThrowsError( - try channel.send(111, deadline: 50.milliseconds.fromNow()), + { try channel.send(111, deadline: 50.milliseconds.fromNow()) }, error: VeniceError.deadlineReached ) } - let coroutine2 = try Coroutine { + Coroutine.run { XCTAssertThrowsError( - try channel.send(222, deadline: 50.milliseconds.fromNow()), + { try channel.send(222, deadline: 50.milliseconds.fromNow()) }, error: VeniceError.deadlineReached ) } - try Coroutine.wakeUp(100.milliseconds.fromNow()) + Coroutine.wakeUp(at: 100.milliseconds.fromNow()) - let coroutine3 = try Coroutine { - try channel.send(333, deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try channel.send(333, deadline: .never) }) } - XCTAssertEqual(try channel.receive(deadline: .never), 333) - - coroutine1.cancel() - coroutine2.cancel() - coroutine3.cancel() - } - func testReceiveOnCanceledCoroutine() throws { - let channel = try Channel() - let coroutine = try Coroutine { - XCTAssertThrowsError( - try channel.receive(deadline: .never), - error: VeniceError.canceledCoroutine - ) - } - - coroutine.cancel() + print("will receive") + XCTAssertEqual(try channel.receive(deadline: .never), 333) + print("did receive") } - func testReceiveOnDoneChannel() throws { - let channel = try Channel() - channel.done() - XCTAssertThrowsError(try channel.receive(deadline: .never), error: VeniceError.doneChannel) + func testReceiveOnCloseChannel() throws { + let channel = Channel() + channel.close() + XCTAssertThrowsError({ try channel.receive(deadline: .never) }, error: VeniceError.closedChannel) } func testReceiveTimeout() throws { - let channel = try Channel() + let channel = Channel() XCTAssertThrowsError( - try channel.receive(deadline: .immediately), + { try channel.receive(deadline: .immediately) }, error: VeniceError.deadlineReached ) - let coroutine = try Coroutine { - XCTAssertEqual(try channel.receive(deadline: .never), 222) + Coroutine.run { + XCTAssertEqual(try? channel.receive(deadline: .never), 222) } try channel.send(222, deadline: .never) - coroutine.cancel() } func testReceiverWaitsForSender() throws { - let channel = try Channel() + let channel = Channel() - let coroutine = try Coroutine { - XCTAssertEqual(try channel.receive(deadline: .never), 333) + Coroutine.run { + XCTAssertEqual(try? channel.receive(deadline: .never), 333) } try channel.send(333, deadline: .never) - coroutine.cancel() } func testSenderWaitsForReceiver() throws { - let channel = try Channel() + let channel = Channel() - let coroutine = try Coroutine { - try channel.send(444, deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try channel.send(444, deadline: .never) }) } XCTAssertEqual(try channel.receive(deadline: .never), 444) - coroutine.cancel() } func testSendingChannel() throws { - let channel = try Channel() + let channel = Channel() func send(to channel: Channel.Sending) throws { try channel.send(111, deadline: .never) } - let coroutine = try Coroutine { - try send(to: channel.sending) + Coroutine.run { + XCTAssertThrowsNoError({ try send(to: channel.sending) }) } XCTAssertEqual(try channel.receive(deadline: .never), 111) - coroutine.cancel() } func testSendErrorToSendingChannel() throws { - let channel = try Channel() + let channel = Channel() func send(to channel: Channel.Sending) throws { try channel.send(VeniceError.unexpectedError, deadline: .never) } - let coroutine = try Coroutine { - try send(to: channel.sending) + Coroutine.run { + XCTAssertThrowsNoError({ try send(to: channel.sending) }) } - XCTAssertThrowsError(try channel.receive(deadline: .never), error: VeniceError.unexpectedError) - coroutine.cancel() + XCTAssertThrowsError({ try channel.receive(deadline: .never) }, error: VeniceError.unexpectedError) } - func testDoneOnDoneSendingChannel() throws { - let channel = try Channel() + func testCloseOnClosedSendingChannel() throws { + let channel = Channel() let sending = channel.sending - channel.done() - sending.done() + channel.close() + sending.close() } func testReceivingChannel() throws { - let channel = try Channel() + let channel = Channel() func receive(_ channel: Channel.Receiving) { XCTAssertEqual(try channel.receive(deadline: .never), 999) } - let coroutine = try Coroutine { - try channel.send(999, deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try channel.send(999, deadline: .never) }) } receive(channel.receiving) - coroutine.cancel() } - func testDoneOnDoneReceivingChannel() throws { - let channel = try Channel() + func testCloseOnClosedReceivingChannel() throws { + let channel = Channel() let receiving = channel.receiving - channel.done() - receiving.done() + channel.close() + receiving.close() } func testTwoSimultaneousSenders() throws { - let channel = try Channel() + let channel = Channel() - let coroutine1 = try Coroutine { - try channel.send(888, deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try channel.send(888, deadline: .never) }) } - let coroutine2 = try Coroutine { - try channel.send(999, deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try channel.send(999, deadline: .never) }) } XCTAssertEqual(try channel.receive(deadline: .never), 888) XCTAssertEqual(try channel.receive(deadline: .never), 999) - - coroutine1.cancel() - coroutine2.cancel() } func testTwoSimultaneousReceivers() throws { - let channel = try Channel() + let channel = Channel() - let coroutine1 = try Coroutine { - XCTAssertEqual(try channel.receive(deadline: .never), 333) + Coroutine.run { + XCTAssertEqual(try? channel.receive(deadline: .never), 333) } - let coroutine2 = try Coroutine { - XCTAssertEqual(try channel.receive(deadline: .never), 444) + Coroutine.run { + XCTAssertEqual(try? channel.receive(deadline: .never), 444) } try channel.send(333, deadline: .never) try channel.send(444, deadline: .never) - - coroutine1.cancel() - coroutine2.cancel() } func testTypedChannels() throws { - let stringChannel = try Channel() + let stringChannel = Channel() - let coroutine1 = try Coroutine { - try stringChannel.send("yo", deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try stringChannel.send("yo", deadline: .never) }) } XCTAssertEqual(try stringChannel.receive(deadline: .never), "yo") - let fooChannel = try Channel() + let fooChannel = Channel() - let coroutine2 = try Coroutine { - try fooChannel.send(Fou(bar: 555, baz: 222), deadline: .never) + Coroutine.run { + XCTAssertThrowsNoError({ try fooChannel.send(Fou(bar: 555, baz: 222), deadline: .never) }) } let foo = try fooChannel.receive(deadline: .never) XCTAssertEqual(foo.bar, 555) XCTAssertEqual(foo.baz, 222) - - coroutine1.cancel() - coroutine2.cancel() } - func testDoneChannelUnblocks() throws { - let channel1 = try Channel() - let channel2 = try Channel() + func testCloseChannelUnblocks() throws { + let channel1 = Channel() + let channel2 = Channel() - let coroutine1 = try Coroutine { + Coroutine.run { XCTAssertThrowsError( - try channel1.receive(deadline: .never), - error: VeniceError.doneChannel + { try channel1.receive(deadline: .never) }, + error: VeniceError.closedChannel ) - try channel2.send(0, deadline: .never) + XCTAssertThrowsNoError({ try channel2.send(0, deadline: .never) }) } - let coroutine2 = try Coroutine { + Coroutine.run { XCTAssertThrowsError( - try channel1.receive(deadline: .never), - error: VeniceError.doneChannel + { try channel1.receive(deadline: .never) }, + error: VeniceError.closedChannel ) - try channel2.send(0, deadline: .never) + XCTAssertThrowsNoError({ try channel2.send(0, deadline: .never) }) } - channel1.done() + channel1.close() XCTAssertEqual(try channel2.receive(deadline: .never), 0) XCTAssertEqual(try channel2.receive(deadline: .never), 0) - - coroutine1.cancel() - coroutine2.cancel() } - func testTenThousandWhispers() throws { + func testOneThousandWhispers() throws { self.measure { - do { - let numberOfWhispers = 10_000 - let whispers = Coroutine.Group(minimumCapacity: numberOfWhispers) + let numberOfWhispers = 1_000 + let deadline = Deadline.never - let leftmost = try Channel() + let leftmost = Channel() - var right = leftmost - var left = leftmost + var right = leftmost + var left = leftmost - for _ in 0 ..< numberOfWhispers { - right = try Channel() + for _ in 0 ..< numberOfWhispers { + right = Channel() - try whispers.addCoroutine { - try left.send(right.receive(deadline: .never) + 1, deadline: .never) + Coroutine.run { [l = left, r = right] in + do { + try l.send(r.receive(deadline: deadline) + 1, deadline: deadline) + } catch { + XCTFail("Expected no error, got: \(error)") } - - left = right - } - - let starter = try Coroutine { - try right.send(1, deadline: .never) } - XCTAssertEqual(try leftmost.receive(deadline: .never), numberOfWhispers + 1) + left = right + } - starter.cancel() - whispers.cancel() - } catch { - XCTFail() + Coroutine.run { + XCTAssertThrowsNoError({ try right.send(1, deadline: deadline) }) } + + XCTAssertThrowsNoError({ XCTAssertEqual(try leftmost.receive(deadline: deadline), numberOfWhispers + 1) }) } } } @@ -324,23 +271,20 @@ public class ChannelTests : XCTestCase { extension ChannelTests { public static var allTests: [(String, (ChannelTests) -> () throws -> Void)] { return [ - ("testCreationOnCanceledCoroutine", testCreationOnCanceledCoroutine), - ("testSendOnCanceledCoroutine", testSendOnCanceledCoroutine), - ("testSendOnDoneChannel", testSendOnDoneChannel), + ("testSendOnCloseChannel", testSendOnCloseChannel), ("testSendTimeout", testSendTimeout), - ("testReceiveOnCanceledCoroutine", testReceiveOnCanceledCoroutine), - ("testReceiveOnDoneChannel", testReceiveOnDoneChannel), + ("testReceiveOnCloseChannel", testReceiveOnCloseChannel), ("testReceiveTimeout", testReceiveTimeout), ("testReceiverWaitsForSender", testReceiverWaitsForSender), ("testSenderWaitsForReceiver", testSenderWaitsForReceiver), ("testSendingChannel", testSendingChannel), - ("testDoneOnDoneSendingChannel", testDoneOnDoneSendingChannel), + ("testCloseOnClosedSendingChannel", testCloseOnClosedSendingChannel), ("testReceivingChannel", testReceivingChannel), ("testTwoSimultaneousSenders", testTwoSimultaneousSenders), ("testTwoSimultaneousReceivers", testTwoSimultaneousReceivers), ("testTypedChannels", testTypedChannels), - ("testDoneChannelUnblocks", testDoneChannelUnblocks), - ("testTenThousandWhispers", testTenThousandWhispers), + ("testCloseChannelUnblocks", testCloseChannelUnblocks), + ("testOneThousandWhispers", testOneThousandWhispers), ] } } diff --git a/Tests/VeniceTests/Venice/CoroutineTests.swift b/Tests/VeniceTests/Venice/CoroutineTests.swift index 5db9b01..c11c85c 100644 --- a/Tests/VeniceTests/Venice/CoroutineTests.swift +++ b/Tests/VeniceTests/Venice/CoroutineTests.swift @@ -6,112 +6,76 @@ import XCTest @testable import Venice +import CLibdill public class CoroutineTests : XCTestCase { func testCoroutine() throws { var sum = 0 - func add(number: Int, count: Int) throws { + func add(number: Int, count: Int) { for _ in 0 ..< count { sum += number - try Coroutine.yield() + Coroutine.yield() } } - let coroutine1 = try Coroutine { - try add(number: 7, count: 3) + Coroutine.run { + add(number: 7, count: 3) } - let coroutine2 = try Coroutine { - try add(number: 11, count: 1) + Coroutine.run { + add(number: 11, count: 1) } - let coroutine3 = try Coroutine { - try add(number: 5, count: 2) + Coroutine.run { + add(number: 5, count: 2) } - try Coroutine.wakeUp(100.milliseconds.fromNow()) + Coroutine.wakeUp(at: 100.milliseconds.fromNow()) XCTAssertEqual(sum, 42) - - coroutine1.cancel() - coroutine2.cancel() - coroutine3.cancel() - } - - func testCoroutineOnCanceledCoroutine() throws { - let coroutine = try Coroutine { - try Coroutine.yield() - XCTAssertThrowsError(try Coroutine(body: {}), error: VeniceError.canceledCoroutine) - } - - coroutine.cancel() - } - - func testThrowOnCoroutine() throws { - let coroutine = try Coroutine { - struct NiceError : Error, CustomStringConvertible { - let description: String - } - - throw NiceError(description: "NICEā„¢") - } - - coroutine.cancel() - } - - func testYiedOnCanceledCoroutine() throws { - let coroutine = try Coroutine { - try Coroutine.yield() - XCTAssertThrowsError(try Coroutine.yield(), error: VeniceError.canceledCoroutine) - } - - coroutine.cancel() } func testWakeUp() throws { let deadline = 100.milliseconds.fromNow() - try Coroutine.wakeUp(deadline) + Coroutine.wakeUp(at: deadline) let difference = Deadline.now().value - deadline.value XCTAssert(difference > -100.milliseconds.value && difference < 100.milliseconds.value) } - func testWakeUpOnCanceledCoroutine() throws { - let coroutine = try Coroutine { - XCTAssertThrowsError( - try Coroutine.wakeUp(100.milliseconds.fromNow()), - error: VeniceError.canceledCoroutine - ) - } - - coroutine.cancel() - } - func testWakeUpWithChannels() throws { - let channel = try Channel() - let group = Coroutine.Group() + let channel = Channel() func send(_ value: Int, after delay: Duration) throws { - try Coroutine.wakeUp(delay.fromNow()) + Coroutine.wakeUp(at: delay.fromNow()) try channel.send(value, deadline: .never) } - try group.addCoroutine(body: { try send(111, after: 30.milliseconds) }) - try group.addCoroutine(body: { try send(222, after: 40.milliseconds) }) - try group.addCoroutine(body: { try send(333, after: 10.milliseconds) }) - try group.addCoroutine(body: { try send(444, after: 20.milliseconds) }) + let runs = [(111, 30), (222, 40), (333, 10), (444, 20)] + for run in runs { + Coroutine.run { + XCTAssertThrowsNoError({ try send(run.0, after: run.1.milliseconds) }) + } + } XCTAssert(try channel.receive(deadline: .never) == 333) XCTAssert(try channel.receive(deadline: .never) == 444) XCTAssert(try channel.receive(deadline: .never) == 111) XCTAssert(try channel.receive(deadline: .never) == 222) + } + + func testSleep() throws { + let duration = 100.milliseconds + let expected = duration.fromNow() + Coroutine.sleep(for: duration) + let difference = Deadline.now().value - expected.value - group.cancel() + XCTAssert(difference > -100 && difference < 100) } - + func testReadWriteFileDescriptor() throws { let deadline = 1.second.fromNow() let (socket1, socket2) = try createSocketPair() - + let socket1Buffer = UnsafeMutableRawBufferPointer.allocate(count: 1) let socket2Buffer = UnsafeMutableRawBufferPointer.allocate(count: 1) @@ -119,9 +83,9 @@ public class CoroutineTests : XCTestCase { socket1Buffer.deallocate() socket2Buffer.deallocate() } - + var read: UnsafeRawBufferPointer - + socket1Buffer[0] = 42 socket2Buffer[0] = 0 try socket1.write(UnsafeRawBufferPointer(socket1Buffer), deadline: deadline) @@ -129,7 +93,7 @@ public class CoroutineTests : XCTestCase { XCTAssertEqual(read[0], 42) XCTAssertEqual(socket1Buffer[0], 42) XCTAssertEqual(socket2Buffer[0], 42) - + socket1Buffer[0] = 0 socket2Buffer[0] = 69 try socket2.write(UnsafeRawBufferPointer(socket2Buffer), deadline: deadline) @@ -140,65 +104,31 @@ public class CoroutineTests : XCTestCase { } func testInvalidFileDescriptor() throws { - XCTAssertThrowsError(try FileDescriptor(-1), error: VeniceError.invalidFileDescriptor) + XCTAssertThrowsError({ try FileDescriptor(-1) }, error: VeniceError.invalidFileDescriptor) } - func testPollOnCanceledCoroutine() throws { - let (socket1, _) = try createSocketPair() - - let coroutine = try Coroutine { - XCTAssertThrowsError( - try FileDescriptor.poll(socket1.handle, event: .read, deadline: .never), - error: VeniceError.canceledCoroutine - ) - } - - coroutine.cancel() - } - - func testFileDescriptorBlockedInAnotherCoroutine() throws { - let (socket1, _) = try createSocketPair() - - let coroutine1 = try Coroutine { - XCTAssertThrowsError( - try FileDescriptor.poll(socket1.handle, event: .read, deadline: .never), - error: VeniceError.canceledCoroutine - ) - } - - let coroutine2 = try Coroutine { - XCTAssertThrowsError( - try FileDescriptor.poll(socket1.handle, event: .read, deadline: .never), - error: VeniceError.fileDescriptorBlockedInAnotherCoroutine - ) - } - - coroutine1.cancel() - coroutine2.cancel() - } - func testDetachFileDescriptor() throws { var sockets = [Int32](repeating: 0, count: 2) - + #if os(Linux) let result = socketpair(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0, &sockets) #else let result = socketpair(AF_UNIX, SOCK_STREAM, 0, &sockets) #endif - + XCTAssert(result == 0) - + let fileDescriptor = try FileDescriptor(sockets[0]) let socket = try fileDescriptor.detach() XCTAssertEqual(socket, sockets[0]) XCTAssertEqual(fileDescriptor.handle, -1) - + XCTAssertThrowsError( - try FileDescriptor.poll(fileDescriptor.handle, event: .read, deadline: .never), + { try FileDescriptor.poll(fileDescriptor.handle, event: .read, deadline: .never) }, error: VeniceError.invalidFileDescriptor ) } - + func testStandardStreams() { let input = FileDescriptor.standardInput let output = FileDescriptor.standardOutput @@ -220,8 +150,8 @@ public class CoroutineTests : XCTestCase { let socketPair = try createSocketPair() let buf = UnsafeMutableRawBufferPointer.allocate(count: Int(BUFSIZ)) XCTAssertThrowsError( - try socketPair.0.read(buf, deadline: 1.second.fromNow()), - error: VeniceError.deadlineReached + { try socketPair.0.read(buf, deadline: 1.second.fromNow()) }, + error: VeniceError.deadlineReached ) } @@ -236,8 +166,8 @@ public class CoroutineTests : XCTestCase { mutableBuf[0] = 42 let buf = UnsafeRawBufferPointer(mutableBuf) XCTAssertThrowsError( - try fildes.write(buf, deadline: 1.second.fromNow()), - error: VeniceError.writeFailed + { try fildes.write(buf, deadline: 1.second.fromNow()) }, + error: VeniceError.writeFailed ) } } @@ -259,16 +189,10 @@ extension CoroutineTests { public static var allTests: [(String, (CoroutineTests) -> () throws -> Void)] { return [ ("testCoroutine", testCoroutine), - ("testCoroutineOnCanceledCoroutine", testCoroutineOnCanceledCoroutine), - ("testThrowOnCoroutine", testThrowOnCoroutine), - ("testYiedOnCanceledCoroutine", testYiedOnCanceledCoroutine), ("testWakeUp", testWakeUp), - ("testWakeUpOnCanceledCoroutine", testWakeUpOnCanceledCoroutine), ("testWakeUpWithChannels", testWakeUpWithChannels), ("testReadWriteFileDescriptor", testReadWriteFileDescriptor), ("testInvalidFileDescriptor", testInvalidFileDescriptor), - ("testPollOnCanceledCoroutine", testPollOnCanceledCoroutine), - ("testFileDescriptorBlockedInAnotherCoroutine", testFileDescriptorBlockedInAnotherCoroutine), ("testDetachFileDescriptor", testDetachFileDescriptor), ("testStandardStreams", testStandardStreams), ("testReadUsingEmptyBuffer", testReadUsingEmptyBuffer),