// // Copyright (C) <2026> // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . /* Functions for creating with disk layout */ use crate::kira_disk_layout::*; use std::process::Command; use std::fs; #[derive(Debug)] pub struct CommandFailed{ stderr: Vec, cmd: String, } impl std::fmt::Display for CommandFailed { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Command failed! Command: {}; Stderr: {}", self.cmd, String::from_utf8(self.stderr.clone()).unwrap_or_else(|_|{"STDEER IS NOT VALID UTF-8".to_string()})) } } impl CommandFailed { pub fn new(cmd: &str, args: &Vec<&str>, stderr: Vec) -> Self { Self { stderr: stderr, cmd: format!("{} {}", cmd, args.join(" ")), } } } #[derive(Debug)] pub enum ExecCommandError { IoError(std::io::Error), FromUtf8Error(std::string::FromUtf8Error), CommandFailed(CommandFailed), Other(String), } // 2. Implement From for each wrapped error type impl From for ExecCommandError { fn from(err: std::io::Error) -> Self { ExecCommandError::IoError(err) } } impl From for ExecCommandError { fn from(err: std::string::FromUtf8Error) -> Self { ExecCommandError::FromUtf8Error(err) } } impl From for ExecCommandError { fn from(err: CommandFailed) -> Self { ExecCommandError::CommandFailed(err) } } impl From for ExecCommandError { fn from(err: String) -> Self { ExecCommandError::Other(err) } } impl std::fmt::Display for ExecCommandError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ExecCommandError::IoError(e) => write!(f, "IO Error: {}", e), ExecCommandError::FromUtf8Error(e) => { write!(f, "IO streem to String parse Error: {}", e) } ExecCommandError::CommandFailed(ex) => { write!(f, "{}", ex) } ExecCommandError::Other(msg) => write!(f, "Error: {}", msg), } } } // Implement std::error::Error impl std::error::Error for ExecCommandError {} /// exec given sommand with arguments, capturing stderr in case or failure fn exec_command(cmd: &str, args: &Vec<&str>) -> Result<(), ExecCommandError> { let out = Command::new(cmd).args(args).output()?; if out.status.success() { Ok(()) } else { Err(CommandFailed::new(cmd, args, out.stderr).into()) } } /// exec given sommand with arguments, returning stdout parsed to UTF-8 string /// capturing stderr in case or failure fn exec_command_out(cmd: &str, args: &Vec<&str>) -> Result { let out = Command::new(cmd).args(args).output()?; if out.status.success() { Ok(String::from_utf8(out.stdout)?) } else { Err(CommandFailed::new(cmd, args, out.stderr).into()) } } /// sync system time with chrony /// dinitctl start chrony pub fn dinit_sync_time_chrony() -> Result<(), ExecCommandError> { exec_command("dinitctl", &vec!["start", "chrony"]) } /// Install base system /// basestrap /mnt base base-devel dinit elogind-dinit pub fn basestrap_dinit(root_dir: &str) -> Result<(), ExecCommandError> { exec_command("basestrap", &vec![root_dir, "base", "base-devel", "dinit", "elogind-dinit"]) } /// Executes parted command with given arguments fn exec_parted(args: &Vec<&str>) -> Result { exec_command_out("parted", args) } fn parted_get_last_part_num(dev: &BlkDev) -> Result { // parted -m /dev/loop0 print let res = exec_parted(&vec!["-m", dev.full_name().as_str(), "print"])?; match res.lines().last().and_then(|last_line| { last_line .split_once(':') .and_then(|(first_part, _)| u32::from_str_radix(first_part, 10).ok()) }) { Some(n) => Ok(n), None => Err("Error parsing parted output".to_string().into()), } } /// init gpt partition table /// parted --script $install_device mklabel gpt /// Its blocking function pub fn parted_init_gpt_part_table(dev: &BlkDev) -> Result<(), ExecCommandError> { exec_parted(&vec![ "--script", dev.full_name().as_str(), "mklabel", "gpt", ]) .map(|_| ()) } const LINUX_ROOT_X86_64_TYPE: &str = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709"; /// creating partition from PartInfo specification /// It assumes that we add partitions sequentially to empty space (for part attributes settings) /// Otherwise can produce bonkers layout pub fn parted_mkpart(dev: &BlkDev, part: &PartInfo) -> Result<(), ExecCommandError> { let part_end = part.start + part.size; // gpt label should be in double qutes let gpt_label = if let Some(s) = &part.gpt_label { format!("\"{}\"", s) } else { "\"\"".to_string() }; // creating partition exec_parted(&vec![ "--script", dev.full_name().as_str(), "mkpart", gpt_label.as_str(), part.fs_type.to_parted_str(), part.start.to_parted_bytes_str().as_str(), part_end.to_parted_bytes_str().as_str(), ])?; // set partition options depending on its role if let Some(role) = &part.role { // getting part number of last created partition (hopefully) let part_id = parted_get_last_part_num(dev)?; match role { PartRole::EFI => { exec_parted(&vec![ "--script", dev.full_name().as_str(), "set", &part_id.to_string(), "esp", "on", ])?; } // parted --script $install_device type 3 4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709 PartRole::ROOT => { exec_parted(&vec![ "--script", dev.full_name().as_str(), "type", &part_id.to_string(), LINUX_ROOT_X86_64_TYPE, ])?; } _ => (), } }; Ok(()) } /// creating single drive bcachefs volume pub fn bcachefs_format( dev_name: &str, compression: Option, password: Option, ) -> Result<(), ExecCommandError> { // sudo bcachefs format --compression=lz4 --background_compression=lz4 --encrypted /dev/loop0 // --data_checksum=none - do not make sense on single dev volumes // sudo bcachefs format --force --compression=lz4 --background_compression=lz4 --encrypted --passphrase_file=./test.pass /dev/loop0 let mut arg = vec!["format", "--force", "--data_checksum=none"]; // if compression algorythm specified if let Some(compresss_alg) = &compression { arg.push(compresss_alg.as_str()); } // if we want encrypted volume if let Some(passw) = password { // creating tmp file with password fs::write("/tmp/bzzpsspass.txt", &passw)?; arg.push("--encrypted"); arg.push("--passphrase_file=/tmp/bzzpsspass.txt"); } // add dev name at the end arg.push(dev_name); let out = exec_command("bcachefs", &arg); // ignore any errors while deleting pass tmp file let _ = fs::remove_file("/tmp/bzzpsspass.txt"); out } /// unlok encrypted bcache volume pub fn bcachefs_unlock(dev_name: &str, pass: &str) -> Result<(), ExecCommandError> { // bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0 let arg = vec!["unlock", "--file=/tmp/bzzpsspass.txt ", dev_name]; // creating tmp file with password fs::write("/tmp/bzzpsspass.txt", &pass)?; let out = exec_command("bcachefs", &arg); // ignore any errors while deleting pass tmp file let _ = fs::remove_file("/tmp/bzzpsspass.txt"); out } /// create subvolume pub fn bcachefs_create_subvolume(vol_path: &str) -> Result<(), ExecCommandError> { // sudo bcachefs subvolume create /mnt/test/nyan exec_command("bcachefs", &vec!["subvolume", "create", vol_path]) } /// mount bcachefs pub fn bcachefs_mount(dev_name: &str, mount_point: &str) -> Result<(), ExecCommandError> { // sudo mount -t bcachefs /dev/loop0 /mnt/test exec_command("mount", &vec!["-t", "bcachefs", dev_name, mount_point]) } pub fn fatlabel(dev: &str, label: &str) -> Result<(), ExecCommandError> { //fatlabel /dev/sda1 ESP exec_command("fatlabel", &vec![dev, label]) } pub fn mkfs_fat32(dev: &str, maybe_label: Option<&str>) -> Result<(), ExecCommandError> { //mkfs.fat -F 32 /dev/sda1 exec_command("mkfs.fat", &vec!["-F", "32", dev])?; // if need to set label if let Some(label) = maybe_label { fatlabel(dev, label) } else { Ok(()) } } /// mkfs.fat -F 32 /dev/sda1 /// fatlabel /dev/sda1 ESP pub fn mkfs_esp(dev: &str) -> Result<(), ExecCommandError> { mkfs_fat32(dev, Some("ESP")) } pub fn mkswap(dev: &str) -> Result<(), ExecCommandError> { //mkswap -L SWAP /dev/sda4 exec_command("mkswap", &vec!["L", "SWAP", dev]) }