]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- show used space for each snapshot
authorArvin Schnell <aschnell@suse.de>
Tue, 2 Oct 2018 10:08:09 +0000 (12:08 +0200)
committerArvin Schnell <aschnell@suse.de>
Tue, 2 Oct 2018 10:08:09 +0000 (12:08 +0200)
15 files changed:
VERSION
client/commands.cc
client/commands.h
client/proxy-dbus.cc
client/proxy-dbus.h
client/proxy-lib.h
client/proxy.h
client/snapper.cc
client/utils/HumanString.cc [new file with mode: 0644]
client/utils/HumanString.h [new file with mode: 0644]
client/utils/Makefile.am
doc/snapper.xml.in
package/snapper.changes
testsuite/Makefile.am
testsuite/humanstring.cc [new file with mode: 0644]

diff --git a/VERSION b/VERSION
index b49b25336d4748652e200792129dc347ee8926e2..a918a2aa18d5bec6a8bb93891a7a63c243111796 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.5.6
+0.6.0
index 41c4dc12eae17ec59d00919423e98cdca4d8aebe..3745a5358a15353f660079a6636b6e78317a627f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2012-2015] Novell, Inc.
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016,2018] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -293,6 +293,49 @@ command_delete_snapshots(DBus::Connection& conn, const string& config_name,
 }
 
 
+void
+command_calculate_used_space(DBus::Connection& conn, const string& config_name)
+{
+    DBus::MessageMethodCall call(SERVICE, OBJECT, INTERFACE, "CalculateUsedSpace");
+
+    DBus::Hoho hoho(call);
+    hoho << config_name;
+
+    try
+    {
+       conn.send_with_reply_and_block(call);
+    }
+    catch (const DBus::ErrorException& e)
+    {
+       SN_CAUGHT(e);
+
+       if (strcmp(e.name(), "error.quota") == 0)
+           SN_THROW(QuotaException(e.message()));
+
+       SN_RETHROW(e);
+    }
+}
+
+
+uint64_t
+command_get_used_space(DBus::Connection& conn, const string& config_name, unsigned int num)
+{
+    DBus::MessageMethodCall call(SERVICE, OBJECT, INTERFACE, "GetUsedSpace");
+
+    DBus::Hoho hoho(call);
+    hoho << config_name << num;
+
+    DBus::Message reply = conn.send_with_reply_and_block(call);
+
+    uint64_t used_space;
+
+    DBus::Hihi hihi(reply);
+    hihi >> used_space;
+
+    return used_space;
+}
+
+
 string
 command_mount_snapshot(DBus::Connection& conn, const string& config_name,
                       unsigned int num, bool user_request)
index b70aa9572aa352921f56153133ac61472ece25d8..b1a27e7b8392281d1a31c299d112e28c774b0810 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2012-2015] Novell, Inc.
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016,2018] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -90,6 +90,12 @@ void
 command_delete_snapshots(DBus::Connection& conn, const string& config_name,
                         const vector<unsigned int>& nums, bool verbose);
 
+void
+command_calculate_used_space(DBus::Connection& conn, const string& config_name);
+
+uint64_t
+command_get_used_space(DBus::Connection& conn, const string& config_name, unsigned int num);
+
 string
 command_mount_snapshot(DBus::Connection& conn, const string& config_name,
                       unsigned int num, bool user_request);
index 262fc0640b339c4a2ee0a71ca7c22485f404ebfd..38cd26c2818debf2d7349882e2b29ea77043f071 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016,2018] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -53,6 +53,20 @@ ProxySnapshotDbus::ProxySnapshotDbus(ProxySnapshotsDbus* backref, SnapshotType t
 }
 
 
+void
+ProxySnapperDbus::calculateUsedSpace() const
+{
+    command_calculate_used_space(conn(), config_name);
+}
+
+
+uint64_t
+ProxySnapshotDbus::getUsedSpace() const
+{
+    return command_get_used_space(conn(), configName(), num);
+}
+
+
 string
 ProxySnapshotDbus::mountFilesystemSnapshot(bool user_request) const
 {
index 0c49771ee1137ba846c94c3cf60a583de1282c15..ff9014934d8789d79c7d0e0cd5d397fc05ea2885 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016,2018] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -62,6 +62,8 @@ public:
 
     virtual bool isCurrent() const override { return num == 0; }
 
+    virtual uint64_t getUsedSpace() const override;
+
     virtual string mountFilesystemSnapshot(bool user_request) const override;
     virtual void umountFilesystemSnapshot(bool user_request) const override;
 
@@ -141,6 +143,8 @@ public:
 
     virtual QuotaData queryQuotaData() const override;
 
+    virtual void calculateUsedSpace() const override;
+
     DBus::Connection& conn() const;
 
 private:
index 8c8528257bb7a5703009b7b4d8c44a3151318937..90ae1f72f93409fddd37538e0e7622893c851a0f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016,2018] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -50,6 +50,11 @@ public:
 
     virtual bool isCurrent() const override { return it->isCurrent(); }
 
+    virtual uint64_t getUsedSpace() const override
+    {
+       return it->getUsedSpace();
+    }
+
     virtual string mountFilesystemSnapshot(bool user_request) const override
     {
        it->mountFilesystemSnapshot(user_request);
@@ -121,6 +126,8 @@ public:
 
     virtual QuotaData queryQuotaData() const override { return snapper->queryQuotaData(); }
 
+    virtual void calculateUsedSpace() const override { snapper->calculateUsedSpace(); }
+
     std::unique_ptr<Snapper> snapper;
 
 private:
index ae15a2c59f5029af1e34063d1c3a18febfff264c..b9f1981f7bfcfbce5737ad3cc0fb48fe5bc50bbd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) [2016-2017] SUSE LLC
+ * Copyright (c) [2016-2018] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -107,6 +107,8 @@ public:
 
     bool isCurrent() const { return impl->isCurrent(); }
 
+    uint64_t getUsedSpace() const { return impl->getUsedSpace(); }
+
     void mountFilesystemSnapshot(bool user_request) const
        { impl->mountFilesystemSnapshot(user_request); }
 
@@ -133,6 +135,8 @@ public:
 
        virtual bool isCurrent() const = 0;
 
+       virtual uint64_t getUsedSpace() const = 0;
+
        virtual string mountFilesystemSnapshot(bool user_request) const = 0;
        virtual void umountFilesystemSnapshot(bool user_request) const = 0;
 
@@ -233,6 +237,8 @@ public:
 
     virtual QuotaData queryQuotaData() const = 0;
 
+    virtual void calculateUsedSpace() const = 0;
+
 };
 
 
index d38138b5dbeb4dbf213693bee25baea2ba26c11e..d42cbebc6b914ce33b405c38deea6855fd69cee9 100644 (file)
@@ -45,6 +45,7 @@
 #include "utils/text.h"
 #include "utils/Table.h"
 #include "utils/GetOpts.h"
+#include "utils/HumanString.h"
 
 #include "cleanup.h"
 #include "errors.h"
@@ -354,6 +355,7 @@ help_list()
         << endl
         << _("    Options for 'list' command:") << endl
         << _("\t--type, -t <type>\t\tType of snapshots to list.") << endl
+        << _("\t--disable-used-space\t\tDisable showing used space.") << endl
         << _("\t--all-configs, -a\t\tList snapshots from all accessible configs.") << endl
         << endl;
 }
@@ -362,7 +364,7 @@ enum ListMode { LM_ALL, LM_SINGLE, LM_PRE_POST };
 
 
 void
-list_from_one_config(ProxySnapper* snapper, ListMode list_mode);
+list_from_one_config(ProxySnapper* snapper, ListMode list_mode, bool show_used_space);
 
 
 void
@@ -370,6 +372,7 @@ command_list(ProxySnappers* snappers, ProxySnapper*)
 {
     const struct option options[] = {
        { "type",               required_argument,      0,      't' },
+       { "disable-used-space", no_argument,            0,      0 },
        { "all-configs",        no_argument,            0,      'a' },
        { 0, 0, 0, 0 }
     };
@@ -382,6 +385,7 @@ command_list(ProxySnappers* snappers, ProxySnapper*)
     }
 
     ListMode list_mode = LM_ALL;
+    bool show_used_space = true;
 
     GetOpts::parsed_opts::const_iterator opt;
 
@@ -400,6 +404,11 @@ command_list(ProxySnappers* snappers, ProxySnapper*)
        }
     }
 
+    if ((opt = opts.find("disable-used-space")) != opts.end())
+    {
+       show_used_space = false;
+    }
+
     vector<string> tmp;
 
     if ((opt = opts.find("all-configs")) == opts.end())
@@ -426,14 +435,40 @@ command_list(ProxySnappers* snappers, ProxySnapper*)
                  << snapper->getConfig().getSubvolume() << endl;
         }
 
-        list_from_one_config(snapper, list_mode);
+        list_from_one_config(snapper, list_mode, show_used_space);
     }
 }
 
 
 void
-list_from_one_config(ProxySnapper* snapper, ListMode list_mode)
+list_from_one_config(ProxySnapper* snapper, ListMode list_mode, bool show_used_space)
 {
+    if (list_mode != LM_ALL && list_mode != LM_SINGLE)
+       show_used_space = false;
+
+    if (show_used_space)
+    {
+       try
+       {
+           snapper->calculateUsedSpace();
+       }
+       catch (const QuotaException& e)
+       {
+           SN_CAUGHT(e);
+
+           show_used_space = false;
+       }
+    }
+
+    auto add_date = [](TableRow& row, const ProxySnapshot& snapshot) {
+       row.add(snapshot.isCurrent() ? "" : datetime(snapshot.getDate(), utc, iso));
+    };
+
+    auto add_used_space = [show_used_space](TableRow& row, const ProxySnapshot& snapshot) {
+       if (show_used_space)
+           row.add(snapshot.isCurrent() ? "" : byte_to_humanstring(snapshot.getUsedSpace(), 2));
+    };
+
     Table table;
 
     switch (list_mode)
@@ -446,6 +481,8 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode)
            header.add(_("Pre #"));
            header.add(_("Date"));
            header.add(_("User"));
+           if (show_used_space)
+               header.add(_("Used Space"));
            header.add(_("Cleanup"));
            header.add(_("Description"));
            header.add(_("Userdata"));
@@ -458,8 +495,9 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode)
                row.add(toString(snapshot.getType()));
                row.add(decString(snapshot.getNum()));
                row.add(snapshot.getType() == POST ? decString(snapshot.getPreNum()) : "");
-               row.add(snapshot.isCurrent() ? "" : datetime(snapshot.getDate(), utc, iso));
+               add_date(row, snapshot);
                row.add(username(snapshot.getUid()));
+               add_used_space(row, snapshot);
                row.add(snapshot.getCleanup());
                row.add(snapshot.getDescription());
                row.add(show_userdata(snapshot.getUserdata()));
@@ -474,6 +512,8 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode)
            header.add(_("#"));
            header.add(_("Date"));
            header.add(_("User"));
+           if (show_used_space)
+               header.add(_("Used Space"));
            header.add(_("Description"));
            header.add(_("Userdata"));
            table.setHeader(header);
@@ -486,8 +526,9 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode)
 
                TableRow row;
                row.add(decString(snapshot.getNum()));
-               row.add(snapshot.isCurrent() ? "" : datetime(snapshot.getDate(), utc, iso));
+               add_date(row, snapshot);
                row.add(username(snapshot.getUid()));
+               add_used_space(row, snapshot);
                row.add(snapshot.getDescription());
                row.add(show_userdata(snapshot.getUserdata()));
                table.add(row);
diff --git a/client/utils/HumanString.cc b/client/utils/HumanString.cc
new file mode 100644 (file)
index 0000000..6abd3ce
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) [2004-2014] Novell, Inc.
+ * Copyright (c) [2016-2018] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <locale>
+#include <cmath>
+#include <sstream>
+#include <stdexcept>
+
+#include "HumanString.h"
+#include "text.h"
+
+
+namespace snapper
+{
+    using std::string;
+
+
+    /*
+     * These are simplified versions of the functions in libstorage-ng.
+     */
+
+
+    int
+    num_suffixes()
+    {
+       return 7;
+    }
+
+
+    string
+    get_suffix(int i)
+    {
+       switch (i)
+       {
+           case 0:
+               // TRANSLATORS: symbol for "bytes" (best keep untranslated)
+               return _("B");
+
+           case 1:
+               // TRANSLATORS: symbol for "kibi bytes" (best keep untranslated)
+               return _("KiB");
+
+           case 2:
+               // TRANSLATORS: symbol for "mebi bytes" (best keep untranslated)
+               return _("MiB");
+
+           case 3:
+               // TRANSLATORS: symbol for "gibi bytes" (best keep untranslated)
+               return _("GiB");
+
+           case 4:
+               // TRANSLATORS: symbol for "tebi bytes" (best keep untranslated)
+               return _("TiB");
+
+           case 5:
+               // TRANSLATORS: symbol for "pebi bytes" (best keep untranslated)
+               return _("PiB");
+
+           case 6:
+               // TRANSLATORS: symbol for "exbi bytes" (best keep untranslated)
+               return _("EiB");
+
+           default:
+               throw std::logic_error("invalid prefix");
+       }
+    }
+
+
+    bool
+    is_multiple_of(unsigned long long i, unsigned long long j)
+    {
+       return i % j == 0;
+    }
+
+
+    int
+    clz(unsigned long long i)
+    {
+       return __builtin_clzll(i);
+    }
+
+
+    // byte_to_humanstring uses integer arithmetic as long as possible
+    // to provide exact results for those cases.
+
+
+    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.
+
+       int i = size > 0 ? (64 - (clz(size) + 1)) / 10 : 0;
+
+       if (i == 0)
+       {
+           unsigned long long v = size >> 10 * i;
+
+           std::ostringstream s;
+           s.imbue(loc);
+           s << v << ' ' << get_suffix(i);
+           return s.str();
+       }
+       else
+       {
+           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);
+           return s.str();
+       }
+    }
+
+}
diff --git a/client/utils/HumanString.h b/client/utils/HumanString.h
new file mode 100644 (file)
index 0000000..1d3a75b
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) [2004-2014] Novell, Inc.
+ * Copyright (c) [2016,2018] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <string>
+
+
+namespace snapper
+{
+
+    const unsigned long long B = 1;
+    const unsigned long long KiB = 1024 * B;
+    const unsigned long long MiB = 1024 * KiB;
+    const unsigned long long GiB = 1024 * MiB;
+    const unsigned long long TiB = 1024 * GiB;
+    const unsigned long long PiB = 1024 * TiB;
+    const unsigned long long EiB = 1024 * PiB;
+
+
+    /**
+     * Return number of suffixes.
+     *
+     * @return number of suffixes
+     */
+    int num_suffixes();
+
+
+    /**
+     * 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).
+     *
+     * @param size size in bytes
+     * @param precision number of fraction digits in output
+     * @return formatted string
+     */
+    std::string byte_to_humanstring(unsigned long long size, int precision);
+
+}
index 910beda7c43382a1985dbc48f29fb8c89e761268..7ebc87b5768dc3a858140fca90676ff7e185147b 100644 (file)
@@ -12,7 +12,8 @@ libutils_la_SOURCES =                 \
        console.cc      console.h       \
        equal-date.cc   equal-date.h    \
        GetOpts.cc      GetOpts.h       \
-       Range.cc        Range.h
+       Range.cc        Range.h         \
+       HumanString.cc  HumanString.h
 
 libutils_la_LIBADD = ../../snapper/libsnapper.la
 
index cdbd7977bf5bb920b3d7f1992df98fe4b1c73e4a..2c0057773aa28751a1dd4377c1ee6014a030f957 100644 (file)
                all, single and pre-post.</para>
              </listitem>
            </varlistentry>
+           <varlistentry>
+             <term><option>--disable-used-space</option></term>
+             <listitem>
+               <para>Disable display of used space.</para>
+               <para>Calculating the used space needs some time. Thus
+               this option can speedup the listing.</para>
+               <para>Display of used space is automatically disable
+               is not available, e.g. quota not enabled on btrfs.</para>
+             </listitem>
+           </varlistentry>
            <varlistentry>
              <term><option>-a, --all-configs</option></term>
              <listitem>
index aaf41e4a96db372d688ae777313363f89754bb2e..bf50440fda78c502cd8b9da23ec9af48caf3e9ce 100644 (file)
@@ -1,3 +1,9 @@
+-------------------------------------------------------------------
+Tue Oct 02 09:56:59 CEST 2018 - aschnell@suse.com
+
+- show used space (exclusive space of btrfs qgroup) for each
+  snapshot (fate#323843)
+
 -------------------------------------------------------------------
 Fri Sep 14 12:13:05 CEST 2018 - aschnell@suse.com
 
index 8b3fbd6f4bb780f7e7ae8a5b91b229f9789c3141..3058449e1232a69c8be55a093aaff08355599aaa 100644 (file)
@@ -7,7 +7,7 @@ AM_CPPFLAGS = -I$(top_srcdir) $(DBUS_CFLAGS)
 LDADD = ../snapper/libsnapper.la ../dbus/libdbus.la -lboost_unit_test_framework
 
 check_PROGRAMS = sysconfig-get1.test dirname1.test basename1.test              \
-       equal-date.test dbus-escape.test cmp-lt.test
+       equal-date.test dbus-escape.test cmp-lt.test humanstring.test
 
 if ENABLE_BTRFS_QUOTA
 check_PROGRAMS +=  qgroup1.test
@@ -21,3 +21,5 @@ EXTRA_DIST = $(noinst_SCRIPTS) sysconfig-get1.txt
 
 equal_date_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
 
+humanstring_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
+
diff --git a/testsuite/humanstring.cc b/testsuite/humanstring.cc
new file mode 100644 (file)
index 0000000..239b77b
--- /dev/null
@@ -0,0 +1,67 @@
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE libstorage
+
+#include <boost/test/unit_test.hpp>
+
+#include <stdlib.h>
+#include <iostream>
+#include <locale>
+
+#include "../client/utils/HumanString.h"
+
+
+using namespace std;
+using namespace snapper;
+
+
+// Tests here must work when using double instead of long double in
+// HumanString.cc.
+
+
+string
+test(const char* loc, unsigned long long size, int precision)
+{
+    locale::global(locale(loc));
+
+    return byte_to_humanstring(size, precision);
+}
+
+
+
+BOOST_AUTO_TEST_CASE(test_byte_to_humanstring)
+{
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 0, 2), "0 B");
+
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1023, 2), "1,023 B");
+
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1024, 2), "1.00 KiB");
+
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1025, 2), "1.00 KiB");
+
+    BOOST_CHECK_EQUAL(test("de_DE.UTF-8", 123456789, 4), "117,7376 MiB");
+
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1000 * KiB, 2), "1,000.00 KiB");
+    BOOST_CHECK_EQUAL(test("de_DE.UTF-8", 1000 * KiB, 2), "1.000,00 KiB");
+    BOOST_CHECK_EQUAL(test("de_CH.UTF-8", 1000 * KiB, 2), "1'000.00 KiB");
+
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 50 * MiB, 2), "50.00 MiB");
+    BOOST_CHECK_EQUAL(test("de_DE.UTF-8", 50 * MiB, 2), "50,00 MiB");
+    BOOST_CHECK_EQUAL(test("de_CH.UTF-8", 50 * MiB, 2), "50.00 MiB");
+}
+
+
+BOOST_AUTO_TEST_CASE(test_big_numbers)
+{
+    // 1 EiB - 1 B
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB - 1 * B, 2), "1,024.00 PiB");
+
+    // 1 EiB
+    BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB, 2), "1.00 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");
+}