]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- compress file lists using gzip 708/head
authorArvin Schnell <aschnell@suse.de>
Thu, 21 Apr 2022 10:31:37 +0000 (12:31 +0200)
committerArvin Schnell <aschnell@suse.de>
Thu, 21 Apr 2022 13:28:22 +0000 (15:28 +0200)
41 files changed:
LIBVERSION
VERSION
client/MyFiles.cc
client/installation-helper.cc
client/proxy-lib.cc
dists/debian/changelog
doc/snapper-configs.xml.in
examples/c++-lib/ListAll.cc
package/snapper.changes
server/Client.cc
server/MetaSnapper.cc
server/MetaSnapper.h
server/Types.cc
snapper.spec.in
snapper/AppUtil.h
snapper/AsciiFile.cc
snapper/AsciiFile.h
snapper/Btrfs.cc
snapper/Comparison.cc
snapper/Comparison.h
snapper/ComparisonImpl.cc [new file with mode: 0644]
snapper/ComparisonImpl.h [new file with mode: 0644]
snapper/Exception.h
snapper/File.cc
snapper/File.h
snapper/Filesystem.cc
snapper/Makefile.am
snapper/Regex.cc [deleted file]
snapper/Regex.h [deleted file]
snapper/Selinux.cc
snapper/Selinux.h
snapper/Snapper.cc
snapper/Snapper.h
snapper/SnapperDefines.h
snapper/Snapshot.cc
snapper/Snapshot.h
testsuite-real/.gitignore
testsuite-real/CAUTION
testsuite-real/Makefile.am
testsuite-real/ascii-file.cc [new file with mode: 0644]
testsuite/sysconfig-get1.cc

index 03f488b076ae34efcfad44444186507b6415e8cc..09b254e90c61ed28bb68a54752cf04f6a736a7d3 100644 (file)
@@ -1 +1 @@
-5.3.0
+6.0.0
diff --git a/VERSION b/VERSION
index 78bc1abd14f2c1f6330989d876c4ee7d5daf7ff6..571215736a666e8d79d7a7958b5ffc400514fb53 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.10.0
+0.10.1
index 2a19a8aeafd0da3656437610827f93ed9a78581c..9fc8d3752194f843dce5de5b4384c58f89829ae4 100644 (file)
@@ -41,10 +41,10 @@ namespace snapper
     {
        if (file)
        {
-           AsciiFileReader asciifile(file);
+           AsciiFileReader asciifile(file, Compression::NONE);
 
            string line;
-           while (asciifile.getline(line))
+           while (asciifile.read_line(line))
            {
                if (line.empty())
                    continue;
index db0a2a249d4bb1da0d4eaff587b010d43fddad43..e3eed4fe25692bb4af2e0641047f6fcb22baa294 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2015 Novell, Inc.
- * Copyright (c) [2018-2020] SUSE LLC
+ * Copyright (c) [2018-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -75,14 +75,17 @@ step1(const string& device, const string& description, const string& cleanup,
     {
        SysconfigFile config(locate_file("default", ETC_CONFIG_TEMPLATE_DIR, USR_CONFIG_TEMPLATE_DIR));
 
-       config.setName(tmp_mount.getFullname() + CONFIGS_DIR "/" "root");
+       config.set_name(tmp_mount.getFullname() + CONFIGS_DIR "/" "root");
 
-       config.setValue(KEY_SUBVOLUME, "/");
-       config.setValue(KEY_FSTYPE, "btrfs");
+       config.set_value(KEY_SUBVOLUME, "/");
+       config.set_value(KEY_FSTYPE, "btrfs");
+
+       config.save();
     }
-    catch (const FileNotFoundException& e)
+    catch (const Exception& e)
     {
-       cerr << "copying/modifying config-file failed" << endl;
+       cerr << "copying/modifying config-file failed"
+            << e.what() << endl;
     }
 
     cout << "creating filesystem config" << endl;
@@ -196,11 +199,15 @@ step4()
     try
     {
        SysconfigFile sysconfig(SYSCONFIG_FILE);
-       sysconfig.setValue("SNAPPER_CONFIGS", { "root" });
+
+       sysconfig.set_value("SNAPPER_CONFIGS", { "root" });
+
+       sysconfig.save();
     }
-    catch (const FileNotFoundException& e)
+    catch (const Exception& e)
     {
-       cerr << "sysconfig-file not found" << endl;
+       cerr << "modifying sysconfig-file failed"
+            << e.what() << endl;
     }
 
     Btrfs btrfs("/", "");
index 11f9cee0ae3562ede2c1dc40966a6fb44ed2b83d..b7981e13ceef58f7581cb6073c61849315c47c7c 100644 (file)
@@ -73,7 +73,7 @@ ProxySnapshotsLib::getActive() const
 ProxyConfig
 ProxySnapperLib::getConfig() const
 {
-    return ProxyConfig(snapper->getConfigInfo().getAllValues());
+    return ProxyConfig(snapper->getConfigInfo().get_all_values());
 }
 
 
@@ -201,7 +201,7 @@ ProxySnappersLib::getConfigs() const
 
     list<ConfigInfo> config_infos = Snapper::getConfigs(target_root);
     for (const ConfigInfo& config_info : config_infos)
-       ret.emplace(make_pair(config_info.getConfigName(), config_info.getAllValues()));
+       ret.emplace(make_pair(config_info.get_config_name(), config_info.get_all_values()));
 
     return ret;
 }
index 1e5b42af9a70933f3d1d04e63b7ea707ec57c0cf..84ad483cfaaa3d09c4f55c79c2a6dcd115751efb 100644 (file)
@@ -1,3 +1,15 @@
+snapper (0.10.1) stable; urgency=low
+
+  * Updated to version 0.10.1
+
+ -- Arvin Schnell <aschnell@suse.com>  Thu, 21 Apr 2022 09:35:06 +0000
+
+snapper (0.10.0) stable; urgency=low
+
+  * Updated to version 0.10.0
+
+ -- Arvin Schnell <aschnell@suse.com>  Mon, 21 Mar 2022 17:29:07 +0000
+
 snapper (0.9.1) stable; urgency=low
 
   * Updated to version 0.9.1
index af5d9aefb07638362314ab40160b5c735f709a35..97373957ba83640a1d877b1855985d17f7203fb6 100644 (file)
@@ -2,13 +2,13 @@
 <refentry id='snapper-configs5'>
 
   <refentryinfo>
-    <date>2021-04-07</date>
+    <date>2022-04-21</date>
   </refentryinfo>
 
   <refmeta>
     <refentrytitle>snapper-configs</refentrytitle>
     <manvolnum>5</manvolnum>
-    <refmiscinfo class='date'>2021-04-07</refmiscinfo>
+    <refmiscinfo class='date'>2022-04-21</refmiscinfo>
     <refmiscinfo class='version'>@VERSION@</refmiscinfo>
     <refmiscinfo class='manual'>Filesystem Snapshot Management</refmiscinfo>
   </refmeta>
        </listitem>
       </varlistentry>
 
+      <varlistentry>
+       <term><option>COMPRESSION=<replaceable>algorithm</replaceable></option></term>
+       <listitem>
+         <para>Defines the compression algorithm used for saving file
+         list. Allowed values are &quot;none&quot; and
+         &quot;gzip&quot;. Depending on the installed libraries, some
+         compression algorithms might not be available.</para>
+         <para>Default value is &quot;gzip&quot;.</para>
+         <para>New in version 0.10.1.</para>
+       </listitem>
+      </varlistentry>
+
       <varlistentry>
        <term><option>NUMBER_CLEANUP=<replaceable>boolean</replaceable></option></term>
        <listitem>
index 4d690de95af2c7cd9c5b321885f96e94df9d89b5..c320c26ea46113f15b26c50342e14e07811c7404 100644 (file)
@@ -15,7 +15,7 @@ main(int argc, char** argv)
     list<Snapper*> sh;
 
     for (list<ConfigInfo>::const_iterator it = c.begin(); it != c.end(); ++it)
-       sh.push_back(new Snapper(it->getConfigName(), "/"));
+       sh.push_back(new Snapper(it->get_config_name(), "/"));
 
     for (list<Snapper*>::const_iterator it = sh.begin(); it != sh.end(); ++it)
        cout << (*it)->configName() << " " << (*it)->subvolumeDir() << " "
index 67a0e7cf17cbc833c6d8e42a6e1d5136df2a1812..8cf8ec066d9d3b0e1573eeb5fae53da6c9f7d3c8 100644 (file)
@@ -1,3 +1,9 @@
+-------------------------------------------------------------------
+Thu Apr 21 09:35:06 CEST 2022 - aschnell@suse.com
+
+- compress file lists using gzip
+- version 0.10.1
+
 -------------------------------------------------------------------
 Mon Mar 21 17:29:07 CET 2022 - aschnell@suse.com
 
index 79e0cc5e7b0c915f57c6589d4e042fe8043bb29b..e8a87858d9b39cc503ab62a06b72b8435f7f714a 100644 (file)
@@ -1054,7 +1054,7 @@ Client::create_post_snapshot(DBus::Connection& conn, DBus::Message& msg)
     Snapshots::iterator snap2 = snapper->createPostSnapshot(snap1, scd);
 
     bool background_comparison = true;
-    it->getConfigInfo().getValue("BACKGROUND_COMPARISON", background_comparison);
+    it->getConfigInfo().get_value("BACKGROUND_COMPARISON", background_comparison);
     if (background_comparison)
        clients.backgrounds().add_task(it, snap1, snap2);
 
index fcf0027e84b4e78bc2bcbfe24474f8cb135f8c5c..bd3e76113dbcf99adef4dc6d2080c2e59edb1b7f 100644 (file)
@@ -53,7 +53,7 @@ void
 MetaSnapper::setConfigInfo(const map<string, string>& raw)
 {
     for (map<string, string>::const_iterator it = raw.begin(); it != raw.end(); ++it)
-       config_info.setValue(it->first, it->second);
+       config_info.set_value(it->first, it->second);
 
     getSnapper()->setConfigInfo(raw);
 
@@ -68,7 +68,7 @@ MetaSnapper::set_permissions()
     allowed_uids.clear();
 
     vector<string> users;
-    if (config_info.getValue(KEY_ALLOW_USERS, users))
+    if (config_info.get_value(KEY_ALLOW_USERS, users))
     {
        for (const string& user : users)
        {
@@ -84,7 +84,7 @@ MetaSnapper::set_permissions()
     allowed_gids.clear();
 
     vector<string> groups;
-    if (config_info.getValue(KEY_ALLOW_GROUPS, groups))
+    if (config_info.get_value(KEY_ALLOW_GROUPS, groups))
     {
        for (const string& group : groups)
        {
@@ -103,7 +103,7 @@ Snapper*
 MetaSnapper::getSnapper()
 {
     if (!snapper)
-       snapper = new Snapper(config_info.getConfigName(), "/");
+       snapper = new Snapper(config_info.get_config_name(), "/");
 
     update_use_time();
 
index 4e47fd7f8c6dc21d5a9f8d5a9bcb7600d29e2866..36053b4035bb5bd3232ff5b74b020fe6a93b53ce 100644 (file)
@@ -49,7 +49,7 @@ public:
     MetaSnapper(ConfigInfo& config_info);
     ~MetaSnapper();
 
-    const string& configName() const { return config_info.getConfigName(); }
+    const string& configName() const { return config_info.get_config_name(); }
 
     const ConfigInfo& getConfigInfo() const { return config_info; }
     void setConfigInfo(const map<string, string>& raw);
index 47f7f05d0a0588eafd2443fb0c5ad02b7b5a80a0..95db31dfa2f5358c5f89ca6fb8d847bd6430b804 100644 (file)
@@ -37,7 +37,7 @@ namespace DBus
     operator<<(Hoho& hoho, const ConfigInfo& data)
     {
        hoho.open_struct();
-       hoho << data.getConfigName() << data.getSubvolume() << data.getAllValues();
+       hoho << data.get_config_name() << data.get_subvolume() << data.get_all_values();
        hoho.close_struct();
        return hoho;
     }
index 8fc3d6e5c4c845c971203ff7e554e0309b716a61..e8385b446c3dae6c4ab28328419b0dcca52c44fa 100644 (file)
@@ -95,6 +95,7 @@ BuildRequires:  json-c-devel
 %else
 BuildRequires:  libjson-c-devel
 %endif
+BuildRequires:  zlib-devel
 %if %{with coverage}
 BuildRequires:  lcov
 %endif
index ccde04ddaeabda5b894a53f38c882b8bbf68763b..c136c1f01a88b062fd21dc75cce49a7eb22f6a4c 100644 (file)
@@ -136,8 +136,10 @@ namespace snapper
     };
 
 
-    struct FdCloser
+    class FdCloser
     {
+    public:
+
        FdCloser(int fd)
            : fd(fd)
        {
@@ -163,7 +165,7 @@ namespace snapper
 
     private:
 
-       int fd;
+       int fd = -1;
 
     };
 
index bc141d4d19dc065c3b509b85a1553aab541277b0..9bcde827799ff13237c431381d77036ed0c8feda 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2004-2015] Novell, Inc.
- * Copyright (c) 2020 SUSE LLC
+ * Copyright (c) [2020-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
 
 
 #include <unistd.h>
-#include <fstream>
+#include <fcntl.h>
+#include <zlib.h>
 #include <regex>
+#include <boost/algorithm/string.hpp>
 
 #include "snapper/Log.h"
 #include "snapper/AppUtil.h"
 #include "snapper/AsciiFile.h"
-#include "snapper/SnapperTypes.h"
 #include "snapper/Exception.h"
 
 
@@ -37,140 +38,789 @@ namespace snapper
     using namespace std;
 
 
-    AsciiFileReader::AsciiFileReader(int fd)
-       : file(fdopen(fd, "r"))
+    bool
+    is_available(Compression compression)
     {
-       if (!file)
+       switch (compression)
        {
-           y2war("file is NULL");
-           SN_THROW(FileNotFoundException());
+           case Compression::NONE:
+               return true;
+
+           case Compression::GZIP:
+               return true;
        }
+
+       return false;
     }
 
 
-    AsciiFileReader::AsciiFileReader(FILE* file)
-       : file(file)
+    string
+    add_extension(Compression compression, const string& name)
     {
-       if (!file)
+       switch (compression)
        {
-           y2war("file is NULL");
-           SN_THROW(FileNotFoundException());
+           case Compression::NONE:
+               return name;
+
+           case Compression::GZIP:
+               return name + ".gz";
        }
+
+       SN_THROW(LogicErrorException("unknown or unsupported compression"));
+       __builtin_unreachable();
     }
 
 
-    AsciiFileReader::AsciiFileReader(const string& filename)
-       : file(fopen(filename.c_str(), "re"))
+    class AsciiFileReader::Impl
     {
-       if (!file)
-       {
-           y2war("open for '" << filename << "' failed");
-           SN_THROW(FileNotFoundException());
-       }
+    public:
+
+       class None;
+       class Gzip;
+
+       template <typename T>
+       static std::unique_ptr<AsciiFileReader::Impl> factory(T t, Compression compression);
+
+       virtual ~Impl() = default;
+
+       virtual bool read_line(string& line) = 0;
+
+       virtual void close() = 0;
+
+    };
+
+
+    class AsciiFileReader::Impl::None : public AsciiFileReader::Impl
+    {
+    public:
+
+       None(int fd);
+       None(FILE* fin);
+       None(const string& name);
+
+       virtual ~None();
+
+       virtual bool read_line(string& line) override;
+
+       virtual void close() override;
+
+    private:
+
+       FILE* fin = nullptr;
+
+       char* buffer = nullptr;
+       size_t len = 0;
+
+    };
+
+
+    AsciiFileReader::Impl::None::None(int fd)
+    {
+       fin = fdopen(fd, "r");
+       if (!fin)
+           SN_THROW(IOErrorException(sformat("fdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
     }
 
 
-    AsciiFileReader::~AsciiFileReader()
+    AsciiFileReader::Impl::None::None(FILE* fin)
+       : fin(fin)
+    {
+    }
+
+
+    AsciiFileReader::Impl::None::None(const string& name)
+    {
+       fin = fopen(name.c_str(), "re");
+       if (!fin)
+           SN_THROW(IOErrorException(sformat("fopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileReader::Impl::None::~None()
     {
        free(buffer);
-       fclose(file);
+
+       try
+       {
+           close();
+       }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
+
+           y2err("exception ignored");
+       }
     }
 
 
     bool
-    AsciiFileReader::getline(string& line)
+    AsciiFileReader::Impl::None::read_line(string& line)
     {
-       ssize_t n = ::getline(&buffer, &len, file);
+       ssize_t n = getline(&buffer, &len, fin);
        if (n == -1)
            return false;
 
-       if (buffer[n - 1] != '\n')
-           line = string(buffer, 0, n);
-       else
-           line = string(buffer, 0, n - 1);
+       if (n > 0 && buffer[n - 1] == '\n')
+           n--;
+
+       line = string(buffer, 0, n);
 
        return true;
     }
 
 
-AsciiFile::AsciiFile(const char* Name_Cv, bool remove_empty)
-    : Name_C(Name_Cv),
-      remove_empty(remove_empty)
-{
-    reload();
-}
+    void
+    AsciiFileReader::Impl::None::close()
+    {
+       if (!fin)
+           return;
 
+       FILE* tmp = fin;
+       fin = nullptr;
 
-AsciiFile::AsciiFile(const string& Name_Cv, bool remove_empty)
-    : Name_C(Name_Cv),
-      remove_empty(remove_empty)
-{
-    reload();
-}
+       if (fclose(tmp) != 0)
+           SN_THROW(IOErrorException(sformat("fclose failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    class AsciiFileReader::Impl::Gzip : public AsciiFileReader::Impl
+    {
+    public:
+
+       Gzip(int fd);
+       Gzip(FILE* fin);
+       Gzip(const string& name);
+
+       virtual ~Gzip();
+
+       virtual bool read_line(string& line) override;
+
+       virtual void close() override;
+
+    private:
+
+       Gzip();
+
+       gzFile gz_file = nullptr;
+
+       vector<char> buffer;
+       size_t buffer_read = 0;         // position to which the buffer has been read
+       size_t buffer_fill = 0;         // position to which the buffer is filled
+
+       bool read_buffer();
+
+    };
+
+
+    AsciiFileReader::Impl::Gzip::Gzip()
+    {
+       buffer.resize(16 * 1024);
+    }
+
+
+    AsciiFileReader::Impl::Gzip::Gzip(int fd)
+       : Gzip()
+    {
+       gz_file = gzdopen(fd, "r");
+       if (!gz_file)
+           SN_THROW(IOErrorException(sformat("gzdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileReader::Impl::Gzip::Gzip(FILE* fin)
+       : Gzip()
+    {
+       int fd = fileno(fin);
+       if (fd < 0)
+           SN_THROW(IOErrorException(sformat("fileno failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       fd = dup(fd);
+       if (fd < 0)
+           SN_THROW(IOErrorException(sformat("dup failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       gz_file = gzdopen(fd, "r");
+       if (!gz_file)
+           SN_THROW(IOErrorException(sformat("gzdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       fclose(fin);
+    }
+
+
+    AsciiFileReader::Impl::Gzip::Gzip(const string& name)
+       : Gzip()
+    {
+       int fd = open(name.c_str(), O_RDONLY | O_CLOEXEC | O_LARGEFILE);
+       if (fd < 0)
+           SN_THROW(IOErrorException(sformat("open failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       gz_file = gzdopen(fd, "r");
+       if (!gz_file)
+           SN_THROW(IOErrorException(sformat("gzdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileReader::Impl::Gzip::~Gzip()
+    {
+       try
+       {
+           close();
+       }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
+
+           y2err("exception ignored");
+       }
+    }
+
+
+    void
+    AsciiFileReader::Impl::Gzip::close()
+    {
+       if (!gz_file)
+           return;
+
+       gzFile tmp = gz_file;
+       gz_file = nullptr;
+
+       int r = gzclose(tmp);
+       if (r != Z_OK)
+           SN_THROW(IOErrorException(sformat("gzclose failed errnum:%d", r)));
+    }
+
+
+    bool
+    AsciiFileReader::Impl::Gzip::read_buffer()
+    {
+       int r = gzread(gz_file, buffer.data(), buffer.size());
+       if (r <= 0)
+       {
+           if (gzeof(gz_file))
+               return false;
+
+           int errnum = 0;
+           const char* msg = gzerror(gz_file, &errnum);
+           SN_THROW(IOErrorException(sformat("gzread failed errno:%d (%s)", errnum, msg)));
+       }
+
+       buffer_read = 0;
+       buffer_fill = r;
+
+       return true;
+    }
+
+
+    bool
+    AsciiFileReader::Impl::Gzip::read_line(string& line)
+    {
+       line.clear();
+
+       while (true)
+       {
+           // check if all of the output buffer has been used
+           if (buffer_read == buffer_fill)
+           {
+               if (!read_buffer())
+                   return !line.empty();
+           }
+
+           const char* p1 = buffer.data() + buffer_read;
+           size_t remaining = buffer_fill - buffer_read;
+
+           const char* p2 = (const char*) memchr(p1, '\n', remaining);
+
+           if (p2)
+           {
+               line += string(p1, p2 - p1);
+               buffer_read = p2 - buffer.data() + 1;
+               return true;
+           }
+
+           line += string(p1, remaining);
+           buffer_read += remaining;
+       }
+    }
+
+
+    template <typename T>
+    std::unique_ptr<AsciiFileReader::Impl>
+    AsciiFileReader::Impl::factory(T t, Compression compression)
+    {
+       switch (compression)
+       {
+           case Compression::NONE:
+               return unique_ptr<Impl::None>(new Impl::None(t));
+
+           case Compression::GZIP:
+               return unique_ptr<Impl::Gzip>(new Impl::Gzip(t));
+       }
+
+       SN_THROW(LogicErrorException("unknown or unsupported compression"));
+       __builtin_unreachable();
+    }
+
+
+    AsciiFileReader::AsciiFileReader(int fd, Compression compression)
+       : impl(AsciiFileReader::Impl::factory(fd, compression))
+    {
+    }
+
+
+    AsciiFileReader::AsciiFileReader(FILE* fin, Compression compression)
+       : impl(AsciiFileReader::Impl::factory(fin, compression))
+    {
+    }
+
+
+    AsciiFileReader::AsciiFileReader(const string& name, Compression compression)
+       : impl(AsciiFileReader::Impl::factory(name, compression))
+    {
+    }
+
+
+    AsciiFileReader::~AsciiFileReader()
+    {
+    }
+
+
+    bool
+    AsciiFileReader::read_line(string& line)
+    {
+       return impl->read_line(line);
+    }
+
+
+    void
+    AsciiFileReader::close()
+    {
+       impl->close();
+    }
+
+
+    class AsciiFileWriter::Impl
+    {
+    public:
+
+       class None;
+       class Gzip;
+
+       template <typename T>
+       static std::unique_ptr<AsciiFileWriter::Impl> factory(T t, Compression compression);
+
+       virtual ~Impl() = default;
+
+       virtual void write_line(const string& line) = 0;
+
+       virtual void close() = 0;
+
+    };
+
+
+    class AsciiFileWriter::Impl::None : public AsciiFileWriter::Impl
+    {
+    public:
+
+       None(int fd);
+       None(FILE* fout);
+       None(const string& name);
+
+       virtual ~None();
+
+       virtual void write_line(const string& line) override;
+
+       virtual void close() override;
+
+    private:
+
+       FILE* fout = nullptr;
+
+    };
+
+
+    AsciiFileWriter::Impl::None::None(int fd)
+    {
+       fout = fdopen(fd, "w");
+       if (!fout)
+           SN_THROW(IOErrorException(sformat("fdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileWriter::Impl::None::None(FILE* fout)
+       : fout(fout)
+    {
+    }
+
+
+    AsciiFileWriter::Impl::None::None(const string& name)
+    {
+       fout = fopen(name.c_str(), "we");
+       if (!fout)
+           SN_THROW(IOErrorException(sformat("fopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileWriter::Impl::None::~None()
+    {
+       try
+       {
+           close();
+       }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
+
+           y2err("exception ignored");
+       }
+    }
+
+
+    void
+    AsciiFileWriter::Impl::None::write_line(const string& line)
+    {
+       if (fprintf(fout, "%s\n", line.c_str()) != (int)(line.size() + 1))
+           SN_THROW(IOErrorException(sformat("fprintf failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    void
+    AsciiFileWriter::Impl::None::close()
+    {
+       if (!fout)
+           return;
+
+       FILE* tmp = fout;
+       fout = nullptr;
+
+       if (fclose(tmp) != 0)
+           SN_THROW(IOErrorException(sformat("fclose failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    class AsciiFileWriter::Impl::Gzip : public AsciiFileWriter::Impl
+    {
+    public:
+
+       Gzip(int fd);
+       Gzip(FILE* fout);
+       Gzip(const string& name);
+
+       virtual ~Gzip();
+
+       virtual void write_line(const string& line) override;
+
+       virtual void close() override;
+
+    private:
+
+       Gzip();
+
+       gzFile gz_file = nullptr;
+
+       vector<char> buffer;
+       size_t buffer_fill = 0;         // position to which the buffer is full
+
+       void write_buffer();
+
+    };
+
+
+    AsciiFileWriter::Impl::Gzip::Gzip()
+    {
+       buffer.resize(16 * 1024);
+    }
+
+
+    AsciiFileWriter::Impl::Gzip::Gzip(int fd)
+       : Gzip()
+    {
+       gz_file = gzdopen(fd, "w");
+       if (!gz_file)
+           SN_THROW(IOErrorException(sformat("gzdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileWriter::Impl::Gzip::Gzip(FILE* fout)
+       : Gzip()
+    {
+       int fd = fileno(fout);
+       if (fd < 0)
+           SN_THROW(IOErrorException(sformat("fileno failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       fd = dup(fd);
+       if (fd < 0)
+           SN_THROW(IOErrorException(sformat("dup failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       gz_file = gzdopen(fd, "w");
+       if (!gz_file)
+           SN_THROW(IOErrorException(sformat("gzdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       fclose(fout);
+    }
+
+
+    AsciiFileWriter::Impl::Gzip::Gzip(const string& name)
+       : Gzip()
+    {
+       int fd = open(name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_LARGEFILE, 0666);
+       if (fd < 0)
+           SN_THROW(IOErrorException(sformat("open failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+
+       gz_file = gzdopen(fd, "w");
+       if (!gz_file)
+           SN_THROW(IOErrorException(sformat("gzdopen failed errno:%d (%s)", errno,
+                                             stringerror(errno).c_str())));
+    }
+
+
+    AsciiFileWriter::Impl::Gzip::~Gzip()
+    {
+       try
+       {
+           close();
+       }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
+
+           y2err("exception ignored");
+       }
+    }
+
+
+    void
+    AsciiFileWriter::Impl::Gzip::close()
+    {
+       if (!gz_file)
+           return;
+
+       write_buffer();
+
+       gzFile tmp = gz_file;
+       gz_file = nullptr;
+
+       int r = gzclose(tmp);
+       if (r != Z_OK)
+           SN_THROW(IOErrorException(sformat("gzclose failed errnum:%d", r)));
+    }
+
+
+    void
+    AsciiFileWriter::Impl::Gzip::write_buffer()
+    {
+       int r = gzwrite(gz_file, buffer.data(), buffer_fill);
+       if (r <= 0)
+       {
+           int errnum = 0;
+           const char* msg = gzerror(gz_file, &errnum);
+           SN_THROW(IOErrorException(sformat("gzwrite failed errno:%d (%s)", errnum, msg)));
+       }
+
+       buffer_fill = 0;
+    }
+
+
+    void
+    AsciiFileWriter::Impl::Gzip::write_line(const string& line)
+    {
+       string tmp = line + "\n";
+
+       while (!tmp.empty())
+       {
+           // still available in input buffer
+           size_t in_avail = buffer.size() - buffer_fill;
+
+           // how much to copy into input buffer
+           size_t to_copy = min(in_avail, tmp.size());
+
+           // copy into input buffer and erase in tmp
+           memcpy(buffer.data() + buffer_fill, tmp.data(), to_copy);
+           buffer_fill += to_copy;
+           tmp.erase(0, to_copy);
+
+           // if input buffer is full, compress it and write to disk
+           if (buffer_fill == buffer.size())
+               write_buffer();
+       }
+    }
+
+
+    template <typename T>
+    std::unique_ptr<AsciiFileWriter::Impl>
+    AsciiFileWriter::Impl::factory(T t, Compression compression)
+    {
+       switch (compression)
+       {
+           case Compression::NONE:
+               return unique_ptr<Impl::None>(new Impl::None(t));
+
+           case Compression::GZIP:
+               return unique_ptr<Impl::Gzip>(new Impl::Gzip(t));
+       }
+
+       SN_THROW(LogicErrorException("unknown or unsupported compression"));
+       __builtin_unreachable();
+    }
+
+
+    AsciiFileWriter::AsciiFileWriter(int fd, Compression compression)
+       : impl(AsciiFileWriter::Impl::factory(fd, compression))
+    {
+    }
+
+
+    AsciiFileWriter::AsciiFileWriter(FILE* fout, Compression compression)
+       : impl(AsciiFileWriter::Impl::factory(fout, compression))
+    {
+    }
+
+
+    AsciiFileWriter::AsciiFileWriter(const string& name, Compression compression)
+       : impl(AsciiFileWriter::Impl::factory(name, compression))
+    {
+    }
+
+
+    AsciiFileWriter::~AsciiFileWriter()
+    {
+    }
+
+
+    void
+    AsciiFileWriter::write_line(const string& line)
+    {
+       impl->write_line(line);
+    }
+
+
+    void
+    AsciiFileWriter::close()
+    {
+       impl->close();
+    }
+
+
+    AsciiFile::AsciiFile(const string& name, bool remove_empty)
+       : name(name), remove_empty(remove_empty)
+    {
+       reload();
+    }
+
+
+    AsciiFile::~AsciiFile()
+    {
+    }
 
 
     void
     AsciiFile::reload()
     {
-       y2mil("loading file " << Name_C);
+       y2mil("loading file " << name);
+
        clear();
 
-       AsciiFileReader file(Name_C);
+       AsciiFileReader ascii_file_reader(name, Compression::NONE);
 
        string line;
-       while (file.getline(line))
-           Lines_C.push_back(line);
+       while (ascii_file_reader.read_line(line))
+           lines.push_back(line);
+
+       ascii_file_reader.close();
     }
 
 
-bool
-AsciiFile::save()
-{
-    if (remove_empty && Lines_C.empty())
+    void
+    AsciiFile::save()
     {
-       y2mil("deleting file " << Name_C);
+       if (remove_empty && empty())
+       {
+           y2mil("removing file " << name);
 
-       if (access(Name_C.c_str(), F_OK) != 0)
-           return true;
+           if (access(name.c_str(), F_OK) != 0)
+               return;
 
-       return unlink(Name_C.c_str()) == 0;
+           if (unlink(name.c_str()) != 0)
+               SN_THROW(IOErrorException(sformat("unlink failed errno:%d (%s)", errno,
+                                                 stringerror(errno).c_str())));
+       }
+       else
+       {
+           y2mil("saving file " << name);
+
+           AsciiFileWriter ascii_file_writer(name, Compression::NONE);
+
+           for (const string& line : lines)
+               ascii_file_writer.write_line(line);
+
+           ascii_file_writer.close();
+       }
     }
-    else
-    {
-       y2mil("saving file " << Name_C);
 
-       ofstream file( Name_C.c_str() );
-       classic(file);
 
-       for (vector<string>::const_iterator it = Lines_C.begin(); it != Lines_C.end(); ++it)
-           file << *it << std::endl;
+    void
+    AsciiFile::log_content() const
+    {
+       y2mil("content of " << (name.empty() ? "<nameless>" : name));
+
+       for (const string& line : lines)
+           y2mil(line);
+    }
 
-       file.close();
 
-       return file.good();
+    SysconfigFile::SysconfigFile(const string& name)
+       : AsciiFile(name)
+    {
     }
-}
 
 
-    void
-    AsciiFile::logContent() const
+    SysconfigFile::~SysconfigFile()
     {
-       y2mil("content of " << (Name_C.empty() ? "<nameless>" : Name_C));
-       for (vector<string>::const_iterator it = Lines_C.begin(); it != Lines_C.end(); ++it)
-           y2mil(*it);
+       if (!modified)
+           return;
+
+       try
+       {
+           save();
+       }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
+
+           y2err("exception ignored");
+       }
     }
 
 
     void
     SysconfigFile::save()
     {
-       if (modified && AsciiFile::save())
-           modified = false;
+       if (!modified)
+           return;
+
+       AsciiFile::save();
+       modified = false;
     }
 
 
     void
-    SysconfigFile::checkKey(const string& key) const
+    SysconfigFile::check_key(const string& key) const
     {
        static const regex rx("([0-9A-Z_]+)", regex::extended);
 
@@ -180,17 +830,17 @@ AsciiFile::save()
 
 
     void
-    SysconfigFile::setValue(const string& key, bool value)
+    SysconfigFile::set_value(const string& key, bool value)
     {
-       setValue(key, value ? "yes" : "no");
+       set_value(key, value ? "yes" : "no");
     }
 
 
     bool
-    SysconfigFile::getValue(const string& key, bool& value) const
+    SysconfigFile::get_value(const string& key, bool& value) const
     {
        string tmp;
-       if (!getValue(key, tmp))
+       if (!get_value(key, tmp))
            return false;
 
        value = tmp == "yes";
@@ -199,27 +849,26 @@ AsciiFile::save()
 
 
     void
-    SysconfigFile::setValue(const string& key, const char* value)
+    SysconfigFile::set_value(const string& key, const char* value)
     {
-       setValue(key, string(value));
+       set_value(key, string(value));
     }
 
 
     void
-    SysconfigFile::setValue(const string& key, const string& value)
+    SysconfigFile::set_value(const string& key, const string& value)
     {
-       checkKey(key);
+       check_key(key);
 
        modified = true;
 
-       for (vector<string>::iterator it = Lines_C.begin(); it != Lines_C.end(); ++it)
+       for (vector<string>::iterator it = lines.begin(); it != lines.end(); ++it)
        {
            ParsedLine parsed_line;
 
            if (parse_line(*it, parsed_line) && parsed_line.key == key)
            {
-               string line = key + "=\"" + value + "\"" + parsed_line.comment;
-               *it = line;
+               *it = key + "=\"" + value + "\"" + parsed_line.comment;
                return;
            }
        }
@@ -230,9 +879,9 @@ AsciiFile::save()
 
 
     bool
-    SysconfigFile::getValue(const string& key, string& value) const
+    SysconfigFile::get_value(const string& key, string& value) const
     {
-       for (const string& line : Lines_C)
+       for (const string& line : lines)
        {
            ParsedLine parsed_line;
 
@@ -249,7 +898,7 @@ AsciiFile::save()
 
 
     void
-    SysconfigFile::setValue(const string& key, const vector<string>& values)
+    SysconfigFile::set_value(const string& key, const vector<string>& values)
     {
        string tmp;
        for (vector<string>::const_iterator it = values.begin(); it != values.end(); ++it)
@@ -258,15 +907,15 @@ AsciiFile::save()
                tmp.append(" ");
            tmp.append(boost::replace_all_copy(*it, " ", "\\ "));
        }
-       setValue(key, tmp);
+       set_value(key, tmp);
     }
 
 
     bool
-    SysconfigFile::getValue(const string& key, vector<string>& values) const
+    SysconfigFile::get_value(const string& key, vector<string>& values) const
     {
        string tmp;
-       if (!getValue(key, tmp))
+       if (!get_value(key, tmp))
            return false;
 
        values.clear();
@@ -302,11 +951,11 @@ AsciiFile::save()
 
 
     map<string, string>
-    SysconfigFile::getAllValues() const
+    SysconfigFile::get_all_values() const
     {
        map<string, string> ret;
 
-       for (const string& line : Lines_C)
+       for (const string& line : lines)
        {
            ParsedLine parsed_line;
 
index 218f92e9833c68f786ac233449fb0ecc544d991e..cf1ecc96acabb4c36fc1181f08399a29f12652da 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2004-2015] Novell, Inc.
- * Copyright (c) 2020 SUSE LLC
+ * Copyright (c) [2020-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -28,6 +28,7 @@
 #include <string>
 #include <vector>
 #include <map>
+#include <memory>
 
 #include "snapper/Exception.h"
 
@@ -39,22 +40,65 @@ namespace snapper
     using std::map;
 
 
+    enum class Compression { NONE, GZIP };
+
+
+    bool
+    is_available(Compression compression);
+
+
+    string
+    add_extension(Compression compression, const string& name);
+
+
     class AsciiFileReader
     {
     public:
 
-       AsciiFileReader(int fd);
-       AsciiFileReader(FILE* file);
-       AsciiFileReader(const string& filename);
+       AsciiFileReader(int fd, Compression compression);
+       AsciiFileReader(FILE* fin, Compression compression);
+       AsciiFileReader(const string& name, Compression compression);
+
+       /**
+        * Exceptions from close are ignored. Use close() explicitely.
+        */
        ~AsciiFileReader();
 
-       bool getline(string& line);
+       bool read_line(string& line);
+
+       void close();
 
     private:
 
-       FILE* file = nullptr;
-       char* buffer = nullptr;
-       size_t len = 0;
+       class Impl;
+
+       std::unique_ptr<Impl> impl;
+
+    };
+
+
+    class AsciiFileWriter
+    {
+    public:
+
+       AsciiFileWriter(int fd, Compression compression);
+       AsciiFileWriter(FILE* fout, Compression compression);
+       AsciiFileWriter(const string& name, Compression compression);
+
+       /**
+        * Exceptions from close are ignored. Use close() explicitely.
+        */
+       ~AsciiFileWriter();
+
+       void write_line(const string& line);
+
+       void close();
+
+    public:
+
+       class Impl;
+
+       std::unique_ptr<Impl> impl;
 
     };
 
@@ -63,32 +107,39 @@ namespace snapper
     {
     public:
 
-       explicit AsciiFile(const char* name, bool remove_empty = false);
        explicit AsciiFile(const string& name, bool remove_empty = false);
 
-       string name() const { return Name_C; }
+       /**
+        * Does not call save().
+        */
+       ~AsciiFile();
 
-       void setName(const string& name) { AsciiFile::Name_C = name; }
+       string get_name() const { return name; }
+       void set_name(const string& name) { AsciiFile::name = name; }
 
        void reload();
-       bool save();
 
-       void logContent() const;
+       void save();
+
+       void log_content() const;
 
-       void clear() { Lines_C.clear(); }
-       void push_back(const string& line) { Lines_C.push_back(line); }
+       bool empty() const { return lines.empty(); }
 
-       vector<string>& lines() { return Lines_C; }
-       const vector<string>& lines() const { return Lines_C; }
+       void clear() { lines.clear(); }
+
+       void push_back(const string& line) { lines.push_back(line); }
+
+       vector<string>& get_lines() { return lines; }
+       const vector<string>& get_lines() const { return lines; }
 
     protected:
 
-       vector<string> Lines_C;
+       vector<string> lines;
 
     private:
 
-       string Name_C;
-       bool remove_empty;
+       string name;
+       bool remove_empty = false;
 
     };
 
@@ -102,31 +153,38 @@ namespace snapper
            explicit InvalidKeyException() : Exception("invalid key") {}
        };
 
-       SysconfigFile(const char* name) : AsciiFile(name), modified(false) {}
-       SysconfigFile(const string& name) : AsciiFile(name), modified(false) {}
-       virtual ~SysconfigFile() { if (modified) save(); }
+       SysconfigFile(const string& name);
+
+       /**
+        * Calls save(). Exceptions from save are ignored. Use save() explicitely in
+        * needed.
+        */
+       virtual ~SysconfigFile();
 
-       void setName(const string& name) { AsciiFile::setName(name); }
+       void set_name(const string& name) { AsciiFile::set_name(name); }
 
+       /**
+        * Save if modified.
+        */
        void save();
 
-       virtual void checkKey(const string& key) const;
+       virtual void check_key(const string& key) const;
 
-       virtual void setValue(const string& key, bool value);
-       bool getValue(const string& key, bool& value) const;
+       virtual void set_value(const string& key, bool value);
+       bool get_value(const string& key, bool& value) const;
 
-       virtual void setValue(const string& key, const char* value);
-       virtual void setValue(const string& key, const string& value);
-       bool getValue(const string& key, string& value) const;
+       virtual void set_value(const string& key, const char* value);
+       virtual void set_value(const string& key, const string& value);
+       bool get_value(const string& key, string& value) const;
 
-       virtual void setValue(const string& key, const vector<string>& values);
-       bool getValue(const string& key, vector<string>& values) const;
+       virtual void set_value(const string& key, const vector<string>& values);
+       bool get_value(const string& key, vector<string>& values) const;
 
-       map<string, string> getAllValues() const;
+       map<string, string> get_all_values() const;
 
     private:
 
-       bool modified;
+       bool modified = false;
 
        struct ParsedLine
        {
index 0a710cff629877e4f696680e4cc70fd0a24647e6..02bf167f828d68b1ebad9336e19886c42438c2b8 100644 (file)
@@ -91,7 +91,7 @@ namespace snapper
 #ifdef ENABLE_BTRFS_QUOTA
 
        string qgroup_str;
-       if (config_info.getValue("QGROUP", qgroup_str) && !qgroup_str.empty())
+       if (config_info.get_value("QGROUP", qgroup_str) && !qgroup_str.empty())
        {
            try
            {
index 08081572221242ffc231207f469a268921613d3b..ef3a273ac34d8c41039182be2e75a4b01a76494d 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) [2011-2014] Novell, Inc.
+ * Copyright (c) 2022 SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -27,6 +28,7 @@
 #include <fcntl.h>
 #include <string.h>
 #include <errno.h>
+#include <regex>
 
 #include "snapper/Comparison.h"
 #include "snapper/Snapper.h"
@@ -37,6 +39,7 @@
 #include "snapper/SnapperTmpl.h"
 #include "snapper/AsciiFile.h"
 #include "snapper/Filesystem.h"
+#include "snapper/ComparisonImpl.h"
 
 
 namespace snapper
@@ -137,6 +140,8 @@ namespace snapper
     {
        y2mil("num1:" << getSnapshot1()->getNum() << " num2:" << getSnapshot2()->getNum());
 
+       files.clear();
+
        cmpdirs_cb_t cb = [this](const string& name, unsigned int status) {
            files.push_back(File(&file_paths, name, status));
        };
@@ -158,39 +163,78 @@ namespace snapper
 
 
     bool
-    Comparison::load()
+    Comparison::check_header(const string& line) const
     {
-       y2mil("num1:" << getSnapshot1()->getNum() << " num2:" << getSnapshot2()->getNum());
+       static const regex rx_header("snapper-([0-9\\.]+)-([a-z]+)-([0-9]+)-begin", regex::extended);
 
-       if (getSnapshot1()->isCurrent() || getSnapshot2()->isCurrent())
-           SN_THROW(IllegalSnapshotException());
+       smatch match;
 
-       unsigned int num1 = getSnapshot1()->getNum();
-       unsigned int num2 = getSnapshot2()->getNum();
+       if (regex_match(line, match, rx_header))
+       {
+           if (match[2] != "list" || match[3] != "1")
+           {
+               y2err("unknown filelist format:'" << match[2] << "' version:'" << match[3] << "'");
+               SN_THROW(Exception("header format/version not supported"));
+           }
 
-       bool invert = num1 > num2;
+           return true;
+       }
+       else
+       {
+           // fine, older files might not have a header
 
-       if (invert)
-           swap(num1, num2);
+           return false;
+       }
+    }
+
+
+    bool
+    Comparison::check_footer(const string& line) const
+    {
+       static const regex rx_footer("snapper-([0-9\\.]+)-([a-z]+)-([0-9]+)-end", regex::extended);
+
+       return regex_match(line, rx_footer);
+    }
+
+
+    bool
+    Comparison::load(int fd, Compression compression, bool invert)
+    {
+       files.clear();
 
        try
        {
-           SDir infos_dir = getSnapper()->openInfosDir();
-           SDir info_dir = SDir(infos_dir, decString(num2));
+           AsciiFileReader ascii_file_reader(fd, compression);
 
-           int fd = info_dir.open("filelist-" + decString(num1) + ".txt", O_RDONLY | O_NOATIME |
-                                  O_NOFOLLOW | O_CLOEXEC);
-           if (fd == -1)
-               return false;
+           bool has_header = false;
+           bool has_footer = false;
 
-           AsciiFileReader asciifile(fd);
+           bool first = true;
 
            string line;
-           while (asciifile.getline(line))
+           while (ascii_file_reader.read_line(line))
            {
+               if (first)
+               {
+                   first = false;
+                   if (check_header(line))
+                   {
+                       has_header = true;
+                       continue;
+                   }
+               }
+               else
+               {
+                   if (has_header && check_footer(line))
+                   {
+                       has_footer = true;
+                       break;
+                   }
+               }
+
                string::size_type pos = line.find(" ");
                if (pos == string::npos)
-                   continue;
+                   SN_THROW(Exception("separator space not found"));
 
                unsigned int status = stringToStatus(string(line, 0, pos));
                string name = string(line, pos + 1);
@@ -201,21 +245,74 @@ namespace snapper
                File file(&file_paths, name, status);
                files.push_back(file);
            }
+
+           ascii_file_reader.close();
+
+           if (has_header && !has_footer)
+               SN_THROW(Exception("footer not found"));
+
+           files.sort();
+
+           y2mil("read " << files.size() << " lines");
+
+           return true;
        }
-       catch (const FileNotFoundException& e)
+       catch (const Exception& e)
        {
+           SN_CAUGHT(e);
+
            return false;
        }
+    }
 
-       files.sort();
 
-       y2mil("read " << files.size() << " lines");
+    bool
+    Comparison::load()
+    {
+       y2mil("num1:" << getSnapshot1()->getNum() << " num2:" << getSnapshot2()->getNum());
+
+       if (getSnapshot1()->isCurrent() || getSnapshot2()->isCurrent())
+           SN_THROW(IllegalSnapshotException());
 
-       return true;
+       unsigned int num1 = getSnapshot1()->getNum();
+       unsigned int num2 = getSnapshot2()->getNum();
+
+       bool invert = num1 > num2;
+
+       if (invert)
+           swap(num1, num2);
+
+       try
+       {
+           SDir infos_dir = getSnapper()->openInfosDir();
+           SDir info_dir = SDir(infos_dir, decString(num2));
+
+           string name = filelist_name(num1);
+
+           for (Compression compression : { Compression::GZIP, Compression::NONE })
+           {
+               if (!is_available(compression))
+                   continue;
+
+               int fd = info_dir.open(add_extension(compression, name), O_RDONLY | O_NOATIME |
+                                      O_NOFOLLOW | O_CLOEXEC);
+               if (fd > -1)
+               {
+                   if (load(fd, compression, invert))
+                       return true;
+               }
+           }
+       }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
+       }
+
+       return false;
     }
 
 
-    void
+    bool
     Comparison::save() const
     {
        y2mil("num1:" << getSnapshot1()->getNum() << " num2:" << getSnapshot2()->getNum());
@@ -231,29 +328,52 @@ namespace snapper
        if (invert)
            swap(num1, num2);
 
-       string file_name = "filelist-" + decString(num1) + ".txt";
+       Compression compression = snapper->get_compression();
+
+       string file_name = add_extension(compression, filelist_name(num1));
        string tmp_name = file_name + ".tmp-XXXXXX";
 
        SDir info_dir = invert ? getSnapshot1()->openInfoDir() : getSnapshot2()->openInfoDir();
 
-       FILE* file = fdopen(info_dir.mktemp(tmp_name), "w");
-       if (!file)
+       int fd = info_dir.mktemp(tmp_name);
+       if (fd < -1)
            SN_THROW(IOErrorException(sformat("mkstemp failed errno:%d (%s)", errno,
                                              stringerror(errno).c_str())));
 
-       for (Files::const_iterator it = files.begin(); it != files.end(); ++it)
+       try
        {
-           unsigned int status = it->getPreToPostStatus();
+           AsciiFileWriter ascii_file_writer(fd, compression);
+
+           ascii_file_writer.write_line("snapper-" VERSION "-list-1-begin");
+
+           for (const File& file : files)
+           {
+               unsigned int status = file.getPreToPostStatus();
+
+               if (invert)
+                   status = invertStatus(status);
+
+               string line = statusToString(status) + " " + file.getName();
 
-           if (invert)
-               status = invertStatus(status);
+               ascii_file_writer.write_line(line);
+           }
+
+           ascii_file_writer.write_line("snapper-" VERSION "-list-1-end");
 
-           fprintf(file, "%s %s\n", statusToString(status).c_str(), it->getName().c_str());
+           ascii_file_writer.close();
        }
+       catch (const Exception& e)
+       {
+           SN_CAUGHT(e);
 
-       fclose(file);
+           info_dir.unlink(tmp_name, 0);
+
+           return false;
+       }
 
        info_dir.rename(tmp_name, file_name);
+
+       return true;
     }
 
 
index a21e3e6c11ff7c5bd275e374b6513528d46c1629..879fb50108d66ac2a8ae539bdd9f4ce44fb3504d 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) [2011-2012] Novell, Inc.
+ * Copyright (c) 2022 SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -25,6 +26,7 @@
 
 
 #include "snapper/Snapshot.h"
+#include "snapper/Snapper.h"
 #include "snapper/File.h"
 
 
@@ -55,7 +57,7 @@ namespace snapper
        const Files& getFiles() const { return files; }
 
        UndoStatistic getUndoStatistic() const;
-        XAUndoStatistic getXAUndoStatistic() const;
+       XAUndoStatistic getXAUndoStatistic() const;
 
        vector<UndoStep> getUndoSteps() const;
 
@@ -65,8 +67,25 @@ namespace snapper
 
        void initialize();
        void create();
+
+       /**
+        * Check the header. Throws if the header is unsupported. Return true iff a header
+        * was found.
+        */
+       bool check_header(const string& line) const;
+
+       /**
+        * Check the footer. Throws if the footer is unsupported. Return true iff a footer
+        * was found.
+        */
+       bool check_footer(const string& line) const;
+
        bool load();
-       void save() const;
+
+       bool load(int fd, Compression compression, bool invert);
+
+       bool save() const;
+
        void filter();
 
        void do_mount() const;
diff --git a/snapper/ComparisonImpl.cc b/snapper/ComparisonImpl.cc
new file mode 100644 (file)
index 0000000..aa9832b
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 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 <dirent.h>
+#include <regex>
+
+#include "snapper/SnapperTmpl.h"
+
+
+namespace snapper
+{
+
+    using std::string;
+    using std::regex;
+
+
+    string
+    filelist_name(unsigned int num)
+    {
+       return "filelist-" + decString(num) + ".txt";
+    }
+
+
+    bool
+    is_filelist_file(unsigned char type, const char* name)
+    {
+       static const regex rx("filelist-([0-9]+).txt(\\.gz)?", regex::extended);
+
+       if (type != DT_UNKNOWN && type != DT_REG)
+           return false;
+
+       return regex_match(name, rx);
+    }
+
+}
diff --git a/snapper/ComparisonImpl.h b/snapper/ComparisonImpl.h
new file mode 100644 (file)
index 0000000..d1104ad
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 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.
+ */
+
+
+#ifndef SNAPPER_COMPARISON_IMPL_H
+#define SNAPPER_COMPARISON_IMPL_H
+
+
+namespace snapper
+{
+
+    string filelist_name(unsigned int num);
+
+    bool is_filelist_file(unsigned char type, const char* name);
+
+}
+
+
+#endif
index fb63b882dc3010d305df0a3548adc442c7e621c7..a9be41925a322d4fb4d17cfc79412b1d4ab026c1 100644 (file)
@@ -149,7 +149,7 @@ namespace snapper
 
        /**
         * Default constructor.
-        ***/
+        **/
        CodeLocation()
            : _line(0) {}
 
@@ -182,7 +182,7 @@ namespace snapper
 
        std::string _file;
        std::string _func;
-       int _line;
+       int _line = 0;
 
     };
 
@@ -361,6 +361,7 @@ namespace snapper
     struct LogicErrorException : public Exception
     {
        explicit LogicErrorException() : Exception("logic error") {}
+       explicit LogicErrorException(const char* msg) : Exception(msg) {}
     };
 
     struct IOErrorException : public Exception
index 17a4aab42732806617f8b25356b04f2e189b8268..24bf610d9a824b47b0e48d9b78bc9e7848aceb39 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) 2022 SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -97,6 +98,13 @@ namespace snapper
     }
 
 
+    void
+    Files::clear()
+    {
+       entries.clear();
+    }
+
+
     bool
     operator<(const File& lhs, const File& rhs)
     {
index ade271b76f26513b2b6730ab9ee10f9f18fa6a87..e1da2753bb778aea6c70154895d7c663bc9b2ef1 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) [2011-2015] Novell, Inc.
+ * Copyright (c) 2022 SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -41,10 +42,9 @@ namespace snapper
        CREATED = 1,            // created
        DELETED = 2,            // deleted
        TYPE = 4,               // type has changed
-       CONTENT = 8,            // content has changed
+       CONTENT = 8,            // content has changed
        PERMISSIONS = 16,       // permissions have changed, see chmod(2)
        OWNER = 32,             // owner has changed, see chown(2)
-       USER = 32,              // deprecated - alias for OWNER
        GROUP = 64,             // group has changed, see chown(2)
        XATTRS = 128,           // extended attributes changed, see attr(5)
        ACL = 256               // access control list changed, see acl(5)
@@ -68,13 +68,11 @@ namespace snapper
 
     struct UndoStatistic
     {
-       UndoStatistic() : numCreate(0), numModify(0), numDelete(0) {}
-
        bool empty() const { return numCreate == 0 && numModify == 0 && numDelete == 0; }
 
-       unsigned int numCreate;
-       unsigned int numModify;
-       unsigned int numDelete;
+       unsigned int numCreate = 0;
+       unsigned int numModify = 0;
+       unsigned int numDelete = 0;
 
        friend std::ostream& operator<<(std::ostream& s, const UndoStatistic& rs);
     };
@@ -82,13 +80,13 @@ namespace snapper
 
     struct XAUndoStatistic
     {
-        XAUndoStatistic(): numCreate(0), numReplace(0), numDelete(0) {}
+       bool empty() const { return numCreate == 0 && numReplace == 0 && numDelete == 0; }
 
-        unsigned int numCreate;
-        unsigned int numReplace;
-        unsigned int numDelete;
+       unsigned int numCreate = 0;
+       unsigned int numReplace = 0;
+       unsigned int numDelete = 0;
 
-        friend XAUndoStatistic& operator+=(XAUndoStatistic&, const XAUndoStatistic&);
+       friend XAUndoStatistic& operator+=(XAUndoStatistic&, const XAUndoStatistic&);
     };
 
 
@@ -115,9 +113,7 @@ namespace snapper
     public:
 
        File(const FilePaths* file_paths, const string& name, unsigned int pre_to_post_status)
-           : file_paths(file_paths), name(name), pre_to_post_status(pre_to_post_status),
-             pre_to_system_status(-1), post_to_system_status(-1), undo(false),
-             xaCreated(0), xaDeleted(0), xaReplaced(0)
+           : file_paths(file_paths), name(name), pre_to_post_status(pre_to_post_status)
        {}
 
        const string& getName() const { return name; }
@@ -160,18 +156,18 @@ namespace snapper
 
        string name;
 
-       unsigned int pre_to_post_status;
-       unsigned int pre_to_system_status; // -1 if invalid
-       unsigned int post_to_system_status; // -1 if invalid
+       unsigned int pre_to_post_status = -1;
+       unsigned int pre_to_system_status = -1; // -1 if invalid
+       unsigned int post_to_system_status = -1; // -1 if invalid
 
-       bool undo;
+       bool undo = false;
 
        bool modifyXattributes();
        bool modifyAcls();
 
-       unsigned int xaCreated;
-       unsigned int xaDeleted;
-       unsigned int xaReplaced;
+       unsigned int xaCreated = 0;
+       unsigned int xaDeleted = 0;
+       unsigned int xaReplaced = 0;
 
     };
 
@@ -201,6 +197,8 @@ namespace snapper
        size_type size() const { return entries.size(); }
        bool empty() const { return entries.empty(); }
 
+       void clear();
+
        iterator find(const string& name);
        const_iterator find(const string& name) const;
 
@@ -213,13 +211,14 @@ namespace snapper
 
        bool doUndoStep(const UndoStep& undo_step);
 
-        XAUndoStatistic getXAUndoStatistic() const;
+       XAUndoStatistic getXAUndoStatistic() const;
 
     protected:
 
-       void push_back(File file) { entries.push_back(file); }
+       void push_back(const File& file) { entries.push_back(file); }
 
        void filter(const vector<string>& ignore_patterns);
+
        void sort();
 
        const FilePaths* file_paths;
index 52ca97fae892362cc404af6d08f3712171300ddc..30164d1963f1c6b97f98c83336d6af42d3f191f9 100644 (file)
@@ -127,9 +127,9 @@ namespace snapper
     Filesystem::create(const ConfigInfo& config_info, const string& root_prefix)
     {
        string fstype = "btrfs";
-       config_info.getValue(KEY_FSTYPE, fstype);
+       config_info.get_value(KEY_FSTYPE, fstype);
 
-       Filesystem* fs = create(fstype, config_info.getSubvolume(), root_prefix);
+       Filesystem* fs = create(fstype, config_info.get_subvolume(), root_prefix);
 
        fs->evalConfigInfo(config_info);
 
index 85982ae56edb578fe6978b259c440960b884147e..c465ca21f04289bf57c77cc0f8227dece66289ca 100644 (file)
@@ -12,6 +12,7 @@ libsnapper_la_SOURCES =                                       \
        Snapper.cc              Snapper.h               \
        Snapshot.cc             Snapshot.h              \
        Comparison.cc           Comparison.h            \
+       ComparisonImpl.cc       ComparisonImpl.h        \
        Filesystem.cc           Filesystem.h            \
        File.cc                 File.h                  \
        XmlFile.cc              XmlFile.h               \
@@ -25,7 +26,6 @@ libsnapper_la_SOURCES =                                       \
        Compare.cc              Compare.h               \
        SystemCmd.cc            SystemCmd.h             \
        AsciiFile.cc            AsciiFile.h             \
-       Regex.cc                Regex.h                 \
        Acls.cc                 Acls.h                  \
        Hooks.cc                Hooks.h                 \
        Exception.cc            Exception.h             \
@@ -64,7 +64,7 @@ libsnapper_la_SOURCES +=                              \
 endif
 
 libsnapper_la_LDFLAGS = -version-info @LIBVERSION_INFO@
-libsnapper_la_LIBADD = -lboost_thread -lboost_system $(XML2_LIBS) -lacl
+libsnapper_la_LIBADD = -lboost_thread -lboost_system $(XML2_LIBS) -lacl -lz
 if ENABLE_ROLLBACK
 libsnapper_la_LIBADD += -lmount
 endif
diff --git a/snapper/Regex.cc b/snapper/Regex.cc
deleted file mode 100644 (file)
index 26962c1..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) [2004-2011] Novell, Inc.
- *
- * 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.
- */
-
-
-// This file is obsolete. It is only present for compatibility of libsnapper, esp. the
-// Regex class was used in older versions of the zypp-plugin and we must avoid potentially
-// problems during updates.
-
-
-#include <stdexcept>
-#include "snapper/Regex.h"
-
-extern int _nl_msg_cat_cntr;
-
-
-namespace snapper
-{
-
-Regex::Regex (const string& pattern, int cflags, unsigned int nm)
-    : pattern (pattern),
-      cflags (cflags),
-      nm (cflags & REG_NOSUB ? 0 : nm)
-{
-    int errcode = regcomp (&rx, pattern.c_str (), cflags);
-    if (errcode) {
-       size_t esize = regerror(errcode, &rx, nullptr, 0);
-       char error[esize];
-       regerror(errcode, &rx, error, esize);
-       throw std::runtime_error(string("Regex compilation error: ") + error);
-    }
-    my_nl_msg_cat_cntr = _nl_msg_cat_cntr;
-    rm = new regmatch_t[nm];
-}
-
-
-Regex::~Regex ()
-{
-    delete [] rm;
-    regfree (&rx);
-}
-
-
-bool
-Regex::match (const string& str, int eflags) const
-{
-    if (my_nl_msg_cat_cntr != _nl_msg_cat_cntr) {
-       regfree (&rx);
-       regcomp (&rx, pattern.c_str (), cflags);
-       my_nl_msg_cat_cntr = _nl_msg_cat_cntr;
-    }
-
-    last_str = str;
-
-    return regexec (&rx, str.c_str (), nm, rm, eflags) == 0;
-}
-
-
-regoff_t
-Regex::so (unsigned int i) const
-{
-    return i < nm ? rm[i].rm_so : -1;
-}
-
-
-regoff_t
-Regex::eo (unsigned int i) const
-{
-    return i < nm ? rm[i].rm_eo : -1;
-}
-
-
-string
-Regex::cap (unsigned int i) const
-{
-    if (i < nm && rm[i].rm_so > -1)
-       return last_str.substr (rm[i].rm_so, rm[i].rm_eo - rm[i].rm_so);
-    return "";
-}
-
-
-const string Regex::ws = "[ \t]*";
-const string Regex::number = "[0123456789]+";
-const string Regex::trailing_comment = "(#.*)?";
-
-}
diff --git a/snapper/Regex.h b/snapper/Regex.h
deleted file mode 100644 (file)
index cd47686..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) [2004-2012] Novell, Inc.
- *
- * 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.
- */
-
-
-// This file is obsolete. It is only present for compatibility of libsnapper, esp. the
-// Regex class was used in older versions of the zypp-plugin and we must avoid potentially
-// problems during updates.
-
-
-#ifndef SNAPPER_REGEX_H
-#define SNAPPER_REGEX_H
-
-
-#include <regex.h>
-#include <string>
-#include <boost/noncopyable.hpp>
-
-
-namespace snapper
-{
-    using std::string;
-
-
-class Regex : private boost::noncopyable
-{
-public:
-
-    Regex (const string& pattern, int cflags = REG_EXTENDED, unsigned int = 10);
-    ~Regex ();
-
-    string getPattern () const { return pattern; }
-    int getCflags () const { return cflags; }
-
-    bool match (const string&, int eflags = 0) const;
-
-    regoff_t so (unsigned int) const;
-    regoff_t eo (unsigned int) const;
-
-    string cap (unsigned int) const;
-
-    static const string ws;
-    static const string number;
-    static const string trailing_comment;
-
-private:
-
-    const string pattern;
-    const int cflags;
-    const unsigned int nm;
-
-    mutable regex_t rx;
-    mutable int my_nl_msg_cat_cntr;
-    mutable regmatch_t* rm;
-
-    mutable string last_str;
-};
-
-}
-
-#endif
index 36a8b23797285cc8766624a9879dad29fe98ca4a..61c1de42357542a938cbc760db611b461fae0b85 100644 (file)
@@ -33,16 +33,15 @@ namespace snapper
 {
 
     SnapperContexts::SnapperContexts()
-       : subvolume_ctx(NULL)
     {
        std::map<string,string> snapperd_contexts;
 
        try
        {
-           AsciiFileReader asciifile(selinux_snapperd_contexts_path());
+           AsciiFileReader ascii_file_reader(selinux_snapperd_contexts_path(), Compression::NONE);
 
            string line;
-           while (asciifile.getline(line))
+           while (ascii_file_reader.read_line(line))
            {
                // commented line
                if (line[0] == '#')
@@ -53,7 +52,8 @@ namespace snapper
                if (pos == string::npos)
                    continue;
 
-               if (!snapperd_contexts.insert(make_pair(boost::trim_copy(line.substr(0, pos)), boost::trim_copy(line.substr(pos + 1)))).second)
+               if (!snapperd_contexts.insert(make_pair(boost::trim_copy(line.substr(0, pos)),
+                                                       boost::trim_copy(line.substr(pos + 1)))).second)
                {
                    SN_THROW(SelinuxException("Duplicate key in contexts file"));
                }
index e4e3549ff644fef10436ca234047c8913697df32..2e5d806bcd16e515356a46f7ac4c9f39b6229ae5 100644 (file)
 #include "snapper/Exception.h"
 
 
-namespace snapper {
+namespace snapper
+{
+    using std::string;
 
     struct SelinuxException : public Exception
     {
        explicit SelinuxException() : Exception("SELinux error") {}
-       explicit SelinuxException(const std::string& msg) : Exception(msg) {}
+       explicit SelinuxException(const string& msg) : Exception(msg) {}
     };
 
-    using std::string;
-
     const static string selinux_snapperd_data = "snapperd_data";
 
     bool _is_selinux_enabled();
@@ -53,8 +53,9 @@ namespace snapper {
        char* subvolume_context() const { return context_str(subvolume_ctx); }
        SnapperContexts();
        ~SnapperContexts() { context_free(subvolume_ctx); }
+
     private:
-       context_t subvolume_ctx;
+       context_t subvolume_ctx = nullptr;
     };
 
     class DefaultSelinuxFileContext : private boost::noncopyable
@@ -73,6 +74,7 @@ namespace snapper {
        char* selabel_lookup(const string& path, int mode);
 
        ~SelinuxLabelHandle() { selabel_close(handle); }
+
     private:
        SelinuxLabelHandle();
 
index ceacef6c7fd93b3899f5f30521c2114ab7b61284..61389f5b48c06653509188aca86aea1796371788 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) [2016-2021] SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -46,6 +46,7 @@
 #include "snapper/AsciiFile.h"
 #include "snapper/Exception.h"
 #include "snapper/Hooks.h"
+#include "snapper/ComparisonImpl.h"
 #ifdef ENABLE_BTRFS
 #include "snapper/Btrfs.h"
 #include "snapper/BtrfsUtils.h"
@@ -64,20 +65,20 @@ namespace snapper
        : SysconfigFile(prepend_root_prefix(root_prefix, CONFIGS_DIR "/" + config_name)),
          config_name(config_name), subvolume("/")
     {
-       if (!getValue(KEY_SUBVOLUME, subvolume))
+       if (!get_value(KEY_SUBVOLUME, subvolume))
            SN_THROW(InvalidConfigException());
     }
 
 
     void
-    ConfigInfo::checkKey(const string& key) const
+    ConfigInfo::check_key(const string& key) const
     {
        if (key == KEY_SUBVOLUME || key == KEY_FSTYPE)
            SN_THROW(InvalidConfigdataException());
 
        try
        {
-           SysconfigFile::checkKey(key);
+           SysconfigFile::check_key(key);
        }
        catch (const InvalidKeyException& e)
        {
@@ -87,7 +88,7 @@ namespace snapper
 
 
     Snapper::Snapper(const string& config_name, const string& root_prefix, bool disable_filters)
-       : config_info(NULL), filesystem(NULL), snapshots(this), selabel_handle(NULL)
+       : snapshots(this)
     {
        y2mil("Snapper constructor");
        y2mil("libsnapper version " VERSION);
@@ -119,10 +120,10 @@ namespace snapper
        syncSelinuxContexts(filesystem->fstype() == "btrfs");
 
        bool sync_acl;
-       if (config_info->getValue(KEY_SYNC_ACL, sync_acl) && sync_acl == true)
+       if (config_info->get_value(KEY_SYNC_ACL, sync_acl) && sync_acl == true)
            syncAcl();
 
-       y2mil("subvolume:" << config_info->getSubvolume() << " filesystem:" <<
+       y2mil("subvolume:" << config_info->get_subvolume() << " filesystem:" <<
              filesystem->fstype());
 
        if (!disable_filters)
@@ -180,15 +181,18 @@ namespace snapper
        {
            try
            {
-               AsciiFileReader asciifile(file);
+               AsciiFileReader ascii_file_reader(file, Compression::NONE);
 
                string line;
-               while (asciifile.getline(line))
+               while (ascii_file_reader.read_line(line))
                    if (!line.empty())
                        ignore_patterns.push_back(line);
+
+               ascii_file_reader.close();
            }
-           catch (const FileNotFoundException& e)
+           catch (const Exception& e)
            {
+               SN_CAUGHT(e);
            }
        }
 
@@ -200,7 +204,7 @@ namespace snapper
     string
     Snapper::subvolumeDir() const
     {
-       return config_info->getSubvolume();
+       return config_info->get_subvolume();
     }
 
 
@@ -296,7 +300,7 @@ namespace snapper
        {
            SysconfigFile sysconfig(prepend_root_prefix(root_prefix, SYSCONFIG_FILE));
            vector<string> config_names;
-           sysconfig.getValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.get_value("SNAPPER_CONFIGS", config_names);
 
            for (vector<string>::const_iterator it = config_names.begin(); it != config_names.end(); ++it)
            {
@@ -346,7 +350,7 @@ namespace snapper
        list<ConfigInfo> configs = getConfigs(root_prefix);
        for (list<ConfigInfo>::const_iterator it = configs.begin(); it != configs.end(); ++it)
        {
-           if (it->getSubvolume() == subvolume)
+           if (it->get_subvolume() == subvolume)
            {
                SN_THROW(CreateConfigFailedException("subvolume already covered"));
            }
@@ -381,14 +385,14 @@ namespace snapper
        {
            SysconfigFile sysconfig(SYSCONFIG_FILE);
            vector<string> config_names;
-           sysconfig.getValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.get_value("SNAPPER_CONFIGS", config_names);
            if (find(config_names.begin(), config_names.end(), config_name) != config_names.end())
            {
                SN_THROW(CreateConfigFailedException("config already exists"));
            }
 
            config_names.push_back(config_name);
-           sysconfig.setValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.set_value("SNAPPER_CONFIGS", config_names);
        }
        catch (const FileNotFoundException& e)
        {
@@ -399,10 +403,10 @@ namespace snapper
        {
            SysconfigFile config(template_file);
 
-           config.setName(CONFIGS_DIR "/" + config_name);
+           config.set_name(CONFIGS_DIR "/" + config_name);
 
-           config.setValue(KEY_SUBVOLUME, subvolume);
-           config.setValue(KEY_FSTYPE, filesystem->fstype());
+           config.set_value(KEY_SUBVOLUME, subvolume);
+           config.set_value(KEY_FSTYPE, filesystem->fstype());
        }
        catch (const FileNotFoundException& e)
        {
@@ -419,10 +423,10 @@ namespace snapper
 
            SysconfigFile sysconfig(SYSCONFIG_FILE);
            vector<string> config_names;
-           sysconfig.getValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.get_value("SNAPPER_CONFIGS", config_names);
            config_names.erase(remove(config_names.begin(), config_names.end(), config_name),
                               config_names.end());
-           sysconfig.setValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.set_value("SNAPPER_CONFIGS", config_names);
 
            SystemCmd cmd(RMBIN " " + quote(CONFIGS_DIR "/" + config_name));
 
@@ -484,10 +488,10 @@ namespace snapper
        {
            SysconfigFile sysconfig(SYSCONFIG_FILE);
            vector<string> config_names;
-           sysconfig.getValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.get_value("SNAPPER_CONFIGS", config_names);
            config_names.erase(remove(config_names.begin(), config_names.end(), config_name),
                               config_names.end());
-           sysconfig.setValue("SNAPPER_CONFIGS", config_names);
+           sysconfig.set_value("SNAPPER_CONFIGS", config_names);
        }
        catch (const FileNotFoundException& e)
        {
@@ -500,7 +504,7 @@ namespace snapper
     Snapper::setConfigInfo(const map<string, string>& raw)
     {
        for (map<string, string>::const_iterator it = raw.begin(); it != raw.end(); ++it)
-           config_info->setValue(it->first, it->second);
+           config_info->set_value(it->first, it->second);
 
        config_info->save();
 
@@ -510,7 +514,7 @@ namespace snapper
            raw.find(KEY_SYNC_ACL) != raw.end())
        {
            bool sync_acl;
-           if (config_info->getValue(KEY_SYNC_ACL, sync_acl) && sync_acl == true)
+           if (config_info->get_value(KEY_SYNC_ACL, sync_acl) && sync_acl == true)
                syncAcl();
        }
     }
@@ -521,7 +525,7 @@ namespace snapper
     {
        vector<uid_t> uids;
        vector<string> users;
-       if (config_info->getValue(KEY_ALLOW_USERS, users))
+       if (config_info->get_value(KEY_ALLOW_USERS, users))
        {
            for (vector<string>::const_iterator it = users.begin(); it != users.end(); ++it)
            {
@@ -534,7 +538,7 @@ namespace snapper
 
        vector<gid_t> gids;
        vector<string> groups;
-       if (config_info->getValue(KEY_ALLOW_GROUPS, groups))
+       if (config_info->get_value(KEY_ALLOW_GROUPS, groups))
        {
            for (vector<string>::const_iterator it = groups.begin(); it != groups.end(); ++it)
            {
@@ -916,7 +920,6 @@ namespace snapper
     {
 #ifdef ENABLE_SELINUX
        static const regex rx("[0-9]+", regex::extended);
-       static const regex rx_filelist("filelist-[0-9]+.txt", regex::extended);
 
        y2deb("Syncing Selinux contexts in infos dir");
 
@@ -940,12 +943,9 @@ namespace snapper
                snapshot_dir.restorecon(selabel_handle);
            }
 
-           vector<string> info_content = info_dir.entries();
+           vector<string> info_content = info_dir.entries(is_filelist_file);
            for (vector<string>::const_iterator it2 = info_content.begin(); it2 != info_content.end(); ++it2)
            {
-               if (!regex_match(*it2, rx_filelist))
-                   continue;
-
                SFile fl(info_dir, *it2);
                fl.restorecon(selabel_handle);
            }
@@ -1012,6 +1012,28 @@ namespace snapper
     }
 
 
+    Compression
+    Snapper::get_compression() const
+    {
+       Compression compression = Compression::GZIP;
+
+       string tmp;
+
+       if (config_info->get_value(KEY_COMPRESSION, tmp))
+       {
+           if (tmp == "none")
+               compression = Compression::NONE;
+           else if (tmp == "gzip")
+               compression = Compression::GZIP;
+       }
+
+       if (!is_available(compression))
+           compression = Compression::NONE;
+
+       return compression;
+    }
+
+
     const char*
     Snapper::compileVersion()
     {
index 8602eb98f191f325a1b21fa9e321b905e07b15b9..3a29d981ee24bb04e23419519bdbcf12c946e15c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) 2016 SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -48,10 +48,10 @@ namespace snapper
 
        explicit ConfigInfo(const string& config_name, const string& root_prefix);
 
-       const string& getConfigName() const { return config_name; }
-       const string& getSubvolume() const { return subvolume; }
+       const string& get_config_name() const { return config_name; }
+       const string& get_subvolume() const { return subvolume; }
 
-       virtual void checkKey(const string& key) const override;
+       virtual void check_key(const string& key) const override;
 
     private:
 
@@ -128,7 +128,7 @@ namespace snapper
        Snapper(const string& config_name, const string& root_prefix, bool disable_filters = false);
        ~Snapper();
 
-       const string& configName() const { return config_info->getConfigName(); }
+       const string& configName() const { return config_info->get_config_name(); }
 
        string subvolumeDir() const;
 
@@ -186,6 +186,12 @@ namespace snapper
         */
        void calculateUsedSpace() const;
 
+       /**
+        * Return the compression algorithm set in the config file or a fallback. Also
+        * checks if the compression is available and uses NONE as a fallback.
+        */
+       Compression get_compression() const;
+
        static const char* compileVersion();
        static const char* compileFlags();
 
@@ -204,15 +210,15 @@ namespace snapper
        void syncSelinuxContextsInInfosDir(bool skip_snapshot_dir) const;
        void syncInfoDir(SDir& dir) const;
 
-       ConfigInfo* config_info;
+       ConfigInfo* config_info = nullptr;
 
-       Filesystem* filesystem;
+       Filesystem* filesystem = nullptr;
 
        vector<string> ignore_patterns;
 
        Snapshots snapshots;
 
-       SelinuxLabelHandle* selabel_handle;
+       SelinuxLabelHandle* selabel_handle = nullptr;
 
     };
 
index c43036c6efb3828a8cfaed00c95181cd41d183bd..46f0c6f161f9e169daf8aa73d08eabe0dd17e3d9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2004-2014] Novell, Inc.
- * Copyright (c) [2020-2021] SUSE LLC
+ * Copyright (c) [2020-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -54,6 +54,7 @@
 #define KEY_ALLOW_USERS "ALLOW_USERS"
 #define KEY_ALLOW_GROUPS "ALLOW_GROUPS"
 #define KEY_SYNC_ACL "SYNC_ACL"
+#define KEY_COMPRESSION "COMPRESSION"
 
 
 #endif
index f63c5461b9ca7e4f4e5b821159bcb6e250ef9871..941bc7725e0eb3f2045f20e0cae953eb3f67b1fa 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) [2016-2020] SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -26,8 +26,6 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <fcntl.h>
-#include <fnmatch.h>
-#include <dirent.h>
 #include <errno.h>
 #include <string.h>
 #include <regex>
@@ -46,6 +44,7 @@
 #include "snapper/SnapperDefines.h"
 #include "snapper/Exception.h"
 #include "snapper/Hooks.h"
+#include "snapper/ComparisonImpl.h"
 
 
 namespace snapper
@@ -79,8 +78,7 @@ namespace snapper
 
 
     Snapshot::Snapshot(const Snapper* snapper, SnapshotType type, unsigned int num, time_t date)
-       : snapper(snapper), type(type), num(num), date(date), uid(0), pre_num(0),
-         mount_checked(false), mount_user_request(false), mount_use_count(0)
+       : snapper(snapper), type(type), num(num), date(date)
     {
     }
 
@@ -184,6 +182,17 @@ namespace snapper
     }
 
 
+    Snapshots::Snapshots(const Snapper* snapper)
+       : snapper(snapper)
+    {
+    }
+
+
+    Snapshots::~Snapshots()
+    {
+    }
+
+
     void
     Snapshots::read()
     {
@@ -706,13 +715,6 @@ namespace snapper
     }
 
 
-    static bool
-    is_filelist_file(unsigned char type, const char* name)
-    {
-       return (type == DT_UNKNOWN || type == DT_REG) && (fnmatch("filelist-*.txt", name, 0) == 0);
-    }
-
-
     void
     Snapshots::modifySnapshot(iterator snapshot, const SMD& smd)
     {
@@ -744,18 +746,22 @@ namespace snapper
 
        info_dir.unlink("info.xml", 0);
 
+       // remove all filelists in the info directory of this shapshot
        vector<string> tmp1 = info_dir.entries(is_filelist_file);
-       for (vector<string>::const_iterator it = tmp1.begin(); it != tmp1.end(); ++it)
+       for (const string& name : tmp1)
        {
-           info_dir.unlink(*it, 0);
+           info_dir.unlink(name, 0);
        }
 
+       // remove all filelists of the snapshot in the info directories of other snapshots
        for (Snapshots::iterator it = begin(); it != end(); ++it)
        {
            if (!it->isCurrent())
            {
                SDir tmp2 = it->openInfoDir();
-               tmp2.unlink("filelist-" + decString(snapshot->getNum()) + ".txt", 0);
+               string name = filelist_name(snapshot->getNum());
+               tmp2.unlink(name, 0);
+               tmp2.unlink(name + ".gz", 0);
            }
        }
 
index 887967aa7e9f45323903805dc4feba57793c0af1..072ad2e8e4428609ac5618d7c65c05bc1053e655 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) [2016-2019] SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
  *
  * All Rights Reserved.
  *
@@ -145,17 +145,17 @@ namespace snapper
 
        time_t date;
 
-       uid_t uid;
+       uid_t uid = 0;
 
-       unsigned int pre_num;   // valid only for type=POST
+       unsigned int pre_num = 0;       // valid only for type=POST
 
        string description;     // likely empty for type=POST
        string cleanup;
        map<string, string> userdata;
 
-       mutable bool mount_checked;
-       mutable bool mount_user_request;
-       mutable unsigned int mount_use_count;
+       mutable bool mount_checked = false;
+       mutable bool mount_user_request = false;
+       mutable unsigned int mount_use_count = 0;
 
        void writeInfo() const;
 
@@ -177,8 +177,6 @@ namespace snapper
     {
     public:
 
-       SMD() : description(), cleanup(), userdata() {}
-
        string description;
        string cleanup;
        map<string, string> userdata;
@@ -190,17 +188,15 @@ namespace snapper
     {
     public:
 
-       SCD() : SMD(), read_only(true), empty(false), uid(0) {}
-
-       bool read_only;
+       bool read_only = true;
 
        /**
         * Create an empty snapshot. For btrfs this creates a subvolume
         * instead of a snapshot, for other filesystem types ignored.
         */
-       bool empty;
+       bool empty = false;
 
-       uid_t uid;
+       uid_t uid = 0;
 
     };
 
@@ -211,7 +207,8 @@ namespace snapper
 
        friend class Snapper;
 
-       Snapshots(const Snapper* snapper) : snapper(snapper) {}
+       Snapshots(const Snapper* snapper);
+       ~Snapshots();
 
        typedef list<Snapshot>::iterator iterator;
        typedef list<Snapshot>::const_iterator const_iterator;
index b333f30f97e78ab2d3ff6dc681758f9a329c2c3a..81ef1ae78ea20b568cecc1ef9fdf8ffcb9f3b810 100644 (file)
@@ -1,4 +1,5 @@
 *.o
+ascii-file
 simple1
 permissions1
 permissions2
index 25c6106540ca835f4cb924311b3f3491ed417489..e339a44861064b209aea4a4cca7074d4a1b19756 100644 (file)
@@ -3,7 +3,7 @@ CAUTION
 
 RUN THESE TESTS IN A SCRATCH MACHINE.
 
-The tests in this directory only work when run as the root user.
+Most tests in this directory only work when run as the root user.
 
 They operate
 - on a scratch BTRFS filesystem (`/testsuite`)
index ceb086262e91f82e06730223f32c244d21feaa05..a3bb2e3e6cccb1f4ad50bdfd1ae5c23ab21d4d8a 100644 (file)
@@ -13,8 +13,9 @@ testdir = $(libdir)/snapper/testsuite
 test_DATA = CAUTION
 test_SCRIPTS = run-all setup-and-run-all
 
-test_PROGRAMS = simple1 permissions1 permissions2 permissions3 owner1 owner2 \
-       owner3 directory1 missing-directory1 error1 error2 error4 ug-tests
+test_PROGRAMS = simple1 permissions1 permissions2 permissions3 owner1 owner2   \
+       owner3 directory1 missing-directory1 error1 error2 error4 ug-tests      \
+       ascii-file
 
 if ENABLE_BTRFS
 test_PROGRAMS += test-btrfsutils
@@ -51,5 +52,7 @@ test_btrfsutils_SOURCES = test-btrfsutils.cc
 
 ug_tests_SOURCES = ug-tests.cc
 
+ascii_file_SOURCES = ascii-file.cc
+
 EXTRA_DIST = $(test_DATA) $(test_SCRIPTS)
 
diff --git a/testsuite-real/ascii-file.cc b/testsuite-real/ascii-file.cc
new file mode 100644 (file)
index 0000000..e259b61
--- /dev/null
@@ -0,0 +1,113 @@
+
+#include <fcntl.h>
+#include <iostream>
+#include <vector>
+
+#include <snapper/AsciiFile.h>
+
+using namespace std;
+using namespace snapper;
+
+
+int
+main()
+{
+    vector<string> lines;
+
+    /***** AsciiFileReader *****/
+
+    try
+    {
+       Compression compression = Compression::GZIP;
+
+       string name = add_extension(compression, "big.txt");
+
+#if 1
+
+       int fd = open(name.c_str(), O_RDONLY | O_CLOEXEC | O_LARGEFILE);
+       if (fd < 0)
+           SN_THROW(Exception("open '" + name + "' for reading failed"));
+
+       AsciiFileReader tmp(fd, compression);
+
+#elif 1
+
+       FILE* file = fopen(name.c_str(), "re");
+       if (!file)
+           SN_THROW(Exception("fopen '" + name + "' for reading failed"));
+
+       AsciiFileReader tmp(file, compression);
+
+#else
+
+       AsciiFileReader tmp(name, compression);
+
+#endif
+
+       string line;
+
+       while (tmp.read_line(line))
+           lines.push_back(line);
+
+#if 1
+       tmp.close();
+#endif
+    }
+    catch (const Exception& e)
+    {
+       SN_CAUGHT(e);
+
+       cerr << e.what() << '\n';
+    }
+
+#if 0
+
+    for (const string& line : lines)
+       cout << line << '\n';
+
+#endif
+
+    /***** AsciiFileWriter *****/
+
+    try
+    {
+       Compression compression = Compression::GZIP;
+
+       string name = add_extension(compression, "tmp.txt");
+
+#if 1
+
+       int fd = open(name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_LARGEFILE, 0666);
+       if (fd < 0)
+           SN_THROW(Exception("open '" + name + "' for writing failed"));
+
+       AsciiFileWriter tmp(fd, compression);
+
+#elif 1
+
+       FILE* file = fopen(name.c_str(), "we");
+       if (!file)
+           SN_THROW(Exception("fopen '" + name + "' for writing failed"));
+
+       AsciiFileWriter tmp(file, compression);
+
+#else
+
+       AsciiFileWriter tmp(name, compression);
+
+#endif
+
+       for (const string& line : lines)
+           tmp.write_line(line);
+
+#if 1
+       tmp.close();
+#endif
+    }
+    catch (const Exception& e)
+    {
+       SN_CAUGHT(e);
+
+       cerr << e.what() << '\n';
+    }
+}
index 8fcbaadf5c6a062e919cf48fda6ea1f780b3f7cc..3d148d97c9cba1f7c9d33f6f7b1bfab6a200848f 100644 (file)
@@ -16,41 +16,42 @@ BOOST_AUTO_TEST_CASE(sysconfig_get1)
 
     string tmp_string;
 
-    BOOST_CHECK(s.getValue("S1", tmp_string));
+    BOOST_CHECK(s.get_value("S1", tmp_string));
     BOOST_CHECK_EQUAL(tmp_string, "hello");
 
-    BOOST_CHECK(s.getValue("S2", tmp_string));
+    BOOST_CHECK(s.get_value("S2", tmp_string));
     BOOST_CHECK_EQUAL(tmp_string, "hello");
 
     bool tmp_bool;
 
-    BOOST_CHECK(s.getValue("B1", tmp_bool));
+    BOOST_CHECK(s.get_value("B1", tmp_bool));
     BOOST_CHECK_EQUAL(tmp_bool, true);
 
-    BOOST_CHECK(s.getValue("B2", tmp_bool));
+    BOOST_CHECK(s.get_value("B2", tmp_bool));
     BOOST_CHECK_EQUAL(tmp_bool, false);
 
     vector<string> tmp_vector;
 
-    BOOST_CHECK(s.getValue("V1", tmp_vector));
+    BOOST_CHECK(s.get_value("V1", tmp_vector));
     BOOST_CHECK_EQUAL(boost::join(tmp_vector, "-"), "one word");
-    BOOST_CHECK_EQUAL(s.getAllValues()["V1"], "one\\ word");
+    BOOST_CHECK_EQUAL(s.get_all_values()["V1"], "one\\ word");
 
-    BOOST_CHECK(s.getValue("V2", tmp_vector));
+    BOOST_CHECK(s.get_value("V2", tmp_vector));
     BOOST_CHECK_EQUAL(boost::join(tmp_vector, "-"), "two-words");
 
-    BOOST_CHECK(s.getValue("V3", tmp_vector));
+    BOOST_CHECK(s.get_value("V3", tmp_vector));
     BOOST_CHECK_EQUAL(boost::join(tmp_vector, "-"), "now-three-words");
 
-    BOOST_CHECK(s.getValue("V4", tmp_vector));
+    BOOST_CHECK(s.get_value("V4", tmp_vector));
     BOOST_CHECK_EQUAL(boost::join(tmp_vector, "-"), "c:\\io.sys");
 
-    BOOST_CHECK(!s.getValue("V5", tmp_vector));
+    BOOST_CHECK(!s.get_value("V5", tmp_vector));
 
-    BOOST_CHECK(s.getValue("V6", tmp_vector));
+    BOOST_CHECK(s.get_value("V6", tmp_vector));
     BOOST_CHECK_EQUAL(boost::join(tmp_vector, "-"), "a-value-with-a-#-hash");
 }
 
+
 BOOST_AUTO_TEST_CASE(sysconfig_set1)
 {
     system("cp sysconfig-set1.txt sysconfig-set1.txt.tmp");
@@ -58,15 +59,15 @@ BOOST_AUTO_TEST_CASE(sysconfig_set1)
 
     string tmp_string;
 
-    BOOST_CHECK(s.getValue("K2", tmp_string));
+    BOOST_CHECK(s.get_value("K2", tmp_string));
     BOOST_CHECK_EQUAL(tmp_string, "changeme");
 
-    s.setValue("K2", "all new");
-    BOOST_CHECK(s.getValue("K2", tmp_string));
+    s.set_value("K2", "all new");
+    BOOST_CHECK(s.get_value("K2", tmp_string));
     BOOST_CHECK_EQUAL(tmp_string, "all new");
 
-    s.setValue("K2", "changeme");
-    BOOST_CHECK(s.getValue("K2", tmp_string));
+    s.set_value("K2", "changeme");
+    BOOST_CHECK(s.get_value("K2", tmp_string));
     BOOST_CHECK_EQUAL(tmp_string, "changeme");
 
     s.save();