@@ -7,6 +7,8 @@ use std::collections::HashMap;
77use std:: ffi:: OsString ;
88use std:: io:: Write ;
99#[ cfg( unix) ]
10+ use std:: os:: unix:: io:: FromRawFd ;
11+ #[ cfg( unix) ]
1012use std:: os:: unix:: prelude:: ExitStatusExt ;
1113#[ cfg( unix) ]
1214use std:: os:: unix:: process:: CommandExt ;
@@ -35,7 +37,6 @@ use deno_io::ChildStderrResource;
3537use deno_io:: ChildStdinResource ;
3638use deno_io:: ChildStdoutResource ;
3739use deno_io:: IntoRawIoHandle ;
38- use deno_io:: fs:: FileResource ;
3940use deno_os:: SignalError ;
4041use deno_permissions:: PathQueryDescriptor ;
4142use deno_permissions:: PermissionsContainer ;
@@ -79,12 +80,12 @@ impl Stdio {
7980}
8081
8182#[ derive( Copy , Clone , Eq , PartialEq ) ]
82- pub enum StdioOrRid {
83+ pub enum StdioOrFd {
8384 Stdio ( Stdio ) ,
84- Rid ( ResourceId ) ,
85+ Fd ( i32 ) ,
8586}
8687
87- impl < ' de > Deserialize < ' de > for StdioOrRid {
88+ impl < ' de > Deserialize < ' de > for StdioOrFd {
8889 fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
8990 where
9091 D : serde:: Deserializer < ' de > ,
@@ -93,47 +94,71 @@ impl<'de> Deserialize<'de> for StdioOrRid {
9394 let value = Value :: deserialize ( deserializer) ?;
9495 match value {
9596 Value :: String ( val) => match val. as_str ( ) {
96- "inherit" => Ok ( StdioOrRid :: Stdio ( Stdio :: Inherit ) ) ,
97- "piped" => Ok ( StdioOrRid :: Stdio ( Stdio :: Piped ) ) ,
98- "null" => Ok ( StdioOrRid :: Stdio ( Stdio :: Null ) ) ,
97+ "inherit" => Ok ( StdioOrFd :: Stdio ( Stdio :: Inherit ) ) ,
98+ "piped" => Ok ( StdioOrFd :: Stdio ( Stdio :: Piped ) ) ,
99+ "null" => Ok ( StdioOrFd :: Stdio ( Stdio :: Null ) ) ,
99100 "ipc_for_internal_use" => {
100- Ok ( StdioOrRid :: Stdio ( Stdio :: IpcForInternalUse ) )
101+ Ok ( StdioOrFd :: Stdio ( Stdio :: IpcForInternalUse ) )
101102 }
102103 val => Err ( serde:: de:: Error :: unknown_variant (
103104 val,
104105 & [ "inherit" , "piped" , "null" ] ,
105106 ) ) ,
106107 } ,
107- Value :: Number ( val) => match val. as_u64 ( ) {
108- Some ( val) if val <= ResourceId :: MAX as u64 => {
109- Ok ( StdioOrRid :: Rid ( val as ResourceId ) )
108+ Value :: Number ( val) => match val. as_i64 ( ) {
109+ Some ( val) if val >= 0 && val <= i32 :: MAX as i64 => {
110+ Ok ( StdioOrFd :: Fd ( val as i32 ) )
110111 }
111- _ => Err ( serde:: de:: Error :: custom ( "Expected a positive integer" ) ) ,
112+ _ => Err ( serde:: de:: Error :: custom (
113+ "Expected a non-negative integer file descriptor" ,
114+ ) ) ,
112115 } ,
113116 _ => Err ( serde:: de:: Error :: custom (
114- r#"Expected a resource id , "inherit", "piped", or "null""# ,
117+ r#"Expected a file descriptor , "inherit", "piped", or "null""# ,
115118 ) ) ,
116119 }
117120 }
118121}
119122
120- impl StdioOrRid {
121- pub fn as_stdio (
122- & self ,
123- state : & mut OpState ,
124- ) -> Result < StdStdio , ProcessError > {
123+ impl StdioOrFd {
124+ pub fn as_stdio ( & self ) -> Result < StdStdio , ProcessError > {
125125 match & self {
126- StdioOrRid :: Stdio ( val) => Ok ( val. as_stdio ( ) ) ,
127- StdioOrRid :: Rid ( rid) => {
128- Ok ( FileResource :: with_file ( state, * rid, |file| {
129- file. as_stdio ( ) . map_err ( deno_error:: JsErrorBox :: from_err)
130- } ) ?)
126+ StdioOrFd :: Stdio ( val) => Ok ( val. as_stdio ( ) ) ,
127+ StdioOrFd :: Fd ( fd) => {
128+ #[ cfg( unix) ]
129+ {
130+ // Safety: we dup the fd so the original remains open for the caller
131+ let new_fd = unsafe { libc:: dup ( * fd) } ;
132+ if new_fd < 0 {
133+ return Err ( ProcessError :: Io ( std:: io:: Error :: last_os_error ( ) ) ) ;
134+ }
135+ // Safety: new_fd is a valid, freshly duplicated file descriptor
136+ Ok ( unsafe {
137+ StdStdio :: from ( std:: os:: unix:: io:: OwnedFd :: from_raw_fd ( new_fd) )
138+ } )
139+ }
140+ #[ cfg( windows) ]
141+ {
142+ // Convert CRT file descriptor to OS handle, clone it, and wrap in Stdio
143+ let handle = unsafe { libc:: get_osfhandle ( * fd as _ ) } ;
144+ if handle == -1 {
145+ return Err ( ProcessError :: Io ( std:: io:: Error :: last_os_error ( ) ) ) ;
146+ }
147+ let borrowed = unsafe {
148+ std:: os:: windows:: io:: BorrowedHandle :: borrow_raw (
149+ handle as std:: os:: windows:: io:: RawHandle ,
150+ )
151+ } ;
152+ let owned =
153+ borrowed. try_clone_to_owned ( ) . map_err ( ProcessError :: Io ) ?;
154+ Ok ( StdStdio :: from ( owned) )
155+ }
131156 }
132157 }
133158 }
134159
135160 pub fn is_ipc ( & self ) -> bool {
136- matches ! ( self , StdioOrRid :: Stdio ( Stdio :: IpcForInternalUse ) )
161+ matches ! ( self , StdioOrFd :: Stdio ( Stdio :: IpcForInternalUse ) )
137162 }
138163}
139164
@@ -248,7 +273,7 @@ pub struct SpawnArgs {
248273
249274 input : Option < JsBuffer > ,
250275
251- extra_stdio : Vec < Stdio > ,
276+ extra_stdio : Vec < StdioOrFd > ,
252277 detached : bool ,
253278 needs_npm_process_state : bool ,
254279 #[ cfg( unix) ]
@@ -349,9 +374,9 @@ pub enum ProcessError {
349374#[ derive( Deserialize ) ]
350375#[ serde( rename_all = "camelCase" ) ]
351376pub struct ChildStdio {
352- stdin : StdioOrRid ,
353- stdout : StdioOrRid ,
354- stderr : StdioOrRid ,
377+ stdin : StdioOrFd ,
378+ stdout : StdioOrFd ,
379+ stderr : StdioOrFd ,
355380}
356381
357382#[ derive( ToV8 ) ]
@@ -515,16 +540,16 @@ fn create_command(
515540 } else if args. input . is_some ( ) {
516541 command. stdin ( StdStdio :: piped ( ) ) ;
517542 } else {
518- command. stdin ( args. stdio . stdin . as_stdio ( state ) ?) ;
543+ command. stdin ( args. stdio . stdin . as_stdio ( ) ?) ;
519544 }
520545
521546 command. stdout ( match args. stdio . stdout {
522- StdioOrRid :: Stdio ( Stdio :: Inherit ) => StdioOrRid :: Rid ( 1 ) . as_stdio ( state ) ?,
523- value => value. as_stdio ( state ) ?,
547+ StdioOrFd :: Stdio ( Stdio :: Inherit ) => StdioOrFd :: Fd ( 1 ) . as_stdio ( ) ?,
548+ value => value. as_stdio ( ) ?,
524549 } ) ;
525550 command. stderr ( match args. stdio . stderr {
526- StdioOrRid :: Stdio ( Stdio :: Inherit ) => StdioOrRid :: Rid ( 2 ) . as_stdio ( state ) ?,
527- value => value. as_stdio ( state ) ?,
551+ StdioOrFd :: Stdio ( Stdio :: Inherit ) => StdioOrFd :: Fd ( 2 ) . as_stdio ( ) ?,
552+ value => value. as_stdio ( ) ?,
528553 } ) ;
529554
530555 #[ cfg( unix) ]
@@ -578,27 +603,34 @@ fn create_command(
578603 for ( i, stdio) in args. extra_stdio . into_iter ( ) . enumerate ( ) {
579604 // index 0 in `extra_stdio` actually refers to fd 3
580605 // because we handle stdin,stdout,stderr specially
581- let fd = ( i + 3 ) as i32 ;
582- // TODO(nathanwhit): handle inherited, but this relies on the parent process having
583- // fds open already. since we don't generally support dealing with raw fds,
584- // we can't properly support this
585- if matches ! ( stdio, Stdio :: Piped ) {
586- let ( fd1, fd2) = deno_io:: bi_pipe_pair_raw ( ) ?;
587- fds_to_dup. push ( ( fd2, fd) ) ;
588- fds_to_close. push ( fd2) ;
589- let rid = state. resource_table . add (
590- match deno_io:: BiPipeResource :: from_raw_handle ( fd1) {
591- Ok ( v) => v,
592- Err ( e) => {
593- log:: warn!( "Failed to open bidirectional pipe for fd {fd}: {e}" ) ;
594- extra_pipe_rids. push ( None ) ;
595- continue ;
596- }
597- } ,
598- ) ;
599- extra_pipe_rids. push ( Some ( rid) ) ;
600- } else {
601- extra_pipe_rids. push ( None ) ;
606+ let target_fd = ( i + 3 ) as i32 ;
607+ match stdio {
608+ StdioOrFd :: Stdio ( Stdio :: Piped ) => {
609+ let ( fd1, fd2) = deno_io:: bi_pipe_pair_raw ( ) ?;
610+ fds_to_dup. push ( ( fd2, target_fd) ) ;
611+ fds_to_close. push ( fd2) ;
612+ let rid = state. resource_table . add (
613+ match deno_io:: BiPipeResource :: from_raw_handle ( fd1) {
614+ Ok ( v) => v,
615+ Err ( e) => {
616+ log:: warn!(
617+ "Failed to open bidirectional pipe for fd {target_fd}: {e}"
618+ ) ;
619+ extra_pipe_rids. push ( None ) ;
620+ continue ;
621+ }
622+ } ,
623+ ) ;
624+ extra_pipe_rids. push ( Some ( rid) ) ;
625+ }
626+ StdioOrFd :: Fd ( fd) => {
627+ // Dup the caller's fd onto the target fd slot in the child
628+ fds_to_dup. push ( ( fd, target_fd) ) ;
629+ extra_pipe_rids. push ( None ) ;
630+ }
631+ _ => {
632+ extra_pipe_rids. push ( None ) ;
633+ }
602634 }
603635 }
604636
@@ -670,29 +702,41 @@ fn create_command(
670702 for ( i, stdio) in args. extra_stdio . into_iter ( ) . enumerate ( ) {
671703 // index 0 in `extra_stdio` actually refers to fd 3
672704 // because we handle stdin,stdout,stderr specially
673- let fd = ( i + 3 ) as i32 ;
674- // TODO(nathanwhit): handle inherited, but this relies on the parent process having
675- // fds open already. since we don't generally support dealing with raw fds,
676- // we can't properly support this
677- if matches ! ( stdio, Stdio :: Piped ) {
678- let ( fd1, fd2) = deno_io:: bi_pipe_pair_raw ( ) ?;
679- handles_to_close. push ( fd2) ;
680- let rid = state. resource_table . add (
681- match deno_io:: BiPipeResource :: from_raw_handle ( fd1) {
682- Ok ( v) => v,
683- Err ( e) => {
684- log:: warn!( "Failed to open bidirectional pipe for fd {fd}: {e}" ) ;
685- extra_pipe_rids. push ( None ) ;
686- continue ;
687- }
688- } ,
689- ) ;
690- command. extra_handle ( Some ( fd2) ) ;
691- extra_pipe_rids. push ( Some ( rid) ) ;
692- } else {
693- // no handle, push an empty handle so we need get the right fds for following handles
694- command. extra_handle ( None ) ;
695- extra_pipe_rids. push ( None ) ;
705+ let target_fd = ( i + 3 ) as i32 ;
706+ match stdio {
707+ StdioOrFd :: Stdio ( Stdio :: Piped ) => {
708+ let ( fd1, fd2) = deno_io:: bi_pipe_pair_raw ( ) ?;
709+ handles_to_close. push ( fd2) ;
710+ let rid = state. resource_table . add (
711+ match deno_io:: BiPipeResource :: from_raw_handle ( fd1) {
712+ Ok ( v) => v,
713+ Err ( e) => {
714+ log:: warn!(
715+ "Failed to open bidirectional pipe for fd {target_fd}: {e}"
716+ ) ;
717+ extra_pipe_rids. push ( None ) ;
718+ continue ;
719+ }
720+ } ,
721+ ) ;
722+ command. extra_handle ( Some ( fd2) ) ;
723+ extra_pipe_rids. push ( Some ( rid) ) ;
724+ }
725+ StdioOrFd :: Fd ( fd) => {
726+ // Convert CRT fd to OS handle and pass to child
727+ let handle = unsafe { libc:: get_osfhandle ( fd as _ ) } ;
728+ if handle != -1 {
729+ command. extra_handle ( Some ( handle as _ ) ) ;
730+ } else {
731+ command. extra_handle ( None ) ;
732+ }
733+ extra_pipe_rids. push ( None ) ;
734+ }
735+ _ => {
736+ // no handle, push an empty handle so we get the right fds for following handles
737+ command. extra_handle ( None ) ;
738+ extra_pipe_rids. push ( None ) ;
739+ }
696740 }
697741 }
698742
@@ -1116,8 +1160,8 @@ fn op_spawn_sync(
11161160 state : & mut OpState ,
11171161 #[ serde] args : SpawnArgs ,
11181162) -> Result < SpawnOutput , ProcessError > {
1119- let stdout = matches ! ( args. stdio. stdout, StdioOrRid :: Stdio ( Stdio :: Piped ) ) ;
1120- let stderr = matches ! ( args. stdio. stderr, StdioOrRid :: Stdio ( Stdio :: Piped ) ) ;
1163+ let stdout = matches ! ( args. stdio. stdout, StdioOrFd :: Stdio ( Stdio :: Piped ) ) ;
1164+ let stderr = matches ! ( args. stdio. stderr, StdioOrFd :: Stdio ( Stdio :: Piped ) ) ;
11211165 let input = args. input . clone ( ) ;
11221166 let ( mut command, _, _, _) =
11231167 create_command ( state, args, "Deno.Command().outputSync()" ) ?;
@@ -1219,11 +1263,11 @@ mod deprecated {
12191263 cwd : Option < String > ,
12201264 env : Vec < ( String , String ) > ,
12211265 #[ from_v8( serde) ]
1222- stdin : StdioOrRid ,
1266+ stdin : StdioOrFd ,
12231267 #[ from_v8( serde) ]
1224- stdout : StdioOrRid ,
1268+ stdout : StdioOrFd ,
12251269 #[ from_v8( serde) ]
1226- stderr : StdioOrRid ,
1270+ stderr : StdioOrFd ,
12271271 }
12281272
12291273 struct ChildResource {
@@ -1296,20 +1340,20 @@ mod deprecated {
12961340 }
12971341
12981342 // TODO: make this work with other resources, eg. sockets
1299- c. stdin ( run_args. stdin . as_stdio ( state ) ?) ;
1343+ c. stdin ( run_args. stdin . as_stdio ( ) ?) ;
13001344 c. stdout (
13011345 match run_args. stdout {
1302- StdioOrRid :: Stdio ( Stdio :: Inherit ) => StdioOrRid :: Rid ( 1 ) ,
1346+ StdioOrFd :: Stdio ( Stdio :: Inherit ) => StdioOrFd :: Fd ( 1 ) ,
13031347 value => value,
13041348 }
1305- . as_stdio ( state ) ?,
1349+ . as_stdio ( ) ?,
13061350 ) ;
13071351 c. stderr (
13081352 match run_args. stderr {
1309- StdioOrRid :: Stdio ( Stdio :: Inherit ) => StdioOrRid :: Rid ( 2 ) ,
1353+ StdioOrFd :: Stdio ( Stdio :: Inherit ) => StdioOrFd :: Fd ( 2 ) ,
13101354 value => value,
13111355 }
1312- . as_stdio ( state ) ?,
1356+ . as_stdio ( ) ?,
13131357 ) ;
13141358
13151359 // We want to kill child when it's closed
0 commit comments