Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FreeBSD's rfork wrapper proposal #2121

Merged
merged 1 commit into from Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/2121.added.md
@@ -0,0 +1 @@
Added rfork support for FreeBSD in `unistd`
48 changes: 48 additions & 0 deletions src/unistd.rs
Expand Up @@ -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! {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use a feature! block here, why not put them all in the block?

feature! {
#![feature = "process"]

#[cfg(target_os = "freebsd")]
libc_bitflags! {
}


#[cfg(target_os = "freebsd")]
pub unsafe fn rfork(flags: RforkFlags) -> Result<ForkResult> {
}

}

#![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<ForkResult> {
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()
Expand Down
31 changes: 31 additions & 0 deletions test/test_unistd.rs
Expand Up @@ -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
Expand Down