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"])
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
--- /dev/null
+
+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
+
+-------------------------------------------------------------------
+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
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);
--disable-btrfs-quota \
%endif
%{?with_selinux:--enable-selinux} \
- --disable-silent-rules --disable-ext4
+ --disable-silent-rules --disable-bcachefs --disable-ext4
make %{?_smp_mflags}
%install
--- /dev/null
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 <cerrno>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#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);
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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 <string>
+
+
+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
/*
* Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) [2016-2023] SUSE LLC
+ * Copyright (c) [2016-2024] SUSE LLC
*
* All Rights Reserved.
*
#ifdef ENABLE_BTRFS
#include "snapper/Btrfs.h"
#endif
+#ifdef ENABLE_BCACHEFS
+#include "snapper/Bcachefs.h"
+#endif
#ifdef ENABLE_EXT4
#include "snapper/Ext4.h"
#endif
#ifdef ENABLE_BTRFS
&Btrfs::create,
#endif
+#ifdef ENABLE_BCACHEFS
+ &Bcachefs::create,
+#endif
#ifdef ENABLE_EXT4
&Ext4::create,
#endif
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
/*
* Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) [2016-2023] SUSE LLC
+ * Copyright (c) [2016-2024] SUSE LLC
*
* All Rights Reserved.
*
#endif
"btrfs,"
+#ifndef ENABLE_BCACHEFS
+ "no-"
+#endif
+ "bcachefs,"
+
#ifndef ENABLE_LVM
"no-"
#endif