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 } ;
@@ -13,34 +13,58 @@ use helpers::immty_from_uint_checked;
13
13
use shims:: time:: system_time_to_duration;
14
14
15
15
#[ derive( Debug ) ]
16
- pub struct FileHandle {
17
- file : File ,
18
- writable : bool ,
16
+ pub enum FileHandle {
17
+ StdInPlaceholder ,
18
+ StdOutPlaceholder ,
19
+ StdErrPlaceholder ,
20
+ File { file : File , writable : bool } ,
21
+ // In the future, could add support for dirfd() and other functions by
22
+ // adding a Directory variant here
19
23
}
20
24
21
25
pub struct FileHandler {
22
- handles : HashMap < i32 , FileHandle > ,
23
- low : i32 ,
26
+ handles : BTreeMap < i32 , FileHandle > ,
24
27
}
25
28
26
29
impl FileHandler {
27
- fn next_fd ( & self ) -> i32 {
28
- self . low + 1
30
+ fn insert_fd ( & mut self , file_handle : FileHandle ) -> i32 {
31
+ self . insert_fd_with_min_fd ( file_handle , 3 )
29
32
}
30
33
31
- fn register_fd ( & mut self , fd : i32 , handle : FileHandle ) {
32
- self . low = fd;
33
- self . handles . insert ( fd, handle) . unwrap_none ( ) ;
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, 3 ) ;
36
+ let candidate_new_fd = self
37
+ . handles
38
+ . range ( min_fd..)
39
+ . zip ( min_fd..)
40
+ . find_map ( |( ( fd, _fh) , counter) | {
41
+ if * fd != counter {
42
+ // There was a gap in the fds stored, return the first unused one
43
+ // (note that this relies on BTreeMap iterating in key order)
44
+ Some ( counter)
45
+ } else {
46
+ // This fd is used, keep going
47
+ None
48
+ }
49
+ } ) ;
50
+ let new_fd = candidate_new_fd. unwrap_or_else ( || {
51
+ // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
52
+ // maximum fd in the map
53
+ self . handles . keys ( ) . rev ( ) . next ( ) . map ( |last_fd| last_fd + 1 ) . unwrap_or ( min_fd)
54
+ } ) ;
55
+ self . handles . insert ( new_fd, file_handle) . unwrap_none ( ) ;
56
+ new_fd
34
57
}
35
58
}
36
59
37
60
impl Default for FileHandler {
38
61
fn default ( ) -> Self {
39
- FileHandler {
40
- handles : Default :: default ( ) ,
41
- // 0, 1 and 2 are reserved for stdin, stdout and stderr.
42
- low : 2 ,
43
- }
62
+ let mut handles: BTreeMap < i32 , FileHandle > = Default :: default ( ) ;
63
+ // 0, 1 and 2 are reserved for stdin, stdout and stderr.
64
+ handles. insert ( 0 , FileHandle :: StdInPlaceholder ) ;
65
+ handles. insert ( 1 , FileHandle :: StdOutPlaceholder ) ;
66
+ handles. insert ( 2 , FileHandle :: StdErrPlaceholder ) ;
67
+ FileHandler { handles }
44
68
}
45
69
}
46
70
@@ -119,9 +143,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
119
143
120
144
let fd = options. open ( & path) . map ( |file| {
121
145
let fh = & mut this. machine . file_handler ;
122
- let fd = fh. next_fd ( ) ;
123
- fh. register_fd ( fd, FileHandle { file, writable } ) ;
124
- fd
146
+ fh. insert_fd ( FileHandle :: File { file, writable } )
125
147
} ) ;
126
148
127
149
this. try_unwrap_io_result ( fd)
@@ -150,23 +172,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
150
172
} else {
151
173
this. handle_not_found ( )
152
174
}
153
- } else if cmd == this. eval_libc_i32 ( "F_DUPFD" ) ? || cmd == this. eval_libc_i32 ( "F_DUPFD_CLOEXEC" ) ? {
175
+ } else if cmd == this. eval_libc_i32 ( "F_DUPFD" ) ?
176
+ || cmd == this. eval_libc_i32 ( "F_DUPFD_CLOEXEC" ) ?
177
+ {
154
178
// Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
155
179
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
156
180
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
157
181
// thus they can share the same implementation here.
158
- let arg_op = arg_op
159
- . ok_or_else ( || err_unsup_format ! ( "fcntl with command F_DUPFD or F_DUPFD_CLOEXEC requires a third argument" ) ) ?;
182
+ let arg_op = arg_op. ok_or_else ( || {
183
+ err_unsup_format ! (
184
+ "fcntl with command F_DUPFD or F_DUPFD_CLOEXEC requires a third argument"
185
+ )
186
+ } ) ?;
160
187
let arg = this. read_scalar ( arg_op) ?. to_i32 ( ) ?;
161
188
let fh = & mut this. machine . file_handler ;
162
189
let ( file_result, writable) = match fh. handles . get ( & fd) {
163
- Some ( original) => ( original. file . try_clone ( ) , original. writable ) ,
190
+ Some ( FileHandle :: File { file, writable } ) => ( file. try_clone ( ) , * writable) ,
191
+ Some ( _) => throw_unsup_format ! ( "Duplicating file descriptors for stdin, stdout, or stderr is not supported" ) ,
164
192
None => return this. handle_not_found ( ) ,
165
193
} ;
166
194
let fd_result = file_result. map ( |duplicated| {
167
- let new_fd = std:: cmp:: max ( fh. next_fd ( ) , arg) ;
168
- fh. register_fd ( new_fd, FileHandle { file : duplicated, writable } ) ;
169
- new_fd
195
+ fh. insert_fd_with_min_fd ( FileHandle :: File { file : duplicated, writable } , arg)
170
196
} ) ;
171
197
this. try_unwrap_io_result ( fd_result)
172
198
} else {
@@ -181,23 +207,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
181
207
182
208
let fd = this. read_scalar ( fd_op) ?. to_i32 ( ) ?;
183
209
184
- if let Some ( handle) = this. machine . file_handler . handles . remove ( & fd) {
210
+ if fd <= 2 {
211
+ // early return to prevent removing StdInPlaceholder, etc., from the handles map
212
+ return this. handle_not_found ( ) ;
213
+ }
214
+
215
+ if let Some ( FileHandle :: File { file, writable } ) = this. machine . file_handler . handles . remove ( & fd) {
185
216
// We sync the file if it was opened in a mode different than read-only.
186
- if handle . writable {
217
+ if writable {
187
218
// `File::sync_all` does the checks that are done when closing a file. We do this to
188
219
// to handle possible errors correctly.
189
- let result = this. try_unwrap_io_result ( handle . file . sync_all ( ) . map ( |_| 0i32 ) ) ;
220
+ let result = this. try_unwrap_io_result ( file. sync_all ( ) . map ( |_| 0i32 ) ) ;
190
221
// Now we actually close the file.
191
- drop ( handle ) ;
222
+ drop ( file ) ;
192
223
// And return the result.
193
224
result
194
225
} else {
195
226
// We drop the file, this closes it but ignores any errors produced when closing
196
- // it. This is done because `File::sync_call ` cannot be done over files like
227
+ // it. This is done because `File::sync_all ` cannot be done over files like
197
228
// `/dev/urandom` which are read-only. Check
198
229
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
199
230
// discussion.
200
- drop ( handle ) ;
231
+ drop ( file ) ;
201
232
Ok ( 0 )
202
233
}
203
234
} else {
@@ -230,16 +261,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
230
261
// host's and target's `isize`. This saves us from having to handle overflows later.
231
262
let count = count. min ( this. isize_max ( ) as u64 ) . min ( isize:: max_value ( ) as u64 ) ;
232
263
233
- if let Some ( handle ) = this. machine . file_handler . handles . get_mut ( & fd) {
264
+ if let Some ( FileHandle :: File { file , writable : _ } ) = this. machine . file_handler . handles . get_mut ( & fd) {
234
265
// This can never fail because `count` was capped to be smaller than
235
266
// `isize::max_value()`.
236
267
let count = isize:: try_from ( count) . unwrap ( ) ;
237
268
// We want to read at most `count` bytes. We are sure that `count` is not negative
238
269
// because it was a target's `usize`. Also we are sure that its smaller than
239
270
// `usize::max_value()` because it is a host's `isize`.
240
271
let mut bytes = vec ! [ 0 ; count as usize ] ;
241
- let result = handle
242
- . file
272
+ let result = file
243
273
. read ( & mut bytes)
244
274
// `File::read` never returns a value larger than `count`, so this cannot fail.
245
275
. map ( |c| i64:: try_from ( c) . unwrap ( ) ) ;
@@ -285,9 +315,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
285
315
// host's and target's `isize`. This saves us from having to handle overflows later.
286
316
let count = count. min ( this. isize_max ( ) as u64 ) . min ( isize:: max_value ( ) as u64 ) ;
287
317
288
- if let Some ( handle ) = this. machine . file_handler . handles . get_mut ( & fd) {
318
+ if let Some ( FileHandle :: File { file , writable : _ } ) = this. machine . file_handler . handles . get_mut ( & fd) {
289
319
let bytes = this. memory . read_bytes ( buf, Size :: from_bytes ( count) ) ?;
290
- let result = handle . file . write ( & bytes) . map ( |c| i64:: try_from ( c) . unwrap ( ) ) ;
320
+ let result = file. write ( & bytes) . map ( |c| i64:: try_from ( c) . unwrap ( ) ) ;
291
321
this. try_unwrap_io_result ( result)
292
322
} else {
293
323
this. handle_not_found ( )
@@ -320,8 +350,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
320
350
return Ok ( -1 ) ;
321
351
} ;
322
352
323
- if let Some ( handle ) = this. machine . file_handler . handles . get_mut ( & fd) {
324
- let result = handle . file . seek ( seek_from) . map ( |offset| offset as i64 ) ;
353
+ if let Some ( FileHandle :: File { file , writable : _ } ) = this. machine . file_handler . handles . get_mut ( & fd) {
354
+ let result = file. seek ( seek_from) . map ( |offset| offset as i64 ) ;
325
355
this. try_unwrap_io_result ( result)
326
356
} else {
327
357
this. handle_not_found ( )
@@ -682,11 +712,11 @@ impl FileMetadata {
682
712
fd : i32 ,
683
713
) -> InterpResult < ' tcx , Option < FileMetadata > > {
684
714
let option = ecx. machine . file_handler . handles . get ( & fd) ;
685
- let handle = match option {
686
- Some ( handle ) => handle ,
687
- None => return ecx. handle_not_found ( ) . map ( |_: i32 | None ) ,
715
+ let file = match option {
716
+ Some ( FileHandle :: File { file , writable : _ } ) => file ,
717
+ Some ( _ ) | None => return ecx. handle_not_found ( ) . map ( |_: i32 | None ) ,
688
718
} ;
689
- let metadata = handle . file . metadata ( ) ;
719
+ let metadata = file. metadata ( ) ;
690
720
691
721
FileMetadata :: from_meta ( ecx, metadata)
692
722
}
0 commit comments