1
- use std:: collections:: HashMap ;
1
+ use std:: collections:: BTreeMap ;
2
2
use std:: convert:: { TryFrom , TryInto } ;
3
3
use std:: fs:: { remove_file, rename, File , OpenOptions } ;
4
4
use std:: io:: { Read , Seek , SeekFrom , Write } ;
@@ -18,18 +18,48 @@ pub struct FileHandle {
18
18
writable : bool ,
19
19
}
20
20
21
+ #[ derive( Debug , Default ) ]
21
22
pub struct FileHandler {
22
- handles : HashMap < i32 , FileHandle > ,
23
- low : i32 ,
23
+ handles : BTreeMap < i32 , FileHandle > ,
24
24
}
25
25
26
- impl Default for FileHandler {
27
- fn default ( ) -> Self {
28
- FileHandler {
29
- handles : Default :: default ( ) ,
30
- // 0, 1 and 2 are reserved for stdin, stdout and stderr.
31
- low : 3 ,
32
- }
26
+ // fd numbers 0, 1, and 2 are reserved for stdin, stdout, and stderr
27
+ const MIN_NORMAL_FILE_FD : i32 = 3 ;
28
+
29
+ impl FileHandler {
30
+ fn insert_fd ( & mut self , file_handle : FileHandle ) -> i32 {
31
+ self . insert_fd_with_min_fd ( file_handle, 0 )
32
+ }
33
+
34
+ fn insert_fd_with_min_fd ( & mut self , file_handle : FileHandle , min_fd : i32 ) -> i32 {
35
+ let min_fd = std:: cmp:: max ( min_fd, MIN_NORMAL_FILE_FD ) ;
36
+
37
+ // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
38
+ // between used FDs, the find_map combinator will return it. If the first such unused FD
39
+ // is after all other used FDs, the find_map combinator will return None, and we will use
40
+ // the FD following the greatest FD thus far.
41
+ let candidate_new_fd = self
42
+ . handles
43
+ . range ( min_fd..)
44
+ . zip ( min_fd..)
45
+ . find_map ( |( ( fd, _fh) , counter) | {
46
+ if * fd != counter {
47
+ // There was a gap in the fds stored, return the first unused one
48
+ // (note that this relies on BTreeMap iterating in key order)
49
+ Some ( counter)
50
+ } else {
51
+ // This fd is used, keep going
52
+ None
53
+ }
54
+ } ) ;
55
+ let new_fd = candidate_new_fd. unwrap_or_else ( || {
56
+ // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
57
+ // maximum fd in the map
58
+ self . handles . last_entry ( ) . map ( |entry| entry. key ( ) + 1 ) . unwrap_or ( min_fd)
59
+ } ) ;
60
+
61
+ self . handles . insert ( new_fd, file_handle) . unwrap_none ( ) ;
62
+ new_fd
33
63
}
34
64
}
35
65
@@ -107,10 +137,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
107
137
let path = this. read_os_str_from_c_str ( this. read_scalar ( path_op) ?. not_undef ( ) ?) ?;
108
138
109
139
let fd = options. open ( & path) . map ( |file| {
110
- let mut fh = & mut this. machine . file_handler ;
111
- fh. low += 1 ;
112
- fh. handles . insert ( fh. low , FileHandle { file, writable } ) . unwrap_none ( ) ;
113
- fh. low
140
+ let fh = & mut this. machine . file_handler ;
141
+ fh. insert_fd ( FileHandle { file, writable } )
114
142
} ) ;
115
143
116
144
this. try_unwrap_io_result ( fd)
@@ -120,7 +148,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
120
148
& mut self ,
121
149
fd_op : OpTy < ' tcx , Tag > ,
122
150
cmd_op : OpTy < ' tcx , Tag > ,
123
- _arg1_op : Option < OpTy < ' tcx , Tag > > ,
151
+ start_op : Option < OpTy < ' tcx , Tag > > ,
124
152
) -> InterpResult < ' tcx , i32 > {
125
153
let this = self . eval_context_mut ( ) ;
126
154
@@ -139,6 +167,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
139
167
} else {
140
168
this. handle_not_found ( )
141
169
}
170
+ } else if cmd == this. eval_libc_i32 ( "F_DUPFD" ) ?
171
+ || cmd == this. eval_libc_i32 ( "F_DUPFD_CLOEXEC" ) ?
172
+ {
173
+ // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
174
+ // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
175
+ // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
176
+ // thus they can share the same implementation here.
177
+ if fd < MIN_NORMAL_FILE_FD {
178
+ throw_unsup_format ! ( "Duplicating file descriptors for stdin, stdout, or stderr is not supported" )
179
+ }
180
+ let start_op = start_op. ok_or_else ( || {
181
+ err_unsup_format ! (
182
+ "fcntl with command F_DUPFD or F_DUPFD_CLOEXEC requires a third argument"
183
+ )
184
+ } ) ?;
185
+ let start = this. read_scalar ( start_op) ?. to_i32 ( ) ?;
186
+ let fh = & mut this. machine . file_handler ;
187
+ let ( file_result, writable) = match fh. handles . get ( & fd) {
188
+ Some ( FileHandle { file, writable } ) => ( file. try_clone ( ) , * writable) ,
189
+ None => return this. handle_not_found ( ) ,
190
+ } ;
191
+ let fd_result = file_result. map ( |duplicated| {
192
+ fh. insert_fd_with_min_fd ( FileHandle { file : duplicated, writable } , start)
193
+ } ) ;
194
+ this. try_unwrap_io_result ( fd_result)
142
195
} else {
143
196
throw_unsup_format ! ( "The {:#x} command is not supported for `fcntl`)" , cmd) ;
144
197
}
@@ -151,23 +204,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
151
204
152
205
let fd = this. read_scalar ( fd_op) ?. to_i32 ( ) ?;
153
206
154
- if let Some ( handle ) = this. machine . file_handler . handles . remove ( & fd) {
207
+ if let Some ( FileHandle { file , writable } ) = this. machine . file_handler . handles . remove ( & fd) {
155
208
// We sync the file if it was opened in a mode different than read-only.
156
- if handle . writable {
209
+ if writable {
157
210
// `File::sync_all` does the checks that are done when closing a file. We do this to
158
211
// to handle possible errors correctly.
159
- let result = this. try_unwrap_io_result ( handle . file . sync_all ( ) . map ( |_| 0i32 ) ) ;
212
+ let result = this. try_unwrap_io_result ( file. sync_all ( ) . map ( |_| 0i32 ) ) ;
160
213
// Now we actually close the file.
161
- drop ( handle ) ;
214
+ drop ( file ) ;
162
215
// And return the result.
163
216
result
164
217
} else {
165
218
// We drop the file, this closes it but ignores any errors produced when closing
166
- // it. This is done because `File::sync_call ` cannot be done over files like
219
+ // it. This is done because `File::sync_all ` cannot be done over files like
167
220
// `/dev/urandom` which are read-only. Check
168
221
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
169
222
// discussion.
170
- drop ( handle ) ;
223
+ drop ( file ) ;
171
224
Ok ( 0 )
172
225
}
173
226
} else {
@@ -200,16 +253,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
200
253
// host's and target's `isize`. This saves us from having to handle overflows later.
201
254
let count = count. min ( this. isize_max ( ) as u64 ) . min ( isize:: max_value ( ) as u64 ) ;
202
255
203
- if let Some ( handle ) = this. machine . file_handler . handles . get_mut ( & fd) {
256
+ if let Some ( FileHandle { file , writable : _ } ) = this. machine . file_handler . handles . get_mut ( & fd) {
204
257
// This can never fail because `count` was capped to be smaller than
205
258
// `isize::max_value()`.
206
259
let count = isize:: try_from ( count) . unwrap ( ) ;
207
260
// We want to read at most `count` bytes. We are sure that `count` is not negative
208
261
// because it was a target's `usize`. Also we are sure that its smaller than
209
262
// `usize::max_value()` because it is a host's `isize`.
210
263
let mut bytes = vec ! [ 0 ; count as usize ] ;
211
- let result = handle
212
- . file
264
+ let result = file
213
265
. read ( & mut bytes)
214
266
// `File::read` never returns a value larger than `count`, so this cannot fail.
215
267
. map ( |c| i64:: try_from ( c) . unwrap ( ) ) ;
@@ -255,9 +307,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
255
307
// host's and target's `isize`. This saves us from having to handle overflows later.
256
308
let count = count. min ( this. isize_max ( ) as u64 ) . min ( isize:: max_value ( ) as u64 ) ;
257
309
258
- if let Some ( handle ) = this. machine . file_handler . handles . get_mut ( & fd) {
310
+ if let Some ( FileHandle { file , writable : _ } ) = this. machine . file_handler . handles . get_mut ( & fd) {
259
311
let bytes = this. memory . read_bytes ( buf, Size :: from_bytes ( count) ) ?;
260
- let result = handle . file . write ( & bytes) . map ( |c| i64:: try_from ( c) . unwrap ( ) ) ;
312
+ let result = file. write ( & bytes) . map ( |c| i64:: try_from ( c) . unwrap ( ) ) ;
261
313
this. try_unwrap_io_result ( result)
262
314
} else {
263
315
this. handle_not_found ( )
@@ -290,8 +342,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
290
342
return Ok ( -1 ) ;
291
343
} ;
292
344
293
- if let Some ( handle ) = this. machine . file_handler . handles . get_mut ( & fd) {
294
- let result = handle . file . seek ( seek_from) . map ( |offset| offset as i64 ) ;
345
+ if let Some ( FileHandle { file , writable : _ } ) = this. machine . file_handler . handles . get_mut ( & fd) {
346
+ let result = file. seek ( seek_from) . map ( |offset| offset as i64 ) ;
295
347
this. try_unwrap_io_result ( result)
296
348
} else {
297
349
this. handle_not_found ( )
@@ -652,11 +704,11 @@ impl FileMetadata {
652
704
fd : i32 ,
653
705
) -> InterpResult < ' tcx , Option < FileMetadata > > {
654
706
let option = ecx. machine . file_handler . handles . get ( & fd) ;
655
- let handle = match option {
656
- Some ( handle ) => handle ,
707
+ let file = match option {
708
+ Some ( FileHandle { file , writable : _ } ) => file ,
657
709
None => return ecx. handle_not_found ( ) . map ( |_: i32 | None ) ,
658
710
} ;
659
- let metadata = handle . file . metadata ( ) ;
711
+ let metadata = file. metadata ( ) ;
660
712
661
713
FileMetadata :: from_meta ( ecx, metadata)
662
714
}
0 commit comments