11use std:: collections:: HashMap ;
2- use std:: convert:: TryFrom ;
2+ use std:: convert:: { TryInto , TryFrom } ;
33use std:: fs:: { remove_file, File , OpenOptions } ;
44use std:: io:: { Read , Write } ;
5+ use std:: path:: PathBuf ;
6+ use std:: time:: SystemTime ;
57
6- use rustc:: ty:: layout:: { Size , Align } ;
8+ use rustc:: ty:: layout:: { Size , Align , LayoutOf } ;
79
810use crate :: stacked_borrows:: Tag ;
911use crate :: * ;
12+ use helpers:: immty_from_uint_checked;
13+ use shims:: time:: system_time_to_duration;
1014
1115#[ derive( Debug ) ]
1216pub struct FileHandle {
@@ -98,7 +102,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
98102
99103 let path = this. read_os_str_from_c_str ( this. read_scalar ( path_op) ?. not_undef ( ) ?) ?;
100104
101- let fd = options. open ( path) . map ( |file| {
105+ let fd = options. open ( & path) . map ( |file| {
102106 let mut fh = & mut this. machine . file_handler ;
103107 fh. low += 1 ;
104108 fh. handles . insert ( fh. low , FileHandle { file } ) . unwrap_none ( ) ;
@@ -257,6 +261,181 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
257261 this. try_unwrap_io_result ( result)
258262 }
259263
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+
260439 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
261440 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
262441 /// `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
268447 Ok ( ( -1 ) . into ( ) )
269448 }
270449}
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