/* * Copyright (C) 2023 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import { bootloaderTypes, getDeviceDisk, } from "../helpers/storage.js"; import { _callClient, _getProperty } from "./helpers.js"; import { runStorageTaskAsync, StorageClient, } from "./storage.js"; import { getBootloaderDrive, setBootloaderDrive, } from "./storage_bootloader.js"; import { getSelectedDisks, } from "./storage_disks_selection.js"; const INTERFACE_NAME_STORAGE = "org.fedoraproject.Anaconda.Modules.Storage"; const INTERFACE_NAME_PARTITIONING = "org.fedoraproject.Anaconda.Modules.Storage.Partitioning"; const INTERFACE_NAME_PARTITIONING_MANUAL = "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Manual"; const INTERFACE_NAME_PARTITIONING_AUTOMATIC = "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Automatic"; const OBJECT_PATH = "/org/fedoraproject/Anaconda/Modules/Storage"; const callClient = (...args) => { return _callClient(StorageClient, OBJECT_PATH, INTERFACE_NAME_STORAGE, ...args); }; const getProperty = (...args) => { return _getProperty(StorageClient, OBJECT_PATH, INTERFACE_NAME_STORAGE, ...args); }; const getPropertyAutomatic = (path, ...args) => { return _getProperty(StorageClient, path, INTERFACE_NAME_PARTITIONING_AUTOMATIC, ...args); }; /** * @param {string} partitioning DBus path to a partitioning * * @returns {Promise} Resolves the DBus path to the partitioning */ export const applyPartitioning = ({ partitioning }) => { return callClient("ApplyPartitioning", [partitioning]); }; /** * @param {string} method A partitioning method * * @returns {Promise} Resolves the DBus path to the partitioning */ export const createPartitioning = ({ method }) => { return callClient("CreatePartitioning", [method]); }; /** * @param {string} partitioning DBus path to a partitioning * * @returns {Promise} The device tree object */ export const getDeviceTree = ({ partitioning }) => { return new StorageClient().client.call( partitioning, INTERFACE_NAME_PARTITIONING, "GetDeviceTree", [] ).then(res => res[0]); }; /** * @param {string} partitioning DBus path to a partitioning * @param {string} passphrase passphrase for disk encryption */ export const partitioningSetPassphrase = ({ partitioning, passphrase }) => { return new StorageClient().client.call( partitioning, INTERFACE_NAME_PARTITIONING_AUTOMATIC, "SetPassphrase", [passphrase] ); }; /** * @param {string} partitioning DBus path to a partitioning * @param {boolean} encrypt True if partitions should be encrypted, False otherwise */ export const partitioningSetEncrypt = ({ encrypt, partitioning }) => { return getPartitioningRequest({ partitioning }) .then(request => { request.encrypted = cockpit.variant("b", encrypt); return setPartitioningRequest({ partitioning, request }); }); }; /* Create DBus request object for home reuse partitioning * @param {string} scheme autopartitioning scheme * @param {string} reuseEFIPart should EFI partition be reused ? */ export const getAutopartReuseDBusRequest = ({ reuseEFIPart, scheme }) => { const configurationSchemeToDBus = { BTRFS: cockpit.variant("i", 1), LVM: cockpit.variant("i", 2), LVM_THINP: cockpit.variant("i", 3), PLAIN: cockpit.variant("i", 0), STRATIS: cockpit.variant("i", 4), }; const request = { "partitioning-scheme": configurationSchemeToDBus?.[scheme], }; const reused = reuseEFIPart ? ["/home", "/boot/efi"] : ["/home"]; request["reused-mount-points"] = cockpit.variant("as", reused); if (scheme === "PLAIN") { // "/" will be reallocated by autopartitioning const removed = reuseEFIPart ? ["/", "/boot"] : ["/", "/boot", "bootloader"]; request["removed-mount-points"] = cockpit.variant("as", removed); } else { // "LVM", "BTRFS", "LVM_THINP", "STRATIS" // "/" can't be reallocated by autopartitioning as it is sharing container device with /home const removed = reuseEFIPart ? ["/boot"] : ["/boot", "bootloader"]; request["removed-mount-points"] = cockpit.variant("as", removed); request["reformatted-mount-points"] = cockpit.variant("as", ["/"]); } return request; }; /** * @param {string} partitioning DBus path to a partitioning * @param {string} homeReuseOptions options for home reuse partitioning requests */ export const partitioningSetHomeReuse = async ({ homeReuseOptions, partitioning }) => { const autopartRequest = await getPartitioningRequest({ partitioning }); const reuseRequest = getAutopartReuseDBusRequest(homeReuseOptions); const request = { ...autopartRequest, ...reuseRequest }; await setPartitioningRequest({ partitioning, request }); }; /** * @returns {Promise} The request of automatic partitioning */ export const getPartitioningRequest = ({ partitioning }) => { return ( new StorageClient().client.call( partitioning, "org.freedesktop.DBus.Properties", "Get", [ INTERFACE_NAME_PARTITIONING_AUTOMATIC, "Request", ] ) .then(res => res[0].v) ); }; /** * @param {string} partitioning DBus path to a partitioning * * @returns {Promise} The partitioning method */ export const getPartitioningMethod = ({ partitioning }) => { return ( new StorageClient().client.call( partitioning, "org.freedesktop.DBus.Properties", "Get", [ INTERFACE_NAME_PARTITIONING, "PartitioningMethod", ] ) .then(res => res[0].v) ); }; /** * @returns {Promise} The applied partitioning */ export const getAppliedPartitioning = () => { return getProperty("AppliedPartitioning"); }; /** * @returns {Promise} The applied partitioning */ export const getAutomaticPartitioningRequest = ({ partitioning }) => { return getPropertyAutomatic(partitioning, "Request"); }; /** * @param {string} partitioning DBus path to a partitioning * @param {Object} request A data object with the request */ export const setPartitioningRequest = ({ partitioning, request }) => { return new StorageClient().client.call( partitioning, "org.freedesktop.DBus.Properties", "Set", [ INTERFACE_NAME_PARTITIONING_AUTOMATIC, "Request", cockpit.variant("a{sv}", request) ] ); }; /** * @param {string} partitioning DBus path to a partitioning * * @returns {Promise} Resolves a DBus path to a task */ export const partitioningConfigureWithTask = ({ partitioning }) => { return new StorageClient().client.call( partitioning, INTERFACE_NAME_PARTITIONING, "ConfigureWithTask", [] ); }; const partitioningValidateAsync = async ({ partitioning }) => { const tasks = await new StorageClient().client.call( partitioning, INTERFACE_NAME_PARTITIONING, "ValidateWithTask", [] ); return runStorageTaskAsync({ onSuccess: async () => { const taskProxy = new StorageClient().client.proxy( "org.fedoraproject.Anaconda.Task", tasks[0] ); const result = await taskProxy.GetResult(); return result.v; }, task: tasks[0], }); }; const parseStorageValidationReport = (validationReport) => ({ errors: validationReport?.["error-messages"]?.v || [], warnings: validationReport?.["warning-messages"]?.v || [], }); export const resetPartitioning = () => { return callClient("ResetPartitioning", []); }; /* * @param {string} partitioning DBus path to a partitioning * @param {Array.} requests An array of request objects */ export const setManualPartitioningRequests = ({ partitioning, requests }) => { return new StorageClient().client.call( partitioning, "org.freedesktop.DBus.Properties", "Set", [ INTERFACE_NAME_PARTITIONING_MANUAL, "Requests", cockpit.variant("aa{sv}", requests) ] ); }; /** * @param {string} partitioning DBus path to a partitioning * * @returns {Promise} The gathered requests for manual partitioning */ export const gatherRequests = ({ partitioning }) => { return new StorageClient().client.call( partitioning, INTERFACE_NAME_PARTITIONING_MANUAL, "GatherRequests", [] ).then(res => res[0]); }; export const applyStorage = async ({ devices, luks, partitioning }) => { if (luks?.encrypted !== undefined) { await partitioningSetEncrypt({ encrypt: luks.encrypted, partitioning }); } if (luks?.passphrase) { await partitioningSetPassphrase({ partitioning, passphrase: luks.passphrase }); } const [currentBootDrive, selectedDisks] = await Promise.all([ getBootloaderDrive(), getSelectedDisks(), ]); const shouldResetBootDrive = currentBootDrive && !selectedDisks?.includes(currentBootDrive); const method = await getPartitioningMethod({ partitioning }); if (method === "MANUAL") { const requests = await gatherRequests({ partitioning }); const bootloaderDevice = requests.find(request => bootloaderTypes.includes(request["format-type"].v))?.["device-spec"].v; const bootloaderDisk = getDeviceDisk(devices, bootloaderDevice); const rootDevice = requests.find(request => request["mount-point"].v === "/")?.["device-spec"].v; const rootDisk = getDeviceDisk(devices, rootDevice); if (bootloaderDisk !== rootDisk && !!bootloaderDisk) { await setBootloaderDrive({ drive: bootloaderDisk }); } else if (shouldResetBootDrive || !currentBootDrive) { await setBootloaderDrive({ drive: "" }); } } else if (shouldResetBootDrive) { await setBootloaderDrive({ drive: "" }); } const configureTasks = await partitioningConfigureWithTask({ partitioning }); const validationReport = await runStorageTaskAsync({ onSuccess: async () => { await applyPartitioning({ partitioning }); return partitioningValidateAsync({ partitioning }); }, task: configureTasks[0], }); const { errors, warnings } = parseStorageValidationReport(validationReport); return { errors, validationReport, warnings }; };