Eric Blake
2018-Feb-07 23:32 UTC
[Libguestfs] [nbdkit PATCH v2 0/2] Improve nbdkit_parse_size
Take two, this time split into two patches. I liked Rich's suggestion of unit-testing src/ files directly in src/, and automake is a lot happier with this than with my v1 attempt that tried to compile .c files across directories. It's still enough of a change that I'm not pushing it right away. Eric Blake (2): build: Add unit-testing of internal files utils: Revamp nbdkit_parse_size src/test-utils.c | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.c | 95 +++++++++++++++++++++++++++-------------- .gitignore | 1 + src/Makefile.am | 15 ++++++- 4 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 src/test-utils.c -- 2.14.3
Eric Blake
2018-Feb-07 23:32 UTC
[Libguestfs] [nbdkit PATCH v2 1/2] build: Add unit-testing of internal files
Everything in tests/ is useful for testing our public interface: nbdkit as a whole, as well as plugins and filters. But we are lacking some unit testing of internal functionality, where it is better to build a standalone binary rather than a full-blown plugin. Unit testing belongs best in src/, in part because cross-directory linking (which would be required if the unit test lived in tests/) makes automake unhappy. The first such unit test is of the nbdkit_parse_size() helper function that is exposed to both plugins and filters. The test intentionally has several lines filtered out due to current weaknesses in the code, which will be fixed later. Having tests in two different directories means that you now have to use 'make check -C tests/ TESTS=...' rather than just 'make check TESTS=...' when limiting (re-)testing to just a single test, but that's not too bad (projects that use gnulib tests are already familiar with that restriction). Signed-off-by: Eric Blake <eblake@redhat.com> --- src/test-utils.c | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + src/Makefile.am | 15 ++++++- 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/test-utils.c diff --git a/src/test-utils.c b/src/test-utils.c new file mode 100644 index 0000000..e26a6f3 --- /dev/null +++ b/src/test-utils.c @@ -0,0 +1,128 @@ +/* nbdkit + * Copyright (C) 2018 Red Hat Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <config.h> + +#include <stdio.h> +#include <inttypes.h> +#include <stdbool.h> + +#include "internal.h" + +static bool error_flagged; + +/* Stub for linking against minimal source files, and for proving that + * an error message is issued when expected. */ +void +nbdkit_error (const char *fs, ...) +{ + error_flagged = true; +} + +static bool +test_nbdkit_parse_size (void) +{ + bool pass = true; + struct pair { + const char *str; + int64_t res; + } pairs[] = { + /* Bogus strings */ + { "", -1 }, + { "0x0", -1 }, + { "garbage", -1 }, + /* { "0garbage", -1 }, */ + /* { "-1", -1 }, */ + /* { "-2", -1 }, */ + /* { "9223372036854775808", -1 }, */ + /* { "-9223372036854775808", -1 }, */ + /* { "8E", -1 }, */ + /* { "8192P", -1 }, */ + /* { "999999999999999999999999", -1 }, */ + + /* Strings we may want to support in the future */ + { "M", -1 }, + /* { "1MB", -1 }, */ + /* { "1MiB", -1 }, */ + { "1.5M", -1 }, + + /* Valid strings */ + { "0", 0 }, + { " 08", 8 }, + { "9223372036854775807", INT64_MAX }, + { "1s", 512 }, + { "2S", 1024 }, + { "1b", 1 }, + { "1B", 1 }, + { "1k", 1024 }, + { "1K", 1024 }, + { "1m", 1024 * 1024 }, + { "1M", 1024 * 1024 }, + { "1g", 1024 * 1024 * 1024 }, + { "1G", 1024 * 1024 * 1024 }, + { "1t", 1024LL * 1024 * 1024 * 1024 }, + { "1T", 1024LL * 1024 * 1024 * 1024 }, + { "1p", 1024LL * 1024 * 1024 * 1024 * 1024 }, + { "1P", 1024LL * 1024 * 1024 * 1024 * 1024 }, + { "1e", 1024LL * 1024 * 1024 * 1024 * 1024 * 1024 }, + { "1E", 1024LL * 1024 * 1024 * 1024 * 1024 * 1024 }, + }; + + for (size_t i = 0; i < sizeof pairs / sizeof pairs[0]; i++) { + int64_t r; + + error_flagged = false; + r = nbdkit_parse_size (pairs[i].str); + if (r != pairs[i].res) { + fprintf (stderr, + "Wrong parse for %s, got %" PRId64 ", expected %" PRId64 "\n", + pairs[i].str, r, pairs[i].res); + pass = false; + } + if ((r == -1) != error_flagged) { + fprintf (stderr, "Wrong error message handling for %s\n", pairs[i].str); + pass = false; + } + } + + return pass; +} + +int +main (int argc, char *argv[]) +{ + bool pass = true; + pass &= test_nbdkit_parse_size (); + /* XXX tests nbdkit_absolute_path, nbdkit_read_password */ + return pass ? 0 : 1; +} diff --git a/.gitignore b/.gitignore index 58a0fdd..3b7365f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ Makefile.in /plugins/tar/nbdkit-tar-plugin /src/nbdkit /src/nbdkit.pc +/src/test-utils /stamp-h1 /tests/disk /tests/disk.gz diff --git a/src/Makefile.am b/src/Makefile.am index ae16fde..d11a2b3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ # nbdkit -# Copyright (C) 2013 Red Hat Inc. +# Copyright (C) 2013-2018 Red Hat Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -78,3 +78,16 @@ CLEANFILES = *~ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = nbdkit.pc + +# Unit testing + +TESTS = test-utils + +check_PROGRAMS = test-utils + +test_utils_SOURCES = \ + test-utils.c \ + utils.c \ + cleanup.c +test_utils_CPPFLAGS = -I$(top_srcdir)/include +test_utils_CFLAGS = $(WARNINGS_CFLAGS) -- 2.14.3
Eric Blake
2018-Feb-07 23:32 UTC
[Libguestfs] [nbdkit PATCH v2 2/2] utils: Revamp nbdkit_parse_size
The existing implementation admitted that it was not very robust in the face of garbage input (including, but not limited to, the the fact that the scanf() family has undefined behavior on integer overflow). Tighten things up by reimplementing the function. The old comment mentioned the 'human*' interface from gnulib; we can't use that for licensing reasons; so my rewrite is done from scratch. In particular, I know that gnulib's code accepts 'M' as a synonym for '1M'; treats '1k' and '1kiB' as 1024 but '1kB' as 1000, and so on, which I didn't bother to implement here. The unit test can now be adjusted to cover the cases that are now handled correctly. Signed-off-by: Eric Blake <eblake@redhat.com> --- src/test-utils.c | 20 ++++++------ src/utils.c | 95 ++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/src/test-utils.c b/src/test-utils.c index e26a6f3..80bef2e 100644 --- a/src/test-utils.c +++ b/src/test-utils.c @@ -61,19 +61,19 @@ test_nbdkit_parse_size (void) { "", -1 }, { "0x0", -1 }, { "garbage", -1 }, - /* { "0garbage", -1 }, */ - /* { "-1", -1 }, */ - /* { "-2", -1 }, */ - /* { "9223372036854775808", -1 }, */ - /* { "-9223372036854775808", -1 }, */ - /* { "8E", -1 }, */ - /* { "8192P", -1 }, */ - /* { "999999999999999999999999", -1 }, */ + { "0garbage", -1 }, + { "-1", -1 }, + { "-2", -1 }, + { "9223372036854775808", -1 }, + { "-9223372036854775808", -1 }, + { "8E", -1 }, + { "8192P", -1 }, + { "999999999999999999999999", -1 }, /* Strings we may want to support in the future */ { "M", -1 }, - /* { "1MB", -1 }, */ - /* { "1MiB", -1 }, */ + { "1MB", -1 }, + { "1MiB", -1 }, { "1.5M", -1 }, /* Valid strings */ diff --git a/src/utils.c b/src/utils.c index 5663043..a7519ec 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2013 Red Hat Inc. + * Copyright (C) 2013-2018 Red Hat Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -80,49 +80,78 @@ nbdkit_absolute_path (const char *path) return ret; } -/* XXX Multiple problems with this function. Really we should use the - * 'human*' functions from gnulib. +/* Parse a string with possible scaling suffix, or return -1 after reporting + * the error. */ int64_t nbdkit_parse_size (const char *str) { uint64_t size; - char t; + char *end; + uint64_t scale = 1; - if (sscanf (str, "%" SCNu64 "%c", &size, &t) == 2) { - switch (t) { - case 'b': case 'B': - return (int64_t) size; - case 'k': case 'K': - return (int64_t) size * 1024; - case 'm': case 'M': - return (int64_t) size * 1024 * 1024; - case 'g': case 'G': - return (int64_t) size * 1024 * 1024 * 1024; - case 't': case 'T': - return (int64_t) size * 1024 * 1024 * 1024 * 1024; - case 'p': case 'P': - return (int64_t) size * 1024 * 1024 * 1024 * 1024 * 1024; - case 'e': case 'E': - return (int64_t) size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024; + /* Scan unsigned, but range check later that result fits in signed. */ + /* XXX Should we also parse things like '1.5M'? */ + /* XXX Should we allow hex? If so, hex cannot use scaling suffixes, + * because some of them are valid hex digits */ + errno = 0; + size = strtoumax (str, &end, 10); + if (errno || str == end) { + nbdkit_error ("could not parse size string (%s)", str); + return -1; + } + switch (*end) { + case '\0': + end--; /* Safe, since we already filtered out empty string */ + break; + + /* Powers of 1024 */ + case 'e': case 'E': + scale *= 1024; + /* fallthru */ + case 'p': case 'P': + scale *= 1024; + /* fallthru */ + case 't': case 'T': + scale *= 1024; + /* fallthru */ + case 'g': case 'G': + scale *= 1024; + /* fallthru */ + case 'm': case 'M': + scale *= 1024; + /* fallthru */ + case 'k': case 'K': + scale *= 1024; + /* fallthru */ + case 'b': case 'B': + break; - case 's': case 'S': /* "sectors", ie. units of 512 bytes, - * even if that's not the real sector size - */ - return (int64_t) size * 512; + case 's': case 'S': /* "sectors", ie. units of 512 bytes, + * even if that's not the real sector size + */ + scale = 512; + break; - default: - nbdkit_error ("could not parse size: unknown specifier '%c'", t); - return -1; - } + default: + nbdkit_error ("could not parse size: unknown suffix '%s'", end); + return -1; } - /* bytes */ - if (sscanf (str, "%" SCNu64, &size) == 1) - return (int64_t) size; + /* XXX Maybe we should support 'MiB' as a synonym for 'M'; and 'MB' + * for powers of 1000, for similarity to GNU tools. But for now, + * anything beyond 'M' is dropped. */ + if (end[1]) { + nbdkit_error ("could not parse size: unknown suffix '%s'", end); + return -1; + } + + if (INT64_MAX / scale < size) { + nbdkit_error ("overflow computing size (%s)", str); + return -1; + } - nbdkit_error ("could not parse size string (%s)", str); - return -1; + return size * scale; } /* Read a password from configuration value. */ -- 2.14.3
Richard W.M. Jones
2018-Feb-08 21:39 UTC
Re: [Libguestfs] [nbdkit PATCH v2 2/2] utils: Revamp nbdkit_parse_size
ACK series. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW
Seemingly Similar Threads
- [PATCH nbdkit] server: Move size parsing code (nbdkit_parse_size) to common/include
- [nbdkit PATCH v2 2/2] utils: Revamp nbdkit_parse_size
- [PATCH nbdkit] server: utils: Fix nbdkit_parse_size to correctly handle negative values
- [RFC nbdkit PATCH] utils: Revamp nbdkit_parse_size
- [PATCH nbdkit v2] server: utils: Make nbdkit_parse_size to reject negative values