// // 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 fot system interaction */ use std::fs; use std::process::Command; #[derive(Debug)] pub struct CommandFailed { pub stderr: Vec, pub 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 command with arguments, capturing stderr in case or failure pub 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 command with arguments, returning stdout parsed to UTF-8 string /// capturing stderr in case or failure // pub 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()) // } // } // ls /dev/disk/by-partlabel/ // here we can find gpt part labels // pub fn list_gpt_labeled_devs() -> Result, ExecCommandError> { // use std::os::unix::fs::FileTypeExt; // let mut res: Vec = Vec::new(); // for maybe_entry in fs::read_dir("/dev/disk/by-partlabel/")? { // let entry = maybe_entry?; // if entry.file_type()?.is_block_device() { // res.push(entry.path().to_str().unwrap().to_string()); // } // } // Ok(res) // } /// bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0somedev /// Ok so. It is stupid, because bcacheutils far from ideal. /// But if we provide wrong password from file or stdin this stuff plays stupid and asks for right password. /// Sooo.. In order to know if unlock was sucessfull we need to check if program exited with Ok status, /// or it shits to STDERR still waiting for stdin /// Returns true if unlock successful, and false otherwise pub fn bcachefs_unlock(pass: String, dev: &str) -> Result { use std::io::Read; use std::io::Write; use std::process::Stdio; // "-k", "session" is important as it stores key in sessyin keyring what allows // system to mount filesystem after switch_root let mut child = Command::new("bcachefs") .args(&vec!["unlock", "-k", "session", dev]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; let mut c_stderr = child.stderr.take().ok_or_else(||{"Unable to get child process STDERR".to_string()})?; // Write password to STDIN in a limited scope { let mut c_stdin = child.stdin.take().ok_or_else(||{"Unable to get child process STDIN".to_string()})?; // neccessery to use thread or we may be deadlocking c_stdin.write_all(pass.as_bytes())?; let bytes_written = c_stdin.write(b"\n")?; println!("bytes_written: {}", bytes_written); // stdin is dropped here automatically when the block ends } let mut buffer = [0u8; 10]; // This blocks. // if unlok fails it reads 10 bytes (piece of err message) // if sucess it reads 0 bytes and exit let read_b = c_stderr.read(&mut buffer)?; if read_b > 0 { println!("STDERR error not empty, password incorrect! Try to kill a child."); child.kill()?; } // Safe to wait now; child will see EOF let exit_status = child.wait()?; // if exit status is SUCCESS - unloking is successful Ok(exit_status.success()) } /// Cheking if device is encripted pub fn bzzpss_dev_encrypted(dev_name: &str) -> Result { match exec_command("bcachefs", &vec!["unlock", "--check", dev_name]) { Ok(_) => Ok(true), // if status OK - dev encrypted Err(ExecCommandError::CommandFailed(_)) => Ok(false), // if bcachefs returned error - unencrypted Err(ex) => Err(ex), // othervice its error } } //const TMP_ROOT: &str = "/mnt/bee-root"; pub fn list_bzzpss_snaps(snaps_dir: &str) -> Result, ExecCommandError> { use regex::Regex; // root snapshoot in a form of bzz-yyyy-mm-ddTHH-MM-SS let re = Regex::new(r"^bzz-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}$").unwrap(); let mut res: Vec = Vec::new(); for maybe_entry in fs::read_dir(snaps_dir)? { let entry = maybe_entry?; if entry.file_type()?.is_dir() { if let Some(dir_name) = entry.file_name().to_str() && re.is_match(dir_name) { res.push(dir_name.to_string()); } } } Ok(res) } // mounting process //# Mount essential virtual filesystems // mount -t proc none /proc // mount -t sysfs none /sys // modprobe bcachefs pub fn prepare_env() -> Result<(), ExecCommandError> { // mount -t proc none /proc exec_command("mount", &vec!["-t", "proc", "none", "/proc"])?; // mount -t sysfs none /sys exec_command("mount", &vec!["-t", "sysfs", "none", "/sys"])?; // modprobe bcachefs exec_command("modprobe", &vec!["bcachefs"])?; Ok(()) } // # Mount the real root filesystem pub fn mount_bee_root(mount_point: &str, dev_name: &str) -> Result<(), ExecCommandError> { exec_command("mount", &vec!["-o", "ro", dev_name, mount_point])?; Ok(()) } // finish boot pub fn hand_off_control(mnt_root: &str, init_path: &str) -> Result<(), ExecCommandError> { use std::env; use std::os::unix::process::CommandExt; // 2. Change directory to the new root (Mandatory for switch_root) env::set_current_dir(mnt_root)?; // # Clean up virtual filesystems // !!! do not do this, switch_root handle this stuff and fail if thay not present! // exec_command("umount", &vec!["/proc"])?; // exec_command("umount", &vec!["/sys"])?; // # Hand off control to the real system's init // exec switch_root /mnt/root /sbin/init // 3. Execute switch_root. // This call replaces the current Rust process. // It only returns if an error occurs. let err = Command::new("switch_root") .arg(mnt_root) .arg(init_path) .exec(); // If execution reaches here, exec() failed. Err(format!("switch_root failed: {}", err).into()) }