feat: automatically regenerate initramfs on arch and debian-based systems

https://github.com/ilya-zlobintsev/LACT/issues/236
https://github.com/ilya-zlobintsev/LACT/issues/190
This commit is contained in:
Ilya Zlobintsev 2024-01-07 11:07:06 +02:00
parent ea6d4d2733
commit 5610e72ab6
6 changed files with 132 additions and 7 deletions

10
Cargo.lock generated
View File

@ -1311,6 +1311,7 @@ dependencies = [
"lact-schema",
"libdrm_amdgpu_sys",
"nix 0.27.1",
"os-release",
"pciid-parser",
"serde",
"serde_json",
@ -1588,6 +1589,15 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "os-release"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82f29ae2f71b53ec19cc23385f8e4f3d90975195aa3d09171ba3bef7159bec27"
dependencies = [
"lazy_static",
]
[[package]]
name = "overload"
version = "0.1.1"

View File

@ -127,7 +127,7 @@ impl DaemonClient {
}
request_plain!(get_system_info, SystemInfo, SystemInfo);
request_plain!(enable_overdrive, EnableOverdrive, ());
request_plain!(enable_overdrive, EnableOverdrive, String);
request_plain!(generate_debug_snapshot, GenerateSnapshot, String);
request_with_id!(get_device_info, DeviceInfo, DeviceInfo);
request_with_id!(get_device_stats, DeviceStats, DeviceStats);

View File

@ -36,3 +36,4 @@ zbus = { version = "3.14.1", default-features = false, features = ["tokio"] }
libdrm_amdgpu_sys = { optional = true, version = "0.4.0" }
tar = "0.4.40"
chrono = "0.4.31"
os-release = "0.1.0"

View File

@ -1,11 +1,13 @@
use anyhow::{anyhow, Context};
use lact_schema::SystemInfo;
use lact_schema::{InitramfsType, SystemInfo};
use os_release::{OsRelease, OS_RELEASE};
use std::{
fs::{self, File, Permissions},
io::Write,
os::unix::prelude::PermissionsExt,
process::Command,
};
use tracing::{info, warn};
const PP_OVERDRIVE_MASK: u64 = 0x4000;
pub const PP_FEATURE_MASK_PATH: &str = "/sys/module/amdgpu/parameters/ppfeaturemask";
@ -41,7 +43,7 @@ pub fn info() -> anyhow::Result<SystemInfo<'static>> {
})
}
pub fn enable_overdrive() -> anyhow::Result<()> {
pub fn enable_overdrive() -> anyhow::Result<String> {
let current_mask = read_current_mask()?;
let new_mask = current_mask | PP_OVERDRIVE_MASK;
@ -58,7 +60,14 @@ pub fn enable_overdrive() -> anyhow::Result<()> {
file.write_all(conf.as_bytes())
.context("Could not write config")?;
Ok(())
let message = match regenerate_initramfs() {
Ok(initramfs_type) => {
format!("Initramfs was successfully regenerated (detected type {initramfs_type:?})")
}
Err(err) => format!("{err:#}"),
};
Ok(message)
}
fn read_current_mask() -> anyhow::Result<u64> {
@ -70,3 +79,102 @@ fn read_current_mask() -> anyhow::Result<u64> {
u64::from_str_radix(ppfeaturemask, 16).context("Invalid ppfeaturemask")
}
fn regenerate_initramfs() -> anyhow::Result<InitramfsType> {
let os_release = OS_RELEASE.as_ref().context("Could not detect distro")?;
match detect_initramfs_type(os_release) {
Some(initramfs_type) => {
info!("Detected initramfs type {initramfs_type:?}, regenerating");
let result = match initramfs_type {
InitramfsType::Debian => run_command("update-initramfs", &["-u"]),
InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]),
};
result.map(|()| initramfs_type)
}
None => Err(anyhow!(
"Could not determine initramfs type, manual initramfs regeneration may be required"
)),
}
}
fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsType> {
let id_like: Vec<_> = os_release.id_like.split_whitespace().collect();
if os_release.id == "debian" || id_like.contains(&"debian") {
Some(InitramfsType::Debian)
} else if os_release.id == "arch" || id_like.contains(&"arch") {
if Command::new("mkinitcpio").arg("--version").output().is_ok() {
Some(InitramfsType::Mkinitcpio)
} else {
warn!(
"Arch-based system with no mkinitcpio detected, refusing to regenerate initramfs"
);
None
}
} else {
None
}
}
fn run_command(exec: &str, args: &[&str]) -> anyhow::Result<()> {
info!("Running {exec} with args {args:?}");
let output = Command::new(exec)
.args(args)
.output()
.context("Could not run command")?;
if output.status.success() {
Ok(())
} else {
let stdout = String::from_utf8(output.stdout).context("stdout is not valid UTF-8")?;
let stderr = String::from_utf8(output.stderr).context("stderr is not valid UTF-8")?;
Err(anyhow!("Command exited with error: {stdout}\n{stderr}"))
}
}
#[cfg(test)]
mod tests {
use crate::server::system::detect_initramfs_type;
use lact_schema::InitramfsType;
use os_release::OsRelease;
#[test]
fn detect_initramfs_debian() {
let data = r#"
PRETTY_NAME="Debian GNU/Linux trixie/sid"
NAME="Debian GNU/Linux"
VERSION_CODENAME=trixie
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
"#;
let os_release: OsRelease = data.lines().map(str::to_owned).collect();
assert_eq!(
Some(InitramfsType::Debian),
detect_initramfs_type(&os_release)
);
}
#[test]
fn detect_initramfs_mint() {
let data = r#"
NAME="Linux Mint"
VERSION="21.2 (Victoria)"
ID=linuxmint
ID_LIKE="ubuntu debian"
PRETTY_NAME="Linux Mint 21.2"
VERSION_ID="21.2"
HOME_URL="https://www.linuxmint.com/"
SUPPORT_URL="https://forums.linuxmint.com/"
BUG_REPORT_URL="http://linuxmint-troubleshooting-guide.readthedocs.io/en/latest/"
PRIVACY_POLICY_URL="https://www.linuxmint.com/"
VERSION_CODENAME=victoria
UBUNTU_CODENAME=jammy
"#;
let os_release: OsRelease = data.lines().map(str::to_owned).collect();
assert_eq!(
Some(InitramfsType::Debian),
detect_initramfs_type(&os_release)
);
}
}

View File

@ -505,7 +505,7 @@ impl App {
}
fn enable_overclocking(&self) {
let text = format!("This will enable the overdrive feature of the amdgpu driver by creating a file at <b>{MODULE_CONF_PATH}</b>. Are you sure you want to do this?");
let text = format!("This will enable the overdrive feature of the amdgpu driver by creating a file at <b>{MODULE_CONF_PATH}</b> and updating the initramfs. Are you sure you want to do this? (Note: the GUI may freeze for a bit)");
let dialog = MessageDialog::builder()
.title("Enable Overclocking")
.use_markup(true)
@ -518,10 +518,10 @@ impl App {
dialog.run_async(clone!(@strong self as app => move |diag, response| {
if response == ResponseType::Ok {
match app.daemon_client.enable_overdrive().and_then(|buffer| buffer.inner()) {
Ok(_) => {
Ok(msg) => {
let success_dialog = MessageDialog::builder()
.title("Success")
.text("Overclocking successfully enabled. A system reboot is required to apply the changes")
.text(format!("Overclocking successfully enabled. A system reboot is required to apply the changes.\nSystem message: {msg}"))
.message_type(MessageType::Info)
.buttons(ButtonsType::Ok)
.build();

View File

@ -242,3 +242,9 @@ pub struct PowerState<T> {
pub enabled: bool,
pub value: T,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum InitramfsType {
Debian,
Mkinitcpio,
}