diff --git a/include/maxscale/config.hh b/include/maxscale/config.hh index e657d09c4..55545bbf4 100644 --- a/include/maxscale/config.hh +++ b/include/maxscale/config.hh @@ -215,6 +215,31 @@ extern const char CN_MAXLOG[]; extern const char CN_LOG_AUGMENTATION[]; extern const char CN_LOG_TO_SHM[]; +namespace maxscale +{ + +namespace config +{ + +enum DurationInterpretation +{ + INTERPRET_AS_SECONDS, + INTERPRET_AS_MILLISECONDS +}; + +enum DurationUnit +{ + DURATION_IN_HOURS, + DURATION_IN_MINUTES, + DURATION_IN_SECONDS, + DURATION_IN_MILLISECONDS, + DURATION_IN_DEFAULT +}; + +} + +} + /** * Config parameter container. Typically includes all parameters of a single configuration file section * such as a server or filter. @@ -294,6 +319,19 @@ public: */ uint64_t get_size(const std::string& key) const; + /** + * @brief Get a duration. + * + * Should be used for MXS_MODULE_PARAM_DURATION parameter types. + * + * @param key Parameter name. + * @param interpretation How a value NOT having a unit suffix should be interpreted. + * + * @return Duration in milliseconds; 0 if the parameter is not found. + */ + std::chrono::milliseconds get_duration(const std::string& key, + mxs::config::DurationInterpretation interpretation) const; + /** * @brief Get a service value * diff --git a/include/maxscale/modinfo.h b/include/maxscale/modinfo.h index a8bd30aa4..4dafa1a68 100644 --- a/include/maxscale/modinfo.h +++ b/include/maxscale/modinfo.h @@ -85,7 +85,8 @@ enum mxs_module_param_type MXS_MODULE_PARAM_SERVICE, /**< Service name */ MXS_MODULE_PARAM_SERVER, /**< Server name */ MXS_MODULE_PARAM_SERVERLIST, /**< List of server names, separated by ',' */ - MXS_MODULE_PARAM_REGEX /**< A regex string enclosed in '/' */ + MXS_MODULE_PARAM_REGEX, /**< A regex string enclosed in '/' */ + MXS_MODULE_PARAM_DURATION, /**< Duration in milliseconds */ }; /** Maximum and minimum values for integer types */ diff --git a/server/core/config.cc b/server/core/config.cc index 596cb5093..726c4537a 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -227,6 +227,8 @@ static pcre2_code* compile_regex_string(const char* regex_string, bool jit_enabled, uint32_t options, uint32_t* output_ovector_size); +static bool duration_is_valid(const char* zValue, mxs::config::DurationUnit* pUnit); + int config_get_ifaddr(unsigned char* output); static int config_get_release_string(char* release); @@ -1750,6 +1752,17 @@ uint64_t MXS_CONFIG_PARAMETER::get_size(const std::string& key) const return intval; } +std::chrono::milliseconds +MXS_CONFIG_PARAMETER::get_duration(const std::string& key, + mxs::config::DurationInterpretation interpretation) const +{ + string value = get_string(key); + std::chrono::milliseconds duration { 0 }; + MXB_AT_DEBUG(bool rval = ) get_suffixed_duration(value.c_str(), interpretation, &duration); + mxb_assert(rval); // When this function is called, the validity of the value should have been checked. + return duration; +} + int64_t MXS_CONFIG_PARAMETER::get_enum(const std::string& key, const MXS_ENUM_VALUE* enum_mapping) const { string param_value = get_string(key); @@ -4322,6 +4335,26 @@ bool config_param_is_valid(const MXS_MODULE_PARAM* params, } break; + case MXS_MODULE_PARAM_DURATION: + { + mxs::config::DurationUnit unit; + + if (duration_is_valid(value, &unit)) + { + valid = true; + + if (unit == mxs::config::DURATION_IN_DEFAULT) + { + MXS_WARNING("Specifying durations without a suffix denoting the unit " + "has been deprecated: '%s=%s'. Use the suffixes 'h' (hour), " + "'m' (minute) 's' (second) or 'ms' (milliseconds). " + "For instance, '%s=%ss' or '%s=%sms.", + key, value, key, value, key, value); + } + } + } + break; + case MXS_MODULE_PARAM_BOOL: if (config_truth_value(value) != -1) { @@ -4818,6 +4851,96 @@ bool get_suffixed_size(const char* value, uint64_t* dest) return rval; } +bool get_suffixed_duration(const char* zValue, + mxs::config::DurationInterpretation interpretation, + std::chrono::milliseconds* pDuration, + mxs::config::DurationUnit* pUnit) +{ + if (!isdigit(*zValue)) + { + // This will also catch negative values + return false; + } + + bool rval = false; + char* zEnd; + uint64_t value = strtoll(zValue, &zEnd, 10); + + std::chrono::milliseconds duration; + mxs::config::DurationUnit unit = mxs::config::DURATION_IN_DEFAULT; + + switch (*zEnd) + { + case 'H': + case 'h': + unit = mxs::config::DURATION_IN_HOURS; + duration = std::chrono::duration_cast(std::chrono::hours(value)); + ++zEnd; + break; + + case 'M': + case 'm': + if (*(zEnd + 1) == 's' || *(zEnd + 1) == 'S') + { + unit = mxs::config::DURATION_IN_MILLISECONDS; + duration = std::chrono::milliseconds(value); + ++zEnd; + } + else + { + unit = mxs::config::DURATION_IN_MINUTES; + duration = std::chrono::duration_cast(std::chrono::minutes(value)); + } + ++zEnd; + break; + + case 'S': + case 's': + unit = mxs::config::DURATION_IN_SECONDS; + duration = std::chrono::duration_cast(std::chrono::seconds(value)); + ++zEnd; + break; + + case 0: + if (interpretation == mxs::config::INTERPRET_AS_SECONDS) + { + duration = std::chrono::duration_cast(std::chrono::seconds(value)); + } + else + { + duration = std::chrono::milliseconds(value); + } + break; + + default: + break; + }; + + if (*zEnd == 0) + { + rval = true; + + if (pDuration) + { + *pDuration = duration; + } + + if (pUnit) + { + *pUnit = unit; + } + } + + return rval; +} + +static bool duration_is_valid(const char* zValue, mxs::config::DurationUnit* pUnit) +{ + // When the validity is checked, it does not matter how the value + // should be interpreted, so any mxs::config::DurationInterpretation is fine. + return get_suffixed_duration(zValue, mxs::config::INTERPRET_AS_SECONDS, nullptr, pUnit); +} + bool config_parse_disk_space_threshold(SERVER::DiskSpaceLimits* pDisk_space_threshold, const char* zDisk_space_threshold) { diff --git a/server/core/internal/config.hh b/server/core/internal/config.hh index f744492c9..dfb2eff80 100644 --- a/server/core/internal/config.hh +++ b/server/core/internal/config.hh @@ -219,6 +219,25 @@ bool is_normal_server_parameter(const char* param); */ bool get_suffixed_size(const char* value, uint64_t* dest); +/** + * Converts a string into a duration, intepreting in a case-insensitive manner + * an 'h'-suffix to indicate hours, an 'm'-suffix to indicate minutes, an + * 's'-suffix to indicate seconds and an 'ms'-suffix to indicate milliseconds. + * + * @param zValue A numerical string, possibly suffixed by 'h', 'm', + * 's' or 'ms'. + * @param interpretation How a value lacking a specific suffix should be interpreted. + * @param pDuration Pointer, if non-NULL, where the result is stored. + * @param pUnit Pointer, if non-NULL, where the detected unit is stored. + * + * @return True on success, false on invalid input in which case @c pUnit and + * @c pDuration will not be modified. + */ +bool get_suffixed_duration(const char* zValue, + mxs::config::DurationInterpretation interpretation, + std::chrono::milliseconds* pDuration, + mxs::config::DurationUnit* pUnit = nullptr); + // Dump a parameter list into a file as `key=value` pairs void dump_param_list(int file, MXS_CONFIG_PARAMETER* list, diff --git a/server/core/test/test_config.cc b/server/core/test/test_config.cc index 6ea25b904..81a6d00c7 100644 --- a/server/core/test/test_config.cc +++ b/server/core/test/test_config.cc @@ -35,14 +35,15 @@ int test_validity() MXS_MODULE_PARAM params[] = { - {"p1", MXS_MODULE_PARAM_INT, "-123" }, - {"p2", MXS_MODULE_PARAM_COUNT, "123" }, - {"p3", MXS_MODULE_PARAM_BOOL, "true" }, - {"p4", MXS_MODULE_PARAM_STRING, "default" }, - {"p5", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_NONE, enum_values}, - {"p6", MXS_MODULE_PARAM_PATH, "/tmp", MXS_MODULE_OPT_PATH_F_OK}, - {"p7", MXS_MODULE_PARAM_SERVICE, "my-service" }, - {"p8", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_ENUM_UNIQUE, enum_values}, + {"p1", MXS_MODULE_PARAM_INT, "-123" }, + {"p2", MXS_MODULE_PARAM_COUNT, "123" }, + {"p3", MXS_MODULE_PARAM_BOOL, "true" }, + {"p4", MXS_MODULE_PARAM_STRING, "default" }, + {"p5", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_NONE, enum_values}, + {"p6", MXS_MODULE_PARAM_PATH, "/tmp", MXS_MODULE_OPT_PATH_F_OK}, + {"p7", MXS_MODULE_PARAM_SERVICE, "my-service" }, + {"p8", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_ENUM_UNIQUE, enum_values}, + {"p9", MXS_MODULE_PARAM_DURATION, "4711s" }, {MXS_END_MODULE_PARAMS} }; @@ -96,6 +97,18 @@ int test_validity() TEST(config_param_is_valid(params, "p6", "/tmp", &ctx)); TEST(!config_param_is_valid(params, "p6", "This is not a valid path", &ctx)); + /** Duration parameter */ + TEST(config_param_is_valid(params, "p9", "4711", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711h", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711m", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711s", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711ms", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711H", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711M", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711S", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711MS", &ctx)); + TEST(!config_param_is_valid(params, "p9", "4711q", &ctx)); + /** Service parameter */ CONFIG_CONTEXT svc = {}; svc.object = (char*)"test-service";