From 4d9512e89deef3bd7d5e50dd85ba3c4e374d1dcb Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Fri, 7 Jun 2024 11:36:44 +0200 Subject: [PATCH 1/6] storaged: support creating btrfs snapshots 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. --- pkg/storaged/btrfs/subvolume.jsx | 69 +++++++++++++++++++++++++++++++- pkg/storaged/btrfs/utils.jsx | 5 +++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index affb6c8840a..a597f8e5e3e 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -34,10 +34,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"; @@ -180,6 +180,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]; @@ -364,6 +423,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"); diff --git a/pkg/storaged/btrfs/utils.jsx b/pkg/storaged/btrfs/utils.jsx index 402ded0fef9..26676c3e0f6 100644 --- a/pkg/storaged/btrfs/utils.jsx +++ b/pkg/storaged/btrfs/utils.jsx @@ -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."); +} From b0d5d56e567b483587cea5b5442f7508ababb34a Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Thu, 18 Jul 2024 16:53:28 +0200 Subject: [PATCH 2/6] WiP --- pkg/storaged/btrfs/get-path-uuid.py | 30 +++++ pkg/storaged/btrfs/subvolume.jsx | 116 +++++++++++++----- pkg/storaged/btrfs/utils.jsx | 44 ++++++- .../btrfs/verify-btrfs-snapshot-location.py | 30 +++++ pkg/storaged/dialog.jsx | 45 ++++++- 5 files changed, 230 insertions(+), 35 deletions(-) create mode 100644 pkg/storaged/btrfs/get-path-uuid.py create mode 100644 pkg/storaged/btrfs/verify-btrfs-snapshot-location.py diff --git a/pkg/storaged/btrfs/get-path-uuid.py b/pkg/storaged/btrfs/get-path-uuid.py new file mode 100644 index 00000000000..dc9d1cdff9d --- /dev/null +++ b/pkg/storaged/btrfs/get-path-uuid.py @@ -0,0 +1,30 @@ +import os +import os.path +import subprocess +import sys + + +def main(path): + # If the path does not exist, we will create but need to verify it lives on the same volume + if not os.path.exists(path): + if path.endswith('/'): + path = path.rstrip('/') + path = os.path.dirname(path) + + # bail out if the parent path is not found + if not os.path.exists(path): + sys.exit(2) + + try: + sys.stdout.write(subprocess.check_output(["findmnt", "--output", "UUID,fstype", "--json", "--target", path]).decode().strip()) + except subprocess.SubprocessError as exc: + print(exc, file=sys.stderr) + sys.exit(3) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + sys.stderr.write("Path not provided\n") + sys.exit(1) + + main(sys.argv[1]) diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index a597f8e5e3e..7958ba51404 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -34,11 +34,12 @@ 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, validate_snapshot_path } from "./utils.jsx"; +import { btrfs_usage, validate_subvolume_name, parse_subvol_from_options, validate_snapshots_location } from "./utils.jsx"; import { at_boot_input, update_at_boot_input, mounting_dialog, mount_options } from "../filesystem/mounting-dialog.jsx"; import { dialog_open, TextInput, CheckBoxes, TeardownMessage, init_teardown_usage, + SelectOneRadioVerticalTextInput, } from "../dialog.jsx"; import { check_mismounted_fsys, MismountAlert } from "../filesystem/mismounting.jsx"; import { @@ -180,60 +181,111 @@ function subvolume_create(volume, subvol, parent_dir) { }); } -function snapshot_create(volume, subvol, parent_dir) { +async 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") } + console.log(volume, subvol); + const action_variants = [ + { tag: null, Title: _("Create snapshot") }, ]; - if (client.in_anaconda_mode()) { - action_variants = [ - { tag: "nomount", Title: _("Create") } - ]; - } + const getCurrentDate = async () => { + const out = await cockpit.spawn(["date", "+%s"]); + const now = parseInt(out.trim()) * 1000; + const d = new Date(now); + d.setSeconds(0); + d.setMilliseconds(0); + return d; + }; + + const folder_exists = async (path) => { + // Check if path does not exist and can be created + try { + await cockpit.spawn(["test", "-d", path]); + return true; + } catch { + return false; + } + }; + + const date = await getCurrentDate(); + const current_date = date.toISOString().split("T")[0]; + const current_date_time = date.toISOString().replace(":00.000Z", ""); + const choices = [ + { + value: "current_date", + title: cockpit.format(_("Current date $0"), current_date), + }, + { + value: "current_date_time", + title: cockpit.format(_("Current date and time $0"), current_date_time), + }, + { + value: "custom_name", + title: _("Custom name"), + type: "radioWithInput", + }, + ]; dialog_open({ Title: _("Create snapshot"), Fields: [ - TextInput("path", _("Path"), + TextInput("subvolume", _("Subvolume"), + { + value: subvol.pathname, + disabled: true, + }), + TextInput("snapshots_location", _("Snapshots location"), { - validate: path => validate_snapshot_path(path) + placeholder: cockpit.format(_("Example, $0"), "/.snapshots"), + explanation: _("Snapshots must reside within their subvolume."), + validate: path => validate_snapshots_location(path, volume), }), - CheckBoxes("readonly", _("Read-only"), + SelectOneRadioVerticalTextInput("snapshot_name", _("Snapshot name"), + { + value: { checked: "current_date", inputs: { } }, + choices, + validate: (val, _values, _variant) => { + if (val.checked === "custom_name") + return validate_subvolume_name(val.inputs.custom_name); + } + }), + + CheckBoxes("readonly", _("Option"), { fields: [ - { tag: "on", title: _("Make the new snapshot readonly") } + { tag: "on", title: _("Read-only") } ], }), - 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) { + // Create snapshot location if it does not exists + console.log("values", vals); + const exists = await folder_exists(vals.snapshots_location); + if (!exists) { + await cockpit.spawn(["btrfs", "subvolume", "create", vals.snapshots_location], { superuser: "require", err: "message" }); + } + // 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) + if (vals.readonly?.on) 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); + let snapshot_name = ""; + if (vals.snapshot_name.checked == "current_date") { + snapshot_name = current_date; + } else if (vals.snapshot_name.checked === "current_date_time") { + snapshot_name = current_date_time; + } else if (vals.snapshot_name.checked === "custom_name") { + snapshot_name = vals.snapshot_name.inputs.custom_name; } + console.log([...cmd, `/${subvol.pathname}`, `${vals.snapshots_location}/${snapshot_name}`]); + // TODO: need full path to subvolume + // ERROR: cannot snapshot '/home': Read-only file system + // This happens when a snapshot already exists! + await cockpit.spawn([...cmd, `/${subvol.pathname}`, `${vals.snapshots_location}/${snapshot_name}`], { superuser: "require", err: "message" }); } } }); diff --git a/pkg/storaged/btrfs/utils.jsx b/pkg/storaged/btrfs/utils.jsx index 26676c3e0f6..a579b9365e4 100644 --- a/pkg/storaged/btrfs/utils.jsx +++ b/pkg/storaged/btrfs/utils.jsx @@ -19,6 +19,8 @@ import cockpit from "cockpit"; import { decode_filename } from "../utils.js"; +import * as python from "python.js"; +import get_path_uuid from "./get-path-uuid.py"; const _ = cockpit.gettext; @@ -89,7 +91,45 @@ export function validate_subvolume_name(name) { return cockpit.format(_("Name cannot contain the character '/'.")); } -export function validate_snapshot_path(path) { +export async function validate_snapshots_location(path, volume) { if (path === "") - return _("Path cannot be empty."); + return _("Location cannot be empty."); + + try { + const output = await python.spawn([get_path_uuid], [path], + { environ: ["LANGUAGE=" + (cockpit.language || "en")] }); + console.log(output); + const path_info = JSON.parse(output); + if (path_info.filesystems.length !== 1) + return _("Unable to detect filesystem for given path"); + + const fs = path_info.filesystems[0]; + if (fs.fstype !== "btrfs") + return _("Provided path is not btrfs"); + + if (fs.uuid !== volume.data.uuid) + return _("Snapshot location needs to be on the same btrfs volume"); + } catch (err) { + if (err.exit_status == 2) + return _("Parent of snapshot location does not exist"); + console.warn("Unable to detect UUID of snapshot location", err); + } + // const path_exists = await folder_exists(path); + // // Verify that the parent is in the same btrfs volume + // if (!path_exists) { + // } + // + // try { + // const output = await cockpit.spawn(["findmnt", "-o", "UUID", "-n", "-T", path], { err: "message" }); + // const uuid = output.trim(); + // if (uuid !== volume.data.uuid) { + // return _("Snapshot location needs to be on the same btrfs volume"); + // } + // } catch (err) { + // console.log(err); + // if (err?.message === "") { + // return _("Given path does not exist"); + // } + // console.warn("Unable to detect UUID of snapshot location", err); + // } } diff --git a/pkg/storaged/btrfs/verify-btrfs-snapshot-location.py b/pkg/storaged/btrfs/verify-btrfs-snapshot-location.py new file mode 100644 index 00000000000..1cfe39849b8 --- /dev/null +++ b/pkg/storaged/btrfs/verify-btrfs-snapshot-location.py @@ -0,0 +1,30 @@ +import os +import os.path +import subprocess +import sys + + +def main(path): + # If the path does not exist, we will create but need to verify it lives on the same volume + if not os.path.exists(path): + if path.endswith('/'): + path = path.rstrip('/') + path = os.path.dirname(path) + + # bail out if the parent path is not found + if not os.path.exists(path): + sys.exit(2) + + try: + print(subprocess.check_output(["findmnt", "--output", "UUID", "--no-heading", "--target", path])) + except subprocess.SubprocessError as exc: + print(exc, file=sys.stderr) + sys.exit(3) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + sys.stdout.write("Path not provided\n") + sys.exit(1) + + main(sys.argv[1]) diff --git a/pkg/storaged/dialog.jsx b/pkg/storaged/dialog.jsx index a510df8ace8..8932ff52a7d 100644 --- a/pkg/storaged/dialog.jsx +++ b/pkg/storaged/dialog.jsx @@ -300,7 +300,7 @@ const Row = ({ field, values, errors, onChange }) => { ); } else if (!field.bare) { return ( - + { field_elts } { nested_elts } @@ -601,6 +601,7 @@ export const TextInput = (tag, title, options) => { title, options, initial_value: options.value || "", + isInline: options.isInline || false, render: (val, change, validated) => { }; }; +export const SelectOneRadioVerticalTextInput = (tag, title, options) => { + return { + tag, + title, + options, + initial_value: options.value || { checked: {}, inputs: {} }, + hasNoPaddingTop: true, + + render: (val, change) => { + const fieldset = options.choices.map(c => { + const ftag = tag + "." + c.value; + const fval = val.checked === c.value; + const tval = val.inputs[c.value] || ''; + function fchange(newval) { + val.checked = newval; + change(val); + } + + function tchange(newval) { + val.inputs[c.value] = newval; + change(val); + } + + return ( + + fchange(c.value)} label={c.title} /> + {fval !== false && c?.type === "radioWithInput" && tchange(value)} />} + + ); + }); + + return ( +
+ {fieldset} +
+ ); + } + }; +}; + export const SelectRow = (tag, headers, options) => { return { tag, From d2215d5d46daff063308381277d2896033a1a47f Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Thu, 25 Jul 2024 14:19:01 +0200 Subject: [PATCH 3/6] storaged: support placeholder in storage dialog TextInput --- pkg/storaged/dialog.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/storaged/dialog.jsx b/pkg/storaged/dialog.jsx index 8932ff52a7d..42ea5a9b8be 100644 --- a/pkg/storaged/dialog.jsx +++ b/pkg/storaged/dialog.jsx @@ -609,6 +609,7 @@ export const TextInput = (tag, title, options) => { aria-label={title} value={val} isDisabled={options.disabled} + placeholder={options.placeholder} onChange={(_event, value) => change(value)} /> }; }; From 4085748b20c5c668e91593bf30a654739bf61f4c Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Thu, 25 Jul 2024 18:08:14 +0200 Subject: [PATCH 4/6] support saving snapshot location --- pkg/storaged/btrfs/subvolume.jsx | 62 ++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index 7958ba51404..dc1a227a8e8 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -183,11 +183,32 @@ function subvolume_create(volume, subvol, parent_dir) { async function snapshot_create(volume, subvol, parent_dir) { const block = client.blocks[volume.path]; + const localstorage_key = "storage:snapshot-locations"; console.log(volume, subvol); const action_variants = [ { tag: null, Title: _("Create snapshot") }, ]; + const getLocalStorageSnapshotLocs = () => { + const localstorage_snapshot_data = localStorage.getItem(localstorage_key); + if (localstorage_snapshot_data === null) + return null; + + try { + return JSON.parse(localstorage_snapshot_data); + } catch (err) { + console.warn("localstorage btrfs snapshot locations data malformed", localstorage_snapshot_data); + return null; + } + }; + + const getSavedSnapshotLocation = subvol => { + const snapshot_locations = getLocalStorageSnapshotLocs(); + if (snapshot_locations != null) + return snapshot_locations[subvol.id] || null; + return snapshot_locations; + }; + const getCurrentDate = async () => { const out = await cockpit.spawn(["date", "+%s"]); const now = parseInt(out.trim()) * 1000; @@ -198,7 +219,7 @@ async function snapshot_create(volume, subvol, parent_dir) { }; const folder_exists = async (path) => { - // Check if path does not exist and can be created + // Check if path exist and can be created try { await cockpit.spawn(["test", "-d", path]); return true; @@ -208,6 +229,7 @@ async function snapshot_create(volume, subvol, parent_dir) { }; const date = await getCurrentDate(); + // Convert dates to ISO-8601 const current_date = date.toISOString().split("T")[0]; const current_date_time = date.toISOString().replace(":00.000Z", ""); const choices = [ @@ -226,6 +248,18 @@ async function snapshot_create(volume, subvol, parent_dir) { }, ]; + const get_snapshot_name = (vals) => { + let snapshot_name = ""; + if (vals.snapshot_name.checked == "current_date") { + snapshot_name = current_date; + } else if (vals.snapshot_name.checked === "current_date_time") { + snapshot_name = current_date_time; + } else if (vals.snapshot_name.checked === "custom_name") { + snapshot_name = vals.snapshot_name.inputs.custom_name; + } + return snapshot_name; + }; + dialog_open({ Title: _("Create snapshot"), Fields: [ @@ -236,8 +270,12 @@ async function snapshot_create(volume, subvol, parent_dir) { }), TextInput("snapshots_location", _("Snapshots location"), { + value: getSavedSnapshotLocation(subvol), placeholder: cockpit.format(_("Example, $0"), "/.snapshots"), - explanation: _("Snapshots must reside within their subvolume."), + explanation: (<> +

{_("Snapshots must reside within the same btrfs volume.")}

+

{_("When the snapshot location does not exist, it will be created as btrfs subvolume automatically.")}

+ ), validate: path => validate_snapshots_location(path, volume), }), SelectOneRadioVerticalTextInput("snapshot_name", _("Snapshot name"), @@ -273,19 +311,15 @@ async function snapshot_create(volume, subvol, parent_dir) { const cmd = ["btrfs", "subvolume", "snapshot"]; if (vals.readonly?.on) cmd.push("-r"); - let snapshot_name = ""; - if (vals.snapshot_name.checked == "current_date") { - snapshot_name = current_date; - } else if (vals.snapshot_name.checked === "current_date_time") { - snapshot_name = current_date_time; - } else if (vals.snapshot_name.checked === "custom_name") { - snapshot_name = vals.snapshot_name.inputs.custom_name; - } + + const snapshot_name = get_snapshot_name(vals); console.log([...cmd, `/${subvol.pathname}`, `${vals.snapshots_location}/${snapshot_name}`]); - // TODO: need full path to subvolume - // ERROR: cannot snapshot '/home': Read-only file system - // This happens when a snapshot already exists! - await cockpit.spawn([...cmd, `/${subvol.pathname}`, `${vals.snapshots_location}/${snapshot_name}`], { superuser: "require", err: "message" }); + const snapshot_location = `${vals.snapshots_location}/${snapshot_name}`; + await cockpit.spawn([...cmd, `/${subvol.pathname}`, snapshot_location], { superuser: "require", err: "message" }); + localStorage.setItem(localstorage_key, JSON.stringify({ ...getLocalStorageSnapshotLocs(), [subvol.id]: vals.snapshots_location })); + + // Re-trigger btrfs poll so the users sees the created snapshot in the overview or subvolume detail page + await btrfs_poll(); } } }); From f03717251dcd1fb0fb62bde88739d1b67479d11f Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Thu, 25 Jul 2024 18:12:31 +0200 Subject: [PATCH 5/6] correct subvolume path --- pkg/storaged/btrfs/subvolume.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index dc1a227a8e8..b3502159637 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -181,10 +181,9 @@ function subvolume_create(volume, subvol, parent_dir) { }); } -async function snapshot_create(volume, subvol, parent_dir) { - const block = client.blocks[volume.path]; +async function snapshot_create(volume, subvol, subvolume_path) { const localstorage_key = "storage:snapshot-locations"; - console.log(volume, subvol); + console.log(volume, subvol, subvolume_path); const action_variants = [ { tag: null, Title: _("Create snapshot") }, ]; @@ -313,9 +312,9 @@ async function snapshot_create(volume, subvol, parent_dir) { cmd.push("-r"); const snapshot_name = get_snapshot_name(vals); - console.log([...cmd, `/${subvol.pathname}`, `${vals.snapshots_location}/${snapshot_name}`]); + console.log([...cmd, subvolume_path, `${vals.snapshots_location}/${snapshot_name}`]); const snapshot_location = `${vals.snapshots_location}/${snapshot_name}`; - await cockpit.spawn([...cmd, `/${subvol.pathname}`, snapshot_location], { superuser: "require", err: "message" }); + await cockpit.spawn([...cmd, subvolume_path, snapshot_location], { superuser: "require", err: "message" }); localStorage.setItem(localstorage_key, JSON.stringify({ ...getLocalStorageSnapshotLocs(), [subvol.id]: vals.snapshots_location })); // Re-trigger btrfs poll so the users sees the created snapshot in the overview or subvolume detail page From 0babff918a67810857ccf571dcc8bdc6c88c94d5 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Fri, 26 Jul 2024 15:36:07 +0200 Subject: [PATCH 6/6] variable shuffling --- pkg/storaged/btrfs/subvolume.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index b3502159637..18be33b6685 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -188,7 +188,7 @@ async function snapshot_create(volume, subvol, subvolume_path) { { tag: null, Title: _("Create snapshot") }, ]; - const getLocalStorageSnapshotLocs = () => { + const get_local_storage_snapshots_locs = () => { const localstorage_snapshot_data = localStorage.getItem(localstorage_key); if (localstorage_snapshot_data === null) return null; @@ -201,14 +201,14 @@ async function snapshot_create(volume, subvol, subvolume_path) { } }; - const getSavedSnapshotLocation = subvol => { - const snapshot_locations = getLocalStorageSnapshotLocs(); + const get_localstorage_snapshot_location = subvol => { + const snapshot_locations = get_local_storage_snapshots_locs(); if (snapshot_locations != null) return snapshot_locations[subvol.id] || null; return snapshot_locations; }; - const getCurrentDate = async () => { + const get_current_date = async () => { const out = await cockpit.spawn(["date", "+%s"]); const now = parseInt(out.trim()) * 1000; const d = new Date(now); @@ -227,7 +227,7 @@ async function snapshot_create(volume, subvol, subvolume_path) { } }; - const date = await getCurrentDate(); + const date = await get_current_date(); // Convert dates to ISO-8601 const current_date = date.toISOString().split("T")[0]; const current_date_time = date.toISOString().replace(":00.000Z", ""); @@ -269,7 +269,7 @@ async function snapshot_create(volume, subvol, subvolume_path) { }), TextInput("snapshots_location", _("Snapshots location"), { - value: getSavedSnapshotLocation(subvol), + value: get_localstorage_snapshot_location(subvol), placeholder: cockpit.format(_("Example, $0"), "/.snapshots"), explanation: (<>

{_("Snapshots must reside within the same btrfs volume.")}

@@ -315,7 +315,7 @@ async function snapshot_create(volume, subvol, subvolume_path) { console.log([...cmd, subvolume_path, `${vals.snapshots_location}/${snapshot_name}`]); const snapshot_location = `${vals.snapshots_location}/${snapshot_name}`; await cockpit.spawn([...cmd, subvolume_path, snapshot_location], { superuser: "require", err: "message" }); - localStorage.setItem(localstorage_key, JSON.stringify({ ...getLocalStorageSnapshotLocs(), [subvol.id]: vals.snapshots_location })); + localStorage.setItem(localstorage_key, JSON.stringify({ ...get_local_storage_snapshots_locs(), [subvol.id]: vals.snapshots_location })); // Re-trigger btrfs poll so the users sees the created snapshot in the overview or subvolume detail page await btrfs_poll();