├── .gitignore ├── Makefile ├── rmutil ├── logging.h ├── test_heap.c ├── Makefile ├── test_priority_queue.c ├── strings.h ├── priority_queue.c ├── alloc.c ├── alloc.h ├── test_vector.c ├── strings.c ├── heap.h ├── priority_queue.h ├── sdsalloc.h ├── vector.c ├── test_util.h ├── vector.h ├── heap.c ├── util.h ├── util.c ├── sds.h └── sds.c ├── src ├── Makefile └── module.c └── include └── redismodule.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.so 4 | *.db 5 | .vscode 6 | rmutil/test_vector 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: all 2 | 3 | .DEFAULT: 4 | cd rmutil && $(MAKE) $@ 5 | cd src && $(MAKE) $@ 6 | 7 | clean: 8 | cd rmutil && $(MAKE) $@ 9 | cd src && $(MAKE) $@ 10 | 11 | -------------------------------------------------------------------------------- /rmutil/logging.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_LOGGING_H__ 2 | #define __RMUTIL_LOGGING_H__ 3 | 4 | /* Convenience macros for redis logging */ 5 | 6 | #define RM_LOG_DEBUG(ctx, ...) RedisModule_Log(ctx, "debug", __VA_ARGS__) 7 | #define RM_LOG_VERBOSE(ctx, ...) RedisModule_Log(ctx, "verbose", __VA_ARGS__) 8 | #define RM_LOG_NOTICE(ctx, ...) RedisModule_Log(ctx, "notice", __VA_ARGS__) 9 | #define RM_LOG_WARNING(ctx, ...) RedisModule_Log(ctx, "warning", __VA_ARGS__) 10 | 11 | #endif -------------------------------------------------------------------------------- /rmutil/test_heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "heap.h" 3 | #include "assert.h" 4 | 5 | int cmp(void *a, void *b) { 6 | int *__a = (int *) a; 7 | int *__b = (int *) b; 8 | return *__a - *__b; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | int myints[] = {10, 20, 30, 5, 15}; 13 | Vector *v = NewVector(int, 5); 14 | for (int i = 0; i < 5; i++) { 15 | Vector_Push(v, myints[i]); 16 | } 17 | 18 | Make_Heap(v, 0, v->top, cmp); 19 | 20 | int n; 21 | Vector_Get(v, 0, &n); 22 | assert(30 == n); 23 | 24 | Heap_Pop(v, 0, v->top, cmp); 25 | v->top = 4; 26 | Vector_Get(v, 0, &n); 27 | assert(20 == n); 28 | 29 | Vector_Push(v, 99); 30 | Heap_Push(v, 0, v->top, cmp); 31 | Vector_Get(v, 0, &n); 32 | assert(99 == n); 33 | 34 | Vector_Free(v); 35 | printf("PASS!"); 36 | return 0; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /rmutil/Makefile: -------------------------------------------------------------------------------- 1 | # set environment variable RM_INCLUDE_DIR to the location of redismodule.h 2 | ifndef RM_INCLUDE_DIR 3 | RM_INCLUDE_DIR=../include/ 4 | endif 5 | 6 | CFLAGS = -g -fPIC -O3 -std=gnu99 -I$(RM_INCLUDE_DIR) -Wall -Wno-unused-function 7 | CC=gcc 8 | 9 | OBJS=util.o strings.o sds.o vector.o heap.o priority_queue.o 10 | 11 | all: librmutil.a 12 | 13 | clean: 14 | rm -rf *.o *.a 15 | 16 | librmutil.a: $(OBJS) 17 | ar rcs $@ $^ 18 | 19 | test_vector: test_vector.o vector.o 20 | $(CC) -Wall -o test_vector vector.o test_vector.o -lc -O0 21 | @(sh -c ./test_vector) 22 | 23 | test_heap: test_heap.o heap.o vector.o 24 | $(CC) -Wall -o test_heap heap.o vector.o test_heap.o -lc -O0 25 | @(sh -c ./test_heap) 26 | 27 | test_priority_queue: test_priority_queue.o priority_queue.o heap.o vector.o 28 | $(CC) -Wall -o test_priority_queue priority_queue.o heap.o vector.o test_priority_queue.o -lc -O0 29 | @(sh -c ./test_heap) 30 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | #set environment variable RM_INCLUDE_DIR to the location of redismodule.h 2 | ifndef RM_INCLUDE_DIR 3 | RM_INCLUDE_DIR=../include/ 4 | endif 5 | 6 | ifndef RMUTIL_INCLUDE_DIR 7 | RMUTIL_INCLUDE_DIR=../ 8 | endif 9 | 10 | ifndef RMUTIL_LIBDIR 11 | RMUTIL_LIBDIR=../rmutil 12 | endif 13 | 14 | # find the OS 15 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 16 | 17 | # Compile flags for linux / osx 18 | ifeq ($(uname_S),Linux) 19 | SHOBJ_CFLAGS ?= -fno-common -g -ggdb 20 | SHOBJ_LDFLAGS ?= -shared -Bsymbolic 21 | else 22 | SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb 23 | SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup 24 | endif 25 | CFLAGS = -I$(RM_INCLUDE_DIR) -I$(RMUTIL_INCLUDE_DIR) -Wall -g -fPIC -std=gnu99 26 | CC=gcc 27 | 28 | all: module.so 29 | 30 | module.so: module.o 31 | $(LD) -o $@ module.o $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc 32 | clean: 33 | rm -rf *.xo *.so *.o 34 | 35 | -------------------------------------------------------------------------------- /rmutil/test_priority_queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "assert.h" 3 | #include "priority_queue.h" 4 | 5 | int cmp(void* i1, void* i2) { 6 | int *__i1 = (int*) i1; 7 | int *__i2 = (int*) i2; 8 | return *__i1 - *__i2; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | PriorityQueue *pq = NewPriorityQueue(int, 10, cmp); 13 | assert(0 == Priority_Queue_Size(pq)); 14 | 15 | for (int i = 0; i < 5; i++) { 16 | Priority_Queue_Push(pq, i); 17 | } 18 | assert(5 == Priority_Queue_Size(pq)); 19 | 20 | Priority_Queue_Pop(pq); 21 | assert(4 == Priority_Queue_Size(pq)); 22 | 23 | Priority_Queue_Push(pq, 10); 24 | Priority_Queue_Push(pq, 20); 25 | Priority_Queue_Push(pq, 15); 26 | int n; 27 | Priority_Queue_Top(pq, &n); 28 | assert(20 == n); 29 | 30 | Priority_Queue_Pop(pq); 31 | Priority_Queue_Top(pq, &n); 32 | assert(15 == n); 33 | 34 | Priority_Queue_Free(pq); 35 | printf("PASS!"); 36 | return 0; 37 | } -------------------------------------------------------------------------------- /rmutil/strings.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_STRINGS_H__ 2 | #define __RMUTIL_STRINGS_H__ 3 | 4 | #include 5 | 6 | /* 7 | * Create a new RedisModuleString object from a printf-style format and arguments. 8 | * Note that RedisModuleString objects CANNOT be used as formatting arguments. 9 | */ 10 | RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...); 11 | 12 | /* Return 1 if the two strings are equal. Case *sensitive* */ 13 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2); 14 | 15 | /* Return 1 if the string is equal to a C NULL terminated string. Case *sensitive* */ 16 | int RMUtil_StringEqualsC(RedisModuleString *s1, const char *s2); 17 | 18 | /* Converts a redis string to lowercase in place without reallocating anything */ 19 | void RMUtil_StringToLower(RedisModuleString *s); 20 | 21 | /* Converts a redis string to uppercase in place without reallocating anything */ 22 | void RMUtil_StringToUpper(RedisModuleString *s); 23 | #endif 24 | -------------------------------------------------------------------------------- /rmutil/priority_queue.c: -------------------------------------------------------------------------------- 1 | #include "priority_queue.h" 2 | #include "heap.h" 3 | 4 | PriorityQueue *__newPriorityQueueSize(size_t elemSize, size_t cap, int (*cmp)(void *, void *)) { 5 | PriorityQueue *pq = malloc(sizeof(PriorityQueue)); 6 | pq->v = __newVectorSize(elemSize, cap); 7 | pq->cmp = cmp; 8 | return pq; 9 | } 10 | 11 | inline size_t Priority_Queue_Size(PriorityQueue *pq) { 12 | return Vector_Size(pq->v); 13 | } 14 | 15 | inline int Priority_Queue_Top(PriorityQueue *pq, void *ptr) { 16 | return Vector_Get(pq->v, 0, ptr); 17 | } 18 | 19 | inline size_t __priority_Queue_PushPtr(PriorityQueue *pq, void *elem) { 20 | size_t top = __vector_PushPtr(pq->v, elem); 21 | Heap_Push(pq->v, 0, top, pq->cmp); 22 | return top; 23 | } 24 | 25 | inline void Priority_Queue_Pop(PriorityQueue *pq) { 26 | if (pq->v->top == 0) { 27 | return; 28 | } 29 | Heap_Pop(pq->v, 0, pq->v->top, pq->cmp); 30 | pq->v->top--; 31 | } 32 | 33 | void Priority_Queue_Free(PriorityQueue *pq) { 34 | Vector_Free(pq->v); 35 | free(pq); 36 | } 37 | -------------------------------------------------------------------------------- /rmutil/alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../redismodule.h" 6 | #include "alloc.h" 7 | 8 | /* A patched implementation of strdup that will use our patched calloc */ 9 | char *rmalloc_strndup(const char *s, size_t n) { 10 | char *ret = calloc(n + 1, sizeof(char)); 11 | if (ret) 12 | memcpy(ret, s, n); 13 | return ret; 14 | } 15 | 16 | /* 17 | * Re-patching RedisModule_Alloc and friends to the original malloc functions 18 | * 19 | * This function shold be called if you are working with malloc-patched code 20 | * ouside of redis, usually for unit tests. Call it once when entering your unit 21 | * tests' main(). 22 | * 23 | * Since including "alloc.h" while defining REDIS_MODULE_TARGET 24 | * replaces all malloc functions in redis with the RM_Alloc family of functions, 25 | * when running that code outside of redis, your app will crash. This function 26 | * patches the RM_Alloc functions back to the original mallocs. */ 27 | void RMUTil_InitAlloc() { 28 | 29 | RedisModule_Alloc = malloc; 30 | RedisModule_Realloc = realloc; 31 | RedisModule_Calloc = calloc; 32 | RedisModule_Free = free; 33 | RedisModule_Strdup = strdup; 34 | } 35 | -------------------------------------------------------------------------------- /rmutil/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_ALLOC__ 2 | #define __RMUTIL_ALLOC__ 3 | 4 | /* Automatic Redis Module Allocation functions monkey-patching. 5 | * 6 | * Including this file while REDIS_MODULE_TARGET is defined, will explicitly 7 | * override malloc, calloc, realloc & free with RedisModule_Alloc, 8 | * RedisModule_Callc, etc implementations, that allow Redis better control and 9 | * reporting over allocations per module. 10 | * 11 | * You should include this file in all c files AS THE LAST INCLUDED FILE 12 | * 13 | * This only has effect when when compiling with the macro REDIS_MODULE_TARGET 14 | * defined. The idea is that for unit tests it will not be defined, but for the 15 | * module build target it will be. 16 | * 17 | */ 18 | 19 | #include 20 | #include "../redismodule.h" 21 | 22 | char *rmalloc_strndup(const char *s, size_t n); 23 | 24 | #ifdef REDIS_MODULE_TARGET /* Set this when compiling your code as a module */ 25 | 26 | #define malloc(size) RedisModule_Alloc(size) 27 | #define calloc(count, size) RedisModule_Calloc(count, size) 28 | #define realloc(ptr, size) RedisModule_Realloc(ptr, size) 29 | #define free(ptr) RedisModule_Free(ptr) 30 | #define strdup(ptr) RedisModule_Strdup(ptr) 31 | 32 | // /* More overriding */ 33 | // // needed to avoid calling strndup->malloc 34 | #ifdef strndup 35 | #undef strndup 36 | #endif 37 | #define strndup(s, n) rmalloc_strndup(s, n) 38 | 39 | #else 40 | /* This function shold be called if you are working with malloc-patched code 41 | * ouside of redis, usually for unit tests. Call it once when entering your unit 42 | * tests' main() */ 43 | void RMUTil_InitAlloc(); 44 | #endif /* REDIS_MODULE_TARGET */ 45 | 46 | #endif /* __RMUTIL_ALLOC__ */ -------------------------------------------------------------------------------- /rmutil/test_vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | #include "assert.h" 4 | 5 | int main(int argc, char **argv) { 6 | 7 | 8 | Vector *v = NewVector(int, 1); 9 | int N = 10; 10 | 11 | for (int i = 0; i < N/2; i++) { 12 | Vector_Put(v, i, i); 13 | } 14 | 15 | for (int i = N/2; i < N; i++) { 16 | Vector_Push(v, i); 17 | } 18 | assert(Vector_Size(v) == N); 19 | assert(Vector_Cap(v) >= N); 20 | 21 | for (int i = 0; i < Vector_Size(v); i++) { 22 | int n; 23 | int rc = Vector_Get(v, i, &n); 24 | printf("%d %d\n", rc, n); 25 | assert ( 1== rc ); 26 | assert (n == i); 27 | } 28 | 29 | Vector_Free(v); 30 | 31 | v = NewVector(char *, 0); 32 | N = 4; 33 | char *strings[4] = {"hello", "world", "foo", "bar"}; 34 | 35 | for (int i = 0; i < N/2; i++) { 36 | Vector_Put(v, i, strings[i]); 37 | } 38 | 39 | for (int i = N/2; i < N; i++) { 40 | Vector_Push(v, strings[i]); 41 | } 42 | assert(Vector_Size(v) == N); 43 | assert(Vector_Cap(v) >= N); 44 | 45 | for (size_t i = 0; i < Vector_Size(v); i++) { 46 | char *x; 47 | int rc = Vector_Get(v, i, &x); 48 | assert (rc == 1); 49 | assert (!strcmp(x, strings[i])); 50 | } 51 | 52 | int rc = Vector_Get(v, 100, NULL); 53 | assert (rc == 0); 54 | 55 | Vector_Free(v); 56 | printf("PASS!"); 57 | 58 | return 0; 59 | //Vector_Push(v, "hello"); 60 | //Vector_Push(v, "world"); 61 | // char *x = NULL; 62 | // int rc = Vector_Getx(v, 0, &x); 63 | // printf("rc: %d got %s\n", rc, x); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /rmutil/strings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "strings.h" 5 | 6 | 7 | #include "sds.h" 8 | 9 | RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...) { 10 | sds s = sdsempty(); 11 | 12 | va_list ap; 13 | va_start(ap, fmt); 14 | s = sdscatvprintf(s, fmt, ap); 15 | va_end(ap); 16 | 17 | RedisModuleString *ret = RedisModule_CreateString(ctx, (const char *)s, sdslen(s)); 18 | sdsfree(s); 19 | return ret; 20 | } 21 | 22 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2) { 23 | 24 | 25 | const char *c1, *c2; 26 | size_t l1, l2; 27 | c1 = RedisModule_StringPtrLen(s1, &l1); 28 | c2 = RedisModule_StringPtrLen(s2, &l2); 29 | if (l1 != l2) return 0; 30 | 31 | return strncmp(c1, c2, l1) == 0; 32 | } 33 | 34 | int RMUtil_StringEqualsC(RedisModuleString *s1, const char *s2) { 35 | 36 | 37 | const char *c1; 38 | size_t l1, l2 = strlen(s2); 39 | c1 = RedisModule_StringPtrLen(s1, &l1); 40 | if (l1 != l2) return 0; 41 | 42 | return strncmp(c1, s2, l1) == 0; 43 | } 44 | 45 | void RMUtil_StringToLower(RedisModuleString *s) { 46 | 47 | size_t l; 48 | char *c = (char *)RedisModule_StringPtrLen(s, &l); 49 | size_t i; 50 | for (i = 0; i < l; i++) { 51 | *c = tolower(*c); 52 | ++c; 53 | } 54 | 55 | } 56 | 57 | void RMUtil_StringToUpper(RedisModuleString *s) { 58 | size_t l; 59 | char *c = (char *)RedisModule_StringPtrLen(s, &l); 60 | size_t i; 61 | for (i = 0; i < l; i++) { 62 | *c = toupper(*c); 63 | ++c; 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /rmutil/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef __HEAP_H__ 2 | #define __HEAP_H__ 3 | 4 | #include "vector.h" 5 | 6 | 7 | /* Make heap from range 8 | * Rearranges the elements in the range [first,last) in such a way that they form a heap. 9 | * A heap is a way to organize the elements of a range that allows for fast retrieval of the element with the highest 10 | * value at any moment (with pop_heap), even repeatedly, while allowing for fast insertion of new elements (with 11 | * push_heap). 12 | * The element with the highest value is always pointed by first. The order of the other elements depends on the 13 | * particular implementation, but it is consistent throughout all heap-related functions of this header. 14 | * The elements are compared using cmp. 15 | */ 16 | void Make_Heap(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 17 | 18 | 19 | /* Push element into heap range 20 | * Given a heap in the range [first,last-1), this function extends the range considered a heap to [first,last) by 21 | * placing the value in (last-1) into its corresponding location within it. 22 | * A range can be organized into a heap by calling make_heap. After that, its heap properties are preserved if elements 23 | * are added and removed from it using push_heap and pop_heap, respectively. 24 | */ 25 | void Heap_Push(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 26 | 27 | 28 | /* Pop element from heap range 29 | * Rearranges the elements in the heap range [first,last) in such a way that the part considered a heap is shortened 30 | * by one: The element with the highest value is moved to (last-1). 31 | * While the element with the highest value is moved from first to (last-1) (which now is out of the heap), the other 32 | * elements are reorganized in such a way that the range [first,last-1) preserves the properties of a heap. 33 | * A range can be organized into a heap by calling make_heap. After that, its heap properties are preserved if elements 34 | * are added and removed from it using push_heap and pop_heap, respectively. 35 | */ 36 | void Heap_Pop(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 37 | 38 | #endif //__HEAP_H__ 39 | -------------------------------------------------------------------------------- /rmutil/priority_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef __PRIORITY_QUEUE_H__ 2 | #define __PRIORITY_QUEUE_H__ 3 | 4 | #include "vector.h" 5 | 6 | /* Priority queue 7 | * Priority queues are designed such that its first element is always the greatest of the elements it contains. 8 | * This context is similar to a heap, where elements can be inserted at any moment, and only the max heap element can be 9 | * retrieved (the one at the top in the priority queue). 10 | * Priority queues are implemented as Vectors. Elements are popped from the "back" of Vector, which is known as the top 11 | * of the priority queue. 12 | */ 13 | typedef struct { 14 | Vector *v; 15 | 16 | int (*cmp)(void *, void *); 17 | } PriorityQueue; 18 | 19 | /* Construct priority queue 20 | * Constructs a priority_queue container adaptor object. 21 | */ 22 | PriorityQueue *__newPriorityQueueSize(size_t elemSize, size_t cap, int (*cmp)(void *, void *)); 23 | 24 | #define NewPriorityQueue(type, cap, cmp) __newPriorityQueueSize(sizeof(type), cap, cmp) 25 | 26 | /* Return size 27 | * Returns the number of elements in the priority_queue. 28 | */ 29 | size_t Priority_Queue_Size(PriorityQueue *pq); 30 | 31 | /* Access top element 32 | * Copy the top element in the priority_queue to ptr. 33 | * The top element is the element that compares higher in the priority_queue. 34 | */ 35 | int Priority_Queue_Top(PriorityQueue *pq, void *ptr); 36 | 37 | /* Insert element 38 | * Inserts a new element in the priority_queue. 39 | */ 40 | size_t __priority_Queue_PushPtr(PriorityQueue *pq, void *elem); 41 | 42 | #define Priority_Queue_Push(pq, elem) __priority_Queue_PushPtr(pq, &(typeof(elem)){elem}) 43 | 44 | /* Remove top element 45 | * Removes the element on top of the priority_queue, effectively reducing its size by one. The element removed is the 46 | * one with the highest value. 47 | * The value of this element can be retrieved before being popped by calling Priority_Queue_Top. 48 | */ 49 | void Priority_Queue_Pop(PriorityQueue *pq); 50 | 51 | /* free the priority queue and the underlying data. Does not release its elements if 52 | * they are pointers */ 53 | void Priority_Queue_Free(PriorityQueue *pq); 54 | 55 | #endif //__PRIORITY_QUEUE_H__ 56 | -------------------------------------------------------------------------------- /rmutil/sdsalloc.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Redis Labs, Inc 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Redis nor the names of its contributors may be used 16 | * to endorse or promote products derived from this software without 17 | * specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | /* SDS allocator selection. 33 | * 34 | * This file is used in order to change the SDS allocator at compile time. 35 | * Just define the following defines to what you want to use. Also add 36 | * the include of your alternate allocator if needed (not needed in order 37 | * to use the default libc allocator). */ 38 | 39 | //#include "zmalloc.h" 40 | #define s_malloc malloc 41 | #define s_realloc realloc 42 | #define s_free free 43 | -------------------------------------------------------------------------------- /rmutil/vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | 4 | inline int __vector_PushPtr(Vector *v, void *elem) { 5 | if (v->top == v->cap) { 6 | Vector_Resize(v, v->cap ? v->cap * 2 : 1); 7 | } 8 | 9 | __vector_PutPtr(v, v->top, elem); 10 | return v->top; 11 | } 12 | 13 | inline int Vector_Get(Vector *v, size_t pos, void *ptr) { 14 | // return 0 if pos is out of bounds 15 | if (pos >= v->top) { 16 | return 0; 17 | } 18 | 19 | memcpy(ptr, v->data + (pos * v->elemSize), v->elemSize); 20 | return 1; 21 | } 22 | 23 | /* Get the element at the end of the vector, decreasing the size by one */ 24 | inline int Vector_Pop(Vector *v, void *ptr) { 25 | if (v->top > 0) { 26 | if (ptr != NULL) { 27 | Vector_Get(v, v->top - 1, ptr); 28 | } 29 | v->top--; 30 | return 1; 31 | } 32 | return 0; 33 | } 34 | 35 | inline int __vector_PutPtr(Vector *v, size_t pos, void *elem) { 36 | // resize if pos is out of bounds 37 | if (pos >= v->cap) { 38 | Vector_Resize(v, pos + 1); 39 | } 40 | 41 | if (elem) { 42 | memcpy(v->data + pos * v->elemSize, elem, v->elemSize); 43 | } else { 44 | memset(v->data + pos * v->elemSize, 0, v->elemSize); 45 | } 46 | // move the end offset to pos + 1 if we grew 47 | if (pos >= v->top) { 48 | v->top = pos + 1; 49 | } 50 | return 1; 51 | } 52 | 53 | int Vector_Resize(Vector *v, size_t newcap) { 54 | int oldcap = v->cap; 55 | v->cap = newcap; 56 | 57 | v->data = realloc(v->data, v->cap * v->elemSize); 58 | 59 | // If we grew: 60 | // put all zeros at the newly realloc'd part of the vector 61 | if (newcap > oldcap) { 62 | int offset = oldcap * v->elemSize; 63 | memset(v->data + offset, 0, v->cap * v->elemSize - offset); 64 | } 65 | return v->cap; 66 | } 67 | 68 | Vector *__newVectorSize(size_t elemSize, size_t cap) { 69 | Vector *vec = malloc(sizeof(Vector)); 70 | vec->data = calloc(cap, elemSize); 71 | vec->top = 0; 72 | vec->elemSize = elemSize; 73 | vec->cap = cap; 74 | 75 | return vec; 76 | } 77 | 78 | void Vector_Free(Vector *v) { 79 | free(v->data); 80 | free(v); 81 | } 82 | 83 | inline int Vector_Size(Vector *v) { return v->top; } 84 | 85 | /* return the actual capacity */ 86 | inline int Vector_Cap(Vector *v) { return v->cap; } 87 | -------------------------------------------------------------------------------- /rmutil/test_util.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_UTIL_H__ 2 | #define __TEST_UTIL_H__ 3 | 4 | #include "util.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #define RMUtil_Test(f) \ 11 | if (argc < 2 || RMUtil_ArgExists(__STRING(f), argv, argc, 1)) { \ 12 | int rc = f(ctx); \ 13 | if (rc != REDISMODULE_OK) { \ 14 | RedisModule_ReplyWithError(ctx, "Test " __STRING(f) " FAILED"); \ 15 | return REDISMODULE_ERR;\ 16 | }\ 17 | } 18 | 19 | 20 | #define RMUtil_Assert(expr) if (!(expr)) { fprintf (stderr, "Assertion '%s' Failed\n", __STRING(expr)); return REDISMODULE_ERR; } 21 | 22 | #define RMUtil_AssertReplyEquals(rep, cstr) RMUtil_Assert( \ 23 | RMUtil_StringEquals(RedisModule_CreateStringFromCallReply(rep), RedisModule_CreateString(ctx, cstr, strlen(cstr))) \ 24 | ) 25 | # 26 | 27 | /** 28 | * Create an arg list to pass to a redis command handler manually, based on the format in fmt. 29 | * The accepted format specifiers are: 30 | * c - for null terminated c strings 31 | * s - for RedisModuleString* objects 32 | * l - for longs 33 | * 34 | * Example: RMUtil_MakeArgs(ctx, &argc, "clc", "hello", 1337, "world"); 35 | * 36 | * Returns an array of RedisModuleString pointers. The size of the array is store in argcp 37 | */ 38 | RedisModuleString **RMUtil_MakeArgs(RedisModuleCtx *ctx, int *argcp, const char *fmt, ...) { 39 | 40 | va_list ap; 41 | va_start(ap, fmt); 42 | RedisModuleString **argv = calloc(strlen(fmt), sizeof(RedisModuleString*)); 43 | int argc = 0; 44 | const char *p = fmt; 45 | while(*p) { 46 | if (*p == 'c') { 47 | char *cstr = va_arg(ap,char*); 48 | argv[argc++] = RedisModule_CreateString(ctx, cstr, strlen(cstr)); 49 | } else if (*p == 's') { 50 | argv[argc++] = va_arg(ap,void*);; 51 | } else if (*p == 'l') { 52 | long ll = va_arg(ap,long long); 53 | argv[argc++] = RedisModule_CreateStringFromLongLong(ctx, ll); 54 | } else { 55 | goto fmterr; 56 | } 57 | p++; 58 | } 59 | *argcp = argc; 60 | 61 | return argv; 62 | fmterr: 63 | free(argv); 64 | return NULL; 65 | } 66 | 67 | #endif -------------------------------------------------------------------------------- /rmutil/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef __VECTOR_H__ 2 | #define __VECTOR_H__ 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * Generic resizable vector that can be used if you just want to store stuff 9 | * temporarily. 10 | * Works like C++ std::vector with an underlying resizable buffer 11 | */ 12 | typedef struct { 13 | char *data; 14 | size_t elemSize; 15 | size_t cap; 16 | size_t top; 17 | 18 | } Vector; 19 | 20 | /* Create a new vector with element size. This should generally be used 21 | * internall by the NewVector macro */ 22 | Vector *__newVectorSize(size_t elemSize, size_t cap); 23 | 24 | // Put a pointer in the vector. To be used internall by the library 25 | int __vector_PutPtr(Vector *v, size_t pos, void *elem); 26 | 27 | /* 28 | * Create a new vector for a given type and a given capacity. 29 | * e.g. NewVector(int, 0) - empty vector of ints 30 | */ 31 | #define NewVector(type, cap) __newVectorSize(sizeof(type), cap) 32 | 33 | /* 34 | * get the element at index pos. The value is copied in to ptr. If pos is outside 35 | * the vector capacity, we return 0 36 | * otherwise 1 37 | */ 38 | int Vector_Get(Vector *v, size_t pos, void *ptr); 39 | 40 | /* Get the element at the end of the vector, decreasing the size by one */ 41 | int Vector_Pop(Vector *v, void *ptr); 42 | 43 | //#define Vector_Getx(v, pos, ptr) pos < v->cap ? 1 : 0; *ptr = 44 | //*(typeof(ptr))(v->data + v->elemSize*pos) 45 | 46 | /* 47 | * Put an element at pos. 48 | * Note: If pos is outside the vector capacity, we resize it accordingly 49 | */ 50 | #define Vector_Put(v, pos, elem) \ 51 | __vector_PutPtr(v, pos, elem ? &(typeof(elem)){elem} : NULL) 52 | 53 | /* Push an element at the end of v, resizing it if needed. This macro wraps 54 | * __vector_PushPtr */ 55 | #define Vector_Push(v, elem) \ 56 | __vector_PushPtr(v, elem ? &(typeof(elem)){elem} : NULL) 57 | 58 | int __vector_PushPtr(Vector *v, void *elem); 59 | 60 | /* resize capacity of v */ 61 | int Vector_Resize(Vector *v, size_t newcap); 62 | 63 | /* return the used size of the vector, regardless of capacity */ 64 | int Vector_Size(Vector *v); 65 | 66 | /* return the actual capacity */ 67 | int Vector_Cap(Vector *v); 68 | 69 | /* free the vector and the underlying data. Does not release its elements if 70 | * they are pointers*/ 71 | void Vector_Free(Vector *v); 72 | 73 | int __vecotr_PutPtr(Vector *v, size_t pos, void *elem); 74 | 75 | #endif -------------------------------------------------------------------------------- /rmutil/heap.c: -------------------------------------------------------------------------------- 1 | #include "heap.h" 2 | 3 | /* Byte-wise swap two items of size SIZE. */ 4 | #define SWAP(a, b, size) \ 5 | do \ 6 | { \ 7 | register size_t __size = (size); \ 8 | register char *__a = (a), *__b = (b); \ 9 | do \ 10 | { \ 11 | char __tmp = *__a; \ 12 | *__a++ = *__b; \ 13 | *__b++ = __tmp; \ 14 | } while (--__size > 0); \ 15 | } while (0) 16 | 17 | inline char *__vector_GetPtr(Vector *v, size_t pos) { 18 | return v->data + (pos * v->elemSize); 19 | } 20 | 21 | void __sift_up(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 22 | size_t len = last - first; 23 | if (len > 1) { 24 | len = (len - 2) / 2; 25 | size_t ptr = first + len; 26 | if (cmp(__vector_GetPtr(v, ptr), __vector_GetPtr(v, --last)) < 0) { 27 | char t[v->elemSize]; 28 | memcpy(t, __vector_GetPtr(v, last), v->elemSize); 29 | do { 30 | memcpy(__vector_GetPtr(v, last), __vector_GetPtr(v, ptr), v->elemSize); 31 | last = ptr; 32 | if (len == 0) 33 | break; 34 | len = (len - 1) / 2; 35 | ptr = first + len; 36 | } while (cmp(__vector_GetPtr(v, ptr), t) < 0); 37 | memcpy(__vector_GetPtr(v, last), t, v->elemSize); 38 | } 39 | } 40 | } 41 | 42 | void __sift_down(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *), size_t start) { 43 | // left-child of __start is at 2 * __start + 1 44 | // right-child of __start is at 2 * __start + 2 45 | size_t len = last - first; 46 | size_t child = start - first; 47 | 48 | if (len < 2 || (len - 2) / 2 < child) 49 | return; 50 | 51 | child = 2 * child + 1; 52 | 53 | if ((child + 1) < len && cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, first + child + 1)) < 0) { 54 | // right-child exists and is greater than left-child 55 | ++child; 56 | } 57 | 58 | // check if we are in heap-order 59 | if (cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, start)) < 0) 60 | // we are, __start is larger than it's largest child 61 | return; 62 | 63 | char top[v->elemSize]; 64 | memcpy(top, __vector_GetPtr(v, start), v->elemSize); 65 | do { 66 | // we are not in heap-order, swap the parent with it's largest child 67 | memcpy(__vector_GetPtr(v, start), __vector_GetPtr(v, first + child), v->elemSize); 68 | start = first + child; 69 | 70 | if ((len - 2) / 2 < child) 71 | break; 72 | 73 | // recompute the child based off of the updated parent 74 | child = 2 * child + 1; 75 | 76 | if ((child + 1) < len && cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, first + child + 1)) < 0) { 77 | // right-child exists and is greater than left-child 78 | ++child; 79 | } 80 | 81 | // check if we are in heap-order 82 | } while (cmp(__vector_GetPtr(v, first + child), top) >= 0); 83 | memcpy(__vector_GetPtr(v, start), top, v->elemSize); 84 | } 85 | 86 | 87 | void Make_Heap(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 88 | if (last - first > 1) { 89 | // start from the first parent, there is no need to consider children 90 | for (int start = (last - first - 2) / 2; start >= 0; --start) { 91 | __sift_down(v, first, last, cmp, first + start); 92 | } 93 | } 94 | } 95 | 96 | 97 | inline void Heap_Push(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 98 | __sift_up(v, first, last, cmp); 99 | } 100 | 101 | 102 | inline void Heap_Pop(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 103 | if (last - first > 1) { 104 | SWAP(__vector_GetPtr(v, first), __vector_GetPtr(v, --last), v->elemSize); 105 | __sift_down(v, first, last, cmp, first); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rmutil/util.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTIL_H__ 2 | #define __UTIL_H__ 3 | 4 | #include 5 | #include 6 | 7 | /// make sure the response is not NULL or an error, and if it is sends the error to the client and exit the current function 8 | #define RMUTIL_ASSERT_NOERROR(r) \ 9 | if (r == NULL) { \ 10 | return RedisModule_ReplyWithError(ctx,"ERR reply is NULL"); \ 11 | } else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \ 12 | RedisModule_ReplyWithCallReply(ctx,r); \ 13 | return REDISMODULE_ERR; \ 14 | } 15 | 16 | #define __rmutil_register_cmd(ctx, cmd, f, mode) \ 17 | if (RedisModule_CreateCommand(ctx, cmd, f, \ 18 | (!strcmp(mode, "readonly ")) ? "readonly" : \ 19 | (!strcmp(mode, "write ")) ? "write" : mode, \ 20 | 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; 21 | 22 | #define RMUtil_RegisterReadCmd(ctx, cmd, f, ...) __rmutil_register_cmd(ctx, cmd, f, "readonly " __VA_ARGS__) 23 | 24 | #define RMUtil_RegisterWriteCmd(ctx, cmd, f, ...) __rmutil_register_cmd(ctx, cmd, f, "write " __VA_ARGS__) 25 | 26 | /* RedisModule utilities. */ 27 | 28 | /** Return the offset of an arg if it exists in the arg list, or 0 if it's not there */ 29 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset); 30 | 31 | /** 32 | Automatically conver the arg list to corresponding variable pointers according to a given format. 33 | You pass it the command arg list and count, the starting offset, a parsing format, and pointers to the variables. 34 | The format is a string consisting of the following identifiers: 35 | 36 | c -- pointer to a Null terminated C string pointer. 37 | s -- pointer to a RedisModuleString 38 | l -- pointer to Long long integer. 39 | d -- pointer to a Double 40 | * -- do not parse this argument at all 41 | 42 | Example: If I want to parse args[1], args[2] as a long long and double, I do: 43 | double d; 44 | long long l; 45 | RMUtil_ParseArgs(argv, argc, 1, "ld", &l, &d); 46 | */ 47 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...); 48 | 49 | /** 50 | Same as RMUtil_ParseArgs, but only parses the arguments after `token`, if it was found. 51 | This is useful for optional stuff like [LIMIT [offset] [limit]] 52 | */ 53 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, ...); 54 | 55 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap); 56 | 57 | // A single key/value entry in a redis info map 58 | typedef struct { 59 | const char *key; 60 | const char *val; 61 | } RMUtilInfoEntry; 62 | 63 | // Representation of INFO command response, as a list of k/v pairs 64 | typedef struct { 65 | RMUtilInfoEntry *entries; 66 | int numEntries; 67 | } RMUtilInfo; 68 | 69 | /** 70 | * Get redis INFO result and parse it as RMUtilInfo. 71 | * Returns NULL if something goes wrong. 72 | * The resulting object needs to be freed with RMUtilRedisInfo_Free 73 | */ 74 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx); 75 | 76 | /** 77 | * Free an RMUtilInfo object and its entries 78 | */ 79 | void RMUtilRedisInfo_Free(RMUtilInfo *info); 80 | 81 | /** 82 | * Get an integer value from an info object. Returns 1 if the value was found and 83 | * is an integer, 0 otherwise. the value is placed in 'val' 84 | */ 85 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val); 86 | 87 | /** 88 | * Get a string value from an info object. The value is placed in str. 89 | * Returns 1 if the key was found, 0 if not 90 | */ 91 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str); 92 | 93 | /** 94 | * Get a double value from an info object. Returns 1 if the value was found and is 95 | * a correctly formatted double, 0 otherwise. the value is placed in 'd' 96 | */ 97 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d); 98 | 99 | /* 100 | * Returns a call reply array's element given by a space-delimited path. E.g., 101 | * the path "1 2 3" will return the 3rd element from the 2 element of the 1st 102 | * element from an array (or NULL if not found) 103 | */ 104 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath( 105 | RedisModuleCallReply *rep, const char *path); 106 | 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* EXAMPLE.PARSE [SUM ] | [PROD ] 7 | * Demonstrates the automatic arg parsing utility. 8 | * If the command receives "SUM " it returns their sum 9 | * If it receives "PROD " it returns their product 10 | */ 11 | int ParseCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 12 | 13 | // we must have at least 4 args 14 | if (argc < 4) { 15 | return RedisModule_WrongArity(ctx); 16 | } 17 | 18 | // init auto memory for created strings 19 | RedisModule_AutoMemory(ctx); 20 | long long x, y; 21 | 22 | // If we got SUM - return the sum of 2 consecutive arguments 23 | if (RMUtil_ParseArgsAfter("SUM", argv, argc, "ll", &x, &y) == 24 | REDISMODULE_OK) { 25 | RedisModule_ReplyWithLongLong(ctx, x + y); 26 | return REDISMODULE_OK; 27 | } 28 | 29 | // If we got PROD - return the product of 2 consecutive arguments 30 | if (RMUtil_ParseArgsAfter("PROD", argv, argc, "ll", &x, &y) == 31 | REDISMODULE_OK) { 32 | RedisModule_ReplyWithLongLong(ctx, x * y); 33 | return REDISMODULE_OK; 34 | } 35 | 36 | // something is fishy... 37 | RedisModule_ReplyWithError(ctx, "Invalid arguments"); 38 | 39 | return REDISMODULE_ERR; 40 | } 41 | 42 | /* 43 | * example.HGETSET 44 | * Atomically set a value in a HASH key to and return its value before 45 | * the HSET. 46 | * 47 | * Basically atomic HGET + HSET 48 | */ 49 | int HGetSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 50 | 51 | // we need EXACTLY 4 arguments 52 | if (argc != 4) { 53 | return RedisModule_WrongArity(ctx); 54 | } 55 | RedisModule_AutoMemory(ctx); 56 | 57 | // open the key and make sure it's indeed a HASH and not empty 58 | RedisModuleKey *key = 59 | RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE); 60 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_HASH && 61 | RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY) { 62 | return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); 63 | } 64 | 65 | // get the current value of the hash element 66 | RedisModuleCallReply *rep = 67 | RedisModule_Call(ctx, "HGET", "ss", argv[1], argv[2]); 68 | RMUTIL_ASSERT_NOERROR(rep); 69 | 70 | // set the new value of the element 71 | RedisModuleCallReply *srep = 72 | RedisModule_Call(ctx, "HSET", "sss", argv[1], argv[2], argv[3]); 73 | RMUTIL_ASSERT_NOERROR(srep); 74 | 75 | // if the value was null before - we just return null 76 | if (RedisModule_CallReplyType(rep) == REDISMODULE_REPLY_NULL) { 77 | RedisModule_ReplyWithNull(ctx); 78 | return REDISMODULE_OK; 79 | } 80 | 81 | // forward the HGET reply to the client 82 | RedisModule_ReplyWithCallReply(ctx, rep); 83 | return REDISMODULE_OK; 84 | } 85 | 86 | // Test the the PARSE command 87 | int testParse(RedisModuleCtx *ctx) { 88 | 89 | RedisModuleCallReply *r = 90 | RedisModule_Call(ctx, "example.parse", "ccc", "SUM", "5", "2"); 91 | RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_INTEGER); 92 | RMUtil_AssertReplyEquals(r, "7"); 93 | 94 | r = RedisModule_Call(ctx, "example.parse", "ccc", "PROD", "5", "2"); 95 | RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_INTEGER); 96 | RMUtil_AssertReplyEquals(r, "10"); 97 | return 0; 98 | } 99 | 100 | // test the HGETSET command 101 | int testHgetSet(RedisModuleCtx *ctx) { 102 | RedisModuleCallReply *r = 103 | RedisModule_Call(ctx, "example.hgetset", "ccc", "foo", "bar", "baz"); 104 | RMUtil_Assert(RedisModule_CallReplyType(r) != REDISMODULE_REPLY_ERROR); 105 | 106 | r = RedisModule_Call(ctx, "example.hgetset", "ccc", "foo", "bar", "bag"); 107 | RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_STRING); 108 | RMUtil_AssertReplyEquals(r, "baz"); 109 | r = RedisModule_Call(ctx, "example.hgetset", "ccc", "foo", "bar", "bang"); 110 | RMUtil_AssertReplyEquals(r, "bag"); 111 | return 0; 112 | } 113 | 114 | // Unit test entry point for the module 115 | int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 116 | RedisModule_AutoMemory(ctx); 117 | 118 | RMUtil_Test(testParse); 119 | RMUtil_Test(testHgetSet); 120 | 121 | RedisModule_ReplyWithSimpleString(ctx, "PASS"); 122 | return REDISMODULE_OK; 123 | } 124 | 125 | int RedisModule_OnLoad(RedisModuleCtx *ctx) { 126 | 127 | // Register the module itself 128 | if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) == 129 | REDISMODULE_ERR) { 130 | return REDISMODULE_ERR; 131 | } 132 | 133 | // register example.parse - the default registration syntax 134 | if (RedisModule_CreateCommand(ctx, "example.parse", ParseCommand, "readonly", 135 | 1, 1, 1) == REDISMODULE_ERR) { 136 | return REDISMODULE_ERR; 137 | } 138 | 139 | // register example.hgetset - using the shortened utility registration macro 140 | RMUtil_RegisterWriteCmd(ctx, "example.hgetset", HGetSetCommand, "fast"); 141 | 142 | // register the unit test 143 | RMUtil_RegisterWriteCmd(ctx, "example.test", TestModule); 144 | 145 | return REDISMODULE_OK; 146 | } 147 | -------------------------------------------------------------------------------- /rmutil/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "util.h" 11 | 12 | /** 13 | Check if an argument exists in an argument list (argv,argc), starting at offset. 14 | @return 0 if it doesn't exist, otherwise the offset it exists in 15 | */ 16 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset) { 17 | 18 | for (; offset < argc; offset++) { 19 | size_t l; 20 | const char *carg = RedisModule_StringPtrLen(argv[offset], &l); 21 | if (carg != NULL && strcasecmp(carg, arg) == 0) { 22 | return offset; 23 | } 24 | } 25 | return 0; 26 | } 27 | 28 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx) { 29 | 30 | RedisModuleCallReply *r = RedisModule_Call(ctx, "INFO", "c", "all"); 31 | if (r == NULL || RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { 32 | return NULL; 33 | } 34 | 35 | 36 | int cap = 100; // rough estimate of info lines 37 | RMUtilInfo *info = malloc(sizeof(RMUtilInfo)); 38 | info->entries = calloc(cap, sizeof(RMUtilInfoEntry)); 39 | 40 | 41 | int i = 0; 42 | char *text = (char *)RedisModule_StringPtrLen(RedisModule_CreateStringFromCallReply(r), NULL); 43 | char *line = text; 44 | while (line) { 45 | char *line = strsep(&text, "\r\n"); 46 | if (line == NULL) break; 47 | 48 | if (!(*line >= 'a' && *line <= 'z')) { //skip non entry lines 49 | continue; 50 | } 51 | 52 | char *key = strsep(&line, ":"); 53 | info->entries[i].key = key; 54 | info->entries[i].val = line; 55 | printf("Got info '%s' = '%s'\n", key, line); 56 | i++; 57 | if (i >= cap) { 58 | cap *=2; 59 | info->entries = realloc(info->entries, cap*sizeof(RMUtilInfoEntry)); 60 | } 61 | } 62 | info->numEntries = i; 63 | 64 | return info; 65 | 66 | } 67 | void RMUtilRedisInfo_Free(RMUtilInfo *info) { 68 | 69 | free(info->entries); 70 | free(info); 71 | 72 | } 73 | 74 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val) { 75 | 76 | const char *p = NULL; 77 | if (!RMUtilInfo_GetString(info, key, &p)) { 78 | return 0; 79 | } 80 | 81 | *val = strtoll(p, NULL, 10); 82 | if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) || 83 | (errno != 0 && *val == 0)) { 84 | *val = -1; 85 | return 0; 86 | } 87 | 88 | 89 | return 1; 90 | } 91 | 92 | 93 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str) { 94 | int i; 95 | for (i = 0; i < info->numEntries; i++) { 96 | if (!strcmp(key, info->entries[i].key)) { 97 | *str = info->entries[i].val; 98 | return 1; 99 | } 100 | } 101 | return 0; 102 | } 103 | 104 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d) { 105 | const char *p = NULL; 106 | if (!RMUtilInfo_GetString(info, key, &p)) { 107 | printf("not found %s\n", key); 108 | return 0; 109 | } 110 | 111 | *d = strtod(p, NULL); 112 | printf("p: %s, d: %f\n",p,*d); 113 | if ((errno == ERANGE && (*d == HUGE_VAL || *d == -HUGE_VAL)) || 114 | (errno != 0 && *d == 0)) { 115 | return 0; 116 | } 117 | 118 | 119 | return 1; 120 | } 121 | 122 | 123 | /* 124 | c -- pointer to a Null terminated C string pointer. 125 | s -- pointer to a RedisModuleString 126 | l -- pointer to Long long integer. 127 | d -- pointer to a Double 128 | * -- do not parse this argument at all 129 | */ 130 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...) { 131 | va_list ap; 132 | va_start(ap, fmt); 133 | int rc = rmutil_vparseArgs(argv, argc, offset, fmt, ap); 134 | va_end(ap); 135 | return rc; 136 | } 137 | 138 | 139 | // Internal function that parses arguments based on the format described above 140 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap) { 141 | 142 | int i = offset; 143 | char *c = (char *)fmt; 144 | while (*c && i < argc) { 145 | 146 | // read c string 147 | if (*c == 'c') { 148 | char **p = va_arg(ap, char**); 149 | *p = (char *)RedisModule_StringPtrLen(argv[i], NULL); 150 | } else if (*c == 's') { //read redis string 151 | 152 | RedisModuleString **s = va_arg(ap, void*); 153 | *s = argv[i]; 154 | 155 | } else if (*c == 'l') { //read long 156 | long long *l = va_arg(ap, long long *); 157 | 158 | if (RedisModule_StringToLongLong(argv[i], l) != REDISMODULE_OK) { 159 | return REDISMODULE_ERR; 160 | } 161 | } else if (*c == 'd') { //read double 162 | double *d = va_arg(ap, double *); 163 | if (RedisModule_StringToDouble(argv[i], d) != REDISMODULE_OK) { 164 | return REDISMODULE_ERR; 165 | } 166 | } else if (*c == '*') { //skip current arg 167 | //do nothing 168 | } else { 169 | return REDISMODULE_ERR; //WAT? 170 | } 171 | c++; 172 | i++; 173 | } 174 | // if the format is longer than argc, retun an error 175 | if (*c != 0) { 176 | return REDISMODULE_ERR; 177 | } 178 | return REDISMODULE_OK; 179 | 180 | 181 | } 182 | 183 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, ...) { 184 | 185 | int pos = RMUtil_ArgExists(token, argv, argc, 0); 186 | if (pos == 0) { 187 | return REDISMODULE_ERR; 188 | } 189 | 190 | va_list ap; 191 | va_start(ap, fmt); 192 | int rc = rmutil_vparseArgs(argv, argc, pos+1, fmt, ap); 193 | va_end(ap); 194 | return rc; 195 | 196 | } 197 | 198 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath( 199 | RedisModuleCallReply *rep, const char *path) { 200 | if (rep == NULL) return NULL; 201 | 202 | RedisModuleCallReply *ele = rep; 203 | const char *s = path; 204 | char *e; 205 | long idx; 206 | do { 207 | errno = 0; 208 | idx = strtol(s, &e, 10); 209 | 210 | if ((errno == ERANGE && (idx == LONG_MAX || idx == LONG_MIN)) || 211 | (errno != 0 && idx == 0) || 212 | (REDISMODULE_REPLY_ARRAY != RedisModule_CallReplyType(ele)) || 213 | (s == e)) { 214 | ele = NULL; 215 | break; 216 | } 217 | s = e; 218 | ele = RedisModule_CallReplyArrayElement(ele, idx - 1); 219 | 220 | } while ((ele != NULL) && (*e != '\0')); 221 | 222 | return ele; 223 | } -------------------------------------------------------------------------------- /rmutil/sds.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __SDS_H 34 | #define __SDS_H 35 | 36 | #define SDS_MAX_PREALLOC (1024*1024) 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | typedef char *sds; 43 | 44 | /* Note: sdshdr5 is never used, we just access the flags byte directly. 45 | * However is here to document the layout of type 5 SDS strings. */ 46 | struct __attribute__ ((__packed__)) sdshdr5 { 47 | unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 48 | char buf[]; 49 | }; 50 | struct __attribute__ ((__packed__)) sdshdr8 { 51 | uint8_t len; /* used */ 52 | uint8_t alloc; /* excluding the header and null terminator */ 53 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 54 | char buf[]; 55 | }; 56 | struct __attribute__ ((__packed__)) sdshdr16 { 57 | uint16_t len; /* used */ 58 | uint16_t alloc; /* excluding the header and null terminator */ 59 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 60 | char buf[]; 61 | }; 62 | struct __attribute__ ((__packed__)) sdshdr32 { 63 | uint32_t len; /* used */ 64 | uint32_t alloc; /* excluding the header and null terminator */ 65 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 66 | char buf[]; 67 | }; 68 | struct __attribute__ ((__packed__)) sdshdr64 { 69 | uint64_t len; /* used */ 70 | uint64_t alloc; /* excluding the header and null terminator */ 71 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 72 | char buf[]; 73 | }; 74 | 75 | #define SDS_TYPE_5 0 76 | #define SDS_TYPE_8 1 77 | #define SDS_TYPE_16 2 78 | #define SDS_TYPE_32 3 79 | #define SDS_TYPE_64 4 80 | #define SDS_TYPE_MASK 7 81 | #define SDS_TYPE_BITS 3 82 | #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); 83 | #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) 84 | #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) 85 | 86 | static inline size_t sdslen(const sds s) { 87 | unsigned char flags = s[-1]; 88 | switch(flags&SDS_TYPE_MASK) { 89 | case SDS_TYPE_5: 90 | return SDS_TYPE_5_LEN(flags); 91 | case SDS_TYPE_8: 92 | return SDS_HDR(8,s)->len; 93 | case SDS_TYPE_16: 94 | return SDS_HDR(16,s)->len; 95 | case SDS_TYPE_32: 96 | return SDS_HDR(32,s)->len; 97 | case SDS_TYPE_64: 98 | return SDS_HDR(64,s)->len; 99 | } 100 | return 0; 101 | } 102 | 103 | static inline size_t sdsavail(const sds s) { 104 | unsigned char flags = s[-1]; 105 | switch(flags&SDS_TYPE_MASK) { 106 | case SDS_TYPE_5: { 107 | return 0; 108 | } 109 | case SDS_TYPE_8: { 110 | SDS_HDR_VAR(8,s); 111 | return sh->alloc - sh->len; 112 | } 113 | case SDS_TYPE_16: { 114 | SDS_HDR_VAR(16,s); 115 | return sh->alloc - sh->len; 116 | } 117 | case SDS_TYPE_32: { 118 | SDS_HDR_VAR(32,s); 119 | return sh->alloc - sh->len; 120 | } 121 | case SDS_TYPE_64: { 122 | SDS_HDR_VAR(64,s); 123 | return sh->alloc - sh->len; 124 | } 125 | } 126 | return 0; 127 | } 128 | 129 | static inline void sdssetlen(sds s, size_t newlen) { 130 | unsigned char flags = s[-1]; 131 | switch(flags&SDS_TYPE_MASK) { 132 | case SDS_TYPE_5: 133 | { 134 | unsigned char *fp = ((unsigned char*)s)-1; 135 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 136 | } 137 | break; 138 | case SDS_TYPE_8: 139 | SDS_HDR(8,s)->len = newlen; 140 | break; 141 | case SDS_TYPE_16: 142 | SDS_HDR(16,s)->len = newlen; 143 | break; 144 | case SDS_TYPE_32: 145 | SDS_HDR(32,s)->len = newlen; 146 | break; 147 | case SDS_TYPE_64: 148 | SDS_HDR(64,s)->len = newlen; 149 | break; 150 | } 151 | } 152 | 153 | static inline void sdsinclen(sds s, size_t inc) { 154 | unsigned char flags = s[-1]; 155 | switch(flags&SDS_TYPE_MASK) { 156 | case SDS_TYPE_5: 157 | { 158 | unsigned char *fp = ((unsigned char*)s)-1; 159 | unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; 160 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 161 | } 162 | break; 163 | case SDS_TYPE_8: 164 | SDS_HDR(8,s)->len += inc; 165 | break; 166 | case SDS_TYPE_16: 167 | SDS_HDR(16,s)->len += inc; 168 | break; 169 | case SDS_TYPE_32: 170 | SDS_HDR(32,s)->len += inc; 171 | break; 172 | case SDS_TYPE_64: 173 | SDS_HDR(64,s)->len += inc; 174 | break; 175 | } 176 | } 177 | 178 | /* sdsalloc() = sdsavail() + sdslen() */ 179 | static inline size_t sdsalloc(const sds s) { 180 | unsigned char flags = s[-1]; 181 | switch(flags&SDS_TYPE_MASK) { 182 | case SDS_TYPE_5: 183 | return SDS_TYPE_5_LEN(flags); 184 | case SDS_TYPE_8: 185 | return SDS_HDR(8,s)->alloc; 186 | case SDS_TYPE_16: 187 | return SDS_HDR(16,s)->alloc; 188 | case SDS_TYPE_32: 189 | return SDS_HDR(32,s)->alloc; 190 | case SDS_TYPE_64: 191 | return SDS_HDR(64,s)->alloc; 192 | } 193 | return 0; 194 | } 195 | 196 | static inline void sdssetalloc(sds s, size_t newlen) { 197 | unsigned char flags = s[-1]; 198 | switch(flags&SDS_TYPE_MASK) { 199 | case SDS_TYPE_5: 200 | /* Nothing to do, this type has no total allocation info. */ 201 | break; 202 | case SDS_TYPE_8: 203 | SDS_HDR(8,s)->alloc = newlen; 204 | break; 205 | case SDS_TYPE_16: 206 | SDS_HDR(16,s)->alloc = newlen; 207 | break; 208 | case SDS_TYPE_32: 209 | SDS_HDR(32,s)->alloc = newlen; 210 | break; 211 | case SDS_TYPE_64: 212 | SDS_HDR(64,s)->alloc = newlen; 213 | break; 214 | } 215 | } 216 | 217 | sds sdsnewlen(const void *init, size_t initlen); 218 | sds sdsnew(const char *init); 219 | sds sdsempty(void); 220 | sds sdsdup(const sds s); 221 | void sdsfree(sds s); 222 | sds sdsgrowzero(sds s, size_t len); 223 | sds sdscatlen(sds s, const void *t, size_t len); 224 | sds sdscat(sds s, const char *t); 225 | sds sdscatsds(sds s, const sds t); 226 | sds sdscpylen(sds s, const char *t, size_t len); 227 | sds sdscpy(sds s, const char *t); 228 | 229 | sds sdscatvprintf(sds s, const char *fmt, va_list ap); 230 | #ifdef __GNUC__ 231 | sds sdscatprintf(sds s, const char *fmt, ...) 232 | __attribute__((format(printf, 2, 3))); 233 | #else 234 | sds sdscatprintf(sds s, const char *fmt, ...); 235 | #endif 236 | 237 | sds sdscatfmt(sds s, char const *fmt, ...); 238 | sds sdstrim(sds s, const char *cset); 239 | void sdsrange(sds s, int start, int end); 240 | void sdsupdatelen(sds s); 241 | void sdsclear(sds s); 242 | int sdscmp(const sds s1, const sds s2); 243 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); 244 | void sdsfreesplitres(sds *tokens, int count); 245 | void sdstolower(sds s); 246 | void sdstoupper(sds s); 247 | sds sdsfromlonglong(long long value); 248 | sds sdscatrepr(sds s, const char *p, size_t len); 249 | sds *sdssplitargs(const char *line, int *argc); 250 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); 251 | sds sdsjoin(char **argv, int argc, char *sep); 252 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); 253 | 254 | /* Low level functions exposed to the user API */ 255 | sds sdsMakeRoomFor(sds s, size_t addlen); 256 | void sdsIncrLen(sds s, int incr); 257 | sds sdsRemoveFreeSpace(sds s); 258 | size_t sdsAllocSize(sds s); 259 | void *sdsAllocPtr(sds s); 260 | 261 | /* Export the allocator used by SDS to the program using SDS. 262 | * Sometimes the program SDS is linked to, may use a different set of 263 | * allocators, but may want to allocate or free things that SDS will 264 | * respectively free or allocate. */ 265 | void *sds_malloc(size_t size); 266 | void *sds_realloc(void *ptr, size_t size); 267 | void sds_free(void *ptr); 268 | 269 | #ifdef REDIS_TEST 270 | int sdsTest(int argc, char *argv[]); 271 | #endif 272 | 273 | #endif 274 | -------------------------------------------------------------------------------- /include/redismodule.h: -------------------------------------------------------------------------------- 1 | #ifndef REDISMODULE_H 2 | #define REDISMODULE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* ---------------- Defines common between core and modules --------------- */ 9 | 10 | /* Error status return values. */ 11 | #define REDISMODULE_OK 0 12 | #define REDISMODULE_ERR 1 13 | 14 | /* API versions. */ 15 | #define REDISMODULE_APIVER_1 1 16 | 17 | /* API flags and constants */ 18 | #define REDISMODULE_READ (1<<0) 19 | #define REDISMODULE_WRITE (1<<1) 20 | 21 | #define REDISMODULE_LIST_HEAD 0 22 | #define REDISMODULE_LIST_TAIL 1 23 | 24 | /* Key types. */ 25 | #define REDISMODULE_KEYTYPE_EMPTY 0 26 | #define REDISMODULE_KEYTYPE_STRING 1 27 | #define REDISMODULE_KEYTYPE_LIST 2 28 | #define REDISMODULE_KEYTYPE_HASH 3 29 | #define REDISMODULE_KEYTYPE_SET 4 30 | #define REDISMODULE_KEYTYPE_ZSET 5 31 | #define REDISMODULE_KEYTYPE_MODULE 6 32 | 33 | /* Reply types. */ 34 | #define REDISMODULE_REPLY_UNKNOWN -1 35 | #define REDISMODULE_REPLY_STRING 0 36 | #define REDISMODULE_REPLY_ERROR 1 37 | #define REDISMODULE_REPLY_INTEGER 2 38 | #define REDISMODULE_REPLY_ARRAY 3 39 | #define REDISMODULE_REPLY_NULL 4 40 | 41 | /* Postponed array length. */ 42 | #define REDISMODULE_POSTPONED_ARRAY_LEN -1 43 | 44 | /* Expire */ 45 | #define REDISMODULE_NO_EXPIRE -1 46 | 47 | /* Sorted set API flags. */ 48 | #define REDISMODULE_ZADD_XX (1<<0) 49 | #define REDISMODULE_ZADD_NX (1<<1) 50 | #define REDISMODULE_ZADD_ADDED (1<<2) 51 | #define REDISMODULE_ZADD_UPDATED (1<<3) 52 | #define REDISMODULE_ZADD_NOP (1<<4) 53 | 54 | /* Hash API flags. */ 55 | #define REDISMODULE_HASH_NONE 0 56 | #define REDISMODULE_HASH_NX (1<<0) 57 | #define REDISMODULE_HASH_XX (1<<1) 58 | #define REDISMODULE_HASH_CFIELDS (1<<2) 59 | #define REDISMODULE_HASH_EXISTS (1<<3) 60 | 61 | /* A special pointer that we can use between the core and the module to signal 62 | * field deletion, and that is impossible to be a valid pointer. */ 63 | #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) 64 | 65 | /* Error messages. */ 66 | #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" 67 | 68 | #define REDISMODULE_POSITIVE_INFINITE (1.0/0.0) 69 | #define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0) 70 | 71 | /* ------------------------- End of common defines ------------------------ */ 72 | 73 | #ifndef REDISMODULE_CORE 74 | 75 | typedef long long mstime_t; 76 | 77 | /* Incomplete structures for compiler checks but opaque access. */ 78 | typedef struct RedisModuleCtx RedisModuleCtx; 79 | typedef struct RedisModuleKey RedisModuleKey; 80 | typedef struct RedisModuleString RedisModuleString; 81 | typedef struct RedisModuleCallReply RedisModuleCallReply; 82 | typedef struct RedisModuleIO RedisModuleIO; 83 | typedef struct RedisModuleType RedisModuleType; 84 | typedef struct RedisModuleDigest RedisModuleDigest; 85 | 86 | typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc); 87 | 88 | typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); 89 | typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); 90 | typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); 91 | typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); 92 | typedef void (*RedisModuleTypeFreeFunc)(void *value); 93 | 94 | #define REDISMODULE_GET_API(name) \ 95 | RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) 96 | 97 | #define REDISMODULE_API_FUNC(x) (*x) 98 | 99 | 100 | void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); 101 | void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); 102 | void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); 103 | void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); 104 | char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str); 105 | int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *); 106 | int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); 107 | int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver); 108 | int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx); 109 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll); 110 | int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx); 111 | int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid); 112 | void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode); 113 | void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp); 114 | int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp); 115 | size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp); 116 | int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele); 117 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where); 118 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 119 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len); 120 | void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply); 121 | int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply); 122 | long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply); 123 | size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply); 124 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); 125 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); 126 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); 127 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); 128 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); 129 | void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str); 130 | const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); 131 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); 132 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); 133 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); 134 | void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); 135 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); 136 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); 137 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); 138 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); 139 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply); 140 | int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll); 141 | int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d); 142 | void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx); 143 | int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 144 | int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx); 145 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len); 146 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply); 147 | int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key); 148 | int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str); 149 | char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode); 150 | int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen); 151 | mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key); 152 | int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire); 153 | int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); 154 | int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); 155 | int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score); 156 | int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted); 157 | void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key); 158 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 159 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 160 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 161 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 162 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score); 163 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); 164 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); 165 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); 166 | int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); 167 | int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); 168 | int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); 169 | void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); 170 | unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx); 171 | void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes); 172 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeLoadFunc rdb_load, RedisModuleTypeSaveFunc rdb_save, RedisModuleTypeRewriteFunc aof_rewrite, RedisModuleTypeDigestFunc digest, RedisModuleTypeFreeFunc free); 173 | int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); 174 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); 175 | void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); 176 | void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); 177 | uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); 178 | void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); 179 | int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io); 180 | void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...); 181 | void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s); 182 | void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len); 183 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io); 184 | char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr); 185 | void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value); 186 | double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); 187 | void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); 188 | float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); 189 | void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); 190 | void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); 191 | int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); 192 | void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); 193 | int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); 194 | RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); 195 | 196 | /* This is included inline inside each Redis module. */ 197 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); 198 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { 199 | void *getapifuncptr = ((void**)ctx)[0]; 200 | RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; 201 | REDISMODULE_GET_API(Alloc); 202 | REDISMODULE_GET_API(Calloc); 203 | REDISMODULE_GET_API(Free); 204 | REDISMODULE_GET_API(Realloc); 205 | REDISMODULE_GET_API(Strdup); 206 | REDISMODULE_GET_API(CreateCommand); 207 | REDISMODULE_GET_API(SetModuleAttribs); 208 | REDISMODULE_GET_API(WrongArity); 209 | REDISMODULE_GET_API(ReplyWithLongLong); 210 | REDISMODULE_GET_API(ReplyWithError); 211 | REDISMODULE_GET_API(ReplyWithSimpleString); 212 | REDISMODULE_GET_API(ReplyWithArray); 213 | REDISMODULE_GET_API(ReplySetArrayLength); 214 | REDISMODULE_GET_API(ReplyWithStringBuffer); 215 | REDISMODULE_GET_API(ReplyWithString); 216 | REDISMODULE_GET_API(ReplyWithNull); 217 | REDISMODULE_GET_API(ReplyWithCallReply); 218 | REDISMODULE_GET_API(ReplyWithDouble); 219 | REDISMODULE_GET_API(ReplySetArrayLength); 220 | REDISMODULE_GET_API(GetSelectedDb); 221 | REDISMODULE_GET_API(SelectDb); 222 | REDISMODULE_GET_API(OpenKey); 223 | REDISMODULE_GET_API(CloseKey); 224 | REDISMODULE_GET_API(KeyType); 225 | REDISMODULE_GET_API(ValueLength); 226 | REDISMODULE_GET_API(ListPush); 227 | REDISMODULE_GET_API(ListPop); 228 | REDISMODULE_GET_API(StringToLongLong); 229 | REDISMODULE_GET_API(StringToDouble); 230 | REDISMODULE_GET_API(Call); 231 | REDISMODULE_GET_API(CallReplyProto); 232 | REDISMODULE_GET_API(FreeCallReply); 233 | REDISMODULE_GET_API(CallReplyInteger); 234 | REDISMODULE_GET_API(CallReplyType); 235 | REDISMODULE_GET_API(CallReplyLength); 236 | REDISMODULE_GET_API(CallReplyArrayElement); 237 | REDISMODULE_GET_API(CallReplyStringPtr); 238 | REDISMODULE_GET_API(CreateStringFromCallReply); 239 | REDISMODULE_GET_API(CreateString); 240 | REDISMODULE_GET_API(CreateStringFromLongLong); 241 | REDISMODULE_GET_API(CreateStringFromString); 242 | REDISMODULE_GET_API(CreateStringPrintf); 243 | REDISMODULE_GET_API(FreeString); 244 | REDISMODULE_GET_API(StringPtrLen); 245 | REDISMODULE_GET_API(AutoMemory); 246 | REDISMODULE_GET_API(Replicate); 247 | REDISMODULE_GET_API(ReplicateVerbatim); 248 | REDISMODULE_GET_API(DeleteKey); 249 | REDISMODULE_GET_API(StringSet); 250 | REDISMODULE_GET_API(StringDMA); 251 | REDISMODULE_GET_API(StringTruncate); 252 | REDISMODULE_GET_API(GetExpire); 253 | REDISMODULE_GET_API(SetExpire); 254 | REDISMODULE_GET_API(ZsetAdd); 255 | REDISMODULE_GET_API(ZsetIncrby); 256 | REDISMODULE_GET_API(ZsetScore); 257 | REDISMODULE_GET_API(ZsetRem); 258 | REDISMODULE_GET_API(ZsetRangeStop); 259 | REDISMODULE_GET_API(ZsetFirstInScoreRange); 260 | REDISMODULE_GET_API(ZsetLastInScoreRange); 261 | REDISMODULE_GET_API(ZsetFirstInLexRange); 262 | REDISMODULE_GET_API(ZsetLastInLexRange); 263 | REDISMODULE_GET_API(ZsetRangeCurrentElement); 264 | REDISMODULE_GET_API(ZsetRangeNext); 265 | REDISMODULE_GET_API(ZsetRangePrev); 266 | REDISMODULE_GET_API(ZsetRangeEndReached); 267 | REDISMODULE_GET_API(HashSet); 268 | REDISMODULE_GET_API(HashGet); 269 | REDISMODULE_GET_API(IsKeysPositionRequest); 270 | REDISMODULE_GET_API(KeyAtPos); 271 | REDISMODULE_GET_API(GetClientId); 272 | REDISMODULE_GET_API(PoolAlloc); 273 | REDISMODULE_GET_API(CreateDataType); 274 | REDISMODULE_GET_API(ModuleTypeSetValue); 275 | REDISMODULE_GET_API(ModuleTypeGetType); 276 | REDISMODULE_GET_API(ModuleTypeGetValue); 277 | REDISMODULE_GET_API(SaveUnsigned); 278 | REDISMODULE_GET_API(LoadUnsigned); 279 | REDISMODULE_GET_API(SaveSigned); 280 | REDISMODULE_GET_API(LoadSigned); 281 | REDISMODULE_GET_API(SaveString); 282 | REDISMODULE_GET_API(SaveStringBuffer); 283 | REDISMODULE_GET_API(LoadString); 284 | REDISMODULE_GET_API(LoadStringBuffer); 285 | REDISMODULE_GET_API(SaveDouble); 286 | REDISMODULE_GET_API(LoadDouble); 287 | REDISMODULE_GET_API(SaveFloat); 288 | REDISMODULE_GET_API(LoadFloat); 289 | REDISMODULE_GET_API(EmitAOF); 290 | REDISMODULE_GET_API(Log); 291 | REDISMODULE_GET_API(LogIOError); 292 | REDISMODULE_GET_API(StringAppendBuffer); 293 | REDISMODULE_GET_API(RetainString); 294 | REDISMODULE_GET_API(StringCompare); 295 | REDISMODULE_GET_API(GetContextFromIO); 296 | 297 | RedisModule_SetModuleAttribs(ctx,name,ver,apiver); 298 | return REDISMODULE_OK; 299 | } 300 | 301 | #else 302 | 303 | /* Things only defined for the modules core, not exported to modules 304 | * including this file. */ 305 | #define RedisModuleString robj 306 | 307 | #endif /* REDISMODULE_CORE */ 308 | #endif /* REDISMOUDLE_H */ 309 | -------------------------------------------------------------------------------- /rmutil/sds.c: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "sds.h" 39 | #include "sdsalloc.h" 40 | 41 | static inline int sdsHdrSize(char type) { 42 | switch(type&SDS_TYPE_MASK) { 43 | case SDS_TYPE_5: 44 | return sizeof(struct sdshdr5); 45 | case SDS_TYPE_8: 46 | return sizeof(struct sdshdr8); 47 | case SDS_TYPE_16: 48 | return sizeof(struct sdshdr16); 49 | case SDS_TYPE_32: 50 | return sizeof(struct sdshdr32); 51 | case SDS_TYPE_64: 52 | return sizeof(struct sdshdr64); 53 | } 54 | return 0; 55 | } 56 | 57 | static inline char sdsReqType(size_t string_size) { 58 | if (string_size < 32) 59 | return SDS_TYPE_5; 60 | if (string_size < 0xff) 61 | return SDS_TYPE_8; 62 | if (string_size < 0xffff) 63 | return SDS_TYPE_16; 64 | if (string_size < 0xffffffff) 65 | return SDS_TYPE_32; 66 | return SDS_TYPE_64; 67 | } 68 | 69 | /* Create a new sds string with the content specified by the 'init' pointer 70 | * and 'initlen'. 71 | * If NULL is used for 'init' the string is initialized with zero bytes. 72 | * 73 | * The string is always null-termined (all the sds strings are, always) so 74 | * even if you create an sds string with: 75 | * 76 | * mystring = sdsnewlen("abc",3); 77 | * 78 | * You can print the string with printf() as there is an implicit \0 at the 79 | * end of the string. However the string is binary safe and can contain 80 | * \0 characters in the middle, as the length is stored in the sds header. */ 81 | sds sdsnewlen(const void *init, size_t initlen) { 82 | void *sh; 83 | sds s; 84 | char type = sdsReqType(initlen); 85 | /* Empty strings are usually created in order to append. Use type 8 86 | * since type 5 is not good at this. */ 87 | if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; 88 | int hdrlen = sdsHdrSize(type); 89 | unsigned char *fp; /* flags pointer. */ 90 | 91 | sh = s_malloc(hdrlen+initlen+1); 92 | if (!init) 93 | memset(sh, 0, hdrlen+initlen+1); 94 | if (sh == NULL) return NULL; 95 | s = (char*)sh+hdrlen; 96 | fp = ((unsigned char*)s)-1; 97 | switch(type) { 98 | case SDS_TYPE_5: { 99 | *fp = type | (initlen << SDS_TYPE_BITS); 100 | break; 101 | } 102 | case SDS_TYPE_8: { 103 | SDS_HDR_VAR(8,s); 104 | sh->len = initlen; 105 | sh->alloc = initlen; 106 | *fp = type; 107 | break; 108 | } 109 | case SDS_TYPE_16: { 110 | SDS_HDR_VAR(16,s); 111 | sh->len = initlen; 112 | sh->alloc = initlen; 113 | *fp = type; 114 | break; 115 | } 116 | case SDS_TYPE_32: { 117 | SDS_HDR_VAR(32,s); 118 | sh->len = initlen; 119 | sh->alloc = initlen; 120 | *fp = type; 121 | break; 122 | } 123 | case SDS_TYPE_64: { 124 | SDS_HDR_VAR(64,s); 125 | sh->len = initlen; 126 | sh->alloc = initlen; 127 | *fp = type; 128 | break; 129 | } 130 | } 131 | if (initlen && init) 132 | memcpy(s, init, initlen); 133 | s[initlen] = '\0'; 134 | return s; 135 | } 136 | 137 | /* Create an empty (zero length) sds string. Even in this case the string 138 | * always has an implicit null term. */ 139 | sds sdsempty(void) { 140 | return sdsnewlen("",0); 141 | } 142 | 143 | /* Create a new sds string starting from a null terminated C string. */ 144 | sds sdsnew(const char *init) { 145 | size_t initlen = (init == NULL) ? 0 : strlen(init); 146 | return sdsnewlen(init, initlen); 147 | } 148 | 149 | /* Duplicate an sds string. */ 150 | sds sdsdup(const sds s) { 151 | return sdsnewlen(s, sdslen(s)); 152 | } 153 | 154 | /* Free an sds string. No operation is performed if 's' is NULL. */ 155 | void sdsfree(sds s) { 156 | if (s == NULL) return; 157 | s_free((char*)s-sdsHdrSize(s[-1])); 158 | } 159 | 160 | /* Set the sds string length to the length as obtained with strlen(), so 161 | * considering as content only up to the first null term character. 162 | * 163 | * This function is useful when the sds string is hacked manually in some 164 | * way, like in the following example: 165 | * 166 | * s = sdsnew("foobar"); 167 | * s[2] = '\0'; 168 | * sdsupdatelen(s); 169 | * printf("%d\n", sdslen(s)); 170 | * 171 | * The output will be "2", but if we comment out the call to sdsupdatelen() 172 | * the output will be "6" as the string was modified but the logical length 173 | * remains 6 bytes. */ 174 | void sdsupdatelen(sds s) { 175 | int reallen = strlen(s); 176 | sdssetlen(s, reallen); 177 | } 178 | 179 | /* Modify an sds string in-place to make it empty (zero length). 180 | * However all the existing buffer is not discarded but set as free space 181 | * so that next append operations will not require allocations up to the 182 | * number of bytes previously available. */ 183 | void sdsclear(sds s) { 184 | sdssetlen(s, 0); 185 | s[0] = '\0'; 186 | } 187 | 188 | /* Enlarge the free space at the end of the sds string so that the caller 189 | * is sure that after calling this function can overwrite up to addlen 190 | * bytes after the end of the string, plus one more byte for nul term. 191 | * 192 | * Note: this does not change the *length* of the sds string as returned 193 | * by sdslen(), but only the free buffer space we have. */ 194 | sds sdsMakeRoomFor(sds s, size_t addlen) { 195 | void *sh, *newsh; 196 | size_t avail = sdsavail(s); 197 | size_t len, newlen; 198 | char type, oldtype = s[-1] & SDS_TYPE_MASK; 199 | int hdrlen; 200 | 201 | /* Return ASAP if there is enough space left. */ 202 | if (avail >= addlen) return s; 203 | 204 | len = sdslen(s); 205 | sh = (char*)s-sdsHdrSize(oldtype); 206 | newlen = (len+addlen); 207 | if (newlen < SDS_MAX_PREALLOC) 208 | newlen *= 2; 209 | else 210 | newlen += SDS_MAX_PREALLOC; 211 | 212 | type = sdsReqType(newlen); 213 | 214 | /* Don't use type 5: the user is appending to the string and type 5 is 215 | * not able to remember empty space, so sdsMakeRoomFor() must be called 216 | * at every appending operation. */ 217 | if (type == SDS_TYPE_5) type = SDS_TYPE_8; 218 | 219 | hdrlen = sdsHdrSize(type); 220 | if (oldtype==type) { 221 | newsh = s_realloc(sh, hdrlen+newlen+1); 222 | if (newsh == NULL) return NULL; 223 | s = (char*)newsh+hdrlen; 224 | } else { 225 | /* Since the header size changes, need to move the string forward, 226 | * and can't use realloc */ 227 | newsh = s_malloc(hdrlen+newlen+1); 228 | if (newsh == NULL) return NULL; 229 | memcpy((char*)newsh+hdrlen, s, len+1); 230 | s_free(sh); 231 | s = (char*)newsh+hdrlen; 232 | s[-1] = type; 233 | sdssetlen(s, len); 234 | } 235 | sdssetalloc(s, newlen); 236 | return s; 237 | } 238 | 239 | /* Reallocate the sds string so that it has no free space at the end. The 240 | * contained string remains not altered, but next concatenation operations 241 | * will require a reallocation. 242 | * 243 | * After the call, the passed sds string is no longer valid and all the 244 | * references must be substituted with the new pointer returned by the call. */ 245 | sds sdsRemoveFreeSpace(sds s) { 246 | void *sh, *newsh; 247 | char type, oldtype = s[-1] & SDS_TYPE_MASK; 248 | int hdrlen; 249 | size_t len = sdslen(s); 250 | sh = (char*)s-sdsHdrSize(oldtype); 251 | 252 | type = sdsReqType(len); 253 | hdrlen = sdsHdrSize(type); 254 | if (oldtype==type) { 255 | newsh = s_realloc(sh, hdrlen+len+1); 256 | if (newsh == NULL) return NULL; 257 | s = (char*)newsh+hdrlen; 258 | } else { 259 | newsh = s_malloc(hdrlen+len+1); 260 | if (newsh == NULL) return NULL; 261 | memcpy((char*)newsh+hdrlen, s, len+1); 262 | s_free(sh); 263 | s = (char*)newsh+hdrlen; 264 | s[-1] = type; 265 | sdssetlen(s, len); 266 | } 267 | sdssetalloc(s, len); 268 | return s; 269 | } 270 | 271 | /* Return the total size of the allocation of the specifed sds string, 272 | * including: 273 | * 1) The sds header before the pointer. 274 | * 2) The string. 275 | * 3) The free buffer at the end if any. 276 | * 4) The implicit null term. 277 | */ 278 | size_t sdsAllocSize(sds s) { 279 | size_t alloc = sdsalloc(s); 280 | return sdsHdrSize(s[-1])+alloc+1; 281 | } 282 | 283 | /* Return the pointer of the actual SDS allocation (normally SDS strings 284 | * are referenced by the start of the string buffer). */ 285 | void *sdsAllocPtr(sds s) { 286 | return (void*) (s-sdsHdrSize(s[-1])); 287 | } 288 | 289 | /* Increment the sds length and decrements the left free space at the 290 | * end of the string according to 'incr'. Also set the null term 291 | * in the new end of the string. 292 | * 293 | * This function is used in order to fix the string length after the 294 | * user calls sdsMakeRoomFor(), writes something after the end of 295 | * the current string, and finally needs to set the new length. 296 | * 297 | * Note: it is possible to use a negative increment in order to 298 | * right-trim the string. 299 | * 300 | * Usage example: 301 | * 302 | * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the 303 | * following schema, to cat bytes coming from the kernel to the end of an 304 | * sds string without copying into an intermediate buffer: 305 | * 306 | * oldlen = sdslen(s); 307 | * s = sdsMakeRoomFor(s, BUFFER_SIZE); 308 | * nread = read(fd, s+oldlen, BUFFER_SIZE); 309 | * ... check for nread <= 0 and handle it ... 310 | * sdsIncrLen(s, nread); 311 | */ 312 | void sdsIncrLen(sds s, int incr) { 313 | unsigned char flags = s[-1]; 314 | size_t len; 315 | switch(flags&SDS_TYPE_MASK) { 316 | case SDS_TYPE_5: { 317 | unsigned char *fp = ((unsigned char*)s)-1; 318 | unsigned char oldlen = SDS_TYPE_5_LEN(flags); 319 | assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); 320 | *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); 321 | len = oldlen+incr; 322 | break; 323 | } 324 | case SDS_TYPE_8: { 325 | SDS_HDR_VAR(8,s); 326 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); 327 | len = (sh->len += incr); 328 | break; 329 | } 330 | case SDS_TYPE_16: { 331 | SDS_HDR_VAR(16,s); 332 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); 333 | len = (sh->len += incr); 334 | break; 335 | } 336 | case SDS_TYPE_32: { 337 | SDS_HDR_VAR(32,s); 338 | assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); 339 | len = (sh->len += incr); 340 | break; 341 | } 342 | case SDS_TYPE_64: { 343 | SDS_HDR_VAR(64,s); 344 | assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); 345 | len = (sh->len += incr); 346 | break; 347 | } 348 | default: len = 0; /* Just to avoid compilation warnings. */ 349 | } 350 | s[len] = '\0'; 351 | } 352 | 353 | /* Grow the sds to have the specified length. Bytes that were not part of 354 | * the original length of the sds will be set to zero. 355 | * 356 | * if the specified length is smaller than the current length, no operation 357 | * is performed. */ 358 | sds sdsgrowzero(sds s, size_t len) { 359 | size_t curlen = sdslen(s); 360 | 361 | if (len <= curlen) return s; 362 | s = sdsMakeRoomFor(s,len-curlen); 363 | if (s == NULL) return NULL; 364 | 365 | /* Make sure added region doesn't contain garbage */ 366 | memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ 367 | sdssetlen(s, len); 368 | return s; 369 | } 370 | 371 | /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the 372 | * end of the specified sds string 's'. 373 | * 374 | * After the call, the passed sds string is no longer valid and all the 375 | * references must be substituted with the new pointer returned by the call. */ 376 | sds sdscatlen(sds s, const void *t, size_t len) { 377 | size_t curlen = sdslen(s); 378 | 379 | s = sdsMakeRoomFor(s,len); 380 | if (s == NULL) return NULL; 381 | memcpy(s+curlen, t, len); 382 | sdssetlen(s, curlen+len); 383 | s[curlen+len] = '\0'; 384 | return s; 385 | } 386 | 387 | /* Append the specified null termianted C string to the sds string 's'. 388 | * 389 | * After the call, the passed sds string is no longer valid and all the 390 | * references must be substituted with the new pointer returned by the call. */ 391 | sds sdscat(sds s, const char *t) { 392 | return sdscatlen(s, t, strlen(t)); 393 | } 394 | 395 | /* Append the specified sds 't' to the existing sds 's'. 396 | * 397 | * After the call, the modified sds string is no longer valid and all the 398 | * references must be substituted with the new pointer returned by the call. */ 399 | sds sdscatsds(sds s, const sds t) { 400 | return sdscatlen(s, t, sdslen(t)); 401 | } 402 | 403 | /* Destructively modify the sds string 's' to hold the specified binary 404 | * safe string pointed by 't' of length 'len' bytes. */ 405 | sds sdscpylen(sds s, const char *t, size_t len) { 406 | if (sdsalloc(s) < len) { 407 | s = sdsMakeRoomFor(s,len-sdslen(s)); 408 | if (s == NULL) return NULL; 409 | } 410 | memcpy(s, t, len); 411 | s[len] = '\0'; 412 | sdssetlen(s, len); 413 | return s; 414 | } 415 | 416 | /* Like sdscpylen() but 't' must be a null-termined string so that the length 417 | * of the string is obtained with strlen(). */ 418 | sds sdscpy(sds s, const char *t) { 419 | return sdscpylen(s, t, strlen(t)); 420 | } 421 | 422 | /* Helper for sdscatlonglong() doing the actual number -> string 423 | * conversion. 's' must point to a string with room for at least 424 | * SDS_LLSTR_SIZE bytes. 425 | * 426 | * The function returns the length of the null-terminated string 427 | * representation stored at 's'. */ 428 | #define SDS_LLSTR_SIZE 21 429 | int sdsll2str(char *s, long long value) { 430 | char *p, aux; 431 | unsigned long long v; 432 | size_t l; 433 | 434 | /* Generate the string representation, this method produces 435 | * an reversed string. */ 436 | v = (value < 0) ? -value : value; 437 | p = s; 438 | do { 439 | *p++ = '0'+(v%10); 440 | v /= 10; 441 | } while(v); 442 | if (value < 0) *p++ = '-'; 443 | 444 | /* Compute length and add null term. */ 445 | l = p-s; 446 | *p = '\0'; 447 | 448 | /* Reverse the string. */ 449 | p--; 450 | while(s < p) { 451 | aux = *s; 452 | *s = *p; 453 | *p = aux; 454 | s++; 455 | p--; 456 | } 457 | return l; 458 | } 459 | 460 | /* Identical sdsll2str(), but for unsigned long long type. */ 461 | int sdsull2str(char *s, unsigned long long v) { 462 | char *p, aux; 463 | size_t l; 464 | 465 | /* Generate the string representation, this method produces 466 | * an reversed string. */ 467 | p = s; 468 | do { 469 | *p++ = '0'+(v%10); 470 | v /= 10; 471 | } while(v); 472 | 473 | /* Compute length and add null term. */ 474 | l = p-s; 475 | *p = '\0'; 476 | 477 | /* Reverse the string. */ 478 | p--; 479 | while(s < p) { 480 | aux = *s; 481 | *s = *p; 482 | *p = aux; 483 | s++; 484 | p--; 485 | } 486 | return l; 487 | } 488 | 489 | /* Create an sds string from a long long value. It is much faster than: 490 | * 491 | * sdscatprintf(sdsempty(),"%lld\n", value); 492 | */ 493 | sds sdsfromlonglong(long long value) { 494 | char buf[SDS_LLSTR_SIZE]; 495 | int len = sdsll2str(buf,value); 496 | 497 | return sdsnewlen(buf,len); 498 | } 499 | 500 | /* Like sdscatprintf() but gets va_list instead of being variadic. */ 501 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) { 502 | va_list cpy; 503 | char staticbuf[1024], *buf = staticbuf, *t; 504 | size_t buflen = strlen(fmt)*2; 505 | 506 | /* We try to start using a static buffer for speed. 507 | * If not possible we revert to heap allocation. */ 508 | if (buflen > sizeof(staticbuf)) { 509 | buf = s_malloc(buflen); 510 | if (buf == NULL) return NULL; 511 | } else { 512 | buflen = sizeof(staticbuf); 513 | } 514 | 515 | /* Try with buffers two times bigger every time we fail to 516 | * fit the string in the current buffer size. */ 517 | while(1) { 518 | buf[buflen-2] = '\0'; 519 | va_copy(cpy,ap); 520 | vsnprintf(buf, buflen, fmt, cpy); 521 | va_end(cpy); 522 | if (buf[buflen-2] != '\0') { 523 | if (buf != staticbuf) s_free(buf); 524 | buflen *= 2; 525 | buf = s_malloc(buflen); 526 | if (buf == NULL) return NULL; 527 | continue; 528 | } 529 | break; 530 | } 531 | 532 | /* Finally concat the obtained string to the SDS string and return it. */ 533 | t = sdscat(s, buf); 534 | if (buf != staticbuf) s_free(buf); 535 | return t; 536 | } 537 | 538 | /* Append to the sds string 's' a string obtained using printf-alike format 539 | * specifier. 540 | * 541 | * After the call, the modified sds string is no longer valid and all the 542 | * references must be substituted with the new pointer returned by the call. 543 | * 544 | * Example: 545 | * 546 | * s = sdsnew("Sum is: "); 547 | * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). 548 | * 549 | * Often you need to create a string from scratch with the printf-alike 550 | * format. When this is the need, just use sdsempty() as the target string: 551 | * 552 | * s = sdscatprintf(sdsempty(), "... your format ...", args); 553 | */ 554 | sds sdscatprintf(sds s, const char *fmt, ...) { 555 | va_list ap; 556 | char *t; 557 | va_start(ap, fmt); 558 | t = sdscatvprintf(s,fmt,ap); 559 | va_end(ap); 560 | return t; 561 | } 562 | 563 | /* This function is similar to sdscatprintf, but much faster as it does 564 | * not rely on sprintf() family functions implemented by the libc that 565 | * are often very slow. Moreover directly handling the sds string as 566 | * new data is concatenated provides a performance improvement. 567 | * 568 | * However this function only handles an incompatible subset of printf-alike 569 | * format specifiers: 570 | * 571 | * %s - C String 572 | * %S - SDS string 573 | * %i - signed int 574 | * %I - 64 bit signed integer (long long, int64_t) 575 | * %u - unsigned int 576 | * %U - 64 bit unsigned integer (unsigned long long, uint64_t) 577 | * %% - Verbatim "%" character. 578 | */ 579 | sds sdscatfmt(sds s, char const *fmt, ...) { 580 | size_t initlen = sdslen(s); 581 | const char *f = fmt; 582 | int i; 583 | va_list ap; 584 | 585 | va_start(ap,fmt); 586 | f = fmt; /* Next format specifier byte to process. */ 587 | i = initlen; /* Position of the next byte to write to dest str. */ 588 | while(*f) { 589 | char next, *str; 590 | size_t l; 591 | long long num; 592 | unsigned long long unum; 593 | 594 | /* Make sure there is always space for at least 1 char. */ 595 | if (sdsavail(s)==0) { 596 | s = sdsMakeRoomFor(s,1); 597 | } 598 | 599 | switch(*f) { 600 | case '%': 601 | next = *(f+1); 602 | f++; 603 | switch(next) { 604 | case 's': 605 | case 'S': 606 | str = va_arg(ap,char*); 607 | l = (next == 's') ? strlen(str) : sdslen(str); 608 | if (sdsavail(s) < l) { 609 | s = sdsMakeRoomFor(s,l); 610 | } 611 | memcpy(s+i,str,l); 612 | sdsinclen(s,l); 613 | i += l; 614 | break; 615 | case 'i': 616 | case 'I': 617 | if (next == 'i') 618 | num = va_arg(ap,int); 619 | else 620 | num = va_arg(ap,long long); 621 | { 622 | char buf[SDS_LLSTR_SIZE]; 623 | l = sdsll2str(buf,num); 624 | if (sdsavail(s) < l) { 625 | s = sdsMakeRoomFor(s,l); 626 | } 627 | memcpy(s+i,buf,l); 628 | sdsinclen(s,l); 629 | i += l; 630 | } 631 | break; 632 | case 'u': 633 | case 'U': 634 | if (next == 'u') 635 | unum = va_arg(ap,unsigned int); 636 | else 637 | unum = va_arg(ap,unsigned long long); 638 | { 639 | char buf[SDS_LLSTR_SIZE]; 640 | l = sdsull2str(buf,unum); 641 | if (sdsavail(s) < l) { 642 | s = sdsMakeRoomFor(s,l); 643 | } 644 | memcpy(s+i,buf,l); 645 | sdsinclen(s,l); 646 | i += l; 647 | } 648 | break; 649 | default: /* Handle %% and generally %. */ 650 | s[i++] = next; 651 | sdsinclen(s,1); 652 | break; 653 | } 654 | break; 655 | default: 656 | s[i++] = *f; 657 | sdsinclen(s,1); 658 | break; 659 | } 660 | f++; 661 | } 662 | va_end(ap); 663 | 664 | /* Add null-term */ 665 | s[i] = '\0'; 666 | return s; 667 | } 668 | 669 | /* Remove the part of the string from left and from right composed just of 670 | * contiguous characters found in 'cset', that is a null terminted C string. 671 | * 672 | * After the call, the modified sds string is no longer valid and all the 673 | * references must be substituted with the new pointer returned by the call. 674 | * 675 | * Example: 676 | * 677 | * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); 678 | * s = sdstrim(s,"Aa. :"); 679 | * printf("%s\n", s); 680 | * 681 | * Output will be just "Hello World". 682 | */ 683 | sds sdstrim(sds s, const char *cset) { 684 | char *start, *end, *sp, *ep; 685 | size_t len; 686 | 687 | sp = start = s; 688 | ep = end = s+sdslen(s)-1; 689 | while(sp <= end && strchr(cset, *sp)) sp++; 690 | while(ep > sp && strchr(cset, *ep)) ep--; 691 | len = (sp > ep) ? 0 : ((ep-sp)+1); 692 | if (s != sp) memmove(s, sp, len); 693 | s[len] = '\0'; 694 | sdssetlen(s,len); 695 | return s; 696 | } 697 | 698 | /* Turn the string into a smaller (or equal) string containing only the 699 | * substring specified by the 'start' and 'end' indexes. 700 | * 701 | * start and end can be negative, where -1 means the last character of the 702 | * string, -2 the penultimate character, and so forth. 703 | * 704 | * The interval is inclusive, so the start and end characters will be part 705 | * of the resulting string. 706 | * 707 | * The string is modified in-place. 708 | * 709 | * Example: 710 | * 711 | * s = sdsnew("Hello World"); 712 | * sdsrange(s,1,-1); => "ello World" 713 | */ 714 | void sdsrange(sds s, int start, int end) { 715 | size_t newlen, len = sdslen(s); 716 | 717 | if (len == 0) return; 718 | if (start < 0) { 719 | start = len+start; 720 | if (start < 0) start = 0; 721 | } 722 | if (end < 0) { 723 | end = len+end; 724 | if (end < 0) end = 0; 725 | } 726 | newlen = (start > end) ? 0 : (end-start)+1; 727 | if (newlen != 0) { 728 | if (start >= (signed)len) { 729 | newlen = 0; 730 | } else if (end >= (signed)len) { 731 | end = len-1; 732 | newlen = (start > end) ? 0 : (end-start)+1; 733 | } 734 | } else { 735 | start = 0; 736 | } 737 | if (start && newlen) memmove(s, s+start, newlen); 738 | s[newlen] = 0; 739 | sdssetlen(s,newlen); 740 | } 741 | 742 | /* Apply tolower() to every character of the sds string 's'. */ 743 | void sdstolower(sds s) { 744 | int len = sdslen(s), j; 745 | 746 | for (j = 0; j < len; j++) s[j] = tolower(s[j]); 747 | } 748 | 749 | /* Apply toupper() to every character of the sds string 's'. */ 750 | void sdstoupper(sds s) { 751 | int len = sdslen(s), j; 752 | 753 | for (j = 0; j < len; j++) s[j] = toupper(s[j]); 754 | } 755 | 756 | /* Compare two sds strings s1 and s2 with memcmp(). 757 | * 758 | * Return value: 759 | * 760 | * positive if s1 > s2. 761 | * negative if s1 < s2. 762 | * 0 if s1 and s2 are exactly the same binary string. 763 | * 764 | * If two strings share exactly the same prefix, but one of the two has 765 | * additional characters, the longer string is considered to be greater than 766 | * the smaller one. */ 767 | int sdscmp(const sds s1, const sds s2) { 768 | size_t l1, l2, minlen; 769 | int cmp; 770 | 771 | l1 = sdslen(s1); 772 | l2 = sdslen(s2); 773 | minlen = (l1 < l2) ? l1 : l2; 774 | cmp = memcmp(s1,s2,minlen); 775 | if (cmp == 0) return l1-l2; 776 | return cmp; 777 | } 778 | 779 | /* Split 's' with separator in 'sep'. An array 780 | * of sds strings is returned. *count will be set 781 | * by reference to the number of tokens returned. 782 | * 783 | * On out of memory, zero length string, zero length 784 | * separator, NULL is returned. 785 | * 786 | * Note that 'sep' is able to split a string using 787 | * a multi-character separator. For example 788 | * sdssplit("foo_-_bar","_-_"); will return two 789 | * elements "foo" and "bar". 790 | * 791 | * This version of the function is binary-safe but 792 | * requires length arguments. sdssplit() is just the 793 | * same function but for zero-terminated strings. 794 | */ 795 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { 796 | int elements = 0, slots = 5, start = 0, j; 797 | sds *tokens; 798 | 799 | if (seplen < 1 || len < 0) return NULL; 800 | 801 | tokens = s_malloc(sizeof(sds)*slots); 802 | if (tokens == NULL) return NULL; 803 | 804 | if (len == 0) { 805 | *count = 0; 806 | return tokens; 807 | } 808 | for (j = 0; j < (len-(seplen-1)); j++) { 809 | /* make sure there is room for the next element and the final one */ 810 | if (slots < elements+2) { 811 | sds *newtokens; 812 | 813 | slots *= 2; 814 | newtokens = s_realloc(tokens,sizeof(sds)*slots); 815 | if (newtokens == NULL) goto cleanup; 816 | tokens = newtokens; 817 | } 818 | /* search the separator */ 819 | if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { 820 | tokens[elements] = sdsnewlen(s+start,j-start); 821 | if (tokens[elements] == NULL) goto cleanup; 822 | elements++; 823 | start = j+seplen; 824 | j = j+seplen-1; /* skip the separator */ 825 | } 826 | } 827 | /* Add the final element. We are sure there is room in the tokens array. */ 828 | tokens[elements] = sdsnewlen(s+start,len-start); 829 | if (tokens[elements] == NULL) goto cleanup; 830 | elements++; 831 | *count = elements; 832 | return tokens; 833 | 834 | cleanup: 835 | { 836 | int i; 837 | for (i = 0; i < elements; i++) sdsfree(tokens[i]); 838 | s_free(tokens); 839 | *count = 0; 840 | return NULL; 841 | } 842 | } 843 | 844 | /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ 845 | void sdsfreesplitres(sds *tokens, int count) { 846 | if (!tokens) return; 847 | while(count--) 848 | sdsfree(tokens[count]); 849 | s_free(tokens); 850 | } 851 | 852 | /* Append to the sds string "s" an escaped string representation where 853 | * all the non-printable characters (tested with isprint()) are turned into 854 | * escapes in the form "\n\r\a...." or "\x". 855 | * 856 | * After the call, the modified sds string is no longer valid and all the 857 | * references must be substituted with the new pointer returned by the call. */ 858 | sds sdscatrepr(sds s, const char *p, size_t len) { 859 | s = sdscatlen(s,"\"",1); 860 | while(len--) { 861 | switch(*p) { 862 | case '\\': 863 | case '"': 864 | s = sdscatprintf(s,"\\%c",*p); 865 | break; 866 | case '\n': s = sdscatlen(s,"\\n",2); break; 867 | case '\r': s = sdscatlen(s,"\\r",2); break; 868 | case '\t': s = sdscatlen(s,"\\t",2); break; 869 | case '\a': s = sdscatlen(s,"\\a",2); break; 870 | case '\b': s = sdscatlen(s,"\\b",2); break; 871 | default: 872 | if (isprint(*p)) 873 | s = sdscatprintf(s,"%c",*p); 874 | else 875 | s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); 876 | break; 877 | } 878 | p++; 879 | } 880 | return sdscatlen(s,"\"",1); 881 | } 882 | 883 | /* Helper function for sdssplitargs() that returns non zero if 'c' 884 | * is a valid hex digit. */ 885 | int is_hex_digit(char c) { 886 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || 887 | (c >= 'A' && c <= 'F'); 888 | } 889 | 890 | /* Helper function for sdssplitargs() that converts a hex digit into an 891 | * integer from 0 to 15 */ 892 | int hex_digit_to_int(char c) { 893 | switch(c) { 894 | case '0': return 0; 895 | case '1': return 1; 896 | case '2': return 2; 897 | case '3': return 3; 898 | case '4': return 4; 899 | case '5': return 5; 900 | case '6': return 6; 901 | case '7': return 7; 902 | case '8': return 8; 903 | case '9': return 9; 904 | case 'a': case 'A': return 10; 905 | case 'b': case 'B': return 11; 906 | case 'c': case 'C': return 12; 907 | case 'd': case 'D': return 13; 908 | case 'e': case 'E': return 14; 909 | case 'f': case 'F': return 15; 910 | default: return 0; 911 | } 912 | } 913 | 914 | /* Split a line into arguments, where every argument can be in the 915 | * following programming-language REPL-alike form: 916 | * 917 | * foo bar "newline are supported\n" and "\xff\x00otherstuff" 918 | * 919 | * The number of arguments is stored into *argc, and an array 920 | * of sds is returned. 921 | * 922 | * The caller should free the resulting array of sds strings with 923 | * sdsfreesplitres(). 924 | * 925 | * Note that sdscatrepr() is able to convert back a string into 926 | * a quoted string in the same format sdssplitargs() is able to parse. 927 | * 928 | * The function returns the allocated tokens on success, even when the 929 | * input string is empty, or NULL if the input contains unbalanced 930 | * quotes or closed quotes followed by non space characters 931 | * as in: "foo"bar or "foo' 932 | */ 933 | sds *sdssplitargs(const char *line, int *argc) { 934 | const char *p = line; 935 | char *current = NULL; 936 | char **vector = NULL; 937 | 938 | *argc = 0; 939 | while(1) { 940 | /* skip blanks */ 941 | while(*p && isspace(*p)) p++; 942 | if (*p) { 943 | /* get a token */ 944 | int inq=0; /* set to 1 if we are in "quotes" */ 945 | int insq=0; /* set to 1 if we are in 'single quotes' */ 946 | int done=0; 947 | 948 | if (current == NULL) current = sdsempty(); 949 | while(!done) { 950 | if (inq) { 951 | if (*p == '\\' && *(p+1) == 'x' && 952 | is_hex_digit(*(p+2)) && 953 | is_hex_digit(*(p+3))) 954 | { 955 | unsigned char byte; 956 | 957 | byte = (hex_digit_to_int(*(p+2))*16)+ 958 | hex_digit_to_int(*(p+3)); 959 | current = sdscatlen(current,(char*)&byte,1); 960 | p += 3; 961 | } else if (*p == '\\' && *(p+1)) { 962 | char c; 963 | 964 | p++; 965 | switch(*p) { 966 | case 'n': c = '\n'; break; 967 | case 'r': c = '\r'; break; 968 | case 't': c = '\t'; break; 969 | case 'b': c = '\b'; break; 970 | case 'a': c = '\a'; break; 971 | default: c = *p; break; 972 | } 973 | current = sdscatlen(current,&c,1); 974 | } else if (*p == '"') { 975 | /* closing quote must be followed by a space or 976 | * nothing at all. */ 977 | if (*(p+1) && !isspace(*(p+1))) goto err; 978 | done=1; 979 | } else if (!*p) { 980 | /* unterminated quotes */ 981 | goto err; 982 | } else { 983 | current = sdscatlen(current,p,1); 984 | } 985 | } else if (insq) { 986 | if (*p == '\\' && *(p+1) == '\'') { 987 | p++; 988 | current = sdscatlen(current,"'",1); 989 | } else if (*p == '\'') { 990 | /* closing quote must be followed by a space or 991 | * nothing at all. */ 992 | if (*(p+1) && !isspace(*(p+1))) goto err; 993 | done=1; 994 | } else if (!*p) { 995 | /* unterminated quotes */ 996 | goto err; 997 | } else { 998 | current = sdscatlen(current,p,1); 999 | } 1000 | } else { 1001 | switch(*p) { 1002 | case ' ': 1003 | case '\n': 1004 | case '\r': 1005 | case '\t': 1006 | case '\0': 1007 | done=1; 1008 | break; 1009 | case '"': 1010 | inq=1; 1011 | break; 1012 | case '\'': 1013 | insq=1; 1014 | break; 1015 | default: 1016 | current = sdscatlen(current,p,1); 1017 | break; 1018 | } 1019 | } 1020 | if (*p) p++; 1021 | } 1022 | /* add the token to the vector */ 1023 | vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); 1024 | vector[*argc] = current; 1025 | (*argc)++; 1026 | current = NULL; 1027 | } else { 1028 | /* Even on empty input string return something not NULL. */ 1029 | if (vector == NULL) vector = s_malloc(sizeof(void*)); 1030 | return vector; 1031 | } 1032 | } 1033 | 1034 | err: 1035 | while((*argc)--) 1036 | sdsfree(vector[*argc]); 1037 | s_free(vector); 1038 | if (current) sdsfree(current); 1039 | *argc = 0; 1040 | return NULL; 1041 | } 1042 | 1043 | /* Modify the string substituting all the occurrences of the set of 1044 | * characters specified in the 'from' string to the corresponding character 1045 | * in the 'to' array. 1046 | * 1047 | * For instance: sdsmapchars(mystring, "ho", "01", 2) 1048 | * will have the effect of turning the string "hello" into "0ell1". 1049 | * 1050 | * The function returns the sds string pointer, that is always the same 1051 | * as the input pointer since no resize is needed. */ 1052 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { 1053 | size_t j, i, l = sdslen(s); 1054 | 1055 | for (j = 0; j < l; j++) { 1056 | for (i = 0; i < setlen; i++) { 1057 | if (s[j] == from[i]) { 1058 | s[j] = to[i]; 1059 | break; 1060 | } 1061 | } 1062 | } 1063 | return s; 1064 | } 1065 | 1066 | /* Join an array of C strings using the specified separator (also a C string). 1067 | * Returns the result as an sds string. */ 1068 | sds sdsjoin(char **argv, int argc, char *sep) { 1069 | sds join = sdsempty(); 1070 | int j; 1071 | 1072 | for (j = 0; j < argc; j++) { 1073 | join = sdscat(join, argv[j]); 1074 | if (j != argc-1) join = sdscat(join,sep); 1075 | } 1076 | return join; 1077 | } 1078 | 1079 | /* Like sdsjoin, but joins an array of SDS strings. */ 1080 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { 1081 | sds join = sdsempty(); 1082 | int j; 1083 | 1084 | for (j = 0; j < argc; j++) { 1085 | join = sdscatsds(join, argv[j]); 1086 | if (j != argc-1) join = sdscatlen(join,sep,seplen); 1087 | } 1088 | return join; 1089 | } 1090 | 1091 | /* Wrappers to the allocators used by SDS. Note that SDS will actually 1092 | * just use the macros defined into sdsalloc.h in order to avoid to pay 1093 | * the overhead of function calls. Here we define these wrappers only for 1094 | * the programs SDS is linked to, if they want to touch the SDS internals 1095 | * even if they use a different allocator. */ 1096 | void *sds_malloc(size_t size) { return s_malloc(size); } 1097 | void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } 1098 | void sds_free(void *ptr) { s_free(ptr); } 1099 | 1100 | #if defined(SDS_TEST_MAIN) 1101 | #include 1102 | #include "testhelp.h" 1103 | #include "limits.h" 1104 | 1105 | #define UNUSED(x) (void)(x) 1106 | int sdsTest(void) { 1107 | { 1108 | sds x = sdsnew("foo"), y; 1109 | 1110 | test_cond("Create a string and obtain the length", 1111 | sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) 1112 | 1113 | sdsfree(x); 1114 | x = sdsnewlen("foo",2); 1115 | test_cond("Create a string with specified length", 1116 | sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) 1117 | 1118 | x = sdscat(x,"bar"); 1119 | test_cond("Strings concatenation", 1120 | sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); 1121 | 1122 | x = sdscpy(x,"a"); 1123 | test_cond("sdscpy() against an originally longer string", 1124 | sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) 1125 | 1126 | x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); 1127 | test_cond("sdscpy() against an originally shorter string", 1128 | sdslen(x) == 33 && 1129 | memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) 1130 | 1131 | sdsfree(x); 1132 | x = sdscatprintf(sdsempty(),"%d",123); 1133 | test_cond("sdscatprintf() seems working in the base case", 1134 | sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) 1135 | 1136 | sdsfree(x); 1137 | x = sdsnew("--"); 1138 | x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); 1139 | test_cond("sdscatfmt() seems working in the base case", 1140 | sdslen(x) == 60 && 1141 | memcmp(x,"--Hello Hi! World -9223372036854775808," 1142 | "9223372036854775807--",60) == 0) 1143 | printf("[%s]\n",x); 1144 | 1145 | sdsfree(x); 1146 | x = sdsnew("--"); 1147 | x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); 1148 | test_cond("sdscatfmt() seems working with unsigned numbers", 1149 | sdslen(x) == 35 && 1150 | memcmp(x,"--4294967295,18446744073709551615--",35) == 0) 1151 | 1152 | sdsfree(x); 1153 | x = sdsnew(" x "); 1154 | sdstrim(x," x"); 1155 | test_cond("sdstrim() works when all chars match", 1156 | sdslen(x) == 0) 1157 | 1158 | sdsfree(x); 1159 | x = sdsnew(" x "); 1160 | sdstrim(x," "); 1161 | test_cond("sdstrim() works when a single char remains", 1162 | sdslen(x) == 1 && x[0] == 'x') 1163 | 1164 | sdsfree(x); 1165 | x = sdsnew("xxciaoyyy"); 1166 | sdstrim(x,"xy"); 1167 | test_cond("sdstrim() correctly trims characters", 1168 | sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) 1169 | 1170 | y = sdsdup(x); 1171 | sdsrange(y,1,1); 1172 | test_cond("sdsrange(...,1,1)", 1173 | sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) 1174 | 1175 | sdsfree(y); 1176 | y = sdsdup(x); 1177 | sdsrange(y,1,-1); 1178 | test_cond("sdsrange(...,1,-1)", 1179 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) 1180 | 1181 | sdsfree(y); 1182 | y = sdsdup(x); 1183 | sdsrange(y,-2,-1); 1184 | test_cond("sdsrange(...,-2,-1)", 1185 | sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) 1186 | 1187 | sdsfree(y); 1188 | y = sdsdup(x); 1189 | sdsrange(y,2,1); 1190 | test_cond("sdsrange(...,2,1)", 1191 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0) 1192 | 1193 | sdsfree(y); 1194 | y = sdsdup(x); 1195 | sdsrange(y,1,100); 1196 | test_cond("sdsrange(...,1,100)", 1197 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) 1198 | 1199 | sdsfree(y); 1200 | y = sdsdup(x); 1201 | sdsrange(y,100,100); 1202 | test_cond("sdsrange(...,100,100)", 1203 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0) 1204 | 1205 | sdsfree(y); 1206 | sdsfree(x); 1207 | x = sdsnew("foo"); 1208 | y = sdsnew("foa"); 1209 | test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) 1210 | 1211 | sdsfree(y); 1212 | sdsfree(x); 1213 | x = sdsnew("bar"); 1214 | y = sdsnew("bar"); 1215 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) 1216 | 1217 | sdsfree(y); 1218 | sdsfree(x); 1219 | x = sdsnew("aar"); 1220 | y = sdsnew("bar"); 1221 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) 1222 | 1223 | sdsfree(y); 1224 | sdsfree(x); 1225 | x = sdsnewlen("\a\n\0foo\r",7); 1226 | y = sdscatrepr(sdsempty(),x,sdslen(x)); 1227 | test_cond("sdscatrepr(...data...)", 1228 | memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) 1229 | 1230 | { 1231 | unsigned int oldfree; 1232 | char *p; 1233 | int step = 10, j, i; 1234 | 1235 | sdsfree(x); 1236 | sdsfree(y); 1237 | x = sdsnew("0"); 1238 | test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); 1239 | 1240 | /* Run the test a few times in order to hit the first two 1241 | * SDS header types. */ 1242 | for (i = 0; i < 10; i++) { 1243 | int oldlen = sdslen(x); 1244 | x = sdsMakeRoomFor(x,step); 1245 | int type = x[-1]&SDS_TYPE_MASK; 1246 | 1247 | test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); 1248 | if (type != SDS_TYPE_5) { 1249 | test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); 1250 | oldfree = sdsavail(x); 1251 | } 1252 | p = x+oldlen; 1253 | for (j = 0; j < step; j++) { 1254 | p[j] = 'A'+j; 1255 | } 1256 | sdsIncrLen(x,step); 1257 | } 1258 | test_cond("sdsMakeRoomFor() content", 1259 | memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); 1260 | test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); 1261 | 1262 | sdsfree(x); 1263 | } 1264 | } 1265 | test_report() 1266 | return 0; 1267 | } 1268 | #endif 1269 | 1270 | #ifdef SDS_TEST_MAIN 1271 | int main(void) { 1272 | return sdsTest(); 1273 | } 1274 | #endif 1275 | --------------------------------------------------------------------------------