diff --git a/pkg/storaged/block/create-pages.jsx b/pkg/storaged/block/create-pages.jsx
index 2463b2d4368b..3054c5ef01a3 100644
--- a/pkg/storaged/block/create-pages.jsx
+++ b/pkg/storaged/block/create-pages.jsx
@@ -35,7 +35,7 @@ import { make_swap_card } from "../swap/swap.jsx";
import { make_encryption_card } from "../crypto/encryption.jsx";
import { make_btrfs_device_card } from "../btrfs/device.jsx";
import { make_btrfs_filesystem_card } from "../btrfs/filesystem.jsx";
-import { make_btrfs_subvolume_pages } from "../btrfs/volume.jsx";
+import { make_btrfs_subvolume_pages } from "../btrfs/subvolume.jsx";
import { new_page } from "../pages.jsx";
diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx
index b4de220a2e43..b64d43f45b54 100644
--- a/pkg/storaged/btrfs/subvolume.jsx
+++ b/pkg/storaged/btrfs/subvolume.jsx
@@ -23,13 +23,16 @@ import React from "react";
import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
-import { StorageCard, StorageDescription, new_card, new_page, navigate_away_from_card } from "../pages.jsx";
+import {
+ StorageCard, StorageDescription, ChildrenTable, new_card, new_page, navigate_away_from_card
+} from "../pages.jsx";
import { StorageUsageBar } from "../storage-controls.jsx";
import {
- encode_filename, get_fstab_config_with_client, reload_systemd, extract_option, parse_options,
+ encode_filename, decode_filename,
+ get_fstab_config_with_client, reload_systemd, extract_option, parse_options,
flatten, teardown_active_usage,
} from "../utils.js";
-import { btrfs_usage, validate_subvolume_name } from "./utils.jsx";
+import { btrfs_usage, validate_subvolume_name, parse_subvol_from_options } from "./utils.jsx";
import { at_boot_input, mounting_dialog, mount_options } from "../filesystem/mounting-dialog.jsx";
import {
dialog_open, TextInput,
@@ -242,7 +245,70 @@ function subvolume_delete(volume, subvol, mount_point_in_parent, card) {
});
}
-export function make_btrfs_subvolume_page(parent, volume, subvol) {
+function dirname(path) {
+ const i = path.lastIndexOf("/");
+ if (i < 0)
+ return null;
+ else
+ return path.substr(0, i);
+}
+
+export function make_btrfs_subvolume_pages(parent, volume) {
+ let subvols = client.uuids_btrfs_subvols[volume.data.uuid];
+ if (!subvols) {
+ const block = client.blocks[volume.path];
+ /*
+ * Try to show subvolumes based on fstab entries, this is a bit tricky
+ * as mounts where subvolid cannot be shown userfriendly.
+ *
+ * The real subvolume data structure has "id" fields and
+ * "parent" fields that refer to the ids to form a tree. We
+ * want to do the same here, and we give fake ids to our fake
+ * subvolumes for this reason. We don't store these fake ids
+ * in the "id" field since we don't want them to be taken
+ * seriously by the rest of the code.
+ */
+ let fake_id = 5;
+ subvols = [{ pathname: "/", id: 5, fake_id: fake_id++ }];
+ const subvols_by_pathname = { };
+ for (const config of block.Configuration) {
+ if (config[0] == "fstab") {
+ const opts = config[1].opts;
+ if (!opts)
+ continue;
+
+ const fstab_subvol = parse_subvol_from_options(decode_filename(opts.v));
+
+ if (fstab_subvol && fstab_subvol.pathname && fstab_subvol.pathname !== "/") {
+ fstab_subvol.fake_id = fake_id++;
+ subvols_by_pathname[fstab_subvol.pathname] = fstab_subvol;
+ subvols.push(fstab_subvol);
+ }
+ }
+ }
+
+ // Find parents
+ for (const pn in subvols_by_pathname) {
+ let dn = pn;
+ while (true) {
+ dn = dirname(dn);
+ if (!dn) {
+ subvols_by_pathname[pn].parent = 5;
+ break;
+ } else if (subvols_by_pathname[dn]) {
+ subvols_by_pathname[pn].parent = subvols_by_pathname[dn].fake_id;
+ break;
+ }
+ }
+ }
+ }
+
+ const root = subvols.find(s => s.id == 5);
+ if (root)
+ make_btrfs_subvolume_page(parent, volume, root, "", subvols);
+}
+
+function make_btrfs_subvolume_page(parent, volume, subvol, path_prefix, subvols) {
const actions = [];
const use = btrfs_usage(client, volume);
@@ -307,11 +373,18 @@ export function make_btrfs_subvolume_page(parent, volume, subvol) {
action: () => subvolume_delete(volume, subvol, mount_point_in_parent, card),
});
+ function strip_prefix(str, prefix) {
+ if (str.startsWith(prefix))
+ return str.slice(prefix.length);
+ else
+ return str;
+ }
+
const card = new_card({
title: _("btrfs subvolume"),
next: null,
page_location: ["btrfs", volume.data.uuid, subvol.pathname],
- page_name: subvol.pathname,
+ page_name: strip_prefix(subvol.pathname, path_prefix),
page_size: is_mounted && ,
location: mp_text,
component: BtrfsSubvolumeCard,
@@ -319,7 +392,12 @@ export function make_btrfs_subvolume_page(parent, volume, subvol) {
props: { subvol, mount_point, mismount_warning, block, fstab_config, forced_options },
actions,
});
- new_page(parent, card);
+ const page = new_page(parent, card);
+ for (const sv of subvols) {
+ if (sv.parent && (sv.parent === subvol.id || sv.parent === subvol.fake_id)) {
+ make_btrfs_subvolume_page(page, volume, sv, subvol.pathname + "/", subvols);
+ }
+ }
}
const BtrfsSubvolumeCard = ({ card, subvol, mismount_warning, block, fstab_config, forced_options }) => {
@@ -339,5 +417,10 @@ const BtrfsSubvolumeCard = ({ card, subvol, mismount_warning, block, fstab_confi
+
+
+
);
};
diff --git a/pkg/storaged/btrfs/volume.jsx b/pkg/storaged/btrfs/volume.jsx
index 9d533f159bb0..431c07d34d31 100644
--- a/pkg/storaged/btrfs/volume.jsx
+++ b/pkg/storaged/btrfs/volume.jsx
@@ -33,7 +33,7 @@ import { StorageUsageBar, StorageLink } from "../storage-controls.jsx";
import { fmt_size_long, validate_fsys_label, decode_filename, should_ignore } from "../utils.js";
import { btrfs_usage, btrfs_is_volume_mounted, parse_subvol_from_options } from "./utils.jsx";
import { dialog_open, TextInput } from "../dialog.jsx";
-import { make_btrfs_subvolume_page } from "./subvolume.jsx";
+import { make_btrfs_subvolume_pages } from "./subvolume.jsx";
import { btrfs_device_actions } from "./device.jsx";
const _ = cockpit.gettext;
@@ -161,42 +161,3 @@ const BtrfsSubVolumesCard = ({ card }) => {
);
};
-
-export function make_btrfs_subvolume_pages(parent, volume) {
- const subvols = client.uuids_btrfs_subvols[volume.data.uuid];
- if (subvols) {
- for (const subvol of subvols) {
- make_btrfs_subvolume_page(parent, volume, subvol);
- }
- } else {
- const block = client.blocks[volume.path];
- /*
- * Try to show subvolumes based on fstab entries, this is a bit tricky
- * as mounts where subvolid cannot be shown userfriendly.
- */
- let has_root = false;
- for (const config of block.Configuration) {
- if (config[0] == "fstab") {
- const opts = config[1].opts;
- if (!opts)
- continue;
-
- const fstab_subvol = parse_subvol_from_options(decode_filename(opts.v));
-
- if (fstab_subvol === null)
- continue;
-
- if (fstab_subvol.pathname === "/")
- has_root = true;
-
- if (fstab_subvol.pathname)
- make_btrfs_subvolume_page(parent, volume, fstab_subvol);
- }
- }
-
- if (!has_root) {
- // Always show the root subvolume even when the volume is not mounted.
- make_btrfs_subvolume_page(parent, volume, { pathname: "/", id: 5 });
- }
- }
-}
diff --git a/pkg/storaged/client.js b/pkg/storaged/client.js
index 67693161cce3..5457a35ef49e 100644
--- a/pkg/storaged/client.js
+++ b/pkg/storaged/client.js
@@ -259,7 +259,7 @@ export async function btrfs_poll() {
// ID 257 gen 7 parent 256 top level 256 path one/two
// ID 258 gen 7 parent 257 top level 257 path /one/two/three/four
const output = await cockpit.spawn(["btrfs", "subvolume", "list", "-ap", mount_point], { superuser: true, err: "message" });
- const subvols = [{ pathname: "/", id: 5 }];
+ const subvols = [{ pathname: "/", id: 5, parent: null }];
for (const line of output.split("\n")) {
const m = line.match(/ID (\d+).*parent (\d+).*path (\/)?(.*)/);
if (m)
diff --git a/test/reference b/test/reference
index 7f14132d3bd1..55fe3409efe8 160000
--- a/test/reference
+++ b/test/reference
@@ -1 +1 @@
-Subproject commit 7f14132d3bd19af16fb112a97a0d897e4794be56
+Subproject commit 55fe3409efe803b8cbc8a77309ee4131e4f06ca0
diff --git a/test/verify/check-storage-basic b/test/verify/check-storage-basic
index 7208216c4af2..16f5a2ae1382 100755
--- a/test/verify/check-storage-basic
+++ b/test/verify/check-storage-basic
@@ -77,7 +77,7 @@ class TestStorageBasic(storagelib.StorageCase):
long = "really-" * 15 + "long-name-that-will-be-truncated"
m.execute(f"btrfs subvol create /{long}")
self.addCleanup(m.execute, f"btrfs subvol delete /{long}")
- b.wait_visible(self.card_row("Storage", name=f"root/{long}"))
+ b.wait_visible(self.card_row("Storage", name=long))
b.assert_pixels(self.card("Storage"), "overview",
# Usage numbers are not stable and also cause
# the table columns to shift. The usage bars
diff --git a/test/verify/check-storage-btrfs b/test/verify/check-storage-btrfs
index 992013eba336..c5ca64b94f1d 100755
--- a/test/verify/check-storage-btrfs
+++ b/test/verify/check-storage-btrfs
@@ -195,9 +195,9 @@ class TestStorageBtrfs(storagelib.StorageCase):
# Finding the correct subvolume parent from a non-mounted subvolume
m.execute(f"btrfs subvolume create {subvol_mount_point}/pizza")
- self.click_dropdown(self.card_row("Storage", name=f"{subvol_mount}/pizza"), "Create subvolume")
+ self.click_dropdown(self.card_row("Storage", name="pizza"), "Create subvolume")
self.dialog({"name": "pineapple"}, secondary=True)
- b.wait_in_text(self.card_row("Storage", name=f"{subvol_mount}/pizza/pineapple"), "btrfs subvolume")
+ b.wait_in_text(self.card_row("Storage", name="pineapple"), "btrfs subvolume")
left_subvol_mount = "/run/left"
right_subvol_mount = "/run/right"
@@ -214,11 +214,11 @@ class TestStorageBtrfs(storagelib.StorageCase):
self.click_dropdown(self.card_row("Storage", location=left_subvol_mount), "Create subvolume")
self.dialog({"name": "links"}, secondary=True)
- b.wait_in_text(self.card_row("Storage", name="left/links"), "btrfs subvolume")
+ b.wait_in_text(self.card_row("Storage", name="links"), "btrfs subvolume")
self.click_dropdown(self.card_row("Storage", location=right_subvol_mount), "Create subvolume")
self.dialog({"name": "rechts"}, secondary=True)
- b.wait_in_text(self.card_row("Storage", name="right/rechts"), "btrfs subvolume")
+ b.wait_in_text(self.card_row("Storage", name="rechts"), "btrfs subvolume")
# Read only mount, cannot create subvolumes once /run/butter
# is unmounted.
@@ -232,7 +232,7 @@ class TestStorageBtrfs(storagelib.StorageCase):
self.click_dropdown(self.card_row("Storage", location=ro_subvol), "Create subvolume")
self.dialog({"name": "bot"}, secondary=True)
- b.wait_visible(self.card_row("Storage", name="ro/bot"))
+ b.wait_visible(self.card_row("Storage", name="bot"))
# But once /run/butter has been unmounted, we can't create
# subvolumes of "ro" anymore.
@@ -252,8 +252,7 @@ class TestStorageBtrfs(storagelib.StorageCase):
mount -o remount,ro /dev/sda {ro_subvol}
""")
- subvol_loc = f"{os.path.basename(ro_subvol)}/readonly"
- self.check_dropdown_action_disabled(self.card_row("Storage", name=subvol_loc), "Create subvolume", "Subvolume needs to be mounted")
+ self.check_dropdown_action_disabled(self.card_row("Storage", name="readonly"), "Create subvolume", "Subvolume needs to be mounted")
def testDeleteSubvolume(self):
m = self.machine
@@ -301,16 +300,16 @@ class TestStorageBtrfs(storagelib.StorageCase):
self.click_dropdown(self.card_row("Storage", name=subvol), "Create subvolume")
self.dialog({"name": child_subvol}, secondary=True)
- b.wait_visible(self.card_row("Storage", name=f"{subvol}/{child_subvol}"))
+ b.wait_visible(self.card_row("Storage", name=child_subvol))
self.click_dropdown(self.card_row("Storage", name=subvol), "Delete")
- self.checkTeardownAction(1, "Device", f"{subvol}/{child_subvol}")
+ self.checkTeardownAction(1, "Device", child_subvol)
self.checkTeardownAction(1, "Action", "delete")
self.checkTeardownAction(2, "Device", subvol)
self.checkTeardownAction(2, "Action", "delete")
self.confirm()
- b.wait_not_present(self.card_row("Storage", name=f"{subvol}/{child_subvol}"))
+ b.wait_not_present(self.card_row("Storage", name=child_subvol))
b.wait_not_present(self.card_row("Storage", name=subvol))
# Delete with subvolume with children and self mounted
@@ -322,10 +321,10 @@ class TestStorageBtrfs(storagelib.StorageCase):
self.click_dropdown(self.card_row("Storage", name=subvol), "Create subvolume")
self.dialog({"name": child_subvol}, secondary=True)
- b.wait_visible(self.card_row("Storage", name=f"{subvol}/{child_subvol}"))
+ b.wait_visible(self.card_row("Storage", name=child_subvol))
self.click_dropdown(self.card_row("Storage", name=subvol), "Delete")
- self.checkTeardownAction(1, "Device", f"{subvol}/{child_subvol}")
+ self.checkTeardownAction(1, "Device", child_subvol)
self.checkTeardownAction(1, "Action", "delete")
self.checkTeardownAction(1, "Device", subvol)
self.checkTeardownAction(2, "Location", subvol_mount_point)
@@ -375,10 +374,10 @@ class TestStorageBtrfs(storagelib.StorageCase):
self.click_dropdown(self.card_row("Storage", name=subvol), "Create subvolume")
self.dialog({"name": child_subvol}, secondary=True)
- b.wait_visible(self.card_row("Storage", name=f"{subvol}/{child_subvol}"))
+ b.wait_visible(self.card_row("Storage", name=child_subvol))
# Allowed as root is mounted
- self.click_dropdown(self.card_row("Storage", name=f"{subvol}/{child_subvol}"), "Delete")
+ self.click_dropdown(self.card_row("Storage", name=child_subvol), "Delete")
self.dialog_wait_open()
self.dialog_cancel()
@@ -387,7 +386,7 @@ class TestStorageBtrfs(storagelib.StorageCase):
self.confirm()
b.wait_visible(self.card_row("Storage", location=f"{mount_point} (not mounted)"))
- self.check_dropdown_action_disabled(self.card_row("Storage", name=f"{subvol}/{child_subvol}"), "Delete", "At least one parent needs to be mounted writable")
+ self.check_dropdown_action_disabled(self.card_row("Storage", name=child_subvol), "Delete", "At least one parent needs to be mounted writable")
def testMultiDevice(self):
m = self.machine
@@ -575,7 +574,7 @@ class TestStorageBtrfs(storagelib.StorageCase):
disk = self.add_ram_disk(size=128)
- m.execute(f"mkfs.btrfs -L butter {disk}; mount {disk} /mnt; btrfs subvolume create /mnt/home; btrfs subvolume create /mnt/backups")
+ m.execute(f"mkfs.btrfs -L butter {disk}; mount {disk} /mnt; btrfs subvolume create /mnt/home; btrfs subvolume create /mnt/home/backups")
m.execute("while mountpoint -q /mnt && ! umount /mnt; do sleep 0.2; done;")
self.login_and_go("/storage")
@@ -588,7 +587,7 @@ class TestStorageBtrfs(storagelib.StorageCase):
# subvolumes mentioned in them and show them.
m.execute(f"echo >>/etc/fstab '{disk} /mnt/home auto noauto,subvol=home 0 0'")
- m.execute(f"echo >>/etc/fstab '{disk} /mnt/backups auto noauto,subvol=backups 0 0'")
+ m.execute(f"echo >>/etc/fstab '{disk} /mnt/backups auto noauto,subvol=home/backups 0 0'")
b.wait_text(self.card_row_col("btrfs filesystem", row_name="home", col_index=3), "/mnt/home (not mounted)")
b.wait_text(self.card_row_col("btrfs filesystem", row_name="backups", col_index=3), "/mnt/backups (not mounted)")
diff --git a/test/verify/check-storage-scaling b/test/verify/check-storage-scaling
index 9b4043f3deb1..9f080b62eea2 100755
--- a/test/verify/check-storage-scaling
+++ b/test/verify/check-storage-scaling
@@ -35,7 +35,7 @@ class TestStorageScaling(storagelib.StorageCase):
b.wait_visible(self.card_row("Storage", name="/dev/vda"))
# Wait on btrfs subvolumes on OS'es with the install on btrfs
if m.image.startswith('fedora'):
- b.wait_in_text(self.card_row("Storage", name="root/var/lib/machines"), "btrfs subvolume")
+ b.wait_in_text(self.card_row("Storage", name="var/lib/machines"), "btrfs subvolume")
elif m.image == "arch":
b.wait_in_text(self.card_row("Storage", name="swap"), "btrfs subvolume")
n_rows = count_rows()