1
1
use std:: collections:: HashMap ;
2
- use std:: convert:: TryFrom ;
2
+ use std:: convert:: { TryInto , TryFrom } ;
3
3
use std:: fs:: { remove_file, File , OpenOptions } ;
4
4
use std:: io:: { Read , Write } ;
5
+ use std:: path:: PathBuf ;
6
+ use std:: time:: SystemTime ;
5
7
6
- use rustc:: ty:: layout:: { Size , Align } ;
8
+ use rustc:: ty:: layout:: { Size , Align , LayoutOf } ;
7
9
8
10
use crate :: stacked_borrows:: Tag ;
9
11
use crate :: * ;
12
+ use helpers:: immty_from_uint_checked;
13
+ use shims:: time:: system_time_to_duration;
10
14
11
15
#[ derive( Debug ) ]
12
16
pub struct FileHandle {
@@ -98,7 +102,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
98
102
99
103
let path = this. read_os_str_from_c_str ( this. read_scalar ( path_op) ?. not_undef ( ) ?) ?;
100
104
101
- let fd = options. open ( path) . map ( |file| {
105
+ let fd = options. open ( & path) . map ( |file| {
102
106
let mut fh = & mut this. machine . file_handler ;
103
107
fh. low += 1 ;
104
108
fh. handles . insert ( fh. low , FileHandle { file } ) . unwrap_none ( ) ;
@@ -257,6 +261,181 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
257
261
this. try_unwrap_io_result ( result)
258
262
}
259
263
264
+ fn statx (
265
+ & mut self ,
266
+ dirfd_op : OpTy < ' tcx , Tag > , // Should be an `int`
267
+ pathname_op : OpTy < ' tcx , Tag > , // Should be a `const char *`
268
+ flags_op : OpTy < ' tcx , Tag > , // Should be an `int`
269
+ _mask_op : OpTy < ' tcx , Tag > , // Should be an `unsigned int`
270
+ statxbuf_op : OpTy < ' tcx , Tag > // Should be a `struct statx *`
271
+ ) -> InterpResult < ' tcx , i32 > {
272
+ let this = self . eval_context_mut ( ) ;
273
+
274
+ this. check_no_isolation ( "statx" ) ?;
275
+
276
+ let statxbuf_scalar = this. read_scalar ( statxbuf_op) ?. not_undef ( ) ?;
277
+ let pathname_scalar = this. read_scalar ( pathname_op) ?. not_undef ( ) ?;
278
+
279
+ // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
280
+ if this. is_null ( statxbuf_scalar) ? || this. is_null ( pathname_scalar) ? {
281
+ let efault = this. eval_libc ( "EFAULT" ) ?;
282
+ this. set_last_error ( efault) ?;
283
+ return Ok ( -1 ) ;
284
+ }
285
+
286
+ // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
287
+ // proper `MemPlace` and then write the results of this function to it. However, the
288
+ // `syscall` function is untyped. This means that all the `statx` parameters are provided
289
+ // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
290
+ // `statxbuf_op` by using the `libc::statx` struct type.
291
+ let statxbuf_place = {
292
+ // FIXME: This long path is required because `libc::statx` is an struct and also a
293
+ // function and `resolve_path` is returning the latter.
294
+ let statx_ty = this
295
+ . resolve_path ( & [ "libc" , "unix" , "linux_like" , "linux" , "gnu" , "statx" ] ) ?
296
+ . ty ( * this. tcx ) ;
297
+ let statxbuf_ty = this. tcx . mk_mut_ptr ( statx_ty) ;
298
+ let statxbuf_layout = this. layout_of ( statxbuf_ty) ?;
299
+ let statxbuf_imm = ImmTy :: from_scalar ( statxbuf_scalar, statxbuf_layout) ;
300
+ this. ref_to_mplace ( statxbuf_imm) ?
301
+ } ;
302
+
303
+ let path: PathBuf = this. read_os_str_from_c_str ( pathname_scalar) ?. into ( ) ;
304
+ // `flags` should be a `c_int` but the `syscall` function provides an `isize`.
305
+ let flags: i32 = this
306
+ . read_scalar ( flags_op) ?
307
+ . to_machine_isize ( & * this. tcx ) ?
308
+ . try_into ( )
309
+ . map_err ( |e| err_unsup_format ! (
310
+ "Failed to convert pointer sized operand to integer: {}" ,
311
+ e
312
+ ) ) ?;
313
+ // `dirfd` should be a `c_int` but the `syscall` function provides an `isize`.
314
+ let dirfd: i32 = this
315
+ . read_scalar ( dirfd_op) ?
316
+ . to_machine_isize ( & * this. tcx ) ?
317
+ . try_into ( )
318
+ . map_err ( |e| err_unsup_format ! (
319
+ "Failed to convert pointer sized operand to integer: {}" ,
320
+ e
321
+ ) ) ?;
322
+ // we only support interpreting `path` as an absolute directory or as a directory relative
323
+ // to `dirfd` when the latter is `AT_FDCWD`. The behavior of `statx` with a relative path
324
+ // and a directory file descriptor other than `AT_FDCWD` is specified but it cannot be
325
+ // tested from `libstd`. If you found this error, please open an issue reporting it.
326
+ if !( path. is_absolute ( ) || dirfd == this. eval_libc_i32 ( "AT_FDCWD" ) ?)
327
+ {
328
+ throw_unsup_format ! (
329
+ "Using statx with a relative path and a file descriptor different from `AT_FDCWD` is not supported"
330
+ )
331
+ }
332
+
333
+ // the `_mask_op` paramter specifies the file information that the caller requested.
334
+ // However `statx` is allowed to return information that was not requested or to not
335
+ // return information that was requested. This `mask` represents the information we can
336
+ // actually provide in any host platform.
337
+ let mut mask =
338
+ this. eval_libc ( "STATX_TYPE" ) ?. to_u32 ( ) ? | this. eval_libc ( "STATX_SIZE" ) ?. to_u32 ( ) ?;
339
+
340
+ // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
341
+ // symbolic links.
342
+ let metadata = if flags & this. eval_libc ( "AT_SYMLINK_NOFOLLOW" ) ?. to_i32 ( ) ? != 0 {
343
+ // FIXME: metadata for symlinks need testing.
344
+ std:: fs:: symlink_metadata ( path)
345
+ } else {
346
+ std:: fs:: metadata ( path)
347
+ } ;
348
+
349
+ let metadata = match metadata {
350
+ Ok ( metadata) => metadata,
351
+ Err ( e) => {
352
+ this. set_last_error_from_io_error ( e) ?;
353
+ return Ok ( -1 ) ;
354
+ }
355
+ } ;
356
+
357
+ let file_type = metadata. file_type ( ) ;
358
+
359
+ let mode_name = if file_type. is_file ( ) {
360
+ "S_IFREG"
361
+ } else if file_type. is_dir ( ) {
362
+ "S_IFDIR"
363
+ } else {
364
+ "S_IFLNK"
365
+ } ;
366
+
367
+ // The `mode` field specifies the type of the file and the permissions over the file for
368
+ // the owner, its group and other users. Given that we can only provide the file type
369
+ // without using platform specific methods, we only set the bits corresponding to the file
370
+ // type. This should be an `__u16` but `libc` provides its values as `u32`.
371
+ let mode: u16 = this. eval_libc ( mode_name) ?
372
+ . to_u32 ( ) ?
373
+ . try_into ( )
374
+ . unwrap_or_else ( |_| bug ! ( "libc contains bad value for `{}` constant" , mode_name) ) ;
375
+
376
+ let size = metadata. len ( ) ;
377
+
378
+ let ( access_sec, access_nsec) = extract_sec_and_nsec (
379
+ metadata. accessed ( ) ,
380
+ & mut mask,
381
+ this. eval_libc ( "STATX_ATIME" ) ?. to_u32 ( ) ?
382
+ ) ?;
383
+
384
+ let ( created_sec, created_nsec) = extract_sec_and_nsec (
385
+ metadata. created ( ) ,
386
+ & mut mask,
387
+ this. eval_libc ( "STATX_BTIME" ) ?. to_u32 ( ) ?
388
+ ) ?;
389
+
390
+ let ( modified_sec, modified_nsec) = extract_sec_and_nsec (
391
+ metadata. modified ( ) ,
392
+ & mut mask,
393
+ this. eval_libc ( "STATX_MTIME" ) ?. to_u32 ( ) ?
394
+ ) ?;
395
+
396
+ let __u32_layout = this. libc_ty_layout ( "__u32" ) ?;
397
+ let __u64_layout = this. libc_ty_layout ( "__u64" ) ?;
398
+ let __u16_layout = this. libc_ty_layout ( "__u16" ) ?;
399
+
400
+ // Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a
401
+ // zero for the unavailable fields.
402
+ // FIXME: Provide more fields using platform specific methods.
403
+ let imms = [
404
+ immty_from_uint_checked ( mask, __u32_layout) ?, // stx_mask
405
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // stx_blksize
406
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_attributes
407
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // stx_nlink
408
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // stx_uid
409
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // stx_gid
410
+ immty_from_uint_checked ( mode, __u16_layout) ?, // stx_mode
411
+ immty_from_uint_checked ( 0u128 , __u16_layout) ?, // statx padding
412
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_ino
413
+ immty_from_uint_checked ( size, __u64_layout) ?, // stx_size
414
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_blocks
415
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_attributes
416
+ immty_from_uint_checked ( access_sec, __u64_layout) ?, // stx_atime.tv_sec
417
+ immty_from_uint_checked ( access_nsec, __u32_layout) ?, // stx_atime.tv_nsec
418
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // statx_timestamp padding
419
+ immty_from_uint_checked ( created_sec, __u64_layout) ?, // stx_btime.tv_sec
420
+ immty_from_uint_checked ( created_nsec, __u32_layout) ?, // stx_btime.tv_nsec
421
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // statx_timestamp padding
422
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_ctime.tv_sec
423
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // stx_ctime.tv_nsec
424
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // statx_timestamp padding
425
+ immty_from_uint_checked ( modified_sec, __u64_layout) ?, // stx_mtime.tv_sec
426
+ immty_from_uint_checked ( modified_nsec, __u32_layout) ?, // stx_mtime.tv_nsec
427
+ immty_from_uint_checked ( 0u128 , __u32_layout) ?, // statx_timestamp padding
428
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_rdev_major
429
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_rdev_minor
430
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_dev_major
431
+ immty_from_uint_checked ( 0u128 , __u64_layout) ?, // stx_dev_minor
432
+ ] ;
433
+
434
+ this. write_packed_immediates ( & statxbuf_place, & imms) ?;
435
+
436
+ Ok ( 0 )
437
+ }
438
+
260
439
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
261
440
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
262
441
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
@@ -268,3 +447,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
268
447
Ok ( ( -1 ) . into ( ) )
269
448
}
270
449
}
450
+
451
+ // Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch, and
452
+ // then sets the `mask` bits determined by `flag` when `time` is Ok. If `time` is an error, it
453
+ // returns `(0, 0)` without setting any bits.
454
+ fn extract_sec_and_nsec < ' tcx > ( time : std:: io:: Result < SystemTime > , mask : & mut u32 , flag : u32 ) -> InterpResult < ' tcx , ( u64 , u32 ) > {
455
+ if let Ok ( time) = time {
456
+ let duration = system_time_to_duration ( & time) ?;
457
+ * mask |= flag;
458
+ Ok ( ( duration. as_secs ( ) , duration. subsec_nanos ( ) ) )
459
+ } else {
460
+ Ok ( ( 0 , 0 ) )
461
+ }
462
+ }
0 commit comments