diff --git a/rmutil/Makefile b/rmutil/Makefile index 09e023b..cf7ebe7 100644 --- a/rmutil/Makefile +++ b/rmutil/Makefile @@ -7,7 +7,7 @@ CFLAGS ?= -g -fPIC -O3 -std=gnu99 -Wall -Wno-unused-function CFLAGS += -I$(RM_INCLUDE_DIR) CC=gcc -OBJS=util.o strings.o sds.o vector.o alloc.o periodic.o +OBJS=util.o strings.o sds.o vector.o alloc.o periodic.o args.o all: librmutil.a diff --git a/rmutil/args.c b/rmutil/args.c new file mode 100644 index 0000000..9b5c93f --- /dev/null +++ b/rmutil/args.c @@ -0,0 +1,273 @@ +#include "args.h" +#include "redismodule.h" +#include +#include +#include +#include + +int AC_Advance(ArgsCursor *ac) { + return AC_AdvanceBy(ac, 1); +} + +int AC_AdvanceBy(ArgsCursor *ac, size_t by) { + if (ac->offset + by > ac->argc) { + return AC_ERR_NOARG; + } else { + ac->offset += by; + } + return AC_OK; +} + +int AC_AdvanceIfMatch(ArgsCursor *ac, const char *s) { + const char *cur; + if (AC_IsAtEnd(ac)) { + return 0; + } + + int rv = AC_GetString(ac, &cur, NULL, AC_F_NOADVANCE); + assert(rv == AC_OK); + rv = !strcasecmp(s, cur); + if (rv) { + AC_Advance(ac); + } + return rv; +} + +#define MAYBE_ADVANCE() \ + if (!(flags & AC_F_NOADVANCE)) { \ + AC_Advance(ac); \ + } + +static int tryReadAsDouble(ArgsCursor *ac, long long *ll, int flags) { + double dTmp = 0.0; + if (AC_GetDouble(ac, &dTmp, flags | AC_F_NOADVANCE) != AC_OK) { + return AC_ERR_PARSE; + } + if (flags & AC_F_COALESCE) { + *ll = dTmp; + return AC_OK; + } + + if ((double)(long long)dTmp != dTmp) { + return AC_ERR_PARSE; + } else { + *ll = dTmp; + return AC_OK; + } +} + +int AC_GetLongLong(ArgsCursor *ac, long long *ll, int flags) { + if (ac->offset == ac->argc) { + return AC_ERR_NOARG; + } + + int hasErr = 0; + // Try to parse the number as a normal integer first. If that fails, try + // to parse it as a double. This will work if the number is in the format of + // 3.00, OR if the number is in the format of 3.14 *AND* AC_F_COALESCE is set. + if (ac->type == AC_TYPE_RSTRING) { + if (RedisModule_StringToLongLong(AC_CURRENT(ac), ll) == REDISMODULE_ERR) { + hasErr = 1; + } + } else { + char *endptr = AC_CURRENT(ac); + *ll = strtoll(AC_CURRENT(ac), &endptr, 10); + if (*endptr != '\0' || *ll == LLONG_MIN || *ll == LLONG_MAX) { + hasErr = 1; + } + } + + if (hasErr && tryReadAsDouble(ac, ll, flags) != AC_OK) { + return AC_ERR_PARSE; + } + + if ((flags & AC_F_GE0) && *ll < 0) { + return AC_ERR_ELIMIT; + } + // Do validation + if ((flags & AC_F_GE1) && *ll < 1) { + return AC_ERR_ELIMIT; + } + MAYBE_ADVANCE(); + return AC_OK; +} + +#define GEN_AC_FUNC(name, T, minVal, maxVal, isUnsigned) \ + int name(ArgsCursor *ac, T *p, int flags) { \ + if (isUnsigned) { \ + flags |= AC_F_GE0; \ + } \ + long long ll; \ + int rv = AC_GetLongLong(ac, &ll, flags | AC_F_NOADVANCE); \ + if (rv) { \ + return rv; \ + } \ + if (ll > maxVal || ll < minVal) { \ + return AC_ERR_ELIMIT; \ + } \ + *p = ll; \ + MAYBE_ADVANCE(); \ + return AC_OK; \ + } + +GEN_AC_FUNC(AC_GetUnsignedLongLong, unsigned long long, 0, LLONG_MAX, 1) +GEN_AC_FUNC(AC_GetUnsigned, unsigned, 0, UINT_MAX, 1) +GEN_AC_FUNC(AC_GetInt, int, INT_MIN, INT_MAX, 0) +GEN_AC_FUNC(AC_GetU32, uint32_t, 0, UINT32_MAX, 1) +GEN_AC_FUNC(AC_GetU64, uint64_t, 0, UINT64_MAX, 1) + +int AC_GetDouble(ArgsCursor *ac, double *d, int flags) { + if (ac->type == AC_TYPE_RSTRING) { + if (RedisModule_StringToDouble(ac->objs[ac->offset], d) != REDISMODULE_OK) { + return AC_ERR_PARSE; + } + } else { + char *endptr = AC_CURRENT(ac); + *d = strtod(AC_CURRENT(ac), &endptr); + if (*endptr != '\0' || *d == HUGE_VAL || *d == -HUGE_VAL) { + return AC_ERR_PARSE; + } + } + if ((flags & AC_F_GE0) && *d < 0.0) { + return AC_ERR_ELIMIT; + } + if ((flags & AC_F_GE1) && *d < 1.0) { + return AC_ERR_ELIMIT; + } + MAYBE_ADVANCE(); + return AC_OK; +} + +int AC_GetRString(ArgsCursor *ac, RedisModuleString **s, int flags) { + assert(ac->type == AC_TYPE_RSTRING); + if (ac->offset == ac->argc) { + return AC_ERR_NOARG; + } + *s = AC_CURRENT(ac); + MAYBE_ADVANCE(); + return AC_OK; +} + +int AC_GetString(ArgsCursor *ac, const char **s, size_t *n, int flags) { + if (ac->offset == ac->argc) { + return AC_ERR_NOARG; + } + if (ac->type == AC_TYPE_RSTRING) { + *s = RedisModule_StringPtrLen(AC_CURRENT(ac), n); + } else { + *s = AC_CURRENT(ac); + if (n) { + if (ac->type == AC_TYPE_SDS) { + *n = sdslen((const sds)*s); + } else { + *n = strlen(*s); + } + } + } + MAYBE_ADVANCE(); + return AC_OK; +} + +const char *AC_GetStringNC(ArgsCursor *ac, size_t *len) { + const char *s = NULL; + if (AC_GetString(ac, &s, len, 0) != AC_OK) { + return NULL; + } + return s; +} + +int AC_GetVarArgs(ArgsCursor *ac, ArgsCursor *dst) { + unsigned nargs; + int rv = AC_GetUnsigned(ac, &nargs, 0); + if (rv != AC_OK) { + return rv; + } + return AC_GetSlice(ac, dst, nargs); +} + +int AC_GetSlice(ArgsCursor *ac, ArgsCursor *dst, size_t n) { + if (n > AC_NumRemaining(ac)) { + return AC_ERR_NOARG; + } + + dst->objs = ac->objs + ac->offset; + dst->argc = n; + dst->offset = 0; + dst->type = ac->type; + AC_AdvanceBy(ac, n); + return 0; +} + +static int parseSingleSpec(ArgsCursor *ac, ACArgSpec *spec) { + switch (spec->type) { + case AC_ARGTYPE_BOOLFLAG: + *(int *)spec->target = 1; + return AC_OK; + case AC_ARGTYPE_BITFLAG: + *(uint32_t *)(spec->target) |= spec->slicelen; + return AC_OK; + case AC_ARGTYPE_UNFLAG: + *(uint32_t *)spec->target &= ~spec->slicelen; + return AC_OK; + case AC_ARGTYPE_DOUBLE: + return AC_GetDouble(ac, spec->target, spec->intflags); + case AC_ARGTYPE_INT: + return AC_GetInt(ac, spec->target, spec->intflags); + case AC_ARGTYPE_LLONG: + return AC_GetLongLong(ac, spec->target, spec->intflags); + case AC_ARGTYPE_ULLONG: + return AC_GetUnsignedLongLong(ac, spec->target, spec->intflags); + case AC_ARGTYPE_UINT: + return AC_GetUnsigned(ac, spec->target, spec->intflags); + case AC_ARGTYPE_STRING: + return AC_GetString(ac, spec->target, spec->len, 0); + case AC_ARGTYPE_RSTRING: + return AC_GetRString(ac, spec->target, 0); + case AC_ARGTYPE_SUBARGS: + return AC_GetVarArgs(ac, spec->target); + case AC_ARGTYPE_SUBARGS_N: + return AC_GetSlice(ac, spec->target, spec->slicelen); + default: + fprintf(stderr, "Unknown type"); + abort(); + } +} + +int AC_ParseArgSpec(ArgsCursor *ac, ACArgSpec *specs, ACArgSpec **errSpec) { + const char *s = NULL; + size_t n; + int rv; + + if (errSpec) { + *errSpec = NULL; + } + + while (!AC_IsAtEnd(ac)) { + if ((rv = AC_GetString(ac, &s, &n, AC_F_NOADVANCE) != AC_OK)) { + return rv; + } + ACArgSpec *cur = specs; + + for (; cur->name != NULL; cur++) { + if (n != strlen(cur->name)) { + continue; + } + if (!strncasecmp(cur->name, s, n)) { + break; + } + } + + if (cur->name == NULL) { + return AC_ERR_ENOENT; + } + + AC_Advance(ac); + if ((rv = parseSingleSpec(ac, cur)) != AC_OK) { + if (errSpec) { + *errSpec = cur; + } + return rv; + } + } + return AC_OK; +} diff --git a/rmutil/args.h b/rmutil/args.h new file mode 100644 index 0000000..89f3f04 --- /dev/null +++ b/rmutil/args.h @@ -0,0 +1,235 @@ +#ifndef RMUTIL_ARGS_H +#define RMUTIL_ARGS_H + +#include +#include +#include +#include "sds.h" +#include "redismodule.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AC_TYPE_UNINIT = 0, // Comment for formatting + AC_TYPE_RSTRING, + AC_TYPE_CHAR, + AC_TYPE_SDS +} ACType; + +#define AC_IsInitialized(ac) ((ac)->type != AC_TYPE_UNINIT) + +/** + * The cursor model simply reads through the current argument list, advancing + * the 'offset' position as required. No tricky declarative syntax, and allows + * for finer grained error handling. + */ +typedef struct { + void **objs; + int type; + size_t argc; + size_t offset; +} ArgsCursor; + +static inline void ArgsCursor_InitCString(ArgsCursor *cursor, const char **argv, int argc) { + cursor->objs = (void **)argv; + cursor->type = AC_TYPE_CHAR; + cursor->offset = 0; + cursor->argc = argc; +} + +static inline void ArgsCursor_InitSDS(ArgsCursor *cursor, const sds *argv, int argc) { + cursor->objs = (void **)argv; + cursor->type = AC_TYPE_SDS; + cursor->offset = 0; + cursor->argc = argc; +} + +static inline void ArgsCursor_InitRString(ArgsCursor *cursor, RedisModuleString **argv, int argc) { + cursor->objs = (void **)argv; + cursor->type = AC_TYPE_RSTRING; + cursor->offset = 0; + cursor->argc = argc; +} + +typedef enum { + AC_OK = 0, // Not an error + AC_ERR_PARSE, // Couldn't parse as integer or other type + AC_ERR_NOARG, // Missing required argument + AC_ERR_ELIMIT, // Exceeded limitations of this type (i.e. bad value, but parsed OK) + AC_ERR_ENOENT // Argument name not found in list +} ACStatus; + +// These flags can be AND'd with the original type +#define AC_F_GE1 0x100 // Must be >= 1 (no zero or negative) +#define AC_F_GE0 0x200 // Must be >= 0 (no negative) +#define AC_F_NOADVANCE 0x400 // Don't advance cursor position +#define AC_F_COALESCE 0x800 // Coalesce non-integral input + +// These functions return AC_OK or an error code on error. Note that the +// output value is not guaranteed to remain untouched in the case of an error +int AC_GetString(ArgsCursor *ac, const char **s, size_t *n, int flags); +int AC_GetRString(ArgsCursor *ac, RedisModuleString **s, int flags); +int AC_GetLongLong(ArgsCursor *ac, long long *ll, int flags); +int AC_GetUnsignedLongLong(ArgsCursor *ac, unsigned long long *ull, int flags); +int AC_GetUnsigned(ArgsCursor *ac, unsigned *u, int flags); +int AC_GetInt(ArgsCursor *ac, int *i, int flags); +int AC_GetDouble(ArgsCursor *ac, double *d, int flags); +int AC_GetU32(ArgsCursor *ac, uint32_t *u, int flags); +int AC_GetU64(ArgsCursor *ac, uint64_t *u, int flags); + +// Gets the string (and optionally the length). If the string does not exist, +// it returns NULL. Used when caller is sure the arg exists +const char *AC_GetStringNC(ArgsCursor *ac, size_t *len); + +int AC_Advance(ArgsCursor *ac); +int AC_AdvanceBy(ArgsCursor *ac, size_t by); + +// Advances the cursor if the next argument matches the given string. This +// will swallow it up. +int AC_AdvanceIfMatch(ArgsCursor *ac, const char *arg); + +/** + * Read the argument list in the format of + * .. + * The output is stored in dest which contains a sub-array of argv/argc + */ +int AC_GetVarArgs(ArgsCursor *ac, ArgsCursor *dest); + +/** + * Consume the next arguments and place them in + */ +int AC_GetSlice(ArgsCursor *ac, ArgsCursor *dest, size_t n); + +typedef enum { + AC_ARGTYPE_STRING, + AC_ARGTYPE_RSTRING, + AC_ARGTYPE_LLONG, + AC_ARGTYPE_ULLONG, + AC_ARGTYPE_UINT, + AC_ARGTYPE_U32 = AC_ARGTYPE_UINT, + AC_ARGTYPE_INT, + AC_ARGTYPE_DOUBLE, + /** + * This means the name is a flag and does not accept any additional arguments. + * In this case, the target value is assumed to be an int, and is set to + * nonzero + */ + AC_ARGTYPE_BOOLFLAG, + + /** + * Uses AC_GetVarArgs, gets a sub-arg list + */ + AC_ARGTYPE_SUBARGS, + + /** + * Use AC_GetSlice. Set slicelen in the spec to the expected count. + */ + AC_ARGTYPE_SUBARGS_N, + + /** + * Accepts U32 target. Use 'slicelen' as the field to indicate which bit should + * be set. + */ + AC_ARGTYPE_BITFLAG, + + /** + * Like bitflag, except the value is _removed_ from the target. Accepts U32 target + */ + AC_ARGTYPE_UNFLAG, +} ACArgType; + +/** + * Helper macro to define bitflag argtype + */ +#define AC_MKBITFLAG(name_, target_, bit_) \ + .name = name_, .target = target_, .type = AC_ARGTYPE_BITFLAG, .slicelen = bit_ + +#define AC_MKUNFLAG(name_, target_, bit_) \ + .name = name_, .target = target_, .type = AC_ARGTYPE_UNFLAG, .slicelen = bit_ + +typedef struct { + const char *name; // Name of the argument + void *target; // [out] Target pointer, e.g. `int*`, `RedisModuleString**` + size_t *len; // [out] Target length pointer. Valid only for strings + ACArgType type; // Type of argument + int intflags; // AC_F_COALESCE, etc. + size_t slicelen; // When using slice length, set this to the expected slice count +} ACArgSpec; + +/** + * Utilizes the argument cursor to traverse a list of known argument specs. This + * function will return: + * - AC_OK if the argument parsed successfully + * - AC_ERR_ENOENT if an argument not mentioned in `specs` is encountered. + * - Any other error is assumed to be a parser error, in which the argument exists + * but did not meet constraints of the type + * + * Note that ENOENT is not a 'hard' error. It simply means that the argument + * was not provided within the list. This may be intentional if, for example, + * it requires complex processing. + */ +int AC_ParseArgSpec(ArgsCursor *ac, ACArgSpec *specs, ACArgSpec **errSpec); + +static inline const char *AC_Strerror(int code) { + switch (code) { + case AC_OK: + return "SUCCESS"; + case AC_ERR_ELIMIT: + return "Value is outside acceptable bounds"; + case AC_ERR_NOARG: + return "Expected an argument, but none provided"; + case AC_ERR_PARSE: + return "Could not convert argument to expected type"; + case AC_ERR_ENOENT: + return "Unknown argument"; + default: + return "(AC: You should not be seeing this message. This is a bug)"; + } +} + +#define AC_CURRENT(ac) ((ac)->objs[(ac)->offset]) +#define AC_Clear(ac) // NOOP +#define AC_IsAtEnd(ac) ((ac)->offset >= (ac)->argc) +#define AC_NumRemaining(ac) ((ac)->argc - (ac)->offset) +#define AC_NumArgs(ac) (ac)->argc +#define AC_StringArg(ac, N) (const char *)((ac)->objs[N]) +#ifdef __cplusplus +} + +#include +#include +#include +#include +class ArgsCursorCXX : public ArgsCursor { + public: + template + ArgsCursorCXX(T... args) { + typedef typename std::tuple_element<0, std::tuple>::type FirstType; + typedef const typename std::remove_pointer::type *ConstPointerType; + typedef typename std::conditional::value, ConstPointerType, + FirstType>::type RealType; + std::array stackarr = {{args...}}; + arr.assign(stackarr.begin(), stackarr.end()); + RealType *arrptr = (RealType *)(&arr[0]); + init(&arrptr[0], arr.size()); + } + + void append(void *p) { + arr.push_back(p); + objs = (void **)&arr[0]; + argc = arr.size(); + } + + private: + std::vector arr; + void init(const char **s, size_t n) { + ArgsCursor_InitCString(this, s, n); + } + void init(RedisModuleString **s, size_t n) { + ArgsCursor_InitRString(this, s, n); + } +}; +#endif +#endif