Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rmutil/Makefile
Original file line number Diff line number Diff line change
@@ -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

273 changes: 273 additions & 0 deletions rmutil/args.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
#include "args.h"
#include "redismodule.h"
#include <float.h>
#include <math.h>
#include <string.h>
#include <assert.h>

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;
}
235 changes: 235 additions & 0 deletions rmutil/args.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#ifndef RMUTIL_ARGS_H
#define RMUTIL_ARGS_H

#include <stdlib.h>
#include <limits.h>
#include <stdint.h>
#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
* <NUM_OF_ARGS> <ARG[1]> <ARG[2]> .. <ARG[NUM_OF_ARGS]>
* The output is stored in dest which contains a sub-array of argv/argc
*/
int AC_GetVarArgs(ArgsCursor *ac, ArgsCursor *dest);

/**
* Consume the next <n> arguments and place them in <dest>
*/
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 <vector>
#include <tuple>
#include <type_traits>
#include <array>
class ArgsCursorCXX : public ArgsCursor {
public:
template <typename... T>
ArgsCursorCXX(T... args) {
typedef typename std::tuple_element<0, std::tuple<T...>>::type FirstType;
typedef const typename std::remove_pointer<FirstType>::type *ConstPointerType;
typedef typename std::conditional<std::is_pointer<FirstType>::value, ConstPointerType,
FirstType>::type RealType;
std::array<const void *, sizeof...(args)> 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<const void *> 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