Skip to content

Commit 8614c2c

Browse files
committed
wip! add callback hooks for custom rsinit derivates
Signed-off-by: Stefan Kerkmann <[email protected]>
1 parent 47e1822 commit 8614c2c

File tree

3 files changed

+306
-37
lines changed

3 files changed

+306
-37
lines changed

src/cmdline.rs

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,48 @@
11
// SPDX-FileCopyrightText: 2024 The rsinit Authors
22
// SPDX-License-Identifier: GPL-2.0-only
33

4+
use std::fmt::Debug;
5+
46
use nix::mount::MsFlags;
57

6-
use crate::util::{read_file, Result};
8+
use crate::{
9+
init::CmdlineCallback,
10+
util::{read_file, Result},
11+
};
712

813
#[derive(Debug, PartialEq)]
9-
pub struct CmdlineOptions {
14+
pub struct CmdlineOptions<'a> {
1015
pub root: Option<String>,
1116
pub rootfstype: Option<String>,
1217
pub rootflags: Option<String>,
1318
pub rootfsflags: MsFlags,
1419
pub nfsroot: Option<String>,
1520
pub init: String,
1621
pub cleanup: bool,
22+
callbacks: CmdlineOptionsCallbacks<'a>,
23+
}
24+
25+
#[derive(Default)]
26+
struct CmdlineOptionsCallbacks<'a>(Vec<Box<CmdlineCallback<'a>>>);
27+
28+
impl PartialEq for CmdlineOptionsCallbacks<'_> {
29+
fn eq(&self, _other: &Self) -> bool {
30+
true
31+
}
32+
}
33+
34+
impl Debug for CmdlineOptionsCallbacks<'_> {
35+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36+
f.debug_struct("CmdlineOptionsCallbacks")
37+
.field("callbacks_count", &self.0.len())
38+
.finish()
39+
}
1740
}
1841

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

21-
impl Default for CmdlineOptions {
22-
fn default() -> CmdlineOptions {
44+
impl<'a> Default for CmdlineOptions<'a> {
45+
fn default() -> CmdlineOptions<'a> {
2346
CmdlineOptions {
2447
root: None,
2548
rootfstype: None,
@@ -28,15 +51,21 @@ impl Default for CmdlineOptions {
2851
nfsroot: None,
2952
init: SBIN_INIT.into(),
3053
cleanup: true,
54+
callbacks: CmdlineOptionsCallbacks::default(),
3155
}
3256
}
3357
}
3458

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

39-
fn parse_option(key: &str, value: Option<&str>, options: &mut CmdlineOptions) -> Result<()> {
63+
fn parse_option<'a>(
64+
key: &str,
65+
value: Option<&str>,
66+
options: &mut CmdlineOptions,
67+
callbacks: &mut [Box<CmdlineCallback<'a>>],
68+
) -> Result<()> {
4069
match key {
4170
"root" => options.root = Some(ensure_value(key, value)?.to_string()),
4271
"rootfstype" => options.rootfstype = Some(ensure_value(key, value)?.to_string()),
@@ -45,7 +74,11 @@ fn parse_option(key: &str, value: Option<&str>, options: &mut CmdlineOptions) ->
4574
"rw" => options.rootfsflags.remove(MsFlags::MS_RDONLY),
4675
"nfsroot" => options.nfsroot = Some(ensure_value(key, value)?.to_string()),
4776
"init" => options.init = ensure_value(key, value)?.into(),
48-
_ => (),
77+
_ => {
78+
for cb in callbacks {
79+
cb(key, value)?
80+
}
81+
}
4982
}
5083
Ok(())
5184
}
@@ -91,8 +124,24 @@ fn parse_nfsroot(options: &mut CmdlineOptions) -> Result<()> {
91124
Ok(())
92125
}
93126

94-
impl CmdlineOptions {
95-
pub fn from_string(cmdline: &str) -> Result<Self> {
127+
impl<'a> CmdlineOptions<'a> {
128+
pub fn new() -> Self {
129+
Self::default()
130+
}
131+
132+
pub fn new_with_callbacks(callbacks: Vec<Box<CmdlineCallback<'a>>>) -> Self {
133+
Self {
134+
callbacks: CmdlineOptionsCallbacks(callbacks),
135+
..Default::default()
136+
}
137+
}
138+
139+
pub fn from_file(&mut self, path: &str) -> Result<Self> {
140+
let cmdline = read_file(path)?;
141+
self.from_string(&cmdline)
142+
}
143+
144+
pub fn from_string(&mut self, cmdline: &str) -> Result<Self> {
96145
let mut options = Self::default();
97146
let mut have_value = false;
98147
let mut quoted = false;
@@ -128,6 +177,7 @@ impl CmdlineOptions {
128177
None
129178
},
130179
&mut options,
180+
&mut self.callbacks.0,
131181
)?;
132182
}
133183
key = &cmdline[0..0];
@@ -150,15 +200,12 @@ impl CmdlineOptions {
150200

151201
Ok(options)
152202
}
153-
154-
pub fn from_file(filename: &str) -> Result<Self> {
155-
let cmdline = read_file(filename)?;
156-
Self::from_string(&cmdline)
157-
}
158203
}
159204

160205
#[cfg(test)]
161206
mod tests {
207+
use std::cell::RefCell;
208+
162209
use super::*;
163210

164211
#[test]
@@ -171,7 +218,7 @@ mod tests {
171218
..Default::default()
172219
};
173220

174-
let options = CmdlineOptions::from_string(cmdline).expect("failed");
221+
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");
175222

176223
assert_eq!(options, expected);
177224
}
@@ -189,7 +236,7 @@ mod tests {
189236
..Default::default()
190237
};
191238

192-
let options = CmdlineOptions::from_string(cmdline).expect("failed");
239+
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");
193240

194241
assert_eq!(options, expected);
195242
}
@@ -206,7 +253,7 @@ mod tests {
206253
..Default::default()
207254
};
208255

209-
let options = CmdlineOptions::from_string(cmdline).expect("failed");
256+
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");
210257

211258
assert_eq!(options, expected);
212259
}
@@ -226,7 +273,7 @@ mod tests {
226273
..Default::default()
227274
};
228275

229-
let options = CmdlineOptions::from_string(cmdline).expect("failed");
276+
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");
230277

231278
assert_eq!(options, expected);
232279
}
@@ -241,8 +288,36 @@ mod tests {
241288
..Default::default()
242289
};
243290

244-
let options = CmdlineOptions::from_string(cmdline).expect("failed");
291+
let options = CmdlineOptions::new().from_string(cmdline).expect("failed");
292+
293+
assert_eq!(options, expected);
294+
}
295+
296+
#[test]
297+
fn test_callbacks() {
298+
let cmdline = "root=/dev/mmcblk0p1 rsinit.custom=xyz\n";
299+
let custom_value = RefCell::new(String::new());
300+
301+
let expected = CmdlineOptions {
302+
root: Some("/dev/mmcblk0p1".into()),
303+
..Default::default()
304+
};
305+
306+
let cb = Box::new(|key: &str, value: Option<&str>| {
307+
match key {
308+
"rsinit.custom" => {
309+
*custom_value.borrow_mut() = ensure_value(key, value)?.to_owned();
310+
}
311+
_ => {}
312+
}
313+
Result::Ok(())
314+
});
315+
316+
let options = CmdlineOptions::new_with_callbacks(vec![cb])
317+
.from_string(cmdline)
318+
.expect("failed");
245319

246320
assert_eq!(options, expected);
321+
assert_eq!(&*custom_value.borrow(), "xyz");
247322
}
248323
}

src/init.rs

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: GPL-2.0-only
33

44
use std::borrow::Borrow;
5-
use std::env;
65
use std::env::current_exe;
76
use std::ffi::CString;
87
use std::fmt::Write as _;
@@ -12,6 +11,7 @@ use std::io::Write as _;
1211
use std::os::fd::AsFd;
1312
use std::os::unix::ffi::OsStrExt;
1413
use std::panic::set_hook;
14+
use std::{env, mem};
1515

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

89-
pub struct InitContext {
90-
pub options: CmdlineOptions,
89+
pub struct InitContext<'a> {
90+
pub options: CmdlineOptions<'a>,
91+
callbacks: InitContextCallbacks<'a>,
9192
}
9293

93-
impl InitContext {
94-
pub fn new() -> Result<Self> {
94+
pub type CmdlineCallback<'a> = dyn FnMut(&str, Option<&str>) -> Result<()> + 'a;
95+
pub type InitCallback<'a> = dyn FnMut(&mut CmdlineOptions) -> Result<()> + 'a;
96+
97+
#[derive(Default)]
98+
pub struct InitContextCallbacks<'a> {
99+
pub cmdline_cb: Vec<Box<CmdlineCallback<'a>>>,
100+
pub post_setup_cb: Vec<Box<InitCallback<'a>>>,
101+
pub post_root_mount_cb: Vec<Box<InitCallback<'a>>>,
102+
}
103+
104+
impl<'a> InitContext<'a> {
105+
pub fn new(callbacks: Option<InitContextCallbacks<'a>>) -> Result<Self> {
95106
setup_console()?;
96107

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

102113
Ok(Self {
103114
options: CmdlineOptions::default(),
115+
callbacks: callbacks.unwrap_or_default(),
104116
})
105117
}
106118

107-
pub fn setup(self: &mut InitContext) -> Result<()> {
119+
pub fn add_cmdline_cb(self: &mut InitContext<'a>, cmdline_cb: Box<CmdlineCallback<'a>>) {
120+
self.callbacks.cmdline_cb.push(cmdline_cb);
121+
}
122+
123+
pub fn add_post_setup_cb(self: &mut InitContext<'a>, post_setup_cb: Box<InitCallback<'a>>) {
124+
self.callbacks.post_setup_cb.push(post_setup_cb);
125+
}
126+
127+
pub fn add_post_root_mount_cb(
128+
self: &mut InitContext<'a>,
129+
post_root_mount_cb: Box<InitCallback<'a>>,
130+
) {
131+
self.callbacks.post_root_mount_cb.push(post_root_mount_cb);
132+
}
133+
134+
pub fn setup(self: &mut InitContext<'a>) -> Result<()> {
108135
mount_special()?;
109136

110137
setup_log()?;
111138

112-
self.options = CmdlineOptions::from_file("/proc/cmdline")?;
139+
let callbacks = mem::take(&mut self.callbacks.cmdline_cb);
140+
141+
self.options = CmdlineOptions::new_with_callbacks(callbacks).from_file("/proc/cmdline")?;
113142

114143
Ok(())
115144
}
116145

117146
#[cfg(any(feature = "dmverity", feature = "usb9pfs"))]
118-
pub fn prepare_aux(self: &mut InitContext) -> Result<()> {
147+
pub fn prepare_aux(self: &mut InitContext<'a>) -> Result<()> {
119148
#[cfg(feature = "dmverity")]
120149
if prepare_dmverity(&mut self.options)? {
121150
return Ok(());
@@ -127,7 +156,7 @@ impl InitContext {
127156
Ok(())
128157
}
129158

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

@@ -144,7 +173,7 @@ impl InitContext {
144173
Ok(())
145174
}
146175

147-
pub fn mount_root(self: &InitContext) -> Result<()> {
176+
pub fn mount_root(self: &InitContext<'a>) -> Result<()> {
148177
mount_root(
149178
self.options.root.as_deref(),
150179
self.options.rootfstype.as_deref(),
@@ -154,7 +183,7 @@ impl InitContext {
154183
Ok(())
155184
}
156185

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

@@ -174,28 +203,36 @@ impl InitContext {
174203
Ok(())
175204
}
176205

177-
pub fn finish(self: &mut InitContext) -> Result<()> {
206+
pub fn finish(self: &mut InitContext<'a>) -> Result<()> {
178207
self.switch_root()?;
179208
self.start_init()?;
180209

181210
Ok(())
182211
}
183212

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

216+
for cb in &mut self.callbacks.post_setup_cb {
217+
cb(&mut self.options)?;
218+
}
219+
187220
#[cfg(any(feature = "dmverity", feature = "usb9pfs"))]
188221
self.prepare_aux()?;
189222

190223
self.mount_root()?;
191224

225+
for cb in &mut self.callbacks.post_root_mount_cb {
226+
cb(&mut self.options)?;
227+
}
228+
192229
self.finish()?;
193230

194231
Ok(())
195232
}
196233
}
197234

198-
impl Drop for InitContext {
235+
impl Drop for InitContext<'_> {
199236
fn drop(&mut self) {
200237
finalize();
201238
}

0 commit comments

Comments
 (0)