Skip to content

Commit

Permalink
storage: Allow creation of swap and maintain fstab entries
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed Jan 18, 2024
1 parent 1135fcb commit ffd6b83
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 15 deletions.
45 changes: 42 additions & 3 deletions pkg/storaged/block/format-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
title = cockpit.format(_("Format $0"), block_name(block));

function is_filesystem(vals) {
return vals.type != "empty" && vals.type != "dos-extended" && vals.type != "biosboot";
return vals.type != "empty" && vals.type != "dos-extended" && vals.type != "biosboot" && vals.type != "swap";
}

function add_fsys(storaged_name, entry) {
Expand All @@ -186,6 +186,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
add_fsys("ext4", { value: "ext4", title: "EXT4" });
add_fsys("vfat", { value: "vfat", title: "VFAT" });
add_fsys("ntfs", { value: "ntfs", title: "NTFS" });
add_fsys("swap", { value: "swap", title: "Swap" });
if (client.in_anaconda_mode()) {
if (block_ptable && block_ptable.Type == "gpt" && !client.anaconda.efi)
add_fsys(true, { value: "biosboot", title: "BIOS boot partition" });
Expand Down Expand Up @@ -288,8 +289,13 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
{ tag: null, Title: create_partition ? _("Create") : _("Format") }
];

let action_variants_for_swap = [
{ tag: null, Title: create_partition ? _("Create and start") : _("Format and start") },
{ tag: "nomount", Title: create_partition ? _("Create only") : _("Format only") }
];

if (client.in_anaconda_mode()) {
action_variants = [
action_variants = action_variants_for_swap = [
{ tag: "nomount", Title: create_partition ? _("Create") : _("Format") }
];
}
Expand Down Expand Up @@ -423,6 +429,8 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
else if (trigger == "type") {
if (dlg.get_value("type") == "empty") {
dlg.update_actions({ Variants: action_variants_for_empty });
} else if (dlg.get_value("type") == "swap") {
dlg.update_actions({ Variants: action_variants_for_swap });
} else {
dlg.update_actions({ Variants: action_variants });
}
Expand Down Expand Up @@ -450,6 +458,12 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
partition_type = "21686148-6449-6e6f-744e-656564454649";
}

if (type == "swap") {
partition_type = (block_ptable && block_ptable.Type == "dos"
? "0x82"
: "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f");
}

const options = {
'tear-down': { t: 'b', v: true }
};
Expand Down Expand Up @@ -532,6 +546,17 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
}
}

if (type == "swap") {
config_items.push(["fstab", {
dir: { t: 'ay', v: encode_filename("none") },
type: { t: 'ay', v: encode_filename("swap") },
opts: { t: 'ay', v: encode_filename(mount_now ? "defaults" : "noauto") },
freq: { t: 'i', v: 0 },
passno: { t: 'i', v: 0 },
"track-parents": { t: 'b', v: true }
}]);
}

if (config_items.length > 0)
options["config-items"] = { t: 'a(sa{sv})', v: config_items };

Expand Down Expand Up @@ -587,6 +612,17 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
return client.blocks_fsys[path];
}

function block_swap_for_block(path) {
if (keep_keys) {
const content_block = client.blocks_cleartext[path];
return client.blocks_swap[content_block.path];
} else if (is_encrypted(vals))
return (client.blocks_cleartext[path] &&
client.blocks_swap[client.blocks_cleartext[path].path]);
else
return client.blocks_swap[path];
}

function block_crypto_for_block(path) {
return client.blocks_crypto[path];
}
Expand All @@ -597,7 +633,10 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
return (client.wait_for(() => block_fsys_for_block(path))
.then(block_fsys => client.mount_at(client.blocks[block_fsys.path],
mount_point)));
if (is_encrypted(vals) && is_filesystem(vals) && !mount_now)
if (type == "swap" && mount_now)
return (client.wait_for(() => block_swap_for_block(path))
.then(block_swap => block_swap.Start({})));
if (is_encrypted(vals) && (is_filesystem(vals) || type == "swap") && !mount_now)
return (client.wait_for(() => block_crypto_for_block(path))
.then(block_crypto => block_crypto.Lock({ })));
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/storaged/block/resize.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ export function get_resize_info(client, block, to_fit) {
grow_needs_unmount: false
};
shrink_excuse = _("VDO backing devices can not be made smaller");
} else if (client.blocks_swap[block.path]) {
info = {
can_shrink: false,
can_grow: false,
};
shrink_excuse = grow_excuse = _("Swap can not be resized here");
} else if (client.blocks_available[block.path]) {
info = {
can_shrink: true,
Expand Down
55 changes: 52 additions & 3 deletions pkg/storaged/swap/swap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,63 @@ import { useEvent } from "hooks";

import { StorageCard, StorageDescription, new_card } from "../pages.jsx";
import { format_dialog } from "../block/format-dialog.jsx";
import { fmt_size, decode_filename } from "../utils.js";
import {
fmt_size, decode_filename, encode_filename,
parse_options, unparse_options, extract_option,
} from "../utils.js";
import { std_lock_action } from "../crypto/actions.jsx";

const _ = cockpit.gettext;

async function set_swap_noauto(block, noauto) {
for (const conf of block.Configuration) {
if (conf[0] == "fstab") {
const options = parse_options(decode_filename(conf[1].opts.v));
extract_option(options, "defaults");
extract_option(options, "noauto");
if (noauto)
options.push("noauto");
if (options.length == 0)
options.push("defaults");
const new_conf = [
"fstab",
Object.assign({ }, conf[1],
{
opts: {
t: 'ay',
v: encode_filename(unparse_options(options))
}
})
];
await block.UpdateConfigurationItem(conf, new_conf, { });
return;
}
}

await block.AddConfigurationItem(
["fstab", {
dir: { t: 'ay', v: encode_filename("none") },
type: { t: 'ay', v: encode_filename("swap") },
opts: { t: 'ay', v: encode_filename(noauto ? "noauto" : "defaults") },
freq: { t: 'i', v: 0 },
passno: { t: 'i', v: 0 },
"track-parents": { t: 'b', v: true }
}], { });
}

export function make_swap_card(next, backing_block, content_block) {
const block_swap = client.blocks_swap[content_block.path];

async function start() {
await block_swap.Start({});
await set_swap_noauto(content_block, false);
}

async function stop() {
await block_swap.Stop({});
await set_swap_noauto(content_block, true);
}

return new_card({
title: _("Swap"),
next,
Expand All @@ -43,10 +92,10 @@ export function make_swap_card(next, backing_block, content_block) {
actions: [
std_lock_action(backing_block, content_block),
(block_swap && block_swap.Active
? { title: _("Stop"), action: () => block_swap.Stop({}) }
? { title: _("Stop"), action: stop }
: null),
(block_swap && !block_swap.Active
? { title: _("Start"), action: () => block_swap.Start({}) }
? { title: _("Start"), action: start }
: null),
{ title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
]
Expand Down
17 changes: 17 additions & 0 deletions pkg/storaged/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ export function get_active_usage(client, path, top_action, child_action, is_temp
function get_usage(usage, path, level) {
const block = client.blocks[path];
const fsys = client.blocks_fsys[path];
const swap = client.blocks_swap[path];
const mdraid = block && client.mdraids[block.MDRaidMember];
const pvol = client.blocks_pvol[path];
const vgroup = pvol && client.vgroups[pvol.VolumeGroup];
Expand Down Expand Up @@ -910,6 +911,15 @@ export function get_active_usage(client, path, top_action, child_action, is_temp
enter_unmount(children[c], c, false);
enter_unmount(block, mp, true);
});
} else if (swap) {
if (swap.Active) {
usage.push({
level,
usage: 'swap',
block,
actions: get_actions(_("stop")),
});
}
} else if (mdraid) {
const active_state = mdraid.ActiveDevices.find(as => as[0] == block.path);
usage.push({
Expand Down Expand Up @@ -1024,6 +1034,12 @@ export function teardown_active_usage(client, usage) {
}
}

async function stop_swap(swaps) {
for (const s of swaps) {
await client.blocks_swap[s.block.path].Stop({});
}
}

function mdraid_remove(members) {
return Promise.all(members.map(m => m.mdraid.RemoveDevice(m.block.path, { wipe: { t: 'b', v: true } })));
}
Expand Down Expand Up @@ -1053,6 +1069,7 @@ export function teardown_active_usage(client, usage) {

return Promise.all(Array.prototype.concat(
unmount(usage.filter(function(use) { return use.usage == "mounted" })),
stop_swap(usage.filter(function(use) { return use.usage == "swap" })),
mdraid_remove(usage.filter(function(use) { return use.usage == "mdraid-member" })),
pvol_remove(usage.filter(function(use) { return use.usage == "pvol" }))
));
Expand Down
85 changes: 76 additions & 9 deletions test/verify/check-storage-swap
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,94 @@ import testlib
@testlib.nondestructive
class TestStorageswap(storagelib.StorageCase):

def test(self):
m = self.machine
def testBasic(self):
b = self.browser
m = self.machine

disk = self.add_ram_disk()

self.login_and_go("/storage")

disk = self.add_ram_disk()
# Create a swap partition on GPT
self.click_card_row("Storage", name=disk)
self.click_card_dropdown("Solid State Drive", "Create partition table")
self.confirm()
b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")
self.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")
self.dialog({"type": "swap"})
b.wait_text(self.card_row_col("GPT partitions", 1, 2), "Swap")

b.wait_visible(self.card("Unformatted data"))
m.execute(f"mkswap {disk}")
b.wait_visible(self.card("Swap"))
# It should have been started and have a fstab entry
self.click_card_row("GPT partitions", 1)
b.wait_text(self.card_desc("Swap", "Used"), "0")
self.assertIn("defaults", m.execute(f"findmnt --fstab -n -o OPTIONS {disk}1"))

# Stopping should set it to noauto
b.click(self.card_button("Swap", "Stop"))
b.wait_text(self.card_desc("Swap", "Used"), "-")
self.assertIn("noauto", m.execute(f"findmnt --fstab -n -o OPTIONS {disk}1"))

# Start it again to test teardown below
b.click(self.card_button("Swap", "Start"))
b.wait_text(self.card_desc("Swap", "Used"), "0")
self.assertIn("defaults", m.execute(f"findmnt --fstab -n -o OPTIONS {disk}1"))

self.assertEqual(m.execute(f"lsblk -n -o MOUNTPOINT {disk}").strip(), "[SWAP]")
# It should have the right partition type
b.wait_visible(self.card("Swap"))
b.wait_text(self.card_desc("Partition", "Type"), "Linux swap space")

b.click(self.card_button("Swap", "Stop"))
b.wait_text(self.card_desc("Swap", "Used"), "-")
# Set it to something else
b.click(self.card_desc_action("Partition", "Type"))
self.dialog({"type": "0fc63daf-8483-4772-8e79-3d69d8477de4"})
b.wait_text(self.card_desc("Partition", "Type"), "Linux filesystem data")

# Correct it by reformatting
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", "stop, format")
self.dialog_apply_secondary()
self.dialog_wait_close()
b.wait_text(self.card_desc("Partition", "Type"), "Linux swap space")

# Delete the partition, the fstab entry should disappear
self.click_card_dropdown("Partition", "Delete")
self.confirm()
b.wait_visible(self.card("Solid State Drive"))
m.execute(f"! findmnt --fstab -n -o OPTIONS {disk}1")

# Format as swap on the command line, starting it should add
# fstab entry
m.execute(f"mkswap -f {disk}")
b.click(self.card_button("Swap", "Start"))
b.wait_text(self.card_desc("Swap", "Used"), "0")
self.assertIn("defaults", m.execute(f"findmnt --fstab -n -o OPTIONS {disk}"))

def testEncrypted(self):
b = self.browser
m = self.machine

disk = self.add_ram_disk()

self.login_and_go("/storage")

# Create a encrypted swap partition directly on a disk
self.click_dropdown(self.card_row("Storage", name=disk), "Format")
with b.wait_timeout(60):
self.dialog({
"type": "swap",
"crypto": "luks2",
"passphrase": "foobar",
"passphrase2": "foobar",
})
b.wait_in_text(self.card_row("Storage", name=disk), "Swap (encrypted)")

# It should have been started and have a fstab entry
self.click_card_row("Storage", name=disk)
b.wait_text(self.card_desc("Swap", "Used"), "0")
dev = b.text(self.card_desc("Encryption", "Cleartext device"))
self.assertIn("defaults", m.execute(f"findmnt --fstab -n -o OPTIONS {dev}"))


if __name__ == '__main__':
Expand Down

0 comments on commit ffd6b83

Please sign in to comment.