diff --git a/pkg/storaged/block/format-dialog.jsx b/pkg/storaged/block/format-dialog.jsx index 3fda309af95..c6a3489214d 100644 --- a/pkg/storaged/block/format-dialog.jsx +++ b/pkg/storaged/block/format-dialog.jsx @@ -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"; @@ -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 diff --git a/pkg/storaged/block/resize.jsx b/pkg/storaged/block/resize.jsx index e399b0cebdb..89a2ae4058c 100644 --- a/pkg/storaged/block/resize.jsx +++ b/pkg/storaged/block/resize.jsx @@ -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"; @@ -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 @@ -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 diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index f00ff317e5c..b11f3455af2 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -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 { @@ -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); @@ -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) ] }); } diff --git a/pkg/storaged/dialog.jsx b/pkg/storaged/dialog.jsx index dbbe9d134c9..dca93a96cce 100644 --- a/pkg/storaged/dialog.jsx +++ b/pkg/storaged/dialog.jsx @@ -235,15 +235,21 @@ import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/comp import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List/index.js"; import { ExclamationTriangleIcon, InfoIcon, HelpIcon, EyeIcon, EyeSlashIcon } from "@patternfly/react-icons"; import { InputGroup } from "@patternfly/react-core/dist/esm/components/InputGroup/index.js"; +import { Table, Tbody, Tr, Td } from '@patternfly/react-table'; import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog.jsx"; 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, get_byte_units } from "./utils.js"; +import { + decode_filename, fmt_size, block_name, format_size_and_text, format_delay, for_each_async, get_byte_units, + 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) { @@ -328,6 +334,19 @@ const Body = ({ body, teardown, fields, values, errors, isFormHorizontal, onChan ); }; +const ExtraConfirmation = ({ text, onChange }) => { + const [confirmed, setConfirmed] = useState(false); + + return ( + { + setConfirmed(val); + onChange(val); + }} />); +}; + function flatten_fields(fields) { return fields.reduce( (acc, val) => acc.concat([val]).concat(val.options && val.options.nested_fields @@ -340,6 +359,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 }); @@ -415,8 +436,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), }); } @@ -436,13 +459,19 @@ export const dialog_open = (def) => { } } - const extra = ( -
- { def.Action && def.Action.Danger - ? {def.Action.Danger} - : null - } -
); + let extra = null; + if (confirmation) { + extra = { + confirmed = val; + update_footer(); + }} />; + } else if (def.Action && def.Action.Danger) { + extra = ( +
+ {def.Action.Danger} +
); + } return { idle_message: (running_promise @@ -537,6 +566,14 @@ export const dialog_open = (def) => { update(); }, + need_confirmation: (conf) => { + confirmation = conf; + confirmed = false; + def.Action.Danger = null; + def.Action.DangerButton = true; + update_footer(); + }, + close: () => { dlg.footerProps.dialog_done(); } @@ -1204,13 +1241,16 @@ const teardown_block_name = use => { name = block_name(client.blocks[use.block.CryptoBackingDevice] || use.block); } - return name; + return name.replace(/^\/dev\//, ""); }; export const TeardownMessage = (usage, expect_single_unmount) => { - if (usage.length == 0) + if (!usage.Teardown) return null; + if (client.in_anaconda_mode() && !expect_single_unmount) + return ; + if (is_expected_unmount(usage, expect_single_unmount)) return ; @@ -1251,6 +1291,36 @@ export const TeardownMessage = (usage, expect_single_unmount) => { ); }; +const AnacondaTeardownMessage = ({ usage }) => { + const rows = []; + + usage.forEach((use, index) => { + if (use.data_warning) { + const name = teardown_block_name(use); + const location = client.strip_mount_point_prefix(use.location) || use.block.IdLabel || "-"; + + rows.push( + + {name} + {location} + {use.data_warning} + ); + } + }); + + if (rows.length > 0) { + return ( +
+ + + {_("Important data might be deleted:")} + + + {rows}
+
); + } +}; + export function teardown_danger_message(usage, expect_single_unmount) { if (is_expected_unmount(usage, expect_single_unmount)) return stop_processes_danger_message(usage[0].users); @@ -1269,24 +1339,54 @@ 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)); + u.users = await client.find_mount_users(u.location); + } + if (client.in_anaconda_mode() && !expect_single_unmount && u.block) { + if (u.block.IdUsage == "filesystem" && + ["xfs", "ext2", "ext3", "ext4", "btrfs", "vfat", "ntfs"].indexOf(u.block.IdType) >= 0) { + const empty = await cockpit.script(fsys_is_empty_sh, + [decode_filename(u.block.PreferredDevice)], + { superuser: "require", err: "message" }); + if (empty.trim() != "yes") { + try { + const info = JSON.parse(empty); + u.data_warning = cockpit.format(_("$0 used, $1 total"), + fmt_size((info.total - info.free) * info.unit), + fmt_size(info.total * info.unit)); + } catch { + u.data_warning = _("Device contains unrecognized data"); + } + } + } 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(_("I confirm I want to lose this data forever")); + } else if (client.in_anaconda_mode() && !expect_single_unmount) { + dlg.need_confirmation(null); + } else { const msg = teardown_danger_message(usage, expect_single_unmount); if (msg) dlg.add_danger(msg); - }); + } + dlg.set_attribute("Teardown", TeardownMessage(usage, expect_single_unmount)); } }; } diff --git a/pkg/storaged/filesystem/mounting-dialog.jsx b/pkg/storaged/filesystem/mounting-dialog.jsx index bb21343a43d..88e265f84d1 100644 --- a/pkg/storaged/filesystem/mounting-dialog.jsx +++ b/pkg/storaged/filesystem/mounting-dialog.jsx @@ -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"; @@ -404,7 +404,7 @@ export function mounting_dialog(client, block, mode, forced_options, subvol) { const dlg = dialog_open({ Title: cockpit.format(mode_title[mode], old_dir_for_display), Fields: fields, - Teardown: TeardownMessage(usage, old_dir), + Teardown: TeardownMessage(usage, old_dir || true), update: function (dlg, vals, trigger) { update_at_boot_input(dlg, vals, trigger); if (trigger == "mount_options") @@ -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 || true), init_existing_passphrase(block, true, type => { passphrase_type = type; update_explicit_passphrase(dlg.get_value("mount_options")?.ro ?? opt_ro); diff --git a/pkg/storaged/fsys-is-empty.sh b/pkg/storaged/fsys-is-empty.sh new file mode 100644 index 00000000000..2cbf3b6625c --- /dev/null +++ b/pkg/storaged/fsys-is-empty.sh @@ -0,0 +1,34 @@ +#! /bin/bash + +set -eux + +dev=$1 + +need_unmount="" + +maybe_unmount () { + if [ -n "$need_unmount" ]; then + umount "$need_unmount" + rmdir "$need_unmount" + fi +} + +trap maybe_unmount EXIT INT QUIT + +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) +info=$(stat -f -c '{ "unit": %S, "free": %f, "total": %b }' "$mp") + +if [ -z "$first" ]; then + echo yes +else + echo "$info" +fi diff --git a/pkg/storaged/legacy-vdo/legacy-vdo.jsx b/pkg/storaged/legacy-vdo/legacy-vdo.jsx index 5b93ec5dabc..ccdb717cebc 100644 --- a/pkg/storaged/legacy-vdo/legacy-vdo.jsx +++ b/pkg/storaged/legacy-vdo/legacy-vdo.jsx @@ -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"; @@ -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 { @@ -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) ] }); } diff --git a/pkg/storaged/lvm2/block-logical-volume.jsx b/pkg/storaged/lvm2/block-logical-volume.jsx index 2e386df20bf..6e576c8239f 100644 --- a/pkg/storaged/lvm2/block-logical-volume.jsx +++ b/pkg/storaged/lvm2/block-logical-volume.jsx @@ -34,7 +34,7 @@ import { StorageCard, StorageDescription, new_card, navigate_to_new_card_locatio import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js"; import { dialog_open, TextInput, SelectSpaces, BlockingMessage, TeardownMessage, - init_active_usage_processes + init_teardown_usage } from "../dialog.jsx"; import { lvm2_create_snapshot_action } from "./volume-group.jsx"; @@ -85,7 +85,7 @@ export function lvol_delete(lvol, card) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } @@ -156,7 +156,7 @@ function deactivate(lvol, block) { dialog_open({ Title: cockpit.format(_("Deactivate logical volume $0/$1?"), vgroup.Name, lvol.Name), - Teardown: TeardownMessage(usage), + Teardown: TeardownMessage(usage, true), Action: { Title: _("Deactivate"), action: async function () { @@ -166,7 +166,7 @@ function deactivate(lvol, block) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage, true) ] }); } diff --git a/pkg/storaged/lvm2/volume-group.jsx b/pkg/storaged/lvm2/volume-group.jsx index a8f6e7c5278..4f40a325ca1 100644 --- a/pkg/storaged/lvm2/volume-group.jsx +++ b/pkg/storaged/lvm2/volume-group.jsx @@ -44,7 +44,7 @@ import { import { dialog_open, SelectSpaces, TextInput, BlockingMessage, TeardownMessage, - init_active_usage_processes + init_teardown_usage } from "../dialog.jsx"; import { create_logical_volume } from "./create-logical-volume-dialog.jsx"; @@ -104,7 +104,7 @@ function vgroup_delete(client, vgroup, card) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } @@ -328,8 +328,10 @@ const LVM2VolumeGroupCard = ({ card, vgroup }) => { a bit inconsistent, but *shrug*. */ - let usage = get_active_usage(client, vgroup.path, _("delete")); - usage = usage.filter(u => u.block && is_partial_linear_lvol(u.block)); + const all_usage = get_active_usage(client, vgroup.path, _("delete")); + const usage = all_usage.filter(u => u.block && is_partial_linear_lvol(u.block)); + usage.Blocking = all_usage.Blocking; + usage.Teardown = all_usage.Teardown; if (usage.Blocking) { dialog_open({ @@ -359,7 +361,7 @@ const LVM2VolumeGroupCard = ({ card, vgroup }) => { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } diff --git a/pkg/storaged/mdraid/mdraid.jsx b/pkg/storaged/mdraid/mdraid.jsx index 026b66ea52c..b8fd63b0ec4 100644 --- a/pkg/storaged/mdraid/mdraid.jsx +++ b/pkg/storaged/mdraid/mdraid.jsx @@ -43,7 +43,7 @@ import { import { dialog_open, SelectSpaces, BlockingMessage, TeardownMessage, - init_active_usage_processes + init_teardown_usage } from "../dialog.jsx"; import { partitionable_block_actions } from "../partitions/actions.jsx"; @@ -81,7 +81,7 @@ function mdraid_stop(mdraid) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); return; @@ -132,7 +132,7 @@ function mdraid_delete(mdraid, block, card) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } diff --git a/pkg/storaged/partitions/format-disk-dialog.jsx b/pkg/storaged/partitions/format-disk-dialog.jsx index f1dede36d3b..97e65f19184 100644 --- a/pkg/storaged/partitions/format-disk-dialog.jsx +++ b/pkg/storaged/partitions/format-disk-dialog.jsx @@ -24,7 +24,7 @@ import { dialog_open, SelectOne, CheckBoxes, BlockingMessage, TeardownMessage, - init_active_usage_processes + init_teardown_usage } from "../dialog.jsx"; import { get_active_usage, block_name, teardown_active_usage, reload_systemd } from "../utils.js"; import { job_progress_wrapper } from "../jobs-panel.jsx"; @@ -84,7 +84,7 @@ export function format_disk(block) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } diff --git a/pkg/storaged/partitions/partition.jsx b/pkg/storaged/partitions/partition.jsx index ec8a8e9806e..d3783484331 100644 --- a/pkg/storaged/partitions/partition.jsx +++ b/pkg/storaged/partitions/partition.jsx @@ -29,7 +29,7 @@ import { StorageButton, StorageLink } from "../storage-controls.jsx"; import { dialog_open, SelectOne, TextInput, - init_active_usage_processes, BlockingMessage, TeardownMessage + init_teardown_usage, BlockingMessage, TeardownMessage } from "../dialog.jsx"; import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js"; import { check_unused_space, get_resize_info, free_space_after_part, grow_dialog, shrink_dialog } from "../block/resize.jsx"; @@ -64,7 +64,7 @@ export function delete_partition(block, card) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } diff --git a/pkg/storaged/stratis/filesystem.jsx b/pkg/storaged/stratis/filesystem.jsx index d5b7cae7317..05dbb99368f 100644 --- a/pkg/storaged/stratis/filesystem.jsx +++ b/pkg/storaged/stratis/filesystem.jsx @@ -26,7 +26,7 @@ import { DescriptionList } from "@patternfly/react-core/dist/esm/components/Desc import { dialog_open, TextInput, BlockingMessage, TeardownMessage, - init_active_usage_processes, + init_teardown_usage, } from "../dialog.jsx"; import { StorageUsageBar, StorageLink } from "../storage-controls.jsx"; import { @@ -142,7 +142,7 @@ export function make_stratis_filesystem_page(parent, pool, fsys, } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } diff --git a/pkg/storaged/stratis/pool.jsx b/pkg/storaged/stratis/pool.jsx index d90842728d2..eb0586621ac 100644 --- a/pkg/storaged/stratis/pool.jsx +++ b/pkg/storaged/stratis/pool.jsx @@ -45,7 +45,7 @@ import { import { dialog_open, SelectSpaces, TextInput, PassInput, SelectOne, SizeSlider, BlockingMessage, TeardownMessage, - init_active_usage_processes + init_teardown_usage } from "../dialog.jsx"; import { validate_url, get_tang_adv } from "../crypto/tang.jsx"; @@ -154,7 +154,7 @@ function delete_pool(pool, card) { } }, Inits: [ - init_active_usage_processes(client, usage) + init_teardown_usage(client, usage) ] }); } diff --git a/pkg/storaged/utils.js b/pkg/storaged/utils.js index 11cb41fcb0f..b7463d7ba35 100644 --- a/pkg/storaged/utils.js +++ b/pkg/storaged/utils.js @@ -691,7 +691,7 @@ export function should_ignore(client, path) { GET_CHILDREN_FOR_TEARDOWN is similar but doesn't consider things like volume groups to be children of their physical volumes. This is appropriate for teardown processing, where tearing down a - physical volume does not imply tearing down the whole volume groups + physical volume does not imply tearing down the whole volume group with everything that it contains. */ @@ -979,15 +979,15 @@ export function get_active_usage(client, path, top_action, child_action, is_temp return usage; } - let usage = []; + const usage = []; get_usage(usage, path, 0); - if (usage.length == 1 && usage[0].level == 0 && usage[0].usage == "none") - usage = []; - usage.Blocking = usage.some(u => u.blocking); usage.Teardown = usage.some(u => !u.blocking); + if (usage.length == 1 && usage[0].level == 0 && usage[0].usage == "none") + usage.Teardown = false; + return usage; } diff --git a/test/reference b/test/reference index e3af5f16685..34a4a212a4c 160000 --- a/test/reference +++ b/test/reference @@ -1 +1 @@ -Subproject commit e3af5f16685eee365f0a2b83f49a86dda378b5b6 +Subproject commit 34a4a212a4c108d3c05e4b1c383428c394b849cf diff --git a/test/verify/check-storage-anaconda b/test/verify/check-storage-anaconda index bd63558b144..4bfcc2487ec 100755 --- a/test/verify/check-storage-anaconda +++ b/test/verify/check-storage-anaconda @@ -18,6 +18,7 @@ # along with Cockpit; If not, see . import json +import os import storagelib import testlib @@ -157,10 +158,7 @@ class TestStorageAnaconda(storagelib.StorageCase): b.click(self.card_parent_link()) b.wait_visible(self.card_row("LVM2 volume group", name=disk)) self.click_card_dropdown("LVM2 volume group", "Delete group") - self.dialog_wait_open() - b.wait_text("#dialog td[data-label='Location']", "/var") - self.dialog_apply() - self.dialog_wait_close() + self.confirm() m.execute("! findmnt --fstab -n /sysroot/var") # Back to the beginning @@ -252,10 +250,7 @@ class TestStorageAnaconda(storagelib.StorageCase): b.click(self.card_parent_link()) b.wait_visible(self.card_row("Stratis pool", name=disk)) self.click_card_dropdown("Stratis pool", "Delete") - self.dialog_wait_open() - b.wait_text("#dialog td[data-label='Location']", "/var") - self.dialog_apply() - self.dialog_wait_close() + self.confirm() m.execute("! findmnt --fstab -n /sysroot/var") @testlib.skipImage('no btrfs support', 'rhel-*', 'centos-*') @@ -549,6 +544,98 @@ class TestStorageAnaconda(storagelib.StorageCase): b.click(self.card_desc("MDRAID disk", "MDRAID device") + " button") b.wait_in_text("body", "Not found") + def testNonEmpty(self): + b = self.browser + m = self.machine + + disk = self.add_loopback_disk(name="loop10") + + anaconda_config = { + "mount_point_prefix": "/sysroot", + "available_devices": [disk], + } + + # Create some content on the disk + m.execute(f"echo einszweidrei | cryptsetup luksFormat --pbkdf-memory 32768 {disk}") + m.execute(f"echo einszweidrei | cryptsetup luksOpen {disk} dm-test") + m.execute("mkfs.ext4 /dev/mapper/dm-test; mount /dev/mapper/dm-test /mnt; echo Hi >/mnt/hello; umount /mnt") + m.execute("cryptsetup close dm-test") + + self.login_and_go("/storage") + self.enterAnacondaMode(anaconda_config) + + # Attempt to wipe it. This requires a extra confirmation + # because it is locked. + b.wait_text(self.card_row_col("Storage", 1, 3), "Locked data (encrypted)") + self.click_dropdown(self.card_row("Storage", 1), "Create partition table") + self.dialog_wait_open() + self.dialog_set_val("type", "empty") + b.wait_in_text("#dialog", "Locked encrypted device might contain data") + self.dialog_wait_apply_disabled() + b.assert_pixels('#dialog', "wipe") + b.set_checked("#dialog-confirm", val=True) + self.dialog_wait_apply_enabled() + self.dialog_cancel() + self.dialog_wait_close() + + # Unlock and confirm the extra warning, and actually wipe it + self.click_dropdown(self.card_row("Storage", 1), "Unlock") + self.dialog({"passphrase": "einszweidrei"}) + b.wait_text(self.card_row_col("Storage", 1, 3), "ext4 filesystem (encrypted)") + self.click_dropdown(self.card_row("Storage", 1), "Create partition table") + self.dialog_wait_open() + self.dialog_set_val("type", "empty") + b.wait_in_text("#dialog", "Important data might be deleted") + b.wait_in_text("#dialog", os.path.basename(disk)) + b.wait_in_text("#dialog", "kB used") + b.wait_in_text("#dialog", "MB total") + b.set_checked("#dialog-confirm", val=True) + self.dialog_wait_apply_enabled() + self.dialog_apply() + self.dialog_wait_close() + + # Put some unrecognized data on it + + # This is the superblock of a legacy VDO device. Cockpit does + # not recognize it. + + data = """ +ZG12ZG8wMDEFAAAABAAAAAAAAABdAAAAAAAAAJQJAgCGsH0mrQgGAC4WnB4G50Fzu20jY6J1rfwA +AAAAAQAAAAAAAAABAAAA2FwKAAAAAAAA////AAAAAAA7tw9zAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= +""" + m.execute(f"base64 -d >{disk}", input=data) + # Wait for udev to settle and re-trigger, udisks sometimes misses udev events on ubuntu 2204. + m.execute(f"udevadm settle; udevadm trigger {disk}") + + b.wait_text(self.card_row_col("Storage", 1, 3), "Unrecognized data") + self.click_dropdown(self.card_row("Storage", 1), "Create partition table") + self.dialog_wait_open() + self.dialog_set_val("type", "empty") + b.wait_in_text("#dialog", "Device contains unrecognized data") + b.set_checked("#dialog-confirm", val=True) + self.dialog_wait_apply_enabled() + self.dialog_apply() + self.dialog_wait_close() + + # A filesystem with just directories should not show the + # warning. + + m.execute(f"mkfs.ext4 {disk}; mount {disk} /mnt; mkdir -p /mnt/dir/ect/ory; umount /mnt") + + b.wait_text(self.card_row_col("Storage", 1, 3), "ext4 filesystem") + self.click_dropdown(self.card_row("Storage", 1), "Create partition table") + self.dialog_wait_open() + self.dialog_set_val("type", "empty") + self.dialog_apply() + self.dialog_wait_close() + if __name__ == '__main__': testlib.test_main() diff --git a/test/verify/check-storage-btrfs b/test/verify/check-storage-btrfs index c5ca64b94f1..247288c26fe 100755 --- a/test/verify/check-storage-btrfs +++ b/test/verify/check-storage-btrfs @@ -112,7 +112,7 @@ class TestStorageBtrfs(storagelib.StorageCase): # The mount of /run/basement might take some time to be recognized by Cockpit self.dialog_open_with_retry(lambda: self.click_dropdown(self.card_row("Storage", name="sda"), "Format"), lambda: "/run/basement" in b.text("#dialog")) - self.checkTeardownAction(1, "Device", dev_1) + self.checkTeardownAction(1, "Device", os.path.basename(dev_1)) self.checkTeardownAction(1, "Location", "/run/basement") self.checkTeardownAction(1, "Action", "unmount, format") self.checkTeardownAction(2, "Location", mount_point) diff --git a/test/verify/check-storage-lvm2 b/test/verify/check-storage-lvm2 index f992ce55556..68660b03819 100755 --- a/test/verify/check-storage-lvm2 +++ b/test/verify/check-storage-lvm2 @@ -507,7 +507,7 @@ class TestStorageLvm2(storagelib.StorageCase): b.click(".pf-v5-c-alert button:contains(Dismiss)") self.dialog_wait_open() - b.wait_in_text("#dialog", "/dev/vgroup0/lvol0") + b.wait_in_text("#dialog", "vgroup0/lvol0") self.dialog_apply() self.dialog_wait_close() diff --git a/test/verify/check-storage-swap b/test/verify/check-storage-swap index 5f73f3f95ae..3fa8e9aaa79 100755 --- a/test/verify/check-storage-swap +++ b/test/verify/check-storage-swap @@ -17,6 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Cockpit; If not, see . +import os + import storagelib import testlib @@ -70,7 +72,7 @@ class TestStorageswap(storagelib.StorageCase): self.click_card_dropdown("Swap", "Format") self.dialog_wait_open() self.dialog_set_val("type", "swap") - b.wait_in_text("#dialog .modal-footer-teardown", f"{disk}1") + b.wait_in_text("#dialog .modal-footer-teardown", f"{os.path.basename(disk)}1") b.wait_in_text("#dialog .modal-footer-teardown", "stop, format") self.dialog_apply_secondary() self.dialog_wait_close() diff --git a/test/verify/check-storage-unrecognized b/test/verify/check-storage-unrecognized index 2deec830689..71799fb5bef 100755 --- a/test/verify/check-storage-unrecognized +++ b/test/verify/check-storage-unrecognized @@ -53,7 +53,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= """ m.execute(f"base64 -d >{disk}", input=data) # Wait for udev to settle and re-trigger, udisks sometimes misses udev events on ubuntu 2204. - m.execute("udevadm settle; udevadm trigger /dev/sda") + m.execute(f"udevadm settle; udevadm trigger {disk}") b.wait_text(self.card_desc("Unrecognized data", "Usage"), "other") b.wait_text(self.card_desc("Unrecognized data", "Type"), "vdo")