]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- added utility classes and functions
authorArvin Schnell <aschnell@suse.de>
Wed, 16 Sep 2020 10:25:42 +0000 (12:25 +0200)
committerArvin Schnell <aschnell@suse.de>
Wed, 16 Sep 2020 10:29:44 +0000 (12:29 +0200)
LIBVERSION
client/utils/HumanString.cc
client/utils/HumanString.h
snapper/AppUtil.cc
snapper/AppUtil.h
snapper/BtrfsUtils.cc
snapper/BtrfsUtils.h
testsuite/humanstring.cc

index 6b244dcd6960b101b0ab4d9e5162d39632dec80c..831446cbd27a6de403344b21c9fa93a25357f43d 100644 (file)
@@ -1 +1 @@
-5.0.1
+5.1.0
index ebf53ee972effefbd0bc8f13672fded5d786cb07..2fb50c13a973080b7b4d5533c2293c339dec9df4 100644 (file)
@@ -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.
  *
  */
 
 
-#include <locale>
 #include <cmath>
+#include <vector>
 #include <sstream>
 #include <stdexcept>
+#include <boost/algorithm/string.hpp>
 
 #include "HumanString.h"
 #include "text.h"
 
+#include <snapper/Exception.h>
+
 
 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<string>
+    get_all_suffixes(int i, bool all = true)
     {
+       vector<string> 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<bool, unsigned long long>
+       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<unsigned long long>::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<bool, unsigned long long>
+       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<long double>::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<unsigned long long>::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<bool, unsigned long long> 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();
+    }
+
 }
index 1d3a75ba6fd06899cebc85dfd698ca2e83209e2e..e665aa3b3de8765109619832e01ca1c74b163d85 100644 (file)
@@ -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);
+
 }
index 553065cda0d834b734046edd257926d025ac9bfb..3c505a86e275cf0ab6555eb179abbc9562310789 100644 (file)
@@ -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));
+    }
+
 }
index 0e449614d4a8bfd5891dcfc988f87de201684612..b9858cfa50b6b71b37205c985e83872ecdc9df7f 100644 (file)
@@ -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
index 8c31c721698a880a8ab59196a8ec569dc9ef97e0..a1f6b0b90f39dd510be078e66ec3e6b7891c9a4c 100644 (file)
@@ -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<char> 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);
+       }
+
     }
 
 }
index dd3dd1746a2c00b9688d416bde7631755cb7318b..cb0e0c06a58f3476b1f1c6ffb0c161fb97855924 100644 (file)
@@ -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 <string>
 #include <vector>
 
+#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);
+
     }
 
 }
index 596c9771293a62af3ca30372a62639d5abcb7921..7adec098503183215d5654b62adb7314a1f8d90f 100644 (file)
@@ -4,9 +4,9 @@
 
 #include <boost/test/unit_test.hpp>
 
-#include <iostream>
 #include <locale>
 
+#include <snapper/Exception.h>
 #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);
 }