See: https://github.com/libguestfs/nbdkit/issues/21
Tested by applying the following patch to the example plugin and running it with verbose enabled: --- a/plugins/rust/examples/ramdisk.rs +++ b/plugins/rust/examples/ramdisk.rs @@ -43,6 +43,12 @@ struct RamDisk { } impl Server for RamDisk { + fn after_fork() -> Result<()> { + // A place to start background threads. + eprintln!("forked"); + Ok(()) + } + fn get_size(&self) -> Result<i64> { Ok(DISK.lock().unwrap().len() as i64) } @@ -76,4 +82,4 @@ impl Server for RamDisk { } } -plugin!(RamDisk {thread_model, write_at}); +plugin!(RamDisk {thread_model, write_at, after_fork}); --- plugins/rust/src/lib.rs | 23 ++++++++++++++++- plugins/rust/tests/common/mod.rs | 1 + plugins/rust/tests/full_featured.rs | 39 +++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/plugins/rust/src/lib.rs b/plugins/rust/src/lib.rs index a5b88e851..fc7b28454 100644 --- a/plugins/rust/src/lib.rs +++ b/plugins/rust/src/lib.rs @@ -143,6 +143,7 @@ mod unreachable { pub(super) fn thread_model() -> Result<ThreadModel> { unreachable!() } } +static mut AFTER_FORK: fn() -> Result<()> = unreachable::config_complete; static mut CONFIG: fn(k: &str, v: &str) -> Result<()> = unreachable::config; static mut CONFIG_COMPLETE: fn() -> Result<()> = unreachable::config_complete; static mut CONFIG_HELP: Vec<u8> = Vec::new(); @@ -315,6 +316,16 @@ impl ExtentHandle { mod ffi { use super::*; + pub(super) extern fn after_fork() -> c_int { + match unsafe { AFTER_FORK() } { + Ok(()) => 0, + Err(e) => { + set_error(e); + -1 + } + } + } + macro_rules! can_method { ( $meth:ident ) => { pub(super) extern fn $meth(h: *mut c_void) -> c_int { @@ -584,6 +595,12 @@ mod ffi { // We want the argument names to show up without underscores in the API docs #[allow(unused_variables)] pub trait Server { + /// This optional callback is called before the server starts serving. + /// + /// It is called after the server forks and changes directory. If + /// a plugin needs to create background threads it should do so here. + fn after_fork() -> Result<()> where Self: Sized { unimplemented!() } + /// Indicates that the client intends to make further accesses to the given /// data region. /// @@ -900,6 +917,7 @@ macro_rules! opt_method { #[doc(hidden)] #[derive(Default)] pub struct Builder { + pub after_fork: bool, pub cache: bool, pub can_cache: bool, pub can_extents: bool, @@ -938,6 +956,7 @@ impl Builder { CONFIG_COMPLETE = S::config_complete; DUMP_PLUGIN = S::dump_plugin; GET_READY = S::get_ready; + AFTER_FORK = S::after_fork; LOAD = S::load; OPEN = S::open; PRECONNECT = S::preconnect; @@ -1021,7 +1040,8 @@ impl Builder { thread_model: opt_method!(self, thread_model), can_fast_zero: opt_method!(self, can_fast_zero), preconnect: opt_method!(self, preconnect), - get_ready: opt_method!(self, get_ready) + get_ready: opt_method!(self, get_ready), + after_fork: opt_method!(self, after_fork) }; // Leak the memory to C. NBDKit will never give it back. Box::into_raw(Box::new(plugin)) @@ -1191,6 +1211,7 @@ pub struct Plugin { pub can_fast_zero: Option<extern fn (h: *mut c_void) -> c_int>, pub preconnect: Option<extern fn(readonly: c_int) -> c_int>, pub get_ready: Option<extern fn() -> c_int>, + pub after_fork: Option<extern fn() -> c_int>, } /// Register your plugin with NBDKit. diff --git a/plugins/rust/tests/common/mod.rs b/plugins/rust/tests/common/mod.rs index de26c89fc..77c987a1f 100644 --- a/plugins/rust/tests/common/mod.rs +++ b/plugins/rust/tests/common/mod.rs @@ -50,6 +50,7 @@ mock!{ pub Server {} #[allow(dead_code)] impl Server for Server { + fn after_fork() -> Result<()> where Self: Sized; fn cache(&self, count: u32, offset: u64) -> Result<()>; fn can_cache(&self) -> Result<CacheFlags>; fn can_extents(&self) -> Result<bool>; diff --git a/plugins/rust/tests/full_featured.rs b/plugins/rust/tests/full_featured.rs index d5f02e064..87a0256e0 100644 --- a/plugins/rust/tests/full_featured.rs +++ b/plugins/rust/tests/full_featured.rs @@ -46,6 +46,7 @@ static mut PLUGIN: Option<*const Plugin> = None; static INIT: Once = Once::new(); plugin!(MockServer{ + after_fork, cache, can_cache, can_extents, @@ -539,6 +540,44 @@ mod get_ready { } } +mod after_fork { + use super::*; + + #[test] + fn einval() { + initialize(); + let _m = MOCK_SERVER_MTX.lock().unwrap(); + + let after_fork_ctx = MockServer::after_fork_context(); + after_fork_ctx.expect() + .returning(|| Err(Error::new(libc::EINVAL, "foo is required"))); + + let pluginp = unsafe { PLUGIN.unwrap()}; + let plugin = unsafe {&*pluginp}; + + let after_fork = plugin.after_fork.as_ref().unwrap(); + assert_eq!( -1, after_fork()); + ERRNO.with(|e| assert_eq!(libc::EINVAL, *e.borrow())); + ERRMSG.with(|e| assert_eq!("foo is required", *e.borrow())); + } + + #[test] + fn ok() { + initialize(); + let _m = MOCK_SERVER_MTX.lock().unwrap(); + + let after_fork_ctx = MockServer::after_fork_context(); + after_fork_ctx.expect() + .returning(|| Ok(())); + + let pluginp = unsafe { PLUGIN.unwrap()}; + let plugin = unsafe {&*pluginp}; + + let after_fork = plugin.after_fork.as_ref().unwrap(); + assert_eq!( 0, after_fork()); + } +} + can_method!(is_rotational, expect_is_rotational); unload_like!(load, load_context); -- 2.39.2 _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs