#[cfg(any(target_os = "linux", target_os = "android"))]
use core::ffi::CStr;
use core::ffi::c_char;
#[cfg(target_os = "macos")]
use core::ffi::{c_int, c_void};
#[cfg(unix)]
use core::sync::atomic::Ordering;
#[cfg(target_os = "macos")]
use bun_core::Output;
use bun_sys::{self, Fd, FdExt as _};
#[cfg(not(windows))]
use crate::posix_spawn::posix_spawn;
#[cfg(unix)]
use posix_spawn::{Actions as PosixSpawnActions, Attr as PosixSpawnAttr};
#[cfg(unix)]
use crate::{Argv, Envp};
#[cfg(unix)]
pub type PidT = libc::pid_t;
#[cfg(windows)]
pub type PidT = bun_libuv_sys::uv_pid_t;
#[cfg(unix)]
pub type FdT = libc::c_int;
#[cfg(not(unix))]
pub type FdT = i32;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub type PidFdType = FdT;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub type PidFdType = ();
#[derive(Default, Clone, Copy)]
pub struct WinTimeval {
pub sec: i64,
pub usec: i64,
}
#[derive(Default, Clone, Copy)]
pub struct WinRusage {
pub utime: WinTimeval,
pub stime: WinTimeval,
pub maxrss: u64,
pub inblock: u64,
pub oublock: u64,
}
pub type IoCounters = bun_windows_sys::IO_COUNTERS;
#[cfg(windows)]
unsafe extern "system" {
fn GetProcessIoCounters(
handle: bun_sys::windows::HANDLE,
counters: *mut IoCounters,
) -> core::ffi::c_int;
}
#[cfg(windows)]
pub fn uv_getrusage(process: &mut bun_libuv_sys::uv_process_t) -> WinRusage {
use core::ffi::c_void;
let mut usage_info = Rusage::default();
let process_pid: *mut c_void = process.process_handle;
type WinTime = bun_sys::windows::FILETIME;
let mut starttime: WinTime = unsafe { bun_core::ffi::zeroed_unchecked() };
let mut exittime: WinTime = unsafe { bun_core::ffi::zeroed_unchecked() };
let mut kerneltime: WinTime = unsafe { bun_core::ffi::zeroed_unchecked() };
let mut usertime: WinTime = unsafe { bun_core::ffi::zeroed_unchecked() };
if unsafe {
bun_sys::windows::GetProcessTimes(
process_pid,
&mut starttime,
&mut exittime,
&mut kerneltime,
&mut usertime,
)
} != 0
{
let mut temp: u64 =
((kerneltime.dwHighDateTime as u64) << 32) | kerneltime.dwLowDateTime as u64;
if temp > 0 {
usage_info.stime.sec = (temp / 10_000_000) as i64;
usage_info.stime.usec = ((temp % 10_000_000) / 10) as i64;
}
temp = ((usertime.dwHighDateTime as u64) << 32) | usertime.dwLowDateTime as u64;
if temp > 0 {
usage_info.utime.sec = (temp / 10_000_000) as i64;
usage_info.utime.usec = ((temp % 10_000_000) / 10) as i64;
}
}
let mut counters = IoCounters::default();
let _ = unsafe { GetProcessIoCounters(process_pid, &mut counters) };
usage_info.inblock = counters.ReadOperationCount;
usage_info.oublock = counters.WriteOperationCount;
let Ok(memory) = bun_sys::windows::GetProcessMemoryInfo(process_pid) else {
return usage_info;
};
usage_info.maxrss = (memory.PeakWorkingSetSize / 1024) as u64;
usage_info
}
#[cfg(windows)]
pub type Rusage = WinRusage;
#[cfg(unix)]
pub type Rusage = libc::rusage;
unsafe impl bun_core::ffi::Zeroable for WinRusage {}
#[inline]
pub fn rusage_zeroed() -> Rusage {
bun_core::ffi::zeroed()
}
pub trait RusageFields {
fn utime_sec(&self) -> i64;
fn utime_usec(&self) -> i64;
fn stime_sec(&self) -> i64;
fn stime_usec(&self) -> i64;
fn maxrss_(&self) -> f64;
fn ixrss_(&self) -> f64;
fn nswap_(&self) -> f64;
fn inblock_(&self) -> f64;
fn oublock_(&self) -> f64;
fn msgsnd_(&self) -> f64;
fn msgrcv_(&self) -> f64;
fn nsignals_(&self) -> f64;
fn nvcsw_(&self) -> f64;
fn nivcsw_(&self) -> f64;
}
#[cfg(unix)]
impl RusageFields for libc::rusage {
#[inline]
fn utime_sec(&self) -> i64 {
self.ru_utime.tv_sec
}
#[inline]
fn utime_usec(&self) -> i64 {
self.ru_utime.tv_usec as i64
}
#[inline]
fn stime_sec(&self) -> i64 {
self.ru_stime.tv_sec
}
#[inline]
fn stime_usec(&self) -> i64 {
self.ru_stime.tv_usec as i64
}
#[inline]
fn maxrss_(&self) -> f64 {
self.ru_maxrss as f64
}
#[inline]
fn ixrss_(&self) -> f64 {
self.ru_ixrss as f64
}
#[inline]
fn nswap_(&self) -> f64 {
self.ru_nswap as f64
}
#[inline]
fn inblock_(&self) -> f64 {
self.ru_inblock as f64
}
#[inline]
fn oublock_(&self) -> f64 {
self.ru_oublock as f64
}
#[inline]
fn msgsnd_(&self) -> f64 {
self.ru_msgsnd as f64
}
#[inline]
fn msgrcv_(&self) -> f64 {
self.ru_msgrcv as f64
}
#[inline]
fn nsignals_(&self) -> f64 {
self.ru_nsignals as f64
}
#[inline]
fn nvcsw_(&self) -> f64 {
self.ru_nvcsw as f64
}
#[inline]
fn nivcsw_(&self) -> f64 {
self.ru_nivcsw as f64
}
}
impl RusageFields for WinRusage {
#[inline]
fn utime_sec(&self) -> i64 {
self.utime.sec
}
#[inline]
fn utime_usec(&self) -> i64 {
self.utime.usec
}
#[inline]
fn stime_sec(&self) -> i64 {
self.stime.sec
}
#[inline]
fn stime_usec(&self) -> i64 {
self.stime.usec
}
#[inline]
fn maxrss_(&self) -> f64 {
self.maxrss as f64
}
#[inline]
fn ixrss_(&self) -> f64 {
0.0
}
#[inline]
fn nswap_(&self) -> f64 {
0.0
}
#[inline]
fn inblock_(&self) -> f64 {
self.inblock as f64
}
#[inline]
fn oublock_(&self) -> f64 {
self.oublock as f64
}
#[inline]
fn msgsnd_(&self) -> f64 {
0.0
}
#[inline]
fn msgrcv_(&self) -> f64 {
0.0
}
#[inline]
fn nsignals_(&self) -> f64 {
0.0
}
#[inline]
fn nvcsw_(&self) -> f64 {
0.0
}
#[inline]
fn nivcsw_(&self) -> f64 {
0.0
}
}
pub struct PosixSpawnOptions {
pub stdin: PosixStdio,
pub stdout: PosixStdio,
pub stderr: PosixStdio,
pub ipc: Option<Fd>,
pub extra_fds: Box<[PosixStdio]>,
pub cwd: Box<[u8]>,
pub detached: bool,
pub windows: (),
pub argv0: Option<*const c_char>,
pub stream: bool,
pub sync: bool,
pub can_block_entire_thread_to_reduce_cpu_usage_in_fast_path: bool,
pub use_execve_on_macos: bool,
pub no_sigpipe: bool,
pub new_process_group: bool,
pub pty_slave_fd: i32,
pub pseudoconsole: (),
pub linux_pdeathsig: Option<u8>,
}
impl Default for PosixSpawnOptions {
fn default() -> Self {
Self {
stdin: PosixStdio::Inherit,
stdout: PosixStdio::Inherit,
stderr: PosixStdio::Inherit,
ipc: None,
extra_fds: Box::default(),
cwd: Box::default(),
detached: false,
windows: (),
argv0: None,
stream: true,
sync: false,
can_block_entire_thread_to_reduce_cpu_usage_in_fast_path: false,
use_execve_on_macos: false,
no_sigpipe: true,
new_process_group: false,
pty_slave_fd: -1,
pseudoconsole: (),
linux_pdeathsig: None,
}
}
}
impl PosixSpawnOptions {
#[inline]
pub fn deinit(&mut self) {}
}
#[derive(enumset::EnumSetType, Debug, strum::IntoStaticStr)]
#[enumset(repr = "u8")]
pub enum StdioKind {
Stdin,
Stdout,
Stderr,
}
impl StdioKind {
#[inline]
pub fn to_fd(self) -> Fd {
match self {
StdioKind::Stdin => Fd::stdin(),
StdioKind::Stdout => Fd::stdout(),
StdioKind::Stderr => Fd::stderr(),
}
}
}
#[derive(Clone, Copy)]
pub struct Dup2 {
pub out: StdioKind,
pub to: StdioKind,
}
pub enum PosixStdio {
Path(Box<[u8]>),
Inherit,
Ignore,
Buffer,
Ipc,
Pipe(Fd),
Dup2(Dup2),
}
impl PosixStdio {
#[inline]
pub fn inherit() -> Self {
PosixStdio::Inherit
}
#[inline]
pub fn ignore() -> Self {
PosixStdio::Ignore
}
#[inline]
pub fn buffer() -> Self {
PosixStdio::Buffer
}
}
#[derive(Default)]
pub struct PosixSpawnResult {
pub pid: PidT,
pub pidfd: Option<PidFdType>,
pub stdin: Option<Fd>,
pub stdout: Option<Fd>,
pub stderr: Option<Fd>,
pub ipc: Option<Fd>,
pub extra_pipes: Vec<ExtraPipe>,
pub memfds: [bool; 3],
pub has_exited: bool,
}
pub enum ExtraPipe {
OwnedFd(Fd),
UnownedFd(Fd),
Unavailable,
}
impl ExtraPipe {
pub fn fd(&self) -> Fd {
match self {
ExtraPipe::OwnedFd(f) | ExtraPipe::UnownedFd(f) => *f,
ExtraPipe::Unavailable => Fd::INVALID,
}
}
}
impl PosixSpawnResult {
pub fn close(&mut self) {
for item in self.extra_pipes.iter() {
match item {
ExtraPipe::OwnedFd(f) => f.close(),
ExtraPipe::UnownedFd(_) | ExtraPipe::Unavailable => {}
}
}
self.extra_pipes.clear();
self.extra_pipes.shrink_to_fit();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn pidfd_flags_for_linux() -> u32 {
bun_sys::O::NONBLOCK as u32
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn pifd_from_pid(&mut self) -> bun_sys::Result<PidFdType> {
if crate::waiter_thread_flag::get() {
return Err(bun_sys::Error::from_code(
bun_sys::E::ENOSYS,
bun_sys::Tag::pidfd_open,
));
}
let pidfd_flags = Self::pidfd_flags_for_linux();
let attempt = 'brk: {
let rc = bun_sys::pidfd_open(self.pid, pidfd_flags);
if let Err(e) = &rc {
if e.get_errno() == bun_sys::E::EINVAL {
break 'brk bun_sys::pidfd_open(self.pid, 0);
}
}
rc
};
match attempt {
Err(err) => {
match err.get_errno() {
bun_sys::E::ENOSYS
| bun_sys::E::ENOTSUP
| bun_sys::E::EPERM
| bun_sys::E::EACCES
| bun_sys::E::EINVAL => {
crate::waiter_thread_flag::set();
return Err(err);
}
bun_sys::E::ESRCH => {}
_ => {
loop {
let mut status: i32 = 0;
let rc = unsafe {
libc::wait4(self.pid, &raw mut status, 0, core::ptr::null_mut())
};
match bun_sys::get_errno(rc as isize) {
bun_sys::E::SUCCESS => {}
bun_sys::E::EINTR => continue,
_ => {}
}
break;
}
}
}
Err(err)
}
Ok(fd) => Ok(fd.native()),
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn pifd_from_pid(&mut self) -> bun_sys::Result<PidFdType> {
Err(bun_sys::Error::from_code(
bun_sys::E::ENOSYS,
bun_sys::Tag::pidfd_open,
))
}
}
#[cfg(target_os = "macos")]
pub(crate) const POSIX_SPAWN_CLOEXEC_DEFAULT: i32 = 0x4000; #[cfg(target_os = "macos")]
pub(crate) const POSIX_SPAWN_SETEXEC: i32 = 0x0040;
#[cfg(unix)]
struct PosixSpawnFdGuard {
to_set_cloexec: Vec<Fd>,
to_close_at_end: Vec<Fd>,
to_close_on_error: Vec<Fd>,
on_error: bool,
}
#[cfg(unix)]
impl Drop for PosixSpawnFdGuard {
fn drop(&mut self) {
if self.on_error {
for fd in self.to_close_on_error.iter() {
fd.close();
}
}
for fd in self.to_set_cloexec.iter() {
let _ = bun_sys::set_close_on_exec(*fd);
}
for fd in self.to_close_at_end.iter() {
fd.close();
}
}
}
#[cfg(unix)]
pub unsafe fn spawn_process_posix(
options: &PosixSpawnOptions,
argv: Argv,
envp: Envp,
) -> Result<bun_sys::Result<PosixSpawnResult>, bun_core::Error> {
bun_analytics::features::spawn.fetch_add(1, Ordering::Relaxed);
let mut actions = PosixSpawnActions::init()?;
let mut attr = PosixSpawnAttr::init()?;
#[cfg(not(target_os = "android"))]
let (setsigdef, setsigmask) = (libc::POSIX_SPAWN_SETSIGDEF, libc::POSIX_SPAWN_SETSIGMASK);
#[cfg(target_os = "android")]
let (setsigdef, setsigmask) = (0x04_i32, 0x08_i32);
let flags: i32 = {
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
let mut f: i32 = setsigdef | setsigmask;
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
let f: i32 = setsigdef | setsigmask;
#[cfg(target_os = "macos")]
{
f |= POSIX_SPAWN_CLOEXEC_DEFAULT;
if options.use_execve_on_macos {
f |= POSIX_SPAWN_SETEXEC;
if matches!(options.stdin, PosixStdio::Buffer)
|| matches!(options.stdout, PosixStdio::Buffer)
|| matches!(options.stderr, PosixStdio::Buffer)
{
Output::panic(format_args!(
"Internal error: stdin, stdout, and stderr cannot be buffered when use_execve_on_macos is true",
));
}
}
}
if options.detached {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
f |= 0x80;
}
#[cfg(target_os = "macos")]
{
f |= 0x0400;
}
attr.detached = true;
}
f
};
attr.pty_slave_fd = options.pty_slave_fd;
attr.new_process_group = options.new_process_group;
#[cfg(any(target_os = "linux", target_os = "android"))]
{
attr.linux_pdeathsig = if let Some(sig) = options.linux_pdeathsig {
i32::from(sig)
} else if crate::pdeathsig::should_default() {
libc::SIGKILL
} else {
0
};
}
if !options.cwd.is_empty() {
actions.chdir(&options.cwd)?;
}
let mut spawned = PosixSpawnResult::default();
let mut extra_fds: Vec<ExtraPipe> = Vec::new();
let mut cleanup = PosixSpawnFdGuard {
to_set_cloexec: Vec::new(),
to_close_at_end: Vec::new(),
to_close_on_error: Vec::new(),
on_error: true,
};
let _ = attr.set(flags as _);
let _ = attr.reset_signals();
if let Some(ipc) = options.ipc {
actions.inherit(ipc)?;
spawned.ipc = Some(ipc);
}
let stdio_options: [&PosixStdio; 3] = [&options.stdin, &options.stdout, &options.stderr];
let mut dup_stdout_to_stderr: bool = false;
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
allow(unused_labels)
)]
'stdio: for i in 0..3usize {
let fileno = Fd::from_native(FdT::try_from(i).unwrap());
let flag: u32 = (if i == 0 {
bun_sys::O::RDONLY
} else {
bun_sys::O::WRONLY
}) as u32;
match stdio_options[i] {
PosixStdio::Dup2(dup2) => {
if i == 1 && dup2.to == StdioKind::Stderr {
dup_stdout_to_stderr = true;
} else {
actions.dup2(dup2.to.to_fd(), dup2.out.to_fd())?;
}
}
PosixStdio::Inherit => {
actions.inherit(fileno)?;
}
PosixStdio::Ipc | PosixStdio::Ignore => {
actions.open_z(fileno, c"/dev/null", flag | bun_sys::O::CREAT as u32, 0o664)?;
}
PosixStdio::Path(path) => {
actions.open(fileno, path, flag | bun_sys::O::CREAT as u32, 0o664)?;
}
PosixStdio::Buffer => {
#[cfg(any(target_os = "linux", target_os = "android"))]
'use_memfd: {
if !options.stream && i > 0 && bun_sys::can_use_memfd() {
let label: &CStr = match i {
0 => c"spawn_stdio_stdin",
1 => c"spawn_stdio_stdout",
2 => c"spawn_stdio_stderr",
_ => c"spawn_stdio_generic",
};
let fd =
match bun_sys::memfd_create(label, bun_sys::MemfdFlags::CrossProcess) {
Ok(fd) => fd,
Err(_) => break 'use_memfd,
};
cleanup.to_close_on_error.push(fd);
cleanup.to_set_cloexec.push(fd);
actions.dup2(fd, fileno)?;
set_spawned_stdio(&mut spawned, i, fd);
spawned.memfds[i] = true;
continue 'stdio;
}
}
let fds: [Fd; 2] = 'brk: {
let pair_result = if !options.no_sigpipe {
bun_sys::socketpair_for_shell(libc::AF_UNIX, libc::SOCK_STREAM, 0, false)
} else {
bun_sys::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, false)
};
let pair = match pair_result {
Ok(p) => p,
Err(e) => return Ok(Err(e)),
};
break 'brk [
pair[if i == 0 { 1 } else { 0 }],
pair[if i == 0 { 0 } else { 1 }],
];
};
#[cfg(target_os = "macos")]
{
let so_recvbuf: c_int = 1024 * 512;
let so_sendbuf: c_int = 1024 * 512;
unsafe {
if i == 0 {
libc::setsockopt(
fds[1].native(),
libc::SOL_SOCKET,
libc::SO_RCVBUF,
core::ptr::from_ref(&so_recvbuf).cast::<c_void>(),
core::mem::size_of::<c_int>() as u32,
);
libc::setsockopt(
fds[0].native(),
libc::SOL_SOCKET,
libc::SO_SNDBUF,
core::ptr::from_ref(&so_sendbuf).cast::<c_void>(),
core::mem::size_of::<c_int>() as u32,
);
} else {
libc::setsockopt(
fds[0].native(),
libc::SOL_SOCKET,
libc::SO_RCVBUF,
core::ptr::from_ref(&so_recvbuf).cast::<c_void>(),
core::mem::size_of::<c_int>() as u32,
);
libc::setsockopt(
fds[1].native(),
libc::SOL_SOCKET,
libc::SO_SNDBUF,
core::ptr::from_ref(&so_sendbuf).cast::<c_void>(),
core::mem::size_of::<c_int>() as u32,
);
}
}
}
cleanup.to_close_at_end.push(fds[1]);
cleanup.to_close_on_error.push(fds[0]);
if !options.sync {
if let Err(e) = bun_sys::set_nonblocking(fds[0]) {
return Ok(Err(e));
}
}
actions.dup2(fds[1], fileno)?;
if fds[1] != fileno {
actions.close(fds[1])?;
}
set_spawned_stdio(&mut spawned, i, fds[0]);
}
PosixStdio::Pipe(fd) => {
actions.dup2(*fd, fileno)?;
set_spawned_stdio(&mut spawned, i, *fd);
}
}
}
if dup_stdout_to_stderr {
if let PosixStdio::Dup2(d) = stdio_options[1] {
actions.dup2(d.to.to_fd(), d.out.to_fd())?;
}
}
for (i, ipc) in options.extra_fds.iter().enumerate() {
let fileno = Fd::from_native(FdT::try_from(3 + i).unwrap());
match ipc {
PosixStdio::Dup2(_) => panic!("TODO dup2 extra fd"),
PosixStdio::Inherit => {
actions.inherit(fileno)?;
extra_fds.push(ExtraPipe::Unavailable);
}
PosixStdio::Ignore => {
actions.open_z(fileno, c"/dev/null", bun_sys::O::RDWR as u32, 0o664)?;
extra_fds.push(ExtraPipe::Unavailable);
}
PosixStdio::Path(path) => {
actions.open(
fileno,
path,
(bun_sys::O::RDWR | bun_sys::O::CREAT) as u32,
0o664,
)?;
extra_fds.push(ExtraPipe::Unavailable);
}
PosixStdio::Ipc | PosixStdio::Buffer => {
let is_ipc = matches!(ipc, PosixStdio::Ipc);
let fds: [Fd; 2] =
match bun_sys::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, is_ipc) {
Ok(p) => p,
Err(e) => return Ok(Err(e)),
};
if !options.sync && !is_ipc {
if let Err(e) = bun_sys::set_nonblocking(fds[0]) {
return Ok(Err(e));
}
}
cleanup.to_close_at_end.push(fds[1]);
cleanup.to_close_on_error.push(fds[0]);
actions.dup2(fds[1], fileno)?;
if fds[1] != fileno {
actions.close(fds[1])?;
}
extra_fds.push(ExtraPipe::OwnedFd(fds[0]));
}
PosixStdio::Pipe(fd) => {
actions.dup2(*fd, fileno)?;
extra_fds.push(ExtraPipe::UnownedFd(*fd));
}
}
}
let argv0 = options.argv0.unwrap_or_else(|| unsafe { *argv });
let argv0_cstr = unsafe { bun_core::ffi::cstr(argv0) };
let spawn_result = posix_spawn::spawn_z(argv0_cstr, Some(&actions), Some(&attr), argv, envp);
match spawn_result {
Err(err) => {
return Ok(Err(err));
}
Ok(pid) => {
spawned.pid = pid;
spawned.extra_pipes = extra_fds;
#[cfg(any(target_os = "linux", target_os = "android"))]
{
if !options.can_block_entire_thread_to_reduce_cpu_usage_in_fast_path {
match spawned.pifd_from_pid() {
Ok(pidfd) => {
spawned.pidfd = Some(pidfd);
}
Err(err) => {
if err.get_errno() == bun_sys::E::ESRCH {
spawned.has_exited = true;
} else if !crate::waiter_thread_flag::get() {
return Ok(Err(err));
}
}
}
}
}
cleanup.on_error = false;
return Ok(Ok(spawned));
}
}
}
#[cfg(unix)]
fn set_spawned_stdio(spawned: &mut PosixSpawnResult, i: usize, fd: Fd) {
match i {
0 => spawned.stdin = Some(fd),
1 => spawned.stdout = Some(fd),
2 => spawned.stderr = Some(fd),
_ => unreachable!(),
}
}