diff options
Diffstat (limited to 'common')
| -rw-r--r-- | common/hash.c | 380 | ||||
| -rw-r--r-- | common/hash.h | 151 | ||||
| -rw-r--r-- | common/server-mainloop.c | 424 | ||||
| -rw-r--r-- | common/server-mainloop.h | 27 | ||||
| -rw-r--r-- | common/sock-any.c | 385 | ||||
| -rw-r--r-- | common/sock-any.h | 90 | ||||
| -rw-r--r-- | common/stringx.c | 155 | ||||
| -rw-r--r-- | common/stringx.h | 53 | 
8 files changed, 1665 insertions, 0 deletions
| diff --git a/common/hash.c b/common/hash.c new file mode 100644 index 0000000..789e85c --- /dev/null +++ b/common/hash.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *     * Redistributions of source code must retain the above + *       copyright notice, this list of conditions and the + *       following disclaimer. + *     * Redistributions in binary form must reproduce the + *       above copyright notice, this list of conditions and + *       the following disclaimer in the documentation and/or + *       other materials provided with the distribution. + *     * The names of contributors to this software may not be + *       used to endorse or promote products derived from this + *       software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Originally from apache 2.0 + * Modifications for general use by <nielsen@memberwebs.com> + */ + +/* Copyright 2000-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *         http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/types.h> +#include <stdlib.h> +#include "hash.h" + +#define KEY_DATA(he)    ((he)->key) + +/* + * The internal form of a hash table. + * + * The table is an array indexed by the hash of the key; collisions + * are resolved by hanging a linked list of hash entries off each + * element of the array. Although this is a really simple design it + * isn't too bad given that pools have a low allocation overhead. + */ + +typedef struct hsh_entry_t hsh_entry_t; + +struct hsh_entry_t +{ +    hsh_entry_t* next; +    unsigned int hash; +    const void* key; +    size_t klen; +    const void* val; +}; + +/* + * Data structure for iterating through a hash table. + * + * We keep a pointer to the next hash entry here to allow the current + * hash entry to be freed or otherwise mangled between calls to + * hsh_next(). + */ +struct hsh_index_t +{ +    hsh_t* ht; +    hsh_entry_t* ths; +    hsh_entry_t* next; +    unsigned int index; +}; + +/* + * The size of the array is always a power of two. We use the maximum + * index rather than the size so that we can use bitwise-AND for + * modular arithmetic. + * The count of hash entries may be greater depending on the chosen + * collision rate. + */ +struct hsh_t +{ +    hsh_entry_t** array; +    hsh_index_t iterator;    /* For hsh_first(...) */ +    unsigned int count; +    unsigned int max; +}; + + +#define INITIAL_MAX 15 /* tunable == 2^n - 1 */ +#define int_malloc malloc +#define int_calloc calloc +#define int_free free + +/* + * Hash creation functions. + */ + +static hsh_entry_t** alloc_array(hsh_t* ht, unsigned int max) +{ +    return (hsh_entry_t**)int_calloc(sizeof(*(ht->array)), (max + 1)); +} + +hsh_t* hsh_create() +{ +    hsh_t* ht = int_malloc(sizeof(hsh_t)); +    if(ht) +    { +        ht->count = 0; +        ht->max = INITIAL_MAX; +        ht->array = alloc_array(ht, ht->max); +        if(!ht->array) +        { +            int_free(ht); +            return NULL; +        } +    } +    return ht; +} + +void hsh_free(hsh_t* ht) +{ +    hsh_index_t* hi; + +    for(hi = hsh_first(ht); hi; hi = hsh_next(hi)) +        int_free(hi->ths); + +    if(ht->array) +        int_free(ht->array); + +    int_free(ht); +} + +/* + * Hash iteration functions. + */ + +hsh_index_t* hsh_next(hsh_index_t* hi) +{ +    hi->ths = hi->next; +    while(!hi->ths) +    { +        if(hi->index > hi->ht->max) +            return NULL; + +        hi->ths = hi->ht->array[hi->index++]; +    } +    hi->next = hi->ths->next; +    return hi; +} + +hsh_index_t* hsh_first(hsh_t* ht) +{ +    hsh_index_t* hi = &ht->iterator; + +    hi->ht = ht; +    hi->index = 0; +    hi->ths = NULL; +    hi->next = NULL; +    return hsh_next(hi); +} + +void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen) +{ +    if(key) +        *key = KEY_DATA(hi->ths); +    if(klen) +        *klen = hi->ths->klen; +    return (void*)hi->ths->val; +} + + +/* + * Expanding a hash table + */ + +static int expand_array(hsh_t* ht) +{ +    hsh_index_t* hi; +    hsh_entry_t** new_array; +    unsigned int new_max; + +    new_max = ht->max * 2 + 1; +    new_array = alloc_array(ht, new_max); + +    if(!new_array) +        return 0; + +    for(hi = hsh_first(ht); hi; hi = hsh_next(hi)) +    { +        unsigned int i = hi->ths->hash & new_max; +        hi->ths->next = new_array[i]; +        new_array[i] = hi->ths; +    } + +    if(ht->array) +        free(ht->array); + +    ht->array = new_array; +    ht->max = new_max; +    return 1; +} + +/* + * This is where we keep the details of the hash function and control + * the maximum collision rate. + * + * If val is non-NULL it creates and initializes a new hash entry if + * there isn't already one there; it returns an updatable pointer so + * that hash entries can be removed. + */ + +static hsh_entry_t** find_entry(hsh_t* ht, const void* key, size_t klen, const void* val) +{ +    hsh_entry_t** hep; +    hsh_entry_t* he; +    const unsigned char* p; +    unsigned int hash; +    size_t i; + +    /* +     * This is the popular `times 33' hash algorithm which is used by +     * perl and also appears in Berkeley DB. This is one of the best +     * known hash functions for strings because it is both computed +     * very fast and distributes very well. +     * +     * The originator may be Dan Bernstein but the code in Berkeley DB +     * cites Chris Torek as the source. The best citation I have found +     * is "Chris Torek, Hash function for text in C, Usenet message +     * <27038@mimsy.umd.edu> in comp.lang.c , October, 1990." in Rich +     * Salz's USENIX 1992 paper about INN which can be found at +     * <http://citeseer.nj.nec.com/salz92internetnews.html>. +     * +     * The magic of number 33, i.e. why it works better than many other +     * constants, prime or not, has never been adequately explained by +     * anyone. So I try an explanation: if one experimentally tests all +     * multipliers between 1 and 256 (as I did while writing a low-level +     * data structure library some time ago) one detects that even +     * numbers are not useable at all. The remaining 128 odd numbers +     * (except for the number 1) work more or less all equally well. +     * They all distribute in an acceptable way and this way fill a hash +     * table with an average percent of approx. 86%. +     * +     * If one compares the chi^2 values of the variants (see +     * Bob Jenkins ``Hashing Frequently Asked Questions'' at +     * http://burtleburtle.net/bob/hash/hashfaq.html for a description +     * of chi^2), the number 33 not even has the best value. But the +     * number 33 and a few other equally good numbers like 17, 31, 63, +     * 127 and 129 have nevertheless a great advantage to the remaining +     * numbers in the large set of possible multipliers: their multiply +     * operation can be replaced by a faster operation based on just one +     * shift plus either a single addition or subtraction operation. And +     * because a hash function has to both distribute good _and_ has to +     * be very fast to compute, those few numbers should be preferred. +     * +     *                        -- Ralf S. Engelschall <rse@engelschall.com> +     */ +    hash = 0; + +    if(klen == HSH_KEY_STRING) +    { +        for(p = key; *p; p++) +            hash = hash * 33 + *p; + +        klen = p - (const unsigned char *)key; +    } +    else +    { +        for(p = key, i = klen; i; i--, p++) +            hash = hash * 33 + *p; +    } + +    /* scan linked list */ +    for(hep = &ht->array[hash & ht->max], he = *hep; +            he; hep = &he->next, he = *hep) +    { +     if(he->hash == hash && +        he->klen == klen && +        memcmp(KEY_DATA(he), key, klen) == 0) +         break; +    } + +    if(he || !val) +        return hep; + +    /* add a new entry for non-NULL val */ +    he = int_malloc(sizeof(*he)); + +    if(he) +    { +        /* Key points to external data */ +        he->key = key; +        he->klen = klen; + +        he->next = NULL; +        he->hash = hash; +        he->val    = val; + +        *hep = he; +        ht->count++; +    } + +    return hep; +} + +void* hsh_get(hsh_t* ht, const void *key, size_t klen) +{ +        hsh_entry_t** he = find_entry(ht, key, klen, NULL); + +        if(he && *he) +            return (void*)((*he)->val); +        else +            return NULL; +} + +int hsh_set(hsh_t* ht, const void* key, size_t klen, void* val) +{ +    hsh_entry_t** hep = find_entry(ht, key, klen, val); + +    if(hep && *hep) +    { +        /* replace entry */ +        (*hep)->val = val; + +        /* check that the collision rate isn't too high */ +        if(ht->count > ht->max) +        { +            if(!expand_array(ht)) +                return 0; +        } + +        return 1; +    } + +    return 0; +} + +void* hsh_rem(hsh_t* ht, const void* key, size_t klen) +{ +    hsh_entry_t** hep = find_entry(ht, key, klen, NULL); +    void* val = NULL; + +    if(hep && *hep) +    { +        hsh_entry_t* old = *hep; +        *hep = (*hep)->next; +        --ht->count; +        val = (void*)old->val; +        free(old); +    } + +    return val; +} + +unsigned int hsh_count(hsh_t* ht) +{ +    return ht->count; +} + diff --git a/common/hash.h b/common/hash.h new file mode 100644 index 0000000..df34a7a --- /dev/null +++ b/common/hash.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *     * Redistributions of source code must retain the above + *       copyright notice, this list of conditions and the + *       following disclaimer. + *     * Redistributions in binary form must reproduce the + *       above copyright notice, this list of conditions and + *       the following disclaimer in the documentation and/or + *       other materials provided with the distribution. + *     * The names of contributors to this software may not be + *       used to endorse or promote products derived from this + *       software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Originally from apache 2.0 + * Modifications for general use by <nielsen@memberwebs.com> + */ + +/* Copyright 2000-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *     http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __HSH_H__ +#define __HSH_H__ + +/* + * OPTIONAL FEATURES + * + * Features to define. You need to build both this file and + * the corresponding hash.c file with whatever options you set here. + * These affect the method signatures, so see the sections below + * for the actual options + */ + +/* + * ARGUMENT DOCUMENTATION + * + * ht: The hashtable + * key: Pointer to the key value + * klen: The length of the key + * val: Pointer to the value + * hi: A hashtable iterator + * stamp: A unix timestamp + */ + + +/* ---------------------------------------------------------------------------------- + * TYPES + */ + +/* Abstract type for hash tables. */ +typedef struct hsh_t hsh_t; + +/* Abstract type for scanning hash tables.  */ +typedef struct hsh_index_t hsh_index_t; + +/* ----------------------------------------------------------------------------- + * MAIN + */ + +/* + * hsh_create : Create a hash table + * - returns an allocated hashtable + */ +hsh_t* hsh_create(); + +/* + * hsh_free : Free a hash table + */ +void hsh_free(hsh_t* ht); + +/* + * hsh_count: Number of values in hash table + * - returns the number of entries in hash table + */ +unsigned int hsh_count(hsh_t* ht); + +/* + * hsh_get: Retrieves a value from the hash table + * - returns the value of the entry + */ +void* hsh_get(hsh_t* ht, const void* key, size_t klen); + +/* + * hsh_set: Set a value in the hash table + * - returns 1 if the entry was added properly + */ +int hsh_set(hsh_t* ht, const void* key, size_t klen, void* val); + +/* + * hsh_rem: Remove a value from the hash table + * - returns the value of the removed entry + */ +void* hsh_rem(hsh_t* ht, const void* key, size_t klen); + +/* + * hsh_first: Start enumerating through the hash table + * - returns a hash iterator + */ +hsh_index_t* hsh_first(hsh_t* ht); + +/* + * hsh_next: Enumerate through hash table + * - returns the hash iterator or null when no more entries + */ +hsh_index_t* hsh_next(hsh_index_t* hi); + +/* + * hsh_this: While enumerating get current value + * - returns the value that the iterator currently points to + */ +void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen); + +/* + * This can be passed as 'klen' in any of the above functions to indicate + * a string-valued key, and have hash compute the length automatically. + */ +#define HSH_KEY_STRING     (-1) + +#endif  /* __HSH_H__ */ diff --git a/common/server-mainloop.c b/common/server-mainloop.c new file mode 100644 index 0000000..07f02b2 --- /dev/null +++ b/common/server-mainloop.c @@ -0,0 +1,424 @@ + +#include "usuals.h" +#include <errno.h> +#include <sys/time.h> + +#include "server-mainloop.h" + +typedef struct _socket_callback +{ +    int fd; +    server_socket_callback callback; +    void* arg; + +    struct _socket_callback* next; +} +socket_callback; + +typedef struct _timer_callback +{ +    struct timeval at; +    struct timeval interval; +    server_timer_callback callback; +    void* arg; + +    struct _timer_callback* next; +} +timer_callback; + +typedef struct _server_context +{ +    int stopped; +    fd_set read_fds; +    fd_set write_fds; +    int max_fd; +    socket_callback* callbacks; +    timer_callback* timers; +} +server_context; + +/* Global context */ +static server_context ctx; + +static void +timeval_add(struct timeval* t1, struct timeval* t2) +{ +    ASSERT(t1->tv_usec < 1000000); +    ASSERT(t2->tv_usec < 1000000); + +    t1->tv_sec += t2->tv_sec; +    t1->tv_usec += t2->tv_usec; +    if(t1->tv_usec >= 1000000) +    { +        t1->tv_usec -= 1000000; +        t1->tv_sec += 1; +    } +} + +static void +timeval_subtract(struct timeval* t1, struct timeval* t2) +{ +    ASSERT(t1->tv_usec < 1000000); +    ASSERT(t2->tv_usec < 1000000); + +    t1->tv_sec -= t2->tv_sec; +    if(t1->tv_usec < t2->tv_usec) +    { +        t1->tv_usec += 1000000; +        t1->tv_sec -= 1; +    } +    t1->tv_usec -= t2->tv_usec; +} + +static int +timeval_compare(struct timeval* t1, struct timeval* t2) +{ +    ASSERT(t1->tv_usec < 1000000); +    ASSERT(t2->tv_usec < 1000000); + +    if(t1->tv_sec > t2->tv_sec) +        return 1; +    else if(t1->tv_sec < t2->tv_sec) +        return -1; +    else +    { +        if(t1->tv_usec > t2->tv_usec) +            return 1; +        else if(t1->tv_usec < t2->tv_usec) +            return -1; +        else +            return 0; +    } +} + +#define timeval_empty(tv) \ +    ((tv)->tv_sec == 0 && (tv)->tv_usec == 0) + +#define timeval_to_ms(tv) \ +    ((((uint64_t)(tv).tv_sec) * 1000L) + (((uint64_t)(tv).tv_usec) / 1000L)) + +static int +timeval_dump(struct timeval* tv) +{ +    fprintf(stderr, "{ %d:%d }", tv->tv_sec, tv->tv_usec / 1000); +} + +static int +add_timer(int ms, int oneshot, server_timer_callback callback, void* arg) +{ +    struct timeval interval; +    timer_callback* cb; +    int i; + +    ASSERT(ms > 0); +    ASSERT(callback != NULL); + +    interval.tv_sec = ms / 1000; +    interval.tv_usec = (ms % 1000) * 1000; /* into micro seconds */ + +    cb = (timer_callback*)calloc(1, sizeof(*cb)); +    if(!cb) +    { +        errno = ENOMEM; +        return -1; +    } + +    if(gettimeofday(&(cb->at), NULL) == -1) +    { +        free(cb); +        return -1; +    } + +    timeval_add(&(cb->at), &interval); + +    if (oneshot) +        memset(&(cb->interval), 0, sizeof(cb->interval)); +    else +        memcpy(&(cb->interval), &interval, sizeof(cb->interval)); + +    cb->callback = callback; +    cb->arg = arg; + +    cb->next = ctx.timers; +    ctx.timers = cb; + +    return 0; +} + +static timer_callback* +remove_timer(timer_callback* timcb) +{ +    timer_callback* cb; +    timer_callback* next; +    int i; + +    if(!ctx.timers) +        return; + +    /* First in list */; +    if(ctx.timers == timcb) +    { +        cb = ctx.timers; +        ctx.timers = ctx.timers->next; +        free(cb); +        return ctx.timers; +    } + +    /* One ahead processing of rest */ +    for(cb = ctx.timers; cb->next; cb = cb->next) +    { +        if(cb->next == timcb) +        { +            next = cb->next->next; +            free(cb->next); +            cb->next = next; +            return cb->next; +        } +    } +} + +void +server_init() +{ +    memset(&ctx, 0, sizeof (ctx)); +    FD_ZERO(&ctx.read_fds); +    FD_ZERO(&ctx.write_fds); + +    ctx.max_fd = -1; +    ctx.stopped = 1; +    ctx.callbacks = NULL; +    ctx.timers = NULL; +} + +void +server_uninit() +{ +    timer_callback* timcb; +    timer_callback* timn; +    socket_callback* sockcb; +    socket_callback* sockn; + +    for(timcb = ctx.timers; timcb; timcb = timn) +    { +        timn = timcb->next; +        free(timcb); +    } + +    ctx.timers = NULL; + +    for(sockcb = ctx.callbacks; sockcb; sockcb = sockn) +    { +        sockn = sockcb->next; +        free(sockcb); +    } + +    ctx.timers = NULL; +} + +uint64_t +server_get_time() +{ +    struct timeval tv; +    if(gettimeofday(&tv, NULL) == -1) +        return 0L; +    return timeval_to_ms(tv); +} + +int +server_run() +{ +    struct timeval* timeout; +    struct timeval tv, current; +    timer_callback* timcb; +    socket_callback* sockcb; +    fd_set rfds, wfds; +    int r, i; + +    /* No watches have been set */ +    ASSERT(ctx.max_fd > -1); + +    ctx.stopped = 0; + +    while(!ctx.stopped) +    { +        /* Watch for the various fds */ +        memcpy(&rfds, &ctx.read_fds, sizeof(rfds)); +        memcpy(&wfds, &ctx.write_fds, sizeof(wfds)); + +        /* Prepare for timers */ +        timeout = NULL; +        if(gettimeofday(¤t, NULL) == -1) +            return -1; + +        /* Cycle through timers */ +        for(timcb = ctx.timers; timcb; ) +        { +            ASSERT(timcb->callback); + +            /* Call any timers that have already passed */ +            if(timeval_compare(¤t, &timcb->at) >= 0) +            { +                /* Convert to milliseconds, and make the call */ +                r = (timcb->callback)(timeval_to_ms(current), timcb->arg); + +                /* Reset timer if so desired */ +                if (r == 1 && !timeval_empty(&timcb->interval)) +                { +                    timeval_add(&timcb->at, &timcb->interval); + +                    /* If the time has already passed, just use current time */ +                    if(timeval_compare(&(timcb->at), ¤t) <= 0) +                        memcpy(&(timcb->at), ¤t, sizeof(timcb->at)); +                } + +                /* Otherwise remove it. Either one shot, or returned 0 */ +                else +                { +                    timcb = remove_timer(timcb); +                    continue; +                } +            } + +            /* Get soonest timer */ +            if (!timeout || timeval_compare(timeout, &timcb->at) < 0) +                timeout = &timcb->at; + +            timcb = timcb->next; +        } + +        /* Convert to an offset */ +        if(timeout) +        { +            memcpy(&tv, timeout, sizeof(tv)); +            timeout = &tv; +            timeval_subtract(timeout, ¤t); +        } + +        /* fprintf(stderr, "selecting with timeout: "); +           timeval_dump(timeout); +           fprintf(stderr, "\n"); */ + +        r = select(ctx.max_fd, &rfds, &wfds, NULL, timeout); +        if (r < 0) +        { +            /* Interrupted so try again, and possibly exit */ +            if (errno == EINTR) +                continue; + +            /* Programmer errors */ +            ASSERT (errno != EBADF); +            ASSERT (errno != EINVAL); +            return r; +        } + +        /* Timeout, just jump to timeout processing */ +        if(r == 0) +            continue; + +        for(sockcb = ctx.callbacks; sockcb; sockcb = sockcb->next) +        { +            ASSERT(sockcb->fd != -1); + +            /* Call any that are set */ +            if (FD_ISSET(sockcb->fd, &rfds)) +                (sockcb->callback)(sockcb->fd, SERVER_READ, sockcb->arg); +            if (FD_ISSET(sockcb->fd, &wfds)) +                (sockcb->callback)(sockcb->fd, SERVER_WRITE, sockcb->arg); +        } +    } + +    return 0; +} + +void +server_stop() +{ +    ctx.stopped = 1; +} + +int +server_stopped() +{ +    return ctx.stopped; +} + +int +server_watch(int fd, int type, server_socket_callback callback, void* arg) +{ +    socket_callback* cb; +    int i; +    ASSERT(type != 0); +    ASSERT(fd != -1); +    ASSERT(callback != NULL); + +    cb = (socket_callback*)calloc(sizeof(*cb), 1); +    if(!cb) +    { +        errno = ENOMEM; +        return -1; +    } + +    cb->fd = fd; +    cb->callback = callback; +    cb->arg = arg; + +    cb->next = ctx.callbacks; +    ctx.callbacks = cb; + +    if (type & SERVER_READ) +        FD_SET(fd, &ctx.read_fds); +    if (type & SERVER_WRITE) +        FD_SET(fd, &ctx.write_fds); + +    if(fd >= ctx.max_fd) +        ctx.max_fd = fd + 1; + +    return 0; +} + +void +server_unwatch(int fd) +{ +    socket_callback* cb; +    int i; + +    ASSERT(fd != -1); + +    FD_CLR(fd, &ctx.read_fds); +    FD_CLR(fd, &ctx.write_fds); + +    if(!ctx.callbacks) +        return; + +    /* First in list */; +    if(ctx.callbacks->fd == fd) +    { +        cb = ctx.callbacks; +        ctx.callbacks = cb->next; +        free(cb); +        return; +    } + +    /* One ahead processing of rest */ +    for(cb = ctx.callbacks; cb->next; cb = cb->next) +    { +        if(cb->next->fd == fd) +        { +            cb->next = cb->next->next; +            free(cb->next); +            return; +        } +    } +} + +int +server_timer(int ms, server_timer_callback callback, void* arg) +{ +    return add_timer(ms, 0, callback, arg); +} + +int +server_oneshot(int ms, server_timer_callback callback, void* arg) +{ +    return add_timer(ms, 1, callback, arg); +} diff --git a/common/server-mainloop.h b/common/server-mainloop.h new file mode 100644 index 0000000..ceff28d --- /dev/null +++ b/common/server-mainloop.h @@ -0,0 +1,27 @@ + +#ifndef __SERVER_MAINLOOP_H__ +#define __SERVER_MAINLOOP_H__ + +#include <stdint.h> + +/* TODO: Prefix functions with svr */ + +#define SERVER_READ       0x01 +#define SERVER_WRITE      0x02 + +typedef void (*server_socket_callback)(int fd, int type, void* arg); +/* TODO: We should declare our own time type: 'mstime' */ +typedef int (*server_timer_callback)(uint64_t when, void* arg); + +void    server_init(); +void    server_uninit(); +int     server_run(); +void    server_stop(); +int     server_stopped(); +int     server_watch(int fd, int type, server_socket_callback callback, void* arg); +void    server_unwatch(int fd); +int     server_timer(int length, server_timer_callback callback, void* arg); +int     server_oneshot(int length, server_timer_callback callback, void* arg); +uint64_t server_get_time(); + +#endif /* __SERVER_MAINLOOP_H__ */ diff --git a/common/sock-any.c b/common/sock-any.c new file mode 100644 index 0000000..1938d5d --- /dev/null +++ b/common/sock-any.c @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *     * Redistributions of source code must retain the above + *       copyright notice, this list of conditions and the + *       following disclaimer. + *     * Redistributions in binary form must reproduce the + *       above copyright notice, this list of conditions and + *       the following disclaimer in the documentation and/or + *       other materials provided with the distribution. + *     * The names of contributors to this software may not be + *       used to endorse or promote products derived from this + *       software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + *  Nate Nielsen <nielsen@memberwebs.com> + * + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <stdio.h> + +#include "sock-any.h" + +#include <arpa/inet.h> + +#define LOCALHOST_ADDR  0x7F000001 + +int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts) +{ +  size_t l; +  char buf[256]; +  char* t; +  char* t2; +  int defport = (opts & 0xFFFF); + +  memset(any, 0, sizeof(*any)); + +  /* Just a port? */ +  do +  { +    #define PORT_CHARS "0123456789" +    #define PORT_MIN 1 +    #define PORT_MAX 5 + +    int port = 0; + +    l = strspn(addr, PORT_CHARS); +    if(l < PORT_MIN || l > PORT_MAX || addr[l] != 0) +      break; + +    port = strtol(addr, &t2, 10); +    if(*t2 || port <= 0 || port >= 65536) +      break; + +    any->s.in.sin_port = htons(port); + +    /* Fill in the type based on defaults */ +#ifdef HAVE_INET6 +    if(opts & SANY_OPT_DEFINET6) +        any->s.in.sin_family = AF_INET6; +    else +#endif +        any->s.in.sin_family = AF_INET; + +    /* Fill in the address based on defaults */ +    if(opts & SANY_OPT_DEFLOCAL) +    { +#ifdef HAVE_INET6 +        if(opts & SANY_OPT_DEFINET6) +            memcpy(&(any->s.in.sin6_addr), &in6addr_loopback, sizeof(struct in6_addr)); +        else +#endif +            any->s.in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); +    } + +    /* +     * Note the 'any' option is the default since we zero out +     * the entire structure above. +     */ + +    any->namelen = sizeof(any->s.in); +    return AF_INET; +  } +  while(0); + +  /* Look and see if we can parse an ipv4 address */ +  do +  { +    #define IPV4_PORT_CHARS +    #define IPV4_CHARS  "0123456789." +    #define IPV4_MIN    3 +    #define IPV4_MAX    21 + +    int port = 0; +    t = NULL; + +    l = strlen(addr); +    if(l < IPV4_MIN || l > IPV4_MAX) +      break; + +    strcpy(buf, addr); + +    /* Find the last set that contains just numbers */ +    l = strspn(buf, IPV4_CHARS); +    if(l < IPV4_MIN) +      break; + +    /* Either end of string or port */ +    if(buf[l] != 0 && buf[l] != ':') +      break; + +    /* Get the port out */ +    if(buf[l] != 0) +    { +      t = buf + l + 1; +      buf[l] = 0; +    } + +    if(t) +    { +      port = strtol(t, &t2, 10); +      if(*t2 || port <= 0 || port >= 65536) +        break; +    } + +    any->s.in.sin_family = AF_INET; +    any->s.in.sin_port = htons((unsigned short)(port <= 0 ? defport : port)); + +    if(inet_pton(AF_INET, buf, &(any->s.in.sin_addr)) <= 0) +      break; + +    any->namelen = sizeof(any->s.in); +    return AF_INET; +  } +  while(0); + +#ifdef HAVE_INET6 +  do +  { +    #define IPV6_CHARS  "0123456789:" +    #define IPV6_MIN    3 +    #define IPV6_MAX    51 + +    int port = -1; +    t = NULL; + +    l = strlen(addr); +    if(l < IPV6_MIN || l > IPV6_MAX) +      break; + +    /* If it starts with a '[' then we can get port */ +    if(buf[0] == '[') +    { +      port = 0; +      addr++; +    } + +    strcpy(buf, addr); + +    /* Find the last set that contains just numbers */ +    l = strspn(buf, IPV6_CHARS); +    if(l < IPV6_MIN) +      break; + +    /* Either end of string or port */ +    if(buf[l] != 0) +    { +      /* If had bracket, then needs to end with a bracket */ +      if(port != 0 || buf[l] != ']') +        break; + +      /* Get the port out */ +      t = buf + l + 1; + +      if(*t = ':') +        t++; +    } + +    if(t) +    { +      port = strtol(t, &t, 10); +      if(*t || port <= 0 || port >= 65536) +        break; +    } + +    any->s.in6.sin6_family = AF_INET6; +    any->s.in6.sin6_port = htons((unsigned short)port <= 0 : defport : port); + +    if(inet_pton(AF_INET6, buf, &(any->s.in6.sin6_addr)) >= 0) +      break; + +    any->namelen = sizeof(any->s.in6); +    return AF_INET6; +  } +  while(0); +#endif + +  /* A unix socket path */ +  do +  { +    /* No colon and must have a path component */ +    if(strchr(addr, ':') || !strchr(addr, '/')) +      break; + +    l = strlen(addr); +    if(l >= sizeof(any->s.un.sun_path)) +      break; + +    any->s.un.sun_family = AF_UNIX; +    strcpy(any->s.un.sun_path, addr); + +    any->namelen = sizeof(any->s.un) - (sizeof(any->s.un.sun_path) - l); +    return AF_UNIX; +  } +  while(0); + +  /* A DNS name and a port? */ +  do +  { +    struct addrinfo* res; +    int port = 0; +    t = NULL; + +    l = strlen(addr); +    if(l >= 255 || !isalpha(addr[0])) +      break; + +    /* Some basic illegal character checks */ +    if(strcspn(addr, " /\\") != l) +      break; + +    strcpy(buf, addr); + +    /* Find the last set that contains just numbers */ +    t = strchr(buf, ':'); +    if(t) +    { +      *t = 0; +      t++; +    } + +    if(t) +    { +      port = strtol(t, &t2, 10); +      if(*t2 || port <= 0 || port >= 65536) +        break; +    } + +    /* Try and resolve the domain name */ +    if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res) +      break; + +    memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr)); +    any->namelen = res->ai_addrlen; +    freeaddrinfo(res); + +    port = htons((unsigned short)(port <= 0 ? defport : port)); + +    switch(any->s.a.sa_family) +    { +    case PF_INET: +      any->s.in.sin_port = port; +      break; +#ifdef HAVE_INET6 +    case PF_INET6: +      any->s.in6.sin6_port = port; +      break; +#endif +    }; + +    return any->s.a.sa_family; +  } +  while(0); + +  return -1; +} + +int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts) +{ +  int len = 0; +  int port = 0; + +  switch(any->s.a.sa_family) +  { +  case AF_UNIX: +    len = strlen(any->s.un.sun_path); +    if(addrlen < len + 1) +    { +      errno = ENOSPC; +      return -1; +    } + +    strcpy(addr, any->s.un.sun_path); +    break; + +  case AF_INET: +    if(inet_ntop(any->s.a.sa_family, &(any->s.in.sin_addr), addr, addrlen) == NULL) +      return -1; +    port = ntohs(any->s.in.sin_port); +    break; + +#ifdef HAVE_INET6 +  case AF_INET6: +    if(inet_ntop(any->s.a.sa_family, &(any->s.in6.sin6_addr), addr, addrlen) == NULL) +      return -1; +    port = ntohs(any->s.in6.sin6_port); +    break; +#endif + +  default: +    errno = EAFNOSUPPORT; +    return -1; +  } + +  if(!(opts & SANY_OPT_NOPORT) && port != 0) +  { +    strncat(addr, ":", addrlen); +    addr[addrlen - 1] = 0; + +    len = strlen(addr); +    addr += len; +    addrlen -= len; + +    snprintf(addr, addrlen, "%d", port); +  } + +  return 0; +} + +int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts) +{ +    if(a1->s.a.sa_family != a2->s.a.sa_family) +        return -1; + +    switch(a1->s.a.sa_family) +    { +    case AF_UNIX: +        return strcmp(a1->s.un.sun_path, a2->s.un.sun_path); + +    case AF_INET: +        if(memcmp(&(a1->s.in.sin_addr), &(a2->s.in.sin_addr), sizeof(a2->s.in.sin_addr)) != 0) +            return -1; +        if(!(opts && SANY_OPT_NOPORT) && a1->s.in.sin_port != a2->s.in.sin_port) +            return -1; +        return 0; +#ifdef HAVE_INET6 +    case AF_INET6: +        if(memcmp(&(a1->s.in6.sin6_addr), &(a2->s.in6.sin6_addr), sizeof(a2->s.in6.sin6_addr)) != 0) +            return -1; +        if(!(opts && SANY_OPT_NOPORT) && a1->s.in6.sin6_port != a2->s.in6.sin6_port) +            return -1; +        return 0; +#endif +    default: +        errno = EAFNOSUPPORT; +        return -1; +    } +} diff --git a/common/sock-any.h b/common/sock-any.h new file mode 100644 index 0000000..31cb13b --- /dev/null +++ b/common/sock-any.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *     * Redistributions of source code must retain the above + *       copyright notice, this list of conditions and the + *       following disclaimer. + *     * Redistributions in binary form must reproduce the + *       above copyright notice, this list of conditions and + *       the following disclaimer in the documentation and/or + *       other materials provided with the distribution. + *     * The names of contributors to this software may not be + *       used to endorse or promote products derived from this + *       software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + *  Nate Nielsen <nielsen@memberwebs.com> + * + */ + +#ifndef __SOCK_ANY_H__ +#define __SOCK_ANY_H__ + +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> + +struct sockaddr_any +{ +  union _sockaddr_any +  { +    /* The header */ +    struct sockaddr a; + +    /* The different types */ +    struct sockaddr_un un; +    struct sockaddr_in in; +#ifdef HAVE_INET6 +    struct sockaddr_in6 in6; +#endif +  } s; +  size_t namelen; +}; + +#define SANY_ADDR(any)  ((any).s.a) +#define SANY_LEN(any)   ((any).namelen) +#define SANY_TYPE(any)  ((any).s.a.sa_family) + +int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts); + +/* The default port to fill in when no IP/IPv6 port specified */ +#define SANY_OPT_DEFPORT(p)     (int)((p) & 0xFFFF) + +/* When only port specified default to IPANY */ +#define SANY_OPT_DEFANY         0x00000000 + +/* When only port specified default to LOCALHOST */ +#define SANY_OPT_DEFLOCAL       0x00100000 + +/* When only port specified default to IPv6 */ +#ifdef HAVE_INET6 +#define SANY_OPT_DEFINET6       0x00200000 +#endif + +int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts); + +/* Don't print or compare the port */ +#define SANY_OPT_NOPORT         0x01000000 + +int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts); + +#endif /* __SOCK_ANY_H__ */ diff --git a/common/stringx.c b/common/stringx.c new file mode 100644 index 0000000..37a3df9 --- /dev/null +++ b/common/stringx.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *     * Redistributions of source code must retain the above + *       copyright notice, this list of conditions and the + *       following disclaimer. + *     * Redistributions in binary form must reproduce the + *       above copyright notice, this list of conditions and + *       the following disclaimer in the documentation and/or + *       other materials provided with the distribution. + *     * The names of contributors to this software may not be + *       used to endorse or promote products derived from this + *       software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + *  Nate Nielsen <nielsen@memberwebs.com> + * + */ + +#include <sys/types.h> + +#include <ctype.h> +#include <syslog.h> +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> + +#include "usuals.h" +#include "stringx.h" + +void +remove_cr(char* data) +{ +    char* p; +    for(p = data; *data; data++, p++) +    { +        while(*data == '\r') +            data++; +        *p = *data; +    } + +    /* Renull terminate */ +    *p = 0; +} + +char* +trim_start(const char* data) +{ +    while(*data && isspace(*data)) +        ++data; +    return (char*)data; +} + +void +trim_end(char* data) +{ +    char* t = data + strlen(data); +    while(t > data && isspace(*(t - 1))) +    { +        t--; +        *t = 0; +    } +} + +char* +trim_space(char* data) +{ +    data = (char*)trim_start(data); +    trim_end(data); +    return data; +} + +/* String to bool helper function */ +int strtob(const char* str) +{ +    if(strcasecmp(str, "0") == 0 || +       strcasecmp(str, "no") == 0 || +       strcasecmp(str, "false") == 0 || +       strcasecmp(str, "f") == 0 || +       strcasecmp(str, "off") == 0) +        return 0; + +    if(strcasecmp(str, "1") == 0 || +       strcasecmp(str, "yes") == 0 || +       strcasecmp(str, "true") == 0 || +       strcasecmp(str, "t") == 0 || +       strcasecmp(str, "on") == 0) +        return 1; + +    return -1; +} + +size_t +strlcpy(char *dst, const char *src, size_t len) +{ +        size_t ret = strlen(dst); + +        while (len > 1) { +                *dst++ = *src++; +                len--; +        } +        if (len > 0) +                *dst = '\0'; +        return (ret); +} + +size_t strlcat(char* dst, const char* src, size_t siz) +{ +    char* d = dst; +    const char* s = src; +    size_t n = siz; +    size_t dlen; + +    /* Find the end of dst and adjust bytes left but don't go past end */ +    while(n-- != 0 && *d != '\0') +     	d++; +    dlen = d - dst; +    n = siz - dlen; + +    if(n == 0) +        return dlen + strlen(s); +    while(*s != '\0') +    { +        if(n != 1) +        { +            *d++ = *s; +            n--; +        } + +        s++; +    } + +    *d = '\0'; + +    return dlen + (s - src);       /* count does not include NUL */ +} diff --git a/common/stringx.h b/common/stringx.h new file mode 100644 index 0000000..042cf2d --- /dev/null +++ b/common/stringx.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *     * Redistributions of source code must retain the above + *       copyright notice, this list of conditions and the + *       following disclaimer. + *     * Redistributions in binary form must reproduce the + *       above copyright notice, this list of conditions and + *       the following disclaimer in the documentation and/or + *       other materials provided with the distribution. + *     * The names of contributors to this software may not be + *       used to endorse or promote products derived from this + *       software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + *  Nate Nielsen <nielsen@memberwebs.com> + * + */ + +#ifndef __STRINGX_H__ +#define __STRINGX_H__ + +void remove_cr(char* data); + +char* trim_start(const char* data); +void trim_end(char* data); +char* trim_space(char* data); + +int strtob(const char* str); + +size_t +strlcpy(char *dst, const char *src, size_t len); + +#endif /* __STRINGX_H__ */ | 
