From 3a20eef318eba8d652c0c9e8dde304869f04d14c Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Mon, 4 Dec 2023 06:24:21 +0000 Subject: [PATCH] FreeBSD's rfork wrapper proposal (#2121) --- changelog/2121.added.md | 1 + src/unistd.rs | 48 +++++++++++++++++++++++++++++++++++++++++ test/test_unistd.rs | 31 ++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 changelog/2121.added.md diff --git a/changelog/2121.added.md b/changelog/2121.added.md new file mode 100644 index 0000000000..687c373947 --- /dev/null +++ b/changelog/2121.added.md @@ -0,0 +1 @@ +Added rfork support for FreeBSD in `unistd` diff --git a/src/unistd.rs b/src/unistd.rs index abdc9bdbaf..a9d8b0aaf2 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -2898,6 +2898,54 @@ mod getres { } } +#[cfg(feature = "process")] +#[cfg(target_os = "freebsd")] +libc_bitflags! { + /// Flags for [`rfork`] + /// + /// subset of flags supported by FreeBSD 12.x and onwards + /// with a safe outcome, thus as `RFMEM` can possibly lead to undefined behavior, + /// it is not in the list. And `rfork_thread` is deprecated. + pub struct RforkFlags: libc::c_int { + /// creates a new process. + RFPROC; + /// the child process will detach from the parent. + /// however, no status will be emitted at child's exit. + RFNOWAIT; + /// the file descriptor's table will be copied + RFFDG; + /// a new file descriptor's table will be created + RFCFDG; + /// force sharing the sigacts structure between + /// the child and the parent. + RFSIGSHARE; + /// enables kernel thread support. + RFTHREAD; + /// sets a status to emit at child's exit. + RFTSIGZMB; + /// linux's behavior compatibility setting. + /// emits SIGUSR1 as opposed to SIGCHLD upon child's exit. + RFLINUXTHPN; + } +} + +feature! { +#![feature = "process"] +#[cfg(target_os = "freebsd")] +/// rfork can be used to have a tigher control about which resources child +/// and parent process will be sharing, file descriptors, address spaces +/// and child exit's behavior. +pub unsafe fn rfork(flags: RforkFlags) -> Result { + use ForkResult::*; + let res = unsafe { libc::rfork(flags.bits()) }; + + Errno::result(res).map(|res| match res { + 0 => Child, + res => Parent { child: Pid(res) }, + }) +} +} + #[cfg(feature = "fs")] libc_bitflags! { /// Options for access() diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 5a58585e7d..0a841df060 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -66,6 +66,37 @@ fn test_fork_and_waitpid() { } } +#[test] +#[cfg(target_os = "freebsd")] +fn test_rfork_and_waitpid() { + let _m = crate::FORK_MTX.lock(); + + // Safe: Child only calls `_exit`, which is signal-safe + match unsafe { rfork(RforkFlags::RFPROC | RforkFlags::RFTHREAD) } + .expect("Error: Rfork Failed") + { + Child => unsafe { _exit(0) }, + Parent { child } => { + // assert that child was created and pid > 0 + let child_raw: ::libc::pid_t = child.into(); + assert!(child_raw > 0); + let wait_status = waitpid(child, None); + match wait_status { + // assert that waitpid returned correct status and the pid is the one of the child + Ok(WaitStatus::Exited(pid_t, _)) => assert_eq!(pid_t, child), + + // panic, must never happen + s @ Ok(_) => { + panic!("Child exited {s:?}, should never happen") + } + + // panic, waitpid should never fail + Err(s) => panic!("Error: waitpid returned Err({s:?}"), + } + } + } +} + #[test] fn test_wait() { // Grab FORK_MTX so wait doesn't reap a different test's child process