Skip to content

Commit

Permalink
Move fork out of origin.
Browse files Browse the repository at this point in the history
Move `fork` from origin to c-scape, and convert it back to using
`unsafe extern "C" fn()` hooks instead of safe boxed closures. See
bytecodealliance/rustix#137 for discussion.

Add an at_thread_exit_raw to origin, which allows thread destructors to
be registered without allocating, which is important since Rust will
register TLS destructors while threads are being constructed, before
parking_lot is prepared for the global allocator to take locks.

Update to the latest rustix, and the change from a safe execve to an
unsafe raw execveat. This similarly allows it to avoid allocating, which
is important as it's primarily called in the child of `fork`.

And switch to a branch of parking_lot with changes to allow parking_lot locks
to be used from within global allocator implementations.

These changes allow us to move the `#[global_allocator]` back to the mustang
crate, and restore the wee_alloc allocator option. More generally,
origin and c-scape should in theory now work with any global allocator
implementation.
  • Loading branch information
sunfishcode committed Dec 21, 2021
1 parent d47569c commit aced4d4
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 228 deletions.
40 changes: 40 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::env::var;
use std::io::Write;

fn main() {
// Don't rerun this on changes other than build.rs, as we only depend on
// the rustc version.
println!("cargo:rerun-if-changed=build.rs");

use_feature_or_nothing("thread_local_const_init");
}

fn use_feature_or_nothing(feature: &str) {
if has_feature(feature) {
use_feature(feature);
}
}

fn use_feature(feature: &str) {
println!("cargo:rustc-cfg={}", feature);
}

/// Test whether the rustc at `var("RUSTC")` supports the given feature.
fn has_feature(feature: &str) -> bool {
let out_dir = var("OUT_DIR").unwrap();
let rustc = var("RUSTC").unwrap();

let mut child = std::process::Command::new(rustc)
.arg("--crate-type=rlib") // Don't require `main`.
.arg("--emit=metadata") // Do as little as possible but still parse.
.arg("--out-dir")
.arg(out_dir) // Put the output somewhere inconsequential.
.arg("-") // Read from stdin.
.stdin(std::process::Stdio::piped()) // Stdin is a pipe.
.spawn()
.unwrap();

writeln!(child.stdin.take().unwrap(), "#![feature({})]", feature).unwrap();

child.wait().unwrap().success()
}
6 changes: 3 additions & 3 deletions c-scape/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ edition = "2018"

[dependencies]
libm = "0.2.1"
rustix = { version = "0.29.2", default-features = false }
rustix = { version = "0.30.0", default-features = false }
memoffset = "0.6"
realpath-ext = { version = "0.1.0", default-features = false }
memchr = { version = "2.4.1", default-features = false }
sync-resolve = { version = "0.3.0", optional = true }
origin = { path = "../origin", default-features = false, version = "^0.3.1-alpha.0"}
parking_lot = { version = "0.11.2", optional = true }
origin = { path = "../origin", default-features = false, features = ["parking_lot_core", "raw_dtors", "set_thread_id"], version = "^0.3.1-alpha.0"}
parking_lot = { git = "https://github.com/sunfishcode/parking_lot", branch = "mustang", optional = true }
log = { version = "0.4.14", default-features = false }
# We use the libc crate for C ABI types and constants, but we don't depend on
# the actual platform libc.
Expand Down
73 changes: 73 additions & 0 deletions c-scape/src/at_fork.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use parking_lot::lock_api::RawMutex as _;
use parking_lot::{Mutex, RawMutex};

/// Functions registered with `at_fork`.
static FORK_FUNCS: Mutex<RegisteredForkFuncs> =
Mutex::const_new(RawMutex::INIT, RegisteredForkFuncs::new());

/// A type for holding `fork` callbacks.
#[derive(Default)]
struct RegisteredForkFuncs {
/// Functions called before calling `fork`.
pub(crate) prepare: Vec<unsafe extern "C" fn()>,

/// Functions called after calling `fork`, in the parent.
pub(crate) parent: Vec<unsafe extern "C" fn()>,

/// Functions called after calling `fork`, in the child.
pub(crate) child: Vec<unsafe extern "C" fn()>,
}

impl RegisteredForkFuncs {
pub(crate) const fn new() -> Self {
Self {
prepare: Vec::new(),
parent: Vec::new(),
child: Vec::new(),
}
}
}

/// Register functions to be called when `fork` is called.
///
/// The handlers for each phase are called in the following order:
/// the prepare handlers are called in reverse order of registration;
/// the parent and child handlers are called in the order of registration.
pub(crate) fn at_fork(
prepare_func: Option<unsafe extern "C" fn()>,
parent_func: Option<unsafe extern "C" fn()>,
child_func: Option<unsafe extern "C" fn()>,
) {
let mut funcs = FORK_FUNCS.lock();

// Add the callbacks to the lists.
funcs.prepare.extend(prepare_func);
funcs.parent.extend(parent_func);
funcs.child.extend(child_func);
}

pub(crate) unsafe fn fork() -> rustix::io::Result<Option<rustix::process::Pid>> {
let funcs = FORK_FUNCS.lock();

// Callbacks before calling `fork`.
funcs.prepare.iter().rev().for_each(|func| func());

// Call `fork`.
match rustix::runtime::fork()? {
None => {
// The child's thread record is copied from the parent;
// update it with the child's current-thread-id.
#[cfg(feature = "threads")]
origin::set_current_thread_id_after_a_fork(rustix::thread::gettid());

// Callbacks after calling `fork`, in the child.
funcs.child.iter().for_each(|func| func());
Ok(None)
}
Some(pid) => {
// Callbacks after calling `fork`, in the parent.
funcs.parent.iter().for_each(|func| func());
Ok(Some(pid))
}
}
}
147 changes: 74 additions & 73 deletions c-scape/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![no_builtins] // don't let LLVM optimize our `memcpy` into a `memcpy` call
#![feature(thread_local)] // for `__errno_location`
#![feature(c_variadic)] // for `ioctl` etc.
#![feature(const_fn_fn_ptr_basics)] // for creating an empty `Vec::new` of function pointers in a const fn
#![feature(rustc_private)] // for compiler-builtins
#![feature(untagged_unions)] // for `PthreadMutexT`
#![feature(atomic_mut_ptr)] // for `RawMutex`
Expand All @@ -15,6 +16,8 @@ mod use_libc;

mod error_str;
// Unwinding isn't supported on 32-bit arm yet.
#[cfg(not(target_os = "wasi"))]
mod at_fork;
#[cfg(target_arch = "arm")]
mod unwind;

Expand All @@ -29,7 +32,7 @@ mod unwind;
// the `rustix` APIs directly, which are safer, more ergonomic, and skip this
// whole layer.

use alloc::borrow::{Cow, ToOwned};
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
#[cfg(feature = "sync-resolve")]
Expand Down Expand Up @@ -59,7 +62,7 @@ use parking_lot::lock_api::{RawMutex as _, RawRwLock};
#[cfg(feature = "threads")]
use parking_lot::RawMutex;
use rustix::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
use rustix::ffi::{ZStr, ZString};
use rustix::ffi::ZStr;
use rustix::fs::{cwd, openat, AtFlags, FdFlags, Mode, OFlags};
#[cfg(any(target_os = "android", target_os = "linux"))]
use rustix::io::EventfdFlags;
Expand Down Expand Up @@ -2321,14 +2324,14 @@ unsafe extern "C" fn setuid() {
#[no_mangle]
unsafe extern "C" fn getpid() -> c_int {
libc!(libc::getpid());
rustix::process::getpid().as_raw() as _
rustix::process::getpid().as_raw_nonzero().get() as _
}

#[cfg(not(target_os = "wasi"))]
#[no_mangle]
unsafe extern "C" fn getppid() -> c_int {
libc!(libc::getppid());
rustix::process::getppid().as_raw() as _
rustix::process::Pid::as_raw(rustix::process::getppid()) as _
}

#[cfg(not(target_os = "wasi"))]
Expand Down Expand Up @@ -2625,80 +2628,74 @@ unsafe extern "C" fn nanosleep(req: *const libc::timespec, rem: *mut libc::times

// exec

#[cfg(not(target_os = "wasi"))]
unsafe fn null_terminated_array<'a>(list: *const *const c_char) -> Vec<&'a ZStr> {
let mut len = 0;
while !(*list.wrapping_add(len)).is_null() {
len += 1;
}
let list = core::slice::from_raw_parts(list, len);
list.into_iter()
.copied()
.map(|ptr| ZStr::from_ptr(ptr.cast()))
.collect()
}
#[cfg(any(target_os = "android", target_os = "linux"))]
#[no_mangle]
unsafe extern "C" fn execvp(file: *const c_char, args: *const *const c_char) -> c_int {
libc!(libc::execvp(file, args));

#[cfg(not(target_os = "wasi"))]
fn file_exists(cwd: &rustix::fd::BorrowedFd<'_>, path: &ZStr) -> bool {
rustix::fs::accessat(
cwd,
path,
rustix::fs::Access::EXISTS | rustix::fs::Access::EXEC_OK,
rustix::fs::AtFlags::empty(),
)
.is_ok()
}
let cwd = rustix::fs::cwd();

#[cfg(not(target_os = "wasi"))]
fn resolve_binary<'a>(file: &'a ZStr, envs: &[&ZStr]) -> rustix::io::Result<Cow<'a, ZStr>> {
let file_bytes = file.to_bytes();
if file_bytes.contains(&b'/') {
Ok(Cow::Borrowed(file))
let file = ZStr::from_ptr(file);
if file.to_bytes().contains(&b'/') {
let err =
rustix::runtime::execveat(&cwd, file, args.cast(), environ.cast(), AtFlags::empty());
set_errno(Errno(err.raw_os_error()));
return -1;
}

let path = _getenv(rustix::zstr!("PATH"));
let path = if path.is_null() {
rustix::zstr!("/bin:/usr/bin")
} else {
let cwd = rustix::fs::cwd();
match envs
.into_iter()
.copied()
.map(ZStr::to_bytes)
.find(|env| env.starts_with(b"PATH="))
.map(|env| env.split_at(b"PATH=".len()).1)
.unwrap_or(b"/bin:/usr/bin")
.split(|&byte| byte == b':')
.filter_map(|path_bytes| {
let mut full_path = path_bytes.to_vec();
full_path.push(b'/');
full_path.extend_from_slice(file_bytes);
ZString::new(full_path).ok()
})
.find(|path| file_exists(&cwd, &path))
{
Some(path) => Ok(Cow::Owned(path)),
None => Err(rustix::io::Error::ACCES),
ZStr::from_ptr(path)
};

let mut access_error = false;
for dir in path.to_bytes().split(|byte| *byte == b':') {
// Open the directory and use `execveat` to avoid needing to
// dynamically allocate a combined path string ourselves.
match openat(
&cwd,
dir,
OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOCTTY,
Mode::empty(),
)
.and_then::<(), _>(|dirfd| {
Err(rustix::runtime::execveat(
&dirfd,
file,
args.cast(),
environ.cast(),
AtFlags::empty(),
))
}) {
Ok(()) => (),
Err(rustix::io::Error::ACCES) => access_error = true,
Err(rustix::io::Error::NOENT) | Err(rustix::io::Error::NOTDIR) => {}
Err(rustix::io::Error::NOSYS) => {
todo!("emulate execveat on older Linux kernels using /proc/self/fd and execve")
}
Err(err) => {
set_errno(Errno(err.raw_os_error()));
return -1;
}
}
}
}

#[cfg(not(target_os = "wasi"))]
#[no_mangle]
unsafe extern "C" fn execvp(file: *const c_char, args: *const *const c_char) -> c_int {
libc!(libc::execvp(file, args));
let args = null_terminated_array(args);
let envs = null_terminated_array(environ.cast::<_>());
match convert_res(
resolve_binary(ZStr::from_ptr(file.cast()), &envs)
.and_then(|path| rustix::runtime::execve(path, &args, &envs)),
) {
Some(_) => 0,
None => -1,
}
set_errno(Errno(if access_error {
libc::EACCES
} else {
libc::ENOENT
}));
-1
}

#[cfg(not(target_os = "wasi"))]
#[no_mangle]
unsafe extern "C" fn fork() -> c_int {
libc!(libc::fork());
match convert_res(origin::fork()) {
Some(Some(pid)) => pid.as_raw() as c_int,
match convert_res(at_fork::fork()) {
Some(Some(pid)) => pid.as_raw_nonzero().get() as c_int,
Some(None) => 0,
None => -1,
}
Expand All @@ -2714,7 +2711,7 @@ unsafe extern "C" fn __register_atfork(
_dso_handle: *mut c_void,
) -> c_int {
//libc!(libc::__register_atfork(prepare, parent, child, _dso_handle));
origin::at_fork(prepare, parent, child);
at_fork::at_fork(prepare, parent, child);
0
}

Expand All @@ -2734,7 +2731,7 @@ unsafe extern "C" fn waitpid(pid: c_int, status: *mut c_int, options: c_int) ->
match pid {
-1 => match convert_res(rustix::process::wait(options)) {
Some(Some((new_pid, new_status))) => {
ret_pid = new_pid.as_raw() as c_int;
ret_pid = new_pid.as_raw_nonzero().get() as c_int;
ret_status = new_status.as_raw() as c_int;
}
Some(None) => return 0,
Expand All @@ -2746,7 +2743,7 @@ unsafe extern "C" fn waitpid(pid: c_int, status: *mut c_int, options: c_int) ->
)) {
Some(Some(new_status)) => {
ret_pid = if pid == 0 {
rustix::process::getpid().as_raw() as c_int
rustix::process::getpid().as_raw_nonzero().get() as c_int
} else {
pid
};
Expand Down Expand Up @@ -3354,6 +3351,10 @@ unsafe extern "C" fn ynf(x: i32, y: f32) -> f32 {
unsafe extern "C" fn getenv(key: *const c_char) -> *mut c_char {
libc!(libc::getenv(key));
let key = ZStr::from_ptr(key.cast());
_getenv(key)
}

unsafe fn _getenv(key: &ZStr) -> *mut c_char {
let mut ptr = environ;
loop {
let env = *ptr;
Expand Down Expand Up @@ -3440,7 +3441,8 @@ unsafe impl parking_lot::lock_api::GetThreadId for GetThreadId {
const INIT: Self = Self;

fn nonzero_thread_id(&self) -> core::num::NonZeroUsize {
(origin::current_thread_id().as_raw() as usize)
origin::current_thread_id()
.as_raw_nonzero()
.try_into()
.unwrap()
}
Expand Down Expand Up @@ -4154,7 +4156,7 @@ unsafe extern "C" fn pthread_atfork(
child: Option<unsafe extern "C" fn()>,
) -> c_int {
libc!(libc::pthread_atfork(prepare, parent, child));
origin::at_fork(prepare, parent, child);
at_fork::at_fork(prepare, parent, child);
0
}

Expand All @@ -4166,8 +4168,7 @@ unsafe extern "C" fn __cxa_thread_atexit_impl(
_dso_symbol: *mut c_void,
) -> c_int {
// TODO: libc!(libc::__cxa_thread_atexit_impl(func, obj, _dso_symbol));
let obj = obj as usize;
origin::at_thread_exit(Box::new(move || func(obj as *mut c_void)));
origin::at_thread_exit_raw(func, obj);
0
}

Expand Down

0 comments on commit aced4d4

Please sign in to comment.