Files
nyanit/src/nyan_system.rs
T

354 lines
11 KiB
Rust
Raw Normal View History

2026-06-20 18:31:34 +02:00
// <Nyanit - init program fot initrd.>
// Copyright (C) <2026> <Kira Foundation>
// 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 <https://www.gnu.org/licenses/>.
/*
Functions fot system interaction
*/
use nix;
2026-06-20 18:31:34 +02:00
use std::fs;
use std::process::Command;
#[derive(Debug)]
pub struct CommandFailed {
pub stderr: Vec<u8>,
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<u8>) -> 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<std::io::Error> for ExecCommandError {
fn from(err: std::io::Error) -> Self {
ExecCommandError::IoError(err)
}
}
impl From<std::string::FromUtf8Error> for ExecCommandError {
fn from(err: std::string::FromUtf8Error) -> Self {
ExecCommandError::FromUtf8Error(err)
}
}
impl From<CommandFailed> for ExecCommandError {
fn from(err: CommandFailed) -> Self {
ExecCommandError::CommandFailed(err)
}
}
impl From<String> 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()?;
2026-06-20 18:31:34 +02:00
if out.status.success() {
Ok(())
} else {
Err(CommandFailed::new(cmd, args, out.stderr).into())
}
}
/// Gets device node aka /dev/sda1 from gpr partition label
/// blkid -s PARTLABEL --match-token "PARTLABEL=primary"
pub fn find_partlabel(partlabel: &str) -> Result<String, ExecCommandError> {
let out = Command::new("/usr/bin/blkid")
.args([
"-s",
"PARTLABEL",
"--match-token",
format!("\"PARTLABEL={}\"", partlabel).as_str(),
])
.output()?;
if out.status.success() {
let out_text = String::from_utf8(out.stdout)?;
let split_res = out_text.split_once(':').ok_or_else(|| {
ExecCommandError::Other(format!("Unexpected blkid output format: \"{}\"", out_text))
})?;
Ok(split_res.0.to_string())
} else {
Err(ExecCommandError::Other(format!(
"Connot find device with partlabel={} or something other is wrong: \"{}\"",
partlabel,
String::from_utf8(out.stderr).unwrap_or_else(|_| String::new())
)))
}
}
2026-06-20 18:31:34 +02:00
/// exec given command with arguments, returning stdout parsed to UTF-8 string
/// capturing stderr in case or failure
2026-06-24 17:44:17 +02:00
// pub fn exec_command_out(cmd: &str, args: &Vec<&str>) -> Result<String, ExecCommandError> {
// 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())
// }
// }
2026-06-20 18:31:34 +02:00
// ls /dev/disk/by-partlabel/
// here we can find gpt part labels
2026-06-24 17:44:17 +02:00
// pub fn list_gpt_labeled_devs() -> Result<Vec<String>, ExecCommandError> {
// use std::os::unix::fs::FileTypeExt;
// let mut res: Vec<String> = 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());
// }
// }
2026-06-20 18:31:34 +02:00
2026-06-24 17:44:17 +02:00
// Ok(res)
// }
2026-06-20 18:31:34 +02:00
2026-06-23 18:34:46 +02:00
/// 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<bool, ExecCommandError> {
use std::io::Read;
use std::io::Write;
use std::process::Stdio;
2026-06-24 18:46:04 +02:00
// "-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("/usr/bin/bcachefs")
.args(&vec!["unlock", "-k", "session", dev])
2026-06-23 18:34:46 +02:00
.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())?;
2026-06-23 18:34:46 +02:00
// 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())?;
2026-06-23 18:34:46 +02:00
// 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())
2026-06-23 18:34:46 +02:00
}
2026-06-20 18:31:34 +02:00
/// Cheking if device is encripted
pub fn bzzpss_dev_encrypted(dev_name: &str) -> Result<bool, ExecCommandError> {
match exec_command("/usr/bin/bcachefs", &vec!["unlock", "--check", dev_name]) {
2026-06-20 18:31:34 +02:00
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<Vec<String>, 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<String> = 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<(), String> {
use nix::mount::{MsFlags, mount};
use std::path::Path;
// Mount proc filesystem
2026-06-20 18:31:34 +02:00
// mount -t proc none /proc
mount(
Some("proc"),
Path::new("/proc"),
Some("proc"),
MsFlags::empty(),
None::<&str>,
)
.map_err(|ex| format!("Error mounting /proc: {}", ex.to_string()))?;
2026-06-20 18:31:34 +02:00
// mount -t sysfs none /sys
mount(
Some("sysfs"),
Path::new("/sys"),
Some("sysfs"),
MsFlags::empty(),
None::<&str>,
)
.map_err(|ex| format!("Error mounting /sys: {}", ex.to_string()))?;
mount(
Some("devtmpfs"),
"/dev",
Some("devtmpfs"),
MsFlags::empty(),
None::<&str>,
)
.map_err(|ex| format!("Error mounting /dev: {}", ex.to_string()))?;
2026-06-20 18:31:34 +02:00
// modprobe bcachefs
// exec_command("modprobe", &vec!["bcachefs"])?;
2026-06-20 18:31:34 +02:00
Ok(())
}
///
/// MUST be done before spawning any threads or async runtimes
pub fn set_env_vars_unsafe() -> () {
use std::env;
// 1. Define your custom paths
let custom_bin = "/usr/bin";
let custom_lib = "/usr/lib";
// 2. Retrieve existing paths to append/prepend (optional but recommended)
// 3. Construct new path strings
let new_path = match env::var("PATH") {
Err(_) => custom_bin.to_string(),
Ok(v) => format!("{}:{}", custom_bin, v),
};
let new_ld_path = match env::var("LD_LIBRARY_PATH") {
Err(_) => custom_lib.to_string(),
Ok(v) => format!("{}:{}", custom_lib, v),
};
// 4. Set environment variables (UNSAFE in Rust 2024+)
// MUST be done before spawning any threads or async runtimes
unsafe {
env::set_var("PATH", new_path);
env::set_var("LD_LIBRARY_PATH", new_ld_path);
}
}
2026-06-20 18:31:34 +02:00
// # Mount the real root filesystem
pub fn mount_bee_root(mount_point: &str, dev_name: &str) -> Result<(), ExecCommandError> {
exec_command("/usr/bin/mount", &vec!["-o", "ro", dev_name, mount_point])?;
2026-06-20 18:31:34 +02:00
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("/usr/bin/switch_root")
2026-06-20 18:31:34 +02:00
.arg(mnt_root)
.arg(init_path)
.exec();
// If execution reaches here, exec() failed.
Err(format!("switch_root failed: {}", err).into())
}