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 0 : } 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 0 : 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 5 : 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 3 : return Promise.resolve();
213 12 : }
214 :
215 7 : if (size < orig_size) {
216 7 : return fsys_resize().then(crypto_resize)
217 7 : .then(lvol_or_part_resize);
218 7 : } 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 1 : } else {
455 3 : grow_size = block.Size;
456 3 : }
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 4 : pvs = pvs.map(spc => spc.block.path);
468 :
469 4 : if (!has_subvols)
470 2 : 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 2 : if (!usage.Teardown && size_fields.length + passphrase_fields.length === 0) {
501 2 : return lvol_or_part_and_fsys_resize(client, lvol_or_part, grow_size, info.grow_needs_unmount,
502 2 : null, prepare_pvs(initial_pvs));
503 2 : }
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 7 : export function shrink_dialog(client, lvol_or_part, info, to_fit) {
544 7 : let title;
545 7 : let block;
546 7 : let name;
547 7 : let orig_size;
548 7 : let round_size;
549 :
550 6 : if (lvol_or_part.iface == "org.freedesktop.UDisks2.LogicalVolume") {
551 6 : const vgroup = client.vgroups[lvol_or_part.VolumeGroup];
552 :
553 6 : title = _("Shrink logical volume");
554 6 : block = client.lvols_block[lvol_or_part.path];
555 6 : name = lvol_or_part.Name;
556 6 : orig_size = lvol_or_part.Size;
557 6 : 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 7 : const usage = get_active_usage(client,
567 1 : block && !to_fit && info.shrink_needs_unmount ? block.path : null,
568 7 : _("shrink"), null,
569 7 : 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 7 : let shrink_size;
580 7 : 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 3 : const crypto = client.blocks_crypto[block.path];
592 3 : const cleartext = client.blocks_cleartext[block.path];
593 3 : let content_path = null;
594 3 : 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 2 : content_path = block.path;
603 2 : }
604 :
605 3 : const fsys = client.blocks_fsys[content_path];
606 3 : if (fsys)
607 3 : shrink_size = fsys.Size + crypto_overhead;
608 :
609 3 : const vdo = client.legacy_vdo_overlay.find_by_backing_block(client.blocks[content_path]);
610 3 : if (vdo)
611 0 : shrink_size = vdo.physical_size + crypto_overhead;
612 :
613 3 : const stratis_bdev = client.blocks_stratis_blockdev[content_path];
614 3 : if (stratis_bdev)
615 0 : 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 3 : }
622 :
623 7 : let recovered_passphrase;
624 7 : 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 2 : if (usage.length == 0 && size_fields.length + passphrase_fields.length === 0) {
629 2 : return lvol_or_part_and_fsys_resize(client, lvol_or_part, shrink_size, false, null);
630 2 : }
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 7 : ]
657 7 : });
658 7 : }
|