Skip to content

Commit 5803fbe

Browse files
committed
storage: Confirmation before erasing actual data
But only in Anaconda mode.
1 parent 5bfe3ce commit 5803fbe

18 files changed

+256
-66
lines changed

pkg/storaged/block/format-dialog.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
dialog_open,
3333
TextInput, PassInput, CheckBoxes, SelectOne, SizeSlider,
3434
BlockingMessage, TeardownMessage,
35-
init_active_usage_processes
35+
init_teardown_usage
3636
} from "../dialog.jsx";
3737

3838
import { get_fstab_config, is_valid_mount_point } from "../filesystem/utils.jsx";
@@ -630,7 +630,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
630630
}
631631
},
632632
Inits: [
633-
init_active_usage_processes(client, usage),
633+
init_teardown_usage(client, usage),
634634
unlock_before_format
635635
? init_existing_passphrase(block, true, type => { existing_passphrase_type = type })
636636
: null

pkg/storaged/block/resize.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
} from "../crypto/keyslots.jsx";
3232
import {
3333
dialog_open, SizeSlider, BlockingMessage, TeardownMessage, SelectSpaces,
34-
init_active_usage_processes
34+
init_teardown_usage
3535
} from "../dialog.jsx";
3636
import { std_reply } from "../stratis/utils.jsx";
3737
import { pvs_to_spaces } from "../lvm2/utils.jsx";
@@ -534,7 +534,7 @@ export function grow_dialog(client, lvol_or_part, info, to_fit) {
534534
}
535535
},
536536
Inits: [
537-
init_active_usage_processes(client, usage),
537+
init_teardown_usage(client, usage),
538538
passphrase_fields.length
539539
? init_existing_passphrase(block, false, pp => { recovered_passphrase = pp })
540540
: null
@@ -647,7 +647,7 @@ export function shrink_dialog(client, lvol_or_part, info, to_fit) {
647647
}
648648
},
649649
Inits: [
650-
init_active_usage_processes(client, usage),
650+
init_teardown_usage(client, usage),
651651
passphrase_fields.length
652652
? init_existing_passphrase(block, false, pp => { recovered_passphrase = pp })
653653
: null

pkg/storaged/btrfs/subvolume.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { btrfs_usage, validate_subvolume_name, parse_subvol_from_options } from
3636
import { at_boot_input, update_at_boot_input, mounting_dialog, mount_options } from "../filesystem/mounting-dialog.jsx";
3737
import {
3838
dialog_open, TextInput,
39-
TeardownMessage, init_active_usage_processes,
39+
TeardownMessage, init_teardown_usage,
4040
} from "../dialog.jsx";
4141
import { check_mismounted_fsys, MismountAlert } from "../filesystem/mismounting.jsx";
4242
import {
@@ -204,6 +204,7 @@ function subvolume_delete(volume, subvol, mount_point_in_parent, card) {
204204
const paths_to_delete = [];
205205
const usage = [];
206206

207+
usage.Teardown = true;
207208
for (const sv of all_subvols) {
208209
const [config, mount_point] = get_fstab_config_with_client(client, block, false, sv);
209210
const fs_is_mounted = is_mounted(client, block, sv);
@@ -241,7 +242,7 @@ function subvolume_delete(volume, subvol, mount_point_in_parent, card) {
241242
}
242243
},
243244
Inits: [
244-
init_active_usage_processes(client, usage)
245+
init_teardown_usage(client, usage)
245246
]
246247
});
247248
}

pkg/storaged/dialog.jsx

Lines changed: 109 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,16 @@ import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog
240240
import { ListingTable } from "cockpit-components-table.jsx";
241241
import { FormHelper } from "cockpit-components-form-helper";
242242

243-
import { fmt_size, block_name, format_size_and_text, format_delay, for_each_async } from "./utils.js";
243+
import {
244+
decode_filename, fmt_size, block_name, format_size_and_text, format_delay, for_each_async,
245+
is_available_block
246+
} from "./utils.js";
244247
import { fmt_to_fragments } from "utils.jsx";
248+
245249
import client from "./client.js";
246250

251+
import fsys_is_empty_sh from "./fsys-is-empty.sh";
252+
247253
const _ = cockpit.gettext;
248254

249255
function make_rows(fields, values, errors, onChange) {
@@ -328,6 +334,23 @@ const Body = ({ body, teardown, fields, values, errors, isFormHorizontal, onChan
328334
);
329335
};
330336

337+
const ExtraConfirmation = ({ title, text, confirm_text, onChange }) => {
338+
const [confirmed, setConfirmed] = useState(false);
339+
340+
return (
341+
<Alert variant="danger" isInline
342+
title={title}>
343+
{text}
344+
<Checkbox isChecked={confirmed}
345+
id="dialog-confirm"
346+
label={confirm_text}
347+
onChange={(_, val) => {
348+
setConfirmed(val);
349+
onChange(val);
350+
}} />
351+
</Alert>);
352+
};
353+
331354
function flatten_fields(fields) {
332355
return fields.reduce(
333356
(acc, val) => acc.concat([val]).concat(val.options && val.options.nested_fields
@@ -340,6 +363,8 @@ export const dialog_open = (def) => {
340363
const nested_fields = def.Fields || [];
341364
const fields = flatten_fields(nested_fields);
342365
const values = { };
366+
let confirmation = null;
367+
let confirmed = false;
343368
let errors = null;
344369

345370
fields.forEach(f => { values[f.tag] = f.initial_value });
@@ -415,8 +440,10 @@ export const dialog_open = (def) => {
415440
caption: variant.Title,
416441
style: actions.length == 0 ? "primary" : "secondary",
417442
danger: def.Action.Danger || def.Action.DangerButton,
418-
disabled: running_promise != null || (def.Action.disable_on_error &&
419-
errors && errors.toString() != "[object Object]"),
443+
disabled: (running_promise != null ||
444+
(def.Action.disable_on_error &&
445+
errors && errors.toString() != "[object Object]") ||
446+
(confirmation && !confirmed)),
420447
clicked: progress_callback => run_action(progress_callback, variant.tag),
421448
});
422449
}
@@ -436,13 +463,21 @@ export const dialog_open = (def) => {
436463
}
437464
}
438465

439-
const extra = (
440-
<div>
441-
{ def.Action && def.Action.Danger
442-
? <HelperText><HelperTextItem variant="error">{def.Action.Danger} </HelperTextItem></HelperText>
443-
: null
444-
}
445-
</div>);
466+
let extra = null;
467+
if (confirmation) {
468+
extra = <ExtraConfirmation title={confirmation.Title}
469+
text={confirmation.Text}
470+
confirm_text={confirmation.ConfirmText}
471+
onChange={val => {
472+
confirmed = val;
473+
update_footer();
474+
}} />;
475+
} else if (def.Action && def.Action.Danger) {
476+
extra = (
477+
<div>
478+
<HelperText><HelperTextItem variant="error">{def.Action.Danger} </HelperTextItem></HelperText>
479+
</div>);
480+
}
446481

447482
return {
448483
idle_message: (running_promise
@@ -537,6 +572,12 @@ export const dialog_open = (def) => {
537572
update();
538573
},
539574

575+
need_confirmation: (conf) => {
576+
confirmation = conf;
577+
confirmed = false;
578+
update_footer();
579+
},
580+
540581
close: () => {
541582
dlg.footerProps.dialog_done();
542583
}
@@ -1207,13 +1248,14 @@ const teardown_block_name = use => {
12071248
};
12081249

12091250
export const TeardownMessage = (usage, expect_single_unmount) => {
1210-
if (usage.length == 0)
1251+
if (!usage.Teardown)
12111252
return null;
12121253

12131254
if (is_expected_unmount(usage, expect_single_unmount))
12141255
return <StopProcessesMessage mount_point={expect_single_unmount} users={usage[0].users} />;
12151256

12161257
const rows = [];
1258+
let have_data = false;
12171259
usage.forEach((use, index) => {
12181260
if (use.block) {
12191261
const name = teardown_block_name(use);
@@ -1223,12 +1265,22 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
12231265
if (location === false)
12241266
location = _("(Not part of target)");
12251267
}
1268+
if (use.data_warning)
1269+
have_data = true;
12261270
rows.push({
12271271
columns: [name,
12281272
location || "-",
12291273
use.actions.length ? use.actions.join(", ") : "-",
12301274
{
1231-
title: <UsersPopover users={use.users || []} />,
1275+
title: <>
1276+
<UsersPopover users={use.users || []} />
1277+
{ use.data_warning &&
1278+
<span>
1279+
<ExclamationTriangleIcon className="ct-icon-exclamation-triangle" /> { "\n" }
1280+
{ use.data_warning }
1281+
</span>
1282+
}
1283+
</>,
12321284
props: { className: "pf-v5-u-text-align-right" }
12331285
}
12341286
]
@@ -1247,6 +1299,11 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
12471299
{ title: "" }
12481300
]}
12491301
rows={rows} />
1302+
{ have_data &&
1303+
<>
1304+
<br />
1305+
</>
1306+
}
12501307
</div>);
12511308
};
12521309

@@ -1268,24 +1325,48 @@ export function teardown_danger_message(usage, expect_single_unmount) {
12681325
}
12691326
}
12701327

1271-
export function init_active_usage_processes(client, usage, expect_single_unmount) {
1328+
// XXX - rename to init_teardown_usage
1329+
1330+
export function init_teardown_usage(client, usage, expect_single_unmount) {
12721331
return {
1273-
title: _("Checking related processes"),
1274-
func: dlg => {
1275-
return for_each_async(usage, u => {
1332+
title: _("Checking filesystem usage"),
1333+
func: async function (dlg) {
1334+
let have_data = false;
1335+
for (const u of usage) {
12761336
if (u.usage == "mounted") {
1277-
return client.find_mount_users(u.location)
1278-
.then(users => {
1279-
u.users = users;
1280-
});
1281-
} else
1282-
return Promise.resolve();
1283-
}).then(() => {
1284-
dlg.set_attribute("Teardown", TeardownMessage(usage, expect_single_unmount));
1285-
const msg = teardown_danger_message(usage, expect_single_unmount);
1286-
if (msg)
1287-
dlg.add_danger(msg);
1288-
});
1337+
u.users = await client.find_mount_users(u.location);
1338+
}
1339+
if (client.in_anaconda_mode() && !expect_single_unmount && u.block) {
1340+
if (u.block.IdUsage == "filesystem") {
1341+
const empty = await cockpit.script(fsys_is_empty_sh,
1342+
[decode_filename(u.block.PreferredDevice)],
1343+
{ superuser: true, err: "message" });
1344+
if (empty.trim() != "yes")
1345+
u.data_warning = _("Filesystem is not empty");
1346+
} else if (u.block.IdUsage == "crypto" && !client.blocks_cleartext[u.block.path]) {
1347+
u.data_warning = _("Locked encrypted device might contain data");
1348+
} else if (!client.blocks_ptable[u.block.path] &&
1349+
u.block.IdUsage != "raid" &&
1350+
!is_available_block(client, u.block)) {
1351+
u.data_warning = _("Device contains unrecognized data");
1352+
}
1353+
if (u.data_warning)
1354+
have_data = true;
1355+
}
1356+
}
1357+
1358+
if (have_data) {
1359+
usage.Teardown = true;
1360+
dlg.need_confirmation({
1361+
Title: _("Important data might be deleted."),
1362+
Text: _("The device that you are about to erase or delete might contain important data. Please confirm that you really want to delete or erase this device."),
1363+
ConfirmText: _("Yes, delete my data."),
1364+
});
1365+
}
1366+
dlg.set_attribute("Teardown", TeardownMessage(usage, expect_single_unmount));
1367+
const msg = teardown_danger_message(usage, expect_single_unmount);
1368+
if (msg)
1369+
dlg.add_danger(msg);
12891370
}
12901371
};
12911372
}

pkg/storaged/filesystem/mounting-dialog.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
dialog_open,
3737
TextInput, PassInput, CheckBoxes, SelectOne,
3838
TeardownMessage,
39-
init_active_usage_processes
39+
init_teardown_usage
4040
} from "../dialog.jsx";
4141
import { init_existing_passphrase, unlock_with_type } from "../crypto/keyslots.jsx";
4242
import { initial_tab_options } from "../block/format-dialog.jsx";
@@ -447,7 +447,7 @@ export function mounting_dialog(client, block, mode, forced_options, subvol) {
447447
}
448448
},
449449
Inits: [
450-
init_active_usage_processes(client, usage, old_dir),
450+
init_teardown_usage(client, usage, old_dir),
451451
init_existing_passphrase(block, true, type => {
452452
passphrase_type = type;
453453
update_explicit_passphrase(dlg.get_value("mount_options")?.ro ?? opt_ro);

pkg/storaged/fsys-is-empty.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#! /bin/bash
2+
3+
set -eux
4+
5+
dev=$1
6+
7+
need_unmount=""
8+
mp=$(findmnt -no TARGET "$dev" | cat)
9+
if [ -z "$mp" ]; then
10+
mp=$(mktemp -d)
11+
need_unmount=$mp
12+
mount "$dev" "$mp" -o ro
13+
fi
14+
15+
# A filesystem is empty if it only has directories in it.
16+
17+
first=$(find "$mp" -not -type d | head -1)
18+
19+
if [ -n "$need_unmount" ]; then
20+
umount "$need_unmount"
21+
rmdir "$need_unmount"
22+
fi
23+
24+
if [ -z "$first" ]; then
25+
echo yes
26+
else
27+
echo no
28+
fi

pkg/storaged/legacy-vdo/legacy-vdo.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { DescriptionList, DescriptionListDescription, DescriptionListGroup, Desc
2727

2828
import { block_short_name, get_active_usage, teardown_active_usage, fmt_size, decode_filename, reload_systemd } from "../utils.js";
2929
import {
30-
dialog_open, SizeSlider, BlockingMessage, TeardownMessage, init_active_usage_processes
30+
dialog_open, SizeSlider, BlockingMessage, TeardownMessage, init_teardown_usage
3131
} from "../dialog.jsx";
3232
import { StorageButton, StorageOnOff } from "../storage-controls.jsx";
3333

@@ -68,7 +68,7 @@ export function make_legacy_vdo_page(parent, vdo, backing_block, next_card) {
6868
}
6969
},
7070
Inits: [
71-
init_active_usage_processes(client, usage)
71+
init_teardown_usage(client, usage)
7272
]
7373
});
7474
} else {
@@ -130,7 +130,7 @@ export function make_legacy_vdo_page(parent, vdo, backing_block, next_card) {
130130
}
131131
},
132132
Inits: [
133-
init_active_usage_processes(client, usage)
133+
init_teardown_usage(client, usage)
134134
]
135135
});
136136
}

pkg/storaged/lvm2/block-logical-volume.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { StorageCard, StorageDescription, new_card, navigate_to_new_card_locatio
3434
import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js";
3535
import {
3636
dialog_open, TextInput, SelectSpaces, BlockingMessage, TeardownMessage,
37-
init_active_usage_processes
37+
init_teardown_usage
3838
} from "../dialog.jsx";
3939

4040
import { lvm2_create_snapshot_action } from "./volume-group.jsx";
@@ -85,7 +85,7 @@ export function lvol_delete(lvol, card) {
8585
}
8686
},
8787
Inits: [
88-
init_active_usage_processes(client, usage)
88+
init_teardown_usage(client, usage)
8989
]
9090
});
9191
}
@@ -166,7 +166,7 @@ function deactivate(lvol, block) {
166166
}
167167
},
168168
Inits: [
169-
init_active_usage_processes(client, usage)
169+
init_teardown_usage(client, usage)
170170
]
171171
});
172172
}

0 commit comments

Comments
 (0)