use core::ffi::c_int;
#[cfg(windows)]
use core::ffi::c_void;
use core::fmt;
#[cfg(debug_assertions)]
use bun_core::Output;
pub use bun_core::{Fd, FdKind, FdNative, FdOptional as Optional, Stdio, fd};
pub type RawFd = FdNative;
#[cfg(windows)]
pub use bun_core::DecodeWindows;
use crate as sys;
bun_core::define_scoped_log!(log, SYS, visible);
pub type FdT = FdNative;
pub type UvFile = c_int;
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ErrorCase {
CloseOnFail,
LeakFdOnFail,
}
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
pub enum MakeLibUvOwnedError {
#[error("SystemFdQuotaExceeded")]
SystemFdQuotaExceeded,
}
bun_core::named_error_set!(MakeLibUvOwnedError);
pub trait FdExt: Copy + Sized {
fn close(self);
fn close_allowing_bad_file_descriptor(
self,
return_address: Option<usize>,
) -> Option<sys::Error>;
fn close_allowing_standard_io(self, return_address: Option<usize>) -> Option<sys::Error>;
fn make_lib_uv_owned(self) -> Result<Fd, MakeLibUvOwnedError>;
fn make_lib_uv_owned_for_syscall(
self,
syscall_tag: sys::Tag,
error_case: ErrorCase,
) -> sys::Result<Fd>;
fn make_path_u8(self, subpath: &[u8]) -> sys::Maybe<()>;
fn delete_tree(self, subpath: &[u8]) -> Result<(), bun_core::Error>;
fn as_socket_fd(self) -> sys::SocketT;
}
impl FdExt for Fd {
fn close(self) {
let err = self.close_allowing_bad_file_descriptor(None);
debug_assert!(err.is_none()); }
fn close_allowing_bad_file_descriptor(
self,
return_address: Option<usize>,
) -> Option<sys::Error> {
if self.stdio_tag().is_some() {
log!("close({}) SKIPPED", self);
return None;
}
self.close_allowing_standard_io(return_address)
}
fn close_allowing_standard_io(self, return_address: Option<usize>) -> Option<sys::Error> {
debug_assert!(self.is_valid());
#[cfg(debug_assertions)]
let mut fd_fmt_buf = [0u8; 1050];
#[cfg(debug_assertions)]
let fd_fmt: &[u8] = {
use std::io::Write as _;
let mut cursor = std::io::Cursor::new(&mut fd_fmt_buf[..]);
let _ = write!(cursor, "{}", self);
let len = cursor.position() as usize;
&fd_fmt_buf[..len]
};
let result: Option<sys::Error> = {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
debug_assert!(self.native() >= 0);
match sys::linux_syscall::close(self.native()) {
Err(e) if e == libc::EBADF => Some(sys::Error {
errno: sys::E::EBADF as _,
syscall: sys::Tag::close,
fd: self,
..Default::default()
}),
_ => None,
}
}
#[cfg(target_os = "freebsd")]
{
debug_assert!(self.native() >= 0);
match sys::get_errno(sys::safe_libc::close(self.native())) {
sys::E::EBADF => Some(sys::Error {
errno: sys::E::EBADF as _,
syscall: sys::Tag::close,
fd: self,
..Default::default()
}),
_ => None,
}
}
#[cfg(target_os = "macos")]
{
debug_assert!(self.native() >= 0);
match sys::get_errno(close_nocancel(self.native())) {
sys::E::EBADF => Some(sys::Error {
errno: sys::E::EBADF as _,
syscall: sys::Tag::close,
fd: self,
..Default::default()
}),
_ => None,
}
}
#[cfg(windows)]
{
use sys::windows::{NTSTATUS, Win32Error, Win32ErrorExt as _, libuv as uv};
match self.decode_windows() {
DecodeWindows::Uv(file_number) => {
let mut req = uv::fs_t::uninitialized();
let rc = unsafe {
uv::uv_fs_close(uv::Loop::get(), &mut req, file_number, None)
};
req.deinit();
if let Some(errno) = rc.errno() {
Some(sys::Error {
errno,
syscall: sys::Tag::close,
fd: self,
from_libuv: true,
..Default::default()
})
} else {
None
}
}
DecodeWindows::Windows(handle) => {
unsafe extern "system" {
safe fn NtClose(Handle: bun_windows_sys::HANDLE) -> NTSTATUS;
}
match NtClose(handle) {
NTSTATUS::SUCCESS => None,
rc => Some(sys::Error {
errno: Win32Error::from_nt_status(rc)
.to_system_errno()
.map_or(1, |e| e as _),
syscall: sys::Tag::CloseHandle,
fd: self,
..Default::default()
}),
}
}
}
}
};
#[cfg(debug_assertions)]
{
if let Some(ref err) = result {
if err.errno == sys::E::EBADF as _ {
Output::debug_warn(format_args!(
"close({}) = EBADF. This is an indication of a file descriptor UAF",
bstr::BStr::new(fd_fmt),
));
bun_core::dump_current_stack_trace(
return_address,
bun_core::DumpStackTraceOptions {
frame_count: 4,
stop_at_jsc_llint: true,
..Default::default()
},
);
} else {
log!("close({}) = {}", bstr::BStr::new(fd_fmt), err);
}
} else {
log!("close({})", bstr::BStr::new(fd_fmt));
}
}
#[cfg(not(debug_assertions))]
{
let _ = return_address;
}
result
}
fn make_lib_uv_owned(self) -> Result<Fd, MakeLibUvOwnedError> {
debug_assert!(self.is_valid());
#[cfg(not(windows))]
{
Ok(self)
}
#[cfg(windows)]
{
match self.kind() {
FdKind::System => {
let n = uv_open_osfhandle(self.native())?;
Ok(Fd::from_uv(n))
}
FdKind::Uv => Ok(self),
}
}
}
fn make_lib_uv_owned_for_syscall(
self,
syscall_tag: sys::Tag,
error_case: ErrorCase,
) -> sys::Result<Fd> {
#[cfg(not(windows))]
{
let _ = (syscall_tag, error_case);
Ok(self)
}
#[cfg(windows)]
{
match self.make_lib_uv_owned() {
Ok(fd) => Ok(fd),
Err(MakeLibUvOwnedError::SystemFdQuotaExceeded) => {
if matches!(error_case, ErrorCase::CloseOnFail) {
self.close();
}
Err(sys::Error {
errno: sys::E::EMFILE as _,
syscall: syscall_tag,
..Default::default()
})
}
}
}
}
fn make_path_u8(self, subpath: &[u8]) -> sys::Maybe<()> {
sys::mkdir_recursive_at(self, subpath)
}
fn delete_tree(self, subpath: &[u8]) -> Result<(), bun_core::Error> {
sys::Dir::borrow(&self).delete_tree(subpath)
}
#[inline]
fn as_socket_fd(self) -> sys::SocketT {
#[cfg(windows)]
{
self.native() as sys::SocketT
}
#[cfg(not(windows))]
{
self.native()
}
}
}
pub trait FdOptionalExt {
fn close(self);
}
impl FdOptionalExt for Optional {
#[inline]
fn close(self) {
if let Some(fd) = self.unwrap() {
fd.close();
}
}
}
pub struct HashMapContext;
impl HashMapContext {
#[inline]
pub fn hash(fd: Fd) -> u64 {
#[cfg(not(windows))]
{
fd.0 as u32 as u64
} #[cfg(windows)]
{
fd.0
}
}
#[inline]
pub fn eql(a: Fd, b: Fd) -> bool {
a == b
}
#[inline]
pub fn pre(input: Fd) -> Prehashed {
Prehashed {
value: Self::hash(input),
input,
}
}
}
pub struct Prehashed {
pub value: u64,
pub input: Fd,
}
impl Prehashed {
#[inline]
pub fn hash(&self, fd: Fd) -> u64 {
if fd == self.input {
return self.value;
}
HashMapContext::hash(fd)
}
#[inline]
pub fn eql(&self, a: Fd, b: Fd) -> bool {
a == b
}
}
pub struct MovableIfWindowsFd {
#[cfg(windows)]
inner: Option<Fd>,
#[cfg(not(windows))]
inner: Fd,
}
impl MovableIfWindowsFd {
#[inline]
pub fn init(fd: Fd) -> Self {
#[cfg(windows)]
{
Self { inner: Some(fd) }
}
#[cfg(not(windows))]
{
Self { inner: fd }
}
}
#[inline]
pub fn get(&self) -> Option<Fd> {
#[cfg(windows)]
{
self.inner
}
#[cfg(not(windows))]
{
Some(self.inner)
}
}
#[cfg(not(windows))]
#[inline]
pub fn get_posix(&self) -> Fd {
self.inner
}
pub fn close(&mut self) {
#[cfg(not(windows))]
{
self.inner.close();
self.inner = Fd::INVALID;
}
#[cfg(windows)]
{
if let Some(fd) = self.inner {
fd.close();
self.inner = None;
}
}
}
#[inline]
pub fn is_valid(&self) -> bool {
#[cfg(not(windows))]
{
self.inner.is_valid()
}
#[cfg(windows)]
{
self.inner.is_some_and(|fd| fd.is_valid())
}
}
#[inline]
pub fn is_owned(&self) -> bool {
#[cfg(not(windows))]
{
true
}
#[cfg(windows)]
{
self.inner.is_some()
}
}
#[cfg(windows)]
pub fn take(&mut self) -> Option<Fd> {
self.inner.take()
}
}
impl fmt::Display for MovableIfWindowsFd {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(windows))]
{
write!(w, "{}", self.inner)
}
#[cfg(windows)]
{
match self.inner {
Some(fd) => write!(w, "{}", fd),
None => w.write_str("[moved]"),
}
}
}
}
#[cfg(target_os = "macos")]
unsafe extern "C" {
#[link_name = "close$NOCANCEL"]
safe fn close_nocancel(fd: c_int) -> c_int;
}
#[cfg(windows)]
pub(crate) fn uv_open_osfhandle(in_: *mut c_void) -> Result<c_int, MakeLibUvOwnedError> {
let out = bun_core::fd::uv_open_osfhandle(in_);
debug_assert!(out >= -1);
if out == -1 {
return Err(MakeLibUvOwnedError::SystemFdQuotaExceeded);
}
Ok(out)
}