From e5fd6527d8c475c22aee595369ba1a5c86d009ea Mon Sep 17 00:00:00 2001 From: Arvin Schnell Date: Tue, 7 Oct 2025 14:58:15 +0200 Subject: [PATCH] - support sending compressed data in snbk --- client/snbk/BackupConfig.cc | 6 ++- client/snbk/BackupConfig.h | 4 ++ client/snbk/CmdBtrfs.cc | 87 ++++++++++++++++++++++++++++++- client/snbk/CmdBtrfs.h | 41 ++++++++++++++- client/snbk/JsonFile.cc | 24 ++++++++- client/snbk/JsonFile.h | 5 +- client/snbk/TheBigThing.cc | 23 ++++++-- client/snbk/TheBigThing.h | 9 +++- doc/snapper-backup-configs.xml.in | 31 ++++++++++- package/snapper.changes | 6 +++ scripts/snbk-btrfs | 4 ++ 11 files changed, 227 insertions(+), 13 deletions(-) diff --git a/client/snbk/BackupConfig.cc b/client/snbk/BackupConfig.cc index 8ed3a35b..7a819333 100644 --- a/client/snbk/BackupConfig.cc +++ b/client/snbk/BackupConfig.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 SUSE LLC + * Copyright (c) [2024-2025] SUSE LLC * * All Rights Reserved. * @@ -70,6 +70,10 @@ namespace snapper get_child_value(json_file.get_root(), "ssh-identity", ssh_identity); } + get_child_value(json_file.get_root(), "send-compressed-data", send_compressed_data); + get_child_nodes(json_file.get_root(), "send-options", send_options); + get_child_nodes(json_file.get_root(), "receive-options", receive_options); + get_child_value(json_file.get_root(), "target-btrfs-bin", target_btrfs_bin); get_child_value(json_file.get_root(), "target-findmnt-bin", target_findmnt_bin); get_child_value(json_file.get_root(), "target-mkdir-bin", target_mkdir_bin); diff --git a/client/snbk/BackupConfig.h b/client/snbk/BackupConfig.h index 39763a88..935c6d61 100644 --- a/client/snbk/BackupConfig.h +++ b/client/snbk/BackupConfig.h @@ -70,6 +70,10 @@ namespace snapper Shell get_source_shell() const; Shell get_target_shell() const; + bool send_compressed_data = true; + vector send_options; + vector receive_options; + string target_btrfs_bin = BTRFS_BIN; string target_findmnt_bin = FINDMNT_BIN; string target_mkdir_bin = MKDIR_BIN; diff --git a/client/snbk/CmdBtrfs.cc b/client/snbk/CmdBtrfs.cc index f7614593..37db965f 100644 --- a/client/snbk/CmdBtrfs.cc +++ b/client/snbk/CmdBtrfs.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2004-2015] Novell, Inc. - * Copyright (c) [2017-2024] SUSE LLC + * Copyright (c) [2017-2025] SUSE LLC * * All Rights Reserved. * @@ -21,6 +21,7 @@ */ +#include #include #include #include @@ -259,4 +260,88 @@ namespace snapper return s; } + + void + CmdBtrfsVersion::query_version() + { + if (did_set_version) + return; + + SystemCmd::Args cmd_args = { btrfs_bin, "--version" }; + SystemCmd cmd(shellify(shell, cmd_args)); + + if (cmd.retcode() != 0) + { + y2err("command '" << cmd.cmd() << "' failed: " << cmd.retcode()); + for (const string& tmp : cmd.get_stdout()) + y2err(tmp); + for (const string& tmp : cmd.get_stderr()) + y2err(tmp); + + SN_THROW(Exception("'btrfs --version' failed")); + } + + parse_version(cmd.get_stdout()[0]); + } + + + void + CmdBtrfsVersion::parse_version(const string& version) + { + // example versions: "5.14 " (yes, with a trailing space), "6.0", "6.0.2" + const regex version_rx("btrfs-progs v([0-9]+)\\.([0-9]+)(\\.([0-9]+))?( )*", regex::extended); + + smatch match; + + if (!regex_match(version, match, version_rx)) + SN_THROW(Exception("failed to parse btrfs version '" + version + "'")); + + major = stoi(match[1]); + minor = stoi(match[2]); + patchlevel = match[4].length() == 0 ? 0 : stoi(match[4]); + + y2mil("major:" << major << " minor:" << minor << " patchlevel:" << patchlevel); + + did_set_version = true; + } + + + int + CmdBtrfsVersion::supported_proto() + { + query_version(); + + if (major >= 6) + return 2; + + return 1; + } + + + int + Uname::supported_proto() + { + const regex release_rx("^([0-9]+)\\.([0-9]+).*", regex::extended); + + struct utsname buffer; + + if (uname(&buffer) < 0) + SN_THROW(Exception("syscall uname failed")); + + cmatch match; + + if (!regex_match(buffer.release, match, release_rx)) + SN_THROW(Exception("failed to parse uname release '" + string(buffer.release) + "'")); + + int major = stoi(match[1]); + int minor = stoi(match[2]); + + y2mil("major:" << major << " minor:" << minor); + + if (major >= 6) + return 2; + + return 1; + } + } diff --git a/client/snbk/CmdBtrfs.h b/client/snbk/CmdBtrfs.h index 86a7e558..61e0243e 100644 --- a/client/snbk/CmdBtrfs.h +++ b/client/snbk/CmdBtrfs.h @@ -1,6 +1,6 @@ /* * Copyright (c) [2004-2015] Novell, Inc. - * Copyright (c) [2017-2024] SUSE LLC + * Copyright (c) [2017-2025] SUSE LLC * * All Rights Reserved. * @@ -116,6 +116,45 @@ namespace snapper }; + + /** + * Lazy query of btrfs command version. + */ + class CmdBtrfsVersion + { + public: + + CmdBtrfsVersion(const string& btrfs_bin, const Shell& shell) + : btrfs_bin(btrfs_bin), shell(shell) + {} + + int supported_proto(); + + private: + + void query_version(); + void parse_version(const string& version); + + const string btrfs_bin; + const Shell shell; + + bool did_set_version = false; + + int major = 0; + int minor = 0; + int patchlevel = 0; + + }; + + + class Uname + { + public: + + static int supported_proto(); + + }; + } #endif diff --git a/client/snbk/JsonFile.cc b/client/snbk/JsonFile.cc index 26f776f2..0a40d6df 100644 --- a/client/snbk/JsonFile.cc +++ b/client/snbk/JsonFile.cc @@ -20,7 +20,7 @@ */ -#include +#include #include #include #include @@ -187,6 +187,28 @@ namespace snapper } + bool + get_child_nodes(json_object* parent, const char* name, vector& values) + { + vector children; + + if (!get_child_nodes(parent, name, children)) + return false; + + values.clear(); + + for (json_object* child : children) + { + if (!json_object_is_type(child, json_type_string)) + return false; + + values.push_back(json_object_get_string(child)); + } + + return true; + } + + bool get_child_nodes(json_object* parent, const char* name, vector& children) { diff --git a/client/snbk/JsonFile.h b/client/snbk/JsonFile.h index 23eab9cf..d2340f98 100644 --- a/client/snbk/JsonFile.h +++ b/client/snbk/JsonFile.h @@ -1,5 +1,5 @@ /* - * Copyright (c) [2017-2024] SUSE LLC + * Copyright (c) [2017-2025] SUSE LLC * * All Rights Reserved. * @@ -68,6 +68,9 @@ namespace snapper get_child_value(json_object* parent, const char* name, Type& value); + bool + get_child_nodes(json_object* parent, const char* name, vector& values); + bool get_child_nodes(json_object* parent, const char* name, vector& children); diff --git a/client/snbk/TheBigThing.cc b/client/snbk/TheBigThing.cc index 0ccc6206..3c57fed0 100644 --- a/client/snbk/TheBigThing.cc +++ b/client/snbk/TheBigThing.cc @@ -57,7 +57,7 @@ namespace snapper void - TheBigThing::transfer(const BackupConfig& backup_config, const TheBigThings& the_big_things, + TheBigThing::transfer(const BackupConfig& backup_config, TheBigThings& the_big_things, bool quiet) { if (!quiet) @@ -137,16 +137,29 @@ namespace snapper // Copy snapshot to target. + const int proto = std::min({ + Uname::supported_proto(), + the_big_things.source_btrfs_version.supported_proto(), + the_big_things.target_btrfs_version.supported_proto() + }); + TheBigThings::const_iterator it1 = the_big_things.find_send_parent(*this); SystemCmd::Args cmd3a_args = { BTRFS_BIN, "send" }; + if (proto >= 2) + cmd3a_args << "--proto" << to_string(proto); + if (backup_config.send_compressed_data && proto >= 2) + cmd3a_args << "--compressed-data"; + cmd3a_args << backup_config.send_options; + if (it1 != the_big_things.end()) cmd3a_args << "-p" << backup_config.source_path + "/" SNAPSHOTS_NAME "/" + to_string(it1->num) + "/" SNAPSHOT_NAME; cmd3a_args << "--" << backup_config.source_path + "/" SNAPSHOTS_NAME "/" + num_string + "/" SNAPSHOT_NAME; - SystemCmd::Args cmd3b_args = { backup_config.target_btrfs_bin, "receive", "--", - backup_config.target_path + "/" + num_string }; + SystemCmd::Args cmd3b_args = { backup_config.target_btrfs_bin, "receive" }; + cmd3b_args << backup_config.receive_options; + cmd3b_args << "--" << backup_config.target_path + "/" + num_string; y2deb("source: " << cmd3a_args.get_values()); y2deb("target: " << cmd3b_args.get_values()); @@ -238,7 +251,9 @@ namespace snapper TheBigThings::TheBigThings(const BackupConfig& backup_config, ProxySnappers* snappers, bool verbose) - : snapper(snappers->getSnapper(backup_config.config)), locker(snapper) + : source_btrfs_version(BTRFS_BIN, backup_config.get_source_shell()), + target_btrfs_version(backup_config.target_btrfs_bin, backup_config.get_target_shell()), + snapper(snappers->getSnapper(backup_config.config)), locker(snapper) { if (backup_config.source_path != snapper->getConfig().getSubvolume()) { diff --git a/client/snbk/TheBigThing.h b/client/snbk/TheBigThing.h index 766d6af9..6a961c07 100644 --- a/client/snbk/TheBigThing.h +++ b/client/snbk/TheBigThing.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 SUSE LLC + * Copyright (c) [2024-2025] SUSE LLC * * All Rights Reserved. * @@ -29,6 +29,8 @@ #include "../proxy/proxy.h" #include "../proxy/locker.h" +#include "CmdBtrfs.h" + namespace snapper { @@ -52,7 +54,7 @@ namespace snapper TheBigThing(unsigned int num) : num(num) {} - void transfer(const BackupConfig& backup_config, const TheBigThings& the_big_things, bool quiet); + void transfer(const BackupConfig& backup_config, TheBigThings& the_big_things, bool quiet); void remove(const BackupConfig& backup_config, bool quiet); @@ -110,6 +112,9 @@ namespace snapper */ const_iterator find_send_parent(const TheBigThing& the_big_thing) const; + CmdBtrfsVersion source_btrfs_version; + CmdBtrfsVersion target_btrfs_version; + private: const ProxySnapper* snapper; diff --git a/doc/snapper-backup-configs.xml.in b/doc/snapper-backup-configs.xml.in index adae5cd1..7a6e202f 100644 --- a/doc/snapper-backup-configs.xml.in +++ b/doc/snapper-backup-configs.xml.in @@ -2,13 +2,13 @@ - 2025-04-09 + 2025-10-07 snapper-backup-configs 5 - 2025-04-09 + 2025-10-07 @VERSION@ Filesystem Snapshot Management @@ -102,6 +102,33 @@ + + + + Per default snbk will add the option --compressed-data iff + supported by the source and target system. This can be disable by + setting this value to false. + + + + + + + Extra options for the btrfs send command as an array + of strings. Optional. + Per default snbk automatically adds --proto and --compressed-data + iff supported by both the source and target. + + + + + + + Extra options for the btrfs receive command as an + array of strings. Optional. + + + diff --git a/package/snapper.changes b/package/snapper.changes index 6a9d331b..ee7331f5 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Oct 07 14:42:16 CEST 2025 - aschnell@suse.com + +- support sending compressed data in snbk + (gh#openSUSE/snapper#1059) + ------------------------------------------------------------------- Wed Sep 24 16:12:34 CEST 2025 - aschnell@suse.com diff --git a/scripts/snbk-btrfs b/scripts/snbk-btrfs index 39e816a3..1c175bf4 100755 --- a/scripts/snbk-btrfs +++ b/scripts/snbk-btrfs @@ -1,5 +1,9 @@ #!/bin/bash +if [ "$#" -eq 1 ] && [ "$1" == "--version" ]; then + exec sudo /usr/sbin/btrfs --version +fi + if [ "$#" -lt 3 ]; then echo "Usage: $0 [subcommand] [options] -- /backup/..." exit 1 -- 2.47.3