LCOV - code coverage report
Current view: top level - pkg/storaged - client.js Coverage Total Hit
Test: cockpit Lines: 87.5 % 1209 1058
Test Date: 2026-07-17 14:32:59

            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              : import * as PK from 'packagekit';
       8              : import { superuser } from 'superuser';
       9              : import { get_manifest_config_matchlist, in_anaconda_mode, read_anaconda_session_storage } from 'utils';
      10              : 
      11              : import * as utils from './utils.js';
      12              : 
      13              : import * as python from "python.js";
      14              : import { read_os_release } from "os-release.js";
      15              : 
      16              : import inotify_py from "inotify.py";
      17              : import mount_users_py from "./mount-users.py";
      18              : import nfs_mounts_py from "./nfs/nfs-mounts.py";
      19              : import vdo_monitor_py from "./legacy-vdo/vdo-monitor.py";
      20              : import stratis3_set_key_py from "./stratis/stratis3-set-key.py";
      21              : 
      22              : import { reset_pages } from "./pages.jsx";
      23              : import { make_overview_page } from "./overview/overview.jsx";
      24              : import { export_mount_point_mapping } from "./anaconda.jsx";
      25              : 
      26              : import { dequal } from 'dequal/lite';
      27              : 
      28              : import btrfs_tool_py from "./btrfs/btrfs-tool.py";
      29              : 
      30              : /* STORAGED CLIENT
      31              :  */
      32              : 
      33          112 : function debug() {
      34           15 :     if (window.debugging == "all" || window.debugging?.includes("storaged")) // not-covered: debugging
      35           15 :         console.debug.apply(console, arguments); // not-covered: debugging
      36          112 : }
      37              : 
      38          113 : const client = {
      39          113 :     busy: 0
      40          113 : };
      41              : 
      42          113 : cockpit.event_target(client);
      43              : 
      44           93 : client.run = async (func) => {
      45           93 :     if (client.in_anaconda_mode())
      46           11 :         await btrfs_stop_monitoring();
      47           91 :     const prom = func() || Promise.resolve();
      48           93 :     client.busy += 1;
      49           93 :     await prom.finally(() => {
      50           93 :         client.busy -= 1;
      51           93 :         btrfs_start_monitor();
      52           93 :         client.dispatchEvent("changed");
      53           93 :     });
      54           93 : };
      55              : 
      56              : /* Superuser
      57              :  */
      58              : 
      59          113 : client.superuser = superuser;
      60          113 : client.superuser.reload_page_on_change();
      61          113 : client.superuser.addEventListener("changed", () => client.dispatchEvent("changed"));
      62              : 
      63              : /* Metrics
      64              :  */
      65              : 
      66          113 : function instance_sampler(metrics, source) {
      67          113 :     let instances;
      68          113 :     const self = {
      69          113 :         data: { },
      70          113 :         close
      71          113 :     };
      72              : 
      73          113 :     cockpit.event_target(self);
      74              : 
      75          113 :     function handle_meta(msg) {
      76          113 :         self.data = { };
      77          113 :         instances = [];
      78          113 :         for (let m = 0; m < msg.metrics.length; m++) {
      79          113 :             instances[m] = msg.metrics[m].instances;
      80          113 :             for (let i = 0; i < instances[m].length; i++)
      81          113 :                 self.data[instances[m][i]] = [];
      82          113 :         }
      83           24 :         if (Object.keys(self.data).length > 100) {
      84           24 :             close();
      85           24 :             self.data = { };
      86           24 :         }
      87          113 :     }
      88              : 
      89          113 :     function handle_data(msg) {
      90          113 :         let changed = false;
      91          113 :         for (let s = 0; s < msg.length; s++) {
      92          113 :             const metrics = msg[s];
      93          113 :             for (let m = 0; m < metrics.length; m++) {
      94          113 :                 const inst = metrics[m];
      95          113 :                 for (let i = 0; i < inst.length; i++) {
      96          113 :                     if (inst[i] !== null && inst[i] != self.data[instances[m][i]][m]) {
      97          113 :                         changed = true;
      98          113 :                         self.data[instances[m][i]][m] = inst[i];
      99          113 :                     }
     100          113 :                 }
     101          113 :             }
     102          113 :         }
     103          113 :         if (changed)
     104          113 :             self.dispatchEvent('changed');
     105          113 :     }
     106              : 
     107          113 :     const channel = cockpit.channel({
     108          113 :         payload: "metrics1",
     109          113 :         source: source || "internal",
     110          113 :         metrics
     111          113 :     });
     112            0 :     channel.addEventListener("closed", function (event, error) {
     113            0 :         console.log("closed", error);
     114            0 :     });
     115          113 :     channel.addEventListener("message", function (event, message) {
     116          113 :         const msg = JSON.parse(message);
     117          113 :         if (msg.length)
     118          113 :             handle_data(msg);
     119              :         else
     120          113 :             handle_meta(msg);
     121          113 :     });
     122              : 
     123            0 :     function close() {
     124            0 :         channel.close();
     125            0 :     }
     126              : 
     127          113 :     return self;
     128          113 : }
     129              : 
     130              : /* D-Bus proxies
     131              :  */
     132              : 
     133          113 : client.time_offset = undefined; /* Number of milliseconds that the server is ahead of us. */
     134          113 : client.features = undefined;
     135              : 
     136          113 : client.storaged_client = undefined;
     137              : 
     138          112 : function proxy(iface, path) {
     139          112 :     return client.storaged_client.proxy("org.freedesktop.UDisks2." + iface,
     140          112 :                                         "/org/freedesktop/UDisks2/" + path,
     141          112 :                                         { watch: true });
     142          112 : }
     143              : 
     144          112 : function proxies(iface) {
     145              :     /* We create the proxies here with 'watch' set to false and
     146              :      * establish a general watch for all of them.  This is more
     147              :      * efficient since it reduces the number of D-Bus calls done
     148              :      * by the cache.
     149              :      */
     150          112 :     return client.storaged_client.proxies("org.freedesktop.UDisks2." + iface,
     151          112 :                                           "/org/freedesktop/UDisks2",
     152          112 :                                           { watch: false });
     153          112 : }
     154              : 
     155            0 : client.call = function call(path, iface, method, args, options) {
     156            0 :     return client.storaged_client.call(path, "org.freedesktop.UDisks2." + iface, method, args, options);
     157            0 : };
     158              : 
     159          112 : function init_proxies () {
     160          112 :     client.mdraids = proxies("MDRaid");
     161          112 :     client.vgroups = proxies("VolumeGroup");
     162          112 :     client.lvols = proxies("LogicalVolume");
     163          112 :     client.drives = proxies("Drive");
     164          112 :     client.drives_ata = proxies("Drive.Ata");
     165          112 :     client.blocks = proxies("Block");
     166          112 :     client.blocks_ptable = proxies("PartitionTable");
     167          112 :     client.blocks_part = proxies("Partition");
     168          112 :     client.blocks_lvm2 = proxies("Block.LVM2");
     169          112 :     client.blocks_pvol = proxies("PhysicalVolume");
     170          112 :     client.blocks_fsys = proxies("Filesystem");
     171          112 :     client.blocks_crypto = proxies("Encrypted");
     172          112 :     client.blocks_swap = proxies("Swapspace");
     173          112 :     client.iscsi_sessions = proxies("ISCSI.Session");
     174          112 :     client.vdo_vols = proxies("VDOVolume");
     175          112 :     client.blocks_fsys_btrfs = proxies("Filesystem.BTRFS");
     176          112 :     client.jobs = proxies("Job");
     177          112 :     client.nvme_controller = proxies("NVMe.Controller");
     178              : 
     179          112 :     return client.storaged_client.watch({ path_namespace: "/org/freedesktop/UDisks2" });
     180          112 : }
     181              : 
     182              : /* Monitors
     183              :  */
     184              : 
     185          113 : client.fsys_sizes = instance_sampler([{ name: "mount.used" },
     186          113 :     { name: "mount.total" }
     187          113 : ]);
     188              : 
     189          113 : client.swap_sizes = instance_sampler([{ name: "swapdev.length" },
     190          113 :     { name: "swapdev.free" },
     191          113 : ], "direct");
     192              : 
     193          112 : function btrfs_findmnt_poll() {
     194          112 :     if (!client.btrfs_mounts)
     195           15 :         client.btrfs_mounts = { };
     196              : 
     197          112 :     const update_btrfs_mounts = output => {
     198          112 :         const btrfs_mounts = {};
     199          112 :         try {
     200              :             // Extract the data into a { uuid: { subvolid: { subvol, target } } }
     201          112 :             const mounts = JSON.parse(output);
     202          112 :             if ("filesystems" in mounts) {
     203          112 :                 for (const fs of mounts.filesystems) {
     204          112 :                     const subvolid_match = fs.options.match(/subvolid=(?<subvolid>\d+)/);
     205          112 :                     const subvol_match = fs.options.match(/subvol=(?<subvol>[\w\\/]+)/);
     206          112 :                     const ro = fs.options.split(",").indexOf("ro") >= 0;
     207              : 
     208           14 :                     if (!subvolid_match && !subvol_match) {
     209           14 :                         console.warn("findmnt entry without subvol and subvolid", fs);
     210           14 :                         break;
     211           14 :                     }
     212              : 
     213          112 :                     const { subvolid } = subvolid_match.groups;
     214          112 :                     const { subvol } = subvol_match.groups;
     215          112 :                     const subvolume = {
     216          112 :                         pathname: subvol,
     217          112 :                         id: subvolid,
     218          112 :                         mount_points: [fs.target],
     219           15 :                         rw_mount_points: ro ? [] : [fs.target],
     220          112 :                     };
     221              : 
     222          112 :                     if (!(fs.uuid in btrfs_mounts)) {
     223          112 :                         btrfs_mounts[fs.uuid] = { };
     224          112 :                     }
     225              : 
     226              :                     // We need to handle multiple mounts, they are listed separate.
     227           93 :                     if (subvolid in btrfs_mounts[fs.uuid]) {
     228           93 :                         btrfs_mounts[fs.uuid][subvolid].mount_points.push(fs.target);
     229           93 :                         if (!ro)
     230           93 :                             btrfs_mounts[fs.uuid][subvolid].rw_mount_points.push(fs.target);
     231           93 :                     } else {
     232          112 :                         btrfs_mounts[fs.uuid][subvolid] = subvolume;
     233          112 :                     }
     234          112 :                 }
     235          112 :             }
     236           14 :         } catch (exc) {
     237           14 :             if (exc.message)
     238           14 :                 console.error("unable to parse findmnt JSON output", exc);
     239           14 :         }
     240              : 
     241              :         // Update client state
     242          112 :         if (!dequal(client.btrfs_mounts, btrfs_mounts)) {
     243          112 :             client.btrfs_mounts = btrfs_mounts;
     244          112 :             debug("btrfs_findmnt_poll mounts:", client.btrfs_mounts);
     245          112 :             client.update();
     246          112 :         }
     247          112 :     };
     248              : 
     249          112 :     const findmnt_poll = () => {
     250           11 :         return cockpit.spawn(["findmnt", "--type", "btrfs", "--mtab", "--poll"], { superuser: "try", err: "message" }).stream(() => {
     251           11 :             cockpit.spawn(["findmnt", "--type", "btrfs", "--mtab", "-o", "UUID,OPTIONS,TARGET", "--json"],
     252           11 :                           { superuser: "try", err: "message" }).then(output => update_btrfs_mounts(output)).catch(err => {
     253              :                 // When there are no btrfs filesystems left this can fail and thus we need to manually reset the mount info.
     254            0 :                 client.btrfs_mounts = {};
     255            0 :                 client.update();
     256            0 :                 if (err.message) {
     257            0 :                     console.error("findmnt exited with an error", err);
     258            0 :                 }
     259            0 :             });
     260            0 :         }).catch(err => {
     261            0 :             console.error("findmnt --poll exited with an error", err);
     262            0 :             throw new Error("findmnt --poll stopped working");
     263            0 :         });
     264          112 :     };
     265              : 
     266              :     // This fails when no btrfs filesystem is found with the --mtab option and exits with 1, so that is kinda useless, however without --mtab
     267              :     // we don't get a nice flat structure. So we ignore the errors
     268          112 :     cockpit.spawn(["findmnt", "--type", "btrfs", "--mtab", "-o", "UUID,OPTIONS,SOURCE,TARGET", "--json"],
     269          112 :                   { superuser: "try", err: "message" }).then(output => {
     270          112 :         update_btrfs_mounts(output);
     271          112 :         findmnt_poll();
     272          112 :     }).catch(err => {
     273              :         // only log error when there is a real issue.
     274            0 :         if (client.superuser.allowed && err.message) {
     275            0 :             console.error(`unable to run findmnt ${err}`);
     276            0 :         }
     277            0 :         findmnt_poll();
     278            0 :     });
     279          112 : }
     280              : 
     281          112 : function btrfs_update(data) {
     282          112 :     if (!client.uuids_btrfs_subvols)
     283           12 :         client.uuids_btrfs_subvols = { };
     284          112 :     if (!client.uuids_btrfs_usage)
     285           12 :         client.uuids_btrfs_usage = { };
     286          112 :     if (!client.uuids_btrfs_default_subvol)
     287           12 :         client.uuids_btrfs_default_subvol = { };
     288              : 
     289          112 :     const uuids_subvols = { };
     290          112 :     const uuids_usage = { };
     291          112 :     const default_subvol = { };
     292              : 
     293          112 :     for (const uuid in data) {
     294           13 :         if (data[uuid].error) {
     295           13 :             console.warn("Error polling btrfs", uuid, data[uuid].error);
     296           13 :         } else {
     297          112 :             if (data[uuid].subvolumes) {
     298          112 :                 uuids_subvols[uuid] = [{ pathname: "/", id: 5, parent: null }].concat(data[uuid].subvolumes);
     299          112 :             }
     300          112 :             if (data[uuid].usages) {
     301          112 :                 uuids_usage[uuid] = data[uuid].usages;
     302          112 :             }
     303          112 :             if (data[uuid].default_subvolume) {
     304          112 :                 default_subvol[uuid] = data[uuid].default_subvolume;
     305          112 :             }
     306          112 :         }
     307          112 :     }
     308              : 
     309          112 :     if (!dequal(client.uuids_btrfs_subvols, uuids_subvols) || !dequal(client.uuids_btrfs_usage, uuids_usage) ||
     310          112 :         !dequal(client.uuids_btrfs_default_subvol, default_subvol)) {
     311          112 :         debug("btrfs_pol new subvols:", uuids_subvols);
     312          112 :         client.uuids_btrfs_subvols = uuids_subvols;
     313          112 :         client.uuids_btrfs_usage = uuids_usage;
     314          112 :         debug("btrfs_pol usage:", uuids_usage);
     315          112 :         client.uuids_btrfs_default_subvol = default_subvol;
     316          112 :         debug("btrfs_pol default subvolumes:", default_subvol);
     317          112 :         client.update();
     318          112 :     }
     319          112 : }
     320              : 
     321          112 : export async function btrfs_tool(args) {
     322          112 :     return await python.spawn(btrfs_tool_py, args, { superuser: "require" });
     323          112 : }
     324              : 
     325          112 : function btrfs_poll_options() {
     326          112 :     if (client.in_anaconda_mode())
     327           14 :         return ["--mount"];
     328              :     else
     329          104 :         return [];
     330          112 : }
     331              : 
     332          112 : export async function btrfs_poll() {
     333           13 :     if (!client.superuser.allowed || !client.features.btrfs) {
     334           13 :         return;
     335           13 :     }
     336              : 
     337          112 :     const data = JSON.parse(await btrfs_tool(["poll", ...btrfs_poll_options()]));
     338          112 :     btrfs_update(data);
     339          112 : }
     340              : 
     341          113 : let btrfs_monitor_channel = null;
     342              : 
     343          112 : function btrfs_start_monitor() {
     344           15 :     if (!client.superuser.allowed || !client.features.btrfs) {
     345           15 :         return;
     346           15 :     }
     347              : 
     348          112 :     if (btrfs_monitor_channel)
     349          112 :         return;
     350              : 
     351          112 :     const channel = python.spawn(btrfs_tool_py, ["monitor", ...btrfs_poll_options()], { superuser: "require" });
     352          112 :     let buf = "";
     353              : 
     354          112 :     channel.stream(output => {
     355          112 :         buf += output;
     356          112 :         const lines = buf.split("\n");
     357          112 :         buf = lines[lines.length - 1];
     358          112 :         if (lines.length >= 2) {
     359          112 :             const data = JSON.parse(lines[lines.length - 2]);
     360          112 :             btrfs_update(data);
     361          112 :         }
     362          112 :     });
     363              : 
     364            0 :     channel.catch(err => {
     365            0 :         throw new Error(err.toString());
     366            0 :     });
     367              : 
     368          112 :     btrfs_monitor_channel = channel;
     369          112 : }
     370              : 
     371            8 : function btrfs_stop_monitoring() {
     372            8 :     if (btrfs_monitor_channel) {
     373            8 :         const res = btrfs_monitor_channel.then(() => {
     374            8 :             btrfs_monitor_channel = null;
     375            8 :         });
     376            8 :         btrfs_monitor_channel.close();
     377            8 :         return res;
     378            0 :     } else {
     379            0 :         return Promise.resolve();
     380            0 :     }
     381            8 : }
     382              : 
     383          112 : function btrfs_start_polling() {
     384          112 :     debug("starting polling for btrfs subvolumes");
     385          112 :     client.uuids_btrfs_subvols = { };
     386          112 :     client.uuids_btrfs_usage = { };
     387          112 :     client.uuids_btrfs_default_subvol = { };
     388          112 :     client.btrfs_mounts = { };
     389          112 :     btrfs_findmnt_poll();
     390          112 :     btrfs_start_monitor();
     391          112 : }
     392              : 
     393              : /* Derived indices.
     394              :  */
     395              : 
     396          112 : function is_multipath_master(block) {
     397              :     // The master has "mpath" in its device mapper UUID.  In the
     398              :     // future, storaged will hopefully provide this information
     399              :     // directly.
     400          112 :     if (block.Symlinks && block.Symlinks.length) {
     401          112 :         for (let i = 0; i < block.Symlinks.length; i++)
     402          112 :             if (utils.decode_filename(block.Symlinks[i]).indexOf("/dev/disk/by-id/dm-uuid-mpath-") === 0)
     403           14 :                 return true;
     404          112 :     }
     405          112 :     return false;
     406          112 : }
     407              : 
     408          112 : function is_toplevel_drive(block) {
     409              :     // We consider all Block objects that point to the same Drive
     410              :     // objects to be multipath members for a single actual device.
     411              :     //
     412              :     // However, objects for partitions point to the same Drive object
     413              :     // as the object for the partition table. We have to ignore them.
     414              : 
     415          112 :     if (client.blocks_part[block.path])
     416          112 :         return false;
     417              : 
     418              :     // Also, eMMCs have special partition-like sub-devices that point
     419              :     // to the main Drive. We identify them by their name, just like
     420              :     // UDisks2.
     421              : 
     422          112 :     if (utils.decode_filename(block.Device).match(/\/dev\/mmcblk[0-9]boot[0-9]$/))
     423           13 :         return false;
     424              : 
     425          112 :     return true;
     426          112 : }
     427              : 
     428          112 : function update_indices() {
     429          112 :     let path;
     430          112 :     let block;
     431          112 :     let mdraid;
     432          112 :     let vgroup;
     433          112 :     let pvol;
     434          112 :     let lvol;
     435          112 :     let pool;
     436          112 :     let blockdev;
     437          112 :     let fsys;
     438          112 :     let part;
     439          112 :     let i;
     440              : 
     441          112 :     client.broken_multipath_present = false;
     442          112 :     client.drives_multipath_blocks = { };
     443          112 :     client.drives_block = { };
     444          112 :     for (path in client.drives) {
     445          112 :         client.drives_multipath_blocks[path] = [];
     446          112 :     }
     447          112 :     for (path in client.blocks) {
     448          112 :         block = client.blocks[path];
     449          112 :         if (client.drives_multipath_blocks[block.Drive] !== undefined && is_toplevel_drive(block)) {
     450          112 :             if (is_multipath_master(block))
     451           14 :                 client.drives_block[block.Drive] = block;
     452              :             else
     453          112 :                 client.drives_multipath_blocks[block.Drive].push(block);
     454          112 :         }
     455          112 :     }
     456          112 :     for (path in client.drives_multipath_blocks) {
     457              :         /* If there is no multipath master and only a single
     458              :          * member, then this is actually a normal singlepath
     459              :          * device.
     460              :          */
     461              : 
     462          112 :         if (!client.drives_block[path] && client.drives_multipath_blocks[path].length == 1) {
     463          112 :             client.drives_block[path] = client.drives_multipath_blocks[path][0];
     464          112 :             client.drives_multipath_blocks[path] = [];
     465           20 :         } else {
     466           20 :             client.drives_multipath_blocks[path].sort(utils.block_cmp);
     467           20 :             if (!client.drives_block[path])
     468           20 :                 client.broken_multipath_present = true;
     469           20 :         }
     470          112 :     }
     471              : 
     472          112 :     client.mdraids_block = { };
     473          112 :     for (path in client.blocks) {
     474          112 :         block = client.blocks[path];
     475          112 :         if (block.MDRaid != "/")
     476           19 :             client.mdraids_block[block.MDRaid] = block;
     477          112 :     }
     478              : 
     479          112 :     client.mdraids_members = { };
     480           19 :     for (path in client.mdraids) {
     481           19 :         client.mdraids_members[path] = [];
     482           19 :     }
     483          112 :     for (path in client.blocks) {
     484          112 :         block = client.blocks[path];
     485          112 :         if (client.mdraids_members[block.MDRaidMember] !== undefined)
     486           19 :             client.mdraids_members[block.MDRaidMember].push(block);
     487          112 :     }
     488           19 :     for (path in client.mdraids_members) {
     489           19 :         client.mdraids_members[path].sort(utils.block_cmp);
     490           19 :     }
     491              : 
     492          112 :     client.slashdevs_block = { };
     493          112 :     function enter_slashdev(block, enc) {
     494          112 :         client.slashdevs_block[utils.decode_filename(enc)] = block;
     495          112 :     }
     496          112 :     for (path in client.blocks) {
     497          112 :         block = client.blocks[path];
     498          112 :         enter_slashdev(block, block.Device);
     499          112 :         enter_slashdev(block, block.PreferredDevice);
     500          112 :         for (i = 0; i < block.Symlinks.length; i++)
     501          112 :             enter_slashdev(block, block.Symlinks[i]);
     502          112 :     }
     503              : 
     504          112 :     client.uuids_mdraid = { };
     505           19 :     for (path in client.mdraids) {
     506           19 :         mdraid = client.mdraids[path];
     507           19 :         client.uuids_mdraid[mdraid.UUID] = mdraid;
     508           19 :     }
     509              : 
     510          112 :     client.vgnames_vgroup = { };
     511           38 :     for (path in client.vgroups) {
     512           38 :         vgroup = client.vgroups[path];
     513           38 :         client.vgnames_vgroup[vgroup.Name] = vgroup;
     514           38 :     }
     515              : 
     516          112 :     const vgroups_with_dm_pvs = { };
     517              : 
     518          112 :     client.vgroups_pvols = { };
     519           38 :     for (path in client.vgroups) {
     520           38 :         client.vgroups_pvols[path] = [];
     521           38 :     }
     522           38 :     for (path in client.blocks_pvol) {
     523           38 :         pvol = client.blocks_pvol[path];
     524           38 :         if (client.vgroups_pvols[pvol.VolumeGroup] !== undefined) {
     525           38 :             client.vgroups_pvols[pvol.VolumeGroup].push(pvol);
     526           38 :             {
     527              :                 // HACK - this is needed below to deal with a UDisks2 bug.
     528              :                 // https://github.com/storaged-project/udisks/pull/1206
     529           38 :                 const block = client.blocks[path];
     530           38 :                 if (block && utils.decode_filename(block.Device).indexOf("/dev/dm-") == 0)
     531           14 :                     vgroups_with_dm_pvs[pvol.VolumeGroup] = true;
     532           38 :             }
     533           38 :         }
     534           38 :     }
     535           10 :     function cmp_pvols(a, b) {
     536           10 :         return utils.block_cmp(client.blocks[a.path], client.blocks[b.path]);
     537           10 :     }
     538           38 :     for (path in client.vgroups_pvols) {
     539           38 :         client.vgroups_pvols[path].sort(cmp_pvols);
     540           38 :     }
     541              : 
     542          112 :     client.vgroups_lvols = { };
     543           38 :     for (path in client.vgroups) {
     544           38 :         client.vgroups_lvols[path] = [];
     545           38 :     }
     546           35 :     for (path in client.lvols) {
     547           35 :         lvol = client.lvols[path];
     548           35 :         if (client.vgroups_lvols[lvol.VolumeGroup] !== undefined)
     549           35 :             client.vgroups_lvols[lvol.VolumeGroup].push(lvol);
     550           35 :     }
     551           38 :     for (path in client.vgroups_lvols) {
     552            5 :         client.vgroups_lvols[path].sort(function (a, b) { return a.Name.localeCompare(b.Name) });
     553           38 :     }
     554              : 
     555          112 :     client.lvols_block = { };
     556           35 :     for (path in client.blocks_lvm2) {
     557           35 :         client.lvols_block[client.blocks_lvm2[path].LogicalVolume] = client.blocks[path];
     558           35 :     }
     559              : 
     560          112 :     client.lvols_pool_members = { };
     561           35 :     for (path in client.lvols) {
     562           35 :         if (client.lvols[path].Type == "pool")
     563           15 :             client.lvols_pool_members[path] = [];
     564           35 :     }
     565           35 :     for (path in client.lvols) {
     566           35 :         lvol = client.lvols[path];
     567           35 :         if (client.lvols_pool_members[lvol.ThinPool] !== undefined)
     568           15 :             client.lvols_pool_members[lvol.ThinPool].push(lvol);
     569           35 :     }
     570           15 :     for (path in client.lvols_pool_members) {
     571            1 :         client.lvols_pool_members[path].sort(function (a, b) { return a.Name.localeCompare(b.Name) });
     572           15 :     }
     573              : 
     574           22 :     function summarize_stripe(lv_size, segments) {
     575           22 :         const pvs = { };
     576           22 :         let total_size = 0;
     577           22 :         for (const [, size, pv] of segments) {
     578           22 :             if (!pvs[pv])
     579           22 :                 pvs[pv] = 0;
     580           22 :             pvs[pv] += size;
     581           22 :             total_size += size;
     582           22 :         }
     583           22 :         if (total_size < lv_size)
     584            3 :             pvs["/"] = lv_size - total_size;
     585           22 :         return pvs;
     586           22 :     }
     587              : 
     588          112 :     client.lvols_stripe_summary = { };
     589          112 :     client.lvols_status = { };
     590           35 :     for (path in client.lvols) {
     591           35 :         const struct = client.lvols[path].Structure;
     592           35 :         const lvol = client.lvols[path];
     593              : 
     594              :         // HACK - UDisks2 befopre 2.11 can't find the PVs of a segment
     595              :         //        when they are on a device mapper device.
     596              :         //
     597              :         // https://github.com/storaged-project/udisks/pull/1206
     598              : 
     599           13 :         if (!client.at_least("2.11") && vgroups_with_dm_pvs[lvol.VolumeGroup])
     600           35 :             continue;
     601              : 
     602           35 :         let summary;
     603           35 :         let status = "";
     604           33 :         if (lvol.Layout != "thin" && struct && struct.segments) {
     605           33 :             summary = summarize_stripe(struct.size.v, struct.segments.v);
     606           33 :             if (summary["/"])
     607           15 :                 status = "partial";
     608           18 :         } else if (struct && struct.data && struct.metadata &&
     609           16 :                    (struct.data.v.length == struct.metadata.v.length || struct.metadata.v.length == 0)) {
     610           20 :             summary = [];
     611           20 :             const n_total = struct.data.v.length;
     612           20 :             let n_missing = 0;
     613           20 :             for (let i = 0; i < n_total; i++) {
     614           20 :                 const data_lv = struct.data.v[i];
     615           16 :                 const metadata_lv = struct.metadata.v[i] || { size: { v: 0 }, segments: { v: [] } };
     616              : 
     617           13 :                 if (!data_lv.segments || (metadata_lv && !metadata_lv.segments)) {
     618           13 :                     summary = undefined;
     619           13 :                     break;
     620           13 :                 }
     621              : 
     622           20 :                 const s = summarize_stripe(data_lv.size.v + metadata_lv.size.v,
     623           20 :                                            data_lv.segments.v.concat(metadata_lv.segments.v));
     624           20 :                 if (s["/"])
     625           15 :                     n_missing += 1;
     626              : 
     627           20 :                 summary.push(s);
     628           20 :             }
     629           15 :             if (n_missing > 0) {
     630           15 :                 status = "partial";
     631           14 :                 if (lvol.Layout == "raid1") {
     632           14 :                     if (n_total - n_missing >= 1)
     633           14 :                         status = "degraded";
     634           14 :                 }
     635           14 :                 if (lvol.Layout == "raid10") {
     636              :                     // This is correct for two-way mirroring, which is
     637              :                     // the only setup supported by lvm2.
     638           14 :                     if (n_missing > n_total / 2) {
     639              :                         // More than half of the PVs are gone -> at
     640              :                         // least one mirror has definitely lost both
     641              :                         // halves.
     642           14 :                         status = "partial";
     643           14 :                     } else if (n_missing > 1) {
     644              :                         // Two or more PVs are lost -> one mirror
     645              :                         // might have lost both halves
     646           14 :                         status = "degraded-maybe-partial";
     647           14 :                     } else {
     648              :                         // Only one PV is missing -> no mirror has
     649              :                         // lost both halves.
     650           14 :                         status = "degraded";
     651           14 :                     }
     652           14 :                 }
     653           15 :                 if (lvol.Layout == "raid4" || lvol.Layout == "raid5") {
     654           15 :                     if (n_missing <= 1)
     655           15 :                         status = "degraded";
     656           15 :                 }
     657           14 :                 if (lvol.Layout == "raid6") {
     658           14 :                     if (n_missing <= 2)
     659           14 :                         status = "degraded";
     660           14 :                 }
     661           15 :             }
     662           20 :         }
     663           35 :         if (summary) {
     664           35 :             client.lvols_stripe_summary[path] = summary;
     665           35 :             client.lvols_status[path] = status;
     666           35 :         }
     667           35 :     }
     668              : 
     669          112 :     client.stratis_poolnames_pool = { };
     670           27 :     for (path in client.stratis_pools) {
     671           27 :         pool = client.stratis_pools[path];
     672           27 :         client.stratis_poolnames_pool[pool.Name] = pool;
     673           27 :     }
     674              : 
     675          112 :     client.stratis_pooluuids_pool = { };
     676           27 :     for (path in client.stratis_pools) {
     677           27 :         pool = client.stratis_pools[path];
     678           27 :         client.stratis_pooluuids_pool[pool.Uuid] = pool;
     679           27 :     }
     680              : 
     681          112 :     client.stratis_pool_blockdevs = { };
     682           27 :     for (path in client.stratis_pools) {
     683           27 :         client.stratis_pool_blockdevs[path] = [];
     684           27 :     }
     685           27 :     for (path in client.stratis_blockdevs) {
     686           27 :         blockdev = client.stratis_blockdevs[path];
     687           27 :         if (client.stratis_pools[blockdev.Pool] !== undefined)
     688           27 :             client.stratis_pool_blockdevs[blockdev.Pool].push(blockdev);
     689           27 :     }
     690              : 
     691          112 :     client.stratis_pool_filesystems = { };
     692           27 :     for (path in client.stratis_pools) {
     693           27 :         client.stratis_pool_filesystems[path] = [];
     694           27 :     }
     695           22 :     for (path in client.stratis_filesystems) {
     696           22 :         fsys = client.stratis_filesystems[path];
     697           22 :         if (client.stratis_pools[fsys.Pool] !== undefined)
     698           22 :             client.stratis_pool_filesystems[fsys.Pool].push(fsys);
     699           22 :     }
     700              : 
     701          112 :     client.blocks_stratis_fsys = { };
     702           22 :     for (path in client.stratis_filesystems) {
     703           22 :         fsys = client.stratis_filesystems[path];
     704           22 :         block = client.slashdevs_block[fsys.Devnode];
     705           22 :         if (block)
     706           22 :             client.blocks_stratis_fsys[block.path] = fsys;
     707           22 :     }
     708              : 
     709          112 :     client.blocks_stratis_blockdev = { };
     710           27 :     for (path in client.stratis_blockdevs) {
     711           27 :         block = client.slashdevs_block[client.stratis_blockdevs[path].PhysicalPath];
     712           27 :         if (block)
     713           27 :             client.blocks_stratis_blockdev[block.path] = client.stratis_blockdevs[path];
     714           27 :     }
     715              : 
     716          112 :     client.blocks_stratis_stopped_pool = { };
     717           20 :     for (const uuid in client.stratis_manager.StoppedPools) {
     718           20 :         const devnodes = client.stratis_stopped_pool_devnodes(uuid);
     719           20 :         for (const d of devnodes) {
     720           20 :             block = client.slashdevs_block[d];
     721           20 :             if (block)
     722           20 :                 client.blocks_stratis_stopped_pool[block.path] = uuid;
     723           20 :         }
     724           20 :     }
     725              : 
     726          112 :     client.stratis_pool_stats = { };
     727           27 :     for (path in client.stratis_pools) {
     728           27 :         const pool = client.stratis_pools[path];
     729           27 :         const filesystems = client.stratis_pool_filesystems[path];
     730              : 
     731           27 :         const fsys_offsets = [];
     732           27 :         let fsys_total_used = 0;
     733           27 :         let fsys_total_size = 0;
     734           13 :         filesystems.forEach(fs => {
     735           13 :             fsys_offsets.push(fsys_total_used);
     736            4 :             fsys_total_used += fs.Used[0] ? Number(fs.Used[1]) : 0;
     737           13 :             fsys_total_size += Number(fs.Size);
     738           13 :         });
     739              : 
     740           13 :         const overhead = pool.TotalPhysicalUsed[0] ? (Number(pool.TotalPhysicalUsed[1]) - fsys_total_used) : 0;
     741           27 :         const pool_total = Number(pool.TotalPhysicalSize) - overhead;
     742           27 :         let pool_free = pool_total - fsys_total_size;
     743              : 
     744              :         // leave some margin since the above computation does not seem to
     745              :         // be exactly right when snapshots are involved.
     746           27 :         pool_free -= filesystems.length * 1024 * 1024;
     747              : 
     748           27 :         client.stratis_pool_stats[path] = {
     749           27 :             fsys_offsets,
     750           27 :             fsys_total_used,
     751           27 :             fsys_total_size,
     752           27 :             pool_total,
     753           27 :             pool_free,
     754           27 :         };
     755           27 :     }
     756              : 
     757          112 :     client.blocks_cleartext = { };
     758          112 :     for (path in client.blocks) {
     759          112 :         block = client.blocks[path];
     760          112 :         if (block.CryptoBackingDevice != "/")
     761           35 :             client.blocks_cleartext[block.CryptoBackingDevice] = block;
     762          112 :     }
     763              : 
     764          112 :     client.blocks_partitions = { };
     765          112 :     for (path in client.blocks_ptable) {
     766          112 :         client.blocks_partitions[path] = [];
     767          112 :     }
     768          112 :     for (path in client.blocks_part) {
     769          112 :         part = client.blocks_part[path];
     770          112 :         if (client.blocks_partitions[part.Table] !== undefined)
     771          112 :             client.blocks_partitions[part.Table].push(part);
     772          112 :     }
     773          112 :     for (path in client.blocks_partitions) {
     774          112 :         client.blocks_partitions[path].sort(function (a, b) { return a.Offset - b.Offset });
     775          112 :     }
     776              : 
     777          112 :     client.iscsi_sessions_drives = { };
     778          112 :     client.drives_iscsi_session = { };
     779          112 :     for (path in client.drives) {
     780          112 :         const block = client.drives_block[path];
     781          112 :         if (!block)
     782          112 :             continue;
     783           14 :         for (const session_path in client.iscsi_sessions) {
     784           14 :             const session = client.iscsi_sessions[session_path];
     785           14 :             for (i = 0; i < block.Symlinks.length; i++) {
     786           14 :                 if (utils.decode_filename(block.Symlinks[i]).includes(session.data.target_name)) {
     787           14 :                     client.drives_iscsi_session[path] = session;
     788           14 :                     if (!client.iscsi_sessions_drives[session_path])
     789           14 :                         client.iscsi_sessions_drives[session_path] = [];
     790           14 :                     client.iscsi_sessions_drives[session_path].push(client.drives[path]);
     791           14 :                 }
     792           14 :             }
     793           14 :         }
     794          112 :     }
     795              : 
     796          112 :     client.path_jobs = { };
     797           62 :     function enter_job(job) {
     798           62 :         if (!job.Objects || !job.Objects.length)
     799           62 :             return;
     800           62 :         job.Objects.forEach(p => {
     801           62 :             if (!client.path_jobs[p])
     802           62 :                 client.path_jobs[p] = [];
     803           62 :             client.path_jobs[p].push(job);
     804           62 :         });
     805           62 :     }
     806           75 :     for (path in client.jobs) {
     807           75 :         enter_job(client.jobs[path]);
     808           75 :     }
     809              : 
     810              :     // UDisks API does not provide a btrfs volume abstraction so we keep track of
     811              :     // volume's by uuid in an object. uuid => [org.freedesktop.UDisks2.Filesystem.BTRFS]
     812              :     // https://github.com/storaged-project/udisks/issues/1232
     813          112 :     const old_uuids = client.uuids_btrfs_volume;
     814          112 :     let need_poll = false;
     815          112 :     client.uuids_btrfs_volume = { };
     816          112 :     client.uuids_btrfs_blocks = { };
     817          112 :     for (const p in client.blocks_fsys_btrfs) {
     818          112 :         const bfs = client.blocks_fsys_btrfs[p];
     819          112 :         const uuid = bfs.data.uuid;
     820          112 :         const block_fsys = client.blocks_fsys[p];
     821          112 :         if (!uuid)
     822          112 :             continue;
     823           23 :         if ((block_fsys && block_fsys.MountPoints.length > 0) || !client.uuids_btrfs_volume[uuid]) {
     824          112 :             client.uuids_btrfs_volume[uuid] = bfs;
     825          112 :             if (!old_uuids || !old_uuids[uuid])
     826          112 :                 need_poll = true;
     827          112 :         }
     828          112 :         if (!client.uuids_btrfs_blocks[uuid])
     829          112 :             client.uuids_btrfs_blocks[uuid] = [];
     830          112 :         client.uuids_btrfs_blocks[uuid].push(client.blocks[p]);
     831          112 :     }
     832              : 
     833          112 :     if (need_poll) {
     834          112 :         btrfs_poll();
     835          112 :     }
     836          112 : }
     837              : 
     838          113 : let lvm2_poll_timer = null;
     839              : 
     840          113 : function update_lvm2_polling(for_visibility) {
     841           25 :     const need_polling = !cockpit.hidden && !!Object.values(client.vgroups).find(vg => vg.NeedsPolling);
     842              : 
     843            2 :     function poll() {
     844            2 :         for (const path in client.vgroups) {
     845            2 :             const vg = client.vgroups[path];
     846            2 :             if (vg.NeedsPolling) {
     847            2 :                 vg.Poll();
     848            2 :             }
     849            2 :         }
     850            2 :     }
     851              : 
     852           19 :     if (need_polling && lvm2_poll_timer == null) {
     853           19 :         lvm2_poll_timer = window.setInterval(poll, 2000);
     854           19 :         if (for_visibility)
     855           14 :             poll();
     856           18 :     } else if (!need_polling && lvm2_poll_timer) {
     857           18 :         window.clearInterval(lvm2_poll_timer);
     858           18 :         lvm2_poll_timer = null;
     859           18 :     }
     860          113 : }
     861              : 
     862          112 : client.update = (first_time) => {
     863          112 :     if (first_time)
     864          112 :         client.ready = true;
     865          112 :     if (client.ready) {
     866          112 :         update_indices();
     867          112 :         update_lvm2_polling(false);
     868          112 :         reset_pages();
     869          112 :         make_overview_page();
     870          112 :         export_mount_point_mapping();
     871          112 :         client.dispatchEvent("changed");
     872          112 :     }
     873          112 : };
     874              : 
     875          112 : function init_model(callback) {
     876          112 :     function pull_time() {
     877          112 :         return cockpit.spawn(["date", "+%s"])
     878          112 :                 .then(function (now) {
     879          112 :                     client.time_offset = parseInt(now, 10) * 1000 - new Date().getTime();
     880          112 :                 });
     881          112 :     }
     882              : 
     883          112 :     async function enable_udisks_features() {
     884          112 :         if (!client.manager.valid)
     885          112 :             return;
     886              : 
     887          112 :         try {
     888          112 :             await client.manager.EnableModule("btrfs", true);
     889          112 :             client.manager_btrfs = proxy("Manager.BTRFS", "Manager");
     890          112 :             await client.manager_btrfs.wait();
     891          112 :             client.features.btrfs = client.manager_btrfs.valid;
     892          112 :             if (client.features.btrfs)
     893          112 :                 btrfs_start_polling();
     894           20 :         } catch (error) {
     895           20 :             console.warn("Can't enable storaged btrfs module", error.toString());
     896           20 :         }
     897              : 
     898          112 :         try {
     899          112 :             await client.manager.EnableModule("iscsi", true);
     900          112 :             client.manager_iscsi = proxy("Manager.ISCSI.Initiator", "Manager");
     901          112 :             await client.manager_iscsi.wait();
     902          112 :             client.features.iscsi = (client.manager_iscsi.valid && client.manager_iscsi.SessionsSupported !== false);
     903           20 :         } catch (error) {
     904           20 :             console.warn("Can't enable storaged iscsi module", error.toString());
     905           20 :         }
     906              : 
     907          112 :         try {
     908          112 :             await client.manager.EnableModule("lvm2", true);
     909          112 :             client.manager_lvm2 = proxy("Manager.LVM2", "Manager");
     910          112 :             await client.manager_lvm2.wait();
     911          112 :             client.features.lvm2 = client.manager_lvm2.valid;
     912           20 :         } catch (error) {
     913           20 :             console.warn("Can't enable storaged lvm2 module", error.toString());
     914           20 :         }
     915          112 :     }
     916              : 
     917          112 :     async function enable_lvm_create_vdo_feature() {
     918          112 :         async function exit_code(cmd) {
     919          112 :             try {
     920          112 :                 await cockpit.spawn(cmd, { err: "ignore", superuser: "try" });
     921           13 :                 return 0;
     922           13 :             } catch (ex) {
     923          112 :                 return ex.exit_status || 1;
     924          112 :             }
     925          112 :         }
     926              : 
     927              :         /* We assume that if LVM has the option to format a VDO volume
     928              :            in the kernel, then it will be used and will work.  If
     929              :            that's not true, the error message from LVM will hopefully
     930              :            be clear enough to help people figure out what needs to be
     931              :            done.
     932              :          */
     933          112 :         if (await exit_code(["vdoformat", "--version"]) == 0 ||
     934           13 :             await exit_code(["lvmconfig", "--list", "allocation/vdo_use_kernel_format"]) == 0) {
     935           13 :             client.features.lvm_create_vdo = true;
     936           13 :         }
     937          112 :     }
     938              : 
     939          112 :     function enable_legacy_vdo_features() {
     940          112 :         return client.legacy_vdo_overlay.start().then(
     941          112 :             function (success) {
     942              :                 // hack here
     943          112 :                 client.features.legacy_vdo = success;
     944          112 :                 return Promise.resolve();
     945          112 :             },
     946            0 :             function () {
     947            0 :                 return Promise.resolve();
     948            0 :             });
     949          112 :     }
     950              : 
     951          112 :     function enable_clevis_features() {
     952          112 :         return cockpit.script("type clevis-luks-bind", { err: "ignore" }).then(
     953          111 :             function () {
     954          111 :                 client.features.clevis = true;
     955          111 :                 return Promise.resolve();
     956          111 :             },
     957            1 :             function () {
     958            1 :                 return Promise.resolve();
     959            1 :             });
     960          112 :     }
     961              : 
     962          112 :     function enable_nfs_features() {
     963              :         // mount.nfs might be in */sbin but that isn't always in
     964              :         // $PATH, such as when connecting from CentOS to another
     965              :         // machine via SSH as non-root.
     966          112 :         const std_path = "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
     967          112 :         return cockpit.script("type mount.nfs", { err: "message", environ: [std_path] }).then(
     968          111 :             function () {
     969          111 :                 client.features.nfs = true;
     970          111 :                 client.nfs.start();
     971          111 :                 return Promise.resolve();
     972          111 :             },
     973            1 :             function () {
     974            1 :                 return Promise.resolve();
     975            1 :             });
     976          112 :     }
     977              : 
     978          112 :     function enable_pk_features() {
     979           23 :         if (client.in_anaconda_mode()) {
     980           23 :             client.features.packagekit = false;
     981           23 :             return Promise.resolve();
     982           23 :         }
     983          101 :         return PK.detect().then(function (available) { client.features.packagekit = available });
     984          112 :     }
     985              : 
     986          112 :     function enable_stratis_feature() {
     987           15 :         return client.stratis_start().catch(error => {
     988           15 :             if (error.problem != "not-found")
     989            0 :                 console.warn("Failed to start Stratis support", error);
     990           15 :             return Promise.resolve();
     991           15 :         });
     992          112 :     }
     993              : 
     994          112 :     function enable_features() {
     995          112 :         client.features = { };
     996          112 :         return (enable_udisks_features()
     997          112 :                 .then(enable_clevis_features)
     998          112 :                 .then(enable_nfs_features)
     999          112 :                 .then(enable_pk_features)
    1000          112 :                 .then(enable_stratis_feature)
    1001          112 :                 .then(enable_lvm_create_vdo_feature)
    1002          112 :                 .then(enable_legacy_vdo_features));
    1003          112 :     }
    1004              : 
    1005          112 :     function query_fsys_info() {
    1006          112 :         const info = {};
    1007          112 :         return Promise.all(client.manager.SupportedFilesystems.map(fs =>
    1008          112 :             client.manager.CanFormat(fs).then(canformat_result => {
    1009          112 :                 info[fs] = {
    1010          112 :                     can_format: canformat_result[0],
    1011          112 :                     can_shrink: false,
    1012          112 :                     can_grow: false
    1013          112 :                 };
    1014          112 :                 return client.manager.CanResize(fs)
    1015          112 :                         .then(canresize_result => {
    1016              :                             // We assume that all filesystems support
    1017              :                             // offline shrinking/growing if they
    1018              :                             // support shrinking or growing at all.
    1019              :                             // The actual resizing utility will
    1020              :                             // temporarily mount the fs if necessary,
    1021          112 :                             if (canresize_result[0]) {
    1022          112 :                                 info[fs].can_shrink = !!(canresize_result[1] & 2);
    1023          112 :                                 info[fs].shrink_needs_unmount = !(canresize_result[1] & 8);
    1024          112 :                                 info[fs].can_grow = !!(canresize_result[1] & 4);
    1025          112 :                                 info[fs].grow_needs_unmount = !(canresize_result[1] & 16);
    1026          112 :                             }
    1027          112 :                         })
    1028              :                         // ignore unsupported filesystems
    1029          112 :                         .catch(() => {});
    1030          112 :             }))
    1031          112 :         ).then(() => info);
    1032          112 :     }
    1033              : 
    1034          112 :     client.anaconda = read_anaconda_session_storage();
    1035              : 
    1036          112 :     pull_time().then(() => {
    1037          112 :         read_os_release().then(os_release => {
    1038          112 :             client.os_release = os_release;
    1039              : 
    1040          112 :             enable_features().then(() => {
    1041          112 :                 query_fsys_info().then((fsys_info) => {
    1042          112 :                     client.fsys_info = fsys_info;
    1043              : 
    1044           98 :                     client.storaged_client.addEventListener('notify', () => client.update());
    1045              : 
    1046          112 :                     update_indices();
    1047            2 :                     cockpit.addEventListener("visibilitychange", () => update_lvm2_polling(true));
    1048          112 :                     btrfs_poll().then(() => {
    1049          112 :                         client.update(true);
    1050          112 :                         callback();
    1051          112 :                     });
    1052          112 :                 });
    1053          112 :             });
    1054          112 :         });
    1055          112 :     });
    1056          112 : }
    1057              : 
    1058          112 : client.younger_than = function younger_than(version) {
    1059          112 :     return utils.compare_versions(this.manager.Version, version) < 0;
    1060          112 : };
    1061              : 
    1062           22 : client.at_least = function at_least(version) {
    1063           22 :     return utils.compare_versions(this.manager.Version, version) >= 0;
    1064           22 : };
    1065              : 
    1066              : /* Mount users
    1067              :  */
    1068              : 
    1069           34 : client.find_mount_users = (target, is_mounted) => {
    1070            2 :     if (is_mounted === undefined || is_mounted)
    1071            0 :         return python.spawn(mount_users_py, ["users", target], { superuser: "try", err: "message" }).then(JSON.parse);
    1072              :     else
    1073            0 :         return Promise.resolve([]);
    1074           34 : };
    1075              : 
    1076           36 : client.stop_mount_users = (users) => {
    1077            6 :     if (users && users.length > 0) {
    1078            6 :         return python.spawn(mount_users_py, ["stop", JSON.stringify(users)],
    1079            6 :                             { superuser: "try", err: "message" });
    1080            6 :     } else
    1081           33 :         return Promise.resolve();
    1082           36 : };
    1083              : 
    1084              : /* Direct mounting and unmounting
    1085              :  *
    1086              :  * We don't use UDisks2 for most of our mounting and unmounting in
    1087              :  * order to get better control over which entry from fstab is
    1088              :  * selected.  Once UDisks2 allows that control, we can switch back to
    1089              :  * it.
    1090              :  *
    1091              :  * But note that these functions still require an fstab entry.
    1092              :  */
    1093              : 
    1094           36 : client.mount_at = (block, target) => {
    1095           36 :     const entry = block.Configuration.find(c => c[0] == "fstab" && utils.decode_filename(c[1].dir.v) == target);
    1096           36 :     if (entry)
    1097           36 :         return cockpit.script('set -e; mkdir -p "$2"; mount "$1" "$2" -o "$3"',
    1098           36 :                               [utils.decode_filename(block.Device), target, utils.get_block_mntopts(entry[1])],
    1099            0 :                               { superuser: "require", err: "message" });
    1100              :     else
    1101            0 :         return Promise.reject(cockpit.format("Internal error: No fstab entry for $0 and $1",
    1102            0 :                                              utils.decode_filename(block.Device),
    1103            0 :                                              target));
    1104           36 : };
    1105              : 
    1106           35 : client.unmount_at = (target, users) => {
    1107           35 :     return client.stop_mount_users(users).then(() => cockpit.spawn(["umount", target],
    1108           35 :                                                                    { superuser: "require", err: "message" }));
    1109           35 : };
    1110              : 
    1111              : /* NFS mounts
    1112              :  */
    1113              : 
    1114          113 : function nfs_mounts() {
    1115          113 :     const self = {
    1116          113 :         entries: [],
    1117          113 :         fsys_sizes: { },
    1118              : 
    1119          113 :         start,
    1120              : 
    1121          113 :         get_fsys_size,
    1122          113 :         entry_users,
    1123              : 
    1124          113 :         update_entry,
    1125          113 :         add_entry,
    1126          113 :         remove_entry,
    1127              : 
    1128          113 :         mount_entry,
    1129          113 :         unmount_entry,
    1130          113 :         stop_and_unmount_entry,
    1131          113 :         stop_and_remove_entry,
    1132              : 
    1133          113 :         find_entry
    1134          113 :     };
    1135              : 
    1136          112 :     function spawn_nfs_mounts(args) {
    1137          112 :         return python.spawn([inotify_py, nfs_mounts_py], args, { superuser: "try", err: "message" });
    1138          112 :     }
    1139              : 
    1140          112 :     function start() {
    1141          112 :         let buf = "";
    1142          112 :         spawn_nfs_mounts(["monitor"])
    1143          112 :                 .stream(function (output) {
    1144          112 :                     buf += output;
    1145          112 :                     const lines = buf.split("\n");
    1146          112 :                     buf = lines[lines.length - 1];
    1147          112 :                     if (lines.length >= 2) {
    1148          112 :                         self.entries = JSON.parse(lines[lines.length - 2]);
    1149          112 :                         self.fsys_sizes = { };
    1150          112 :                         client.update();
    1151          112 :                     }
    1152          112 :                 })
    1153            0 :                 .catch(function (error) {
    1154            0 :                     if (error != "closed") {
    1155            0 :                         console.warn(error);
    1156            0 :                     }
    1157            0 :                 });
    1158          112 :     }
    1159              : 
    1160            3 :     function get_fsys_size(entry) {
    1161            3 :         const path = entry.fields[1];
    1162            3 :         if (self.fsys_sizes[path])
    1163            3 :             return self.fsys_sizes[path];
    1164              : 
    1165            3 :         if (self.fsys_sizes[path] === false)
    1166            1 :             return null;
    1167              : 
    1168            3 :         self.fsys_sizes[path] = false;
    1169            3 :         cockpit.spawn(["stat", "-f", "-c", "[ %S, %f, %b ]", path], { err: "message" })
    1170            3 :                 .then(function (output) {
    1171            3 :                     const data = JSON.parse(output);
    1172            3 :                     self.fsys_sizes[path] = [(data[2] - data[1]) * data[0], data[2] * data[0]];
    1173            3 :                     client.update();
    1174            3 :                 })
    1175            0 :                 .catch(function () {
    1176            0 :                     self.fsys_sizes[path] = [0, 0];
    1177            0 :                     client.update();
    1178            0 :                 });
    1179              : 
    1180            3 :         return null;
    1181            3 :     }
    1182              : 
    1183            1 :     function update_entry(entry, new_fields) {
    1184            1 :         return spawn_nfs_mounts(["update", JSON.stringify(entry), JSON.stringify(new_fields)]);
    1185            1 :     }
    1186              : 
    1187            3 :     function add_entry(fields) {
    1188            3 :         return spawn_nfs_mounts(["add", JSON.stringify(fields)]);
    1189            3 :     }
    1190              : 
    1191            2 :     function remove_entry(entry) {
    1192            2 :         return spawn_nfs_mounts(["remove", JSON.stringify(entry)]);
    1193            2 :     }
    1194              : 
    1195            2 :     function mount_entry(entry) {
    1196            2 :         return spawn_nfs_mounts(["mount", JSON.stringify(entry)]);
    1197            2 :     }
    1198              : 
    1199            2 :     function unmount_entry(entry) {
    1200            2 :         return spawn_nfs_mounts(["unmount", JSON.stringify(entry)]);
    1201            2 :     }
    1202              : 
    1203            1 :     function stop_and_unmount_entry(users, entry) {
    1204            1 :         return client.stop_mount_users(users).then(() => unmount_entry(entry));
    1205            1 :     }
    1206              : 
    1207            1 :     function stop_and_remove_entry(users, entry) {
    1208            1 :         return client.stop_mount_users(users).then(() => remove_entry(entry));
    1209            1 :     }
    1210              : 
    1211            2 :     function entry_users(entry) {
    1212            2 :         return client.find_mount_users(entry.fields[1], entry.mounted);
    1213            2 :     }
    1214              : 
    1215            0 :     function find_entry(remote, local) {
    1216            0 :         for (let i = 0; i < self.entries.length; i++) {
    1217            0 :             if (self.entries[i].fields[0] == remote && self.entries[i].fields[1] == local)
    1218            0 :                 return self.entries[i];
    1219            0 :         }
    1220            0 :     }
    1221              : 
    1222          113 :     return self;
    1223          113 : }
    1224              : 
    1225          113 : client.nfs = nfs_mounts();
    1226              : 
    1227              : /* Legacy VDO CLI (RHEL 8), unsupported; newer versions use VDO through LVM API */
    1228              : 
    1229          113 : function legacy_vdo_overlay() {
    1230          113 :     const self = {
    1231          113 :         start,
    1232              : 
    1233          113 :         volumes: [],
    1234              : 
    1235          113 :         by_name: { },
    1236          113 :         by_dev: { },
    1237          113 :         by_backing_dev: { },
    1238              : 
    1239          113 :         find_by_block,
    1240          113 :         find_by_backing_block,
    1241              : 
    1242          113 :         create
    1243          113 :     };
    1244              : 
    1245            0 :     function cmd(args) {
    1246            0 :         return cockpit.spawn(["vdo"].concat(args),
    1247            0 :                              {
    1248            0 :                                  superuser: "require",
    1249            0 :                                  err: "message"
    1250            0 :                              });
    1251            0 :     }
    1252              : 
    1253            0 :     function update(data) {
    1254            0 :         self.by_name = { };
    1255            0 :         self.by_dev = { };
    1256            0 :         self.by_backing_dev = { };
    1257              : 
    1258            0 :         self.volumes = data.map(function (vol, index) {
    1259            0 :             const name = vol.name;
    1260              : 
    1261            0 :             function volcmd(args) {
    1262            0 :                 return cmd(args.concat(["--name", name]));
    1263            0 :             }
    1264              : 
    1265            0 :             const v = {
    1266            0 :                 name,
    1267            0 :                 broken: vol.broken,
    1268            0 :                 dev: "/dev/mapper/" + name,
    1269            0 :                 backing_dev: vol.device,
    1270            0 :                 logical_size: vol.logical_size,
    1271            0 :                 physical_size: vol.physical_size,
    1272            0 :                 index_mem: vol.index_mem,
    1273            0 :                 compression: vol.compression,
    1274            0 :                 deduplication: vol.deduplication,
    1275            0 :                 activated: vol.activated,
    1276              : 
    1277            0 :                 set_compression: function(val) {
    1278            0 :                     return volcmd([val ? "enableCompression" : "disableCompression"]);
    1279            0 :                 },
    1280              : 
    1281            0 :                 set_deduplication: function(val) {
    1282            0 :                     return volcmd([val ? "enableDeduplication" : "disableDeduplication"]);
    1283            0 :                 },
    1284              : 
    1285            0 :                 set_activate: function(val) {
    1286            0 :                     return volcmd([val ? "activate" : "deactivate"]);
    1287            0 :                 },
    1288              : 
    1289            0 :                 start: function() {
    1290            0 :                     return volcmd(["start"]);
    1291            0 :                 },
    1292              : 
    1293            0 :                 stop: function() {
    1294            0 :                     return volcmd(["stop"]);
    1295            0 :                 },
    1296              : 
    1297            0 :                 remove: function() {
    1298            0 :                     return volcmd(["remove"]);
    1299            0 :                 },
    1300              : 
    1301            0 :                 force_remove: function() {
    1302            0 :                     return volcmd(["remove", "--force"]);
    1303            0 :                 },
    1304              : 
    1305            0 :                 grow_physical: function() {
    1306            0 :                     return volcmd(["growPhysical"]);
    1307            0 :                 },
    1308              : 
    1309            0 :                 grow_logical: function(lsize) {
    1310            0 :                     return volcmd(["growLogical", "--vdoLogicalSize", lsize + "B"]);
    1311            0 :                 }
    1312            0 :             };
    1313              : 
    1314            0 :             self.by_name[v.name] = v;
    1315            0 :             self.by_dev[v.dev] = v;
    1316            0 :             self.by_backing_dev[v.backing_dev] = v;
    1317              : 
    1318            0 :             return v;
    1319            0 :         });
    1320              : 
    1321            0 :         client.update();
    1322            0 :     }
    1323              : 
    1324          112 :     function start() {
    1325          112 :         let buf = "";
    1326              : 
    1327          112 :         return cockpit.spawn(["/bin/sh", "-c", "head -1 $(command -v vdo || echo /dev/null)"],
    1328          112 :                              { err: "ignore" })
    1329          112 :                 .then(function (shebang) {
    1330           13 :                     if (shebang != "") {
    1331           13 :                         self.python = shebang.replace(/#! */, "").trim("\n");
    1332           13 :                         cockpit.spawn([self.python, "--", "-"], { superuser: "try", err: "message" })
    1333           13 :                                 .input(inotify_py + vdo_monitor_py)
    1334            0 :                                 .stream(function (output) {
    1335            0 :                                     buf += output;
    1336            0 :                                     const lines = buf.split("\n");
    1337            0 :                                     buf = lines[lines.length - 1];
    1338            0 :                                     if (lines.length >= 2) {
    1339            0 :                                         update(JSON.parse(lines[lines.length - 2]));
    1340            0 :                                     }
    1341            0 :                                 })
    1342            0 :                                 .catch(function (error) {
    1343            0 :                                     if (error != "closed") {
    1344            0 :                                         console.warn(error);
    1345            0 :                                     }
    1346            0 :                                 });
    1347           13 :                         return true;
    1348           13 :                     } else {
    1349          112 :                         return false;
    1350          112 :                     }
    1351          112 :                 });
    1352          112 :     }
    1353              : 
    1354          112 :     function some(array, func) {
    1355          112 :         let i;
    1356          112 :         for (i = 0; i < array.length; i++) {
    1357          112 :             const val = func(array[i]);
    1358          112 :             if (val)
    1359           13 :                 return val;
    1360          112 :         }
    1361          112 :         return null;
    1362          112 :     }
    1363              : 
    1364           97 :     function find_by_block(block) {
    1365           97 :         function check(encoded) { return self.by_dev[utils.decode_filename(encoded)] }
    1366           97 :         return check(block.Device) || some(block.Symlinks, check);
    1367           97 :     }
    1368              : 
    1369          112 :     function find_by_backing_block(block) {
    1370          112 :         function check(encoded) { return self.by_backing_dev[utils.decode_filename(encoded)] }
    1371          112 :         return check(block.Device) || some(block.Symlinks, check);
    1372          112 :     }
    1373              : 
    1374            0 :     function create(options) {
    1375            0 :         const args = ["create", "--name", options.name,
    1376            0 :             "--device", utils.decode_filename(options.block.PreferredDevice)];
    1377            0 :         if (options.logical_size !== undefined)
    1378            0 :             args.push("--vdoLogicalSize", options.logical_size + "B");
    1379            0 :         if (options.index_mem !== undefined)
    1380            0 :             args.push("--indexMem", options.index_mem / (1024 * 1024 * 1024));
    1381            0 :         if (options.compression !== undefined)
    1382            0 :             args.push("--compression", options.compression ? "enabled" : "disabled");
    1383            0 :         if (options.deduplication !== undefined)
    1384            0 :             args.push("--deduplication", options.deduplication ? "enabled" : "disabled");
    1385            0 :         if (options.emulate_512 !== undefined)
    1386            0 :             args.push("--emulate512", options.emulate_512 ? "enabled" : "disabled");
    1387            0 :         return cmd(args);
    1388            0 :     }
    1389              : 
    1390          113 :     return self;
    1391          113 : }
    1392              : 
    1393          113 : client.legacy_vdo_overlay = legacy_vdo_overlay();
    1394              : 
    1395              : /* Stratis */
    1396              : 
    1397          112 : client.stratis_start = () => {
    1398          112 :     return stratis3_start();
    1399          112 : };
    1400              : 
    1401              : // We need to use the same revision for all interfaces, mixing them is
    1402              : // not allowed.  If we need to bump it, it should be bumped here for all
    1403              : // of them at the same time.
    1404              : //
    1405              : // We try all these versions in order, and use the first we find.
    1406              : //
    1407          113 : const stratis3_interface_revisions = [8, 6];
    1408              : 
    1409          112 : async function stratis3_start() {
    1410          112 :     const stratis = cockpit.dbus("org.storage.stratis3", { superuser: "try" });
    1411              : 
    1412              :     // The rest of the code expects these to be initialized even if no
    1413              :     // stratisd is found.
    1414          112 :     client.stratis_pools = { };
    1415          112 :     client.stratis_blockdevs = { };
    1416          112 :     client.stratis_filesystems = { };
    1417              : 
    1418          112 :     client.stratis_interface_revision = null;
    1419          112 :     let last_error;
    1420          112 :     for (const rev of stratis3_interface_revisions) {
    1421          112 :         client.stratis_manager = stratis.proxy("org.storage.stratis3.Manager.r" + rev, "/org/storage/stratis3");
    1422          112 :         client.stratis_manager.StoppedPools = {};
    1423          112 :         try {
    1424          112 :             await client.stratis_manager.wait();
    1425           98 :             client.stratis_interface_revision = rev;
    1426           98 :             break;
    1427           14 :         } catch (e) {
    1428           28 :             last_error = e;
    1429           28 :         }
    1430          112 :     }
    1431              : 
    1432          112 :     if (!client.stratis_interface_revision)
    1433           28 :         throw last_error;
    1434              : 
    1435            1 :     client.stratis_store_passphrase = (desc, passphrase) => {
    1436            1 :         return python.spawn(stratis3_set_key_py, [desc], { superuser: "require" })
    1437            1 :                 .input(passphrase);
    1438            1 :     };
    1439              : 
    1440            4 :     client.stratis_set_property = (proxy, prop, sig, value) => {
    1441              :         // DBusProxy is smart enough to allow "proxy.Prop
    1442              :         // = value" to just work, but we want to catch any
    1443              :         // error ourselves, and we want to wait for the
    1444              :         // method call to complete.
    1445            4 :         return stratis.call(proxy.path, "org.freedesktop.DBus.Properties", "Set",
    1446            4 :                             [proxy.iface, prop, cockpit.variant(sig, value)]);
    1447            4 :     };
    1448              : 
    1449            9 :     client.stratis_stopped_pool_devnodes = (uuid) => {
    1450            9 :         const devs = client.stratis_manager.StoppedPools[uuid]?.devs;
    1451            9 :         if (!devs)
    1452            2 :             return [];
    1453            9 :         if (devs.t == 'aa{ss}')
    1454            9 :             return devs.v.map(d => d.devnode);
    1455            2 :         else if (devs.t == 'aa{sv}')
    1456            0 :             return devs.v.map(d => d.devnode.v);
    1457              :         else
    1458            2 :             return [];
    1459            9 :     };
    1460              : 
    1461           98 :     client.features.stratis = true;
    1462           98 :     client.stratis_pools = client.stratis_manager.client.proxies("org.storage.stratis3.pool.r" +
    1463           98 :         client.stratis_interface_revision,
    1464           98 :                                                                  "/org/storage/stratis3",
    1465           98 :                                                                  { watch: false });
    1466           98 :     client.stratis_blockdevs = client.stratis_manager.client.proxies("org.storage.stratis3.blockdev.r" +
    1467           98 :         client.stratis_interface_revision,
    1468           98 :                                                                      "/org/storage/stratis3",
    1469           98 :                                                                      { watch: false });
    1470           98 :     client.stratis_filesystems = client.stratis_manager.client.proxies("org.storage.stratis3.filesystem.r" +
    1471           98 :         client.stratis_interface_revision,
    1472           98 :                                                                        "/org/storage/stratis3",
    1473           98 :                                                                        { watch: false });
    1474              : 
    1475           98 :     await stratis.watch({ path_namespace: "/org/storage/stratis3" });
    1476           16 :     client.stratis_manager.client.addEventListener('notify', (event, data) => {
    1477           16 :         client.update();
    1478           16 :     });
    1479          112 : }
    1480              : 
    1481          112 : function init_client(manager, callback) {
    1482          112 :     if (client.manager)
    1483          112 :         return;
    1484              : 
    1485          112 :     client.storaged_client = manager.client;
    1486          112 :     client.manager = manager;
    1487              : 
    1488          112 :     init_proxies().then(() => init_model(callback));
    1489          112 : }
    1490              : 
    1491          113 : client.init = function init_storaged(callback) {
    1492          113 :     const udisks = cockpit.dbus("org.freedesktop.UDisks2", { superuser: "try" });
    1493          113 :     const udisks_manager = udisks.proxy("org.freedesktop.UDisks2.Manager",
    1494          113 :                                         "/org/freedesktop/UDisks2/Manager", { watch: true });
    1495              : 
    1496          112 :     udisks_manager.wait().then(() => init_client(udisks_manager, callback))
    1497            0 :             .catch(ex => {
    1498            0 :                 console.warn("client.init(): udisks manager proxy failed:", JSON.stringify(ex));
    1499            0 :                 client.features = false;
    1500            0 :                 callback();
    1501            0 :             });
    1502              : 
    1503          112 :     udisks_manager.addEventListener("changed", () => init_client(udisks_manager, callback));
    1504          113 : };
    1505              : 
    1506           48 : client.wait_for = function wait_for(cond) {
    1507           48 :     return new Promise(resolve => {
    1508           48 :         function check() {
    1509           48 :             const res = cond();
    1510           48 :             if (res) {
    1511           48 :                 client.removeEventListener("changed", check);
    1512           48 :                 resolve(res);
    1513           48 :             }
    1514           48 :         }
    1515              : 
    1516           48 :         client.addEventListener("changed", check);
    1517           48 :         check();
    1518           48 :     });
    1519           48 : };
    1520              : 
    1521          106 : client.get_config = (name, def) =>
    1522          106 :     get_manifest_config_matchlist("storage", name, def, [client.os_release.PLATFORM_ID, client.os_release.ID]);
    1523              : 
    1524          113 : client.in_anaconda_mode = in_anaconda_mode;
    1525              : 
    1526          112 : client.strip_mount_point_prefix = (dir) => {
    1527           23 :     const mpp = client.anaconda?.mount_point_prefix;
    1528              : 
    1529           23 :     if (dir && mpp) {
    1530           23 :         if (dir.indexOf(mpp) != 0)
    1531           23 :             return false;
    1532              : 
    1533           18 :         dir = dir.substring(mpp.length);
    1534           18 :         if (dir == "")
    1535           17 :             dir = "/";
    1536           23 :     }
    1537              : 
    1538          110 :     return dir;
    1539          112 : };
    1540              : 
    1541           52 : client.add_mount_point_prefix = (dir) => {
    1542            5 :     const mpp = client.anaconda?.mount_point_prefix;
    1543            5 :     if (mpp && dir != "") {
    1544            5 :         if (dir == "/")
    1545            3 :             dir = mpp;
    1546              :         else
    1547            4 :             dir = mpp + dir;
    1548            5 :     }
    1549           52 :     return dir;
    1550           52 : };
    1551              : 
    1552           11 : client.should_ignore_device = (devname) => {
    1553           11 :     return client.anaconda?.available_devices && client.anaconda.available_devices.indexOf(devname) == -1;
    1554           11 : };
    1555              : 
    1556           11 : client.should_ignore_block = (block) => {
    1557           11 :     return client.should_ignore_device(utils.decode_filename(block.PreferredDevice));
    1558           11 : };
    1559              : 
    1560          113 : export default client;
        

Generated by: LCOV version 2.0-1