Functions for creating partitions and filesystems.
This commit is contained in:
+224
-13
@@ -18,9 +18,9 @@
|
|||||||
Functions for working with disk layout
|
Functions for working with disk layout
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::kira_size::KiraSize;
|
||||||
use log;
|
use log;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, io::Read, process::ExitStatus};
|
||||||
use crate::kira_size::{KiraSize};
|
|
||||||
///
|
///
|
||||||
/// size - device size in bytes
|
/// size - device size in bytes
|
||||||
/// sector size - default 4096
|
/// sector size - default 4096
|
||||||
@@ -38,6 +38,7 @@ impl std::fmt::Display for BlkDev {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlkDev {
|
impl BlkDev {
|
||||||
|
/// Create new BlkDev struct with specifyed name and size
|
||||||
pub fn new(name: String, size: KiraSize) -> Self {
|
pub fn new(name: String, size: KiraSize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name,
|
name: name,
|
||||||
@@ -45,6 +46,9 @@ impl BlkDev {
|
|||||||
sector_size: KiraSize::new_b(4096),
|
sector_size: KiraSize::new_b(4096),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create new BlkDev struct with name and size parsed from hashmap
|
||||||
|
/// that contains String key val pairs with "NAME" and "SIZE" keys
|
||||||
pub fn from_hash_map(data: &HashMap<String, String>) -> Option<Self> {
|
pub fn from_hash_map(data: &HashMap<String, String>) -> Option<Self> {
|
||||||
Some(Self {
|
Some(Self {
|
||||||
name: data.get("NAME")?.clone(),
|
name: data.get("NAME")?.clone(),
|
||||||
@@ -52,7 +56,13 @@ impl BlkDev {
|
|||||||
sector_size: KiraSize::new_b(4096),
|
sector_size: KiraSize::new_b(4096),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// returns dev name in a "/dev/name" form
|
||||||
|
pub fn full_name(&self) -> String {
|
||||||
|
format!("/dev/{}", self.name)
|
||||||
|
}
|
||||||
|
|
||||||
/// this is blocking function
|
/// this is blocking function
|
||||||
|
/// Lists block devices in system thru "lsbk" command
|
||||||
pub fn list_sys_blk_dev() -> Result<Vec<Self>, String> {
|
pub fn list_sys_blk_dev() -> Result<Vec<Self>, String> {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -139,6 +149,22 @@ impl FSType {
|
|||||||
_ => Self::UNKNOWN,
|
_ => Self::UNKNOWN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// return fs type that can be used with parted utility
|
||||||
|
pub fn to_parted_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::VFAT => "fat32",
|
||||||
|
Self::XFS => "xfs",
|
||||||
|
Self::LUKS => "luks",
|
||||||
|
Self::SWAP => "linux-swap",
|
||||||
|
Self::EXT4 => "ext4",
|
||||||
|
Self::BTRFS => "btrfs",
|
||||||
|
// workaroud as parted do not support bcachefs partition type
|
||||||
|
Self::BCACHEFS => "bcachefs",
|
||||||
|
// lets do something generic here instead of panic
|
||||||
|
Self::UNKNOWN => "linux-swap",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn part_type_to_color(t: &str) -> Color {
|
// fn part_type_to_color(t: &str) -> Color {
|
||||||
@@ -177,13 +203,7 @@ pub struct PartInfo {
|
|||||||
|
|
||||||
impl std::fmt::Display for PartInfo {
|
impl std::fmt::Display for PartInfo {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(f, "{} {} {}", &self.name, &self.fs_type, self.size)
|
||||||
f,
|
|
||||||
"{} {} {}",
|
|
||||||
&self.name,
|
|
||||||
&self.fs_type,
|
|
||||||
self.size
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +311,8 @@ pub fn align_part(size: u64, start: u64, align: u64) -> Option<(u64, u64, u64)>
|
|||||||
if align_size < align {
|
if align_size < align {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"!!! align_size < align !!! align_size: {} align: {}",
|
"!!! align_size < align !!! align_size: {} align: {}",
|
||||||
align_size, align
|
align_size,
|
||||||
|
align
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -316,7 +337,9 @@ impl PartLayout {
|
|||||||
|
|
||||||
pub fn read_from_disk(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 part_list = get_dev_part_blocking(&dev.name)?;
|
||||||
let parts_size = part_list.iter().fold(KiraSize::new_b(0), |acc, part| acc + part.size);
|
let parts_size = part_list
|
||||||
|
.iter()
|
||||||
|
.fold(KiraSize::new_b(0), |acc, part| acc + part.size);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
dev: dev.clone(),
|
dev: dev.clone(),
|
||||||
empty_space: dev.size - parts_size,
|
empty_space: dev.size - parts_size,
|
||||||
@@ -392,7 +415,7 @@ impl PartLayout {
|
|||||||
)
|
)
|
||||||
.add_part_reserve(
|
.add_part_reserve(
|
||||||
swap_size,
|
swap_size,
|
||||||
FSType::XFS,
|
FSType::BTRFS,
|
||||||
Some("root".into()),
|
Some("root".into()),
|
||||||
None,
|
None,
|
||||||
Some("/".into()),
|
Some("/".into()),
|
||||||
@@ -415,6 +438,195 @@ impl PartLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes parted command with given arguments
|
||||||
|
fn exec_parted(args: &Vec<&str>) -> Result<String, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
match Command::new("parted").args(args).output() {
|
||||||
|
Err(ex) => Err(ex.to_string()),
|
||||||
|
Ok(out) => {
|
||||||
|
if out.status.success() {
|
||||||
|
String::from_utf8(out.stdout).map_err(|ex| ex.to_string())
|
||||||
|
} else {
|
||||||
|
Err(out.status.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parted_get_last_part_num(dev: &BlkDev) -> Result<u32, String> {
|
||||||
|
// parted -m /dev/loop0 print
|
||||||
|
match exec_parted(&vec!["-m", dev.full_name().as_str(), "print"]) {
|
||||||
|
Ok(res) => {
|
||||||
|
match res.lines().last().and_then(|last_line| {
|
||||||
|
last_line
|
||||||
|
.split_once(':')
|
||||||
|
.and_then(|(first_part, a)| u32::from_str_radix(first_part, 10).ok())
|
||||||
|
}) {
|
||||||
|
Some(n) => Ok(n),
|
||||||
|
None => Err("Error parsing parted output".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ex) => Err(ex),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// init gpt partition table
|
||||||
|
/// parted --script $install_device mklabel gpt
|
||||||
|
/// Its blocking function
|
||||||
|
pub fn parted_init_gpt_part_table(dev: &BlkDev) -> Result<(), String> {
|
||||||
|
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<(), String> {
|
||||||
|
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: &BlkDev,
|
||||||
|
compression: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// 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
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
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).map_err(|ex| ex.to_string())?;
|
||||||
|
arg.push("--encrypted");
|
||||||
|
arg.push("--passphrase_file=/tmp/bzzpsspass.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// add dev name at the end
|
||||||
|
let dev_name = dev.full_name();
|
||||||
|
arg.push(dev_name.as_str());
|
||||||
|
|
||||||
|
let out = Command::new("bcachefs")
|
||||||
|
.args(arg)
|
||||||
|
.output()
|
||||||
|
.map_err(|ex| ex.to_string())?;
|
||||||
|
|
||||||
|
// ignore any errors while deleting pass tmp file
|
||||||
|
let _ = fs::remove_file("/tmp/bzzpsspass.txt");
|
||||||
|
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8(out.stderr).map_err(|ex| ex.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// unlok encrypted bcache volume
|
||||||
|
pub fn bcachefs_unlock(dev: &BlkDev, pass: String) -> Result<(), String> {
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0
|
||||||
|
let dev_name = dev.full_name();
|
||||||
|
let arg = vec!["unlock", "--file=/tmp/bzzpsspass.txt ", dev_name.as_str()];
|
||||||
|
// creating tmp file with password
|
||||||
|
fs::write("/tmp/bzzpsspass.txt", &pass).map_err(|ex| ex.to_string())?;
|
||||||
|
|
||||||
|
let out = Command::new("bcachefs")
|
||||||
|
.args(arg)
|
||||||
|
.output()
|
||||||
|
.map_err(|ex| ex.to_string())?;
|
||||||
|
// ignore any errors while deleting pass tmp file
|
||||||
|
let _ = fs::remove_file("/tmp/bzzpsspass.txt");
|
||||||
|
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8(out.stderr).map_err(|ex| ex.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// create subvolume
|
||||||
|
pub fn bcachefs_create_subvolume(vol_path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// sudo bcachefs subvolume create /mnt/test/nyan
|
||||||
|
let out = Command::new("bcachefs")
|
||||||
|
.args(["subvolume", "create", vol_path])
|
||||||
|
.output()
|
||||||
|
.map_err(|ex| ex.to_string())?;
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8(out.stderr).map_err(|ex| ex.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
@@ -466,4 +678,3 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user