Skip to content

Commit

Permalink
Add support for number unit suffixes
Browse files Browse the repository at this point in the history
There are a number of cases in the configuration where one needs to set
a size (think buffer sizes), and counting zeroes is extremely annoying
and unintuitive. For this reason, we introduce number unit suffixes for
kilo-, mega- and giga-: K, M, G, respectively (case-insensitive,
optionally followed by a 'b' or 'B', for byte). The parser also
understands KiB, MiB, etc to indicate base-two units.

Anywhere where you can specify a number, you'll be able to specify one
with a unit suffix from now on:

 log-fifo-size(200M)

A function to parse longs already existed in basic-funcs (tf_parse_int),
which was moved to lib/parse-number.c now, and enhanced to recognise the
unit suffixes too.

This also closes elastic#28.

Signed-off-by: Balazs Scheidler <[email protected]>
Signed-off-by: Gergely Nagy <[email protected]>
  • Loading branch information
algernon authored and bazsi committed Sep 12, 2013
1 parent 604d412 commit 6ca25bb
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 27 deletions.
2 changes: 2 additions & 0 deletions lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pkginclude_HEADERS += \
lib/ml-batched-timer.h \
lib/msg-format.h \
lib/nvtable.h \
lib/parse-number.h \
lib/persist-state.h \
lib/plugin.h \
lib/plugin-types.h \
Expand Down Expand Up @@ -139,6 +140,7 @@ lib_libsyslog_ng_la_SOURCES = \
lib/ml-batched-timer.c \
lib/msg-format.c \
lib/nvtable.c \
lib/parse-number.c \
lib/persist-state.c \
lib/plugin.c \
lib/poll-events.c \
Expand Down
10 changes: 8 additions & 2 deletions lib/cfg-lex.l
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#include "cfg-lexer.h"
#include "cfg-grammar.h"
#include "messages.h"
#include "misc.h"
#include "parse-number.h"

#include <string.h>
#include <strings.h>
Expand Down Expand Up @@ -160,7 +160,13 @@ word [^ \#'"\(\)\{\}\\;\n\t,|\.@:]
(-|\+)?{digit}+\.{digit}+ { yylval->fnum = strtod(yytext, NULL); return LL_FLOAT; }
0x{xdigit}+ { yylval->num = strtoll(yytext + 2, NULL, 16); return LL_NUMBER; }
0{odigit}+ { yylval->num = strtoll(yytext + 1, NULL, 8); return LL_NUMBER; }
(-|\+)?{digit}+ { yylval->num = strtoll(yytext, NULL, 10); return LL_NUMBER; }
(-|\+)?{digit}+(M|m|G|g|k|K)?(i|I)?(b|B)? {
if (!parse_number_with_suffix(yytext, &yylval->num))
{
YY_FATAL_ERROR("Invalid number specification");
}
return LL_NUMBER;
}
({word}+(\.)?)*{word}+ { return cfg_lexer_lookup_keyword(yyextra, yylval, yylloc, yytext); }
\( |
\) |
Expand Down
161 changes: 161 additions & 0 deletions lib/parse-number.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#include "parse-number.h"

#include <string.h>
#include <errno.h>
#include <stdlib.h>

static gboolean
_valid_unit(const gchar unit_char)
{
return (unit_char == 'B' || unit_char == 'b');
}

static gboolean
_valid_exponent(const gchar exponent_char)
{
gchar e = exponent_char;

return e == 'k' || e == 'K' ||
e == 'm' || e == 'M' ||
e == 'g' || e == 'G';
}

static gboolean
_parse_suffix(const gchar *suffix, gchar *exponent_char, gchar *base_char, gchar *unit_char)
{
gint suffix_len;

suffix_len = strlen(suffix);
if (suffix_len > 3)
return FALSE;
if (suffix_len == 0)
return TRUE;

if (suffix_len == 3)
{
*exponent_char = suffix[0];
*base_char = suffix[1];
*unit_char = suffix[2];
}
else if (suffix_len == 2)
{
*exponent_char = suffix[0];
if (_valid_unit(suffix[1]))
*unit_char = suffix[1];
else
*base_char = suffix[1];
}
else if (suffix_len == 1)
{
if (_valid_exponent(suffix[0]))
*exponent_char = suffix[0];
else if (_valid_unit(suffix[0]))
*unit_char = suffix[0];
else
return FALSE;
}
return TRUE;
}

static gboolean
_determine_multiplier(gchar base_char, glong *multiplier)
{
if (base_char == 0)
*multiplier = 1000;
else if (base_char == 'I' || base_char == 'i')
*multiplier = 1024;
else
return FALSE;
return TRUE;
}

static gboolean
_validate_unit(gchar unit_char)
{
if (unit_char && !_valid_unit(unit_char))
return FALSE;
return TRUE;
}

static gboolean
_process_exponent(gchar exponent_char, glong *d, glong multiplier)
{
switch (exponent_char)
{
case 'G':
case 'g':
(*d) *= multiplier;
case 'm':
case 'M':
(*d) *= multiplier;
case 'K':
case 'k':
(*d) *= multiplier;
case 0:
return TRUE;
default:
return FALSE;
}
}

static gboolean
_process_suffix(const gchar *suffix, glong *d)
{
gchar exponent_char = 0, base_char = 0, unit_char = 0;
glong multiplier = 0;

if (!_parse_suffix(suffix, &exponent_char, &base_char, &unit_char))
return FALSE;

if (!_determine_multiplier(base_char, &multiplier))
return FALSE;

if (!_validate_unit(unit_char))
return FALSE;

if (!_process_exponent(exponent_char, d, multiplier))
return FALSE;

return TRUE;
}

static gboolean
_parse_number(const gchar *s, gchar **endptr, long *d)
{
glong val;

errno = 0;
val = strtoll(s, endptr, 10);

if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
|| (errno != 0 && val == 0))
return FALSE;

if (*endptr == s)
return FALSE;

*d = val;
return TRUE;
}

gboolean
parse_number(const gchar *s, glong *d)
{
gchar *endptr;

if (!_parse_number(s, &endptr, d))
return FALSE;
if (*endptr)
return FALSE;
return TRUE;
}

gboolean
parse_number_with_suffix(const gchar *s, glong *d)
{
gchar *endptr;

if (!_parse_number(s, &endptr, d))
return FALSE;
return _process_suffix(endptr, d);
}
9 changes: 9 additions & 0 deletions lib/parse-number.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef PARSE_NUMBER_H_INCLUDED
#define PARSE_NUMBER_H_INCLUDED

#include "syslog-ng.h"

gboolean parse_number_with_suffix(const gchar *str, glong *result);
gboolean parse_number(const gchar *str, glong *result);

#endif
8 changes: 7 additions & 1 deletion lib/tests/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
lib_tests_TESTS = \
lib/tests/test_log_message \
lib/tests/test_cfg_lexer_subst \
lib/tests/test_type_hints
lib/tests/test_type_hints \
lib/tests/test_parse_number

check_PROGRAMS += ${lib_tests_TESTS}

Expand All @@ -19,3 +20,8 @@ lib_tests_test_type_hints_CFLAGS = \
$(TEST_CFLAGS)
lib_tests_test_type_hints_LDADD = \
$(TEST_LDADD)

lib_tests_test_parse_number_CFLAGS = \
$(TEST_CFLAGS)
lib_tests_test_parse_number_LDADD = \
$(TEST_LDADD)
118 changes: 118 additions & 0 deletions lib/tests/test_parse_number.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include <stdlib.h>

#include "testutils.h"
#include "parse-number.h"

static void
assert_parse_with_suffix(const gchar *str, long expected)
{
long n;
gboolean res;

res = parse_number_with_suffix(str, &n);

assert_gboolean(res, TRUE, "Parsing (w/ suffix) %s failed", str);
assert_gint64(n, expected, "Parsing (w/ suffix) %s failed", str);
}

static void
assert_parse_with_suffix_fails(const gchar *str)
{
long n;
gboolean res;

res = parse_number_with_suffix(str, &n);
assert_gboolean(res, FALSE, "Parsing (w/ suffix) %s succeeded, while expecting failure", str);
}

static void
assert_parse(const gchar *str, long expected)
{
long n;
gboolean res;

res = parse_number(str, &n);

assert_gboolean(res, TRUE, "Parsing (w/o suffix) %s failed", str);
assert_gint64(n, expected, "Parsing (w/o suffix) %s failed", str);
}

static void
assert_parse_fails(const gchar *str)
{
long n;
gboolean res;

res = parse_number(str, &n);

assert_gboolean(res, FALSE, "Parsing (w/o suffix) %s succeeded, while expecting failure", str);
}

static void
test_simple_numbers_are_parsed_properly(void)
{
assert_parse_with_suffix("1234", 1234);
assert_parse_with_suffix("+1234", 1234);
assert_parse_with_suffix("-1234", -1234);

assert_parse("1234", 1234);
}

static void
test_exponent_suffix_is_parsed_properly(void)
{
assert_parse_with_suffix("1K", 1000);
assert_parse_with_suffix("1k", 1000);
assert_parse_with_suffix("1m", 1000 * 1000);
assert_parse_with_suffix("1M", 1000 * 1000);
assert_parse_with_suffix("1G", 1000 * 1000 * 1000);
assert_parse_with_suffix("1g", 1000 * 1000 * 1000);
}

static void
test_byte_units_are_accepted(void)
{
assert_parse_with_suffix("1b", 1);
assert_parse_with_suffix("1B", 1);
assert_parse_with_suffix("1Kb", 1000);
assert_parse_with_suffix("1kB", 1000);
assert_parse_with_suffix("1mb", 1000 * 1000);
assert_parse_with_suffix("1MB", 1000 * 1000);
assert_parse_with_suffix("1Gb", 1000 * 1000 * 1000);
assert_parse_with_suffix("1gB", 1000 * 1000 * 1000);
}

static void
test_base2_is_selected_by_an_i_modifier(void)
{
assert_parse_with_suffix("1Kib", 1024);
assert_parse_with_suffix("1kiB", 1024);
assert_parse_with_suffix("1Ki", 1024);
assert_parse_with_suffix("1kI", 1024);
assert_parse_with_suffix("1mib", 1024 * 1024);
assert_parse_with_suffix("1MiB", 1024 * 1024);
assert_parse_with_suffix("1Gib", 1024 * 1024 * 1024);
assert_parse_with_suffix("1giB", 1024 * 1024 * 1024);
}

static void
test_invalid_formats_are_not_accepted(void)
{
assert_parse_with_suffix_fails("1234Z");
assert_parse_with_suffix_fails("1234kZ");
assert_parse_with_suffix_fails("1234kdZ");
assert_parse_with_suffix_fails("1234kiZ");
assert_parse_fails("1234kiZ");
}

int
main(int argc, char *argv[])
{
test_simple_numbers_are_parsed_properly();
test_exponent_suffix_is_parsed_properly();
test_byte_units_are_accepted();
test_base2_is_selected_by_an_i_modifier();
test_invalid_formats_are_not_accepted();

return 0;
}
21 changes: 1 addition & 20 deletions modules/basicfuncs/basic-funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,14 @@
#include "filter/filter-expr.h"
#include "filter/filter-expr-parser.h"
#include "cfg.h"
#include "parse-number.h"
#include "str-format.h"
#include "plugin-types.h"

#include <stdlib.h>
#include <errno.h>
#include <string.h>

static gboolean
tf_parse_int(const gchar *s, long *d)
{
gchar *endptr;
glong val;

errno = 0;
val = strtoll(s, &endptr, 10);

if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
|| (errno != 0 && val == 0))
return FALSE;

if (endptr == s || *endptr != '\0')
return FALSE;

*d = val;
return TRUE;
}

/* in order to avoid having to declare all construct functions, we
* include them all here. If it causes compilation times to increase
* drastically, we should probably make them into separate compilation
Expand Down
Loading

0 comments on commit 6ca25bb

Please sign in to comment.