From: Arvin Schnell Date: Wed, 14 Feb 2024 08:15:40 +0000 (+0100) Subject: - added experimental bcachefs support X-Git-Tag: v0.11.0~47^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9219cc5b8b068a3296fa095086126388fd647151;p=thirdparty%2Fsnapper.git - added experimental bcachefs support --- diff --git a/LIBVERSION b/LIBVERSION index ba7f754d..815da58b 100644 --- a/LIBVERSION +++ b/LIBVERSION @@ -1 +1 @@ -7.4.0 +7.4.1 diff --git a/configure.ac b/configure.ac index fbb0e103..b5fe5f4d 100644 --- a/configure.ac +++ b/configure.ac @@ -77,6 +77,14 @@ if test "x$with_btrfs" = "xyes"; then AC_DEFINE(ENABLE_BTRFS, 1, [Enable Btrfs internal snapshots support]) fi +AC_ARG_ENABLE([bcachefs], AS_HELP_STRING([--disable-bcachefs], [Disable Bcachefs internal snapshots support]), + [with_bcachefs=$enableval], [with_bcachefs=yes]) +AM_CONDITIONAL(ENABLE_BCACHEFS, [test "x$with_bcachefs" = "xyes"]) + +if test "x$with_bcachefs" = "xyes"; then + AC_DEFINE(ENABLE_BCACHEFS, 1, [Enable Bcachefs internal snapshots support]) +fi + AC_ARG_ENABLE([ext4], AS_HELP_STRING([--disable-ext4], [Disable ext4 snapshots support]), [with_ext4=$enableval], [with_ext4=yes]) AM_CONDITIONAL(ENABLE_EXT4, [test "x$with_ext4" = "xyes"]) diff --git a/dists/debian/rules b/dists/debian/rules index 8b3f0843..a58f42ea 100644 --- a/dists/debian/rules +++ b/dists/debian/rules @@ -16,8 +16,8 @@ endif override_dh_auto_configure: dh_auto_configure -- --docdir=/usr/share/doc/packages/snapper --disable-silent-rules \ - --disable-ext4 --enable-xattrs --disable-rollback --disable-btrfs-quota \ - --with-pam-security=/lib/security + --disable-bcachefs --disable-ext4 --enable-xattrs --disable-rollback \ + --disable-btrfs-quota --with-pam-security=/lib/security override_dh_auto_install: dh_auto_install diff --git a/doc/bcachefs.txt b/doc/bcachefs.txt new file mode 100644 index 00000000..b1538f29 --- /dev/null +++ b/doc/bcachefs.txt @@ -0,0 +1,14 @@ + +bcachefs support is experimental + +TODOs (at least): + +- ioctl for snapshot creation takes path instead of fd + +- read-only snapshots not supported by bcachefs + - background comparison started anyway + +- check if a directory is a snapshot + +- get ioctl defines from a header file or use a library + diff --git a/package/snapper.changes b/package/snapper.changes index 3d727e35..03a848c4 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Feb 14 09:14:32 CET 2024 - aschnell@suse.com + +- added experimental bcachefs support (gh#openSUSE/snapper#858) + ------------------------------------------------------------------- Wed Feb 07 11:16:40 CET 2024 - aschnell@suse.com diff --git a/server/Client.cc b/server/Client.cc index 15cba3ee..539d9145 100644 --- a/server/Client.cc +++ b/server/Client.cc @@ -1081,7 +1081,11 @@ Client::create_post_snapshot(DBus::Connection& conn, DBus::Message& msg) bool background_comparison = true; it->getConfigInfo().get_value("BACKGROUND_COMPARISON", background_comparison); if (background_comparison) - clients.backgrounds().add_task(it, snap1, snap2); + { + // TODO isReadOnly is wrong if read-only is not supported by file system + if (snap1->isReadOnly() && snap2->isReadOnly()) + clients.backgrounds().add_task(it, snap1, snap2); + } DBus::MessageMethodReturn reply(msg); diff --git a/snapper.spec.in b/snapper.spec.in index be79b219..7a787ede 100644 --- a/snapper.spec.in +++ b/snapper.spec.in @@ -143,7 +143,7 @@ autoreconf -fvi --disable-btrfs-quota \ %endif %{?with_selinux:--enable-selinux} \ - --disable-silent-rules --disable-ext4 + --disable-silent-rules --disable-bcachefs --disable-ext4 make %{?_smp_mflags} %install diff --git a/snapper/Bcachefs.cc b/snapper/Bcachefs.cc new file mode 100644 index 00000000..19e5bf3c --- /dev/null +++ b/snapper/Bcachefs.cc @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2024 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 "config.h" + +#include +#include +#include +#include +#include + +#include "snapper/Log.h" +#include "snapper/Bcachefs.h" +#include "snapper/BcachefsUtils.h" +#include "snapper/File.h" +#include "snapper/Snapper.h" +#include "snapper/SnapperTmpl.h" + +#include "snapper/Acls.h" +#include "snapper/Exception.h" +#ifdef ENABLE_SELINUX +#include "snapper/Selinux.h" +#endif + + +namespace snapper +{ + using namespace std; + + using namespace BcachefsUtils; + + + Filesystem* + Bcachefs::create(const string& fstype, const string& subvolume, const string& root_prefix) + { + if (fstype == "bcachefs") + return new Bcachefs(subvolume, root_prefix); + + return nullptr; + } + + + Bcachefs::Bcachefs(const string& subvolume, const string& root_prefix) + : Filesystem(subvolume, root_prefix) + { + } + + + void + Bcachefs::createConfig() const + { + SDir subvolume_dir = openSubvolumeDir(); + + try + { + create_subvolume(subvolume_dir.fd(), ".snapshots"); + } + catch (const runtime_error_with_errno& e) + { + y2err("create subvolume failed, " << e.what()); + + switch (e.error_number) + { + case EEXIST: + SN_THROW(CreateConfigFailedException("creating bcachefs subvolume .snapshots failed " + "since it already exists")); + break; + + default: + SN_THROW(CreateConfigFailedException("creating bcachefs subvolume .snapshots failed")); + } + } + + SFile x(subvolume_dir, ".snapshots"); + +#ifdef ENABLE_SELINUX + try + { + SnapperContexts scontexts; + + x.fsetfilecon(scontexts.subvolume_context()); + } + catch (const SelinuxException& e) + { + SN_CAUGHT(e); + // fall through intentional + } +#endif + + struct stat stat; + if (x.stat(&stat, 0) == 0) + x.chmod(stat.st_mode & ~0027, 0); + } + + + void + Bcachefs::deleteConfig() const + { + SDir subvolume_dir = openSubvolumeDir(); + + try + { + delete_subvolume(subvolume_dir.fd(), ".snapshots"); + } + catch (const runtime_error& e) + { + y2err("delete subvolume failed, " << e.what()); + SN_THROW(DeleteConfigFailedException("deleting bcachefs snapshot failed")); + } + } + + + string + Bcachefs::snapshotDir(unsigned int num) const + { + return (subvolume == "/" ? "" : subvolume) + "/.snapshots/" + decString(num) + + "/snapshot"; + } + + + SDir + Bcachefs::openSubvolumeDir() const + { + SDir subvolume_dir = Filesystem::openSubvolumeDir(); + + struct stat stat; + if (subvolume_dir.stat(&stat) != 0) + { + SN_THROW(IOErrorException("stat on subvolume directory failed")); + } + + if (!is_subvolume(stat)) + { + SN_THROW(IOErrorException("subvolume is not a bcachefs subvolume")); + } + + return subvolume_dir; + } + + + SDir + Bcachefs::openInfosDir() const + { + SDir subvolume_dir = openSubvolumeDir(); + SDir infos_dir(subvolume_dir, ".snapshots"); + + struct stat stat; + if (infos_dir.stat(&stat) != 0) + { + SN_THROW(IOErrorException("stat on info directory failed")); + } + + if (!is_subvolume(stat)) + { + SN_THROW(IOErrorException(".snapshots is not a bcachefs subvolume")); + } + + if (stat.st_uid != 0) + { + y2err(".snapshots must have owner root"); + SN_THROW(IOErrorException(".snapshots must have owner root")); + } + + if (stat.st_gid != 0 && stat.st_mode & S_IWGRP) + { + y2err(".snapshots must have group root or must not be group-writable"); + SN_THROW(IOErrorException(".snapshots must have group root or must not be group-writable")); + } + + if (stat.st_mode & S_IWOTH) + { + y2err(".snapshots must not be world-writable"); + SN_THROW(IOErrorException(".snapshots must not be world-writable")); + } + + return infos_dir; + } + + + SDir + Bcachefs::openSnapshotDir(unsigned int num) const + { + SDir info_dir = openInfoDir(num); + SDir snapshot_dir(info_dir, "snapshot"); + + return snapshot_dir; + } + + + void + Bcachefs::createSnapshot(unsigned int num, unsigned int num_parent, bool read_only, bool quota, + bool empty) const + { + if (num_parent == 0) + { + SDir subvolume_dir = openSubvolumeDir(); + SDir info_dir = openInfoDir(num); + + try + { + if (empty) + create_subvolume(info_dir.fd(), "snapshot"); + else + create_snapshot(subvolume_dir.fd(), subvolume, info_dir.fd(), "snapshot", read_only); + } + catch (const runtime_error& e) + { + y2err("create snapshot failed, " << e.what()); + SN_THROW(CreateSnapshotFailedException()); + } + } + else + { + SDir snapshot_dir = openSnapshotDir(num_parent); + SDir info_dir = openInfoDir(num); + + try + { + create_snapshot(snapshot_dir.fd(), subvolume, info_dir.fd(), "snapshot", read_only); + } + catch (const runtime_error& e) + { + y2err("create snapshot failed, " << e.what()); + SN_THROW(CreateSnapshotFailedException()); + } + } + } + + + void + Bcachefs::deleteSnapshot(unsigned int num) const + { + SDir info_dir = openInfoDir(num); + + try + { + delete_subvolume(info_dir.fd(), "snapshot"); + } + catch (const runtime_error& e) + { + y2err("delete snapshot failed, " << e.what()); + SN_THROW(DeleteSnapshotFailedException()); + } + } + + + bool + Bcachefs::isSnapshotMounted(unsigned int num) const + { + return true; + } + + + void + Bcachefs::mountSnapshot(unsigned int num) const + { + } + + + void + Bcachefs::umountSnapshot(unsigned int num) const + { + } + + + bool + Bcachefs::isSnapshotReadOnly(unsigned int num) const + { + SDir snapshot_dir = openSnapshotDir(num); + return is_subvolume_read_only(snapshot_dir.fd()); + } + + + void + Bcachefs::setSnapshotReadOnly(unsigned int num, bool read_only) const + { + SDir snapshot_dir = openSnapshotDir(num); + set_subvolume_read_only(snapshot_dir.fd(), read_only); + } + + + bool + Bcachefs::checkSnapshot(unsigned int num) const + { + try + { + SDir info_dir = openInfoDir(num); + + struct stat stat; + int r = info_dir.stat("snapshot", &stat, AT_SYMLINK_NOFOLLOW); + return r == 0 && is_subvolume(stat); + } + catch (const IOErrorException& e) + { + SN_CAUGHT(e); + + // TODO the openInfoDir above logs an error although when this + // function is used from nextNumber the failure is ok + + return false; + } + } + +} diff --git a/snapper/Bcachefs.h b/snapper/Bcachefs.h new file mode 100644 index 00000000..17be882d --- /dev/null +++ b/snapper/Bcachefs.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 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_BCACHEFS_H +#define SNAPPER_BCACHEFS_H + + +#include "snapper/Filesystem.h" + + +namespace snapper +{ + + class Bcachefs : public Filesystem + { + public: + + static Filesystem* create(const string& fstype, const string& subvolume, + const string& root_prefix); + + Bcachefs(const string& subvolume, const string& root_prefix); + + virtual string fstype() const override { return "bcachefs"; } + + virtual void createConfig() const override; + virtual void deleteConfig() const override; + + virtual string snapshotDir(unsigned int num) const override; + + virtual SDir openSubvolumeDir() const override; + virtual SDir openInfosDir() const override; + virtual SDir openSnapshotDir(unsigned int num) const override; + + virtual void createSnapshot(unsigned int num, unsigned int num_parent, bool read_only, + bool quota, bool empty) const override; + virtual void deleteSnapshot(unsigned int num) const override; + + virtual bool isSnapshotMounted(unsigned int num) const override; + virtual void mountSnapshot(unsigned int num) const override; + virtual void umountSnapshot(unsigned int num) const override; + + virtual bool isSnapshotReadOnly(unsigned int num) const override; + virtual void setSnapshotReadOnly(unsigned int num, bool read_only) const override; + + virtual bool checkSnapshot(unsigned int num) const override; + + }; + +} + + +#endif diff --git a/snapper/BcachefsUtils.cc b/snapper/BcachefsUtils.cc new file mode 100644 index 00000000..2150b365 --- /dev/null +++ b/snapper/BcachefsUtils.cc @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 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 "config.h" + +#include +#include +#include + +#include "snapper/Log.h" +#include "snapper/AppUtil.h" +#include "snapper/BcachefsUtils.h" + + +struct bch_ioctl_subvolume { + __u32 flags; + __u32 dirfd; + __u16 mode; + __u16 pad[3]; + __u64 dst_ptr; + __u64 src_ptr; +}; + +#define BCH_IOCTL_SUBVOLUME_CREATE _IOW(0xbc, 16, struct bch_ioctl_subvolume) +#define BCH_IOCTL_SUBVOLUME_DESTROY _IOW(0xbc, 17, struct bch_ioctl_subvolume) + +#define BCH_SUBVOL_SNAPSHOT_CREATE (1U << 0) +#define BCH_SUBVOL_SNAPSHOT_RO (1U << 1) + + +namespace snapper +{ + + namespace BcachefsUtils + { + + bool + is_subvolume(const struct stat& stat) + { + return true; // TODO + } + + + bool + is_subvolume_read_only(int fd) + { + return false; // TODO + } + + + void + set_subvolume_read_only(int fd, bool read_only) + { + throw std::runtime_error("set_subvolume_read_only not implemented"); // TODO + } + + + void + create_subvolume(int fddst, const string& name) + { + struct bch_ioctl_subvolume args = { + .flags = 0, + .dirfd = (__u32) fddst, + .mode = 0777, + .dst_ptr = (__u64) name.c_str(), + .src_ptr = 0, + }; + + if (ioctl(fddst, BCH_IOCTL_SUBVOLUME_CREATE, &args) < 0) + throw runtime_error_with_errno("ioctl(BCH_IOCTL_SUBVOLUME_CREATE) failed", errno); + } + + + void + create_snapshot(int fd, const string& subvolume, int fddst, const string& name, bool read_only) + { + __u32 flags = BCH_SUBVOL_SNAPSHOT_CREATE; + if (read_only) + flags |= BCH_SUBVOL_SNAPSHOT_RO; // TODO does not work + + struct bch_ioctl_subvolume args = { + .flags = flags, + .dirfd = (__u32) fddst, + .mode = 0777, + .dst_ptr = (__u64) name.c_str(), + .src_ptr = (__u64) subvolume.c_str(), // TODO use fd instead of subvolume + }; + + if (ioctl(fddst, BCH_IOCTL_SUBVOLUME_CREATE, &args) < 0) + throw runtime_error_with_errno("ioctl(BCH_IOCTL_SUBVOLUME_CREATE) failed", errno); + } + + + void + delete_subvolume(int fd, const string& name) + { + struct bch_ioctl_subvolume args = { + .flags = 0, + .dirfd = (__u32) fd, + .mode = 0777, + .dst_ptr = (__u64) name.c_str(), + .src_ptr = 0, + }; + + if (ioctl(fd, BCH_IOCTL_SUBVOLUME_DESTROY, &args) < 0) + throw runtime_error_with_errno("ioctl(BCH_IOCTL_SUBVOLUME_DESTROY) failed", errno); + } + + } + +} diff --git a/snapper/BcachefsUtils.h b/snapper/BcachefsUtils.h new file mode 100644 index 00000000..39981aa4 --- /dev/null +++ b/snapper/BcachefsUtils.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 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_BCACHEFS_UTILS_H +#define SNAPPER_BCACHEFS_UTILS_H + + +#include + + +namespace snapper +{ + using std::string; + + + namespace BcachefsUtils + { + + bool is_subvolume(const struct stat& stat); + + bool is_subvolume_read_only(int fd); + void set_subvolume_read_only(int fd, bool read_only); + + void create_subvolume(int fddst, const string& name); + void create_snapshot(int fd, const string& subvolume, int fddst, const string& name, bool read_only); + void delete_subvolume(int fd, const string& name); + + } + +} + + +#endif diff --git a/snapper/Filesystem.cc b/snapper/Filesystem.cc index 38916d3d..7f4d27cb 100644 --- a/snapper/Filesystem.cc +++ b/snapper/Filesystem.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2011-2015] Novell, Inc. - * Copyright (c) [2016-2023] SUSE LLC + * Copyright (c) [2016-2024] SUSE LLC * * All Rights Reserved. * @@ -39,6 +39,9 @@ #ifdef ENABLE_BTRFS #include "snapper/Btrfs.h" #endif +#ifdef ENABLE_BCACHEFS +#include "snapper/Bcachefs.h" +#endif #ifdef ENABLE_EXT4 #include "snapper/Ext4.h" #endif @@ -101,6 +104,9 @@ namespace snapper #ifdef ENABLE_BTRFS &Btrfs::create, #endif +#ifdef ENABLE_BCACHEFS + &Bcachefs::create, +#endif #ifdef ENABLE_EXT4 &Ext4::create, #endif diff --git a/snapper/Makefile.am b/snapper/Makefile.am index 2e994a6d..b0fe55c8 100644 --- a/snapper/Makefile.am +++ b/snapper/Makefile.am @@ -41,6 +41,12 @@ libsnapper_la_SOURCES += \ BtrfsUtils.cc BtrfsUtils.h endif +if ENABLE_BCACHEFS +libsnapper_la_SOURCES += \ + Bcachefs.cc Bcachefs.h \ + BcachefsUtils.cc BcachefsUtils.h +endif + if ENABLE_EXT4 libsnapper_la_SOURCES += \ Ext4.cc Ext4.h diff --git a/snapper/Snapper.cc b/snapper/Snapper.cc index 8a761b5d..d5821c6a 100644 --- a/snapper/Snapper.cc +++ b/snapper/Snapper.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2011-2015] Novell, Inc. - * Copyright (c) [2016-2023] SUSE LLC + * Copyright (c) [2016-2024] SUSE LLC * * All Rights Reserved. * @@ -1194,6 +1194,11 @@ namespace snapper #endif "btrfs," +#ifndef ENABLE_BCACHEFS + "no-" +#endif + "bcachefs," + #ifndef ENABLE_LVM "no-" #endif