use core::ffi::{c_int, c_void};
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicBool, Ordering};
use bun_sys::windows::libuv as uv;
use bun_sys::windows::libuv::UvHandle as _;
use bun_sys::Fd;
use bun_sys::ReturnCodeExt as _;
bun_core::declare_scope!(PipeSource, hidden);
pub type Pipe = uv::Pipe;
pub type Tty = uv::uv_tty_t;
pub enum Source {
Pipe(Box<Pipe>),
Tty(bun_ptr::BackRef<Tty>),
File(Box<File>),
SyncFile(Box<File>),
}
#[repr(C)]
pub struct File {
pub fs: uv::fs_t,
pub iov: uv::uv_buf_t,
pub file: uv::uv_file,
pub state: FileState,
pub close_after_operation: bool,
}
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub enum FileState {
#[default]
Deinitialized,
Operating,
Canceling,
Closing,
}
impl Default for File {
fn default() -> Self {
Self {
fs: bun_core::ffi::zeroed(),
iov: bun_core::ffi::zeroed(),
file: 0,
state: FileState::Deinitialized,
close_after_operation: false,
}
}
}
impl File {
pub unsafe fn from_fs(fs: *mut uv::fs_t) -> *mut File {
unsafe { bun_core::from_field_ptr!(File, fs, fs) }
}
#[inline]
pub unsafe fn from_fs_callback<'a>(
fs: *mut uv::fs_t,
) -> (&'a mut File, uv::ReturnCodeI64, *mut c_void) {
let (result, data) = unsafe { ((*fs).result, (*fs).data) };
(unsafe { &mut *Self::from_fs(fs) }, result, data)
}
pub fn can_start(&self) -> bool {
self.state == FileState::Deinitialized && !self.fs.data.is_null()
}
pub fn prepare(&mut self) {
debug_assert!(self.state == FileState::Deinitialized);
debug_assert!(!self.fs.data.is_null());
self.state = FileState::Operating;
self.close_after_operation = false;
}
pub fn stop(&mut self) {
if self.state != FileState::Operating {
return;
}
let cancel_result =
unsafe { uv::uv_cancel(core::ptr::from_mut::<uv::fs_t>(&mut self.fs).cast()) };
if cancel_result == 0 {
self.state = FileState::Canceling;
}
}
pub fn detach(&mut self) {
self.fs.data = core::ptr::null_mut();
self.close_after_operation = true;
self.stop();
if self.state == FileState::Deinitialized {
self.close_after_operation = false;
self.start_close();
}
}
pub fn complete(&mut self, was_canceled: bool) {
debug_assert!(self.state == FileState::Operating || self.state == FileState::Canceling);
if was_canceled {
debug_assert!(self.state == FileState::Canceling);
}
self.fs.deinit();
self.state = FileState::Deinitialized;
if self.close_after_operation {
self.close_after_operation = false;
self.start_close();
}
}
fn start_close(&mut self) {
debug_assert!(self.state == FileState::Deinitialized);
self.state = FileState::Closing;
unsafe {
let fs_ptr = core::ptr::from_mut::<File>(self).cast::<uv::fs_t>();
uv::uv_fs_close(
uv::Loop::get(),
fs_ptr,
self.file,
Some(Self::on_close_complete),
);
}
}
extern "C" fn on_close_complete(fs: *mut uv::fs_t) {
let file = unsafe { &mut *File::from_fs(fs) };
debug_assert!(file.state == FileState::Closing);
file.fs.deinit();
drop(unsafe { bun_core::heap::take(file as *mut File) });
}
}
impl Source {
#[inline]
fn tty_mut(tty: &mut bun_ptr::BackRef<Tty>) -> &mut Tty {
unsafe { tty.get_mut() }
}
pub fn is_closed(&self) -> bool {
match self {
Source::Pipe(pipe) => pipe.is_closed(),
Source::Tty(tty) => tty.is_closed(),
Source::SyncFile(file) | Source::File(file) => file.file == -1,
}
}
pub fn is_active(&self) -> bool {
match self {
Source::Pipe(pipe) => pipe.is_active(),
Source::Tty(tty) => tty.is_active(),
Source::SyncFile(_) | Source::File(_) => true,
}
}
pub fn get_handle(&mut self) -> *mut uv::Handle {
match self {
Source::Pipe(pipe) => core::ptr::from_mut::<Pipe>(pipe.as_mut()).cast(),
Source::Tty(tty) => tty.as_ptr().cast(),
Source::SyncFile(_) | Source::File(_) => unreachable!(),
}
}
pub fn to_stream(&mut self) -> *mut uv::uv_stream_t {
match self {
Source::Pipe(pipe) => core::ptr::from_mut::<Pipe>(pipe.as_mut()).cast(),
Source::Tty(tty) => tty.as_ptr().cast(),
Source::SyncFile(_) | Source::File(_) => unreachable!(),
}
}
pub fn get_fd(&self) -> Fd {
match self {
Source::Pipe(pipe) => Fd::from_system(pipe.fd()),
Source::Tty(tty) => Fd::from_system(tty.fd()),
Source::SyncFile(file) | Source::File(file) => Fd::from_uv(file.file),
}
}
pub fn set_data(&mut self, data: *mut c_void) {
match self {
Source::Pipe(pipe) => pipe.data = data,
Source::Tty(tty) => Self::tty_mut(tty).data = data,
Source::SyncFile(file) | Source::File(file) => file.fs.data = data,
}
}
pub fn get_data(&self) -> *mut c_void {
match self {
Source::Pipe(pipe) => pipe.data,
Source::Tty(tty) => tty.data,
Source::SyncFile(file) | Source::File(file) => file.fs.data,
}
}
pub fn ref_(&mut self) {
match self {
Source::Pipe(pipe) => pipe.ref_(),
Source::Tty(tty) => Self::tty_mut(tty).ref_(),
Source::SyncFile(_) | Source::File(_) => {}
}
}
pub fn unref(&mut self) {
match self {
Source::Pipe(pipe) => pipe.unref(),
Source::Tty(tty) => Self::tty_mut(tty).unref(),
Source::SyncFile(_) | Source::File(_) => {}
}
}
pub fn has_ref(&self) -> bool {
match self {
Source::Pipe(pipe) => pipe.has_ref(),
Source::Tty(tty) => tty.has_ref(),
Source::SyncFile(_) | Source::File(_) => false,
}
}
pub fn open_pipe(loop_: *mut uv::Loop, fd: Fd) -> bun_sys::Result<Box<Pipe>> {
bun_core::scoped_log!(PipeSource, "openPipe (fd = {})", fd);
let mut pipe: Box<Pipe> = Box::new(bun_core::ffi::zeroed::<Pipe>());
if let Some(err) = pipe.init(loop_, false).to_error(bun_sys::Tag::pipe) {
drop(pipe);
return bun_sys::Result::Err(err);
}
if let Some(err) = pipe.open(fd.uv()).to_error(bun_sys::Tag::open) {
let raw = bun_core::heap::into_raw(pipe);
unsafe { uv::Pipe::close_and_destroy(raw) };
return bun_sys::Result::Err(err);
}
bun_sys::Result::Ok(pipe)
}
pub fn open_tty(loop_: *mut uv::Loop, fd: Fd) -> bun_sys::Result<bun_ptr::BackRef<Tty>> {
bun_core::scoped_log!(PipeSource, "openTTY (fd = {})", fd);
let uv_fd = fd.uv();
if uv_fd == 0 {
return stdin_tty::get_stdin_tty(loop_);
}
let mut tty: Box<Tty> = bun_core::boxed_zeroed();
if let Some(err) = tty.init(loop_, uv_fd).to_error(bun_sys::Tag::open) {
drop(tty);
return bun_sys::Result::Err(err);
}
bun_sys::Result::Ok(bun_ptr::BackRef::from(bun_core::heap::into_raw_nn(tty)))
}
pub fn open_file(fd: Fd) -> Box<File> {
debug_assert!(fd.is_valid() && fd.uv() != -1);
bun_core::scoped_log!(PipeSource, "openFile (fd = {})", fd);
let mut file: Box<File> = Box::new(File::default());
file.file = fd.uv();
file
}
pub fn open(loop_: *mut uv::Loop, fd: Fd) -> bun_sys::Result<Source> {
let rc = uv::uv_guess_handle(fd.uv());
bun_core::scoped_log!(
PipeSource,
"open(fd: {}, type: {})",
fd,
<&'static str>::from(rc)
);
match rc {
uv::HandleType::NamedPipe => match Self::open_pipe(loop_, fd) {
bun_sys::Result::Ok(pipe) => bun_sys::Result::Ok(Source::Pipe(pipe)),
bun_sys::Result::Err(err) => bun_sys::Result::Err(err),
},
uv::HandleType::Tty => match Self::open_tty(loop_, fd) {
bun_sys::Result::Ok(tty) => bun_sys::Result::Ok(Source::Tty(tty)),
bun_sys::Result::Err(err) => bun_sys::Result::Err(err),
},
uv::HandleType::File => bun_sys::Result::Ok(Source::File(Self::open_file(fd))),
_ => {
let errno = bun_sys::windows::get_last_errno();
if errno == bun_sys::E::SUCCESS {
return bun_sys::Result::Ok(Source::File(Self::open_file(fd)));
}
bun_sys::Result::Err(bun_sys::Error::from_code(errno, bun_sys::Tag::open))
}
}
}
pub fn file(&self) -> &File {
match self {
Source::SyncFile(file) | Source::File(file) => file,
_ => unreachable!("Source::file() on non-file source"),
}
}
pub fn set_raw_mode(&mut self, value: bool) -> bun_sys::Result<()> {
match self {
Source::Tty(tty) => {
if let Some(err) = Self::tty_mut(tty)
.set_mode(if value {
uv::TtyMode::Raw
} else {
uv::TtyMode::Normal
})
.to_error(bun_sys::Tag::uv_tty_set_mode)
{
bun_sys::Result::Err(err)
} else {
bun_sys::Result::Ok(())
}
}
_ => bun_sys::Result::Err(bun_sys::Error {
errno: bun_sys::E::NOTSUP as _,
syscall: bun_sys::Tag::uv_tty_set_mode,
fd: self.get_fd(),
..Default::default()
}),
}
}
}
pub mod stdin_tty {
use super::*;
static DATA: bun_core::RacyCell<MaybeUninit<uv::uv_tty_t>> =
bun_core::RacyCell::new(MaybeUninit::uninit());
static LOCK: bun_threading::Mutex = bun_threading::Mutex::new();
static INITIALIZED: AtomicBool = AtomicBool::new(false);
#[inline]
pub(crate) fn value() -> *mut uv::uv_tty_t {
DATA.get().cast::<uv::uv_tty_t>()
}
pub(crate) fn is_stdin_tty(tty: *const Tty) -> bool {
core::ptr::eq(tty, value())
}
pub(super) fn get_stdin_tty(loop_: *mut uv::Loop) -> bun_sys::Result<bun_ptr::BackRef<Tty>> {
let _guard = LOCK.lock_guard();
if !INITIALIZED.swap(true, Ordering::Relaxed) {
let rc = unsafe { uv::uv_tty_init(loop_, value(), 0, 0) };
if let Some(err) = rc.to_error(bun_sys::Tag::open) {
INITIALIZED.store(false, Ordering::Relaxed);
return bun_sys::Result::Err(err);
}
}
bun_sys::Result::Ok(bun_ptr::BackRef::from(
core::ptr::NonNull::new(value()).expect("stdin_tty value() is a process-global static"),
))
}
}
#[unsafe(no_mangle)]
pub(crate) extern "C" fn Source__setRawModeStdin(uv_loop: *mut uv::Loop, raw: bool) -> c_int {
let mut tty = match Source::open_tty(uv_loop, Fd::stdin()) {
bun_sys::Result::Ok(tty) => tty,
bun_sys::Result::Err(e) => return e.errno as c_int,
};
if let Some(err) = Source::tty_mut(&mut tty)
.set_mode(if raw {
uv::TtyMode::Vt
} else {
uv::TtyMode::Normal
})
.to_error(bun_sys::Tag::uv_tty_set_mode)
{
return err.errno as c_int;
}
0
}