More work on partition stuff #2, things looks much better now.

This commit is contained in:
2026-05-21 08:39:11 +02:00
parent c0ae014eef
commit c6e7fa6069
2 changed files with 136 additions and 220 deletions
+100 -15
View File
@@ -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<String, String>) -> Option<Self> {
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<Vec<Self>, 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::<HashMap<String, String>>()
})
.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<String>,
fs_label: Option<String>,
mount_point: Option<String>,
pub name: String,
pub size: u64,
pub start: u64,
pub fs_type: FSType,
pub gpt_label: Option<String>,
pub fs_label: Option<String>,
pub mount_point: Option<String>,
}
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<PartInfo>,
pub dev: BlkDev,
pub empty_space: u64,
pub part_list: Vec<PartInfo>,
}
impl PartLayout {
@@ -247,7 +300,7 @@ impl PartLayout {
}
}
pub fn read_from_dev(dev: &BlkDev) -> Result<Self, String> {
pub fn read_from_disk(dev: &BlkDev) -> Result<Self, String> {
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<String>,
mount_point: Option<String>,
) -> 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<String>,
fs_label: Option<String>,
mount_point: Option<String>,
) -> 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::<u32>()
.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);
+36 -205
View File
@@ -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<String>) -> 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<String>) -> Option<Self> {
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<Vec<BlkDev>, 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::<Vec<String>>()
})
.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<Vec<PartInfo>, 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::<Vec<String>>()
})
.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<PartInfo> {
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<PartInfo> = 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<Self, String> {
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<BlkDev> = 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<Vec<(String, u16, Color)>> = 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