LCOV - code coverage report
Current view: top level - pkg/storaged/block - resize.jsx Coverage Total Hit
Test: cockpit Lines: 90.4 % 533 482
Test Date: 2026-07-17 14:32:59

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2023 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import React from "react";
       7          113 : import cockpit from "cockpit";
       8              : import client from "../client.js";
       9              : 
      10              : import {
      11              :     block_name, get_active_usage, teardown_active_usage,
      12              :     undo_temporary_teardown, is_mounted_synch, get_partitions
      13              : } from "../utils.js";
      14              : import {
      15              :     existing_passphrase_fields, init_existing_passphrase,
      16              :     request_passphrase_on_error_handler
      17              : } from "../crypto/keyslots.jsx";
      18              : import {
      19              :     dialog_open, SizeSlider, BlockingMessage, TeardownMessage, SelectSpaces,
      20              :     init_teardown_usage
      21              : } from "../dialog.jsx";
      22              : import { std_reply } from "../stratis/utils.jsx";
      23              : import { pvs_to_spaces } from "../lvm2/utils.jsx";
      24              : 
      25          113 : const _ = cockpit.gettext;
      26              : 
      27          105 : export function check_unused_space(path) {
      28          105 :     const block = client.blocks[path];
      29          105 :     const lvm2 = client.blocks_lvm2[path];
      30           34 :     const lvol = lvm2 && client.lvols[lvm2.LogicalVolume];
      31          105 :     const part = client.blocks_part[path];
      32              : 
      33          105 :     let size;
      34          105 :     let min_change;
      35              : 
      36           34 :     if (lvol) {
      37           34 :         size = lvol.Size;
      38           34 :         min_change = client.vgroups[lvol.VolumeGroup].ExtentSize;
      39           33 :     } else if (part) {
      40          104 :         size = part.Size;
      41          104 :         min_change = 1024 * 1024;
      42           12 :     } else {
      43           12 :         return null;
      44           12 :     }
      45              : 
      46           24 :     if (size != block.Size) {
      47              :         // Let's ignore inconsistent lvol,part/block combinations.
      48              :         // These happen during a resize and the inconsistency will
      49              :         // eventually go away.
      50           24 :         return null;
      51           24 :     }
      52              : 
      53          105 :     let content_path = null;
      54          105 :     let crypto_overhead = 0;
      55              : 
      56          105 :     const crypto = client.blocks_crypto[block.path];
      57          105 :     const cleartext = client.blocks_cleartext[block.path];
      58           20 :     if (crypto) {
      59           20 :         if (crypto.MetadataSize !== undefined && cleartext) {
      60           20 :             content_path = cleartext.path;
      61           20 :             crypto_overhead = crypto.MetadataSize;
      62           20 :         }
      63           20 :     } else {
      64          105 :         content_path = path;
      65          105 :     }
      66              : 
      67          105 :     const fsys = client.blocks_fsys[content_path];
      68          105 :     const content_block = client.blocks[content_path];
      69           20 :     const vdo = content_block ? client.legacy_vdo_overlay.find_by_backing_block(content_block) : null;
      70          105 :     const stratis_bdev = client.blocks_stratis_blockdev[content_path];
      71              : 
      72           18 :     if (fsys && fsys.Size && (size - fsys.Size - crypto_overhead) > min_change && fsys.Resize) {
      73           18 :         return {
      74           18 :             volume_size: size - crypto_overhead,
      75           18 :             content_size: fsys.Size
      76           18 :         };
      77           18 :     }
      78              : 
      79           12 :     if (vdo && (size - vdo.physical_size - crypto_overhead) > min_change) {
      80           12 :         return {
      81           12 :             volume_size: size - crypto_overhead,
      82           12 :             content_size: vdo.physical_size
      83           12 :         };
      84           12 :     }
      85              : 
      86           14 :     if (stratis_bdev && (size - Number(stratis_bdev.TotalPhysicalSize) - crypto_overhead) > min_change) {
      87           14 :         return {
      88           14 :             volume_size: size - crypto_overhead,
      89           14 :             content_size: Number(stratis_bdev.TotalPhysicalSize)
      90           14 :         };
      91           14 :     }
      92              : 
      93          105 :     return null;
      94          105 : }
      95              : 
      96           12 : function lvol_or_part_and_fsys_resize(client, lvol_or_part, size, offline, passphrase, pvs) {
      97           12 :     let fsys;
      98           12 :     let crypto_overhead;
      99           12 :     let vdo;
     100           12 :     let stratis_bdev;
     101           12 :     let orig_size;
     102           12 :     let block;
     103              : 
     104           11 :     if (lvol_or_part.iface == "org.freedesktop.UDisks2.LogicalVolume") {
     105           11 :         orig_size = lvol_or_part.Size;
     106           11 :         block = client.lvols_block[lvol_or_part.path];
     107           11 :         if (!block)
     108            1 :             return lvol_or_part.Resize(size, { });
     109            0 :     } else {
     110            1 :         orig_size = lvol_or_part.Size;
     111            1 :         block = client.blocks[lvol_or_part.path];
     112            1 :     }
     113              : 
     114           12 :     const crypto = client.blocks_crypto[block.path];
     115            2 :     if (crypto) {
     116            2 :         const cleartext = client.blocks_cleartext[block.path];
     117            2 :         if (!cleartext)
     118            2 :             return;
     119            2 :         fsys = client.blocks_fsys[cleartext.path];
     120            2 :         vdo = client.legacy_vdo_overlay.find_by_backing_block(cleartext);
     121            2 :         stratis_bdev = client.blocks_stratis_blockdev[cleartext.path];
     122            2 :         crypto_overhead = crypto.MetadataSize;
     123            0 :     } else {
     124           10 :         fsys = client.blocks_fsys[block.path];
     125           10 :         vdo = client.legacy_vdo_overlay.find_by_backing_block(block);
     126           10 :         stratis_bdev = client.blocks_stratis_blockdev[block.path];
     127           10 :         crypto_overhead = 0;
     128           10 :     }
     129              : 
     130           12 :     function fsys_resize() {
     131            8 :         if (fsys) {
     132              :             // When growing a filesystem, always grow it to fill its
     133              :             // block device. This is always the right thing to do in
     134              :             // Cockpit.
     135              :             //
     136            4 :             const resize_size = (size > orig_size) ? 0 : size - crypto_overhead;
     137              : 
     138              :             // HACK - https://bugzilla.redhat.com/show_bug.cgi?id=1934567
     139              :             //
     140              :             // block_fsys.MountedAt might be out of synch with reality
     141              :             // here if resizing the crypto card accidentally
     142              :             // triggered an unmount.  Thus, we check synchronously
     143              :             // whether or not we should be doing a offline resize or
     144              :             // not.
     145              :             //
     146              :             // Another option for us would be to just mount the
     147              :             // filesystem back if that's what we expect, to undo the
     148              :             // bug mentioned above. But let's be a bit more passive
     149              :             // here and hope the bug gets fixed eventually.
     150            8 :             return (is_mounted_synch(client.blocks[fsys.path])
     151            8 :                     .then(is_mounted => {
     152              :                         // When doing an offline resize, we need to first repair the filesystem.
     153            5 :                         if (!is_mounted) {
     154            5 :                             return (fsys.Repair({ })
     155            5 :                                     .then(() => fsys.Resize(resize_size, { })));
     156            2 :                         } else {
     157            5 :                             return fsys.Resize(resize_size, { });
     158            5 :                         }
     159            8 :                     }));
     160            0 :         } else if (vdo) {
     161            0 :             if (size - crypto_overhead > vdo.physical_size)
     162            0 :                 return vdo.grow_physical();
     163            0 :             else if (size - crypto_overhead < vdo.physical_size)
     164            0 :                 return Promise.reject(_("VDO backing devices can not be made smaller"));
     165              :             else
     166            0 :                 return Promise.resolve();
     167            0 :         } else if (stratis_bdev) {
     168            2 :             const delta = size - crypto_overhead - Number(stratis_bdev.TotalPhysicalSize);
     169            2 :             if (delta > 0) {
     170            2 :                 const pool = client.stratis_pools[stratis_bdev.Pool];
     171            2 :                 return pool.GrowPhysicalDevice(stratis_bdev.Uuid).then(std_reply);
     172            2 :             } else if (delta < 0)
     173              :                 // This shouldn't happen. But if it does, continuing is harmful, so we throw an error.
     174            0 :                 return Promise.reject(_("Stratis blockdevs can not be made smaller")); // not-covered: safety check
     175              :             else
     176            2 :                 return Promise.resolve();
     177            0 :         } else if (!block.IdUsage) {
     178              :             // Growing or shrinking unformatted data, nothing to do
     179            3 :             return Promise.resolve();
     180            0 :         } else if (size < orig_size) {
     181              :             // This shouldn't happen. But if it does, continuing is harmful, so we throw an error.
     182            0 :             return Promise.reject(_("Unrecognized data can not be made smaller here.")); // not-covered: safety check
     183            0 :         } else {
     184              :             // Growing unrecognized content, nothing to do.
     185            0 :             return Promise.resolve();
     186            0 :         }
     187           12 :     }
     188              : 
     189           12 :     function crypto_resize() {
     190              :         // When growing a LUKS device, always grow it to fill its
     191              :         // block device. This is always the right thing to do in
     192              :         // Cockpit.
     193              :         //
     194            7 :         const resize_size = (size > orig_size) ? 0 : size - crypto_overhead;
     195              : 
     196            2 :         if (crypto) {
     197            2 :             const opts = { };
     198            2 :             if (passphrase)
     199            2 :                 opts.passphrase = { t: "s", v: passphrase };
     200            2 :             return crypto.Resize(resize_size, opts);
     201            0 :         } else {
     202           10 :             return Promise.resolve();
     203           10 :         }
     204           12 :     }
     205              : 
     206           12 :     function lvol_or_part_resize() {
     207           12 :         if (size != orig_size) {
     208              :             // Both LogicalVolume and Partition have a Resize method
     209              :             // with the same signature, so this will work on both.
     210            1 :             return lvol_or_part.Resize(size, { pvs: pvs ? { t: 'ao', v: pvs } : undefined });
     211           12 :         } else
     212            5 :             return Promise.resolve();
     213           12 :     }
     214              : 
     215            9 :     if (size < orig_size) {
     216            9 :         return fsys_resize().then(crypto_resize)
     217            9 :                 .then(lvol_or_part_resize);
     218            9 :     } else if (size >= orig_size) {
     219           12 :         return lvol_or_part_resize().then(crypto_resize)
     220           12 :                 .then(fsys_resize);
     221           12 :     }
     222           12 : }
     223              : 
     224          105 : export function get_resize_info(client, block, to_fit) {
     225          105 :     let info;
     226          105 :     let shrink_excuse;
     227          105 :     let grow_excuse;
     228              : 
     229          105 :     if (block) {
     230           20 :         if (block.IdUsage == 'crypto' && client.blocks_crypto[block.path]) {
     231           20 :             const cleartext = client.blocks_cleartext[block.path];
     232              : 
     233           20 :             if (!cleartext) {
     234           20 :                 info = { };
     235           20 :                 shrink_excuse = grow_excuse = _("Unlock before resizing");
     236           20 :             } else {
     237           20 :                 return get_resize_info(client, cleartext, to_fit);
     238           20 :             }
     239           20 :         } else if (block.IdUsage == 'filesystem') {
     240          104 :             info = client.fsys_info && client.fsys_info[block.IdType];
     241              : 
     242           13 :             if (!info) {
     243           13 :                 info = { };
     244           13 :                 shrink_excuse = grow_excuse = cockpit.format(_("$0 can not be resized here"),
     245           13 :                                                              block.IdType);
     246           13 :             } else {
     247          102 :                 if (!info.can_shrink && !info.can_grow) {
     248          102 :                     shrink_excuse = grow_excuse = cockpit.format(_("$0 can not be resized"),
     249          102 :                                                                  block.IdType);
     250           29 :                 } else {
     251           31 :                     if (!info.can_shrink)
     252           13 :                         shrink_excuse = cockpit.format(_("$0 can not be made smaller"),
     253           13 :                                                        block.IdType);
     254           31 :                     if (!info.can_grow)
     255           12 :                         grow_excuse = cockpit.format(_("$0 can not be made larger"),
     256           12 :                                                      block.IdType);
     257           31 :                 }
     258          104 :             }
     259           14 :         } else if (client.blocks_stratis_blockdev[block.path]) {
     260           14 :             info = {
     261           14 :                 can_shrink: false,
     262           14 :                 can_grow: true,
     263           14 :                 grow_needs_unmount: false
     264           14 :             };
     265           14 :             shrink_excuse = _("Stratis blockdevs can not be made smaller");
     266           14 :         } else if (block.IdUsage == 'raid') {
     267           15 :             info = { };
     268           15 :             shrink_excuse = grow_excuse = _("Physical volumes can not be resized here");
     269           12 :         } else if (client.legacy_vdo_overlay.find_by_backing_block(block)) {
     270           12 :             info = {
     271           12 :                 can_shrink: false,
     272           12 :                 can_grow: true,
     273           12 :                 grow_needs_unmount: false
     274           12 :             };
     275           12 :             shrink_excuse = _("VDO backing devices can not be made smaller");
     276           12 :         } else if (client.blocks_swap[block.path]) {
     277           13 :             info = {
     278           13 :                 can_shrink: false,
     279           13 :                 can_grow: false,
     280           13 :             };
     281           13 :             shrink_excuse = grow_excuse = _("Swap can not be resized here");
     282           13 :         } else if (!block.IdUsage) {
     283          105 :             info = {
     284          105 :                 can_shrink: true,
     285          105 :                 can_grow: true,
     286          105 :                 shrink_needs_unmount: false,
     287          105 :                 grow_needs_unmount: false,
     288          105 :             };
     289           21 :         } else {
     290           21 :             info = {
     291           21 :                 can_shrink: false,
     292           21 :                 can_grow: true,
     293           21 :                 grow_needs_unmount: true
     294           21 :             };
     295           21 :             shrink_excuse = _("Unrecognized data can not be made smaller here");
     296           21 :         }
     297           20 :         if (to_fit) {
     298              :             // Shrink to fit doesn't need to resize the content
     299           20 :             shrink_excuse = null;
     300           20 :         }
     301           12 :     } else {
     302           12 :         info = { };
     303           12 :         shrink_excuse = grow_excuse = _("Activate before resizing");
     304           12 :     }
     305              : 
     306          105 :     return { info, shrink_excuse, grow_excuse };
     307          105 : }
     308              : 
     309          104 : export function free_space_after_part(client, part) {
     310          104 :     const parts = get_partitions(client, client.blocks[part.Table]);
     311              : 
     312          104 :     function find_it(parts) {
     313          104 :         for (const p of parts) {
     314           20 :             if (p.type == "free" && p.start == part.Offset + part.Size)
     315           20 :                 return p.size;
     316           14 :             if (p.type == "container") {
     317           14 :                 const s = find_it(p.partitions);
     318           14 :                 if (s)
     319           13 :                     return s;
     320           14 :             }
     321          104 :         }
     322          104 :         return false;
     323          104 :     }
     324              : 
     325          104 :     return find_it(parts) || 0;
     326          104 : }
     327              : 
     328           12 : export function grow_dialog(client, lvol_or_part, info, to_fit) {
     329           12 :     let title;
     330           12 :     let block;
     331           12 :     let name;
     332           12 :     let orig_size;
     333           12 :     let max_size;
     334           12 :     let allow_infinite;
     335           12 :     let round_size;
     336           12 :     let has_subvols;
     337           12 :     let subvols;
     338           12 :     let pvs_as_spaces;
     339           12 :     let initial_pvs;
     340              : 
     341           11 :     function compute_max_size(spaces) {
     342           11 :         const layout = lvol_or_part.Layout;
     343           11 :         const pvs = spaces.map(s => s.pvol);
     344           11 :         const n_pvs = pvs.length;
     345           11 :         const sum = pvs.reduce((sum, pv) => sum + pv.FreeSize, 0);
     346           11 :         const min = Math.min.apply(null, pvs.map(pv => pv.FreeSize));
     347              : 
     348            9 :         if (!has_subvols) {
     349            9 :             return sum;
     350            0 :         } else if (layout == "raid0") {
     351            1 :             return n_pvs * min;
     352            1 :         } else if (layout == "raid1") {
     353            1 :             return min;
     354            1 :         } else if (layout == "raid10") {
     355            1 :             return (n_pvs / 2) * min;
     356            1 :         } else if ((layout == "raid4" || layout == "raid5")) {
     357            2 :             return (n_pvs - 1) * min;
     358            1 :         } else if (layout == "raid6") {
     359            1 :             return (n_pvs - 2) * min;
     360            1 :         } else
     361            0 :             return 0; // not-covered: internal error
     362           11 :     }
     363              : 
     364           11 :     if (lvol_or_part.iface == "org.freedesktop.UDisks2.LogicalVolume") {
     365           11 :         const vgroup = client.vgroups[lvol_or_part.VolumeGroup];
     366           11 :         const pool = client.lvols[lvol_or_part.ThinPool];
     367              : 
     368           11 :         pvs_as_spaces = pvs_to_spaces(client, client.vgroups_pvols[vgroup.path].filter(pvol => pvol.FreeSize > 0));
     369           11 :         subvols = client.lvols_stripe_summary[lvol_or_part.path];
     370           11 :         has_subvols = subvols && (lvol_or_part.Layout == "mirror" || lvol_or_part.Layout.indexOf("raid") == 0);
     371              : 
     372           11 :         if (!has_subvols)
     373            0 :             initial_pvs = pvs_as_spaces;
     374            2 :         else {
     375            2 :             initial_pvs = [];
     376              : 
     377              :             // Step 1: Find the spaces that are already used for a
     378              :             // subvolume.  If a subvolume uses more than one, prefer the
     379              :             // one with more available space.
     380            2 :             for (const sv of subvols) {
     381            2 :                 let sel = null;
     382            2 :                 for (const p in sv) {
     383            2 :                     for (const spc of pvs_as_spaces)
     384            0 :                         if (spc.block.path == p && (!sel || sel.size < spc.size))
     385            2 :                             sel = spc;
     386            2 :                 }
     387            2 :                 if (sel)
     388            2 :                     initial_pvs.push(sel);
     389            2 :             }
     390              : 
     391              :             // Step 2: Select missing one randomly.
     392            2 :             for (const pv of pvs_as_spaces) {
     393            1 :                 if (initial_pvs.indexOf(pv) == -1 && initial_pvs.length < subvols.length)
     394            1 :                     initial_pvs.push(pv);
     395            2 :             }
     396            2 :         }
     397              : 
     398           11 :         title = _("Grow logical volume");
     399           11 :         block = client.lvols_block[lvol_or_part.path];
     400           11 :         name = lvol_or_part.Name;
     401           11 :         orig_size = lvol_or_part.Size;
     402            0 :         max_size = pool ? pool.Size * 3 : lvol_or_part.Size + compute_max_size(initial_pvs);
     403           11 :         allow_infinite = !!pool;
     404           11 :         round_size = vgroup.ExtentSize;
     405            0 :     } else {
     406            1 :         has_subvols = false;
     407            1 :         title = _("Grow partition");
     408            1 :         block = client.blocks[lvol_or_part.path];
     409            1 :         name = block_name(block);
     410            1 :         orig_size = lvol_or_part.Size;
     411            1 :         max_size = lvol_or_part.Size + free_space_after_part(client, lvol_or_part);
     412            1 :         allow_infinite = false;
     413            1 :         round_size = 1024 * 1024;
     414            1 :     }
     415              : 
     416           12 :     const usage = get_active_usage(client,
     417            0 :                                    block && info.grow_needs_unmount ? block.path : null,
     418           12 :                                    _("grow"), null,
     419           12 :                                    true);
     420              : 
     421            0 :     if (usage.Blocking) {
     422            0 :         dialog_open({
     423            0 :             Title: cockpit.format(_("$0 is in use"), name),
     424            0 :             Body: BlockingMessage(usage)
     425            0 :         });
     426            0 :         return;
     427            0 :     }
     428              : 
     429           12 :     let grow_size;
     430           12 :     const size_fields = [];
     431           10 :     if (!to_fit) {
     432            7 :         if ((has_subvols || lvol_or_part.Layout == "linear") && pvs_as_spaces.length > 1)
     433            3 :             size_fields.push(
     434            3 :                 SelectSpaces("pvs", _("Physical Volumes"),
     435            3 :                              {
     436            3 :                                  spaces: pvs_as_spaces,
     437            3 :                                  value: initial_pvs,
     438            3 :                                  min_selected: subvols.length,
     439            3 :                                  validate: val => {
     440            2 :                                      if (has_subvols && subvols.length != val.length)
     441            0 :                                          return cockpit.format(_("Exactly $0 physical volumes must be selected"),
     442            0 :                                                                subvols.length);
     443            3 :                                  }
     444            3 :                              }));
     445           10 :         size_fields.push(
     446           10 :             SizeSlider("size", _("Size"),
     447           10 :                        {
     448           10 :                            value: orig_size,
     449           10 :                            min: orig_size,
     450           10 :                            max: max_size,
     451           10 :                            allow_infinite,
     452           10 :                            round: round_size,
     453           10 :                        }));
     454            3 :     } else {
     455            5 :         grow_size = block.Size;
     456            5 :     }
     457              : 
     458           12 :     let recovered_passphrase;
     459           12 :     let passphrase_fields = [];
     460            2 :     if (block && block.IdType == "crypto_LUKS" && block.IdVersion == 2)
     461            2 :         passphrase_fields = existing_passphrase_fields(_("Resizing an encrypted filesystem requires unlocking the disk. Please provide a current disk passphrase."));
     462              : 
     463           12 :     function prepare_pvs(pvs) {
     464           12 :         if (!pvs)
     465            9 :             return pvs;
     466              : 
     467            6 :         pvs = pvs.map(spc => spc.block.path);
     468              : 
     469            6 :         if (!has_subvols)
     470            4 :             return pvs;
     471              : 
     472            2 :         const subvol_pvs = [];
     473              : 
     474              :         // Step 1: Find PVs that are already used by a subvolume
     475            2 :         subvols.forEach((sv, idx) => {
     476            2 :             subvol_pvs[idx] = null;
     477            2 :             for (const pv in sv) {
     478            2 :                 if (pvs.indexOf(pv) >= 0 && subvol_pvs.indexOf(pv) == -1) {
     479            2 :                     subvol_pvs[idx] = pv;
     480            2 :                     break;
     481            2 :                 }
     482            2 :             }
     483            2 :         });
     484              : 
     485              :         // Step 2: Use the rest for the leftover subvolumes
     486            2 :         subvols.forEach((sv, idx) => {
     487            1 :             if (!subvol_pvs[idx]) {
     488            1 :                 for (const pv of pvs) {
     489            1 :                     if (subvol_pvs.indexOf(pv) == -1) {
     490            1 :                         subvol_pvs[idx] = pv;
     491            1 :                         break;
     492            1 :                     }
     493            1 :                 }
     494            1 :             }
     495            2 :         });
     496              : 
     497            2 :         return subvol_pvs;
     498           12 :     }
     499              : 
     500            4 :     if (!usage.Teardown && size_fields.length + passphrase_fields.length === 0) {
     501            4 :         return lvol_or_part_and_fsys_resize(client, lvol_or_part, grow_size, info.grow_needs_unmount,
     502            4 :                                             null, prepare_pvs(initial_pvs));
     503            4 :     }
     504              : 
     505           11 :     const dlg = dialog_open({
     506           11 :         Title: title,
     507           11 :         Teardown: TeardownMessage(usage),
     508            2 :         Body: has_subvols && <div><p>{cockpit.format(_("Exactly $0 physical volumes need to be selected, one for each stripe of the logical volume."), subvols.length)}</p><br /></div>,
     509           12 :         Fields: size_fields.concat(passphrase_fields),
     510           11 :         update: (dlg, vals, trigger) => {
     511            3 :             if (vals.pvs) {
     512            3 :                 const max = lvol_or_part.Size + compute_max_size(vals.pvs);
     513            3 :                 if (vals.size > max)
     514            0 :                     dlg.set_values({ size: max });
     515            3 :                 dlg.set_options("size", { max });
     516            3 :             }
     517           11 :         },
     518           12 :         Action: {
     519           12 :             Title: _("Grow"),
     520           12 :             disable_on_error: usage.Teardown,
     521           11 :             action: function (vals) {
     522           11 :                 return teardown_active_usage(client, usage)
     523           11 :                         .then(function () {
     524           11 :                             return (lvol_or_part_and_fsys_resize(client, lvol_or_part,
     525            0 :                                                                  to_fit ? grow_size : vals.size,
     526           11 :                                                                  info.grow_needs_unmount,
     527            9 :                                                                  vals.passphrase || recovered_passphrase,
     528           11 :                                                                  prepare_pvs(vals.pvs))
     529           11 :                                     .then(() => undo_temporary_teardown(client, usage))
     530           11 :                                     .catch(request_passphrase_on_error_handler(dlg, vals, recovered_passphrase, block)));
     531           11 :                         });
     532           11 :             }
     533           12 :         },
     534           12 :         Inits: [
     535           12 :             init_teardown_usage(client, usage),
     536           12 :             passphrase_fields.length
     537            2 :                 ? init_existing_passphrase(block, false, pp => { recovered_passphrase = pp })
     538            9 :                 : null
     539           12 :         ]
     540           12 :     });
     541           12 : }
     542              : 
     543            9 : export function shrink_dialog(client, lvol_or_part, info, to_fit) {
     544            9 :     let title;
     545            9 :     let block;
     546            9 :     let name;
     547            9 :     let orig_size;
     548            9 :     let round_size;
     549              : 
     550            8 :     if (lvol_or_part.iface == "org.freedesktop.UDisks2.LogicalVolume") {
     551            8 :         const vgroup = client.vgroups[lvol_or_part.VolumeGroup];
     552              : 
     553            8 :         title = _("Shrink logical volume");
     554            8 :         block = client.lvols_block[lvol_or_part.path];
     555            8 :         name = lvol_or_part.Name;
     556            8 :         orig_size = lvol_or_part.Size;
     557            8 :         round_size = vgroup.ExtentSize;
     558            0 :     } else {
     559            1 :         title = _("Shrink partition");
     560            1 :         block = client.blocks[lvol_or_part.path];
     561            1 :         name = block_name(block);
     562            1 :         orig_size = lvol_or_part.Size;
     563            1 :         round_size = 1024 * 1024;
     564            1 :     }
     565              : 
     566            9 :     const usage = get_active_usage(client,
     567            1 :                                    block && !to_fit && info.shrink_needs_unmount ? block.path : null,
     568            9 :                                    _("shrink"), null,
     569            9 :                                    true);
     570              : 
     571            0 :     if (usage.Blocking) {
     572            0 :         dialog_open({
     573            0 :             Title: cockpit.format(_("$0 is in use"), name),
     574            0 :             Body: BlockingMessage(usage)
     575            0 :         });
     576            0 :         return;
     577            0 :     }
     578              : 
     579            9 :     let shrink_size;
     580            9 :     let size_fields = [];
     581            5 :     if (!to_fit) {
     582            5 :         size_fields = [
     583            5 :             SizeSlider("size", _("Size"),
     584            5 :                        {
     585            5 :                            value: orig_size,
     586            5 :                            max: orig_size,
     587            5 :                            round: round_size,
     588            5 :                        })
     589            5 :         ];
     590            1 :     } else {
     591            5 :         const crypto = client.blocks_crypto[block.path];
     592            5 :         const cleartext = client.blocks_cleartext[block.path];
     593            5 :         let content_path = null;
     594            5 :         let crypto_overhead = 0;
     595              : 
     596            1 :         if (crypto) {
     597            1 :             if (crypto.MetadataSize !== undefined && cleartext) {
     598            1 :                 content_path = cleartext.path;
     599            1 :                 crypto_overhead = crypto.MetadataSize;
     600            1 :             }
     601            0 :         } else {
     602            4 :             content_path = block.path;
     603            4 :         }
     604              : 
     605            5 :         const fsys = client.blocks_fsys[content_path];
     606            5 :         if (fsys)
     607            3 :             shrink_size = fsys.Size + crypto_overhead;
     608              : 
     609            5 :         const vdo = client.legacy_vdo_overlay.find_by_backing_block(client.blocks[content_path]);
     610            5 :         if (vdo)
     611            0 :             shrink_size = vdo.physical_size + crypto_overhead;
     612              : 
     613            5 :         const stratis_bdev = client.blocks_stratis_blockdev[content_path];
     614            5 :         if (stratis_bdev)
     615            2 :             shrink_size = Number(stratis_bdev.TotalPhysicalSize) + crypto_overhead;
     616              : 
     617            0 :         if (shrink_size === undefined) {
     618            0 :             console.warn("Couldn't determine size to shrink to."); // not-covered: safety check
     619            0 :             return; // not-covered: safety check
     620            0 :         }
     621            5 :     }
     622              : 
     623            9 :     let recovered_passphrase;
     624            9 :     let passphrase_fields = [];
     625            2 :     if (block && block.IdType == "crypto_LUKS" && block.IdVersion == 2)
     626            2 :         passphrase_fields = existing_passphrase_fields(_("Resizing an encrypted filesystem requires unlocking the disk. Please provide a current disk passphrase."));
     627              : 
     628            4 :     if (usage.length == 0 && size_fields.length + passphrase_fields.length === 0) {
     629            4 :         return lvol_or_part_and_fsys_resize(client, lvol_or_part, shrink_size, false, null);
     630            4 :     }
     631              : 
     632            6 :     const dlg = dialog_open({
     633            6 :         Title: title,
     634            6 :         Teardown: TeardownMessage(usage),
     635            6 :         Fields: size_fields.concat(passphrase_fields),
     636            6 :         Action: {
     637            6 :             Title: _("Shrink"),
     638            6 :             disable_on_error: usage.Teardown,
     639            6 :             action: function (vals) {
     640            6 :                 return teardown_active_usage(client, usage)
     641            6 :                         .then(function () {
     642            6 :                             return (lvol_or_part_and_fsys_resize(client, lvol_or_part,
     643            0 :                                                                  to_fit ? shrink_size : vals.size,
     644            0 :                                                                  to_fit ? false : info.shrink_needs_unmount,
     645            4 :                                                                  vals.passphrase || recovered_passphrase)
     646            6 :                                     .then(() => undo_temporary_teardown(client, usage))
     647            6 :                                     .catch(request_passphrase_on_error_handler(dlg, vals, recovered_passphrase, block)));
     648            6 :                         });
     649            6 :             }
     650            6 :         },
     651            6 :         Inits: [
     652            6 :             init_teardown_usage(client, usage),
     653            6 :             passphrase_fields.length
     654            2 :                 ? init_existing_passphrase(block, false, pp => { recovered_passphrase = pp })
     655            4 :                 : null
     656            9 :         ]
     657            9 :     });
     658            9 : }
        

Generated by: LCOV version 2.0-1