diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 2d6eafdf8f1..deeb37da44a 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -494,11 +494,21 @@ fn emulate_foreign_item( this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; } + "symlink" => { + let result = this.symlink(args[0], args[1])?; + this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; + } + "stat$INODE64" => { let result = this.stat(args[0], args[1])?; this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; } + "lstat$INODE64" => { + let result = this.lstat(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 85836968289..8a7cf9d31c0 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -276,15 +276,68 @@ fn unlink(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { this.try_unwrap_io_result(result) } + fn symlink( + &mut self, + target_op: OpTy<'tcx, Tag>, + linkpath_op: OpTy<'tcx, Tag> + ) -> InterpResult<'tcx, i32> { + #[cfg(target_family = "unix")] + fn create_link(src: PathBuf, dst: PathBuf) -> std::io::Result<()> { + std::os::unix::fs::symlink(src, dst) + } + + #[cfg(target_family = "windows")] + fn create_link(src: PathBuf, dst: PathBuf) -> std::io::Result<()> { + use std::os::windows::fs; + if src.is_dir() { + fs::symlink_dir(src, dst) + } else { + fs::symlink_file(src, dst) + } + } + + let this = self.eval_context_mut(); + + this.check_no_isolation("symlink")?; + + let target = this.read_os_str_from_c_str(this.read_scalar(target_op)?.not_undef()?)?.into(); + let linkpath = this.read_os_str_from_c_str(this.read_scalar(linkpath_op)?.not_undef()?)?.into(); + + this.try_unwrap_io_result(create_link(target, linkpath).map(|_| 0)) + } + fn stat( &mut self, path_op: OpTy<'tcx, Tag>, buf_op: OpTy<'tcx, Tag>, ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); + this.check_no_isolation("stat")?; + // `stat` always follows symlinks. + this.stat_or_lstat(true, path_op, buf_op) + } + + // `lstat` is used to get symlink metadata. + fn lstat( + &mut self, + path_op: OpTy<'tcx, Tag>, + buf_op: OpTy<'tcx, Tag>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.check_no_isolation("lstat")?; + this.stat_or_lstat(false, path_op, buf_op) + } + + fn stat_or_lstat( + &mut self, + follow_symlink: bool, + path_op: OpTy<'tcx, Tag>, + buf_op: OpTy<'tcx, Tag>, + ) -> 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 available for `macos` targets.") + throw_unsup_format!("The `stat` and `lstat` shims are only available for `macos` targets.") } let path_scalar = this.read_scalar(path_op)?.not_undef()?; @@ -292,8 +345,7 @@ fn stat( let buf = this.deref_operand(buf_op)?; - // `stat` always follows symlinks. `lstat` is used to get symlink metadata. - let metadata = match FileMetadata::new(this, path, true)? { + let metadata = match FileMetadata::new(this, path, follow_symlink)? { Some(metadata) => metadata, None => return Ok(-1), }; @@ -545,7 +597,6 @@ fn new<'tcx, 'mir>( let metadata = if follow_symlink { std::fs::metadata(path) } else { - // FIXME: metadata for symlinks need testing. std::fs::symlink_metadata(path) }; diff --git a/tests/run-pass/fs.rs b/tests/run-pass/fs.rs index 85e39bc4511..81c56e4aafc 100644 --- a/tests/run-pass/fs.rs +++ b/tests/run-pass/fs.rs @@ -19,7 +19,11 @@ fn main() { let tmp = std::env::temp_dir(); let filename = PathBuf::from("miri_test_fs.txt"); let path = tmp.join(&filename); + let symlink_path = tmp.join("miri_test_fs_symlink.txt"); let bytes = b"Hello, World!\n"; + // Clean the paths for robustness. + remove_file(&path).ok(); + remove_file(&symlink_path).ok(); // Test creating, writing and closing a file (closing is tested when `file` is dropped). let mut file = File::create(&path).unwrap(); @@ -39,9 +43,23 @@ fn main() { // Test that metadata of an absolute path is correct. test_metadata(bytes, &path).unwrap(); // Test that metadata of a relative path is correct. - std::env::set_current_dir(tmp).unwrap(); + std::env::set_current_dir(&tmp).unwrap(); test_metadata(bytes, &filename).unwrap(); + // Creating a symbolic link should succeed. + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + // Test that the symbolic link has the same contents as the file. + let mut symlink_file = File::open(&symlink_path).unwrap(); + let mut contents = Vec::new(); + symlink_file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + // Test that metadata of a symbolic link is correct. + test_metadata(bytes, &symlink_path).unwrap(); + // Test that the metadata of a symbolic link is correct when not following it. + assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink()); + // Removing symbolic link should succeed. + remove_file(&symlink_path).unwrap(); + // Removing file should succeed. remove_file(&path).unwrap();