LCOV - code coverage report
Current view: top level - pkg/storaged - utils.js Coverage Total Hit
Test: cockpit Lines: 92.6 % 834 772
Test Date: 2026-07-03 14:25:32

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2015 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import cockpit from "cockpit";
       7              : 
       8              : import * as service from "service";
       9              : import * as timeformat from "timeformat";
      10              : 
      11          113 : const _ = cockpit.gettext;
      12          113 : const C_ = cockpit.gettext;
      13              : 
      14          113 : export const BTRFS_TOOL_MOUNT_PATH = "/run/cockpit/btrfs/";
      15              : 
      16              : /* UTILITIES
      17              :  */
      18              : 
      19          112 : export function compare_versions(a, b) {
      20          112 :     function to_ints(str) {
      21           12 :         return str.split(".").map(function (s) { return s ? parseInt(s, 10) : 0 });
      22          112 :     }
      23              : 
      24          112 :     const a_ints = to_ints(a);
      25          112 :     const b_ints = to_ints(b);
      26          112 :     const len = Math.min(a_ints.length, b_ints.length);
      27          112 :     let i;
      28              : 
      29          112 :     for (i = 0; i < len; i++) {
      30          112 :         if (a_ints[i] == b_ints[i])
      31          112 :             continue;
      32          112 :         return a_ints[i] - b_ints[i];
      33          112 :     }
      34              : 
      35           34 :     return a_ints.length - b_ints.length;
      36          112 : }
      37              : 
      38          108 : export function parse_options(options) {
      39          108 :     if (options)
      40          105 :         return (options.split(",")
      41          105 :                 .map(function (s) { return s.trim() })
      42          104 :                 .filter(function (s) { return s != "" }));
      43              :     else
      44          107 :         return [];
      45          108 : }
      46              : 
      47           64 : export function unparse_options(split) {
      48           64 :     return split.join(",");
      49           64 : }
      50              : 
      51          109 : export function extract_option(split, opt) {
      52          109 :     const index = split.indexOf(opt);
      53           64 :     if (index >= 0) {
      54           64 :         split.splice(index, 1);
      55           64 :         return true;
      56           64 :     } else {
      57          109 :         return false;
      58          109 :     }
      59          109 : }
      60              : 
      61           16 : export function edit_crypto_config(block, modify) {
      62           16 :     let old_config;
      63           16 :     let new_config;
      64              : 
      65           16 :     function commit() {
      66           16 :         new_config[1]["track-parents"] = { t: 'b', v: true };
      67           16 :         if (old_config)
      68            1 :             return block.UpdateConfigurationItem(old_config, new_config, { });
      69              :         else
      70            4 :             return block.AddConfigurationItem(new_config, { });
      71           16 :     }
      72              : 
      73           16 :     return block.GetSecretConfiguration({}).then(
      74           16 :         function (items) {
      75           15 :             old_config = items.find(c => c[0] == "crypttab");
      76            1 :             new_config = ["crypttab", old_config ? Object.assign({ }, old_config[1]) : { }];
      77              : 
      78              :             // UDisks insists on always having a "passphrase-contents" field when
      79              :             // adding a crypttab entry, but doesn't include one itself when returning
      80              :             // an entry without a stored passphrase.
      81              :             //
      82           16 :             if (!new_config[1]['passphrase-contents'])
      83           16 :                 new_config[1]['passphrase-contents'] = { t: 'ay', v: encode_filename("") };
      84              : 
      85           16 :             return modify(new_config[1], commit);
      86           16 :         });
      87           16 : }
      88              : 
      89           13 : export function set_crypto_options(block, readonly, auto, nofail, netdev) {
      90           13 :     return edit_crypto_config(block, (config, commit) => {
      91            1 :         const opts = config.options ? parse_options(decode_filename(config.options.v)) : [];
      92            7 :         if (readonly !== null) {
      93            7 :             extract_option(opts, "readonly");
      94            7 :             extract_option(opts, "read-only");
      95            7 :             if (readonly)
      96            0 :                 opts.push("readonly");
      97            7 :         }
      98           13 :         if (auto !== null) {
      99           13 :             extract_option(opts, "noauto");
     100           13 :             if (!auto)
     101            9 :                 opts.push("noauto");
     102           13 :         }
     103            7 :         if (nofail !== null) {
     104            7 :             extract_option(opts, "nofail");
     105            7 :             if (nofail)
     106            4 :                 opts.push("nofail");
     107            7 :         }
     108            7 :         if (netdev !== null) {
     109            7 :             extract_option(opts, "_netdev");
     110            7 :             if (netdev)
     111            2 :                 opts.push("_netdev");
     112            7 :         }
     113           13 :         config.options = { t: 'ay', v: encode_filename(unparse_options(opts)) };
     114           13 :         return commit();
     115           13 :     });
     116           13 : }
     117              : 
     118            9 : export function set_crypto_auto_option(block, flag) {
     119            9 :     return set_crypto_options(block, null, flag, null, null);
     120            9 : }
     121              : 
     122          113 : export let hostnamed = cockpit.dbus("org.freedesktop.hostname1").proxy();
     123              : 
     124              : // for unit tests
     125              : let orig_hostnamed;
     126              : 
     127              : export function mock_hostnamed(value) {
     128              :     if (value) {
     129              :         orig_hostnamed = hostnamed;
     130              :         hostnamed = value;
     131              :     } else {
     132              :         hostnamed = orig_hostnamed;
     133              :     }
     134              : }
     135              : 
     136           51 : export function flatten(array_of_arrays) {
     137           51 :     if (array_of_arrays.length > 0)
     138           20 :         return Array.prototype.concat.apply([], array_of_arrays);
     139              :     else
     140           51 :         return [];
     141           51 : }
     142              : 
     143          112 : export const decode_filename = encoded => window.atob(encoded).replace('\u0000', '');
     144           55 : export const encode_filename = decoded => window.btoa(decoded + '\u0000');
     145              : 
     146          106 : export function get_block_mntopts(config) {
     147              :     // treat an absent field as "default", like util-linux
     148           12 :     return (config.opts ? decode_filename(config.opts.v) : "defaults");
     149          106 : }
     150              : 
     151          110 : export function fmt_size(bytes) {
     152          110 :     return cockpit.format_bytes(bytes);
     153          110 : }
     154              : 
     155           62 : export function fmt_size_long(bytes) {
     156           62 :     const with_decimal_unit = cockpit.format_bytes(bytes);
     157           62 :     const with_binary_unit = cockpit.format_bytes(bytes, { base2: true });
     158              :     /* Translators: Used in "..." */
     159           62 :     return with_decimal_unit + ", " + with_binary_unit + ", " + bytes + " " + C_("format-bytes", "bytes");
     160           62 : }
     161              : 
     162              : export function fmt_rate(bytes_per_sec) {
     163              :     return cockpit.format_bytes_per_sec(bytes_per_sec);
     164              : }
     165              : 
     166            1 : export function format_temperature(kelvin) {
     167            1 :     const celsius = kelvin - 273.15;
     168            1 :     const fahrenheit = 9.0 * celsius / 5.0 + 32.0;
     169            1 :     return celsius.toFixed(1) + "° C / " + fahrenheit.toFixed(1) + "° F";
     170            1 : }
     171              : 
     172          101 : export function format_fsys_usage(used, total) {
     173          101 :     let text = "";
     174          101 :     let parts = cockpit.format_bytes(total, { separate: true, precision: 2 });
     175          101 :     text = " / " + parts.join(" ");
     176          101 :     const unit = parts[1];
     177              : 
     178              :     // FIXME: passing explicit unit is deprecated, redesign this
     179          101 :     parts = cockpit.format_bytes(used, unit, { separate: true, precision: 2 });
     180          101 :     return parts[0] + text;
     181          101 : }
     182              : 
     183            5 : export function format_delay(d) {
     184            5 :     return timeformat.distanceToNow(new Date().valueOf() + d);
     185            5 : }
     186              : 
     187           25 : export function format_size_and_text(size, text) {
     188           25 :     return fmt_size(size) + " " + text;
     189           25 : }
     190              : 
     191            4 : export function validate_mdraid_name(name) {
     192            4 :     return validate_lvm2_name(name);
     193            4 : }
     194              : 
     195           15 : export function validate_lvm2_name(name) {
     196           15 :     if (name === "")
     197            0 :         return _("Name cannot be empty.");
     198           15 :     if (name.length > 127)
     199            0 :         return _("Name cannot be longer than 127 characters.");
     200           15 :     const m = name.match(/[^a-zA-Z0-9+._-]/);
     201            1 :     if (m) {
     202            1 :         if (m[0].search(/\s+/) === -1)
     203            0 :             return cockpit.format(_("Name cannot contain the character '$0'."), m[0]);
     204              :         else
     205            1 :             return cockpit.format(_("Name cannot contain whitespace."), m[0]);
     206            1 :     }
     207           15 : }
     208              : 
     209           37 : export function validate_fsys_label(label, type) {
     210           37 :     const fs_label_max = {
     211           37 :         xfs: 12,
     212           37 :         ext4: 16,
     213           37 :         vfat: 11,
     214           37 :         ntfs: 128,
     215           37 :         btrfs: 256,
     216           37 :     };
     217              : 
     218           37 :     const limit = fs_label_max[type.replace("luks+", "")];
     219           37 :     const bytes = new TextEncoder().encode(label);
     220            1 :     if (limit && bytes.length > limit) {
     221              :         // Let's not confuse people with encoding issues unless
     222              :         // they use funny characters.
     223            1 :         if (bytes.length == label.length)
     224            0 :             return cockpit.format(_("Name cannot be longer than $0 characters"), limit);
     225              :         else
     226            0 :             return cockpit.format(_("Name cannot be longer than $0 bytes"), limit);
     227            1 :     }
     228           37 : }
     229              : 
     230          112 : export function block_name(block) {
     231          112 :     return decode_filename(block.PreferredDevice);
     232          112 : }
     233              : 
     234          112 : export function block_short_name(block) {
     235          112 :     return block_name(block).replace(/^\/dev\//, "");
     236          112 : }
     237              : 
     238            7 : export function mdraid_name(mdraid) {
     239            7 :     if (!mdraid.Name)
     240            1 :         return "";
     241              : 
     242            7 :     const parts = mdraid.Name.split(":");
     243              : 
     244            7 :     if (parts.length != 2)
     245            3 :         return mdraid.Name;
     246              : 
     247              :     /* Check the static (from /etc/hostname) and transient (acquired from DHCP server via
     248              :      * NetworkManager → hostnamed, may not exist) host name -- if either one matches, we
     249              :      * consider the RAID a local one and just show the device name.
     250              :      * Otherwise it's a remote one, and include the host in the name.
     251              :      *
     252              :      * However: if we call hostnamed too early, before the dbus.proxy() promise is
     253              :      * fulfilled, it will not be valid yet (hostnamed properties are undefined);
     254              :      * it's too inconvenient to make this function asynchronous, so just don't
     255              :      * show the host name in this case. */
     256            1 :     if (hostnamed.StaticHostname === undefined || parts[0] == hostnamed.StaticHostname || parts[0] == hostnamed.Hostname)
     257            1 :         return parts[1];
     258              :     else
     259            1 :         return cockpit.format(_("$name (from $host)"),
     260            1 :                               {
     261            1 :                                   name: parts[1],
     262            1 :                                   host: parts[0]
     263            1 :                               });
     264            7 : }
     265              : 
     266            0 : export function lvol_name(lvol) {
     267            0 :     let type;
     268            0 :     if (lvol.Type == "pool")
     269            0 :         type = _("Pool for thin logical volumes");
     270            0 :     else if (lvol.ThinPool != "/")
     271            0 :         type = _("Thin logical volume");
     272            0 :     else if (lvol.Origin != "/")
     273            0 :         type = _("Logical volume (snapshot)");
     274              :     else
     275            0 :         type = _("Logical volume");
     276            0 :     return cockpit.format('$0 "$1"', type, lvol.Name);
     277            0 : }
     278              : 
     279          108 : export function drive_name(drive) {
     280          108 :     const name_parts = [];
     281          108 :     if (drive.Vendor)
     282           81 :         name_parts.push(drive.Vendor);
     283          108 :     if (drive.Model)
     284          108 :         name_parts.push(drive.Model);
     285              : 
     286          108 :     let name = name_parts.join(" ");
     287          108 :     if (drive.Serial)
     288           11 :         name += " (" + drive.Serial + ")";
     289           11 :     else if (drive.WWN)
     290           11 :         name += " (" + drive.WWN + ")";
     291              : 
     292          108 :     return name;
     293          108 : }
     294              : 
     295           89 : export function get_block_link_parts(client, path) {
     296           89 :     let is_part;
     297           89 :     let is_crypt;
     298           89 :     let is_lvol;
     299              : 
     300           89 :     while (true) {
     301           17 :         if (client.blocks_part[path] && client.blocks_ptable[client.blocks_part[path].Table]) {
     302           17 :             is_part = true;
     303           17 :             path = client.blocks_part[path].Table;
     304            4 :         } else if (client.blocks[path] && client.blocks[client.blocks[path].CryptoBackingDevice]) {
     305           20 :             is_crypt = true;
     306           20 :             path = client.blocks[path].CryptoBackingDevice;
     307           20 :         } else
     308           89 :             break;
     309           89 :     }
     310              : 
     311           23 :     if (client.blocks_lvm2[path] && client.lvols[client.blocks_lvm2[path].LogicalVolume])
     312           23 :         is_lvol = true;
     313              : 
     314           89 :     const block = client.blocks[path];
     315           89 :     if (!block)
     316           89 :         return;
     317              : 
     318           89 :     let location;
     319           89 :     let link;
     320            7 :     if (client.mdraids[block.MDRaid]) {
     321            7 :         location = ["mdraid", client.mdraids[block.MDRaid].UUID];
     322            7 :         link = cockpit.format(_("MDRAID device $0"), mdraid_name(client.mdraids[block.MDRaid]));
     323            7 :     } else if (client.blocks_lvm2[path] &&
     324           23 :                client.lvols[client.blocks_lvm2[path].LogicalVolume] &&
     325           23 :                client.vgroups[client.lvols[client.blocks_lvm2[path].LogicalVolume].VolumeGroup]) {
     326           23 :         const target = client.vgroups[client.lvols[client.blocks_lvm2[path].LogicalVolume].VolumeGroup].Name;
     327           23 :         location = ["vg", target];
     328           23 :         link = cockpit.format(_("LVM2 volume group $0"), target);
     329           23 :     } else {
     330           89 :         const vdo = client.legacy_vdo_overlay.find_by_block(block);
     331            1 :         if (vdo) {
     332            1 :             location = ["vdo", vdo.name];
     333            1 :             link = cockpit.format(_("VDO device $0"), vdo.name);
     334            1 :         } else {
     335           89 :             location = [block_short_name(block)];
     336           89 :             if (client.drives[block.Drive])
     337           24 :                 link = drive_name(client.drives[block.Drive]);
     338              :             else
     339           49 :                 link = block_name(block);
     340           89 :         }
     341           89 :     }
     342              : 
     343              :     // Partitions of logical volumes are shown as just logical volumes.
     344           89 :     let format;
     345           23 :     if (is_lvol && is_crypt)
     346            6 :         format = _("Encrypted logical volume of $0");
     347           17 :     else if (is_part && is_crypt)
     348            4 :         format = _("Encrypted partition of $0");
     349           89 :     else if (is_lvol)
     350           23 :         format = _("Logical volume of $0");
     351           89 :     else if (is_part)
     352           17 :         format = _("Partition of $0");
     353           89 :     else if (is_crypt)
     354           13 :         format = _("Encrypted $0");
     355              :     else
     356           89 :         format = "$0";
     357              : 
     358           89 :     return {
     359           89 :         location,
     360           89 :         format,
     361           89 :         link
     362           89 :     };
     363           89 : }
     364              : 
     365              : export function go_to_block(client, path) {
     366              :     const parts = get_block_link_parts(client, path);
     367              :     cockpit.location.go(parts.location);
     368              : }
     369              : 
     370          104 : export function get_partitions(client, block) {
     371          104 :     const partitions = client.blocks_partitions[block.path];
     372              : 
     373          104 :     function process_level(level, container_start, container_size) {
     374          104 :         let n;
     375          104 :         let last_end = container_start;
     376          104 :         const total_end = container_start + container_size;
     377          104 :         let block;
     378          104 :         let start;
     379          104 :         let size;
     380          104 :         let is_container;
     381          104 :         let is_contained;
     382              : 
     383          104 :         const result = [];
     384              : 
     385          104 :         function append_free_space(start, size) {
     386              :             // There is a lot of rounding and aligning going on in
     387              :             // the storage stack.  All of udisks2, libblockdev,
     388              :             // and libparted seem to contribute their own ideas of
     389              :             // where a partition really should start.
     390              :             //
     391              :             // The start of partitions are aggressively rounded
     392              :             // up, sometimes twice, but the end is not aligned in
     393              :             // the same way.  This means that a few megabytes of
     394              :             // free space will show up between partitions.
     395              :             //
     396              :             // We hide these small free spaces because they are
     397              :             // unexpected and can't be used for anything anyway.
     398              :             //
     399              :             // "Small" is anything less than 3 MiB, which seems to
     400              :             // work okay.  (The worst case is probably creating
     401              :             // the first logical partition inside a extended
     402              :             // partition with udisks+libblockdev.  It leads to a 2
     403              :             // MiB gap.)
     404              : 
     405           28 :             if (size >= 3 * 1024 * 1024) {
     406           28 :                 result.push({ type: 'free', start, size });
     407           28 :             }
     408          104 :         }
     409              : 
     410          104 :         for (n = 0; n < partitions.length; n++) {
     411          104 :             block = client.blocks[partitions[n].path];
     412          104 :             start = partitions[n].Offset;
     413          104 :             size = partitions[n].Size;
     414          104 :             is_container = partitions[n].IsContainer;
     415          104 :             is_contained = partitions[n].IsContained;
     416              : 
     417          104 :             if (block === null)
     418          104 :                 continue;
     419              : 
     420          104 :             if (level === 0 && is_contained)
     421          104 :                 continue;
     422              : 
     423           13 :             if (level == 1 && !is_contained)
     424          104 :                 continue;
     425              : 
     426          104 :             if (start < container_start || start + size > container_start + container_size)
     427          104 :                 continue;
     428              : 
     429          104 :             append_free_space(last_end, start - last_end);
     430           13 :             if (is_container) {
     431           13 :                 result.push({
     432           13 :                     type: 'container',
     433           13 :                     block,
     434           13 :                     size,
     435           13 :                     partitions: process_level(level + 1, start, size)
     436           13 :                 });
     437           13 :             } else {
     438          104 :                 result.push({ type: 'block', block });
     439          104 :             }
     440          104 :             last_end = start + size;
     441          104 :         }
     442              : 
     443          104 :         append_free_space(last_end, total_end - last_end);
     444              : 
     445          104 :         return result;
     446          104 :     }
     447              : 
     448          104 :     return process_level(0, 0, block.Size);
     449          104 : }
     450              : 
     451          113 : let available_spaces = [];
     452              : 
     453           26 : export function get_available_spaces() {
     454           15 :     return available_spaces.sort((a, b) => block_cmp(a.block, b.block));
     455           26 : }
     456              : 
     457          112 : export function reset_available_spaces() {
     458          112 :     available_spaces = [];
     459          112 : }
     460              : 
     461           89 : export function register_available_block_space(client, block) {
     462           89 :     const parts = get_block_link_parts(client, block.path);
     463           89 :     const text = cockpit.format(parts.format, parts.link);
     464           89 :     available_spaces.push({ type: 'block', block, size: block.Size, desc: text });
     465           89 : }
     466              : 
     467           18 : export function register_available_free_space(client, block, partition) {
     468           18 :     const link_parts = get_block_link_parts(client, block.path);
     469           18 :     const text = cockpit.format(link_parts.format, link_parts.link);
     470           18 :     available_spaces.push({
     471           18 :         type: 'free',
     472           18 :         block,
     473           18 :         start: partition.start,
     474           18 :         size: partition.size,
     475           18 :         desc: cockpit.format(_("unpartitioned space on $0"), text)
     476           18 :     });
     477           18 : }
     478              : 
     479           23 : export function prepare_available_spaces(client, spcs) {
     480           23 :     function prepare(spc) {
     481           23 :         if (spc.type == 'block')
     482            0 :             return Promise.resolve(spc.block.path);
     483            1 :         else if (spc.type == 'free') {
     484            1 :             const block_ptable = client.blocks_ptable[spc.block.path];
     485            1 :             return block_ptable.CreatePartition(spc.start, spc.size, "", "", { });
     486            1 :         }
     487           23 :     }
     488              : 
     489           23 :     return Promise.all(spcs.map(prepare));
     490           23 : }
     491              : 
     492           55 : export function is_snap(client, block) {
     493           55 :     const block_fsys = client.blocks_fsys[block.path];
     494            3 :     return block_fsys && block_fsys.MountPoints.map(decode_filename).some(mp => mp.indexOf("/snap/") == 0 || mp.indexOf("/var/lib/snapd/snap/") == 0);
     495           55 : }
     496              : 
     497          112 : export function get_other_devices(client) {
     498          112 :     return Object.keys(client.blocks).filter(path => {
     499          112 :         const block = client.blocks[path];
     500          112 :         const block_part = client.blocks_part[path];
     501          112 :         const block_lvm2 = client.blocks_lvm2[path];
     502              : 
     503          112 :         return ((!block_part || block_part.Table == "/") &&
     504          112 :                 block.Drive == "/" &&
     505          112 :                 block.CryptoBackingDevice == "/" &&
     506          112 :                 block.MDRaid == "/" &&
     507           34 :                 (!block_lvm2 || block_lvm2.LogicalVolume == "/") &&
     508          112 :                 !block.HintIgnore &&
     509           90 :                 block.Size > 0 &&
     510           63 :                 !client.legacy_vdo_overlay.find_by_block(block) &&
     511           63 :                 !client.blocks_stratis_fsys[block.path] &&
     512           63 :                 !is_snap(client, block) &&
     513           63 :                 !should_ignore(client, block.path));
     514          112 :     });
     515          112 : }
     516              : 
     517              : /* Comparison function for sorting lists of block devices.
     518              : 
     519              :    We sort by major:minor numbers to get the expected order when
     520              :    there are more than 10 devices of a kind.  For example, if you
     521              :    have 20 loopback devices named loop0 to loop19, sorting them
     522              :    alphabetically would put them in the wrong order
     523              : 
     524              :        loop0, loop1, loop10, loop11, ..., loop2, ...
     525              : 
     526              :    Sorting by major:minor is an easy way to do the right thing.
     527              : */
     528              : 
     529           21 : export function block_cmp(a, b) {
     530           21 :     return a.DeviceNumber - b.DeviceNumber;
     531           21 : }
     532              : 
     533              : export function make_block_path_cmp(client) {
     534              :     return function(path_a, path_b) {
     535              :         return block_cmp(client.blocks[path_a], client.blocks[path_b]);
     536              :     };
     537              : }
     538              : 
     539          113 : let multipathd_service;
     540              : 
     541          112 : export function get_multipathd_service () {
     542          112 :     if (!multipathd_service)
     543          112 :         multipathd_service = service.proxy("multipathd");
     544          112 :     return multipathd_service;
     545          112 : }
     546              : 
     547           56 : function get_parent(client, path) {
     548           14 :     if (client.blocks_part[path] && client.blocks[client.blocks_part[path].Table])
     549           14 :         return client.blocks_part[path].Table;
     550           56 :     if (client.blocks[path] && client.blocks[client.blocks[path].CryptoBackingDevice])
     551            3 :         return client.blocks[path].CryptoBackingDevice;
     552           56 :     if (client.blocks[path] && client.drives[client.blocks[path].Drive])
     553           50 :         return client.blocks[path].Drive;
     554           21 :     if (client.blocks[path] && client.mdraids[client.blocks[path].MDRaid])
     555            3 :         return client.blocks[path].MDRaid;
     556           11 :     if (client.blocks_lvm2[path] && client.lvols[client.blocks_lvm2[path].LogicalVolume])
     557           11 :         return client.blocks_lvm2[path].LogicalVolume;
     558            1 :     if (client.lvols[path] && client.vgroups[client.lvols[path].VolumeGroup])
     559            1 :         return client.lvols[path].VolumeGroup;
     560           13 :     if (client.blocks_stratis_fsys[path])
     561            1 :         return client.blocks_stratis_fsys[path].Pool;
     562           13 :     if (client.vgroups[path])
     563            1 :         return path;
     564           13 :     if (client.stratis_pools[path])
     565            1 :         return path;
     566           56 : }
     567              : 
     568           56 : function get_direct_parent_blocks(client, path) {
     569           56 :     if (client.blocks[path])
     570           56 :         path = get_parent(client, path);
     571           56 :     if (!path)
     572           13 :         return [];
     573           52 :     if (client.blocks[path])
     574           15 :         return [path];
     575           52 :     if (client.mdraids[path])
     576            3 :         return client.mdraids_members[path].map(function (m) { return m.path });
     577           50 :     if (client.lvols[path])
     578           11 :         path = client.lvols[path].VolumeGroup;
     579           50 :     if (client.vgroups[path])
     580           10 :         return client.vgroups_pvols[path].map(function (pv) { return pv.path });
     581           50 :     if (client.stratis_pools[path])
     582            1 :         return client.stratis_pool_blockdevs[path].map(bd => client.slashdevs_block[bd.Devnode].path);
     583           50 :     return [];
     584           56 : }
     585              : 
     586           51 : export function get_parent_blocks(client, path) {
     587           51 :     const direct_parents = get_direct_parent_blocks(client, path);
     588           19 :     const direct_and_indirect_parents = flatten(direct_parents.map(function (p) {
     589           19 :         return get_parent_blocks(client, p);
     590           19 :     }));
     591           51 :     return [path].concat(direct_and_indirect_parents);
     592           51 : }
     593              : 
     594           51 : export function is_netdev(client, path) {
     595           51 :     const block = client.blocks[path];
     596           51 :     const drive = block && client.drives[block.Drive];
     597           45 :     if (drive && drive.Vendor == "LIO-ORG")
     598            1 :         return true;
     599           50 :     if (block && block.Major == 43) // NBD
     600            0 :         return true;
     601           50 :     return false;
     602           51 : }
     603              : 
     604          112 : export function should_ignore(client, path) {
     605          112 :     const block = client.blocks[path];
     606              : 
     607              :     // HACK - https://github.com/stratis-storage/stratisd/issues/3801
     608              :     //
     609              :     // Filter out Stratis private device mapper devices. This normally
     610              :     // happens by setting the DM_UDEV_DISABLE_OTHER_RULES_FLAG in the
     611              :     // udev database (which causes UDisks2 to ignore the block
     612              :     // device), but since Stratis 3.8 the "*-crypt" devices don't have
     613              :     // them.
     614              : 
     615          112 :     if (block && decode_filename(block.PreferredDevice).startsWith("/dev/mapper/stratis-1-private"))
     616           12 :         return true;
     617              : 
     618              :     // Check what Anaconda tells us.
     619              : 
     620          112 :     if (!client.in_anaconda_mode())
     621          102 :         return false;
     622              : 
     623           22 :     const parents = get_direct_parent_blocks(client, path);
     624           22 :     if (parents.length == 0) {
     625           22 :         return block && client.should_ignore_block(block);
     626           22 :     } else {
     627           11 :         return parents.some(p => should_ignore(client, p));
     628           22 :     }
     629          112 : }
     630              : 
     631              : /* GET_CHILDREN gets the direct children of the storage object at
     632              :    PATH, like the partitions of a partitioned block device, or the
     633              :    volume group of a physical volume.  By calling GET_CHILDREN
     634              :    recursively, you can traverse the whole storage hierarchy from
     635              :    hardware drives at the bottom to filesystems at the top.
     636              : 
     637              :    GET_CHILDREN_FOR_TEARDOWN is similar but doesn't consider things
     638              :    like volume groups to be children of their physical volumes.  This
     639              :    is appropriate for teardown processing, where tearing down a
     640              :    physical volume does not imply tearing down the whole volume group
     641              :    with everything that it contains.
     642              : */
     643              : 
     644           70 : function get_children_for_teardown(client, path) {
     645           70 :     const children = [];
     646              : 
     647           11 :     if (client.blocks_cleartext[path]) {
     648           11 :         children.push(client.blocks_cleartext[path].path);
     649           11 :     }
     650              : 
     651            5 :     if (client.blocks_ptable[path]) {
     652            4 :         client.blocks_partitions[path].forEach(function (part) {
     653            4 :             if (!part.IsContainer)
     654            4 :                 children.push(part.path);
     655            4 :         });
     656            5 :     }
     657              : 
     658            1 :     if (client.blocks_part[path] && client.blocks_part[path].IsContainer) {
     659            1 :         const ptable_path = client.blocks_part[path].Table;
     660            1 :         client.blocks_partitions[ptable_path].forEach(function (part) {
     661            1 :             if (part.IsContained)
     662            1 :                 children.push(part.path);
     663            1 :         });
     664            1 :     }
     665              : 
     666            5 :     if (client.vgroups[path]) {
     667            5 :         client.vgroups_lvols[path].forEach(function (lvol) {
     668            5 :             if (client.lvols_block[lvol.path])
     669            4 :                 children.push(client.lvols_block[lvol.path].path);
     670            5 :         });
     671            5 :     }
     672              : 
     673            1 :     if (client.lvols_pool_members[path]) {
     674            1 :         for (const lvol of client.lvols_pool_members[path]) {
     675            1 :             const block = client.lvols_block[lvol.path];
     676            1 :             if (block)
     677            1 :                 children.push(block.path);
     678            1 :         }
     679            1 :     }
     680              : 
     681            3 :     if (client.stratis_pools[path]) {
     682            3 :         client.stratis_pool_filesystems[path].forEach(function (fsys) {
     683            3 :             const block = client.slashdevs_block[fsys.Devnode];
     684            3 :             if (block)
     685            3 :                 children.push(block.path);
     686            3 :         });
     687            3 :     }
     688              : 
     689           70 :     return children;
     690           70 : }
     691              : 
     692            1 : export function get_children(client, path) {
     693            1 :     const children = get_children_for_teardown(client, path);
     694              : 
     695            0 :     if (client.blocks[path]) {
     696            0 :         const mdraid = client.blocks[path].MDRaidMember;
     697            0 :         if (mdraid != "/")
     698            0 :             children.push(mdraid);
     699            0 :     }
     700              : 
     701            0 :     if (client.blocks_pvol[path]) {
     702            0 :         const vgroup = client.blocks_pvol[path].VolumeGroup;
     703            0 :         if (vgroup != "/")
     704            0 :             children.push(vgroup);
     705            0 :     }
     706              : 
     707            0 :     if (client.blocks_stratis_blockdev[path]) {
     708            0 :         const pool = client.blocks_stratis_blockdev[path].Pool;
     709            0 :         if (pool != "/")
     710            0 :             children.push(pool);
     711            0 :     }
     712              : 
     713            1 :     return children;
     714            1 : }
     715              : 
     716              : /**
     717              :  * True if `active_mount` refers to a path strictly below `parent_mount`.
     718              :  *
     719              :  * Examples: `/home` is below `/`; `/srv/jail` is below `/srv`.  `/etc` is not below `/srv`.
     720              :  *
     721              :  * Root is special: the only character shared by `/` and `/home` is the leading slash, so we
     722              :  * cannot require a "/" immediately after the parent string (that would look at `h` in `/home`).
     723              :  */
     724           42 : function is_strict_descendant_mount_path(parent_mount, active_mount) {
     725           42 :     if (active_mount.length <= parent_mount.length)
     726           42 :         return false;
     727           15 :     if (!active_mount.startsWith(parent_mount))
     728           14 :         return false;
     729            2 :     if (parent_mount === "/")
     730            0 :         return true;
     731            2 :     return active_mount[parent_mount.length] === "/";
     732           42 : }
     733              : 
     734           42 : export function find_children_for_mount_point(client, mount_point, self, self_subvol) {
     735           42 :     const children = {};
     736              : 
     737           42 :     function is_self(b) {
     738           36 :         return self && (b == self || client.blocks[b.CryptoBackingDevice] == self);
     739           42 :     }
     740              : 
     741           42 :     for (const p in client.blocks) {
     742           42 :         const b = client.blocks[p];
     743           42 :         const fs = client.blocks_fsys[p];
     744              : 
     745           42 :         if (!fs)
     746           42 :             continue;
     747              : 
     748              :         // Skip self block except btrfs (subvolumes share one block); then ignore only this subvol's mounts.
     749           18 :         if (is_self(b) && !client.blocks_fsys_btrfs[self.path])
     750           42 :             continue;
     751              : 
     752            8 :         const skip = is_self(b) && self_subvol ? get_mount_points(client, fs, self_subvol) : [];
     753              : 
     754           42 :         for (const mp of fs.MountPoints) {
     755           42 :             const active = decode_filename(mp);
     756           42 :             if (skip.includes(active))
     757           42 :                 continue;
     758           42 :             if (is_strict_descendant_mount_path(mount_point, active))
     759            2 :                 children[active] = b;
     760           42 :         }
     761           42 :     }
     762              : 
     763           42 :     return children;
     764           42 : }
     765              : 
     766          112 : export function get_fstab_config_with_client(client, block, also_child_config, subvol) {
     767          108 :     function match(c) {
     768          108 :         if (c[0] != "fstab")
     769           26 :             return false;
     770          102 :         if (subvol !== undefined) {
     771          102 :             if (!c[1].opts)
     772           11 :                 return false;
     773              : 
     774          102 :             const opts = decode_filename(c[1].opts.v).split(",");
     775          102 :             if (opts.indexOf("subvolid=" + subvol.id) >= 0)
     776           11 :                 return true;
     777          102 :             if (opts.indexOf("subvol=" + subvol.pathname) >= 0 || opts.indexOf("subvol=/" + subvol.pathname) >= 0)
     778          102 :                 return true;
     779              : 
     780              :             // btrfs mounted without subvol argument.
     781          102 :             const btrfs_volume = client.blocks_fsys_btrfs[block.path];
     782          102 :             const default_subvolid = client.uuids_btrfs_default_subvol[btrfs_volume.data.uuid];
     783          101 :             if (default_subvolid === subvol.id && !opts.find(o => o.indexOf("subvol=") >= 0 || o.indexOf("subvolid=") >= 0))
     784           13 :                 return true;
     785              : 
     786          102 :             return false;
     787          102 :         }
     788          107 :         return true;
     789          108 :     }
     790              : 
     791          112 :     let config = block.Configuration.find(match);
     792              : 
     793          112 :     if (!config && also_child_config && client.blocks_crypto[block.path])
     794           13 :         config = client.blocks_crypto[block.path]?.ChildConfiguration.find(c => c[0] == "fstab");
     795              : 
     796          107 :     if (config && decode_filename(config[1].type.v) != "swap") {
     797          107 :         const mnt_opts = get_block_mntopts(config[1]).split(",");
     798          107 :         let dir = decode_filename(config[1].dir.v);
     799          107 :         let opts = mnt_opts
     800          106 :                 .filter(function (s) { return s.indexOf("x-parent") !== 0 })
     801          107 :                 .join(",");
     802          107 :         const parents = mnt_opts
     803          106 :                 .filter(function (s) { return s.indexOf("x-parent") === 0 })
     804          107 :                 .join(",");
     805          107 :         if (dir[0] != "/")
     806           12 :             dir = "/" + dir;
     807          107 :         if (opts == "defaults")
     808           16 :             opts = "";
     809          107 :         return [config, dir, opts, parents];
     810          107 :     } else
     811          112 :         return [];
     812          112 : }
     813              : 
     814           70 : export function get_active_usage(client, path, top_action, child_action, is_temporary, subvol, allow_multi_device_delete) {
     815           70 :     function get_usage(usage, path, level) {
     816           70 :         const block = client.blocks[path];
     817           70 :         const fsys = client.blocks_fsys[path];
     818           70 :         const swap = client.blocks_swap[path];
     819           67 :         const mdraid = block && client.mdraids[block.MDRaidMember];
     820           70 :         const pvol = client.blocks_pvol[path];
     821            1 :         const vgroup = pvol && client.vgroups[pvol.VolumeGroup];
     822           67 :         const vdo = block && client.legacy_vdo_overlay.find_by_backing_block(block);
     823           67 :         const stratis_blockdev = block && client.blocks_stratis_blockdev[path];
     824            0 :         const stratis_pool = stratis_blockdev && client.stratis_pools[stratis_blockdev.Pool];
     825           70 :         const btrfs_volume = client.blocks_fsys_btrfs[path];
     826              : 
     827           20 :         get_children_for_teardown(client, path).map(p => get_usage(usage, p, level + 1));
     828              : 
     829           67 :         function get_actions(teardown_action) {
     830           67 :             const actions = [];
     831           67 :             if (teardown_action)
     832           32 :                 actions.push(teardown_action);
     833            1 :             const global_action = (level == 0 || (block && client.blocks[block.CryptoBackingDevice] && level == 1)) ? top_action : child_action || top_action;
     834           67 :             if (global_action)
     835           62 :                 actions.push(global_action);
     836           67 :             return actions;
     837           67 :         }
     838              : 
     839           30 :         function enter_unmount(block, location, is_top) {
     840           30 :             const [, mount_point] = get_fstab_config_with_client(client, block);
     841            3 :             const has_fstab_entry = is_temporary && location == mount_point;
     842              : 
     843              :             // Ignore the secret btrfs mount point unless we are
     844              :             // formatting (in which case subvol is false).
     845            6 :             if (btrfs_volume && subvol && location.startsWith(BTRFS_TOOL_MOUNT_PATH))
     846           30 :                 return;
     847              : 
     848            5 :             for (const u of usage) {
     849            1 :                 if (u.usage == 'mounted' && u.location == location) {
     850            1 :                     if (is_top) {
     851            1 :                         u.actions = get_actions(_("unmount"));
     852            1 :                         u.set_noauto = false;
     853            1 :                     }
     854            1 :                     return;
     855            1 :                 }
     856            5 :             }
     857           30 :             usage.push({
     858           30 :                 level,
     859           30 :                 block,
     860           30 :                 usage: 'mounted',
     861           30 :                 location,
     862           30 :                 has_fstab_entry,
     863            1 :                 set_noauto: !is_top && !is_temporary,
     864            0 :                 actions: (is_top ? get_actions(_("unmount")) : [_("unmount")]).concat(has_fstab_entry ? [_("mount")] : []),
     865            0 :                 blocking: client.strip_mount_point_prefix(location) === false && !location.startsWith(BTRFS_TOOL_MOUNT_PATH),
     866           30 :             });
     867           30 :         }
     868              : 
     869              :         // HACK: get_active_usage is used for mounting and formatting so we use the absence of the subvol argument
     870              :         // to figure out that we want to format this device.
     871              :         // This is separate from the if's below as we also always have to umount the filesystem.
     872              : 
     873              :         // We allow a btrfs volume with one device to be formatted as this
     874              :         // looks the most like a normal filesystem use case.
     875            1 :         if (btrfs_volume && btrfs_volume.data.num_devices !== 1 && !subvol && !allow_multi_device_delete) {
     876            1 :             usage.push({
     877            1 :                 level,
     878            1 :                 usage: 'btrfs-device',
     879            1 :                 block,
     880            1 :                 btrfs_volume,
     881            0 :                 location: btrfs_volume.data.label || btrfs_volume.data.uuid,
     882            1 :                 actions: get_actions(_("remove from btrfs volume")),
     883            1 :                 blocking: true,
     884            1 :             });
     885            1 :         }
     886              : 
     887           70 :         const mount_points = get_mount_points(client, fsys, subvol);
     888           30 :         if (mount_points.length > 0) {
     889           30 :             mount_points.forEach(mp => {
     890           30 :                 const children = find_children_for_mount_point(client, mp, null);
     891           30 :                 for (const c in children)
     892            1 :                     enter_unmount(children[c], c, false);
     893           30 :                 enter_unmount(block, mp, true);
     894           30 :             });
     895            0 :         } else if (swap) {
     896            1 :             if (swap.Active) {
     897            1 :                 usage.push({
     898            1 :                     level,
     899            1 :                     usage: 'swap',
     900            1 :                     block,
     901            1 :                     actions: get_actions(_("stop")),
     902            1 :                 });
     903            1 :             }
     904            0 :         } else if (mdraid) {
     905            1 :             const active_state = mdraid.ActiveDevices.find(as => as[0] == block.path);
     906            1 :             usage.push({
     907            1 :                 level,
     908            1 :                 usage: 'mdraid-member',
     909            1 :                 block,
     910            1 :                 mdraid,
     911            1 :                 location: mdraid_name(mdraid),
     912            1 :                 actions: get_actions(_("remove from MDRAID")),
     913            1 :                 blocking: !(active_state && active_state[1] < 0)
     914            1 :             });
     915            0 :         } else if (vgroup) {
     916            1 :             usage.push({
     917            1 :                 level,
     918            1 :                 usage: 'pvol',
     919            1 :                 block,
     920            1 :                 vgroup,
     921            1 :                 pvol,
     922            1 :                 location: vgroup.Name,
     923            1 :                 actions: get_actions(_("remove from LVM2")),
     924            1 :                 blocking: pvol.FreeSize != pvol.Size
     925            1 :             });
     926            0 :         } else if (vdo) {
     927            0 :             usage.push({
     928            0 :                 level,
     929            0 :                 usage: 'vdo-backing',
     930            0 :                 block,
     931            0 :                 vdo,
     932            0 :                 location: vdo.name,
     933            0 :                 blocking: true
     934            0 :             });
     935            0 :         } else if (stratis_pool) {
     936            0 :             usage.push({
     937            0 :                 level,
     938            0 :                 usage: 'stratis-pool-member',
     939            0 :                 block,
     940            0 :                 stratis_pool,
     941            0 :                 location: stratis_pool.Name,
     942            0 :                 blocking: true
     943            0 :             });
     944            0 :         } else if (block && !client.blocks_cleartext[block.path]) {
     945           64 :             usage.push({
     946           64 :                 level,
     947           64 :                 usage: 'none',
     948           64 :                 block,
     949           64 :                 actions: get_actions(null),
     950           64 :                 blocking: false
     951           64 :             });
     952           64 :         }
     953              : 
     954           70 :         return usage;
     955           70 :     }
     956              : 
     957           70 :     const usage = [];
     958           70 :     get_usage(usage, path, 0);
     959              : 
     960           67 :     usage.Blocking = usage.some(u => u.blocking);
     961           67 :     usage.Teardown = usage.some(u => !u.blocking);
     962              : 
     963           64 :     if (usage.length == 1 && usage[0].level == 0 && usage[0].usage == "none")
     964           61 :         usage.Teardown = false;
     965              : 
     966           70 :     return usage;
     967           70 : }
     968              : 
     969            1 : async function set_fsys_noauto(client, block, mount_point) {
     970            1 :     for (const conf of block.Configuration) {
     971            1 :         if (conf[0] == "fstab" &&
     972            1 :             decode_filename(conf[1].dir.v) == mount_point) {
     973            1 :             const options = parse_options(get_block_mntopts(conf[1]));
     974            1 :             if (options.indexOf("noauto") >= 0)
     975            1 :                 continue;
     976            1 :             options.push("noauto");
     977            1 :             const new_conf = [
     978            1 :                 "fstab",
     979            1 :                 Object.assign({ }, conf[1],
     980            1 :                               {
     981            1 :                                   opts: {
     982            1 :                                       t: 'ay',
     983            1 :                                       v: encode_filename(unparse_options(options))
     984            1 :                                   }
     985            1 :                               })
     986            1 :             ];
     987            1 :             await block.UpdateConfigurationItem(conf, new_conf, { });
     988            1 :         }
     989            1 :     }
     990              : 
     991            1 :     const crypto_backing = client.blocks[block.CryptoBackingDevice];
     992            1 :     if (crypto_backing) {
     993            1 :         const crypto_backing_crypto = client.blocks_crypto[crypto_backing.path];
     994            1 :         await set_crypto_auto_option(crypto_backing, false);
     995            1 :         if (crypto_backing_crypto)
     996            1 :             await crypto_backing_crypto.Lock({});
     997            1 :     }
     998            1 : }
     999              : 
    1000           69 : export function teardown_active_usage(client, usage) {
    1001              :     // The code below is complicated by the fact that the last
    1002              :     // physical volume of a volume group can not be removed
    1003              :     // directly (even if it is completely empty).  We want to
    1004              :     // remove the whole volume group instead in this case.
    1005              :     //
    1006              :     // However, we might be removing the last two (or more)
    1007              :     // physical volumes here, and it is easiest to catch this
    1008              :     // condition upfront by reshuffling the data structures.
    1009              : 
    1010           69 :     async function unmount(mounteds) {
    1011           30 :         for (const m of mounteds) {
    1012           30 :             await client.unmount_at(m.location, m.users);
    1013           30 :             if (m.set_noauto)
    1014            1 :                 await set_fsys_noauto(client, m.block, m.location);
    1015           30 :         }
    1016           69 :     }
    1017              : 
    1018           69 :     async function stop_swap(swaps) {
    1019            1 :         for (const s of swaps) {
    1020            1 :             await client.blocks_swap[s.block.path].Stop({});
    1021            1 :         }
    1022           69 :     }
    1023              : 
    1024           69 :     function mdraid_remove(members) {
    1025            1 :         return Promise.all(members.map(m => m.mdraid.RemoveDevice(m.block.path, { wipe: { t: 'b', v: true } })));
    1026           69 :     }
    1027              : 
    1028           69 :     function pvol_remove(pvols) {
    1029           69 :         const by_vgroup = { };
    1030            1 :         pvols.forEach(function (p) {
    1031            1 :             if (!by_vgroup[p.vgroup.path])
    1032            1 :                 by_vgroup[p.vgroup.path] = [];
    1033            1 :             by_vgroup[p.vgroup.path].push(p.block);
    1034            1 :         });
    1035              : 
    1036            1 :         function handle_vg(p) {
    1037            1 :             const vg = client.vgroups[p];
    1038            1 :             const pvs = by_vgroup[p];
    1039              :             // If we would remove all physical volumes of a volume
    1040              :             // group, remove the whole volume group instead.
    1041            1 :             if (pvs.length == client.vgroups_pvols[p].length) {
    1042            1 :                 return vg.Delete(true, { 'tear-down': { t: 'b', v: true } }).then(reload_systemd);
    1043            1 :             } else {
    1044            1 :                 return Promise.all(pvs.map(pv => vg.RemoveDevice(pv.path, true, {})));
    1045            1 :             }
    1046            1 :         }
    1047              : 
    1048           69 :         return Promise.all(Object.keys(by_vgroup).map(handle_vg));
    1049           69 :     }
    1050              : 
    1051           69 :     return Promise.all(Array.prototype.concat(
    1052           65 :         unmount(usage.filter(function(use) { return use.usage == "mounted" })),
    1053           65 :         stop_swap(usage.filter(function(use) { return use.usage == "swap" })),
    1054           65 :         mdraid_remove(usage.filter(function(use) { return use.usage == "mdraid-member" })),
    1055           65 :         pvol_remove(usage.filter(function(use) { return use.usage == "pvol" }))
    1056           69 :     ));
    1057           69 : }
    1058              : 
    1059           11 : export async function undo_temporary_teardown(client, usage) {
    1060            5 :     for (let i = usage.length - 1; i >= 0; i--) {
    1061            5 :         const u = usage[i];
    1062            3 :         if (u.usage == "mounted" && u.has_fstab_entry) {
    1063            3 :             await client.mount_at(u.block, u.location);
    1064            3 :         }
    1065            5 :     }
    1066           11 : }
    1067              : 
    1068              : // TODO - generalize this to arbitrary number of arguments (when needed)
    1069              : export function fmt_to_array(fmt, arg) {
    1070              :     const index = fmt.indexOf("$0");
    1071              :     if (index >= 0)
    1072              :         return [fmt.slice(0, index), arg, fmt.slice(index + 2)];
    1073              :     else
    1074              :         return [fmt];
    1075              : }
    1076              : 
    1077           64 : export function reload_systemd() {
    1078           64 :     return cockpit.spawn(["systemctl", "daemon-reload"], { superuser: "require", err: "message" });
    1079           64 : }
    1080              : 
    1081            9 : export function is_mounted_synch(block) {
    1082            9 :     return (cockpit.spawn(["findmnt", "-n", "-o", "TARGET", "-S", decode_filename(block.Device)],
    1083            9 :                           { superuser: "require", err: "message" })
    1084            5 :             .then(data => data.trim())
    1085            6 :             .catch(() => false));
    1086            9 : }
    1087              : 
    1088           91 : export function for_each_async(arr, func) {
    1089           69 :     return arr.reduce((promise, elt) => promise.then(() => func(elt)), Promise.resolve());
    1090           91 : }
    1091              : 
    1092              : /*
    1093              :  * Get mount points for a given org.freedesktop.UDisks2.Filesystem object
    1094              :  *
    1095              :  * This generalises getting the given MountPoints of a Filesystem for btrfs and
    1096              :  * other filesystems, for btrfs we want to know if a subvolume is mounted
    1097              :  * anywhere. UDisks is currently not aware of subvolumes and it's Filesystem
    1098              :  * object gives us the MountPoints for all subvolumes while we want it per
    1099              :  * subvolume.
    1100              :  *
    1101              :  * @param {Object} block_fsys
    1102              :  * @param {Object|null} subvol
    1103              :  * @returns {Array} an array of MountPoints
    1104              :  */
    1105          110 : export function get_mount_points(client, block_fsys, subvol) {
    1106          110 :     let mounted_at = [];
    1107              : 
    1108          103 :     if (subvol && block_fsys) {
    1109          103 :         const btrfs_volume = client.blocks_fsys_btrfs[block_fsys.path];
    1110          103 :         const volume_mounts = client.btrfs_mounts[btrfs_volume.data.uuid];
    1111          103 :         if (volume_mounts)
    1112          101 :             mounted_at = subvol.id in volume_mounts ? volume_mounts[subvol.id].mount_points : [];
    1113          102 :     } else {
    1114           80 :         mounted_at = block_fsys ? block_fsys.MountPoints.map(decode_filename) : [];
    1115          109 :     }
    1116              : 
    1117          110 :     return mounted_at;
    1118          110 : }
    1119              : 
    1120           32 : export function get_byte_units(guide_value) {
    1121           32 :     const units = [
    1122           32 :         { factor: 1000 ** 2, name: "MB" },
    1123           32 :         { factor: 1000 ** 3, name: "GB" },
    1124           32 :         { factor: 1000 ** 4, name: "TB" },
    1125           32 :     ];
    1126              :     // Find the biggest unit which gives two digits left of the decimal point (>= 10)
    1127           32 :     let unit;
    1128           32 :     for (unit = units.length - 1; unit >= 0; unit--)
    1129           32 :         if (guide_value / units[unit].factor >= 10)
    1130           32 :             break;
    1131              :     // Mark it selected.  If we couldn't find one (-1), then use MB.
    1132           32 :     units[Math.max(0, unit)].selected = true;
    1133           32 :     return units;
    1134           32 : }
    1135              : 
    1136              : /** @type (client: any, path: string) => boolean */
    1137            1 : export function contains_rootfs(client, path) {
    1138            1 :     const block = client.blocks[path];
    1139            1 :     const crypto = client.blocks_crypto[path];
    1140            1 :     let fsys_config = null;
    1141              : 
    1142            1 :     if (block)
    1143            1 :         fsys_config = block.Configuration.find(c => c[0] == "fstab");
    1144            1 :     if (!fsys_config && crypto)
    1145            0 :         fsys_config = crypto.ChildConfiguration.find(c => c[0] == "fstab");
    1146              : 
    1147            1 :     if (fsys_config) {
    1148            1 :         const dir = decode_filename(fsys_config[1].dir.v);
    1149            1 :         return dir == "/";
    1150            1 :     }
    1151              : 
    1152            1 :     return get_children(client, path).some(p => contains_rootfs(client, p));
    1153            1 : }
        

Generated by: LCOV version 2.0-1