From c6e7fa60698cf04ebcda0f9e97749e5dde8c3969103df1446928af4daacc7c2b Mon Sep 17 00:00:00 2001 From: Kira Date: Thu, 21 May 2026 08:39:11 +0200 Subject: [PATCH] More work on partition stuff #2, things looks much better now. --- src/kira_disk_layout.rs | 115 ++++++++++++++--- src/stages/partition/mod.rs | 241 ++++++------------------------------ 2 files changed, 136 insertions(+), 220 deletions(-) diff --git a/src/kira_disk_layout.rs b/src/kira_disk_layout.rs index 2479926..d6012e8 100644 --- a/src/kira_disk_layout.rs +++ b/src/kira_disk_layout.rs @@ -24,9 +24,9 @@ pub fn format_bytes_size(size: u64) -> String { /// sector size - default 4096 #[derive(Debug, Clone, PartialEq, Eq)] pub struct BlkDev { - name: String, - size: u64, - sector_size: u32, + pub name: String, + pub size: u64, + pub sector_size: u32, } impl std::fmt::Display for BlkDev { @@ -43,6 +43,57 @@ impl BlkDev { sector_size: 4096, } } + pub fn from_hash_map(data: &HashMap) -> Option { + Some(Self { + name: data.get("NAME")?.clone(), + size: u64::from_str_radix(data.get("SIZE")?, 10).ok()?, + sector_size: 4096, + }) + } + /// this is blocking function + pub fn list_sys_blk_dev() -> Result, String> { + use std::process::Command; + + // lsblk -d -b -P -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT + match Command::new("lsblk") + .args(["-d", "-b", "-P", "-o", "NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT"]) + .output() + { + Ok(cmd_output) => { + if cmd_output.status.success() { + match String::from_utf8(cmd_output.stdout) { + Ok(raw_str_list) => Ok(raw_str_list + .lines() + .map(|l| { + l.split_whitespace() + .filter_map(|pair| { + pair.split_once("=").and_then(|kv| { + Some(( + kv.0.trim_matches('"').to_string(), + kv.1.trim_matches('"').to_string(), + )) + }) + }) + .collect::>() + }) + .filter(|mp| mp.get("MOUNTPOINT").is_some_and(|v| v.is_empty())) + .filter_map(|mp| Self::from_hash_map(&mp)) + .collect()), + Err(ex) => Err(format!( + "Exception while converting disks info to UTF8 {}", + ex + )), + } + } else { + Err(format!( + "Error getting disk devices list: {}", + String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into()) + )) + } + } + Err(ex) => Err(ex.to_string()), + } + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -102,12 +153,13 @@ impl FSType { #[derive(Debug, Clone, PartialEq, Eq)] pub struct PartInfo { - name: String, - size: u64, - fs_type: FSType, - gpt_label: Option, - fs_label: Option, - mount_point: Option, + pub name: String, + pub size: u64, + pub start: u64, + pub fs_type: FSType, + pub gpt_label: Option, + pub fs_label: Option, + pub mount_point: Option, } impl std::fmt::Display for PartInfo { @@ -127,6 +179,7 @@ impl PartInfo { Some(Self { name: data.get("NAME")?.clone(), size: u64::from_str_radix(data.get("SIZE")?, 10).ok()?, + start: 0, fs_type: FSType::from_str(data.get("FSTYPE")?), gpt_label: data .get("PARTLABEL") @@ -233,9 +286,9 @@ pub fn align_part(size: u64, start: u64, align: u64) -> Option<(u64, u64, u64)> } pub struct PartLayout { - dev: BlkDev, - empty_space: u64, - part_list: Vec, + pub dev: BlkDev, + pub empty_space: u64, + pub part_list: Vec, } impl PartLayout { @@ -247,7 +300,7 @@ impl PartLayout { } } - pub fn read_from_dev(dev: &BlkDev) -> Result { + pub fn read_from_disk(dev: &BlkDev) -> Result { let part_list = get_dev_part_blocking(&dev.name)?; let parts_size = part_list.iter().fold(0, |acc, part| acc + part.size); Ok(Self { @@ -271,8 +324,40 @@ impl PartLayout { fs_label: Option, mount_point: Option, ) -> Self { + let part_start = if self.part_list.is_empty() { + 2u64 * 1024u64 * 1024u64 + } else { + self.dev.size.checked_sub(self.empty_space).unwrap() + }; + + let (size, start, _) = + align_part(part_size, part_start, self.dev.sector_size as u64).unwrap(); + + self.part_list.push(PartInfo { + name: "_".into(), + size: size, + start: start, + fs_type: fs_type, + gpt_label: gpt_label, + fs_label: fs_label, + mount_point: mount_point, + }); + + self.empty_space = self.empty_space.checked_sub(size).unwrap(); + self } + pub fn add_part_reserve( + self, + reserve_space: u64, + fs_type: FSType, + gpt_label: Option, + fs_label: Option, + mount_point: Option, + ) -> Self { + let p_size = self.empty_space.checked_sub(reserve_space).unwrap(); + self.add_part(p_size, fs_type, gpt_label, fs_label, mount_point) + } } #[cfg(test)] @@ -304,7 +389,7 @@ mod tests { .random_iter::() .filter(|n| *n > 0) .take(100) - .flat_map(|align| + .flat_map(|align| { (0..u64::MAX) .step_by(align as usize) .take(1000) @@ -319,7 +404,7 @@ mod tests { (size_align, start_align.clone(), end_align), ) }) - ) + }) { let res = align_part(size, start, align).unwrap(); assert_eq!(res, test_res); diff --git a/src/stages/partition/mod.rs b/src/stages/partition/mod.rs index 8edcdf7..7363b35 100644 --- a/src/stages/partition/mod.rs +++ b/src/stages/partition/mod.rs @@ -21,6 +21,8 @@ use crate::stage::{StageAction, StageResult}; use crate::kira_color_bar; +use crate::kira_disk_layout::{BlkDev,PartInfo, PartLayout, FSType}; +use toml::Table; use iced::{Alignment, Color, Length, widget}; use rust_i18n::t; @@ -47,98 +49,25 @@ const SWAP_MODES: [SwapMode; 3] = [ SwapMode::SwapHibernate, ]; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BlkDev { - name: String, - size_mb: u64, -} - -impl std::fmt::Display for BlkDev { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} ({})", &self.name, size_mb_to_text(self.size_mb)) - } -} - -impl BlkDev { - pub fn from_lsblk_out_vec(v: &Vec) -> Self { - let name = v[0].clone(); - let size = u64::from_str_radix(&v[3], 10).unwrap_or(0) / 1048576; - Self { - name: name, - size_mb: size, - } - } -} - -fn part_type_to_color(t: &str) -> Color { +fn part_type_to_color(t: &FSType) -> Color { match t { - "vfat" => Color::from_rgb8(204, 121, 167), - "xfs" => Color::from_rgb8(0, 158, 115), - "crypto_LUKS" => Color::from_rgb8(240, 228, 66), - "swap" => Color::from_rgb8(213, 94, 0), - "ext4" => Color::from_rgb8(0, 114, 178), - "btrfs" => Color::from_rgb8(86, 180, 233), + FSType::VFAT => Color::from_rgb8(204, 121, 167), + FSType::XFS => Color::from_rgb8(0, 158, 115), + FSType::LUKS => Color::from_rgb8(240, 228, 66), + FSType::SWAP => Color::from_rgb8(213, 94, 0), + FSType::EXT4 => Color::from_rgb8(0, 114, 178), + FSType::BTRFS => Color::from_rgb8(86, 180, 233), _ => Color::from_rgb8(230, 159, 0), } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PartInfo { - name: String, - part_type: String, - size_mb: u64, -} - -fn size_mb_to_text(size_mb: u64) -> String { - let mut s = size_mb; - if s < 1000 { - format!("{}MB", s) - } else { - s = s / 1024; - if s < 1000 { - format!("{}GB", s) - } else { - s = s / 1024; - format!("{}TB", s) - } - } -} - -impl std::fmt::Display for PartInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} {} {}", - &self.name, - self.part_type, - size_mb_to_text(self.size_mb) - ) - } -} - -impl PartInfo { - pub fn from_vect(v: &Vec) -> Option { - if v.len() == 4 && v[3] == "part" { - let name = v[0].clone(); - let size_mb = u64::from_str_radix(&v[1], 10).unwrap_or(0) / 1048576; - let part_type = v[2].clone(); - Some(Self { - name: name, - part_type: part_type, - size_mb: size_mb, - }) - } else { - None - } - } - - pub fn to_ct_params(&self, dev_size_mb: u64) -> (String, u16, Color) { - let fill_ratio: u16 = ((self.size_mb as f32 / dev_size_mb as f32) * 12.0) as u16; +fn part_to_ct_params(part: &PartInfo, dev_size: u64) -> (String, u16, Color) { + let fill_ratio: u16 = ((part.size as f32 / dev_size as f32) * 12.0) as u16; let fill_ratio = fill_ratio.max(1); - let part_color = part_type_to_color(&self.part_type); - (self.to_string(), fill_ratio, part_color) + let part_color = part_type_to_color(&part.fs_type); + (part.to_string(), fill_ratio, part_color) } -} + #[derive(Debug, Clone)] pub struct PartitionStage { @@ -160,133 +89,35 @@ pub enum Message { Back, } -/// returns list of system block devices with their size in MB -pub fn get_disks_info_blocking() -> Result, String> { - use std::process::Command; - match Command::new("lsblk").args(["-n", "-d", "-b"]).output() { - Ok(cmd_output) => { - if cmd_output.status.success() { - match String::from_utf8(cmd_output.stdout) { - Ok(raw_str_list) => Ok(raw_str_list - .lines() - .map(|l| { - l.split_whitespace() - .map(str::to_string) - .collect::>() - }) - .filter(|l_v| l_v.len() == 6) - .map(|dev_params| BlkDev::from_lsblk_out_vec(&dev_params)) - .collect()), - Err(ex) => Err(format!( - "Exception while converting disks info to UTF8 {}", - ex - )), - } - } else { - Err(format!( - "Error getting disk devices list: {}", - String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into()) - )) - } - } - Err(ex) => Err(ex.to_string()), - } -} -/// getting list of device partitions -pub fn get_dev_part_blocking(dev_name: &str) -> Result, String> { - use std::process::Command; - - //"lsblk -o NAME,SIZE,FSTYPE -l -n /dev/nvme0n1" - - match Command::new("lsblk") - .args([ - "-o", - "NAME,SIZE,FSTYPE,TYPE", - "-l", - "-n", - "-b", - format!("/dev/{}", dev_name).as_str(), - ]) - .output() - { - Ok(cmd_output) => { - if cmd_output.status.success() { - match String::from_utf8(cmd_output.stdout) { - Ok(raw_str_list) => Ok(raw_str_list - .lines() - .map(|l| { - l.split_whitespace() - .map(str::to_string) - .collect::>() - }) - .filter_map(|v| PartInfo::from_vect(&v)) - .collect()), - Err(ex) => Err(format!( - "Exception while converting partition info to UTF8 {}", - ex - )), - } - } else { - Err(format!( - "Error getting device partitions list: {}", - String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into()) - )) - } - } - Err(ex) => Err(ex.to_string()), - } -} - -use toml::Table; impl PartitionStage { - pub fn gen_disk_layout_estimate(&self) -> Vec { + pub fn gen_disk_layout_estimate(&self) -> PartLayout { let dev = self.device.clone().unwrap(); + let swap_size: u64 = match self.swap_mode { Some(SwapMode::NoSwap) => 0, - Some(SwapMode::SwapHibernate) => 666, - Some(SwapMode::SwapNoHibernate) => 1024, - _ => 1024, + Some(SwapMode::SwapHibernate) => 666*1024*1024, + Some(SwapMode::SwapNoHibernate) => 1024*1024*1024, + _ => 1024*1024*1024, }; - let mut res: Vec = Vec::new(); + let mut res: PartLayout = PartLayout::new(dev); + // UEFI partition - res.push(PartInfo { - name: "EFI".into(), - part_type: "vfat".into(), - size_mb: 256, - }); - // boot A and B - res.push(PartInfo { - name: "kira_boot_a".into(), - part_type: "xfs".into(), - size_mb: 512, - }); - res.push(PartInfo { - name: "kira_boot_b".into(), - part_type: "xfs".into(), - size_mb: 512, - }); - // user data - res.push(PartInfo { - name: "kira_data".into(), - part_type: "xfs".into(), - size_mb: dev.size_mb - 1024 - 256 - swap_size, - }); + res = res.add_part(256*1024*1024, FSType::VFAT, Some("kira-efi".into()), Some("EFI".into()), Some("/efi".into())) + .add_part(512*1024*1024, FSType::XFS, Some("kira-boot-a".into()), None, Some("/".into())) + .add_part_reserve(swap_size, FSType::XFS, Some("kira-home".into()), None, Some("/home".into())); + // swap if swap_size > 0 { - res.push(PartInfo { - name: "".into(), - part_type: "swap".into(), - size_mb: swap_size, - }); + res = res.add_part(swap_size, FSType::SWAP, Some("kira-swap".into()), None, Some("SWAP".into())); } res } pub fn new(toml_config: &Table) -> Result { - let maybe_dev_list = get_disks_info_blocking(); + let maybe_dev_list = BlkDev::list_sys_blk_dev(); let min_disk_size_mb = toml_config .get("partition") @@ -295,13 +126,13 @@ impl PartitionStage { .and_then(|v| v.as_integer()) .unwrap_or(8192); - let min_disk_size_mb: u64 = min_disk_size_mb.try_into().unwrap_or(8192); + let min_disk_size: u64 = min_disk_size_mb.try_into().unwrap_or(8192) * 1024 * 1024; match maybe_dev_list { Ok(dev_list) => { let acepted_dev: Vec = dev_list .iter() - .filter(|v| v.size_mb > min_disk_size_mb) + .filter(|v| v.size >= min_disk_size) .cloned() .collect(); @@ -341,11 +172,11 @@ impl PartitionStage { pub fn update(&mut self, message: Message) -> StageAction { match message { Message::SelectDevice(dev) => { - self.selected_disk_parts = match get_dev_part_blocking(&dev.name) { + self.selected_disk_parts = match PartLayout::read_from_disk(&dev) { Ok(dev_parts) => Some( - dev_parts + dev_parts.part_list .iter() - .map(|part_inf| part_inf.to_ct_params(dev.size_mb)) + .map(|part_inf| part_to_ct_params(&part_inf, dev.size)) .collect(), ), Err(ex) => { @@ -377,11 +208,11 @@ impl PartitionStage { pub fn view(&self) -> iced::Element<'_, Message> { let dev_parts_es: Option> = if self.selected_disk_parts.is_some() { + let p_l = self.gen_disk_layout_estimate(); Some( - self.gen_disk_layout_estimate() - .iter() - .map(|part_inf| part_inf.to_ct_params(self.device.clone().unwrap().size_mb)) - .collect(), + p_l.part_list.iter() + .map(|part_info| part_to_ct_params(part_info, p_l.dev.size)) + .collect() ) } else { None