Skip to content

Commit

Permalink
storaged: support creating btrfs snapshots
Browse files Browse the repository at this point in the history
Btrfs supports creating a snapshot of a subvolume, either readonly or
write-able. Unlike creating subvolumes, it doesn't make sense to put a
snapshot of a subvolume in the subvolume itself users likely have a
special `snapshots` subvolume where they collect their snapshots.
  • Loading branch information
jelly committed Jun 7, 2024
1 parent 8b64080 commit 2b1674a
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
69 changes: 67 additions & 2 deletions pkg/storaged/btrfs/subvolume.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ import {
get_fstab_config_with_client, reload_systemd, extract_option, parse_options,
flatten, teardown_active_usage,
} from "../utils.js";
import { btrfs_usage, validate_subvolume_name, parse_subvol_from_options } from "./utils.jsx";
import { btrfs_usage, validate_subvolume_name, parse_subvol_from_options, validate_snapshot_path } from "./utils.jsx";
import { at_boot_input, update_at_boot_input, mounting_dialog, mount_options } from "../filesystem/mounting-dialog.jsx";
import {
dialog_open, TextInput,
dialog_open, TextInput, CheckBoxes,
TeardownMessage, init_teardown_usage,
} from "../dialog.jsx";
import { check_mismounted_fsys, MismountAlert } from "../filesystem/mismounting.jsx";
Expand Down Expand Up @@ -178,6 +178,65 @@ function subvolume_create(volume, subvol, parent_dir) {
});
}

function snapshot_create(volume, subvol, parent_dir) {
const block = client.blocks[volume.path];

let action_variants = [
{ tag: null, Title: _("Create and mount") },
{ tag: "nomount", Title: _("Create only") }
];

if (client.in_anaconda_mode()) {
action_variants = [
{ tag: "nomount", Title: _("Create") }
];
}

dialog_open({
Title: _("Create snapshot"),
Fields: [
TextInput("path", _("Path"),
{
validate: path => validate_snapshot_path(path)
}),
CheckBoxes("readonly", _("Read-only"),
{
fields: [
{ tag: "on", title: _("Make the new snapshot readonly") }
],
}),
TextInput("mount_point", _("Mount Point"),
{
validate: (val, _values, variant) => {
return is_valid_mount_point(client,
block,
client.add_mount_point_prefix(val),
variant == "nomount");
}
}),
mount_options(false, false),
at_boot_input(),
],
update: update_at_boot_input,
Action: {
Variants: action_variants,
action: async function (vals) {
// HACK: cannot use block_btrfs.CreateSnapshot as it always creates a subvolume relative to MountPoints[0] which
// makes it impossible to handle a situation where we have multiple subvolumes mounted.
// https://github.com/storaged-project/udisks/issues/1242
const cmd = ["btrfs", "subvolume", "snapshot"];
if (vals.readonly)
cmd.push("-r");
await cockpit.spawn([...cmd, parent_dir, vals.path], { superuser: "require", err: "message" });
await btrfs_poll();
if (vals.mount_point !== "") {
await set_mount_options(subvol, block, vals);
}
}
}
});
}

function subvolume_delete(volume, subvol, mount_point_in_parent, card) {
const block = client.blocks[volume.path];
const subvols = client.uuids_btrfs_subvols[volume.data.uuid];
Expand Down Expand Up @@ -362,6 +421,12 @@ function make_btrfs_subvolume_page(parent, volume, subvol, path_prefix, subvols)
action: () => subvolume_create(volume, subvol, (mounted && !opt_ro) ? mount_point : mount_point_in_parent),
});

actions.push({
title: _("Create snapshot"),
excuse: create_excuse,
action: () => snapshot_create(volume, subvol, (mounted && !opt_ro) ? mount_point : mount_point_in_parent),
});

let delete_excuse = "";
if (!mount_point_in_parent) {
delete_excuse = _("At least one parent needs to be mounted writable");
Expand Down
5 changes: 5 additions & 0 deletions pkg/storaged/btrfs/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,8 @@ export function validate_subvolume_name(name) {
if (name.includes('/'))
return cockpit.format(_("Name cannot contain the character '/'."));
}

export function validate_snapshot_path(path) {
if (path === "")
return _("Path cannot be empty.");
}

0 comments on commit 2b1674a

Please sign in to comment.