LCOV - code coverage report
Current view: top level - pkg/storaged/filesystem - utils.jsx Coverage Total Hit
Test: cockpit Lines: 96.6 % 178 172
Test Date: 2026-07-17 14:32:59

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2016 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import React from "react";
       7              : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
       8              : 
       9          113 : import cockpit from "cockpit";
      10              : import client from "../client.js";
      11              : 
      12              : import {
      13              :     block_name, decode_filename,
      14              :     parse_options, extract_option,
      15              :     get_fstab_config_with_client,
      16              :     find_children_for_mount_point,
      17              :     get_mount_points,
      18              : } from "../utils.js";
      19              : import { parse_subvol_from_options } from "../btrfs/utils.jsx";
      20              : import { StorageLink } from "../storage-controls.jsx";
      21              : 
      22              : import { mounting_dialog } from "./mounting-dialog.jsx";
      23              : 
      24          113 : const _ = cockpit.gettext;
      25              : 
      26              : /* is mounted with an entry in fstab */
      27          108 : export function is_mounted(client, block, subvol) {
      28          108 :     const block_fsys = client.blocks_fsys[block.path];
      29          108 :     const mounted_at = get_mount_points(client, block_fsys, subvol);
      30          108 :     const [, dir] = get_fstab_config(block, false, subvol);
      31          106 :     if (dir) {
      32          106 :         return mounted_at.indexOf(dir) >= 0;
      33          106 :     } else
      34          108 :         return null;
      35          108 : }
      36              : 
      37          112 : export function get_fstab_config(block, also_child_config, subvol) {
      38          112 :     return get_fstab_config_with_client(client, block, also_child_config, subvol);
      39          112 : }
      40              : 
      41            3 : function nice_block_name(block) {
      42            1 :     return block_name(client.blocks[block.CryptoBackingDevice] || block);
      43            3 : }
      44              : 
      45           48 : function find_blocks_for_mount_point(client, mount_point, self_block, self_subvol) {
      46            7 :     function same_btrfs_volume(a, b) {
      47            7 :         return (client.blocks_fsys_btrfs[a.path] &&
      48            7 :                 client.blocks_fsys_btrfs[b.path] &&
      49            7 :                 client.blocks_fsys_btrfs[a.path].data.uuid == client.blocks_fsys_btrfs[b.path].data.uuid);
      50            7 :     }
      51              : 
      52            7 :     function same_btrfs_subvol(a, b) {
      53            7 :         return (a && b &&
      54            6 :                 ((a.pathname && a.pathname == b.pathname) ||
      55            0 :                  (a.id && a.id == b.id)));
      56            7 :     }
      57              : 
      58           17 :     function is_self(b, subvol) {
      59           17 :         if (client.blocks_fsys_btrfs[self_block.path])
      60            0 :             return same_btrfs_volume(b, self_block) && same_btrfs_subvol(subvol, self_subvol);
      61              :         else
      62            4 :             return self_block && (b == self_block || client.blocks[b.CryptoBackingDevice] == self_block);
      63           17 :     }
      64              : 
      65            3 :     function fmt_block_and_subvol(block, subvol) {
      66            3 :         if (subvol)
      67            2 :             return cockpit.format(_("btrfs subvolume $0 of $1"),
      68            0 :                                   (subvol.pathname == "/" || subvol.id == 5) ? "top-level" : subvol.pathname || subvol.id,
      69            0 :                                   block.IdLabel || block.IdUUID);
      70              :         else
      71            1 :             return nice_block_name(block);
      72            3 :     }
      73              : 
      74           48 :     const blocks = [];
      75           48 :     const seen_uuids = {};
      76              : 
      77           48 :     for (const p in client.blocks) {
      78           48 :         const b = client.blocks[p];
      79           48 :         for (const c of b.Configuration) {
      80           48 :             if (c[0] == "fstab") {
      81           48 :                 let dir = decode_filename(c[1].dir.v);
      82           48 :                 if (dir[0] != "/")
      83            0 :                     dir = "/" + dir;
      84           48 :                 const subvol = parse_subvol_from_options(decode_filename(c[1].opts.v));
      85            3 :                 if (dir == mount_point && !is_self(b, subvol)) {
      86            3 :                     if (!seen_uuids[b.IdUUID]) {
      87            3 :                         seen_uuids[b.IdUUID] = true;
      88            3 :                         blocks.push(fmt_block_and_subvol(b, subvol));
      89            3 :                     }
      90            3 :                 }
      91           48 :             }
      92           48 :         }
      93           48 :     }
      94              : 
      95           48 :     return blocks;
      96           48 : }
      97              : 
      98           52 : export function is_valid_mount_point(client, block, val, will_not_mount, allow_empty, subvol) {
      99           13 :     if (val === "") {
     100            3 :         if (!will_not_mount && !allow_empty)
     101            3 :             return _("Mount point cannot be empty");
     102           12 :         return null;
     103           12 :     }
     104              : 
     105           48 :     const other_blocks = find_blocks_for_mount_point(client, val, block, subvol);
     106           48 :     if (other_blocks.length > 0)
     107            3 :         return cockpit.format(_("Mount point is already used for $0"), other_blocks.join(", "));
     108              : 
     109           38 :     if (!will_not_mount) {
     110           38 :         const children = find_children_for_mount_point(client, val, block, subvol);
     111           38 :         if (Object.keys(children).length > 0)
     112            2 :             return <>
     113            2 :                 {_("Filesystems are already mounted below this mountpoint.")}
     114            2 :                 {Object.keys(children).map(m => <div key={m}>
     115            2 :                     {cockpit.format("• $0 on $1", nice_block_name(children[m]),
     116            2 :                                     client.strip_mount_point_prefix(m))}
     117            2 :                 </div>)}
     118            2 :                 {_("Please unmount them first.")}
     119            2 :             </>;
     120           38 :     }
     121           52 : }
     122              : 
     123          101 : export function get_cryptobacking_noauto(client, block) {
     124           25 :     const crypto_backing = block.IdUsage == "crypto" ? block : client.blocks[block.CryptoBackingDevice];
     125          101 :     if (!crypto_backing)
     126          101 :         return false;
     127              : 
     128           15 :     const crypto_config = crypto_backing.Configuration.find(c => c[0] == "crypttab");
     129           26 :     if (!crypto_config)
     130           26 :         return false;
     131              : 
     132           25 :     const crypto_options = decode_filename(crypto_config[1].options.v).split(",")
     133           15 :             .map(o => o.trim());
     134           25 :     return crypto_options.indexOf("noauto") >= 0;
     135          101 : }
     136              : 
     137            6 : export function edit_mount_point(block, forced_options, subvol) {
     138            6 :     mounting_dialog(client, block, "update", forced_options, subvol);
     139            6 : }
     140              : 
     141           48 : export const MountPoint = ({ fstab_config, forced_options, backing_block, content_block, subvol }) => {
     142           47 :     const is_filesystem_mounted = content_block && is_mounted(client, content_block, subvol);
     143           48 :     const [, old_dir, old_opts] = fstab_config;
     144           48 :     const split_options = parse_options(old_opts);
     145           48 :     extract_option(split_options, "noauto");
     146           48 :     const opt_ro = extract_option(split_options, "ro");
     147           48 :     const opt_never_auto = extract_option(split_options, "x-cockpit-never-auto");
     148           48 :     const opt_nofail = extract_option(split_options, "nofail");
     149           48 :     const opt_netdev = extract_option(split_options, "_netdev");
     150           48 :     if (forced_options)
     151           14 :         for (const opt of forced_options)
     152           14 :             extract_option(split_options, opt);
     153              : 
     154           48 :     let mount_point_text = null;
     155           39 :     if (old_dir) {
     156           39 :         mount_point_text = client.strip_mount_point_prefix(old_dir);
     157           39 :         let opt_texts = [];
     158           39 :         if (opt_ro)
     159            3 :             opt_texts.push(_("read only"));
     160           39 :         if (opt_never_auto)
     161           10 :             opt_texts.push(_("never mount at boot"));
     162           38 :         else if (opt_netdev)
     163            5 :             opt_texts.push(_("after network"));
     164           36 :         else if (opt_nofail)
     165            7 :             opt_texts.push(_("ignore failure"));
     166              :         else
     167           12 :             opt_texts.push(_("stop boot on failure"));
     168           39 :         opt_texts = opt_texts.concat(split_options);
     169           36 :         if (opt_texts.length && !client.in_anaconda_mode()) {
     170           36 :             mount_point_text = cockpit.format("$0 ($1)", mount_point_text, opt_texts.join(", "));
     171           36 :         }
     172           39 :     }
     173              : 
     174           48 :     let extra_text = null;
     175            5 :     if (client.in_anaconda_mode()) {
     176            5 :         if (!old_dir)
     177            2 :             extra_text = _("The filesystem has no assigned mount point.");
     178            2 :     } else {
     179           34 :         if (!is_filesystem_mounted) {
     180           34 :             if (!old_dir)
     181            8 :                 extra_text = _("The filesystem has no permanent mount point.");
     182              :             else
     183           20 :                 extra_text = _("The filesystem is not mounted.");
     184            8 :         } else if (backing_block != content_block) {
     185           12 :             if (!opt_never_auto)
     186           11 :                 extra_text = _("The filesystem will be unlocked and mounted on the next boot. This might require inputting a passphrase.");
     187           12 :         }
     188           45 :     }
     189              : 
     190           22 :     if (!mount_point_text) {
     191           22 :         mount_point_text = extra_text;
     192           22 :         extra_text = null;
     193           22 :     }
     194              : 
     195           48 :     if (extra_text)
     196           24 :         extra_text = <><br />{extra_text}</>;
     197              : 
     198           48 :     return (
     199           48 :         <>
     200           48 :             <Flex>
     201           48 :                 { mount_point_text &&
     202           48 :                 <FlexItem>{ mount_point_text }</FlexItem>
     203              :                 }
     204           48 :                 <FlexItem>
     205            0 :                     <StorageLink onClick={() => edit_mount_point(content_block || backing_block,
     206            4 :                                                                  forced_options, subvol)}>
     207           48 :                         {_("edit")}
     208           48 :                     </StorageLink>
     209           48 :                 </FlexItem>
     210           48 :             </Flex>
     211           48 :             { extra_text }
     212           48 :         </>);
     213           48 : };
     214              : 
     215          108 : export const mount_point_text = (mount_point, mounted) => {
     216          108 :     let mp_text;
     217          106 :     if (mount_point) {
     218          106 :         mp_text = client.strip_mount_point_prefix(mount_point);
     219          106 :         if (mp_text == false)
     220           12 :             return null;
     221           56 :         if (!mounted && !client.in_anaconda_mode())
     222           51 :             mp_text = mp_text + " " + _("(not mounted)");
     223          106 :     } else {
     224          108 :         if (client.in_anaconda_mode())
     225           12 :             mp_text = _("(no assigned mount point)");
     226              :         else
     227          101 :             mp_text = _("(not mounted)");
     228          108 :     }
     229          108 :     return mp_text;
     230          108 : };
        

Generated by: LCOV version 2.0-1