@@ -28,6 +28,9 @@ trait FileDescriptor : std::fmt::Debug {
2828 fn read < ' tcx > ( & mut self , communicate_allowed : bool , bytes : & mut [ u8 ] ) -> InterpResult < ' tcx , io:: Result < usize > > ;
2929 fn write < ' tcx > ( & mut self , communicate_allowed : bool , bytes : & [ u8 ] ) -> InterpResult < ' tcx , io:: Result < usize > > ;
3030 fn seek < ' tcx > ( & mut self , communicate_allowed : bool , offset : SeekFrom ) -> InterpResult < ' tcx , io:: Result < u64 > > ;
31+ fn close < ' tcx > ( self : Box < Self > , _communicate_allowed : bool ) -> InterpResult < ' tcx , io:: Result < i32 > > ;
32+
33+ fn dup < ' tcx > ( & mut self ) -> io:: Result < Box < dyn FileDescriptor > > ;
3134}
3235
3336impl FileDescriptor for FileHandle {
@@ -49,6 +52,34 @@ impl FileDescriptor for FileHandle {
4952 assert ! ( communicate_allowed, "isolation should have prevented even opening a file" ) ;
5053 Ok ( self . file . seek ( offset) )
5154 }
55+
56+ fn close < ' tcx > ( self : Box < Self > , communicate_allowed : bool ) -> InterpResult < ' tcx , io:: Result < i32 > > {
57+ assert ! ( communicate_allowed, "isolation should have prevented even opening a file" ) ;
58+ // We sync the file if it was opened in a mode different than read-only.
59+ if self . writable {
60+ // `File::sync_all` does the checks that are done when closing a file. We do this to
61+ // to handle possible errors correctly.
62+ let result = self . file . sync_all ( ) . map ( |_| 0i32 ) ;
63+ // Now we actually close the file.
64+ drop ( self ) ;
65+ // And return the result.
66+ Ok ( result)
67+ } else {
68+ // We drop the file, this closes it but ignores any errors
69+ // produced when closing it. This is done because
70+ // `File::sync_all` cannot be done over files like
71+ // `/dev/urandom` which are read-only. Check
72+ // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
73+ // for a deeper discussion.
74+ drop ( self ) ;
75+ Ok ( Ok ( 0 ) )
76+ }
77+ }
78+
79+ fn dup < ' tcx > ( & mut self ) -> io:: Result < Box < dyn FileDescriptor > > {
80+ let duplicated = self . file . try_clone ( ) ?;
81+ Ok ( Box :: new ( FileHandle { file : duplicated, writable : self . writable } ) )
82+ }
5283}
5384
5485impl FileDescriptor for io:: Stdin {
@@ -71,6 +102,14 @@ impl FileDescriptor for io::Stdin {
71102 fn seek < ' tcx > ( & mut self , _communicate_allowed : bool , _offset : SeekFrom ) -> InterpResult < ' tcx , io:: Result < u64 > > {
72103 throw_unsup_format ! ( "cannot seek on stdin" ) ;
73104 }
105+
106+ fn close < ' tcx > ( self : Box < Self > , _communicate_allowed : bool ) -> InterpResult < ' tcx , io:: Result < i32 > > {
107+ throw_unsup_format ! ( "stdin cannot be closed" ) ;
108+ }
109+
110+ fn dup < ' tcx > ( & mut self ) -> io:: Result < Box < dyn FileDescriptor > > {
111+ Ok ( Box :: new ( io:: stdin ( ) ) )
112+ }
74113}
75114
76115impl FileDescriptor for io:: Stdout {
@@ -98,6 +137,14 @@ impl FileDescriptor for io::Stdout {
98137 fn seek < ' tcx > ( & mut self , _communicate_allowed : bool , _offset : SeekFrom ) -> InterpResult < ' tcx , io:: Result < u64 > > {
99138 throw_unsup_format ! ( "cannot seek on stdout" ) ;
100139 }
140+
141+ fn close < ' tcx > ( self : Box < Self > , _communicate_allowed : bool ) -> InterpResult < ' tcx , io:: Result < i32 > > {
142+ throw_unsup_format ! ( "stdout cannot be closed" ) ;
143+ }
144+
145+ fn dup < ' tcx > ( & mut self ) -> io:: Result < Box < dyn FileDescriptor > > {
146+ Ok ( Box :: new ( io:: stdout ( ) ) )
147+ }
101148}
102149
103150impl FileDescriptor for io:: Stderr {
@@ -118,6 +165,14 @@ impl FileDescriptor for io::Stderr {
118165 fn seek < ' tcx > ( & mut self , _communicate_allowed : bool , _offset : SeekFrom ) -> InterpResult < ' tcx , io:: Result < u64 > > {
119166 throw_unsup_format ! ( "cannot seek on stderr" ) ;
120167 }
168+
169+ fn close < ' tcx > ( self : Box < Self > , _communicate_allowed : bool ) -> InterpResult < ' tcx , io:: Result < i32 > > {
170+ throw_unsup_format ! ( "stderr cannot be closed" ) ;
171+ }
172+
173+ fn dup < ' tcx > ( & mut self ) -> io:: Result < Box < dyn FileDescriptor > > {
174+ Ok ( Box :: new ( io:: stderr ( ) ) )
175+ }
121176}
122177
123178#[ derive( Debug ) ]
@@ -137,18 +192,12 @@ impl<'tcx> Default for FileHandler {
137192 }
138193}
139194
140-
141- // fd numbers 0, 1, and 2 are reserved for stdin, stdout, and stderr
142- const MIN_NORMAL_FILE_FD : i32 = 3 ;
143-
144195impl < ' tcx > FileHandler {
145- fn insert_fd ( & mut self , file_handle : FileHandle ) -> i32 {
196+ fn insert_fd ( & mut self , file_handle : Box < dyn FileDescriptor > ) -> i32 {
146197 self . insert_fd_with_min_fd ( file_handle, 0 )
147198 }
148199
149- fn insert_fd_with_min_fd ( & mut self , file_handle : FileHandle , min_fd : i32 ) -> i32 {
150- let min_fd = std:: cmp:: max ( min_fd, MIN_NORMAL_FILE_FD ) ;
151-
200+ fn insert_fd_with_min_fd ( & mut self , file_handle : Box < dyn FileDescriptor > , min_fd : i32 ) -> i32 {
152201 // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
153202 // between used FDs, the find_map combinator will return it. If the first such unused FD
154203 // is after all other used FDs, the find_map combinator will return None, and we will use
@@ -173,7 +222,7 @@ impl<'tcx> FileHandler {
173222 self . handles . last_key_value ( ) . map ( |( fd, _) | fd. checked_add ( 1 ) . unwrap ( ) ) . unwrap_or ( min_fd)
174223 } ) ;
175224
176- self . handles . insert ( new_fd, Box :: new ( file_handle) ) . unwrap_none ( ) ;
225+ self . handles . insert ( new_fd, file_handle) . unwrap_none ( ) ;
177226 new_fd
178227 }
179228}
@@ -449,7 +498,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
449498
450499 let fd = options. open ( & path) . map ( |file| {
451500 let fh = & mut this. machine . file_handler ;
452- fh. insert_fd ( FileHandle { file, writable } )
501+ fh. insert_fd ( Box :: new ( FileHandle { file, writable } ) )
453502 } ) ;
454503
455504 this. try_unwrap_io_result ( fd)
@@ -489,22 +538,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
489538 // thus they can share the same implementation here.
490539 let & [ _, _, start] = check_arg_count ( args) ?;
491540 let start = this. read_scalar ( start) ?. to_i32 ( ) ?;
492- if fd < MIN_NORMAL_FILE_FD {
493- throw_unsup_format ! ( "duplicating file descriptors for stdin, stdout, or stderr is not supported" )
494- }
541+
495542 let fh = & mut this. machine . file_handler ;
496- let ( file_result, writable) = match fh. handles . get ( & fd) {
543+
544+ match fh. handles . get_mut ( & fd) {
497545 Some ( file_descriptor) => {
498- // FIXME: Support "dup" for all FDs(stdin, etc)
499- let FileHandle { file, writable } = file_descriptor. as_file_handle ( ) ?;
500- ( file. try_clone ( ) , * writable)
546+ let dup_result = file_descriptor. dup ( ) ;
547+ match dup_result {
548+ Ok ( dup_fd) => Ok ( fh. insert_fd_with_min_fd ( dup_fd, start) ) ,
549+ Err ( e) => {
550+ this. set_last_error_from_io_error ( e) ?;
551+ Ok ( -1 )
552+ }
553+ }
501554 } ,
502555 None => return this. handle_not_found ( ) ,
503- } ;
504- let fd_result = file_result. map ( |duplicated| {
505- fh. insert_fd_with_min_fd ( FileHandle { file : duplicated, writable } , start)
506- } ) ;
507- this. try_unwrap_io_result ( fd_result)
556+ }
508557 } else if this. tcx . sess . target . target . target_os == "macos"
509558 && cmd == this. eval_libc_i32 ( "F_FULLFSYNC" ) ?
510559 {
@@ -530,26 +579,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
530579 let fd = this. read_scalar ( fd_op) ?. to_i32 ( ) ?;
531580
532581 if let Some ( file_descriptor) = this. machine . file_handler . handles . remove ( & fd) {
533- // FIXME: Support `close` for all FDs(stdin, etc)
534- let FileHandle { file, writable } = file_descriptor. as_file_handle ( ) ?;
535- // We sync the file if it was opened in a mode different than read-only.
536- if * writable {
537- // `File::sync_all` does the checks that are done when closing a file. We do this to
538- // to handle possible errors correctly.
539- let result = this. try_unwrap_io_result ( file. sync_all ( ) . map ( |_| 0i32 ) ) ;
540- // Now we actually close the file.
541- drop ( file) ;
542- // And return the result.
543- result
544- } else {
545- // We drop the file, this closes it but ignores any errors produced when closing
546- // it. This is done because `File::sync_all` cannot be done over files like
547- // `/dev/urandom` which are read-only. Check
548- // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
549- // discussion.
550- drop ( file) ;
551- Ok ( 0 )
552- }
582+ let result = file_descriptor. close ( this. machine . communicate ) ?;
583+ this. try_unwrap_io_result ( result)
553584 } else {
554585 this. handle_not_found ( )
555586 }
0 commit comments