LCOV - code coverage report
Current view: top level - pkg/storaged/btrfs - volume.jsx Coverage Total Hit
Test: cockpit Lines: 94.2 % 154 145
Test Date: 2026-07-13 10:00:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2023 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import cockpit from "cockpit";
       7          113 : import React from "react";
       8              : import client from "../client";
       9              : 
      10              : import { CardHeader, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
      11              : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
      12              : import { VolumeIcon } from "../icons/gnome-icons.jsx";
      13              : 
      14              : import {
      15              :     new_card, new_page, PAGE_CATEGORY_VIRTUAL,
      16              :     get_crossrefs, ChildrenTable, PageTable, StorageCard, StorageDescription,
      17              :     navigate_away_from_card
      18              : } from "../pages.jsx";
      19              : import { StorageUsageBar, StorageLink } from "../storage-controls.jsx";
      20              : import {
      21              :     fmt_size_long, validate_fsys_label, should_ignore,
      22              :     get_active_usage, teardown_active_usage,
      23              :     reload_systemd,
      24              : } from "../utils.js";
      25              : import { btrfs_usage } from "./utils.jsx";
      26              : import {
      27              :     dialog_open, TextInput,
      28              :     BlockingMessage, TeardownMessage,
      29              :     init_teardown_usage
      30              : } from "../dialog.jsx";
      31              : import { make_btrfs_subvolume_pages } from "./subvolume.jsx";
      32              : import { btrfs_device_actions } from "./device.jsx";
      33              : 
      34          113 : const _ = cockpit.gettext;
      35              : 
      36              : /*
      37              :  * Udisks is a disk/block library so it manages that, btrfs turns this a bit
      38              :  * around and has one "volume" which can have multiple blocks by a unique uuid.
      39              :  *
      40              :  * Cockpit shows Btrfs as following:
      41              :  *
      42              :  * -> btrfs subvolume
      43              :  *    -> btrfs volume
      44              :  *        -> btrfs device
      45              :  *            -> block device
      46              :  */
      47              : 
      48            1 : async function btrfs_delete(uuid, card) {
      49            1 :     const volume = client.uuids_btrfs_volume[uuid];
      50            0 :     const name = volume.data.label || uuid;
      51              : 
      52            1 :     const usage = get_active_usage(client, volume.path, _("delete"), undefined, false, undefined, true);
      53              : 
      54            0 :     if (usage.Blocking) {
      55            0 :         dialog_open({
      56            0 :             Title: cockpit.format(_("$0 is in use"), name),
      57            0 :             Body: BlockingMessage(usage)
      58            0 :         });
      59            0 :         return;
      60            0 :     }
      61              : 
      62            1 :     dialog_open({
      63            1 :         Title: cockpit.format(_("Permanently delete $0?"), name),
      64            1 :         Teardown: TeardownMessage(usage),
      65            1 :         Action: {
      66            1 :             Danger: _("Deleting erases all data on a btrfs volume."),
      67            1 :             Title: _("Delete"),
      68            1 :             disable_on_error: usage.Teardown,
      69            1 :             action: async function () {
      70            1 :                 await teardown_active_usage(client, usage);
      71            1 :                 const blocks = client.uuids_btrfs_blocks[uuid];
      72            1 :                 for (let i = 0; i < blocks.length; i++) {
      73              :                     // All block devices share the same Configuration
      74              :                     // property, so we only tear down the first.
      75            1 :                     await blocks[i].Format("empty", { 'tear-down': { t: 'b', v: i == 0 } });
      76            1 :                 }
      77            1 :                 await reload_systemd();
      78            1 :                 navigate_away_from_card(card);
      79            1 :             }
      80            1 :         },
      81            1 :         Inits: [
      82            1 :             init_teardown_usage(client, usage)
      83            1 :         ]
      84            1 :     });
      85            1 : }
      86              : 
      87          112 : export function make_btrfs_volume_page(parent, uuid) {
      88          112 :     const block_devices = client.uuids_btrfs_blocks[uuid];
      89          112 :     const block_btrfs = client.blocks_fsys_btrfs[block_devices[0].path];
      90          112 :     const volume = client.uuids_btrfs_volume[uuid];
      91          112 :     const use = btrfs_usage(client, volume);
      92              : 
      93          112 :     if (block_devices.some(blk => should_ignore(client, blk.path)))
      94          112 :         return;
      95              : 
      96              :     // Single-device btrfs volumes are shown directly on the page of
      97              :     // their device; they don't get a standalone "btrfs volume" page.
      98          104 :     if (block_btrfs.data.num_devices === 1)
      99          104 :         return;
     100              : 
     101           13 :     const name = block_btrfs.data.label || uuid;
     102          112 :     const btrfs_volume_card = new_card({
     103          112 :         title: _("btrfs volume"),
     104          112 :         next: null,
     105          112 :         page_location: ["btrfs-volume", uuid],
     106          112 :         page_name: name,
     107          112 :         page_icon: VolumeIcon,
     108          112 :         page_category: PAGE_CATEGORY_VIRTUAL,
     109          112 :         page_size: use[1],
     110          112 :         component: BtrfsVolumeCard,
     111          112 :         props: { block_devices, uuid, use },
     112          112 :         actions: [
     113          112 :             {
     114          112 :                 title: _("Delete filesystem"),
     115            1 :                 action: () => btrfs_delete(uuid, btrfs_volume_card),
     116          112 :                 danger: true,
     117          112 :             },
     118          112 :         ],
     119          112 :     });
     120              : 
     121          112 :     const subvolumes_card = make_btrfs_subvolumes_card(btrfs_volume_card, null, null);
     122              : 
     123          112 :     const subvolumes_page = new_page(parent, subvolumes_card);
     124          112 :     make_btrfs_subvolume_pages(subvolumes_page, volume);
     125          112 : }
     126              : 
     127            1 : function rename_dialog(block_btrfs, label, rw_mount_point) {
     128            1 :     dialog_open({
     129            1 :         Title: _("Change label"),
     130            1 :         Fields: [
     131            1 :             TextInput("name", _("Name"),
     132            1 :                       {
     133            1 :                           validate: name => validate_fsys_label(name, "btrfs"),
     134            1 :                           value: label
     135            1 :                       })
     136            1 :         ],
     137            1 :         Action: {
     138            1 :             Title: _("Save"),
     139            1 :             action: async function (vals) {
     140            1 :                 if (rw_mount_point) {
     141            1 :                     await cockpit.spawn(["btrfs", "filesystem", "label", rw_mount_point, vals.name],
     142            1 :                                         { superuser: true });
     143            1 :                     const block = client.blocks[block_btrfs.path];
     144            1 :                     await block.Rescan({});
     145            1 :                 } else
     146            1 :                     await block_btrfs.SetLabel(vals.name, {});
     147            1 :             }
     148            1 :         }
     149            1 :     });
     150            1 : }
     151              : 
     152            8 : export const BtrfsLabelDescription = ({ block_btrfs }) => {
     153            0 :     const label = block_btrfs.data.label || "-";
     154              : 
     155              :     // We can change the label when at least one filesystem subvolume
     156              :     // is mounted rw, or when nothing is mounted.
     157              : 
     158            8 :     let rw_mount_point = null;
     159            8 :     let is_mounted = false;
     160            8 :     const mount_points = client.btrfs_mounts[block_btrfs.data.uuid];
     161            6 :     for (const id in mount_points) {
     162            6 :         const mp = mount_points[id];
     163            6 :         if (mp.mount_points.length > 0)
     164            6 :             is_mounted = true;
     165            6 :         if (mp.rw_mount_points.length > 0 && !rw_mount_point)
     166            6 :             rw_mount_point = mp.rw_mount_points[0];
     167            6 :     }
     168              : 
     169            6 :     let excuse = null;
     170            6 :     if (is_mounted && !rw_mount_point)
     171            1 :         excuse = _("Filesystem is mounted read-only");
     172              : 
     173            8 :     return <StorageDescription title={_("Label")}
     174            8 :                                value={label}
     175            8 :                                action={
     176            1 :                                    <StorageLink onClick={() => rename_dialog(block_btrfs, label, rw_mount_point)}
     177            8 :                                                 excuse={excuse}>
     178            8 :                                        {_("edit")}
     179            8 :                                    </StorageLink>}
     180            8 :     />;
     181            8 : };
     182              : 
     183            1 : const BtrfsVolumeCard = ({ card, block_devices, uuid, use }) => {
     184            1 :     const block_btrfs = client.blocks_fsys_btrfs[block_devices[0].path];
     185              : 
     186            1 :     return (
     187            1 :         <StorageCard card={card}>
     188            1 :             <CardBody>
     189            1 :                 <DescriptionList className="pf-m-horizontal-on-sm">
     190            1 :                     <BtrfsLabelDescription block_btrfs={block_btrfs} />
     191            1 :                     <StorageDescription title={_("UUID")} value={uuid} />
     192            1 :                     <StorageDescription title={_("Capacity")} value={fmt_size_long(use[1])} />
     193            1 :                     <StorageDescription title={_("Usage")}>
     194            1 :                         <StorageUsageBar key="s" stats={use} />
     195            1 :                     </StorageDescription>
     196            1 :                 </DescriptionList>
     197            1 :             </CardBody>
     198            1 :             <CardHeader><strong>{_("btrfs devices")}</strong></CardHeader>
     199            1 :             <PageTable
     200            1 :                 emptyCaption={_("No devices found")}
     201            1 :                 aria-label={_("btrfs device")}
     202            1 :                 crossrefs={get_crossrefs(uuid)} />
     203            1 :         </StorageCard>
     204              :     );
     205            1 : };
     206              : 
     207            2 : export function make_btrfs_subvolumes_card(next, block, backing_block) {
     208            2 :     return new_card({
     209            2 :         title: _("btrfs subvolumes"),
     210            2 :         next,
     211            2 :         actions: btrfs_device_actions(block, backing_block),
     212            2 :         component: BtrfsSubVolumesCard,
     213            2 :     });
     214            2 : }
     215              : 
     216            1 : const BtrfsSubVolumesCard = ({ card }) => {
     217            1 :     return (
     218            1 :         <StorageCard card={card}>
     219            1 :             <ChildrenTable
     220            1 :                 emptyCaption={_("No subvolumes")}
     221            1 :                 aria-label={_("btrfs subvolumes")}
     222            1 :                 page={card.page} />
     223            1 :         </StorageCard>
     224              :     );
     225            1 : };
        

Generated by: LCOV version 2.0-1