Skip to content

Commit

Permalink
storage: Confirmation before erasing actual data
Browse files Browse the repository at this point in the history
But only in Anaconda mode.
  • Loading branch information
mvollmer committed Apr 10, 2024
1 parent 5bfe3ce commit 1acb017
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 67 deletions.
4 changes: 2 additions & 2 deletions pkg/storaged/block/format-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
dialog_open,
TextInput, PassInput, CheckBoxes, SelectOne, SizeSlider,
BlockingMessage, TeardownMessage,
init_active_usage_processes
init_teardown_usage
} from "../dialog.jsx";

import { get_fstab_config, is_valid_mount_point } from "../filesystem/utils.jsx";
Expand Down Expand Up @@ -630,7 +630,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
}
},
Inits: [
init_active_usage_processes(client, usage),
init_teardown_usage(client, usage),
unlock_before_format
? init_existing_passphrase(block, true, type => { existing_passphrase_type = type })
: null
Expand Down
6 changes: 3 additions & 3 deletions pkg/storaged/block/resize.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
} from "../crypto/keyslots.jsx";
import {
dialog_open, SizeSlider, BlockingMessage, TeardownMessage, SelectSpaces,
init_active_usage_processes
init_teardown_usage
} from "../dialog.jsx";
import { std_reply } from "../stratis/utils.jsx";
import { pvs_to_spaces } from "../lvm2/utils.jsx";
Expand Down Expand Up @@ -534,7 +534,7 @@ export function grow_dialog(client, lvol_or_part, info, to_fit) {
}
},
Inits: [
init_active_usage_processes(client, usage),
init_teardown_usage(client, usage),
passphrase_fields.length
? init_existing_passphrase(block, false, pp => { recovered_passphrase = pp })
: null
Expand Down Expand Up @@ -647,7 +647,7 @@ export function shrink_dialog(client, lvol_or_part, info, to_fit) {
}
},
Inits: [
init_active_usage_processes(client, usage),
init_teardown_usage(client, usage),
passphrase_fields.length
? init_existing_passphrase(block, false, pp => { recovered_passphrase = pp })
: null
Expand Down
5 changes: 3 additions & 2 deletions pkg/storaged/btrfs/subvolume.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { btrfs_usage, validate_subvolume_name, parse_subvol_from_options } from
import { at_boot_input, update_at_boot_input, mounting_dialog, mount_options } from "../filesystem/mounting-dialog.jsx";
import {
dialog_open, TextInput,
TeardownMessage, init_active_usage_processes,
TeardownMessage, init_teardown_usage,
} from "../dialog.jsx";
import { check_mismounted_fsys, MismountAlert } from "../filesystem/mismounting.jsx";
import {
Expand Down Expand Up @@ -204,6 +204,7 @@ function subvolume_delete(volume, subvol, mount_point_in_parent, card) {
const paths_to_delete = [];
const usage = [];

usage.Teardown = true;
for (const sv of all_subvols) {
const [config, mount_point] = get_fstab_config_with_client(client, block, false, sv);
const fs_is_mounted = is_mounted(client, block, sv);
Expand Down Expand Up @@ -241,7 +242,7 @@ function subvolume_delete(volume, subvol, mount_point_in_parent, card) {
}
},
Inits: [
init_active_usage_processes(client, usage)
init_teardown_usage(client, usage)
]
});
}
Expand Down
135 changes: 107 additions & 28 deletions pkg/storaged/dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,16 @@ import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog
import { ListingTable } from "cockpit-components-table.jsx";
import { FormHelper } from "cockpit-components-form-helper";

import { fmt_size, block_name, format_size_and_text, format_delay, for_each_async } from "./utils.js";
import {
decode_filename, fmt_size, block_name, format_size_and_text, format_delay, for_each_async,
is_available_block
} from "./utils.js";
import { fmt_to_fragments } from "utils.jsx";

import client from "./client.js";

import fsys_is_empty_sh from "./fsys-is-empty.sh";

const _ = cockpit.gettext;

function make_rows(fields, values, errors, onChange) {
Expand Down Expand Up @@ -328,6 +334,23 @@ const Body = ({ body, teardown, fields, values, errors, isFormHorizontal, onChan
);
};

const ExtraConfirmation = ({ title, text, confirm_text, onChange }) => {
const [confirmed, setConfirmed] = useState(false);

return (
<Alert variant="danger" isInline
title={title}>
{text}
<Checkbox isChecked={confirmed}
id="dialog-confirm"
label={confirm_text}
onChange={(_, val) => {
setConfirmed(val);
onChange(val);
}} />
</Alert>);
};

function flatten_fields(fields) {
return fields.reduce(
(acc, val) => acc.concat([val]).concat(val.options && val.options.nested_fields
Expand All @@ -340,6 +363,8 @@ export const dialog_open = (def) => {
const nested_fields = def.Fields || [];
const fields = flatten_fields(nested_fields);
const values = { };
let confirmation = null;
let confirmed = false;
let errors = null;

fields.forEach(f => { values[f.tag] = f.initial_value });
Expand Down Expand Up @@ -415,8 +440,10 @@ export const dialog_open = (def) => {
caption: variant.Title,
style: actions.length == 0 ? "primary" : "secondary",
danger: def.Action.Danger || def.Action.DangerButton,
disabled: running_promise != null || (def.Action.disable_on_error &&
errors && errors.toString() != "[object Object]"),
disabled: (running_promise != null ||
(def.Action.disable_on_error &&
errors && errors.toString() != "[object Object]") ||
(confirmation && !confirmed)),
clicked: progress_callback => run_action(progress_callback, variant.tag),
});
}
Expand All @@ -436,13 +463,21 @@ export const dialog_open = (def) => {
}
}

const extra = (
<div>
{ def.Action && def.Action.Danger
? <HelperText><HelperTextItem variant="error">{def.Action.Danger} </HelperTextItem></HelperText>
: null
}
</div>);
let extra = null;
if (confirmation) {
extra = <ExtraConfirmation title={confirmation.Title}
text={confirmation.Text}
confirm_text={confirmation.ConfirmText}
onChange={val => {
confirmed = val;
update_footer();
}} />;
} else if (def.Action && def.Action.Danger) {
extra = (
<div>
<HelperText><HelperTextItem variant="error">{def.Action.Danger} </HelperTextItem></HelperText>
</div>);
}

return {
idle_message: (running_promise
Expand Down Expand Up @@ -537,6 +572,12 @@ export const dialog_open = (def) => {
update();
},

need_confirmation: (conf) => {
confirmation = conf;
confirmed = false;
update_footer();
},

close: () => {
dlg.footerProps.dialog_done();
}
Expand Down Expand Up @@ -1207,13 +1248,14 @@ const teardown_block_name = use => {
};

export const TeardownMessage = (usage, expect_single_unmount) => {
if (usage.length == 0)
if (!usage.Teardown)
return null;

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

const rows = [];
let have_data = false;
usage.forEach((use, index) => {
if (use.block) {
const name = teardown_block_name(use);
Expand All @@ -1223,12 +1265,22 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
if (location === false)
location = _("(Not part of target)");
}
if (use.data_warning)
have_data = true;
rows.push({
columns: [name,
location || "-",
use.actions.length ? use.actions.join(", ") : "-",
{
title: <UsersPopover users={use.users || []} />,
title: <>
<UsersPopover users={use.users || []} />
{ use.data_warning &&
<span>
<ExclamationTriangleIcon className="ct-icon-exclamation-triangle" /> { "\n" }
{ use.data_warning }
</span>
}
</>,
props: { className: "pf-v5-u-text-align-right" }
}
]
Expand All @@ -1247,6 +1299,11 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
{ title: "" }
]}
rows={rows} />
{ have_data &&
<>
<br />
</>
}
</div>);
};

Expand All @@ -1268,24 +1325,46 @@ export function teardown_danger_message(usage, expect_single_unmount) {
}
}

export function init_active_usage_processes(client, usage, expect_single_unmount) {
export function init_teardown_usage(client, usage, expect_single_unmount) {
return {
title: _("Checking related processes"),
func: dlg => {
return for_each_async(usage, u => {
title: _("Checking filesystem usage"),
func: async function (dlg) {
let have_data = false;
for (const u of usage) {
if (u.usage == "mounted") {
return client.find_mount_users(u.location)
.then(users => {
u.users = users;
});
} else
return Promise.resolve();
}).then(() => {
dlg.set_attribute("Teardown", TeardownMessage(usage, expect_single_unmount));
const msg = teardown_danger_message(usage, expect_single_unmount);
if (msg)
dlg.add_danger(msg);
});
u.users = await client.find_mount_users(u.location);
}
if (client.in_anaconda_mode() && !expect_single_unmount && u.block) {
if (u.block.IdUsage == "filesystem") {
const empty = await cockpit.script(fsys_is_empty_sh,
[decode_filename(u.block.PreferredDevice)],
{ superuser: true, err: "message" });
if (empty.trim() != "yes")
u.data_warning = _("Filesystem is not empty");
} else if (u.block.IdUsage == "crypto" && !client.blocks_cleartext[u.block.path]) {
u.data_warning = _("Locked encrypted device might contain data");
} else if (!client.blocks_ptable[u.block.path] &&
u.block.IdUsage != "raid" &&
!is_available_block(client, u.block)) {
u.data_warning = _("Device contains unrecognized data");
}
if (u.data_warning)
have_data = true;
}
}

if (have_data) {
usage.Teardown = true;
dlg.need_confirmation({
Title: _("Important data might be deleted."),
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."),
ConfirmText: _("Yes, delete my data."),
});
}
dlg.set_attribute("Teardown", TeardownMessage(usage, expect_single_unmount));
const msg = teardown_danger_message(usage, expect_single_unmount);
if (msg)
dlg.add_danger(msg);
}
};
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/storaged/filesystem/mounting-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
dialog_open,
TextInput, PassInput, CheckBoxes, SelectOne,
TeardownMessage,
init_active_usage_processes
init_teardown_usage
} from "../dialog.jsx";
import { init_existing_passphrase, unlock_with_type } from "../crypto/keyslots.jsx";
import { initial_tab_options } from "../block/format-dialog.jsx";
Expand Down Expand Up @@ -447,7 +447,7 @@ export function mounting_dialog(client, block, mode, forced_options, subvol) {
}
},
Inits: [
init_active_usage_processes(client, usage, old_dir),
init_teardown_usage(client, usage, old_dir),
init_existing_passphrase(block, true, type => {
passphrase_type = type;
update_explicit_passphrase(dlg.get_value("mount_options")?.ro ?? opt_ro);
Expand Down
28 changes: 28 additions & 0 deletions pkg/storaged/fsys-is-empty.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#! /bin/bash

set -eux

dev=$1

need_unmount=""
mp=$(findmnt -no TARGET "$dev" | cat)
if [ -z "$mp" ]; then
mp=$(mktemp -d)
need_unmount=$mp
mount "$dev" "$mp" -o ro
fi

# A filesystem is empty if it only has directories in it.

first=$(find "$mp" -not -type d | head -1)

if [ -n "$need_unmount" ]; then
umount "$need_unmount"
rmdir "$need_unmount"
fi

if [ -z "$first" ]; then
echo yes
else
echo no
fi
6 changes: 3 additions & 3 deletions pkg/storaged/legacy-vdo/legacy-vdo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { DescriptionList, DescriptionListDescription, DescriptionListGroup, Desc

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

Expand Down Expand Up @@ -68,7 +68,7 @@ export function make_legacy_vdo_page(parent, vdo, backing_block, next_card) {
}
},
Inits: [
init_active_usage_processes(client, usage)
init_teardown_usage(client, usage)
]
});
} else {
Expand Down Expand Up @@ -130,7 +130,7 @@ export function make_legacy_vdo_page(parent, vdo, backing_block, next_card) {
}
},
Inits: [
init_active_usage_processes(client, usage)
init_teardown_usage(client, usage)
]
});
}
Expand Down

0 comments on commit 1acb017

Please sign in to comment.