* 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 });
});
};
* @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") {
const removed = reuseEFIPart ? ["/", "/boot"] : ["/", "/boot", "bootloader"];
request["removed-mount-points"] = cockpit.variant("as", removed);
} else {
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.<Object>} 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 };
};