diff --git a/Makefile b/Makefile index f1659c882..36793877f 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ all: $(BUILD)/libcommon.a $(BUILD)/libcommon.a: event_loop.o common.o task.o io.o state/redis.o ar rcs $@ $^ +$(BUILD)/common_tests: test/common_tests.c $(BUILD)/libcommon.a + $(CC) -o $@ test/common_tests.c $(BUILD)/libcommon.a $(CFLAGS) + $(BUILD)/db_tests: hiredis test/db_tests.c $(BUILD)/libcommon.a $(CC) -o $@ test/db_tests.c $(BUILD)/libcommon.a thirdparty/hiredis/libhiredis.a $(CFLAGS) @@ -29,8 +32,8 @@ redis: hiredis: git submodule update --init --recursive -- "thirdparty/hiredis" ; cd thirdparty/hiredis ; make -test: hiredis redis $(BUILD)/db_tests $(BUILD)/io_tests $(BUILD)/task_tests $(BUILD)/redis_tests FORCE +test: hiredis redis $(BUILD)/common_tests $(BUILD)/db_tests $(BUILD)/io_tests $(BUILD)/task_tests $(BUILD)/redis_tests FORCE ./thirdparty/redis-3.2.3/src/redis-server & - sleep 1s ; ./build/db_tests ; ./build/io_tests ; ./build/task_tests ; ./build/redis_tests + sleep 1s ; ./build/common_tests ; ./build/db_tests ; ./build/io_tests ; ./build/task_tests ; ./build/redis_tests FORCE: diff --git a/common.c b/common.c index 9e0a86310..53e32fe13 100644 --- a/common.c +++ b/common.c @@ -31,3 +31,63 @@ char *sha1_to_hex(const unsigned char *sha1, char *buffer) { return buffer; } + +const signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + +0, +1, +2, +3, +4, +5, +6, +7, /* 30-37 */ + +8, +9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; + +static inline unsigned int hexval(unsigned char c) { + return hexval_table[c]; +} + +/* + * Convert two consecutive hexadecimal digits into a char. Return a + * negative value on error. Don't run over the end of short strings. + */ +static inline int hex2chr(const char *s) { + int val = hexval(s[0]); + return (val < 0) ? val : (val << 4) | hexval(s[1]); +} + +int hex_to_sha1(const char *hex, unsigned char *sha1) { + int i; + for (i = 0; i < UNIQUE_ID_SIZE; i++) { + int val = hex2chr(hex); + if (val < 0) + return -1; + *sha1++ = val; + hex += 2; + } + return 0; +} diff --git a/common.h b/common.h index 3f30b5661..7e4b73ebe 100644 --- a/common.h +++ b/common.h @@ -28,8 +28,8 @@ #define UNIQUE_ID_SIZE 20 -// Cleanup method for running tests with the greatest library. -// Runs the test, then clears the Redis database. +/* Cleanup method for running tests with the greatest library. + * Runs the test, then clears the Redis database. */ #define RUN_REDIS_TEST(context, test) \ RUN_TEST(test); \ freeReplyObject(redisCommand(context, "FLUSHALL")); @@ -44,6 +44,11 @@ unique_id globally_unique_id(void); * UNIQUE_ID_SIZE + 1 */ char *sha1_to_hex(const unsigned char *sha1, char *buffer); +/* Convert a hexdecimal string of length 40 to a 20 byte sha1 hash. This + * function assumes that sha1 points to an already allocated char array of size + * UNIQUE_ID_SIZE. */ +int hex_to_sha1(const char *hex, unsigned char *sha1); + typedef unique_id object_id; #endif diff --git a/event_loop.c b/event_loop.c index 0fd79e6af..d89710ba5 100644 --- a/event_loop.c +++ b/event_loop.c @@ -59,10 +59,10 @@ void event_loop_detach(event_loop *loop, int64_t index, int shall_close) { } /* Poll the file descriptors associated to this event loop. - * See http://linux.die.net/man/2/poll */ -int event_loop_poll(event_loop *loop) { + * See http://linux.die.net/man/2/poll. The timeout is in milliseconds. */ +int event_loop_poll(event_loop *loop, int timeout) { return poll((struct pollfd *) utarray_front(loop->waiting), - utarray_len(loop->waiting), -1); + utarray_len(loop->waiting), timeout); } /* Get the total number of file descriptors participating in the event loop. */ diff --git a/event_loop.h b/event_loop.h index a96ec4643..840abfd8c 100644 --- a/event_loop.h +++ b/event_loop.h @@ -29,7 +29,7 @@ int64_t event_loop_attach(event_loop *loop, int fd, int events); void event_loop_detach(event_loop *loop, int64_t index, int shall_close); -int event_loop_poll(event_loop *loop); +int event_loop_poll(event_loop *loop, int timeout); int64_t event_loop_size(event_loop *loop); struct pollfd *event_loop_get(event_loop *loop, int64_t index); void event_loop_set_data(event_loop *loop, int64_t index, void *data); diff --git a/state/redis.c b/state/redis.c index ae3fb1a6f..c2e01df5e 100644 --- a/state/redis.c +++ b/state/redis.c @@ -2,6 +2,8 @@ #include +#include "utstring.h" + #include "common.h" #include "db.h" #include "object_table.h" @@ -215,6 +217,22 @@ void object_table_lookup(db_conn *db, } } +void task_queue_submit_task(db_conn *db, task_iid task_iid, task_spec *task) { + /* For converting an id to hex, which has double the number + * of bytes compared to the id (+ 1 byte for '\0'). */ + static char hex[2 * UNIQUE_ID_SIZE + 1]; + UT_string *command; + utstring_new(command); + sha1_to_hex(&task_iid.id[0], &hex[0]); + utstring_printf(command, "HMSET queue:%s", &hex[0]); + print_task(task, command); + redisAsyncCommand(db->context, NULL, NULL, utstring_body(command)); + if (db->context->err) { + LOG_REDIS_ERR(db->context, "error in task_queue submit_task"); + } + utstring_free(command); +} + void send_redis_command(int socket_fd, const char *format, ...) { char *cmd; va_list ap; diff --git a/task.c b/task.c index 5d5271d91..fd4708d78 100644 --- a/task.c +++ b/task.c @@ -2,6 +2,8 @@ #include #include +#include "utarray.h" + #include "task.h" #include "common.h" #include "io.h" @@ -30,7 +32,8 @@ typedef struct { } task_arg; struct task_spec_impl { - function_id func_id; + /* Function ID of the task. */ + function_id function_id; /* Total number of arguments. */ int64_t num_args; /* Index of the last argument that has been constructed. */ @@ -52,14 +55,14 @@ struct task_spec_impl { (sizeof(task_spec) + ((NUM_ARGS) + (NUM_RETURNS)) * sizeof(task_arg) + \ (ARGS_VALUE_SIZE)) -task_spec *alloc_task_spec(function_id func_id, +task_spec *alloc_task_spec(function_id function_id, int64_t num_args, int64_t num_returns, int64_t args_value_size) { int64_t size = TASK_SPEC_SIZE(num_args, num_returns, args_value_size); task_spec *task = malloc(size); memset(task, 0, size); - task->func_id = func_id; + task->function_id = function_id; task->num_args = num_args; task->arg_index = 0; task->num_returns = num_returns; @@ -72,6 +75,10 @@ int64_t task_size(task_spec *spec) { spec->args_value_size); } +unique_id *task_function(task_spec *spec) { + return &spec->function_id; +} + int64_t task_num_args(task_spec *spec) { return spec->num_args; } @@ -153,3 +160,86 @@ task_spec *read_task(int fd) { CHECK(task_size(spec) == length); return spec; } + +void print_task(task_spec *spec, UT_string *output) { + /* For converting an id to hex, which has double the number + * of bytes compared to the id (+ 1 byte for '\0'). */ + static char hex[2 * UNIQUE_ID_SIZE + 1]; + /* Print function id. */ + sha1_to_hex(&task_function(spec)->id[0], &hex[0]); + utstring_printf(output, "fun %s ", &hex[0]); + /* Print arguments. */ + for (int i = 0; i < task_num_args(spec); ++i) { + sha1_to_hex(&task_arg_id(spec, i)->id[0], &hex[0]); + utstring_printf(output, " id:%d %s", i, &hex[0]); + } + /* Print return ids. */ + for (int i = 0; i < task_num_returns(spec); ++i) { + object_id *object_id = task_return(spec, i); + sha1_to_hex(&object_id->id[0], &hex[0]); + utstring_printf(output, " ret:%d %s", i, &hex[0]); + } +} + +UT_icd unique_id_icd = {sizeof(unique_id), NULL, NULL, NULL}; + +task_spec *parse_task(char *task_string, int64_t task_length) { + /* We make one pass through task_string to store all the argument ids + * in "args" and all the return ids in "returns". */ + UT_array *args; + utarray_new(args, &unique_id_icd); + UT_array *returns; + utarray_new(returns, &unique_id_icd); + function_id function_id; + char *cursor = strtok(task_string, " "); + int index = 0; + while (cursor != NULL) { + /* This will be equal to "args" or "returns" depending on whether we + * are processing an argument id or a return id. */ + UT_array *target = NULL; + if (strncmp("fun", cursor, 3) == 0) { + /* Parse function id. */ + CHECK(cursor + 2 * UNIQUE_ID_SIZE + 1 <= task_string + task_length); + cursor = strtok(NULL, " "); + hex_to_sha1(cursor, &function_id.id[0]); + cursor = strtok(NULL, " "); + CHECK(cursor); + continue; + } else if (strncmp("id:", cursor, 3) == 0) { + /* Parse pass by reference argument. */ + sscanf(cursor, "id:%d", &index); + target = args; + } else if (strncmp("val:", cursor, 4) == 0) { + /* Parse pass by value argument. */ + sscanf(cursor, "val:%d", &index); + CHECK(0); /* Not implemented yet */ + } else if (strncmp("ret:", cursor, 4) == 0) { + /* Parse return object reference. */ + sscanf(cursor, "ret:%d", &index); + target = returns; + } + cursor = strtok(NULL, " "); + CHECK(cursor); + if (index >= utarray_len(target)) { + utarray_resize(target, index + 1); + } + object_id *id = (object_id *) utarray_eltptr(target, index); + hex_to_sha1(cursor, &id->id[0]); + cursor = strtok(NULL, " "); + } + /* TODO(pcm): Implement pass by value. */ + /* Now assemble the task specification. */ + task_spec *spec = + alloc_task_spec(function_id, utarray_len(args), utarray_len(returns), 0); + for (int i = 0; i < utarray_len(args); ++i) { + object_id *id = (object_id *) utarray_eltptr(args, i); + task_args_add_ref(spec, *id); + } + for (int i = 0; i < utarray_len(returns); ++i) { + object_id *id = (object_id *) utarray_eltptr(returns, i); + *task_return(spec, i) = *id; + } + utarray_free(args); + utarray_free(returns); + return spec; +} diff --git a/task.h b/task.h index ad85540ee..96c97b80d 100644 --- a/task.h +++ b/task.h @@ -10,6 +10,7 @@ #include #include #include "common.h" +#include "utstring.h" typedef unique_id function_id; typedef unique_id object_id; @@ -22,7 +23,7 @@ enum arg_type { ARG_BY_REF, ARG_BY_VAL }; /* Construct and modify task specifications. */ /* Allocating and initializing a task. */ -task_spec *alloc_task_spec(function_id func_id, +task_spec *alloc_task_spec(function_id function_id, int64_t num_args, int64_t num_returns, int64_t args_value_size); @@ -30,6 +31,9 @@ task_spec *alloc_task_spec(function_id func_id, /* Size of the task in bytes. */ int64_t task_size(task_spec *spec); +/* Return the function ID of the task. */ +unique_id *task_function(task_spec *spec); + /* Getting the number of arguments and returns. */ int64_t task_num_args(task_spec *spec); int64_t task_num_returns(task_spec *spec); @@ -58,4 +62,10 @@ void write_task(int fd, task_spec *spec); * responsibility to free the task after it has been used. */ task_spec *read_task(int fd); +/* Print task as a humanly readable string. */ +void print_task(task_spec *spec, UT_string *output); + +/* Parse task as printed by print_task. */ +task_spec *parse_task(char *task_string, int64_t task_length); + #endif diff --git a/test/common_tests.c b/test/common_tests.c new file mode 100644 index 000000000..3673c335d --- /dev/null +++ b/test/common_tests.c @@ -0,0 +1,27 @@ +#include "greatest.h" + +#include "common.h" + +SUITE(common_tests); + +TEST sha1_test(void) { + static char hex[2 * UNIQUE_ID_SIZE + 1]; + static unsigned char id[UNIQUE_ID_SIZE]; + unique_id uid = globally_unique_id(); + sha1_to_hex(&uid.id[0], &hex[0]); + hex_to_sha1(&hex[0], &id[0]); + ASSERT(memcmp(&uid.id[0], &id[0], 20) == 0); + PASS(); +} + +SUITE(common_tests) { + RUN_TEST(sha1_test); +} + +GREATEST_MAIN_DEFS(); + +int main(int argc, char **argv) { + GREATEST_MAIN_BEGIN(); + RUN_SUITE(common_tests); + GREATEST_MAIN_END(); +} diff --git a/test/db_tests.c b/test/db_tests.c index 568eeaeb1..5bdbf25df 100644 --- a/test/db_tests.c +++ b/test/db_tests.c @@ -3,6 +3,7 @@ #include #include "event_loop.h" +#include "test/example_task.h" #include "state/db.h" #include "state/object_table.h" #include "state/redis.h" @@ -62,7 +63,7 @@ TEST object_table_lookup_test(void) { object_table_add(&conn2, id); object_table_lookup(&conn1, id, sync_test_callback); while (!lookup_successful) { - int num_ready = event_loop_poll(&loop); + int num_ready = event_loop_poll(&loop, -1); if (num_ready < 0) { exit(-1); } @@ -81,7 +82,7 @@ TEST object_table_lookup_test(void) { lookup_successful = 0; object_table_lookup(&conn1, id, test_callback); while (!lookup_successful) { - int num_ready = event_loop_poll(&loop); + int num_ready = event_loop_poll(&loop, -1); if (num_ready < 0) { exit(-1); } @@ -112,10 +113,44 @@ TEST object_table_lookup_test(void) { PASS(); } +TEST task_queue_test(void) { + event_loop loop; + event_loop_init(&loop); + db_conn conn; + db_connect("127.0.0.1", 6379, "local_scheduler", "", -1, &conn); + int64_t index = db_attach(&conn, &loop, 0); + + task_spec *task = example_task(); + task_queue_submit_task(&conn, globally_unique_id(), task); + while (1) { + int num_ready = event_loop_poll(&loop, 100); + if (num_ready < 0) { + exit(-1); + } + if (num_ready == 0) { + break; + } + for (int i = 0; i < event_loop_size(&loop); ++i) { + struct pollfd *waiting = event_loop_get(&loop, i); + if (waiting->revents == 0) + continue; + if (i == index) { + db_event(&conn); + } + } + } + + free_task_spec(task); + db_disconnect(&conn); + event_loop_free(&loop); + PASS(); +} + SUITE(db_tests) { redisContext *context = redisConnect("127.0.0.1", 6379); redisCommand(context, "FLUSHALL"); RUN_REDIS_TEST(context, object_table_lookup_test); + RUN_TEST(task_queue_test); redisFree(context); } diff --git a/test/example_task.h b/test/example_task.h new file mode 100644 index 000000000..0dddc4dc1 --- /dev/null +++ b/test/example_task.h @@ -0,0 +1,14 @@ +#ifndef EXAMPLE_TASK_H +#define EXAMPLE_TASK_H + +#include "task.h" + +task_spec *example_task(void) { + function_id func_id = globally_unique_id(); + task_spec *task = alloc_task_spec(func_id, 2, 1, 0); + task_args_add_ref(task, globally_unique_id()); + task_args_add_ref(task, globally_unique_id()); + return task; +} + +#endif diff --git a/test/redis_tests.c b/test/redis_tests.c index 9efd3bace..2f0f09110 100644 --- a/test/redis_tests.c +++ b/test/redis_tests.c @@ -85,7 +85,7 @@ TEST async_redis_socket_test(void) { send_redis_command(client_fd, test_set_format, test_key, test_value); while (!lookup_successful) { - int num_ready = event_loop_poll(&loop); + int num_ready = event_loop_poll(&loop, -1); if (num_ready < 0) { exit(-1); } diff --git a/test/task_tests.c b/test/task_tests.c index 68a6a6537..4293eec04 100644 --- a/test/task_tests.c +++ b/test/task_tests.c @@ -5,6 +5,7 @@ #include #include "common.h" +#include "test/example_task.h" #include "task.h" SUITE(task_tests); @@ -56,9 +57,28 @@ TEST send_task(void) { PASS(); } +TEST print_and_parse_task(void) { + task_spec *task = example_task(); + + UT_string *output; + utstring_new(output); + print_task(task, output); + task_spec *result = parse_task(utstring_body(output), utstring_len(output)); + utstring_free(output); + + ASSERT_EQ(task_size(task), task_size(result)); + ASSERT(memcmp(task, result, task_size(task)) == 0); + + free_task_spec(task); + free_task_spec(result); + + PASS(); +} + SUITE(task_tests) { RUN_TEST(task_test); RUN_TEST(send_task); + RUN_TEST(print_and_parse_task); } GREATEST_MAIN_DEFS(); diff --git a/thirdparty/utstring.h b/thirdparty/utstring.h new file mode 100644 index 000000000..debe5f3df --- /dev/null +++ b/thirdparty/utstring.h @@ -0,0 +1,398 @@ +/* +Copyright (c) 2008-2016, Troy D. Hanson http://troydhanson.github.com/uthash/ +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. + +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. +*/ + +/* a dynamic string implementation using macros + */ +#ifndef UTSTRING_H +#define UTSTRING_H + +#define UTSTRING_VERSION 2.0.1 + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((__unused__)) +#else +#define _UNUSED_ +#endif + +#include +#include +#include +#include + +#ifndef oom +#define oom() exit(-1) +#endif + +typedef struct { + char *d; + size_t n; /* allocd size */ + size_t i; /* index of first unused byte */ +} UT_string; + +#define utstring_reserve(s,amt) \ +do { \ + if (((s)->n - (s)->i) < (size_t)(amt)) { \ + char *utstring_tmp = (char*)realloc( \ + (s)->d, (s)->n + (amt)); \ + if (utstring_tmp == NULL) oom(); \ + (s)->d = utstring_tmp; \ + (s)->n += (amt); \ + } \ +} while(0) + +#define utstring_init(s) \ +do { \ + (s)->n = 0; (s)->i = 0; (s)->d = NULL; \ + utstring_reserve(s,100); \ + (s)->d[0] = '\0'; \ +} while(0) + +#define utstring_done(s) \ +do { \ + if ((s)->d != NULL) free((s)->d); \ + (s)->n = 0; \ +} while(0) + +#define utstring_free(s) \ +do { \ + utstring_done(s); \ + free(s); \ +} while(0) + +#define utstring_new(s) \ +do { \ + s = (UT_string*)calloc(sizeof(UT_string),1); \ + if (!s) oom(); \ + utstring_init(s); \ +} while(0) + +#define utstring_renew(s) \ +do { \ + if (s) { \ + utstring_clear(s); \ + } else { \ + utstring_new(s); \ + } \ +} while(0) + +#define utstring_clear(s) \ +do { \ + (s)->i = 0; \ + (s)->d[0] = '\0'; \ +} while(0) + +#define utstring_bincpy(s,b,l) \ +do { \ + utstring_reserve((s),(l)+1); \ + if (l) memcpy(&(s)->d[(s)->i], b, l); \ + (s)->i += (l); \ + (s)->d[(s)->i]='\0'; \ +} while(0) + +#define utstring_concat(dst,src) \ +do { \ + utstring_reserve((dst),((src)->i)+1); \ + if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \ + (dst)->i += (src)->i; \ + (dst)->d[(dst)->i]='\0'; \ +} while(0) + +#define utstring_len(s) ((unsigned)((s)->i)) + +#define utstring_body(s) ((s)->d) + +_UNUSED_ static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) { + int n; + va_list cp; + for (;;) { +#ifdef _WIN32 + cp = ap; +#else + va_copy(cp, ap); +#endif + n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp); + va_end(cp); + + if ((n > -1) && ((size_t) n < (s->n-s->i))) { + s->i += n; + return; + } + + /* Else try again with more space. */ + if (n > -1) utstring_reserve(s,n+1); /* exact */ + else utstring_reserve(s,(s->n)*2); /* 2x */ + } +} +#ifdef __GNUC__ +/* support printf format checking (2=the format string, 3=start of varargs) */ +static void utstring_printf(UT_string *s, const char *fmt, ...) + __attribute__ (( format( printf, 2, 3) )); +#endif +_UNUSED_ static void utstring_printf(UT_string *s, const char *fmt, ...) { + va_list ap; + va_start(ap,fmt); + utstring_printf_va(s,fmt,ap); + va_end(ap); +} + +/******************************************************************************* + * begin substring search functions * + ******************************************************************************/ +/* Build KMP table from left to right. */ +_UNUSED_ static void _utstring_BuildTable( + const char *P_Needle, + size_t P_NeedleLen, + long *P_KMP_Table) +{ + long i, j; + + i = 0; + j = i - 1; + P_KMP_Table[i] = j; + while (i < (long) P_NeedleLen) + { + while ( (j > -1) && (P_Needle[i] != P_Needle[j]) ) + { + j = P_KMP_Table[j]; + } + i++; + j++; + if (i < (long) P_NeedleLen) + { + if (P_Needle[i] == P_Needle[j]) + { + P_KMP_Table[i] = P_KMP_Table[j]; + } + else + { + P_KMP_Table[i] = j; + } + } + else + { + P_KMP_Table[i] = j; + } + } + + return; +} + + +/* Build KMP table from right to left. */ +_UNUSED_ static void _utstring_BuildTableR( + const char *P_Needle, + size_t P_NeedleLen, + long *P_KMP_Table) +{ + long i, j; + + i = P_NeedleLen - 1; + j = i + 1; + P_KMP_Table[i + 1] = j; + while (i >= 0) + { + while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) ) + { + j = P_KMP_Table[j + 1]; + } + i--; + j--; + if (i >= 0) + { + if (P_Needle[i] == P_Needle[j]) + { + P_KMP_Table[i + 1] = P_KMP_Table[j + 1]; + } + else + { + P_KMP_Table[i + 1] = j; + } + } + else + { + P_KMP_Table[i + 1] = j; + } + } + + return; +} + + +/* Search data from left to right. ( Multiple search mode. ) */ +_UNUSED_ static long _utstring_find( + const char *P_Haystack, + size_t P_HaystackLen, + const char *P_Needle, + size_t P_NeedleLen, + long *P_KMP_Table) +{ + long i, j; + long V_FindPosition = -1; + + /* Search from left to right. */ + i = j = 0; + while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) ) + { + while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) ) + { + i = P_KMP_Table[i]; + } + i++; + j++; + if (i >= (int)P_NeedleLen) + { + /* Found. */ + V_FindPosition = j - i; + break; + } + } + + return V_FindPosition; +} + + +/* Search data from right to left. ( Multiple search mode. ) */ +_UNUSED_ static long _utstring_findR( + const char *P_Haystack, + size_t P_HaystackLen, + const char *P_Needle, + size_t P_NeedleLen, + long *P_KMP_Table) +{ + long i, j; + long V_FindPosition = -1; + + /* Search from right to left. */ + j = (P_HaystackLen - 1); + i = (P_NeedleLen - 1); + while ( (j >= 0) && (j >= i) ) + { + while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) ) + { + i = P_KMP_Table[i + 1]; + } + i--; + j--; + if (i < 0) + { + /* Found. */ + V_FindPosition = j + 1; + break; + } + } + + return V_FindPosition; +} + + +/* Search data from left to right. ( One time search mode. ) */ +_UNUSED_ static long utstring_find( + UT_string *s, + long P_StartPosition, /* Start from 0. -1 means last position. */ + const char *P_Needle, + size_t P_NeedleLen) +{ + long V_StartPosition; + long V_HaystackLen; + long *V_KMP_Table; + long V_FindPosition = -1; + + if (P_StartPosition < 0) + { + V_StartPosition = s->i + P_StartPosition; + } + else + { + V_StartPosition = P_StartPosition; + } + V_HaystackLen = s->i - V_StartPosition; + if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) ) + { + V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1)); + if (V_KMP_Table != NULL) + { + _utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table); + + V_FindPosition = _utstring_find(s->d + V_StartPosition, + V_HaystackLen, + P_Needle, + P_NeedleLen, + V_KMP_Table); + if (V_FindPosition >= 0) + { + V_FindPosition += V_StartPosition; + } + + free(V_KMP_Table); + } + } + + return V_FindPosition; +} + + +/* Search data from right to left. ( One time search mode. ) */ +_UNUSED_ static long utstring_findR( + UT_string *s, + long P_StartPosition, /* Start from 0. -1 means last position. */ + const char *P_Needle, + size_t P_NeedleLen) +{ + long V_StartPosition; + long V_HaystackLen; + long *V_KMP_Table; + long V_FindPosition = -1; + + if (P_StartPosition < 0) + { + V_StartPosition = s->i + P_StartPosition; + } + else + { + V_StartPosition = P_StartPosition; + } + V_HaystackLen = V_StartPosition + 1; + if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) ) + { + V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1)); + if (V_KMP_Table != NULL) + { + _utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table); + + V_FindPosition = _utstring_findR(s->d, + V_HaystackLen, + P_Needle, + P_NeedleLen, + V_KMP_Table); + + free(V_KMP_Table); + } + } + + return V_FindPosition; +} +/******************************************************************************* + * end substring search functions * + ******************************************************************************/ + +#endif /* UTSTRING_H */