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
|
|
|
|
|
*/
|
|
|
|
|
|
2026-07-20 23:21:48 +02:00
|
|
|
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> {
|
2026-07-20 23:21:48 +02:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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
|
2026-07-20 23:21:48 +02:00
|
|
|
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()?;
|
|
|
|
|
|
2026-07-20 23:21:48 +02:00
|
|
|
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
|
|
|
|
|
{
|
2026-07-20 23:21:48 +02:00
|
|
|
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
|
2026-07-20 23:21:48 +02:00
|
|
|
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> {
|
2026-07-20 23:21:48 +02:00
|
|
|
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
|
2026-07-20 23:21:48 +02:00
|
|
|
pub fn prepare_env() -> Result<(), String> {
|
|
|
|
|
use nix::mount::{mount, MsFlags};
|
|
|
|
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
|
|
// Mount proc filesystem
|
2026-06-20 18:31:34 +02:00
|
|
|
// mount -t proc none /proc
|
2026-07-20 23:21:48 +02:00
|
|
|
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
|
2026-07-20 23:21:48 +02:00
|
|
|
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
|
2026-07-13 01:39:42 +02:00
|
|
|
// exec_command("modprobe", &vec!["bcachefs"])?;
|
2026-06-20 18:31:34 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-15 01:13:28 +02:00
|
|
|
///
|
|
|
|
|
/// 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
|
2026-07-15 16:15:09 +02:00
|
|
|
let new_path = match env::var("PATH") {
|
|
|
|
|
Err(_) => custom_bin.to_string(),
|
2026-07-20 23:21:48 +02:00
|
|
|
Ok(v) => format!("{}:{}", custom_bin, v),
|
2026-07-15 16:15:09 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let new_ld_path = match env::var("LD_LIBRARY_PATH") {
|
|
|
|
|
Err(_) => custom_lib.to_string(),
|
2026-07-20 23:21:48 +02:00
|
|
|
Ok(v) => format!("{}:{}", custom_lib, v),
|
2026-07-15 16:15:09 +02:00
|
|
|
};
|
2026-07-15 01:13:28 +02:00
|
|
|
|
|
|
|
|
// 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> {
|
2026-07-20 23:21:48 +02:00
|
|
|
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.
|
2026-07-20 23:21:48 +02:00
|
|
|
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())
|
|
|
|
|
}
|