Skip to content

Commit

Permalink
- added experimental bcachefs support
Browse files Browse the repository at this point in the history
  • Loading branch information
aschnell committed Feb 14, 2024
1 parent cc0a238 commit 9219cc5
Show file tree
Hide file tree
Showing 14 changed files with 631 additions and 7 deletions.
2 changes: 1 addition & 1 deletion LIBVERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.4.0
7.4.1
8 changes: 8 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
4 changes: 2 additions & 2 deletions dists/debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions doc/bcachefs.txt
Original file line number Diff line number Diff line change
@@ -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

5 changes: 5 additions & 0 deletions package/snapper.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
-------------------------------------------------------------------
Wed Feb 14 09:14:32 CET 2024 - [email protected]

- added experimental bcachefs support (gh#openSUSE/snapper#858)

-------------------------------------------------------------------
Wed Feb 07 11:16:40 CET 2024 - [email protected]

Expand Down
6 changes: 5 additions & 1 deletion server/Client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion snapper.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
323 changes: 323 additions & 0 deletions snapper/Bcachefs.cc
Original file line number Diff line number Diff line change
@@ -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 <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;
}
}

}
Loading

0 comments on commit 9219cc5

Please sign in to comment.