From: Arvin Schnell Date: Wed, 16 Sep 2020 10:25:42 +0000 (+0200) Subject: - added utility classes and functions X-Git-Tag: v0.8.14~12^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=65168a1165530f71f62cff7279847861f55085da;p=thirdparty%2Fsnapper.git - added utility classes and functions --- diff --git a/LIBVERSION b/LIBVERSION index 6b244dcd..831446cb 100644 --- a/LIBVERSION +++ b/LIBVERSION @@ -1 +1 @@ -5.0.1 +5.1.0 diff --git a/client/utils/HumanString.cc b/client/utils/HumanString.cc index ebf53ee9..2fb50c13 100644 --- a/client/utils/HumanString.cc +++ b/client/utils/HumanString.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2004-2014] Novell, Inc. - * Copyright (c) [2016-2018] SUSE LLC + * Copyright (c) [2016-2020] SUSE LLC * * All Rights Reserved. * @@ -21,22 +21,26 @@ */ -#include #include +#include #include #include +#include #include "HumanString.h" #include "text.h" +#include + namespace snapper { - using std::string; + using namespace std; /* - * These are simplified versions of the functions in libstorage-ng. + * These are simplified versions of the functions in libstorage-ng, + * https://github.com/openSUSE/libstorage-ng/blob/master/storage/Utils/HumanString.h. */ @@ -47,56 +51,180 @@ namespace snapper } - string - get_suffix(int i) + vector + get_all_suffixes(int i, bool all = true) { + vector ret; + switch (i) { case 0: // TRANSLATORS: symbol for "bytes" (best keep untranslated) - return _("B"); + ret.push_back(_("B")); + if (all) + { + ret.push_back(""); + } + break; case 1: // TRANSLATORS: symbol for "kibi bytes" (best keep untranslated) - return _("KiB"); + ret.push_back(_("KiB")); + if (all) + { + // TRANSLATORS: symbol for "kilo bytes" (best keep untranslated) + ret.push_back(_("kB")); + // TRANSLATORS: symbol for "kilo" (best keep untranslated) + ret.push_back(_("k")); + } + break; case 2: // TRANSLATORS: symbol for "mebi bytes" (best keep untranslated) - return _("MiB"); + ret.push_back(_("MiB")); + if (all) + { + // TRANSLATORS: symbol for "mega bytes" (best keep untranslated) + ret.push_back(_("MB")); + // TRANSLATORS: symbol for "mega" (best keep untranslated) + ret.push_back(_("M")); + } + break; case 3: // TRANSLATORS: symbol for "gibi bytes" (best keep untranslated) - return _("GiB"); + ret.push_back(_("GiB")); + if (all) + { + // TRANSLATORS: symbol for "giga bytes" (best keep untranslated) + ret.push_back(_("GB")); + // TRANSLATORS: symbol for "giga" (best keep untranslated) + ret.push_back(_("G")); + } + break; case 4: // TRANSLATORS: symbol for "tebi bytes" (best keep untranslated) - return _("TiB"); + ret.push_back(_("TiB")); + if (all) + { + // TRANSLATORS: symbol for "tera bytes" (best keep untranslated) + ret.push_back(_("TB")); + // TRANSLATORS: symbol for "tera" (best keep untranslated) + ret.push_back(_("T")); + } + break; case 5: // TRANSLATORS: symbol for "pebi bytes" (best keep untranslated) - return _("PiB"); + ret.push_back(_("PiB")); + if (all) + { + // TRANSLATORS: symbol for "peta bytes" (best keep untranslated) + ret.push_back(_("PB")); + // TRANSLATORS: symbol for "peta" (best keep untranslated) + ret.push_back(_("P")); + } + break; case 6: // TRANSLATORS: symbol for "exbi bytes" (best keep untranslated) - return _("EiB"); - - default: - throw std::logic_error("invalid prefix"); + ret.push_back(_("EiB")); + if (all) + { + // TRANSLATORS: symbol for "exa bytes" (best keep untranslated) + ret.push_back(_("EB")); + // TRANSLATORS: symbol for "exa" (best keep untranslated) + ret.push_back(_("E")); + } + break; } + + return ret; } - bool - is_multiple_of(unsigned long long i, unsigned long long j) + string + get_suffix(int i) { - return i % j == 0; + return get_all_suffixes(i, false).front(); } - int - clz(unsigned long long i) + namespace { - return __builtin_clzll(i); + + int + clz(unsigned long long i) + { + return __builtin_clzll(i); + } + + + // Helper functions to parse a number as int or float, multiply + // according to the suffix. Do all required error checks. + + pair + parse_i(const string& str, int i) + { + istringstream s(str); + + unsigned long long v; + s >> v; + + if (!s.eof()) + return make_pair(false, 0); + + if (s.fail()) + { + if (v == std::numeric_limits::max()) + SN_THROW(Exception("overflow")); + + return make_pair(false, 0); + } + + if (v != 0 && str[0] == '-') + SN_THROW(Exception("overflow")); + + if (v > 0 && clz(v) < 10 * i) + SN_THROW(Exception("overflow")); + + v <<= 10 * i; + + return make_pair(true, v); + } + + + pair + parse_f(const string& str, int i) + { + istringstream s(str); + + long double v; + s >> v; + + if (!s.eof()) + return make_pair(false, 0); + + if (s.fail()) + { + if (v == std::numeric_limits::max()) + SN_THROW(Exception("overflow")); + + return make_pair(false, 0); + } + + if (v < 0.0) + SN_THROW(Exception("overflow")); + + v = std::round(std::ldexp(v, 10 * i)); + + if (v > std::numeric_limits::max()) + SN_THROW(Exception("overflow")); + + return make_pair(true, v); + } + } @@ -107,8 +235,6 @@ namespace snapper string byte_to_humanstring(unsigned long long size, int precision) { - const std::locale loc = std::locale(); - // Calculate the index of the suffix to use. The index increases by 1 // when the number of leading zeros decreases by 10. @@ -121,7 +247,6 @@ namespace snapper unsigned long long v = size >> 10 * i; std::ostringstream s; - s.imbue(loc); s << v << ' ' << get_suffix(i); return s.str(); } @@ -130,7 +255,6 @@ namespace snapper long double v = std::ldexp((long double)(size), - 10 * i); std::ostringstream s; - s.imbue(loc); s.setf(std::ios::fixed); s.precision(precision); s << v << ' ' << get_suffix(i); @@ -138,4 +262,35 @@ namespace snapper } } + + unsigned long long + humanstring_to_byte(const string& str) + { + const string str_trimmed = boost::trim_copy(str); + + for (int i = 0; i < num_suffixes(); ++i) + { + for (const string& suffix : get_all_suffixes(i)) + { + if (boost::iends_with(str_trimmed, suffix)) + { + string number = boost::trim_copy(str_trimmed.substr(0, str_trimmed.size() - suffix.size())); + + pair v; + + v = parse_i(number, i); + if (v.first) + return v.second; + + v = parse_f(number, i); + if (v.first) + return v.second; + } + } + } + + SN_THROW(Exception("failed to parse size")); + __builtin_unreachable(); + } + } diff --git a/client/utils/HumanString.h b/client/utils/HumanString.h index 1d3a75ba..e665aa3b 100644 --- a/client/utils/HumanString.h +++ b/client/utils/HumanString.h @@ -1,6 +1,6 @@ /* * Copyright (c) [2004-2014] Novell, Inc. - * Copyright (c) [2016,2018] SUSE LLC + * Copyright (c) [2016-2020] SUSE LLC * * All Rights Reserved. * @@ -48,16 +48,15 @@ namespace snapper * Return a suffix. * * @param i index of suffix - * @param classic use classic locale instead of global C++ locale * @return suffix */ std::string get_suffix(int i); /** - * Return a pretty description of a size with required precision and using - * B, KiB, MiB, GiB, TiB, PiB or EiB as unit as appropriate. Supported - * range is 0 B to (16 EiB - 1 B). + * Return a pretty description of a size with required precision and using B, KiB, + * MiB, GiB, TiB, PiB or EiB as unit as appropriate. Supported range is 0 B to (16 EiB + * - 1 B). * * @param size size in bytes * @param precision number of fraction digits in output @@ -65,4 +64,17 @@ namespace snapper */ std::string byte_to_humanstring(unsigned long long size, int precision); + + /** + * Converts a size description using B, KiB, MiB, GiB, TiB, PiB or EiB into an + * integer. Decimal suffixes are also allowed and treated as binary. Supported range + * is 0 B to (16 EiB - 1 B). + * + * @param str size string + * @return bytes + * + * The conversion is always case-insensitive. + */ + unsigned long long humanstring_to_byte(const std::string& str); + } diff --git a/snapper/AppUtil.cc b/snapper/AppUtil.cc index 553065cd..3c505a86 100644 --- a/snapper/AppUtil.cc +++ b/snapper/AppUtil.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2004-2015] Novell, Inc. - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016-2020] SUSE LLC * * All Rights Reserved. * @@ -415,4 +415,11 @@ namespace snapper return s << fixed << sw.read() << "s"; } + + bool + Uuid::operator==(const Uuid& rhs) const + { + return std::equal(std::begin(value), std::end(value), std::begin(rhs.value)); + } + } diff --git a/snapper/AppUtil.h b/snapper/AppUtil.h index 0e449614..b9858cfa 100644 --- a/snapper/AppUtil.h +++ b/snapper/AppUtil.h @@ -1,5 +1,6 @@ /* * Copyright (c) [2004-2015] Novell, Inc. + * Copyright (c) 2020 SUSE LLC * * All Rights Reserved. * @@ -121,7 +122,7 @@ namespace snapper ~FdCloser() { - if (fd > -1 ) + if (fd > -1) ::close(fd); } @@ -158,6 +159,17 @@ namespace snapper const int error_number; }; + + class Uuid + { + public: + + uint8_t value[16]; + + bool operator==(const Uuid& rhs) const; + + }; + } #endif diff --git a/snapper/BtrfsUtils.cc b/snapper/BtrfsUtils.cc index 8c31c721..a1f6b0b9 100644 --- a/snapper/BtrfsUtils.cc +++ b/snapper/BtrfsUtils.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2011-2015] Novell, Inc. - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016-2020] SUSE LLC * * All Rights Reserved. * @@ -145,7 +145,7 @@ namespace snapper size_t size = sizeof(btrfs_qgroup_inherit) + sizeof(((btrfs_qgroup_inherit*) 0)->qgroups[0]); vector buffer(size, 0); - + if (qgroup != no_qgroup) { struct btrfs_qgroup_inherit* inherit = (btrfs_qgroup_inherit*) &buffer[0]; @@ -624,6 +624,34 @@ namespace snapper throw runtime_error_with_errno("ioctl(BTRFS_IOC_SYNC) failed", errno); } + + Uuid + get_uuid(int fd) + { + static_assert(BTRFS_UUID_SIZE == 16); + + struct btrfs_ioctl_fs_info_args fs_info_args; + if (ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info_args) < 0) + throw runtime_error_with_errno("ioctl(BTRFS_IOC_FS_INFO) failed", errno); + + Uuid uuid; + std::copy(std::begin(fs_info_args.fsid), std::end(fs_info_args.fsid), std::begin(uuid.value)); + return uuid; + } + + + Uuid + get_uuid(const string& path) + { + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) + throw runtime_error_with_errno("open failed", errno); + + FdCloser fd_closer(fd); + + return BtrfsUtils::get_uuid(fd); + } + } } diff --git a/snapper/BtrfsUtils.h b/snapper/BtrfsUtils.h index dd3dd174..cb0e0c06 100644 --- a/snapper/BtrfsUtils.h +++ b/snapper/BtrfsUtils.h @@ -1,6 +1,6 @@ /* * Copyright (c) [2011-2015] Novell, Inc. - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016-2020] SUSE LLC * * All Rights Reserved. * @@ -29,6 +29,8 @@ #include #include +#include "snapper/AppUtil.h" + namespace snapper { @@ -84,19 +86,20 @@ namespace snapper struct QGroupUsage { - QGroupUsage() : referenced(0), referenced_compressed(0), exclusive(0), - exclusive_compressed(0) {} - - uint64_t referenced; - uint64_t referenced_compressed; - uint64_t exclusive; - uint64_t exclusive_compressed; + uint64_t referenced = 0; + uint64_t referenced_compressed = 0; + uint64_t exclusive = 0; + uint64_t exclusive_compressed = 0; }; QGroupUsage qgroup_query_usage(int fd, qgroup_t qgroup); void sync(int fd); + Uuid get_uuid(int fd); + + Uuid get_uuid(const string& path); + } } diff --git a/testsuite/humanstring.cc b/testsuite/humanstring.cc index 596c9771..7adec098 100644 --- a/testsuite/humanstring.cc +++ b/testsuite/humanstring.cc @@ -4,9 +4,9 @@ #include -#include #include +#include #include "../client/utils/HumanString.h" @@ -27,6 +27,15 @@ test(const char* loc, unsigned long long size, int precision) } +unsigned long long +test(const char* loc, const char* str) +{ + locale::global(locale(loc)); + + return humanstring_to_byte(str); +} + + BOOST_AUTO_TEST_CASE(test_byte_to_humanstring) { BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 0, 2), "0 B"); @@ -48,6 +57,56 @@ BOOST_AUTO_TEST_CASE(test_byte_to_humanstring) } +BOOST_AUTO_TEST_CASE(test_humanstring_to_byte) +{ + BOOST_CHECK_THROW(test("en_GB.UTF-8", "hello"), Exception); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "0 B"), 0); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "-0 B"), 0); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "+0 B"), 0); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "42B"), 42); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "42 b"), 42); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "42"), 42); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "42b"), 42); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "42 B"), 42); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "12.4GB"), 13314398618); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "12.4 GB"), 13314398618); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "12.4 gb"), 13314398618); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "12.4g"), 13314398618); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "12.4 G"), 13314398618); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "123,456 kB"), 126418944); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", "123.456 kB"), 126418944); + BOOST_CHECK_EQUAL(test("de_CH.UTF-8", "123'456 kB"), 126418944); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "123,456.789kB"), 126419752); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", "123.456,789kB"), 126419752); + BOOST_CHECK_EQUAL(test("de_CH.UTF-8", "123'456.789kB"), 126419752); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "123,456.789 kB"), 126419752); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", "123.456,789 kB"), 126419752); + BOOST_CHECK_EQUAL(test("de_CH.UTF-8", "123'456.789 kB"), 126419752); + + BOOST_CHECK_THROW(test("en_US.UTF-8", "5 G B"), Exception); + + BOOST_CHECK_THROW(test("de_DE.UTF-8", "12.34 kB"), Exception); + BOOST_CHECK_THROW(test("de_DE.UTF-8", "12'34 kB"), Exception); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "3.14 G"), 3371549327); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "3.14 GB"), 3371549327); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "3.14 GiB"), 3371549327); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "12345 GB"), 13255342817280); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", "12345 GB"), 13255342817280); + BOOST_CHECK_EQUAL(test("de_CH.UTF-8", "12345 GB"), 13255342817280); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", ".5 GiB"), 512 * MiB); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", ",5 GiB"), 512 * MiB); +} + + BOOST_AUTO_TEST_CASE(test_big_numbers) { // 1 EiB - 1 B @@ -55,10 +114,39 @@ BOOST_AUTO_TEST_CASE(test_big_numbers) // 1 EiB BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB, 2), "1.00 EiB"); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "1 EiB"), 1 * EiB); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "1.00 EiB"), 1 * EiB); // 1 EiB + 1 B BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB + 1 * B, 2), "1.00 EiB"); // 16 EiB - 1 B BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 16 * EiB - 1 * B, 2), "16.00 EiB"); + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", "18446744073709551615 B"), 16 * EiB - 1 * B); + + // 16 EiB + BOOST_CHECK_THROW(test("en_GB.UTF-8", "16 EiB"), Exception); + BOOST_CHECK_THROW(test("en_GB.UTF-8", "18446744073709551616 B"), Exception); +} + + +BOOST_AUTO_TEST_CASE(test_ridiculous_high_numbers) +{ + // The unshifted value fits 64-bit IEEE but the shifted value + // overflows. Tests error handling if long double is 64-bit IEEE. + BOOST_CHECK_THROW(test("en_GB.UTF-8", "1.0E305 EiB"), Exception); + + // The unshifted value fits 80-bit IEEE but the shifted value + // overflows. Tests error handling if long double is 80-bit IEEE. + BOOST_CHECK_THROW(test("en_GB.UTF-8", "1.0E4930 EiB"), Exception); + + // Even the unshifted value is too high for 80-bit (and even 128-bit) IEEE. + BOOST_CHECK_THROW(test("en_GB.UTF-8", "1.0E5000 B"), Exception); +} + + +BOOST_AUTO_TEST_CASE(test_negative_numbers) +{ + BOOST_CHECK_THROW(test("en_GB.UTF-8", "-1 B"), Exception); + BOOST_CHECK_THROW(test("en_GB.UTF-8", "-1.0 B"), Exception); }