diff --git a/doc/user.rst b/doc/user.rst index c92a3de55..c4b07b9e6 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -712,8 +712,9 @@ Do not change these unless you know what you are doing. * - Setting - Description - * - **audio_buffer_size KBYTES** - - Adjust the size of the internal audio buffer. Default is 4096 (4 MiB). + * - **audio_buffer_size SIZE** + - Adjust the size of the internal audio buffer. Default is + :samp:`4 MB` (4 MiB). Zeroconf ~~~~~~~~ diff --git a/src/Main.cxx b/src/Main.cxx index 7adf42689..86a10f3d2 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -58,6 +58,7 @@ #include "config/Defaults.hxx" #include "config/Option.hxx" #include "config/Domain.hxx" +#include "config/Parser.hxx" #include "util/RuntimeError.hxx" #include "util/ScopeExit.hxx" @@ -280,12 +281,10 @@ initialize_decoder_and_player(Instance &instance, param = config.GetParam(ConfigOption::AUDIO_BUFFER_SIZE); if (param != nullptr) { buffer_size = param->With([](const char *s){ - char *test; - long tmp = strtol(s, &test, 10); - if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) + size_t result = ParseSize(s, KILOBYTE); + if (result <= 0) throw FormatRuntimeError("buffer size \"%s\" is not a " "positive integer", s); - size_t result = tmp * KILOBYTE; if (result < MIN_BUFFER_SIZE) { FormatWarning(config_domain, "buffer size %lu is too small, using %lu bytes instead", diff --git a/src/config/Parser.cxx b/src/config/Parser.cxx index 2f06b8952..7e472944b 100644 --- a/src/config/Parser.cxx +++ b/src/config/Parser.cxx @@ -19,6 +19,7 @@ #include "Parser.hxx" #include "util/RuntimeError.hxx" +#include "util/StringStrip.hxx" #include "util/StringUtil.hxx" bool @@ -35,3 +36,69 @@ ParseBool(const char *value) throw FormatRuntimeError("Not a valid boolean (\"yes\" or \"no\"): \"%s\"", value); } + +template +static size_t +Multiply(size_t value) +{ + static constexpr size_t MAX_INPUT = SIZE_MAX / OPERAND; + if (value > MAX_INPUT) + throw std::runtime_error("Value too large"); + + return value * OPERAND; +} + +size_t +ParseSize(const char *s, size_t default_factor) +{ + char *endptr; + size_t value = strtoul(s, &endptr, 10); + if (endptr == s) + throw std::runtime_error("Failed to parse integer"); + + static constexpr size_t KILO = 1024; + static constexpr size_t MEGA = 1024 * KILO; + static constexpr size_t GIGA = 1024 * MEGA; + + s = StripLeft(endptr); + + bool apply_factor = false; + + switch (*s) { + case 'k': + value = Multiply(value); + ++s; + break; + + case 'M': + value = Multiply(value); + ++s; + break; + + case 'G': + value = Multiply(value); + ++s; + break; + + case '\0': + apply_factor = true; + break; + + default: + throw std::runtime_error("Unknown size suffix"); + } + + /* ignore 'B' for "byte" */ + if (*s == 'B') { + apply_factor = false; + ++s; + } + + if (*s != '\0') + throw std::runtime_error("Unknown size suffix"); + + if (apply_factor) + value *= default_factor; + + return value; +} diff --git a/src/config/Parser.hxx b/src/config/Parser.hxx index 4e19f052c..155a8993d 100644 --- a/src/config/Parser.hxx +++ b/src/config/Parser.hxx @@ -20,10 +20,20 @@ #ifndef MPD_CONFIG_PARSER_HXX #define MPD_CONFIG_PARSER_HXX +#include + /** * Throws on error. */ bool ParseBool(const char *value); +/** + * Parse a string as a byte size. + * + * Throws on error. + */ +size_t +ParseSize(const char *s, size_t default_factor=1); + #endif