From d9ecd77adafc669854e5b74a790b243ac6f0ec5f Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Tue, 24 Dec 2019 11:01:01 -0500 Subject: [PATCH 01/13] add dummy stat shim --- src/shims/foreign_items.rs | 5 ++++ src/shims/fs.rs | 49 ++++++++++++++++++++++++++++++++++++++ tests/run-pass/fs.rs | 7 ------ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 1a52d8721470..9092828a1556 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -494,6 +494,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; } + "stat64" => { + let result = this.stat(args[0], args[1])?; + this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; + } + "clock_gettime" => { let result = this.clock_gettime(args[0], args[1])?; this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; diff --git a/src/shims/fs.rs b/src/shims/fs.rs index 0b78111533a5..d891d132f24f 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -261,6 +261,55 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.try_unwrap_io_result(result) } + fn stat(&mut self, + _path_op: OpTy<'tcx, Tag>, + buf_op: OpTy<'tcx, Tag>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let buf = this.deref_operand(buf_op)?; + + let dev_t_layout = this.libc_ty_layout("dev_t")?; + let mode_t_layout = this.libc_ty_layout("mode_t")?; + let nlink_t_layout = this.libc_ty_layout("nlink_t")?; + let ino_t_layout = this.libc_ty_layout("ino_t")?; + let uid_t_layout = this.libc_ty_layout("uid_t")?; + let gid_t_layout = this.libc_ty_layout("gid_t")?; + let time_t_layout = this.libc_ty_layout("time_t")?; + let long_layout = this.libc_ty_layout("c_long")?; + let off_t_layout = this.libc_ty_layout("off_t")?; + let blkcnt_t_layout = this.libc_ty_layout("blkcnt_t")?; + let blksize_t_layout = this.libc_ty_layout("blksize_t")?; + let uint32_t_layout = this.libc_ty_layout("uint32_t")?; + + let imms = [ + immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev + immty_from_uint_checked(0u128, mode_t_layout)?, // st_mode + immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink + immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino + immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid + immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid + immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev + immty_from_uint_checked(0u128, time_t_layout)?, // st_atime + immty_from_uint_checked(0u128, long_layout)?, // st_atime_nsec + immty_from_uint_checked(0u128, time_t_layout)?, // st_mtime + immty_from_uint_checked(0u128, long_layout)?, // st_mtime_nsec + immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime + immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec + immty_from_uint_checked(0u128, time_t_layout)?, // st_birthtime + immty_from_uint_checked(0u128, long_layout)?, // st_birthtime_nsec + immty_from_uint_checked(0u128, off_t_layout)?, // st_size + immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks + immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize + immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags + immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen + ]; + + this.write_packed_immediates(&buf, &imms)?; + + Ok(0) + } + fn statx( &mut self, dirfd_op: OpTy<'tcx, Tag>, // Should be an `int` diff --git a/tests/run-pass/fs.rs b/tests/run-pass/fs.rs index b8f9e3229af6..9f000961d5a0 100644 --- a/tests/run-pass/fs.rs +++ b/tests/run-pass/fs.rs @@ -5,7 +5,6 @@ use std::fs::{File, remove_file}; use std::io::{Read, Write, ErrorKind, Result}; use std::path::{PathBuf, Path}; -#[cfg(target_os = "linux")] fn test_metadata(bytes: &[u8], path: &Path) -> Result<()> { // Test that the file metadata is correct. let metadata = path.metadata()?; @@ -16,12 +15,6 @@ fn test_metadata(bytes: &[u8], path: &Path) -> Result<()> { Ok(()) } -// FIXME: Implement stat64 for macos. -#[cfg(not(target_os = "linux"))] -fn test_metadata(_bytes: &[u8], _path: &Path) -> Result<()> { - Ok(()) -} - fn main() { let tmp = std::env::temp_dir(); let filename = PathBuf::from("miri_test_fs.txt"); From 6177e6df7e212340ad194319d698f321b44bb35a Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Tue, 24 Dec 2019 11:21:00 -0500 Subject: [PATCH 02/13] provide correct name for shim --- src/shims/foreign_items.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 9092828a1556..51c43b28b68a 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -494,7 +494,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; } - "stat64" => { + "stat$INODE64" => { let result = this.stat(args[0], args[1])?; this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; } From b2c4ff2aee08854d13b33877a4275307e27091ee Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Tue, 24 Dec 2019 11:53:03 -0500 Subject: [PATCH 03/13] add remanining fields to stat stuct --- src/shims/fs.rs | 64 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index d891d132f24f..8b102bf04abf 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -261,14 +261,58 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.try_unwrap_io_result(result) } - fn stat(&mut self, - _path_op: OpTy<'tcx, Tag>, + fn stat( + &mut self, + path_op: OpTy<'tcx, Tag>, buf_op: OpTy<'tcx, Tag>, ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); + let path_scalar = this.read_scalar(path_op)?.not_undef()?; + let path = this.read_os_str_from_c_str(path_scalar)?; + let buf = this.deref_operand(buf_op)?; + let metadata = match std::fs::metadata(path) { + Ok(metadata) => metadata, + Err(e) => { + this.set_last_error_from_io_error(e)?; + return Ok(-1); + } + }; + + let file_type = metadata.file_type(); + + let mode_name = if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else { + "S_IFLNK" + }; + + let mode = this.eval_libc(mode_name)?.to_u32()?; + + let size = metadata.len(); + + let (access_sec, access_nsec) = extract_sec_and_nsec( + metadata.accessed(), + &mut 0, + 0, + )?; + + let (created_sec, created_nsec) = extract_sec_and_nsec( + metadata.created(), + &mut 0, + 0, + )?; + + let (modified_sec, modified_nsec) = extract_sec_and_nsec( + metadata.modified(), + &mut 0, + 0, + )?; + let dev_t_layout = this.libc_ty_layout("dev_t")?; let mode_t_layout = this.libc_ty_layout("mode_t")?; let nlink_t_layout = this.libc_ty_layout("nlink_t")?; @@ -284,21 +328,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let imms = [ immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev - immty_from_uint_checked(0u128, mode_t_layout)?, // st_mode + immty_from_uint_checked(mode, mode_t_layout)?, // st_mode immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev - immty_from_uint_checked(0u128, time_t_layout)?, // st_atime - immty_from_uint_checked(0u128, long_layout)?, // st_atime_nsec - immty_from_uint_checked(0u128, time_t_layout)?, // st_mtime - immty_from_uint_checked(0u128, long_layout)?, // st_mtime_nsec + immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime + immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec + immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime + immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec - immty_from_uint_checked(0u128, time_t_layout)?, // st_birthtime - immty_from_uint_checked(0u128, long_layout)?, // st_birthtime_nsec - immty_from_uint_checked(0u128, off_t_layout)?, // st_size + immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime + immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec + immty_from_uint_checked(size, off_t_layout)?, // st_size immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags From 0184e10f2f1cfcdf845c680e9fd43b07c16483bd Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Tue, 24 Dec 2019 12:10:36 -0500 Subject: [PATCH 04/13] fix size for file mode --- src/shims/fs.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index 8b102bf04abf..b3d16dba6e6e 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -291,27 +291,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx "S_IFLNK" }; - let mode = this.eval_libc(mode_name)?.to_u32()?; + let mode = this.eval_libc(mode_name)?.to_bits(Size::from_bits(16))? as u16; let size = metadata.len(); - let (access_sec, access_nsec) = extract_sec_and_nsec( - metadata.accessed(), - &mut 0, - 0, - )?; - - let (created_sec, created_nsec) = extract_sec_and_nsec( - metadata.created(), - &mut 0, - 0, - )?; - - let (modified_sec, modified_nsec) = extract_sec_and_nsec( - metadata.modified(), - &mut 0, - 0, - )?; + let (access_sec, access_nsec) = extract_sec_and_nsec(metadata.accessed(), &mut 0, 0)?; + let (created_sec, created_nsec) = extract_sec_and_nsec(metadata.created(), &mut 0, 0)?; + let (modified_sec, modified_nsec) = extract_sec_and_nsec(metadata.modified(), &mut 0, 0)?; let dev_t_layout = this.libc_ty_layout("dev_t")?; let mode_t_layout = this.libc_ty_layout("mode_t")?; From dbc118919aea52645cb278d4db6d38612a448095 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 10:32:34 -0500 Subject: [PATCH 05/13] add padding to immediates --- src/shims/fs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index b3d16dba6e6e..d4a000305f64 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -320,6 +320,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev + immty_from_uint_checked(0u128, dev_t_layout)?, // padding immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime From 75f7a118e63d6408b5cad81fe25f062405608f14 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 11:27:25 -0500 Subject: [PATCH 06/13] remove restrictions due to `stat` unavailability --- src/shims/env.rs | 3 +-- tests/run-pass/fs.rs | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/shims/env.rs b/src/shims/env.rs index 3994cf78780a..1e655ca821d8 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -20,8 +20,7 @@ impl EnvVars { ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>, mut excluded_env_vars: Vec, ) { - - // FIXME: this can be removed when we have the `stat64` shim for macos. + // FIXME: this can be removed when we fix the behavior of the `close` shim for macos. if ecx.tcx.sess.target.target.target_os.to_lowercase() != "linux" { // Exclude `TERM` var to avoid terminfo trying to open the termcap file. excluded_env_vars.push("TERM".to_owned()); diff --git a/tests/run-pass/fs.rs b/tests/run-pass/fs.rs index 9f000961d5a0..85e39bc45112 100644 --- a/tests/run-pass/fs.rs +++ b/tests/run-pass/fs.rs @@ -51,7 +51,5 @@ fn main() { // Removing a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind()); // Reading the metadata of a non-existing file should fail with a "not found" error. - if cfg!(target_os = "linux") { // FIXME: Implement stat64 for macos. - assert_eq!(ErrorKind::NotFound, test_metadata(bytes, &path).unwrap_err().kind()); - } + assert_eq!(ErrorKind::NotFound, test_metadata(bytes, &path).unwrap_err().kind()); } From 6d88a4704a030b2c963abf38be481c1805b407d0 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 11:30:01 -0500 Subject: [PATCH 07/13] restrict `stat` shim to macos only --- src/shims/fs.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index d4a000305f64..78ec2169fd68 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -268,6 +268,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); + if this.tcx.sess.target.target.target_os.to_lowercase() != "macos" { + throw_unsup_format!("The `stat` shim is only only available in the `macos` platform.") + } + let path_scalar = this.read_scalar(path_op)?.not_undef()?; let path = this.read_os_str_from_c_str(path_scalar)?; From 515c11935969f6bffc42ab2e4cece862f109bb31 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 11:39:57 -0500 Subject: [PATCH 08/13] Add padding on 64-bits only --- src/shims/fs.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index 78ec2169fd68..adc60a52e6d1 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -295,6 +295,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx "S_IFLNK" }; + // FIXME: use Scalar::to_u16 let mode = this.eval_libc(mode_name)?.to_bits(Size::from_bits(16))? as u16; let size = metadata.len(); @@ -316,6 +317,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let blksize_t_layout = this.libc_ty_layout("blksize_t")?; let uint32_t_layout = this.libc_ty_layout("uint32_t")?; + // We need to add 32 bits of padding after `st_rdev` if we are in a 64-bit platform. To do + // this, we store `st_rdev` as a `c_long` instead of a `dev_t`. + let st_rdev_layout = if this.tcx.sess.target.ptr_width == 64 { + long_layout + } else { + dev_t_layout + }; + let imms = [ immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev immty_from_uint_checked(mode, mode_t_layout)?, // st_mode @@ -323,8 +332,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid - immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev - immty_from_uint_checked(0u128, dev_t_layout)?, // padding + immty_from_uint_checked(0u128, st_rdev_layout)?, // st_rdev immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime From d17625900234f9643180075ea44756c98a92819a Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 16:09:54 -0500 Subject: [PATCH 09/13] deduplicate shared code between stat and statx --- src/shims/fs.rs | 173 +++++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 81 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index adc60a52e6d1..a5bac27cc449 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -269,40 +269,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let this = self.eval_context_mut(); if this.tcx.sess.target.target.target_os.to_lowercase() != "macos" { - throw_unsup_format!("The `stat` shim is only only available in the `macos` platform.") + throw_unsup_format!("The `stat` shim is only only available for `macos` targets.") } let path_scalar = this.read_scalar(path_op)?.not_undef()?; - let path = this.read_os_str_from_c_str(path_scalar)?; + let path: PathBuf = this.read_os_str_from_c_str(path_scalar)?.into(); let buf = this.deref_operand(buf_op)?; - let metadata = match std::fs::metadata(path) { - Ok(metadata) => metadata, - Err(e) => { - this.set_last_error_from_io_error(e)?; - return Ok(-1); - } - }; - - let file_type = metadata.file_type(); - - let mode_name = if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else { - "S_IFLNK" + let stats = match FileStatus::new(this, path, false)? { + Some(stats) => stats, + None => return Ok(-1), }; // FIXME: use Scalar::to_u16 - let mode = this.eval_libc(mode_name)?.to_bits(Size::from_bits(16))? as u16; + let mode: u16 = stats.mode.to_bits(Size::from_bits(16))? as u16; - let size = metadata.len(); - - let (access_sec, access_nsec) = extract_sec_and_nsec(metadata.accessed(), &mut 0, 0)?; - let (created_sec, created_nsec) = extract_sec_and_nsec(metadata.created(), &mut 0, 0)?; - let (modified_sec, modified_nsec) = extract_sec_and_nsec(metadata.modified(), &mut 0, 0)?; + let (access_sec, access_nsec) = stats.accessed.unwrap_or((0, 0)); + let (created_sec, created_nsec) = stats.created.unwrap_or((0, 0)); + let (modified_sec, modified_nsec) = stats.modified.unwrap_or((0, 0)); let dev_t_layout = this.libc_ty_layout("dev_t")?; let mode_t_layout = this.libc_ty_layout("mode_t")?; @@ -341,7 +326,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec - immty_from_uint_checked(size, off_t_layout)?, // st_size + immty_from_uint_checked(stats.size, off_t_layout)?, // st_size immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags @@ -365,6 +350,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.check_no_isolation("statx")?; + if this.tcx.sess.target.target.target_os.to_lowercase() != "linux" { + throw_unsup_format!("The `statx` shim is only only available for `linux` targets.") + } + let statxbuf_scalar = this.read_scalar(statxbuf_op)?.not_undef()?; let pathname_scalar = this.read_scalar(pathname_op)?.not_undef()?; @@ -422,60 +411,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following // symbolic links. - let metadata = if flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? != 0 { - // FIXME: metadata for symlinks need testing. - std::fs::symlink_metadata(path) - } else { - std::fs::metadata(path) - }; + let is_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? != 0; - let metadata = match metadata { - Ok(metadata) => metadata, - Err(e) => { - this.set_last_error_from_io_error(e)?; - return Ok(-1); - } - }; - - let file_type = metadata.file_type(); - - let mode_name = if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else { - "S_IFLNK" + let stats = match FileStatus::new(this, path, is_symlink)? { + Some(stats) => stats, + None => return Ok(-1), }; // The `mode` field specifies the type of the file and the permissions over the file for // the owner, its group and other users. Given that we can only provide the file type // without using platform specific methods, we only set the bits corresponding to the file // type. This should be an `__u16` but `libc` provides its values as `u32`. - let mode: u16 = this - .eval_libc(mode_name)? + let mode: u16 = stats + .mode .to_u32()? .try_into() - .unwrap_or_else(|_| bug!("libc contains bad value for `{}` constant", mode_name)); + .unwrap_or_else(|_| bug!("libc contains bad value for constant")); - let size = metadata.len(); + let (access_sec, access_nsec) = if let Some(tup) = stats.accessed { + tup + } else { + mask |= this.eval_libc("STATX_ATIME")?.to_u32()?; + (0, 0) + }; - let (access_sec, access_nsec) = extract_sec_and_nsec( - metadata.accessed(), - &mut mask, - this.eval_libc("STATX_ATIME")?.to_u32()?, - )?; + let (created_sec, created_nsec) = if let Some(tup) = stats.created { + tup + } else { + mask |= this.eval_libc("STATX_BTIME")?.to_u32()?; + (0, 0) + }; - let (created_sec, created_nsec) = extract_sec_and_nsec( - metadata.created(), - &mut mask, - this.eval_libc("STATX_BTIME")?.to_u32()?, - )?; - - let (modified_sec, modified_nsec) = extract_sec_and_nsec( - metadata.modified(), - &mut mask, - this.eval_libc("STATX_MTIME")?.to_u32()?, - )?; + let (modified_sec, modified_nsec) = if let Some(tup) = stats.modified { + tup + } else { + mask |= this.eval_libc("STATX_MTIME")?.to_u32()?; + (0, 0) + }; let __u32_layout = this.libc_ty_layout("__u32")?; let __u64_layout = this.libc_ty_layout("__u64")?; @@ -483,7 +455,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a // zero for the unavailable fields. - // FIXME: Provide more fields using platform specific methods. let imms = [ immty_from_uint_checked(mask, __u32_layout)?, // stx_mask immty_from_uint_checked(0u128, __u32_layout)?, // stx_blksize @@ -494,7 +465,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(mode, __u16_layout)?, // stx_mode immty_from_uint_checked(0u128, __u16_layout)?, // statx padding immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino - immty_from_uint_checked(size, __u64_layout)?, // stx_size + immty_from_uint_checked(stats.size, __u64_layout)?, // stx_size immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec @@ -532,19 +503,59 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } } -// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch, and -// then sets the `mask` bits determined by `flag` when `time` is Ok. If `time` is an error, it -// returns `(0, 0)` without setting any bits. -fn extract_sec_and_nsec<'tcx>( - time: std::io::Result, - mask: &mut u32, - flag: u32, -) -> InterpResult<'tcx, (u64, u32)> { - if let Ok(time) = time { +// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when +// `time` is Ok. If `time` is an error, it returns `None`. +fn extract_sec_and_nsec<'tcx>(time: std::io::Result) -> InterpResult<'tcx, Option<(u64, u32)>> { + time.ok().map(|time| { let duration = system_time_to_duration(&time)?; - *mask |= flag; Ok((duration.as_secs(), duration.subsec_nanos())) - } else { - Ok((0, 0)) + }).transpose() +} + +struct FileStatus { + mode: Scalar, + size: u64, + created: Option<(u64, u32)>, + accessed: Option<(u64, u32)>, + modified: Option<(u64, u32)>, +} + +impl FileStatus { + fn new<'tcx, 'mir>(ecx: &mut MiriEvalContext<'mir, 'tcx>, path: PathBuf, is_symlink: bool) -> InterpResult<'tcx, Option> { + let metadata = if is_symlink { + // FIXME: metadata for symlinks need testing. + std::fs::symlink_metadata(path) + } else { + std::fs::metadata(path) + }; + + let metadata = match metadata { + Ok(metadata) => metadata, + Err(e) => { + ecx.set_last_error_from_io_error(e)?; + return Ok(None); + } + }; + + let file_type = metadata.file_type(); + + let mode_name = if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else { + "S_IFLNK" + }; + + let mode = ecx.eval_libc(mode_name)?; + + let size = metadata.len(); + + let created = extract_sec_and_nsec(metadata.created())?; + let accessed = extract_sec_and_nsec(metadata.accessed())?; + let modified = extract_sec_and_nsec(metadata.modified())?; + + // FIXME: Provide more fields using platform specific methods. + Ok(Some(FileStatus { mode, size, created, accessed, modified })) } } From 1bc362908440792123d8b8022f6e09a7401389f2 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 18:22:33 -0500 Subject: [PATCH 10/13] do padding correctly --- src/shims/fs.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index a5bac27cc449..8cd1afd02c9f 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -302,12 +302,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let blksize_t_layout = this.libc_ty_layout("blksize_t")?; let uint32_t_layout = this.libc_ty_layout("uint32_t")?; - // We need to add 32 bits of padding after `st_rdev` if we are in a 64-bit platform. To do - // this, we store `st_rdev` as a `c_long` instead of a `dev_t`. - let st_rdev_layout = if this.tcx.sess.target.ptr_width == 64 { - long_layout + // We need to add 32 bits of padding after `st_rdev` if we are in a 64-bit platform. + let pad_layout = if this.tcx.sess.target.ptr_width == 64 { + uint32_t_layout } else { - dev_t_layout + this.layout_of(this.tcx.mk_unit())? }; let imms = [ @@ -317,7 +316,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid - immty_from_uint_checked(0u128, st_rdev_layout)?, // st_rdev + immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev + immty_from_uint_checked(0u128, pad_layout)?, // padding for 64-bit targets immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime From bbbb50a09aadfc53df8ed4810d9ff67b194b2492 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 25 Dec 2019 22:22:25 -0500 Subject: [PATCH 11/13] set mask for statx correctly --- src/shims/fs.rs | 52 ++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index 8cd1afd02c9f..3084d977de82 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -277,17 +277,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let buf = this.deref_operand(buf_op)?; - let stats = match FileStatus::new(this, path, false)? { - Some(stats) => stats, + let status = match FileStatus::new(this, path, false)? { + Some(status) => status, None => return Ok(-1), }; // FIXME: use Scalar::to_u16 - let mode: u16 = stats.mode.to_bits(Size::from_bits(16))? as u16; + let mode: u16 = status.mode.to_bits(Size::from_bits(16))? as u16; - let (access_sec, access_nsec) = stats.accessed.unwrap_or((0, 0)); - let (created_sec, created_nsec) = stats.created.unwrap_or((0, 0)); - let (modified_sec, modified_nsec) = stats.modified.unwrap_or((0, 0)); + let (access_sec, access_nsec) = status.accessed.unwrap_or((0, 0)); + let (created_sec, created_nsec) = status.created.unwrap_or((0, 0)); + let (modified_sec, modified_nsec) = status.modified.unwrap_or((0, 0)); let dev_t_layout = this.libc_ty_layout("dev_t")?; let mode_t_layout = this.libc_ty_layout("mode_t")?; @@ -326,7 +326,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec - immty_from_uint_checked(stats.size, off_t_layout)?, // st_size + immty_from_uint_checked(status.size, off_t_layout)?, // st_size immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags @@ -413,8 +413,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // symbolic links. let is_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? != 0; - let stats = match FileStatus::new(this, path, is_symlink)? { - Some(stats) => stats, + let status = match FileStatus::new(this, path, is_symlink)? { + Some(status) => status, None => return Ok(-1), }; @@ -422,32 +422,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // the owner, its group and other users. Given that we can only provide the file type // without using platform specific methods, we only set the bits corresponding to the file // type. This should be an `__u16` but `libc` provides its values as `u32`. - let mode: u16 = stats + let mode: u16 = status .mode .to_u32()? .try_into() .unwrap_or_else(|_| bug!("libc contains bad value for constant")); - let (access_sec, access_nsec) = if let Some(tup) = stats.accessed { - tup - } else { + let (access_sec, access_nsec) = status.accessed.map(|tup| { mask |= this.eval_libc("STATX_ATIME")?.to_u32()?; - (0, 0) - }; + InterpResult::Ok(tup) + }).unwrap_or(Ok((0, 0)))?; - let (created_sec, created_nsec) = if let Some(tup) = stats.created { - tup - } else { + let (created_sec, created_nsec) = status.created.map(|tup| { mask |= this.eval_libc("STATX_BTIME")?.to_u32()?; - (0, 0) - }; + InterpResult::Ok(tup) + }).unwrap_or(Ok((0, 0)))?; - let (modified_sec, modified_nsec) = if let Some(tup) = stats.modified { - tup - } else { + let (modified_sec, modified_nsec) = status.modified.map(|tup| { mask |= this.eval_libc("STATX_MTIME")?.to_u32()?; - (0, 0) - }; + InterpResult::Ok(tup) + }).unwrap_or(Ok((0, 0)))?; let __u32_layout = this.libc_ty_layout("__u32")?; let __u64_layout = this.libc_ty_layout("__u64")?; @@ -465,7 +459,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(mode, __u16_layout)?, // stx_mode immty_from_uint_checked(0u128, __u16_layout)?, // statx padding immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino - immty_from_uint_checked(stats.size, __u64_layout)?, // stx_size + immty_from_uint_checked(status.size, __u64_layout)?, // stx_size immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec @@ -521,7 +515,11 @@ struct FileStatus { } impl FileStatus { - fn new<'tcx, 'mir>(ecx: &mut MiriEvalContext<'mir, 'tcx>, path: PathBuf, is_symlink: bool) -> InterpResult<'tcx, Option> { + fn new<'tcx, 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + path: PathBuf, + is_symlink: bool + ) -> InterpResult<'tcx, Option> { let metadata = if is_symlink { // FIXME: metadata for symlinks need testing. std::fs::symlink_metadata(path) From 2151e958ceba1fcbe906eebf0b42855a0a20178f Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Thu, 26 Dec 2019 12:12:19 -0500 Subject: [PATCH 12/13] minor fixes and updated docs --- src/shims/fs.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index 3084d977de82..f4a792a0410c 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -411,9 +411,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following // symbolic links. - let is_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? != 0; + let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0; - let status = match FileStatus::new(this, path, is_symlink)? { + let status = match FileStatus::new(this, path, follow_symlink)? { Some(status) => status, None => return Ok(-1), }; @@ -428,6 +428,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx .try_into() .unwrap_or_else(|_| bug!("libc contains bad value for constant")); + // We need to set the corresponding bits of `mask` if the access, creation and modification + // times were available. Otherwise we let them be zero. let (access_sec, access_nsec) = status.accessed.map(|tup| { mask |= this.eval_libc("STATX_ATIME")?.to_u32()?; InterpResult::Ok(tup) @@ -497,9 +499,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } } -// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when -// `time` is Ok. If `time` is an error, it returns `None`. -fn extract_sec_and_nsec<'tcx>(time: std::io::Result) -> InterpResult<'tcx, Option<(u64, u32)>> { +/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when +/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix +/// epoch. +fn extract_sec_and_nsec<'tcx>( + time: std::io::Result +) -> InterpResult<'tcx, Option<(u64, u32)>> { time.ok().map(|time| { let duration = system_time_to_duration(&time)?; Ok((duration.as_secs(), duration.subsec_nanos())) @@ -518,9 +523,9 @@ impl FileStatus { fn new<'tcx, 'mir>( ecx: &mut MiriEvalContext<'mir, 'tcx>, path: PathBuf, - is_symlink: bool + follow_symlink: bool ) -> InterpResult<'tcx, Option> { - let metadata = if is_symlink { + let metadata = if follow_symlink { // FIXME: metadata for symlinks need testing. std::fs::symlink_metadata(path) } else { From c8190e8de79b22843c4d0976def95aee120f0329 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Thu, 26 Dec 2019 13:30:04 -0500 Subject: [PATCH 13/13] rename metadata struct --- src/shims/fs.rs | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/shims/fs.rs b/src/shims/fs.rs index f4a792a0410c..63aa750bdd54 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -269,7 +269,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let this = self.eval_context_mut(); if this.tcx.sess.target.target.target_os.to_lowercase() != "macos" { - throw_unsup_format!("The `stat` shim is only only available for `macos` targets.") + throw_unsup_format!("The `stat` shim is only available for `macos` targets.") } let path_scalar = this.read_scalar(path_op)?.not_undef()?; @@ -277,17 +277,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let buf = this.deref_operand(buf_op)?; - let status = match FileStatus::new(this, path, false)? { - Some(status) => status, + // `stat` always follows symlinks. `lstat` is used to get symlink metadata. + let metadata = match FileMetadata::new(this, path, true)? { + Some(metadata) => metadata, None => return Ok(-1), }; // FIXME: use Scalar::to_u16 - let mode: u16 = status.mode.to_bits(Size::from_bits(16))? as u16; + let mode: u16 = metadata.mode.to_bits(Size::from_bits(16))? as u16; - let (access_sec, access_nsec) = status.accessed.unwrap_or((0, 0)); - let (created_sec, created_nsec) = status.created.unwrap_or((0, 0)); - let (modified_sec, modified_nsec) = status.modified.unwrap_or((0, 0)); + let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0)); + let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0)); + let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); let dev_t_layout = this.libc_ty_layout("dev_t")?; let mode_t_layout = this.libc_ty_layout("mode_t")?; @@ -302,7 +303,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let blksize_t_layout = this.libc_ty_layout("blksize_t")?; let uint32_t_layout = this.libc_ty_layout("uint32_t")?; - // We need to add 32 bits of padding after `st_rdev` if we are in a 64-bit platform. + // We need to add 32 bits of padding after `st_rdev` if we are on a 64-bit platform. let pad_layout = if this.tcx.sess.target.ptr_width == 64 { uint32_t_layout } else { @@ -326,7 +327,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec - immty_from_uint_checked(status.size, off_t_layout)?, // st_size + immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags @@ -351,7 +352,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.check_no_isolation("statx")?; if this.tcx.sess.target.target.target_os.to_lowercase() != "linux" { - throw_unsup_format!("The `statx` shim is only only available for `linux` targets.") + throw_unsup_format!("The `statx` shim is only available for `linux` targets.") } let statxbuf_scalar = this.read_scalar(statxbuf_op)?.not_undef()?; @@ -413,8 +414,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // symbolic links. let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0; - let status = match FileStatus::new(this, path, follow_symlink)? { - Some(status) => status, + let metadata = match FileMetadata::new(this, path, follow_symlink)? { + Some(metadata) => metadata, None => return Ok(-1), }; @@ -422,7 +423,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // the owner, its group and other users. Given that we can only provide the file type // without using platform specific methods, we only set the bits corresponding to the file // type. This should be an `__u16` but `libc` provides its values as `u32`. - let mode: u16 = status + let mode: u16 = metadata .mode .to_u32()? .try_into() @@ -430,17 +431,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // We need to set the corresponding bits of `mask` if the access, creation and modification // times were available. Otherwise we let them be zero. - let (access_sec, access_nsec) = status.accessed.map(|tup| { + let (access_sec, access_nsec) = metadata.accessed.map(|tup| { mask |= this.eval_libc("STATX_ATIME")?.to_u32()?; InterpResult::Ok(tup) }).unwrap_or(Ok((0, 0)))?; - let (created_sec, created_nsec) = status.created.map(|tup| { + let (created_sec, created_nsec) = metadata.created.map(|tup| { mask |= this.eval_libc("STATX_BTIME")?.to_u32()?; InterpResult::Ok(tup) }).unwrap_or(Ok((0, 0)))?; - let (modified_sec, modified_nsec) = status.modified.map(|tup| { + let (modified_sec, modified_nsec) = metadata.modified.map(|tup| { mask |= this.eval_libc("STATX_MTIME")?.to_u32()?; InterpResult::Ok(tup) }).unwrap_or(Ok((0, 0)))?; @@ -461,7 +462,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx immty_from_uint_checked(mode, __u16_layout)?, // stx_mode immty_from_uint_checked(0u128, __u16_layout)?, // statx padding immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino - immty_from_uint_checked(status.size, __u64_layout)?, // stx_size + immty_from_uint_checked(metadata.size, __u64_layout)?, // stx_size immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec @@ -511,7 +512,9 @@ fn extract_sec_and_nsec<'tcx>( }).transpose() } -struct FileStatus { +/// Stores a file's metadata in order to avoid code duplication in the different metadata related +/// shims. +struct FileMetadata { mode: Scalar, size: u64, created: Option<(u64, u32)>, @@ -519,17 +522,17 @@ struct FileStatus { modified: Option<(u64, u32)>, } -impl FileStatus { +impl FileMetadata { fn new<'tcx, 'mir>( ecx: &mut MiriEvalContext<'mir, 'tcx>, path: PathBuf, follow_symlink: bool - ) -> InterpResult<'tcx, Option> { + ) -> InterpResult<'tcx, Option> { let metadata = if follow_symlink { + std::fs::metadata(path) + } else { // FIXME: metadata for symlinks need testing. std::fs::symlink_metadata(path) - } else { - std::fs::metadata(path) }; let metadata = match metadata { @@ -559,6 +562,6 @@ impl FileStatus { let modified = extract_sec_and_nsec(metadata.modified())?; // FIXME: Provide more fields using platform specific methods. - Ok(Some(FileStatus { mode, size, created, accessed, modified })) + Ok(Some(FileMetadata { mode, size, created, accessed, modified })) } }