diff --git a/pkg/storaged/client.js b/pkg/storaged/client.js
index 6dd9f43ec9c9..68492a8859c2 100644
--- a/pkg/storaged/client.js
+++ b/pkg/storaged/client.js
@@ -176,6 +176,7 @@ function init_proxies () {
client.blocks_swap = proxies("Swapspace");
client.iscsi_sessions = proxies("ISCSI.Session");
client.vdo_vols = proxies("VDOVolume");
+ client.blocks_fsys_btrfs = proxies("Filesystem.BTRFS");
client.jobs = proxies("Job");
return client.storaged_client.watch({ path_namespace: "/org/freedesktop/UDisks2" });
@@ -526,6 +527,18 @@ function update_indices() {
};
}
+ // UDisks API does provide a btrfs volume abstraction so we keep track of
+ // volume's by uuid in an object. uuid => [org.freedesktop.UDisks2.Filesystem.BTRFS]
+ client.uuids_btrfs_blocks = { };
+ for (const p in client.blocks_fsys_btrfs) {
+ const bfs = client.blocks_fsys_btrfs[p];
+ const uuid = bfs.data.uuid;
+ if (!client.uuids_btrfs_blocks[uuid])
+ client.uuids_btrfs_blocks[uuid] = [];
+ client.uuids_btrfs_blocks[uuid].push(client.blocks[p]);
+ }
+ console.log("uuids_btrfs_blocks", client.uuids_btrfs_blocks);
+
client.blocks_cleartext = { };
for (path in client.blocks) {
block = client.blocks[path];
@@ -612,11 +625,13 @@ function init_model(callback) {
function() {
client.manager_lvm2 = proxy("Manager.LVM2", "Manager");
client.manager_iscsi = proxy("Manager.ISCSI.Initiator", "Manager");
- return Promise.allSettled([client.manager_lvm2.wait(), client.manager_iscsi.wait()])
+ client.manager_btrfs = proxy("Manager.BTRFS", "Manager");
+ return Promise.allSettled([client.manager_lvm2.wait(), client.manager_iscsi.wait(), client.manager_btrfs.wait()])
.then(() => {
client.features.lvm2 = client.manager_lvm2.valid;
client.features.iscsi = (client.manager_iscsi.valid &&
client.manager_iscsi.SessionsSupported !== false);
+ client.features.btrfs = client.manager_btrfs.valid;
});
}, function(error) {
console.warn("Can't enable storaged modules", error.toString());
diff --git a/pkg/storaged/dialog.jsx b/pkg/storaged/dialog.jsx
index ec97684d52be..6c4125a36654 100644
--- a/pkg/storaged/dialog.jsx
+++ b/pkg/storaged/dialog.jsx
@@ -1081,7 +1081,8 @@ export const BlockingMessage = (usage) => {
pvol: _("physical volume of LVM2 volume group"),
"mdraid-member": _("member of RAID device"),
vdo: _("backing device for VDO device"),
- "stratis-pool-member": _("member of Stratis pool")
+ "stratis-pool-member": _("member of Stratis pool"),
+ "btrfs-member": _("member of BTRFS volume")
};
console.log("U", usage);
diff --git a/pkg/storaged/format-dialog.jsx b/pkg/storaged/format-dialog.jsx
index 2da510c65df7..c126fdb9023a 100644
--- a/pkg/storaged/format-dialog.jsx
+++ b/pkg/storaged/format-dialog.jsx
@@ -175,6 +175,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
add_fsys("ext4", { value: "ext4", title: "EXT4" });
add_fsys("vfat", { value: "vfat", title: "VFAT" });
add_fsys("ntfs", { value: "ntfs", title: "NTFS" });
+ add_fsys("btrfs", { value: "btrfs", title: "BTRFS" });
add_fsys(true, { value: "empty", title: _("No filesystem") });
if (create_partition && enable_dos_extended)
add_fsys(true, { value: "dos-extended", title: _("Extended partition") });
diff --git a/pkg/storaged/pages/btrfs.jsx b/pkg/storaged/pages/btrfs.jsx
new file mode 100644
index 000000000000..61132d9bd7b1
--- /dev/null
+++ b/pkg/storaged/pages/btrfs.jsx
@@ -0,0 +1,143 @@
+/*
+ * This file is part of Cockpit.
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Cockpit 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.
+ *
+ * Cockpit 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 Cockpit; If not, see .
+ */
+
+import cockpit from "cockpit";
+import React from "react";
+import client from "../client";
+
+import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
+import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
+import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
+
+import { SCard } from "../utils/card.jsx";
+import { SDesc } from "../utils/desc.jsx";
+import { PageChildrenCard, PageCrossrefCard, ActionButtons, new_page, page_type, get_crossrefs } from "../pages.jsx";
+import { fmt_size, fmt_size_long } from "../utils.js";
+
+const _ = cockpit.gettext;
+
+function btrfs_usage(uuid) {
+ const block_devices = client.uuids_btrfs_blocks[uuid];
+ console.log(block_devices);
+
+ let size = 0;
+ for (const block_device of block_devices) {
+ size += client.blocks[block_device.path].Size;
+ }
+ const used = client.blocks_fsys_btrfs[block_devices[0].path].data.used;
+
+ // const block_fsys = client.blocks_fsys[volume.path];
+ // const mount_point = block_fsys && block_fsys.MountPoints[0];
+ // let use = mount_point && client.fsys_sizes.data[decode_filename(mount_point)];
+ // if (!use) {
+ // const blocks = [];
+ // Object.keys(client.blocks_fsys_btrfs).forEach(obj_path => {
+ // const blk = client.blocks_fsys_btrfs[obj_path];
+ // if (blk.data.uuid === volume.data.uuid) {
+ // blocks.push(client.blocks[obj_path]);
+ // }
+ // });
+ // use = [volume.data.used, blocks.reduce((sum, b) => sum + b.Size, 0)];
+ // }
+ // console.log(use);
+ return [used, size];
+}
+
+/*
+ * Udisks is a disk/block library so it manages that, btrfs turns this a bit
+ * around and has one "volume" which can have multiple blocks by a unique uuid.
+ */
+export function make_btrfs_volume_page(parent, uuid) {
+ const block_devices = client.uuids_btrfs_blocks[uuid];
+
+ const device = client.blocks_fsys_btrfs[block_devices[0].path];
+ // TODO: label is optional, but do we want to show uuid then?
+ const name = device.data.label || uuid;
+ const total_capacity = btrfs_usage(uuid)[1];
+ const btrfs_volume_page = new_page({
+ location: ["btrfs-volume", name],
+ parent,
+ name,
+ columns: [
+ _("Btrfs volume"),
+ "",
+ fmt_size(total_capacity),
+ ],
+ component: BtrfsVolumePage,
+ props: { block_devices, name: device.data.label, uuid: device.data.uuid, total_capacity },
+ actions: [],
+ });
+
+ for (const blk of block_devices) {
+ const device = client.blocks_fsys_btrfs[blk.path];
+ device.GetSubvolumes(false, {}).then(subvolumes => {
+ for (const subvolume of subvolumes) {
+ make_btrfs_volume_subvolume(btrfs_volume_page, uuid, subvolume);
+ }
+ });
+ }
+}
+
+function make_btrfs_volume_subvolume(parent, uuid, subvol) {
+ const [id, parent_id, path] = subvol;
+ new_page({
+ location: ["btrfs-volume", uuid, id],
+ parent,
+ name: path,
+ columns: [
+ _("Btrfs subvolume"),
+ id,
+ parent_id,
+ ],
+ component: BtrfsVolumePage,
+ props: { uuid, id, parent_id, path },
+ actions: [],
+ });
+}
+
+const BtrfsVolumePage = ({ page, block_devices, name, uuid, total_capacity, subvolumes }) => {
+ let crossrefs = [];
+ for (const blk of block_devices) {
+ crossrefs = crossrefs.concat(get_crossrefs(blk));
+ }
+ return (
+
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/pkg/storaged/pages/drive.jsx b/pkg/storaged/pages/drive.jsx
index 7066e8eda162..5bed10354aee 100644
--- a/pkg/storaged/pages/drive.jsx
+++ b/pkg/storaged/pages/drive.jsx
@@ -29,7 +29,7 @@ import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { SCard } from "../utils/card.jsx";
import { SDesc } from "../utils/desc.jsx";
import { StorageButton } from "../storage-controls.jsx";
-import { PageChildrenCard, ParentPageLink, new_page, page_type, block_location } from "../pages.jsx";
+import { PageChildrenCard, ParentPageLink, new_page, page_type, block_location, register_crossref } from "../pages.jsx";
import { block_name, drive_name, format_temperature, fmt_size, fmt_size_long } from "../utils.js";
import { format_disk } from "../content-views.jsx"; // XXX
diff --git a/pkg/storaged/pages/overview.jsx b/pkg/storaged/pages/overview.jsx
index 0ba6ab074eb0..d68139d92cd1 100644
--- a/pkg/storaged/pages/overview.jsx
+++ b/pkg/storaged/pages/overview.jsx
@@ -39,6 +39,7 @@ import { get_other_devices } from "../utils.js"; // XXX
import { new_page, PageChildrenCard } from "../pages.jsx";
import { make_drive_page } from "./drive.jsx";
+import { make_btrfs_volume_page } from "./btrfs.jsx";
import { make_lvm2_volume_group_page } from "./lvm2-volume-group.jsx";
import { make_mdraid_page } from "./mdraid.jsx";
import { make_stratis_pool_page } from "./stratis-pool.jsx";
@@ -65,6 +66,15 @@ export function make_overview_page() {
Object.keys(client.mdraids).forEach(p => make_mdraid_page(overview_page, client.mdraids[p]));
Object.keys(client.stratis_pools).map(p => make_stratis_pool_page(overview_page, client.stratis_pools[p]));
Object.keys(client.stratis_manager.StoppedPools).map(uuid => make_stratis_stopped_pool_page(overview_page, uuid));
+ // TODO: this needs to poll? What does udisks do for us?
+ const btrfs_uuids = new Set();
+ Object.keys(client.blocks_fsys_btrfs).forEach(p => {
+ const bfs = client.blocks_fsys_btrfs[p];
+ btrfs_uuids.add(bfs.data.uuid);
+ });
+ for (const uuid of btrfs_uuids) {
+ make_btrfs_volume_page(overview_page, uuid);
+ }
client.nfs.entries.forEach(e => make_nfs_page(overview_page, e));
get_other_devices(client).map(p => make_other_page(overview_page, client.blocks[p]));
}