diff --git a/src/config.c b/src/config.c index 5b90ebbd60..46e804c42d 100644 --- a/src/config.c +++ b/src/config.c @@ -1357,6 +1357,17 @@ void rewriteConfigOctalOption(struct rewriteConfigState *state, rewriteConfigRewriteLine(state, option, line, force); } +/* Rewrite an unsigned number option. */ +void rewriteConfigUnsignedOption(struct rewriteConfigState *state, + const char *option, + unsigned long long value, + unsigned long long defvalue) { + int force = value != defvalue; + sds line = sdscatprintf(sdsempty(), "%s %llu", option, value); + + rewriteConfigRewriteLine(state, option, line, force); +} + /* Rewrite an enumeration option. It takes as usually state and option name, * and in addition the enumeration array and the default value for the * option. */ @@ -2026,7 +2037,10 @@ int setNumericType(standardConfig *config, long long val, const char **err) { else *(config->data.numeric.config.ll) = (long long)val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { - *(config->data.numeric.config.ull) = (unsigned long long)val; + if (config->flags & MODULE_CONFIG) + return setModuleUnsignedNumericConfig(config->privdata, (unsigned long long)val, err); + else + *(config->data.numeric.config.ull) = (unsigned long long)val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { *(config->data.numeric.config.st) = (size_t)val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { @@ -2056,7 +2070,10 @@ int setNumericType(standardConfig *config, long long val, const char **err) { else \ val = *(config->data.numeric.config.ll); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { \ - val = *(config->data.numeric.config.ull); \ + if (config->flags & MODULE_CONFIG) \ + val = getModuleUnsignedNumericConfig(config->privdata); \ + else \ + val = *(config->data.numeric.config.ull); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { \ val = *(config->data.numeric.config.st); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { \ @@ -2133,10 +2150,20 @@ static int numericParseString(standardConfig *config, sds value, const char **er if (config->data.numeric.flags & OCTAL_CONFIG) { char *endptr; errno = 0; - *res = strtoll(value, &endptr, 8); + *res = strtoull(value, &endptr, 8); if (errno == 0 && *endptr == '\0') return 1; /* No overflow or invalid characters */ } + /* Attempt to parse as an unsigned number */ + if (config->data.numeric.flags & UNSIGNED_CONFIG) { + unsigned long long ull; + int ok = string2ull(value, sdslen(value), &ull); + if (ok) { + *res = (long long)ull; + return 1; /* No overflow or invalid characters */ + } + } + /* Attempt a simple number (no special flags set) */ if (!config->data.numeric.flags && string2ll(value, sdslen(value), res)) return 1; @@ -2147,6 +2174,8 @@ static int numericParseString(standardConfig *config, sds value, const char **er *err = "argument must be a memory value"; else if (config->data.numeric.flags & OCTAL_CONFIG) *err = "argument couldn't be parsed as an octal number"; + else if (config->data.numeric.flags & UNSIGNED_CONFIG) + *err = "argument couldn't be parsed as an unsigned number"; else *err = "argument couldn't be parsed into an integer"; return 0; @@ -2184,6 +2213,8 @@ static sds numericConfigGet(standardConfig *config) { ull2string(buf, sizeof(buf), value); } else if (config->data.numeric.flags & OCTAL_CONFIG) { snprintf(buf, sizeof(buf), "%llo", value); + } else if (config->data.numeric.flags & UNSIGNED_CONFIG) { + ull2string(buf, sizeof(buf), (unsigned long long)value); } else { ll2string(buf, sizeof(buf), value); } @@ -2201,6 +2232,8 @@ static void numericConfigRewrite(standardConfig *config, const char *name, struc rewriteConfigBytesOption(state, name, value, config->data.numeric.default_value); } else if (config->data.numeric.flags & OCTAL_CONFIG) { rewriteConfigOctalOption(state, name, value, config->data.numeric.default_value); + } else if (config->data.numeric.flags & UNSIGNED_CONFIG) { + rewriteConfigUnsignedOption(state, name, value, config->data.numeric.default_value); } else { rewriteConfigNumericalOption(state, name, value, config->data.numeric.default_value); } @@ -3488,6 +3521,23 @@ void addModuleNumericConfig(const char *module_name, registerConfigValue(config_name, &module_config, 0); } +void addModuleUnsignedNumericConfig(const char *module_name, + const char *name, + int flags, + void *privdata, + unsigned long long default_val, + int conf_flags, + unsigned long long lower, + unsigned long long upper) { + sds config_name = sdscatfmt(sdsempty(), "%s.%s", module_name, name); + unsigned long long config_dummy_address; + standardConfig module_config = createULongLongConfig(config_name, NULL, flags | MODULE_CONFIG, lower, upper, + config_dummy_address, default_val, conf_flags, NULL, NULL); + module_config.data.numeric.config.ull = NULL; + module_config.privdata = privdata; + registerConfigValue(config_name, &module_config, 0); +} + /*----------------------------------------------------------------------------- * CONFIG HELP *----------------------------------------------------------------------------*/ diff --git a/src/db.c b/src/db.c index 55ffe5da5a..2568309782 100644 --- a/src/db.c +++ b/src/db.c @@ -1066,7 +1066,7 @@ void hashtableScanCallback(void *privdata, void *entry) { * returns C_OK. Otherwise return C_ERR and send an error to the * client. */ int parseScanCursorOrReply(client *c, robj *o, unsigned long long *cursor) { - if (!string2ull(o->ptr, cursor)) { + if (!string2ull(o->ptr, sdslen(o->ptr), cursor)) { addReplyError(c, "invalid cursor"); return C_ERR; } diff --git a/src/module.c b/src/module.c index 58555839f2..729d3dc3df 100644 --- a/src/module.c +++ b/src/module.c @@ -452,6 +452,7 @@ typedef struct ValkeyModuleKeyOptCtx { /* The function signatures for module config get callbacks. These are identical to the ones exposed in valkeymodule.h. */ typedef ValkeyModuleString *(*ValkeyModuleConfigGetStringFunc)(const char *name, void *privdata); typedef long long (*ValkeyModuleConfigGetNumericFunc)(const char *name, void *privdata); +typedef unsigned long long (*ValkeyModuleConfigGetUnsignedNumericFunc)(const char *name, void *privdata); typedef int (*ValkeyModuleConfigGetBoolFunc)(const char *name, void *privdata); typedef int (*ValkeyModuleConfigGetEnumFunc)(const char *name, void *privdata); /* The function signatures for module config set callbacks. These are identical to the ones exposed in valkeymodule.h. */ @@ -463,6 +464,10 @@ typedef int (*ValkeyModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, ValkeyModuleString **err); +typedef int (*ValkeyModuleConfigSetUnsignedNumericFunc)(const char *name, + unsigned long long val, + void *privdata, + ValkeyModuleString **err); typedef int (*ValkeyModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, ValkeyModuleString **err); typedef int (*ValkeyModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, ValkeyModuleString **err); /* Apply signature, identical to valkeymodule.h */ @@ -475,12 +480,14 @@ struct ModuleConfig { union get_fn { /* The get callback specified by the module */ ValkeyModuleConfigGetStringFunc get_string; ValkeyModuleConfigGetNumericFunc get_numeric; + ValkeyModuleConfigGetUnsignedNumericFunc get_unsigned_numeric; ValkeyModuleConfigGetBoolFunc get_bool; ValkeyModuleConfigGetEnumFunc get_enum; } get_fn; union set_fn { /* The set callback specified by the module */ ValkeyModuleConfigSetStringFunc set_string; ValkeyModuleConfigSetNumericFunc set_numeric; + ValkeyModuleConfigSetUnsignedNumericFunc set_unsigned_numeric; ValkeyModuleConfigSetBoolFunc set_bool; ValkeyModuleConfigSetEnumFunc set_enum; } set_fn; @@ -2965,7 +2972,7 @@ int VM_StringToLongLong(const ValkeyModuleString *str, long long *ll) { * as a valid, strict `unsigned long long` (no spaces before/after), VALKEYMODULE_ERR * is returned. */ int VM_StringToULongLong(const ValkeyModuleString *str, unsigned long long *ull) { - return string2ull(str->ptr, ull) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return string2ull(str->ptr, sdslen(str->ptr), ull) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; } /* Convert the string into a double, storing it at `*d`. @@ -10526,7 +10533,7 @@ unsigned long long VM_ServerInfoGetFieldUnsigned(ValkeyModuleServerInfoData *dat return 0; } sds val = result; - if (!string2ull(val, &ll)) { + if (!string2ull(val, sdslen(val), &ll)) { if (out_err) *out_err = VALKEYMODULE_ERR; return 0; } @@ -12570,11 +12577,11 @@ int isModuleConfigNameRegistered(ValkeyModule *module, const char *name) { int moduleVerifyConfigFlags(unsigned int flags, configType type) { if ((flags & ~(VALKEYMODULE_CONFIG_DEFAULT | VALKEYMODULE_CONFIG_IMMUTABLE | VALKEYMODULE_CONFIG_SENSITIVE | VALKEYMODULE_CONFIG_HIDDEN | VALKEYMODULE_CONFIG_PROTECTED | VALKEYMODULE_CONFIG_DENY_LOADING | - VALKEYMODULE_CONFIG_BITFLAGS | VALKEYMODULE_CONFIG_MEMORY))) { + VALKEYMODULE_CONFIG_BITFLAGS | VALKEYMODULE_CONFIG_MEMORY | VALKEYMODULE_CONFIG_UNSIGNED))) { serverLogRaw(LL_WARNING, "Invalid flag(s) for configuration"); return VALKEYMODULE_ERR; } - if (type != NUMERIC_CONFIG && flags & VALKEYMODULE_CONFIG_MEMORY) { + if (type != NUMERIC_CONFIG && (flags & (VALKEYMODULE_CONFIG_MEMORY | VALKEYMODULE_CONFIG_UNSIGNED))) { serverLogRaw(LL_WARNING, "Numeric flag provided for non-numeric configuration."); return VALKEYMODULE_ERR; } @@ -12646,6 +12653,13 @@ int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err return return_code == VALKEYMODULE_OK ? 1 : 0; } +int setModuleUnsignedNumericConfig(ModuleConfig *config, unsigned long long val, const char **err) { + ValkeyModuleString *error = NULL; + int return_code = config->set_fn.set_unsigned_numeric(config->name, val, config->privdata, &error); + propagateErrorString(error, err); + return return_code == VALKEYMODULE_OK ? 1 : 0; +} + /* This is a series of get functions for each type that act as dispatchers for * config.c to call module set callbacks. */ int getModuleBoolConfig(ModuleConfig *module_config) { @@ -12665,6 +12679,10 @@ long long getModuleNumericConfig(ModuleConfig *module_config) { return module_config->get_fn.get_numeric(module_config->name, module_config->privdata); } +unsigned long long getModuleUnsignedNumericConfig(ModuleConfig *module_config) { + return module_config->get_fn.get_unsigned_numeric(module_config->name, module_config->privdata); +} + /* This function takes a module and a list of configs stored as sds NAME VALUE pairs. * It attempts to call set on each of these configs. */ int loadModuleConfigs(ValkeyModule *module) { @@ -12785,6 +12803,7 @@ unsigned int maskModuleConfigFlags(unsigned int flags) { unsigned int maskModuleNumericConfigFlags(unsigned int flags) { unsigned int new_flags = 0; if (flags & VALKEYMODULE_CONFIG_MEMORY) new_flags |= MEMORY_CONFIG; + if (flags & VALKEYMODULE_CONFIG_UNSIGNED) new_flags |= UNSIGNED_CONFIG; return new_flags; } @@ -13009,6 +13028,34 @@ int VM_RegisterNumericConfig(ValkeyModuleCtx *ctx, return VALKEYMODULE_OK; } +/* + * Create an unsigned integer config that server clients can interact with via the + * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See + * ValkeyModule_RegisterStringConfig for detailed information about configs. */ +int VM_RegisterUnsignedNumericConfig(ValkeyModuleCtx *ctx, + const char *name, + unsigned long long default_val, + unsigned int flags, + unsigned long long min, + unsigned long long max, + ValkeyModuleConfigGetUnsignedNumericFunc getfn, + ValkeyModuleConfigSetUnsignedNumericFunc setfn, + ValkeyModuleConfigApplyFunc applyfn, + void *privdata) { + ValkeyModule *module = ctx->module; + if (moduleConfigValidityCheck(module, name, flags, NUMERIC_CONFIG)) { + return VALKEYMODULE_ERR; + } + ModuleConfig *new_config = createModuleConfig(name, applyfn, privdata, module); + new_config->get_fn.get_unsigned_numeric = getfn; + new_config->set_fn.set_unsigned_numeric = setfn; + listAddNodeTail(module->module_configs, new_config); + unsigned int numeric_flags = maskModuleNumericConfigFlags(flags); + flags = maskModuleConfigFlags(flags); + addModuleUnsignedNumericConfig(module->name, name, flags, new_config, default_val, numeric_flags, min, max); + return VALKEYMODULE_OK; +} + /* Applies all pending configurations on the module load. This should be called * after all of the configurations have been registered for the module inside of ValkeyModule_OnLoad. * This will return VALKEYMODULE_ERR if it is called outside ValkeyModule_OnLoad. @@ -14073,6 +14120,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(Yield); REGISTER_API(RegisterBoolConfig); REGISTER_API(RegisterNumericConfig); + REGISTER_API(RegisterUnsignedNumericConfig); REGISTER_API(RegisterStringConfig); REGISTER_API(RegisterEnumConfig); REGISTER_API(LoadConfigs); diff --git a/src/redismodule.h b/src/redismodule.h index d692104d52..54ed5c7861 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -691,6 +691,7 @@ #define RedisModule_EventLoopAddOneShot ValkeyModule_EventLoopAddOneShot #define RedisModule_RegisterBoolConfig ValkeyModule_RegisterBoolConfig #define RedisModule_RegisterNumericConfig ValkeyModule_RegisterNumericConfig +#define RedisModule_RegisterUnsignedNumericConfig ValkeyModule_RegisterUnsignedNumericConfig #define RedisModule_RegisterStringConfig ValkeyModule_RegisterStringConfig #define RedisModule_RegisterEnumConfig ValkeyModule_RegisterEnumConfig #define RedisModule_LoadConfigs ValkeyModule_LoadConfigs diff --git a/src/server.h b/src/server.h index 25c6ec7f4c..983c6989eb 100644 --- a/src/server.h +++ b/src/server.h @@ -3294,10 +3294,11 @@ sds keyspaceEventsFlagsToString(int flags); * to apply the configuration change even if the new config value is the same as \ * the old. */ -#define INTEGER_CONFIG 0 /* No flags means a simple integer configuration */ -#define MEMORY_CONFIG (1 << 0) /* Indicates if this value can be loaded as a memory value */ -#define PERCENT_CONFIG (1 << 1) /* Indicates if this value can be loaded as a percent (and stored as a negative int) */ -#define OCTAL_CONFIG (1 << 2) /* This value uses octal representation */ +#define INTEGER_CONFIG 0 /* No flags means a simple integer configuration */ +#define MEMORY_CONFIG (1 << 0) /* Indicates if this value can be loaded as a memory value */ +#define PERCENT_CONFIG (1 << 1) /* Indicates if this value can be loaded as a percent (and stored as a negative int) */ +#define OCTAL_CONFIG (1 << 2) /* This value uses octal representation */ +#define UNSIGNED_CONFIG (1 << 3) /* This value uses unsigned representation */ /* Enum Configs contain an array of configEnum objects that match a string with an integer. */ typedef struct configEnum { @@ -3350,6 +3351,14 @@ void addModuleNumericConfig(const char *module_name, int conf_flags, long long lower, long long upper); +void addModuleUnsignedNumericConfig(const char *module_name, + const char *name, + int flags, + void *privdata, + unsigned long long default_val, + int conf_flags, + unsigned long long lower, + unsigned long long upper); void addModuleConfigApply(list *module_configs, ModuleConfig *module_config); int moduleConfigApplyConfig(list *module_configs, const char **err, const char **err_arg_name); int getModuleBoolConfig(ModuleConfig *module_config); @@ -3360,6 +3369,8 @@ int getModuleEnumConfig(ModuleConfig *module_config); int setModuleEnumConfig(ModuleConfig *config, int val, const char **err); long long getModuleNumericConfig(ModuleConfig *module_config); int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err); +unsigned long long getModuleUnsignedNumericConfig(ModuleConfig *module_config); +int setModuleUnsignedNumericConfig(ModuleConfig *config, unsigned long long val, const char **err); /* db.c -- Keyspace access API */ int removeExpire(serverDb *db, robj *key); diff --git a/src/t_stream.c b/src/t_stream.c index 17254b58dd..010d6ac3c2 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -1893,14 +1893,14 @@ int streamGenericParseIDOrReply(client *c, unsigned long long ms, seq; char *dot = strchr(buf, '-'); if (dot) *dot = '\0'; - if (string2ull(buf, &ms) == 0) goto invalid; + if (string2ull(buf, strlen(buf), &ms) == 0) goto invalid; if (dot) { size_t seqlen = strlen(dot + 1); if (seq_given != NULL && seqlen == 1 && *(dot + 1) == '*') { /* Handle the -* form. */ seq = 0; *seq_given = 0; - } else if (string2ull(dot + 1, &seq) == 0) { + } else if (string2ull(dot + 1, seqlen, &seq) == 0) { goto invalid; } } else { diff --git a/src/util.c b/src/util.c index 6e44392ce1..7b86e6000c 100644 --- a/src/util.c +++ b/src/util.c @@ -497,9 +497,9 @@ int string2ll(const char *s, size_t slen, long long *value) { * Valkey: if it fails, strtoull() is used instead. The function returns * 1 if the conversion happened successfully or 0 if the number is * invalid or out of range. */ -int string2ull(const char *s, unsigned long long *value) { +int string2ull(const char *s, size_t slen, unsigned long long *value) { long long ll; - if (string2ll(s, strlen(s), &ll)) { + if (string2ll(s, slen, &ll)) { if (ll < 0) return 0; /* Negative values are out of range. */ *value = ll; return 1; diff --git a/src/util.h b/src/util.h index 61095ddb65..fe4c2a272a 100644 --- a/src/util.h +++ b/src/util.h @@ -68,7 +68,7 @@ uint32_t sdigits10(int64_t v); int ll2string(char *s, size_t len, long long value); int ull2string(char *s, size_t len, unsigned long long value); int string2ll(const char *s, size_t slen, long long *value); -int string2ull(const char *s, unsigned long long *value); +int string2ull(const char *s, size_t slen, unsigned long long *value); int string2l(const char *s, size_t slen, long *value); int string2ul_base16_async_signal_safe(const char *src, size_t slen, unsigned long *result_output); int string2ld(const char *s, size_t slen, long double *dp); diff --git a/src/valkeymodule.h b/src/valkeymodule.h index 1d99d2ff7a..75fb845c06 100644 --- a/src/valkeymodule.h +++ b/src/valkeymodule.h @@ -146,6 +146,7 @@ typedef long long ustime_t; #define VALKEYMODULE_CONFIG_MEMORY (1ULL << 7) /* Indicates if this value can be set as a memory value */ #define VALKEYMODULE_CONFIG_BITFLAGS (1ULL << 8) /* Indicates if this value can be set as a multiple enum values */ +#define VALKEYMODULE_CONFIG_UNSIGNED (1ULL << 9) /* StreamID type. */ typedef struct ValkeyModuleStreamID { @@ -967,6 +968,7 @@ typedef void (*ValkeyModuleScanKeyCB)(ValkeyModuleKey *key, void *privdata); typedef ValkeyModuleString *(*ValkeyModuleConfigGetStringFunc)(const char *name, void *privdata); typedef long long (*ValkeyModuleConfigGetNumericFunc)(const char *name, void *privdata); +typedef unsigned long long (*ValkeyModuleConfigGetUnsignedNumericFunc)(const char *name, void *privdata); typedef int (*ValkeyModuleConfigGetBoolFunc)(const char *name, void *privdata); typedef int (*ValkeyModuleConfigGetEnumFunc)(const char *name, void *privdata); typedef int (*ValkeyModuleConfigSetStringFunc)(const char *name, @@ -977,6 +979,10 @@ typedef int (*ValkeyModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, ValkeyModuleString **err); +typedef int (*ValkeyModuleConfigSetUnsignedNumericFunc)(const char *name, + unsigned long long val, + void *privdata, + ValkeyModuleString **err); typedef int (*ValkeyModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, ValkeyModuleString **err); typedef int (*ValkeyModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, ValkeyModuleString **err); typedef int (*ValkeyModuleConfigApplyFunc)(ValkeyModuleCtx *ctx, void *privdata, ValkeyModuleString **err); @@ -1708,6 +1714,16 @@ VALKEYMODULE_API int (*ValkeyModule_RegisterNumericConfig)(ValkeyModuleCtx *ctx, ValkeyModuleConfigSetNumericFunc setfn, ValkeyModuleConfigApplyFunc applyfn, void *privdata) VALKEYMODULE_ATTR; +VALKEYMODULE_API int (*ValkeyModule_RegisterUnsignedNumericConfig)(ValkeyModuleCtx *ctx, + const char *name, + unsigned long long default_val, + unsigned int flags, + unsigned long long min, + unsigned long long max, + ValkeyModuleConfigGetUnsignedNumericFunc getfn, + ValkeyModuleConfigSetUnsignedNumericFunc setfn, + ValkeyModuleConfigApplyFunc applyfn, + void *privdata) VALKEYMODULE_ATTR; VALKEYMODULE_API int (*ValkeyModule_RegisterStringConfig)(ValkeyModuleCtx *ctx, const char *name, const char *default_val, @@ -2105,6 +2121,7 @@ static int ValkeyModule_Init(ValkeyModuleCtx *ctx, const char *name, int ver, in VALKEYMODULE_GET_API(EventLoopAddOneShot); VALKEYMODULE_GET_API(RegisterBoolConfig); VALKEYMODULE_GET_API(RegisterNumericConfig); + VALKEYMODULE_GET_API(RegisterUnsignedNumericConfig); VALKEYMODULE_GET_API(RegisterStringConfig); VALKEYMODULE_GET_API(RegisterEnumConfig); VALKEYMODULE_GET_API(LoadConfigs); diff --git a/tests/modules/moduleconfigs.c b/tests/modules/moduleconfigs.c index 7314902f49..6244595932 100644 --- a/tests/modules/moduleconfigs.c +++ b/tests/modules/moduleconfigs.c @@ -1,9 +1,12 @@ #include "valkeymodule.h" #include +#include int mutable_bool_val; int immutable_bool_val; long long longval; long long memval; +unsigned long long mutable_ull_val; +unsigned long long immutable_ull_val; ValkeyModuleString *strval = NULL; int enumval; int flagsval; @@ -28,6 +31,11 @@ long long getNumericConfigCommand(const char *name, void *privdata) { return (*(long long *) privdata); } +unsigned long long getUnsignedNumericConfigCommand(const char *name, void *privdata) { + VALKEYMODULE_NOT_USED(name); + return (*(unsigned long long *) privdata); +} + int setNumericConfigCommand(const char *name, long long new, void *privdata, ValkeyModuleString **err) { VALKEYMODULE_NOT_USED(name); VALKEYMODULE_NOT_USED(err); @@ -35,6 +43,13 @@ int setNumericConfigCommand(const char *name, long long new, void *privdata, Val return VALKEYMODULE_OK; } +int setUnsignedNumericConfigCommand(const char *name, unsigned long long new, void *privdata, ValkeyModuleString **err) { + VALKEYMODULE_NOT_USED(name); + VALKEYMODULE_NOT_USED(err); + *(unsigned long long *)privdata = new; + return VALKEYMODULE_OK; +} + ValkeyModuleString *getStringConfigCommand(const char *name, void *privdata) { VALKEYMODULE_NOT_USED(name); VALKEYMODULE_NOT_USED(privdata); @@ -103,6 +118,16 @@ int longlongApplyFunc(ValkeyModuleCtx *ctx, void *privdata, ValkeyModuleString * return VALKEYMODULE_OK; } +int unsignedlonglongApplyFunc(ValkeyModuleCtx *ctx, void *privdata, ValkeyModuleString **err) { + VALKEYMODULE_NOT_USED(ctx); + VALKEYMODULE_NOT_USED(privdata); + if (mutable_ull_val == immutable_ull_val) { + *err = ValkeyModule_CreateString(NULL, "These configs cannot equal each other.", 38); + return VALKEYMODULE_ERR; + } + return VALKEYMODULE_OK; +} + int registerBlockCheck(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { VALKEYMODULE_NOT_USED(argv); VALKEYMODULE_NOT_USED(argc); @@ -120,6 +145,9 @@ int registerBlockCheck(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc result = ValkeyModule_RegisterNumericConfig(ctx, "numeric", -1, VALKEYMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval); response_ok |= (result == VALKEYMODULE_OK); + + result = ValkeyModule_RegisterUnsignedNumericConfig(ctx, "unsigned_numeric", 1, VALKEYMODULE_CONFIG_DEFAULT | VALKEYMODULE_CONFIG_UNSIGNED, 0, LLONG_MAX * 1ULL + 1000, getUnsignedNumericConfigCommand, setUnsignedNumericConfigCommand, unsignedlonglongApplyFunc, &mutable_ull_val); + response_ok |= (result == VALKEYMODULE_OK); result = ValkeyModule_LoadConfigs(ctx); response_ok |= (result == VALKEYMODULE_OK); @@ -168,6 +196,9 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg if (ValkeyModule_RegisterNumericConfig(ctx, "numeric", -1, VALKEYMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval) == VALKEYMODULE_ERR) { return VALKEYMODULE_ERR; } + if (ValkeyModule_RegisterUnsignedNumericConfig(ctx, "unsigned_numeric", 1, VALKEYMODULE_CONFIG_DEFAULT | VALKEYMODULE_CONFIG_UNSIGNED, 0, LLONG_MAX * 1ULL + 1000, getUnsignedNumericConfigCommand, setUnsignedNumericConfigCommand, NULL, &mutable_ull_val) == VALKEYMODULE_ERR) { + return VALKEYMODULE_ERR; + } size_t len; if (argc && !strcasecmp(ValkeyModule_StringPtrLen(argv[0], &len), "noload")) { return VALKEYMODULE_OK; diff --git a/tests/unit/moduleapi/moduleconfigs.tcl b/tests/unit/moduleapi/moduleconfigs.tcl index 2474ad3567..011723b440 100644 --- a/tests/unit/moduleapi/moduleconfigs.tcl +++ b/tests/unit/moduleapi/moduleconfigs.tcl @@ -81,6 +81,15 @@ start_server {tags {"modules"}} { assert_match {*argument must be between*} $e } + test {Unsigned numeric limits work properly} { + catch {[r config set moduleconfigs.unsigned_numeric 9223372036854790000]} e + assert_match {*argument must be between*} $e + catch {[r config set moduleconfigs.unsigned_numeric 184467440737095516150]} e + assert_match {*argument couldn't be parsed as an unsigned*} $e + catch {[r config set moduleconfigs.unsigned_numeric -5]} e + assert_match {*argument couldn't be parsed as an unsigned*} $e + } + test {Enums only able to be set to passed in values} { # Module authors specify what values are valid for enums, check that only those values are ok on a set catch {[r config set moduleconfigs.enum asdf]} e @@ -103,6 +112,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" + assert_equal [r config get moduleconfigs.unsigned_numeric] "moduleconfigs.unsigned_numeric 1" r module unload moduleconfigs } @@ -117,6 +127,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" + assert_equal [r config get moduleconfigs.unsigned_numeric] "moduleconfigs.unsigned_numeric 1" } test {apply function works} { @@ -177,6 +188,7 @@ start_server {tags {"modules"}} { r config set moduleconfigs.memory_numeric 750 r config set moduleconfigs.enum two r config set moduleconfigs.flags "four two" + r config set moduleconfigs.unsigned_numeric 9223372036854775808 r config rewrite restart_server 0 true false # Ensure configs we rewrote are present and that the conf file is readable @@ -186,6 +198,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" + assert_equal [r config get moduleconfigs.unsigned_numeric] "moduleconfigs.unsigned_numeric 9223372036854775808" r module unload moduleconfigs }