Skip to content

Commit

Permalink
storage: Don't allow mounting over other mounted filesystems
Browse files Browse the repository at this point in the history
And unmount nested filesystems during teardown.
  • Loading branch information
mvollmer committed Sep 28, 2023
1 parent 8fcf63f commit 60b44b5
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 55 deletions.
13 changes: 10 additions & 3 deletions pkg/storaged/dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1140,10 +1140,14 @@ const UsersPopover = ({ users }) => {
</Popover>);
};

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

if (expect_single_unmount && usage.length == 1 &&
usage[0].usage == "mounted" && usage[0].location == expect_single_unmount)
return <StopProcessesMessage mount_point={expect_single_unmount} users={usage[0].users} />;

const rows = [];
usage.forEach((use, index) => {
if (use.block) {
Expand Down Expand Up @@ -1178,7 +1182,7 @@ export const TeardownMessage = (usage) => {
</div>);
};

export function init_active_usage_processes(client, usage) {
export function init_active_usage_processes(client, usage, expect_single_unmount) {
return {
title: _("Checking related processes"),
func: dlg => {
Expand All @@ -1191,7 +1195,7 @@ export function init_active_usage_processes(client, usage) {
} else
return Promise.resolve();
}).then(() => {
dlg.set_attribute("Teardown", TeardownMessage(usage));
dlg.set_attribute("Teardown", TeardownMessage(usage, expect_single_unmount));
const usage_with_users = usage.filter(u => u.users);
const n_processes = usage_with_users.reduce((sum, u) => sum + u.users.filter(u => u.pid).length, 0);
const n_services = usage_with_users.reduce((sum, u) => sum + u.users.filter(u => u.unit).length, 0);
Expand All @@ -1207,6 +1211,9 @@ export function init_active_usage_processes(client, usage) {
}

export const StopProcessesMessage = ({ mount_point, users }) => {
if (!users || users.length == 0)
return null;

const process_rows = users.filter(u => u.pid).map(u => {
return {
columns: [
Expand Down
57 changes: 20 additions & 37 deletions pkg/storaged/fsys-tab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { parse_options, unparse_options, extract_option, set_crypto_options, set

import {
dialog_open, TextInput, PassInput, CheckBoxes, SelectOne,
StopProcessesMessage, stop_processes_danger_message
TeardownMessage, init_active_usage_processes
} from "./dialog.jsx";
import { StorageButton, StorageLink } from "./storage-controls.jsx";
import { initial_tab_options, mount_explanation } from "./format-dialog.jsx";
Expand Down Expand Up @@ -96,14 +96,24 @@ function nice_block_name(block) {
return utils.block_name(client.blocks[block.CryptoBackingDevice] || block);
}

export function is_valid_mount_point(client, block, val) {
export async function is_valid_mount_point(client, block, val, ignore_overmounting) {
if (val === "")
return _("Mount point cannot be empty");

const other_blocks = find_blocks_for_mount_point(client, val, block);
if (other_blocks.length > 0)
return cockpit.format(_("Mount point is already used for $0"),
other_blocks.map(nice_block_name).join(", "));

if (!ignore_overmounting) {
const children = utils.find_children_for_mount_point(client, val, block);
if (Object.keys(children).length > 0)
return <>
{_("Filesystems are already mounted below this mountpoint.")}
{Object.keys(children).map(m => <div key={m}>{cockpit.format("• $0 on $1", nice_block_name(children[m]), m)}</div>)}
{_("Please unmount them first.")}
</>;
}
}

export function get_cryptobacking_noauto(client, block) {
Expand Down Expand Up @@ -179,7 +189,6 @@ export function mounting_dialog(client, block, mode, forced_options) {
const extra_options = unparse_options(split_options);

const is_filesystem_mounted = is_mounted(client, block);
let mount_point_users = null;

function maybe_update_config(new_dir, new_opts, passphrase, passphrase_type) {
let new_config = null;
Expand Down Expand Up @@ -217,13 +226,6 @@ export function mounting_dialog(client, block, mode, forced_options) {
}
}

function maybe_unmount() {
if (block_fsys && block_fsys.MountPoints.indexOf(utils.encode_filename(old_dir)) >= 0)
return client.unmount_at(old_dir, mount_point_users);
else
return Promise.resolve();
}

function get_block_fsys() {
if (block_fsys)
return Promise.resolve(block_fsys);
Expand Down Expand Up @@ -293,7 +295,7 @@ export function mounting_dialog(client, block, mode, forced_options) {
// backs.

return (utils.reload_systemd()
.then(maybe_unmount)
.then(() => utils.teardown_active_usage(client, usage))
.then(maybe_unlock)
.then(() => {
if (!old_config && new_config)
Expand Down Expand Up @@ -327,7 +329,8 @@ export function mounting_dialog(client, block, mode, forced_options) {
TextInput("mount_point", _("Mount point"),
{
value: old_dir,
validate: val => is_valid_mount_point(client, block, val)
validate: val => is_valid_mount_point(client, block, val,
mode == "update" && !is_filesystem_mounted)
}),
CheckBoxes("mount_options", _("Mount options"),
{
Expand Down Expand Up @@ -375,17 +378,6 @@ export function mounting_dialog(client, block, mode, forced_options) {
]);
}

let teardown = null;
if (!is_filesystem_mounted && block_fsys && block_fsys.MountPoints.length > 0)
teardown = (
<>
{teardown}
<div className="modal-footer-teardown">
<p>{cockpit.format(_("The filesystem is already mounted at $0. Proceeding will unmount it."),
utils.decode_filename(block_fsys.MountPoints[0]))}</p>
</div>
</>);

const mode_title = {
mount: _("Mount filesystem"),
unmount: _("Unmount filesystem $0"),
Expand Down Expand Up @@ -428,10 +420,13 @@ export function mounting_dialog(client, block, mode, forced_options) {
return Promise.resolve();
}

const usage = utils.get_active_usage(client, block.path);
console.log("USAGE", usage);

const dlg = dialog_open({
Title: cockpit.format(mode_title[mode], old_dir),
Fields: fields,
Teardown: teardown,
Teardown: TeardownMessage(usage, old_dir),
update: function (dlg, vals, trigger) {
if (trigger == "at_boot")
dlg.set_options("at_boot", { explanation: mount_explanation[vals.at_boot] });
Expand Down Expand Up @@ -467,19 +462,7 @@ export function mounting_dialog(client, block, mode, forced_options) {
}
},
Inits: [
{
title: _("Checking related processes"),
func: dlg => {
return client.find_mount_users(old_dir, is_filesystem_mounted)
.then(users => {
mount_point_users = users;
if (users.length > 0) {
dlg.set_attribute("Teardown", <StopProcessesMessage mount_point={old_dir} users={users} />);
dlg.add_danger(stop_processes_danger_message(users));
}
});
}
},
init_active_usage_processes(client, usage, old_dir),
(block.IdUsage == "crypto" && mode == "mount")
? init_existing_passphrase(block, true, type => { passphrase_type = type })
: null
Expand Down
108 changes: 93 additions & 15 deletions pkg/storaged/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -741,8 +741,34 @@ export function get_children(client, path) {
return children;
}

export function find_children_for_mount_point(client, mount_point, self) {
const children = {};

function is_self(b) {
return self && (b == self || client.blocks[b.CryptoBackingDevice] == self);
}

for (const p in client.blocks) {
const b = client.blocks[p];
const fs = client.blocks_fsys[p];

if (is_self(b))
continue;

if (fs) {
for (const mp of fs.MountPoints) {
const mpd = decode_filename(mp);
if (mpd.length > mount_point.length && mpd.indexOf(mount_point) == 0 && mpd[mount_point.length] == "/")
children[mpd] = b;
}
}
}

return children;
}

export function get_active_usage(client, path, top_action, child_action) {
function get_usage(path, level) {
function get_usage(usage, path, level) {
const block = client.blocks[path];
const fsys = client.blocks_fsys[path];
const mdraid = block && client.mdraids[block.MDRaidMember];
Expand All @@ -752,7 +778,7 @@ export function get_active_usage(client, path, top_action, child_action) {
const stratis_blockdev = block && client.blocks_stratis_blockdev[path];
const stratis_pool = stratis_blockdev && client.stratis_pools[stratis_blockdev.Pool];

const usage = flatten(get_children_for_teardown(client, path).map(p => get_usage(p, level + 1)));
get_children_for_teardown(client, path).map(p => get_usage(usage, p, level + 1));

function get_actions(teardown_action) {
const actions = [];
Expand All @@ -764,16 +790,34 @@ export function get_active_usage(client, path, top_action, child_action) {
return actions;
}

function enter_unmount(block, location, is_top) {
for (const u of usage) {
if (u.usage == 'mounted' && u.location == location) {
if (is_top) {
u.actions = get_actions(_("unmount"));
u.set_noauto = false;
}
return;
}
}
usage.push({
level,
block,
usage: 'mounted',
location,
set_noauto: !is_top,
actions: is_top ? get_actions(_("unmount")) : [_("unmount")],
blocking: false
});
}

if (fsys && fsys.MountPoints.length > 0) {
fsys.MountPoints.forEach(mp => {
usage.push({
level,
usage: 'mounted',
block,
location: decode_filename(mp),
actions: get_actions(_("unmount")),
blocking: false,
});
const mpd = decode_filename(mp);
const children = find_children_for_mount_point(client, mpd, null);
for (const c in children)
enter_unmount(children[c], c, false);
enter_unmount(block, mpd, true);
});
} else if (mdraid) {
const active_state = mdraid.ActiveDevices.find(as => as[0] == block.path);
Expand Down Expand Up @@ -828,7 +872,8 @@ export function get_active_usage(client, path, top_action, child_action) {
return usage;
}

let usage = get_usage(path, 0);
let usage = [];
get_usage(usage, path, 0);

if (usage.length == 1 && usage[0].level == 0 && usage[0].usage == "none")
usage = [];
Expand All @@ -839,6 +884,37 @@ export function get_active_usage(client, path, top_action, child_action) {
return usage;
}

async function set_fsys_noauto(client, block, mount_point) {
for (const conf of block.Configuration) {
if (conf[0] == "fstab" &&
decode_filename(conf[1].dir.v) == mount_point) {
const options = parse_options(get_block_mntopts(conf[1]));
if (options.indexOf("noauto") >= 0)
continue;
options.push("noauto");
const new_conf = [
"fstab",
Object.assign({ }, conf[1],
{
opts: {
t: 'ay',
v: encode_filename(unparse_options(options))
}
})
];
await block.UpdateConfigurationItem(conf, new_conf, { });
}
}

const crypto_backing = client.blocks[block.CryptoBackingDevice];
if (crypto_backing) {
const crypto_backing_crypto = client.blocks_crypto[crypto_backing.path];
await set_crypto_auto_option(crypto_backing, false);
if (crypto_backing_crypto)
await crypto_backing_crypto.Lock({});
}
}

export function teardown_active_usage(client, usage) {
// The code below is complicated by the fact that the last
// physical volume of a volume group can not be removed
Expand All @@ -849,10 +925,12 @@ export function teardown_active_usage(client, usage) {
// physical volumes here, and it is easiest to catch this
// condition upfront by reshuffling the data structures.

function unmount(mounteds) {
return Promise.all(mounteds.map(m => {
return client.unmount_at(m.location, m.users);
}));
async function unmount(mounteds) {
for (const m of mounteds) {
await client.unmount_at(m.location, m.users);
if (m.set_noauto)
await set_fsys_noauto(client, m.block, m.location);
}
}

function mdraid_remove(members) {
Expand Down
Loading

0 comments on commit 60b44b5

Please sign in to comment.