Skip to content

Commit 577773b

Browse files
committed
wip
1 parent 2141d44 commit 577773b

File tree

1 file changed

+60
-6
lines changed

1 file changed

+60
-6
lines changed

ext/node/ops/fs.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -844,13 +844,67 @@ fn dup_fd_impl(state: &mut OpState, fd: i32) -> Result<ResourceId, FsError> {
844844
#[op2(fast)]
845845
#[smi]
846846
pub fn op_node_dup_fd(
847-
_state: &mut OpState,
848-
#[smi] _fd: i32,
847+
state: &mut OpState,
848+
#[smi] fd: i32,
849849
) -> Result<ResourceId, FsError> {
850-
Err(FsError::Io(std::io::Error::new(
851-
std::io::ErrorKind::Unsupported,
852-
"op_node_dup_fd is not supported on this platform",
853-
)))
850+
dup_fd_impl(state, fd)
851+
}
852+
853+
#[cfg(not(unix))]
854+
fn dup_fd_impl(state: &mut OpState, fd: i32) -> Result<ResourceId, FsError> {
855+
use std::fs::File as StdFile;
856+
use std::os::windows::io::BorrowedHandle;
857+
use std::os::windows::io::RawHandle;
858+
859+
if fd < 0 {
860+
return Err(FsError::Io(std::io::Error::new(
861+
std::io::ErrorKind::InvalidInput,
862+
"Invalid file descriptor",
863+
)));
864+
}
865+
866+
// Convert CRT fd to Windows HANDLE.
867+
// get_osfhandle returns -1 for invalid fds, -2 for non-stream fds.
868+
// SAFETY: get_osfhandle is safe to call with any fd value; it returns
869+
// -1 for invalid fds rather than causing undefined behavior.
870+
let raw_handle = unsafe { libc::get_osfhandle(fd) };
871+
if raw_handle == -1 || raw_handle == -2 {
872+
// ERROR_INVALID_HANDLE (6) maps to EBADF via uvTranslateSysError
873+
return Err(FsError::Io(std::io::Error::from_raw_os_error(6)));
874+
}
875+
let handle = raw_handle as RawHandle;
876+
877+
// Check if this handle is managed by a non-fsFile internal Deno resource.
878+
// fsFile resources (opened via node:fs) are safe to dup.
879+
// Other managed resources (SQLite, internal pipes) are denied.
880+
// Unmanaged fds (child_process, native addons) are allowed through.
881+
for (rid, name) in state.resource_table.names() {
882+
if name == "fsFile" || name == "signal" {
883+
continue;
884+
}
885+
if matches!(
886+
state.resource_table.get_handle(rid),
887+
Ok(deno_core::ResourceHandle::Fd(existing_handle)) if existing_handle == handle
888+
) {
889+
return Err(FsError::Io(std::io::Error::new(
890+
std::io::ErrorKind::PermissionDenied,
891+
"File descriptor is managed by an internal Deno resource",
892+
)));
893+
}
894+
}
895+
896+
// Duplicate the handle so it's independently owned and closeable.
897+
// SAFETY: handle is valid (checked above via get_osfhandle).
898+
let borrowed = unsafe { BorrowedHandle::borrow_raw(handle) };
899+
let owned = borrowed.try_clone_to_owned().map_err(FsError::Io)?;
900+
901+
let std_file = StdFile::from(owned);
902+
let file: Rc<dyn deno_io::fs::File> =
903+
Rc::new(deno_io::StdFileResourceInner::file(std_file, None));
904+
let rid = state
905+
.resource_table
906+
.add(FileResource::new(file, "fsFile".to_string()));
907+
Ok(rid)
854908
}
855909

856910
#[derive(Debug, Serialize)]

0 commit comments

Comments
 (0)