Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 94 additions & 19 deletions src/cmdline.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
// SPDX-FileCopyrightText: 2024 The rsinit Authors
// SPDX-License-Identifier: GPL-2.0-only

use std::fmt::Debug;

use nix::mount::MsFlags;

use crate::util::{read_file, Result};
use crate::{
init::CmdlineCallback,
util::{read_file, Result},
};

#[derive(Debug, PartialEq)]
pub struct CmdlineOptions {
pub struct CmdlineOptions<'a> {
pub root: Option<String>,
pub rootfstype: Option<String>,
pub rootflags: Option<String>,
pub rootfsflags: MsFlags,
pub nfsroot: Option<String>,
pub init: String,
pub cleanup: bool,
callbacks: CmdlineOptionsCallbacks<'a>,
}

#[derive(Default)]
struct CmdlineOptionsCallbacks<'a>(Vec<Box<CmdlineCallback<'a>>>);

impl PartialEq for CmdlineOptionsCallbacks<'_> {
fn eq(&self, _other: &Self) -> bool {
true
}
}

impl Debug for CmdlineOptionsCallbacks<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CmdlineOptionsCallbacks")
.field("callbacks_count", &self.0.len())
.finish()
}
}

const SBIN_INIT: &str = "/sbin/init";

impl Default for CmdlineOptions {
fn default() -> CmdlineOptions {
impl<'a> Default for CmdlineOptions<'a> {
fn default() -> CmdlineOptions<'a> {
CmdlineOptions {
root: None,
rootfstype: None,
Expand All @@ -28,15 +51,21 @@ impl Default for CmdlineOptions {
nfsroot: None,
init: SBIN_INIT.into(),
cleanup: true,
callbacks: CmdlineOptionsCallbacks::default(),
}
}
}

fn ensure_value<'a>(key: &str, value: Option<&'a str>) -> Result<&'a str> {
pub fn ensure_value<'a>(key: &str, value: Option<&'a str>) -> Result<&'a str> {
value.ok_or(format!("Cmdline option '{key}' must have an argument!").into())
}

fn parse_option(key: &str, value: Option<&str>, options: &mut CmdlineOptions) -> Result<()> {
fn parse_option<'a>(
key: &str,
value: Option<&str>,
options: &mut CmdlineOptions,
callbacks: &mut [Box<CmdlineCallback<'a>>],
) -> Result<()> {
match key {
"root" => options.root = Some(ensure_value(key, value)?.to_string()),
"rootfstype" => options.rootfstype = Some(ensure_value(key, value)?.to_string()),
Expand All @@ -45,7 +74,11 @@ fn parse_option(key: &str, value: Option<&str>, options: &mut CmdlineOptions) ->
"rw" => options.rootfsflags.remove(MsFlags::MS_RDONLY),
"nfsroot" => options.nfsroot = Some(ensure_value(key, value)?.to_string()),
"init" => options.init = ensure_value(key, value)?.into(),
_ => (),
_ => {
for cb in callbacks {
cb(key, value)?
}
}
}
Ok(())
}
Expand Down Expand Up @@ -91,8 +124,24 @@ fn parse_nfsroot(options: &mut CmdlineOptions) -> Result<()> {
Ok(())
}

impl CmdlineOptions {
pub fn from_string(cmdline: &str) -> Result<Self> {
impl<'a> CmdlineOptions<'a> {
pub fn new() -> Self {
Self::default()
}

pub fn new_with_callbacks(callbacks: Vec<Box<CmdlineCallback<'a>>>) -> Self {
Self {
callbacks: CmdlineOptionsCallbacks(callbacks),
..Default::default()
}
}

pub fn from_file(&mut self, path: &str) -> Result<Self> {
let cmdline = read_file(path)?;
self.from_string(&cmdline)
}

pub fn from_string(&mut self, cmdline: &str) -> Result<Self> {
let mut options = Self::default();
let mut have_value = false;
let mut quoted = false;
Expand Down Expand Up @@ -128,6 +177,7 @@ impl CmdlineOptions {
None
},
&mut options,
&mut self.callbacks.0,
)?;
}
key = &cmdline[0..0];
Expand All @@ -150,15 +200,12 @@ impl CmdlineOptions {

Ok(options)
}

pub fn from_file(filename: &str) -> Result<Self> {
let cmdline = read_file(filename)?;
Self::from_string(&cmdline)
}
}

#[cfg(test)]
mod tests {
use std::cell::RefCell;

use super::*;

#[test]
Expand All @@ -171,7 +218,7 @@ mod tests {
..Default::default()
};

let options = CmdlineOptions::from_string(cmdline).expect("failed");
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");

assert_eq!(options, expected);
}
Expand All @@ -189,7 +236,7 @@ mod tests {
..Default::default()
};

let options = CmdlineOptions::from_string(cmdline).expect("failed");
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");

assert_eq!(options, expected);
}
Expand All @@ -206,7 +253,7 @@ mod tests {
..Default::default()
};

let options = CmdlineOptions::from_string(cmdline).expect("failed");
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");

assert_eq!(options, expected);
}
Expand All @@ -226,7 +273,7 @@ mod tests {
..Default::default()
};

let options = CmdlineOptions::from_string(cmdline).expect("failed");
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");

assert_eq!(options, expected);
}
Expand All @@ -241,8 +288,36 @@ mod tests {
..Default::default()
};

let options = CmdlineOptions::from_string(cmdline).expect("failed");
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");

assert_eq!(options, expected);
}

#[test]
fn test_callbacks() {
let cmdline = "root=/dev/mmcblk0p1 rsinit.custom=xyz\n";
let custom_value = RefCell::new(String::new());

let expected = CmdlineOptions {
root: Some("/dev/mmcblk0p1".into()),
..Default::default()
};

let cb = Box::new(|key: &str, value: Option<&str>| {
match key {
"rsinit.custom" => {
*custom_value.borrow_mut() = ensure_value(key, value)?.to_owned();
}
_ => {}
}
Result::Ok(())
});

let options = CmdlineOptions::new_with_callbacks(vec![cb])
.from_string(cmdline)
.expect("failed");

assert_eq!(options, expected);
assert_eq!(&*custom_value.borrow(), "xyz");
}
}
65 changes: 51 additions & 14 deletions src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-only

use std::borrow::Borrow;
use std::env;
use std::env::current_exe;
use std::ffi::CString;
use std::fmt::Write as _;
Expand All @@ -12,6 +11,7 @@ use std::io::Write as _;
use std::os::fd::AsFd;
use std::os::unix::ffi::OsStrExt;
use std::panic::set_hook;
use std::{env, mem};

use log::{debug, Level, LevelFilter, Metadata, Record};
#[cfg(feature = "reboot-on-failure")]
Expand Down Expand Up @@ -86,12 +86,23 @@ fn finalize() {
let _ = reboot(RebootMode::RB_AUTOBOOT);
}

pub struct InitContext {
pub options: CmdlineOptions,
pub struct InitContext<'a> {
pub options: CmdlineOptions<'a>,
callbacks: InitContextCallbacks<'a>,
}

impl InitContext {
pub fn new() -> Result<Self> {
pub type CmdlineCallback<'a> = dyn FnMut(&str, Option<&str>) -> Result<()> + 'a;
pub type InitCallback<'a> = dyn FnMut(&mut CmdlineOptions) -> Result<()> + 'a;

#[derive(Default)]
pub struct InitContextCallbacks<'a> {
pub cmdline_cb: Vec<Box<CmdlineCallback<'a>>>,
pub post_setup_cb: Vec<Box<InitCallback<'a>>>,
pub post_root_mount_cb: Vec<Box<InitCallback<'a>>>,
}

impl<'a> InitContext<'a> {
pub fn new(callbacks: Option<InitContextCallbacks<'a>>) -> Result<Self> {
setup_console()?;

set_hook(Box::new(|panic_info| {
Expand All @@ -101,21 +112,39 @@ impl InitContext {

Ok(Self {
options: CmdlineOptions::default(),
callbacks: callbacks.unwrap_or_default(),
})
}

pub fn setup(self: &mut InitContext) -> Result<()> {
pub fn add_cmdline_cb(self: &mut InitContext<'a>, cmdline_cb: Box<CmdlineCallback<'a>>) {
self.callbacks.cmdline_cb.push(cmdline_cb);
}

pub fn add_post_setup_cb(self: &mut InitContext<'a>, post_setup_cb: Box<InitCallback<'a>>) {
self.callbacks.post_setup_cb.push(post_setup_cb);
}

pub fn add_post_root_mount_cb(
self: &mut InitContext<'a>,
post_root_mount_cb: Box<InitCallback<'a>>,
) {
self.callbacks.post_root_mount_cb.push(post_root_mount_cb);
}

pub fn setup(self: &mut InitContext<'a>) -> Result<()> {
mount_special()?;

setup_log()?;

self.options = CmdlineOptions::from_file("/proc/cmdline")?;
let callbacks = mem::take(&mut self.callbacks.cmdline_cb);

self.options = CmdlineOptions::new_with_callbacks(callbacks).from_file("/proc/cmdline")?;

Ok(())
}

#[cfg(any(feature = "dmverity", feature = "usb9pfs"))]
pub fn prepare_aux(self: &mut InitContext) -> Result<()> {
pub fn prepare_aux(self: &mut InitContext<'a>) -> Result<()> {
#[cfg(feature = "dmverity")]
if prepare_dmverity(&mut self.options)? {
return Ok(());
Expand All @@ -127,7 +156,7 @@ impl InitContext {
Ok(())
}

pub fn switch_root(self: &mut InitContext) -> Result<()> {
pub fn switch_root(self: &mut InitContext<'a>) -> Result<()> {
#[cfg(feature = "systemd")]
mount_systemd(&mut self.options)?;

Expand All @@ -144,7 +173,7 @@ impl InitContext {
Ok(())
}

pub fn mount_root(self: &InitContext) -> Result<()> {
pub fn mount_root(self: &InitContext<'a>) -> Result<()> {
mount_root(
self.options.root.as_deref(),
self.options.rootfstype.as_deref(),
Expand All @@ -154,7 +183,7 @@ impl InitContext {
Ok(())
}

pub fn start_init(self: &InitContext) -> Result<()> {
pub fn start_init(self: &InitContext<'a>) -> Result<()> {
let mut args = Vec::new();
args.push(CString::new(self.options.init.as_str())?);

Expand All @@ -174,28 +203,36 @@ impl InitContext {
Ok(())
}

pub fn finish(self: &mut InitContext) -> Result<()> {
pub fn finish(self: &mut InitContext<'a>) -> Result<()> {
self.switch_root()?;
self.start_init()?;

Ok(())
}

pub fn run(self: &mut InitContext) -> Result<()> {
pub fn run(self: &mut InitContext<'a>) -> Result<()> {
self.setup()?;

for cb in &mut self.callbacks.post_setup_cb {
cb(&mut self.options)?;
}

#[cfg(any(feature = "dmverity", feature = "usb9pfs"))]
self.prepare_aux()?;

self.mount_root()?;

for cb in &mut self.callbacks.post_root_mount_cb {
cb(&mut self.options)?;
}

self.finish()?;

Ok(())
}
}

impl Drop for InitContext {
impl Drop for InitContext<'_> {
fn drop(&mut self) {
finalize();
}
Expand Down
Loading
Loading