From 9a623ab065da3e148c3e4c3ed9a4d22976bc370f Mon Sep 17 00:00:00 2001 From: Arvin Schnell Date: Thu, 21 Apr 2022 12:31:37 +0200 Subject: [PATCH] - compress file lists using gzip --- LIBVERSION | 2 +- VERSION | 2 +- client/MyFiles.cc | 4 +- client/installation-helper.cc | 25 +- client/proxy-lib.cc | 4 +- dists/debian/changelog | 12 + doc/snapper-configs.xml.in | 16 +- examples/c++-lib/ListAll.cc | 2 +- package/snapper.changes | 6 + server/Client.cc | 2 +- server/MetaSnapper.cc | 8 +- server/MetaSnapper.h | 2 +- server/Types.cc | 2 +- snapper.spec.in | 1 + snapper/AppUtil.h | 6 +- snapper/AsciiFile.cc | 827 ++++++++++++++++++++++++++++++---- snapper/AsciiFile.h | 126 ++++-- snapper/Btrfs.cc | 2 +- snapper/Comparison.cc | 184 ++++++-- snapper/Comparison.h | 23 +- snapper/ComparisonImpl.cc | 54 +++ snapper/ComparisonImpl.h | 37 ++ snapper/Exception.h | 5 +- snapper/File.cc | 8 + snapper/File.h | 47 +- snapper/Filesystem.cc | 4 +- snapper/Makefile.am | 4 +- snapper/Regex.cc | 103 ----- snapper/Regex.h | 78 ---- snapper/Selinux.cc | 8 +- snapper/Selinux.h | 12 +- snapper/Snapper.cc | 84 ++-- snapper/Snapper.h | 22 +- snapper/SnapperDefines.h | 3 +- snapper/Snapshot.cc | 36 +- snapper/Snapshot.h | 25 +- testsuite-real/.gitignore | 1 + testsuite-real/CAUTION | 2 +- testsuite-real/Makefile.am | 7 +- testsuite-real/ascii-file.cc | 113 +++++ testsuite/sysconfig-get1.cc | 33 +- 41 files changed, 1449 insertions(+), 493 deletions(-) create mode 100644 snapper/ComparisonImpl.cc create mode 100644 snapper/ComparisonImpl.h delete mode 100644 snapper/Regex.cc delete mode 100644 snapper/Regex.h create mode 100644 testsuite-real/ascii-file.cc diff --git a/LIBVERSION b/LIBVERSION index 03f488b0..09b254e9 100644 --- a/LIBVERSION +++ b/LIBVERSION @@ -1 +1 @@ -5.3.0 +6.0.0 diff --git a/VERSION b/VERSION index 78bc1abd..57121573 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.10.0 +0.10.1 diff --git a/client/MyFiles.cc b/client/MyFiles.cc index 2a19a8ae..9fc8d375 100644 --- a/client/MyFiles.cc +++ b/client/MyFiles.cc @@ -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; diff --git a/client/installation-helper.cc b/client/installation-helper.cc index db0a2a24..e3eed4fe 100644 --- a/client/installation-helper.cc +++ b/client/installation-helper.cc @@ -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("/", ""); diff --git a/client/proxy-lib.cc b/client/proxy-lib.cc index 11f9cee0..b7981e13 100644 --- a/client/proxy-lib.cc +++ b/client/proxy-lib.cc @@ -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 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; } diff --git a/dists/debian/changelog b/dists/debian/changelog index 1e5b42af..84ad483c 100644 --- a/dists/debian/changelog +++ b/dists/debian/changelog @@ -1,3 +1,15 @@ +snapper (0.10.1) stable; urgency=low + + * Updated to version 0.10.1 + + -- Arvin Schnell Thu, 21 Apr 2022 09:35:06 +0000 + +snapper (0.10.0) stable; urgency=low + + * Updated to version 0.10.0 + + -- Arvin Schnell Mon, 21 Mar 2022 17:29:07 +0000 + snapper (0.9.1) stable; urgency=low * Updated to version 0.9.1 diff --git a/doc/snapper-configs.xml.in b/doc/snapper-configs.xml.in index af5d9aef..97373957 100644 --- a/doc/snapper-configs.xml.in +++ b/doc/snapper-configs.xml.in @@ -2,13 +2,13 @@ - 2021-04-07 + 2022-04-21 snapper-configs 5 - 2021-04-07 + 2022-04-21 @VERSION@ Filesystem Snapshot Management @@ -134,6 +134,18 @@ + + + + Defines the compression algorithm used for saving file + list. Allowed values are "none" and + "gzip". Depending on the installed libraries, some + compression algorithms might not be available. + Default value is "gzip". + New in version 0.10.1. + + + diff --git a/examples/c++-lib/ListAll.cc b/examples/c++-lib/ListAll.cc index 4d690de9..c320c26e 100644 --- a/examples/c++-lib/ListAll.cc +++ b/examples/c++-lib/ListAll.cc @@ -15,7 +15,7 @@ main(int argc, char** argv) list sh; for (list::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::const_iterator it = sh.begin(); it != sh.end(); ++it) cout << (*it)->configName() << " " << (*it)->subvolumeDir() << " " diff --git a/package/snapper.changes b/package/snapper.changes index 67a0e7cf..8cf8ec06 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -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 diff --git a/server/Client.cc b/server/Client.cc index 79e0cc5e..e8a87858 100644 --- a/server/Client.cc +++ b/server/Client.cc @@ -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); diff --git a/server/MetaSnapper.cc b/server/MetaSnapper.cc index fcf0027e..bd3e7611 100644 --- a/server/MetaSnapper.cc +++ b/server/MetaSnapper.cc @@ -53,7 +53,7 @@ void MetaSnapper::setConfigInfo(const map& raw) { for (map::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 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 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(); diff --git a/server/MetaSnapper.h b/server/MetaSnapper.h index 4e47fd7f..36053b40 100644 --- a/server/MetaSnapper.h +++ b/server/MetaSnapper.h @@ -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& raw); diff --git a/server/Types.cc b/server/Types.cc index 47f7f05d..95db31df 100644 --- a/server/Types.cc +++ b/server/Types.cc @@ -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; } diff --git a/snapper.spec.in b/snapper.spec.in index 8fc3d6e5..e8385b44 100644 --- a/snapper.spec.in +++ b/snapper.spec.in @@ -95,6 +95,7 @@ BuildRequires: json-c-devel %else BuildRequires: libjson-c-devel %endif +BuildRequires: zlib-devel %if %{with coverage} BuildRequires: lcov %endif diff --git a/snapper/AppUtil.h b/snapper/AppUtil.h index ccde04dd..c136c1f0 100644 --- a/snapper/AppUtil.h +++ b/snapper/AppUtil.h @@ -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; }; diff --git a/snapper/AsciiFile.cc b/snapper/AsciiFile.cc index bc141d4d..9bcde827 100644 --- a/snapper/AsciiFile.cc +++ b/snapper/AsciiFile.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2004-2015] Novell, Inc. - * Copyright (c) 2020 SUSE LLC + * Copyright (c) [2020-2022] SUSE LLC * * All Rights Reserved. * @@ -22,13 +22,14 @@ #include -#include +#include +#include #include +#include #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 + static std::unique_ptr 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 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 + std::unique_ptr + AsciiFileReader::Impl::factory(T t, Compression compression) + { + switch (compression) + { + case Compression::NONE: + return unique_ptr(new Impl::None(t)); + + case Compression::GZIP: + return unique_ptr(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 + static std::unique_ptr 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 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 + std::unique_ptr + AsciiFileWriter::Impl::factory(T t, Compression compression) + { + switch (compression) + { + case Compression::NONE: + return unique_ptr(new Impl::None(t)); + + case Compression::GZIP: + return unique_ptr(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::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() ? "" : 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() ? "" : Name_C)); - for (vector::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::iterator it = Lines_C.begin(); it != Lines_C.end(); ++it) + for (vector::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& values) + SysconfigFile::set_value(const string& key, const vector& values) { string tmp; for (vector::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& values) const + SysconfigFile::get_value(const string& key, vector& values) const { string tmp; - if (!getValue(key, tmp)) + if (!get_value(key, tmp)) return false; values.clear(); @@ -302,11 +951,11 @@ AsciiFile::save() map - SysconfigFile::getAllValues() const + SysconfigFile::get_all_values() const { map ret; - for (const string& line : Lines_C) + for (const string& line : lines) { ParsedLine parsed_line; diff --git a/snapper/AsciiFile.h b/snapper/AsciiFile.h index 218f92e9..cf1ecc96 100644 --- a/snapper/AsciiFile.h +++ b/snapper/AsciiFile.h @@ -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 #include #include +#include #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; + + }; + + + 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; }; @@ -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& lines() { return Lines_C; } - const vector& lines() const { return Lines_C; } + void clear() { lines.clear(); } + + void push_back(const string& line) { lines.push_back(line); } + + vector& get_lines() { return lines; } + const vector& get_lines() const { return lines; } protected: - vector Lines_C; + vector 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& values); - bool getValue(const string& key, vector& values) const; + virtual void set_value(const string& key, const vector& values); + bool get_value(const string& key, vector& values) const; - map getAllValues() const; + map get_all_values() const; private: - bool modified; + bool modified = false; struct ParsedLine { diff --git a/snapper/Btrfs.cc b/snapper/Btrfs.cc index 0a710cff..02bf167f 100644 --- a/snapper/Btrfs.cc +++ b/snapper/Btrfs.cc @@ -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 { diff --git a/snapper/Comparison.cc b/snapper/Comparison.cc index 08081572..ef3a273a 100644 --- a/snapper/Comparison.cc +++ b/snapper/Comparison.cc @@ -1,5 +1,6 @@ /* * Copyright (c) [2011-2014] Novell, Inc. + * Copyright (c) 2022 SUSE LLC * * All Rights Reserved. * @@ -27,6 +28,7 @@ #include #include #include +#include #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; } diff --git a/snapper/Comparison.h b/snapper/Comparison.h index a21e3e6c..879fb501 100644 --- a/snapper/Comparison.h +++ b/snapper/Comparison.h @@ -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 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 index 00000000..aa9832b4 --- /dev/null +++ b/snapper/ComparisonImpl.cc @@ -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 +#include + +#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 index 00000000..d1104adc --- /dev/null +++ b/snapper/ComparisonImpl.h @@ -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 diff --git a/snapper/Exception.h b/snapper/Exception.h index fb63b882..a9be4192 100644 --- a/snapper/Exception.h +++ b/snapper/Exception.h @@ -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 diff --git a/snapper/File.cc b/snapper/File.cc index 17a4aab4..24bf610d 100644 --- a/snapper/File.cc +++ b/snapper/File.cc @@ -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) { diff --git a/snapper/File.h b/snapper/File.h index ade271b7..e1da2753 100644 --- a/snapper/File.h +++ b/snapper/File.h @@ -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& ignore_patterns); + void sort(); const FilePaths* file_paths; diff --git a/snapper/Filesystem.cc b/snapper/Filesystem.cc index 52ca97fa..30164d19 100644 --- a/snapper/Filesystem.cc +++ b/snapper/Filesystem.cc @@ -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); diff --git a/snapper/Makefile.am b/snapper/Makefile.am index 85982ae5..c465ca21 100644 --- a/snapper/Makefile.am +++ b/snapper/Makefile.am @@ -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 index 26962c1d..00000000 --- a/snapper/Regex.cc +++ /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 -#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 index cd476865..00000000 --- a/snapper/Regex.h +++ /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 -#include -#include - - -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 diff --git a/snapper/Selinux.cc b/snapper/Selinux.cc index 36a8b237..61c1de42 100644 --- a/snapper/Selinux.cc +++ b/snapper/Selinux.cc @@ -33,16 +33,15 @@ namespace snapper { SnapperContexts::SnapperContexts() - : subvolume_ctx(NULL) { std::map 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")); } diff --git a/snapper/Selinux.h b/snapper/Selinux.h index e4e3549f..2e5d806b 100644 --- a/snapper/Selinux.h +++ b/snapper/Selinux.h @@ -33,16 +33,16 @@ #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(); diff --git a/snapper/Snapper.cc b/snapper/Snapper.cc index ceacef6c..61389f5b 100644 --- a/snapper/Snapper.cc +++ b/snapper/Snapper.cc @@ -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 config_names; - sysconfig.getValue("SNAPPER_CONFIGS", config_names); + sysconfig.get_value("SNAPPER_CONFIGS", config_names); for (vector::const_iterator it = config_names.begin(); it != config_names.end(); ++it) { @@ -346,7 +350,7 @@ namespace snapper list configs = getConfigs(root_prefix); for (list::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 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 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 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& raw) { for (map::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 uids; vector users; - if (config_info->getValue(KEY_ALLOW_USERS, users)) + if (config_info->get_value(KEY_ALLOW_USERS, users)) { for (vector::const_iterator it = users.begin(); it != users.end(); ++it) { @@ -534,7 +538,7 @@ namespace snapper vector gids; vector groups; - if (config_info->getValue(KEY_ALLOW_GROUPS, groups)) + if (config_info->get_value(KEY_ALLOW_GROUPS, groups)) { for (vector::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 info_content = info_dir.entries(); + vector info_content = info_dir.entries(is_filelist_file); for (vector::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() { diff --git a/snapper/Snapper.h b/snapper/Snapper.h index 8602eb98..3a29d981 100644 --- a/snapper/Snapper.h +++ b/snapper/Snapper.h @@ -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 ignore_patterns; Snapshots snapshots; - SelinuxLabelHandle* selabel_handle; + SelinuxLabelHandle* selabel_handle = nullptr; }; diff --git a/snapper/SnapperDefines.h b/snapper/SnapperDefines.h index c43036c6..46f0c6f1 100644 --- a/snapper/SnapperDefines.h +++ b/snapper/SnapperDefines.h @@ -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 diff --git a/snapper/Snapshot.cc b/snapper/Snapshot.cc index f63c5461..941bc772 100644 --- a/snapper/Snapshot.cc +++ b/snapper/Snapshot.cc @@ -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 #include #include -#include -#include #include #include #include @@ -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 tmp1 = info_dir.entries(is_filelist_file); - for (vector::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); } } diff --git a/snapper/Snapshot.h b/snapper/Snapshot.h index 887967aa..072ad2e8 100644 --- a/snapper/Snapshot.h +++ b/snapper/Snapshot.h @@ -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 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 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::iterator iterator; typedef list::const_iterator const_iterator; diff --git a/testsuite-real/.gitignore b/testsuite-real/.gitignore index b333f30f..81ef1ae7 100644 --- a/testsuite-real/.gitignore +++ b/testsuite-real/.gitignore @@ -1,4 +1,5 @@ *.o +ascii-file simple1 permissions1 permissions2 diff --git a/testsuite-real/CAUTION b/testsuite-real/CAUTION index 25c61065..e339a448 100644 --- a/testsuite-real/CAUTION +++ b/testsuite-real/CAUTION @@ -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`) diff --git a/testsuite-real/Makefile.am b/testsuite-real/Makefile.am index ceb08626..a3bb2e3e 100644 --- a/testsuite-real/Makefile.am +++ b/testsuite-real/Makefile.am @@ -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 index 00000000..e259b610 --- /dev/null +++ b/testsuite-real/ascii-file.cc @@ -0,0 +1,113 @@ + +#include +#include +#include + +#include + +using namespace std; +using namespace snapper; + + +int +main() +{ + vector 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'; + } +} diff --git a/testsuite/sysconfig-get1.cc b/testsuite/sysconfig-get1.cc index 8fcbaadf..3d148d97 100644 --- a/testsuite/sysconfig-get1.cc +++ b/testsuite/sysconfig-get1.cc @@ -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 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(); -- 2.47.3