From 35046e09eb7b4a497e4a9153fda681613da5e556 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sat, 23 Sep 2023 18:17:57 +0200 Subject: [PATCH] fs: basic bcachefs support This adds mkfs/get_info support for Bcachefs. --- README.md | 2 +- src/lib/plugin_apis/fs.api | 86 ++++++++++++ src/plugins/fs.h | 4 +- src/plugins/fs/Makefile.am | 3 +- src/plugins/fs/bcachefs.c | 229 ++++++++++++++++++++++++++++++++ src/plugins/fs/bcachefs.h | 19 +++ src/plugins/fs/generic.c | 23 ++++ tests/fs_tests/bcachefs_test.py | 17 +++ tests/fs_tests/fs_test.py | 6 + tests/fs_tests/generic_test.py | 2 +- tests/utils.py | 2 +- 11 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 src/plugins/fs/bcachefs.c create mode 100644 src/plugins/fs/bcachefs.h create mode 100644 tests/fs_tests/bcachefs_test.py diff --git a/README.md b/README.md index e5eb67dac..13d192a37 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Following storage technologies are supported by libblockdev - partitions - MSDOS, GPT - filesystem operations - - ext2, ext3, ext4, xfs, vfat, ntfs, exfat, btrfs, f2fs, nilfs2, udf + - ext2, ext3, ext4, xfs, vfat, ntfs, exfat, btrfs, f2fs, nilfs2, udf, bcachefs - mounting - LVM - thin provisioning, LVM RAID, cache, LVM VDO diff --git a/src/lib/plugin_apis/fs.api b/src/lib/plugin_apis/fs.api index 8879ab454..9d68c24a8 100644 --- a/src/lib/plugin_apis/fs.api +++ b/src/lib/plugin_apis/fs.api @@ -811,6 +811,63 @@ GType bd_fs_udf_info_get_type () { return type; } +/** + * BDFSBcachefsInfo: + * @uuid: uuid of the filesystem + * @size: size of the filesystem in bytes + * @free_space: free space on the filesystem in bytes + */ +typedef struct BDFSBcachefsInfo { + gchar *uuid; + guint64 size; + guint64 free_space; +} BDFSBcachefsInfo; + +/** + * bd_fs_bcachefs_info_copy: (skip) + * @data: (nullable): %BDFSBcachefsInfo to copy + * + * Creates a new copy of @data. + */ +BDFSBcachefsInfo* bd_fs_bcachefs_info_copy (BDFSBcachefsInfo *data) { + if (data == NULL) + return NULL; + + BDFSBcachefsInfo *ret = g_new0 (BDFSBcachefsInfo, 1); + + ret->uuid = g_strdup (data->uuid); + ret->size = data->size; + ret->free_space = data->free_space; + + return ret; +} + +/** + * bd_fs_bcachefs_info_free: (skip) + * @data: (nullable): %BDFSBcachefsInfo to free + * + * Frees @data. + */ +void bd_fs_bcachefs_info_free (BDFSBcachefsInfo *data) { + if (data == NULL) + return; + + g_free (data->uuid); + g_free (data); +} + +GType bd_fs_bcachefs_info_get_type () { + static GType type = 0; + + if (G_UNLIKELY(type == 0)) { + type = g_boxed_type_register_static("BDFSBcachefsInfo", + (GBoxedCopyFunc) bd_fs_bcachefs_info_copy, + (GBoxedFreeFunc) bd_fs_bcachefs_info_free); + } + + return type; +} + /** * bd_fs_is_tech_avail: * @tech: the queried tech @@ -2524,6 +2581,35 @@ gboolean bd_fs_udf_check_uuid (const gchar *uuid, GError **error); */ BDFSUdfInfo* bd_fs_udf_get_info (const gchar *device, GError **error); +/** + * bd_fs_bcachefs_mkfs: + * @device: the device to create a new bcachefs fs on + * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now + * passed to the 'mkfs.bcachefs' utility) + * @error: (out) (optional): place to store error (if any) + * + * Returns: whether a new bcachefs fs was successfully created on @device or not + * + * Tech category: %BD_FS_TECH_BCACHEFS-%BD_FS_TECH_MODE_MKFS + * + */ +gboolean bd_fs_bcachefs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error); + +/** + * bd_fs_bcachefs_get_info: + * @mpoint: a mountpoint of the bcachefs filesystem to get information about + * @error: (out) (optional): place to store error (if any) + * + * Returns: (transfer full): information about the file system on @device or + * %NULL in case of error + * + * Note: This function WON'T WORK for multi device bcachefs filesystems, + * for more complicated setups use the bcachefs plugin instead. + * + * Tech category: %BD_FS_TECH_BCACHEFS-%BD_FS_TECH_MODE_QUERY + */ +BDFSBcachefsInfo* bd_fs_bcachefs_get_info (const gchar *mpoint, GError **error); + typedef enum { BD_FS_SUPPORT_SET_LABEL = 1 << 1, BD_FS_SUPPORT_SET_UUID = 1 << 2 diff --git a/src/plugins/fs.h b/src/plugins/fs.h index 3c38f5cb0..071efd0d1 100644 --- a/src/plugins/fs.h +++ b/src/plugins/fs.h @@ -23,7 +23,7 @@ typedef enum { /* XXX: where the file systems start at the enum of technologies */ #define BD_FS_OFFSET 2 -#define BD_FS_LAST_FS 13 +#define BD_FS_LAST_FS 14 typedef enum { BD_FS_TECH_GENERIC = 0, BD_FS_TECH_MOUNT = 1, @@ -38,6 +38,7 @@ typedef enum { BD_FS_TECH_EXFAT = 10, BD_FS_TECH_BTRFS = 11, BD_FS_TECH_UDF = 12, + BD_FS_TECH_BCACHEFS = 13, } BDFSTech; /* XXX: number of the highest bit of all modes */ @@ -80,3 +81,4 @@ gboolean bd_fs_is_tech_avail (BDFSTech tech, guint64 mode, GError **error); #include "fs/exfat.h" #include "fs/btrfs.h" #include "fs/udf.h" +#include "fs/bcachefs.h" diff --git a/src/plugins/fs/Makefile.am b/src/plugins/fs/Makefile.am index 7d4849c0e..2998626ae 100644 --- a/src/plugins/fs/Makefile.am +++ b/src/plugins/fs/Makefile.am @@ -19,7 +19,8 @@ libbd_fs_la_SOURCES = ../check_deps.c ../check_deps.h \ nilfs.c nilfs.h \ exfat.c exfat.h \ btrfs.c btrfs.h \ - udf.c udf.h + udf.c udf.h \ + bcachefs.c bcachefs.h libincludefsdir = $(includedir)/blockdev/fs/ libincludefs_HEADERS = ext.h \ diff --git a/src/plugins/fs/bcachefs.c b/src/plugins/fs/bcachefs.c new file mode 100644 index 000000000..ad8fcc1a2 --- /dev/null +++ b/src/plugins/fs/bcachefs.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2023 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + * Author: Jelle van der Waa + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bcachefs.h" +#include "fs.h" +#include "common.h" + +static volatile guint avail_deps = 0; +static GMutex deps_check_lock; + +#define DEPS_MKFSBCACHEFS 0 +#define DEPS_MKFSBCACHEFS_MASK (1 << DEPS_MKFSBCACHEFS) +#define DEPS_BCACHEFSCK 1 +#define DEPS_BCACHEFSCK_MASK (1 << DEPS_BCACHEFSCK) +#define DEPS_BCACHEFS 2 +#define DEPS_BCACHEFS_MASK (1 << DEPS_BCACHEFS) + +#define DEPS_LAST 3 + +static const UtilDep deps[DEPS_LAST] = { + {"mkfs.bcachefs", NULL, NULL, NULL}, + {"fsck.bcachefs", NULL, NULL, NULL}, + {"bcachefs", NULL, NULL, NULL}, +}; + +static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = { + DEPS_MKFSBCACHEFS_MASK, /* mkfs */ + 0, /* wipe */ + DEPS_BCACHEFSCK_MASK, /* check */ + DEPS_BCACHEFSCK_MASK, /* repair */ + DEPS_BCACHEFS_MASK, /* set-label */ + DEPS_BCACHEFS_MASK, /* query */ + DEPS_BCACHEFS_MASK, /* resize */ +}; + +#define UNUSED __attribute__((unused)) + +/** + * bd_fs_bcachefs_is_tech_avail: + * @tech: the queried tech + * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech + * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available) + * + * Returns: whether the @tech-@mode combination is available -- supported by the + * plugin implementation and having all the runtime dependencies available + */ +gboolean __attribute__ ((visibility ("hidden"))) +bd_fs_bcachefs_is_tech_avail (BDFSTech tech UNUSED, guint64 mode, GError **error) { + guint32 required = 0; + guint i = 0; + + for (i = 0; i <= BD_FS_MODE_LAST; i++) + if (mode & (1 << i)) + required |= fs_mode_util[i]; + + return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error); +} + +/** + * bd_fs_bcachefs_info_copy: (skip) + * + * Creates a new copy of @data. + */ +BDFSBcachefsInfo* bd_fs_bcachefs_info_copy (BDFSBcachefsInfo *data) { + if (data == NULL) + return NULL; + + g_info("copying"); + BDFSBcachefsInfo *ret = g_new0 (BDFSBcachefsInfo, 1); + + ret->uuid = g_strdup (data->uuid); + ret->size = data->size; + ret->free_space = data->free_space; + + return ret; +} + +/** + * bd_fs_bcachefs_info_free: (skip) + * + * Frees @data. + */ +void bd_fs_bcachefs_info_free (BDFSBcachefsInfo *data) { + if (data == NULL) + return; + g_free (data->uuid); + g_free (data); +} + +BDExtraArg __attribute__ ((visibility ("hidden"))) +**bd_fs_bcachefs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) { + GPtrArray *options_array = g_ptr_array_new (); + const BDExtraArg **extra_p = NULL; + + if (options->label && g_strcmp0 (options->label, "") != 0) + g_ptr_array_add (options_array, bd_extra_arg_new ("-L", options->label)); + + if (options->uuid && g_strcmp0 (options->uuid, "") != 0) + g_ptr_array_add (options_array, bd_extra_arg_new ("-U", options->uuid)); + + if (options->force) + g_ptr_array_add (options_array, bd_extra_arg_new ("-f", "")); + + if (extra) { + for (extra_p = extra; *extra_p; extra_p++) + g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p)); + } + + g_ptr_array_add (options_array, NULL); + + return (BDExtraArg **) g_ptr_array_free (options_array, FALSE); +} + +/** + * bd_fs_bcachefs_mkfs: + * @device: the device to create a new bcachefs fs on + * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now + * passed to the 'mkfs.bcachefs' utility) + * @error: (out) (optional): place to store error (if any) + * + * Returns: whether a new bcachefs fs was successfully created on @device or not + * + * Tech category: %BD_FS_TECH_BCACHEFS-%BD_FS_TECH_MODE_MKFS + * + */ +gboolean bd_fs_bcachefs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) { + const gchar *args[3] = {"mkfs.bcachefs", device, NULL}; + + if (!check_deps (&avail_deps, DEPS_MKFSBCACHEFS_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return FALSE; + + return bd_utils_exec_and_report_error (args, extra, error); +} + +/** + * bd_fs_bcachefs_get_info: + * @mpoint: a mountpoint of the bcachefs filesystem to get information about + * @error: (out) (optional): place to store error (if any) + * + * Returns: (transfer full): information about the file system on @device or + * %NULL in case of error + * + * Note: This function WON'T WORK for multi device bcachefs filesystems, + * for more complicated setups use the btrfs plugin instead. + * + * Tech category: %BD_FS_TECH_BCACHEFS-%BD_FS_TECH_MODE_QUERY + */ +BDFSBcachefsInfo* bd_fs_bcachefs_get_info (const gchar *mpoint, GError **error) { + const gchar *args[5] = {"bcachefs", "fs", "usage", mpoint, NULL}; + gboolean success = FALSE; + gchar *output = NULL; + gchar const * const pattern = "Filesystem:\\s+(?P\\S+)\\s+" \ + "Size:\\s+(?P\\d+)\\s+" \ + "Used:\\s+(?P\\d+)\\s+\\S+" ; + GRegex *regex = NULL; + GMatchInfo *match_info = NULL; + BDFSBcachefsInfo *ret = NULL; + gchar *item = NULL; + guint64 used = 0; + + if (!check_deps (&avail_deps, DEPS_BCACHEFS_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return NULL; + + regex = g_regex_new (pattern, G_REGEX_EXTENDED, 0, error); + if (!regex) { + bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create new GRegex"); + /* error is already populated */ + return NULL; + } + + success = bd_utils_exec_and_capture_output (args, NULL, &output, error); + if (!success) { + /* error is already populated */ + return NULL; + } + + success = g_regex_match (regex, output, 0, &match_info); + if (!success) { + g_regex_unref (regex); + g_match_info_free (match_info); + g_free (output); + return NULL; + } + + ret = g_new (BDFSBcachefsInfo, 1); + + ret->uuid = g_match_info_fetch_named (match_info, "uuid"); + item = g_match_info_fetch_named (match_info, "size"); + ret->size = g_ascii_strtoull (item, NULL, 0); + g_free (item); + item = g_match_info_fetch_named (match_info, "used"); + used = g_ascii_strtoull (item, NULL, 0); + g_free (item); + + g_match_info_free (match_info); + g_regex_unref (regex); + g_free (output); + + // TODO error out if there are more then 1 devices + // TODO: detect label + ret->free_space = ret->size - used; + + return ret; +} diff --git a/src/plugins/fs/bcachefs.h b/src/plugins/fs/bcachefs.h new file mode 100644 index 000000000..0f80ed793 --- /dev/null +++ b/src/plugins/fs/bcachefs.h @@ -0,0 +1,19 @@ +#include +#include + +#ifndef BD_FS_BCACHEFS +#define BD_FS_BCACHEFS + +typedef struct BDFSBcachefsInfo { + gchar *uuid; + guint64 size; + guint64 free_space; +} BDFSBcachefsInfo; + +BDFSBcachefsInfo* bd_fs_bcachefs_info_copy (BDFSBcachefsInfo *data); +void bd_fs_bcachefs_info_free (BDFSBcachefsInfo *data); + +gboolean bd_fs_bcachefs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error); +BDFSBcachefsInfo* bd_fs_bcachefs_get_info (const gchar *device, GError **error); + +#endif /* BD_FS_BCACHEFS */ diff --git a/src/plugins/fs/generic.c b/src/plugins/fs/generic.c index 77e54969b..12caae2f4 100644 --- a/src/plugins/fs/generic.c +++ b/src/plugins/fs/generic.c @@ -150,6 +150,14 @@ static const BDFSFeatures fs_features[BD_FS_LAST_FS] = { .features = BD_FS_FEATURE_OWNERS | BD_FS_FEATURE_PARTITION_TABLE, .partition_id = "0x07", .partition_type = "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" }, + /* BCACHEFS */ + { .resize = 0, + .mkfs = BD_FS_MKFS_LABEL | BD_FS_MKFS_UUID, + .fsck = BD_FS_FSCK_CHECK | BD_FS_FSCK_REPAIR, + .configure = 0, + .features = BD_FS_FEATURE_OWNERS | BD_FS_FEATURE_PARTITION_TABLE, + .partition_id = "0x83", // TODO: figure out + .partition_type = "0fc63daf-8483-4772-8e79-3d69d8477de4" }, // TODO: figure out }; /** @@ -276,6 +284,15 @@ const BDFSInfo fs_info[BD_FS_LAST_FS] = { .label_util = "udflabel", .info_util = "udfinfo", .uuid_util = "udflabel" }, + /* BCACHEFS */ + { .type = "bcachefs", + .mkfs_util = "mkfs.bcachefs", + .check_util = "fsck.bcachefs", + .repair_util = "fsck.bcachefs", + .resize_util = "bcachefs", + .label_util = "bcachefs", + .info_util = "bcachefs", + .uuid_util = "bcachefs" }, }; /** @@ -330,6 +347,8 @@ static BDFSTech fstype_to_tech (const gchar *fstype) { return BD_FS_TECH_BTRFS; } else if (g_strcmp0 (fstype, "udf") == 0) { return BD_FS_TECH_UDF; + } else if (g_strcmp0 (fstype, "bcachefs") == 0) { + return BD_FS_TECH_BCACHEFS; } else { return BD_FS_TECH_GENERIC; } @@ -1784,6 +1803,7 @@ extern BDExtraArg** bd_fs_vfat_mkfs_options (BDFSMkfsOptions *options, const BDE extern BDExtraArg** bd_fs_xfs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra); extern BDExtraArg** bd_fs_btrfs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra); extern BDExtraArg** bd_fs_udf_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra); +extern BDExtraArg** bd_fs_bcachefs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra); /** * bd_fs_mkfs: @@ -1847,6 +1867,9 @@ gboolean bd_fs_mkfs (const gchar *device, const gchar *fstype, BDFSMkfsOptions * } else if (g_strcmp0 (fstype, "udf") == 0) { extra_args = bd_fs_udf_mkfs_options (options, extra); ret = bd_fs_udf_mkfs (device, NULL, NULL, 0, (const BDExtraArg **) extra_args, error); + } else if (g_strcmp0 (fstype, "bcachefs") == 0) { + extra_args = bd_fs_bcachefs_mkfs_options (options, extra); + ret = bd_fs_bcachefs_mkfs (device, (const BDExtraArg **) extra_args, error); } else { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_NOT_SUPPORTED, "Filesystem '%s' is not supported.", fstype); diff --git a/tests/fs_tests/bcachefs_test.py b/tests/fs_tests/bcachefs_test.py new file mode 100644 index 000000000..a8e52d353 --- /dev/null +++ b/tests/fs_tests/bcachefs_test.py @@ -0,0 +1,17 @@ +import tempfile + +from .fs_test import FSTestCase, FSNoDevTestCase, mounted + +import overrides_hack +import utils +from utils import TestTags, tag_test + +from gi.repository import BlockDev, GLib + + +class BcachefsNoDevTestCase(FSNoDevTestCase): + def setUp(self): + if not self.bcachefs_avail: + self.skipTest("skipping Btrfs: not available") + + super(BcachefsNoDevTestCase, self).setUp() diff --git a/tests/fs_tests/fs_test.py b/tests/fs_tests/fs_test.py index 4cecb9e98..d5008b3c9 100644 --- a/tests/fs_tests/fs_test.py +++ b/tests/fs_tests/fs_test.py @@ -95,6 +95,12 @@ def setUpClass(cls): except Exception : cls.udf_avail = False + try: + cls.bcachefs_avail = BlockDev.fs_is_tech_avail(BlockDev.FSTech.BCACHEFS, + BlockDev.FSTechMode.MKFS | + BlockDev.FSTechMode.SET_LABEL) + except Exception : + cls.bcachefs_avail = False class FSTestCase(FSNoDevTestCase): diff --git a/tests/fs_tests/generic_test.py b/tests/fs_tests/generic_test.py index ed3066604..09d1e9410 100644 --- a/tests/fs_tests/generic_test.py +++ b/tests/fs_tests/generic_test.py @@ -1236,4 +1236,4 @@ def test_supported_filesystems(self): filesystems = BlockDev.fs_supported_filesystems() self.assertListEqual(filesystems, ["ext2", "ext3", "ext4", "xfs", "vfat", "ntfs", - "f2fs", "nilfs2", "exfat", "btrfs", "udf"]) + "f2fs", "nilfs2", "exfat", "btrfs", "udf", "bcachefs"]) diff --git a/tests/utils.py b/tests/utils.py index 51d880962..4fd81dfe8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -505,7 +505,7 @@ def get_version_from_pretty_name(pretty_name): if match is not None: version = match.group(0) else: - version = get_version_from_lsb() + version = "1" # get_version_from_lsb() return (distro, version)