use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc;
use deno_core::CppgcInherits;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::uv_compat;
use deno_core::uv_compat::UvConnect;
use deno_core::uv_compat::UvLoop;
use deno_core::uv_compat::UvStream;
use deno_core::v8;
use deno_permissions::PermissionsContainer;
use crate::ops::handle_wrap::AsyncWrap;
use crate::ops::handle_wrap::Handle;
use crate::ops::handle_wrap::HandleWrap;
use crate::ops::handle_wrap::OwnedPtr;
use crate::ops::handle_wrap::ProviderType;
use crate::ops::stream_wrap::LibUvStreamWrap;
use crate::ops::stream_wrap::clone_context_from_uv_loop;
type UvPipe = uv_compat::uv_pipe_t;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
enum PipeType {
Socket = 0,
Server = 1,
Ipc = 2,
}
macro_rules! with_js_handle {
($stream:expr, |$scope:ident, $this:ident| $body:block) => {{
let Some(handle_data_ptr) = LibUvStreamWrap::stable_handle_data($stream)
else {
return;
};
let handle_data = unsafe { handle_data_ptr.as_ref() };
let isolate_ptr = unsafe { *handle_data.isolate.get() };
if isolate_ptr.is_null() {
return;
}
let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(isolate_ptr) };
let loop_ptr = unsafe { (*$stream).loop_ };
let context =
unsafe { clone_context_from_uv_loop(&mut isolate, loop_ptr) };
v8::scope!(let handle_scope, &mut isolate);
let context_local = v8::Local::new(handle_scope, context);
let $scope = &mut v8::ContextScope::new(handle_scope, context_local);
let Some(js_global) =
(unsafe { (*handle_data.js_handle.get()).to_global($scope) })
else {
return;
};
let $this: v8::Local<v8::Object> = v8::Local::new($scope, js_global);
$body
}};
}
#[allow(
unused_unsafe,
clippy::undocumented_unsafe_blocks,
reason = "macro expands unsafe blocks inside unsafe fn"
)]
unsafe extern "C" fn server_connection_cb(server: *mut UvStream, status: i32) {
with_js_handle!(server, |scope, this| {
let key = v8::String::new(scope, "onconnection").unwrap();
if let Some(onconnection) = this.get(scope, key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(onconnection)
{
let status_val: v8::Local<v8::Value> =
v8::Integer::new(scope, status).into();
func.call(scope, this.into(), &[status_val]);
}
});
}
#[repr(C)]
struct ConnectReqData {
uv_req: UvConnect,
js_req: v8::Global<v8::Object>,
}
#[allow(
unused_unsafe,
clippy::undocumented_unsafe_blocks,
reason = "macro expands unsafe blocks inside unsafe fn"
)]
unsafe extern "C" fn connect_cb(req: *mut UvConnect, status: i32) {
let stream = unsafe { (*req).handle as *mut UvStream };
let req_data = unsafe { Box::from_raw(req as *mut ConnectReqData) };
let js_req_global = req_data.js_req;
with_js_handle!(stream, |scope, this| {
let js_req = v8::Local::new(scope, &js_req_global);
let oncomplete_key = v8::String::new(scope, "oncomplete").unwrap();
if let Some(oncomplete) = js_req.get(scope, oncomplete_key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(oncomplete)
{
let status_val: v8::Local<v8::Value> =
v8::Integer::new(scope, status).into();
let readable: v8::Local<v8::Value> =
v8::Boolean::new(scope, status == 0).into();
let writable: v8::Local<v8::Value> =
v8::Boolean::new(scope, status == 0).into();
func.call(
scope,
js_req.into(),
&[status_val, this.into(), js_req.into(), readable, writable],
);
}
});
}
#[derive(CppgcInherits)]
#[cppgc_inherits_from(LibUvStreamWrap)]
#[repr(C)]
pub struct PipeWrap {
base: LibUvStreamWrap,
handle: Option<OwnedPtr<UvPipe>>,
#[allow(dead_code, reason = "stored for parity with TCPWrap::socket_type")]
pipe_type: Cell<PipeType>,
}
unsafe impl GarbageCollected for PipeWrap {
fn get_name(&self) -> &'static std::ffi::CStr {
c"Pipe"
}
fn trace(&self, visitor: &mut v8::cppgc::Visitor) {
self.base.trace(visitor);
}
}
impl Drop for PipeWrap {
fn drop(&mut self) {
self.base.detach_stream();
}
}
impl PipeWrap {
fn new(pipe_type: PipeType, op_state: &mut OpState) -> Self {
let loop_ =
&**op_state.borrow::<Box<UvLoop>>() as *const UvLoop as *mut UvLoop;
let ipc = pipe_type == PipeType::Ipc;
let pipe = OwnedPtr::from_box(Box::new(uv_compat::new_pipe(ipc)));
unsafe {
uv_compat::uv_pipe_init(loop_, pipe.as_mut_ptr(), ipc as i32);
}
let provider = match pipe_type {
PipeType::Server => ProviderType::PipeServerWrap,
_ => ProviderType::PipeWrap,
};
let base = LibUvStreamWrap::new(
HandleWrap::create(
AsyncWrap::create(op_state, provider as i32),
Some(Handle::New(pipe.as_ptr().cast())),
),
-1,
pipe.as_ptr().cast(),
);
unsafe {
(*pipe.as_mut_ptr()).data = base.handle_data_ptr();
}
Self {
base,
handle: Some(pipe),
pipe_type: Cell::new(pipe_type),
}
}
fn pipe_ptr(&self) -> *mut UvPipe {
match &self.handle {
Some(h) => h.as_mut_ptr(),
None => std::ptr::null_mut(),
}
}
pub fn stream_ptr(&self) -> *mut uv_compat::uv_stream_t {
self.base.stream_ptr()
}
}
#[op2(inherit = LibUvStreamWrap)]
impl PipeWrap {
#[constructor]
#[cppgc]
fn new_pipe(
#[smi] pipe_type: i32,
op_state: &mut OpState,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope,
) -> PipeWrap {
let pt = match pipe_type {
1 => PipeType::Server,
2 => PipeType::Ipc,
_ => PipeType::Socket,
};
let pipe = PipeWrap::new(pt, op_state);
pipe.base.set_js_handle(this, scope);
pipe
}
#[getter]
fn fd(&self) -> i32 {
let pipe = self.pipe_ptr();
if pipe.is_null() {
return -1;
}
#[cfg(unix)]
{
unsafe { &*pipe }.fd().unwrap_or(-1)
}
#[cfg(windows)]
{
-1
}
}
#[fast]
fn fd_for_ipc(&self) -> i32 {
#[cfg(unix)]
{
let pipe = self.pipe_ptr();
if pipe.is_null() {
return -1;
}
unsafe { uv_compat::uv_pipe_fd_for_ipc(pipe) }
}
#[cfg(not(unix))]
-1
}
#[fast]
fn socket_type_for_ipc(&self) -> i32 {
match self.pipe_type.get() {
PipeType::Server => 1,
_ => 0,
}
}
#[fast]
fn open(&self, state: &mut OpState, #[smi] fd: i32) -> i32 {
{
let fd_table = state.borrow::<deno_io::FdTable>();
if fd_table.contains(fd) && !(0..=2).contains(&fd) {
return -libc::EEXIST;
}
}
let pipe = self.pipe_ptr();
if pipe.is_null() {
return uv_compat::UV_EBADF;
}
let ret = unsafe { uv_compat::uv_pipe_open(pipe, fd) };
if ret == 0 {
state.borrow_mut::<deno_io::FdTable>().register_uv_owned(fd);
self.base.set_fd(fd);
}
ret
}
#[fast]
fn bind(&self, #[string] path: &str) -> i32 {
let pipe = self.pipe_ptr();
if pipe.is_null() {
return uv_compat::UV_EBADF;
}
unsafe { uv_compat::uv_pipe_bind(pipe, path) }
}
#[nofast]
fn listen(
&self,
state: &mut OpState,
#[smi] backlog: i32,
) -> Result<i32, deno_permissions::PermissionCheckError> {
let pipe = self.pipe_ptr();
if pipe.is_null() {
return Ok(uv_compat::UV_EBADF);
}
if let Some(path) = unsafe { &*pipe }.bind_path() {
state.borrow_mut::<PermissionsContainer>().check_open(
std::borrow::Cow::Borrowed(std::path::Path::new(path)),
deno_permissions::OpenAccessKind::ReadWriteNoFollow,
Some("node:net.Server.listen()"),
)?;
}
Ok(unsafe {
uv_compat::uv_pipe_listen(pipe, backlog, Some(server_connection_cb))
})
}
#[fast]
fn accept(&self, #[cppgc] client: &PipeWrap) -> i32 {
let server = self.pipe_ptr();
let client_pipe = client.pipe_ptr();
if server.is_null() || client_pipe.is_null() {
return uv_compat::UV_EBADF;
}
unsafe { uv_compat::uv_pipe_accept(server, client_pipe) }
}
#[nofast]
fn connect(
&self,
state: &mut OpState,
js_req: v8::Local<v8::Object>,
#[string] path: &str,
scope: &mut v8::PinScope,
) -> Result<i32, deno_permissions::PermissionCheckError> {
state.borrow_mut::<PermissionsContainer>().check_open(
std::borrow::Cow::Borrowed(std::path::Path::new(path)),
deno_permissions::OpenAccessKind::ReadWriteNoFollow,
Some("node:net.createConnection()"),
)?;
let pipe = self.pipe_ptr();
if pipe.is_null() {
return Ok(uv_compat::UV_EBADF);
}
let js_req_global = v8::Global::new(scope, js_req);
let mut connect_req = Box::new(ConnectReqData {
uv_req: uv_compat::new_connect(),
js_req: js_req_global,
});
let req_ptr = &mut connect_req.uv_req as *mut UvConnect;
let _ = Box::into_raw(connect_req);
let ret = unsafe {
uv_compat::uv_pipe_connect(req_ptr, pipe, path, Some(connect_cb))
};
if ret != 0 {
unsafe {
let _ = Box::from_raw(req_ptr as *mut ConnectReqData);
}
}
Ok(ret)
}
#[fast]
#[rename("setPendingInstances")]
fn set_pending_instances(&self, #[smi] instances: i32) {
let pipe = self.pipe_ptr();
if !pipe.is_null() {
unsafe { uv_compat::uv_pipe_set_pending_instances(pipe, instances) };
}
}
#[fast]
fn fchmod(&self, #[smi] mode: i32) -> i32 {
#[cfg(unix)]
{
let pipe = self.pipe_ptr();
if pipe.is_null() {
return uv_compat::UV_EBADF;
}
if let Some(path) = unsafe { &*pipe }.bind_path() {
if path.as_bytes().first().is_some_and(|&b| b == 0) {
return 0;
}
let c_path = match std::ffi::CString::new(path) {
Ok(p) => p,
Err(_) => return uv_compat::UV_EINVAL,
};
if unsafe { libc::chmod(c_path.as_ptr(), mode as libc::mode_t) } != 0 {
return -1;
}
0
} else {
uv_compat::UV_EBADF
}
}
#[cfg(windows)]
{
let _ = mode;
0
}
}
#[reentrant]
fn close(
&self,
op_state: Rc<RefCell<OpState>>,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope<'_, '_>,
#[scoped] cb: Option<v8::Global<v8::Function>>,
) -> Result<(), ResourceError> {
#[cfg(unix)]
{
let pipe = self.pipe_ptr();
if !pipe.is_null() {
if let Some(fd) = unsafe { &*pipe }.fd() {
op_state
.borrow_mut()
.borrow_mut::<deno_io::FdTable>()
.remove(fd);
}
}
}
#[cfg(windows)]
let crt_fd = {
let fd = self.base.get_fd();
if fd >= 0 {
op_state
.borrow_mut()
.borrow_mut::<deno_io::FdTable>()
.remove(fd);
}
fd
};
self.base.clear_js_handle();
let result = self
.base
.handle_wrap()
.close_handle(op_state, this, scope, cb);
#[cfg(windows)]
if crt_fd >= 0 {
self.base.set_fd(-1);
unsafe {
libc::close(crt_fd);
}
}
result
}
}