Skip to content

Commit

Permalink
Auto merge of #3591 - RalfJung:win-symlink-trouble, r=RalfJung
Browse files Browse the repository at this point in the history
do not run symlink tests on Windows hosts

Fixes #3587
  • Loading branch information
bors committed May 9, 2024
2 parents e1473e6 + b7f37ba commit 304ccc5
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 89 deletions.
68 changes: 68 additions & 0 deletions tests/pass-dep/libc/libc-fs-symlink.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Symlink tests are separate since they don't in general work on a Windows host.
//@ignore-host-windows: creating symlinks requires admin permissions on Windows
//@ignore-target-windows: File handling is not implemented yet
//@compile-flags: -Zmiri-disable-isolation

use std::ffi::CString;
use std::fs;
use std::io::{Error, ErrorKind};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;

#[path = "../../utils/mod.rs"]
mod utils;

/// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf {
let path = utils::tmp().join(filename);
// Clean the paths for robustness.
fs::remove_file(&path).ok();
path
}

/// Prepare like above, and also write some initial content to the file.
fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
let path = prepare(filename);
fs::write(&path, content).unwrap();
path
}

fn main() {
let bytes = b"Hello, World!\n";
let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
let expected_path = path.as_os_str().as_bytes();

let symlink_path = prepare("miri_test_fs_symlink.txt");
std::os::unix::fs::symlink(&path, &symlink_path).unwrap();

// Test that the expected string gets written to a buffer of proper
// length, and that a trailing null byte is not written.
let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
let symlink_c_ptr = symlink_c_str.as_ptr();

// Make the buf one byte larger than it needs to be,
// and check that the last byte is not overwritten.
let mut large_buf = vec![0xFF; expected_path.len() + 1];
let res =
unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) };
// Check that the resolved path was properly written into the buf.
assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
assert_eq!(large_buf.last(), Some(&0xFF));
assert_eq!(res, large_buf.len() as isize - 1);

// Test that the resolved path is truncated if the provided buffer
// is too small.
let mut small_buf = [0u8; 2];
let res =
unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) };
assert_eq!(small_buf, &expected_path[..small_buf.len()]);
assert_eq!(res, small_buf.len() as isize);

// Test that we report a proper error for a missing path.
let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
let res = unsafe {
libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len())
};
assert_eq!(res, -1);
assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
}
2 changes: 1 addition & 1 deletion tests/pass-dep/libc/libc-fs-with-isolation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ignore-target-windows: no libc on Windows
//@ignore-target-windows: File handling is not implemented yet
//@compile-flags: -Zmiri-isolation-error=warn-nobacktrace
//@normalize-stderr-test: "(stat(x)?)" -> "$$STAT"

Expand Down
53 changes: 5 additions & 48 deletions tests/pass-dep/libc/libc-fs.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//@ignore-target-windows: no libc on Windows
//@ignore-target-windows: File handling is not implemented yet

Check failure on line 1 in tests/pass-dep/libc/libc-fs.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

pass test got exit status: 1

no message

Check failure on line 1 in tests/pass-dep/libc/libc-fs.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

Unmatched diagnostics outside the testfile

Error: miri cannot be run on programs that fail compilation

Check failure on line 1 in tests/pass-dep/libc/libc-fs.rs

View workflow job for this annotation

GitHub Actions / build (macos-14, aarch64-apple-darwin)

pass test got exit status: 1

no message

Check failure on line 1 in tests/pass-dep/libc/libc-fs.rs

View workflow job for this annotation

GitHub Actions / build (macos-14, aarch64-apple-darwin)

Unmatched diagnostics outside the testfile

Error: miri cannot be run on programs that fail compilation
//@compile-flags: -Zmiri-disable-isolation

#![feature(io_error_more)]
#![feature(io_error_uncategorized)]

use std::ffi::{CStr, CString, OsString};
use std::fs::{canonicalize, remove_dir_all, remove_file, File};
use std::fs::{self, canonicalize, remove_file, File};
use std::io::{Error, ErrorKind, Write};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::AsRawFd;
Expand All @@ -21,7 +21,6 @@ fn main() {
test_ftruncate::<libc::off_t>(libc::ftruncate);
#[cfg(target_os = "linux")]
test_ftruncate::<libc::off64_t>(libc::ftruncate64);
test_readlink();
test_file_open_unix_allow_two_args();
test_file_open_unix_needs_three_args();
test_file_open_unix_extra_third_arg();
Expand All @@ -42,24 +41,22 @@ fn main() {
fn prepare(filename: &str) -> PathBuf {
let path = utils::tmp().join(filename);
// Clean the paths for robustness.
remove_file(&path).ok();
fs::remove_file(&path).ok();
path
}

/// Prepare directory: compute directory name and make sure it does not exist.
#[allow(unused)]
fn prepare_dir(dirname: &str) -> PathBuf {

Check failure on line 49 in tests/pass-dep/libc/libc-fs.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

Unmatched diagnostics

Error: function `prepare_dir` is never used

Check failure on line 49 in tests/pass-dep/libc/libc-fs.rs

View workflow job for this annotation

GitHub Actions / build (macos-14, aarch64-apple-darwin)

Unmatched diagnostics

Error: function `prepare_dir` is never used
let path = utils::tmp().join(&dirname);
// Clean the directory for robustness.
remove_dir_all(&path).ok();
fs::remove_dir_all(&path).ok();
path
}

/// Prepare like above, and also write some initial content to the file.
fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
let path = prepare(filename);
let mut file = File::create(&path).unwrap();
file.write(content).unwrap();
fs::write(&path, content).unwrap();
path
}

Expand Down Expand Up @@ -106,46 +103,6 @@ fn test_canonicalize_too_long() {
assert!(canonicalize(too_long).is_err());
}

fn test_readlink() {
let bytes = b"Hello, World!\n";
let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
let expected_path = path.as_os_str().as_bytes();

let symlink_path = prepare("miri_test_fs_symlink.txt");
std::os::unix::fs::symlink(&path, &symlink_path).unwrap();

// Test that the expected string gets written to a buffer of proper
// length, and that a trailing null byte is not written.
let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
let symlink_c_ptr = symlink_c_str.as_ptr();

// Make the buf one byte larger than it needs to be,
// and check that the last byte is not overwritten.
let mut large_buf = vec![0xFF; expected_path.len() + 1];
let res =
unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) };
// Check that the resolved path was properly written into the buf.
assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
assert_eq!(large_buf.last(), Some(&0xFF));
assert_eq!(res, large_buf.len() as isize - 1);

// Test that the resolved path is truncated if the provided buffer
// is too small.
let mut small_buf = [0u8; 2];
let res =
unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) };
assert_eq!(small_buf, &expected_path[..small_buf.len()]);
assert_eq!(res, small_buf.len() as isize);

// Test that we report a proper error for a missing path.
let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
let res = unsafe {
libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len())
};
assert_eq!(res, -1);
assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
}

fn test_rename() {
let path1 = prepare("miri_test_libc_fs_source.txt");
let path2 = prepare("miri_test_libc_fs_rename_destination.txt");
Expand Down
2 changes: 1 addition & 1 deletion tests/pass-dep/libc/libc-time.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ignore-target-windows: no libc on Windows
//@ignore-target-windows: no libc time APIs on Windows
//@compile-flags: -Zmiri-disable-isolation
use std::ffi::CStr;
use std::{env, mem, ptr};
Expand Down
65 changes: 65 additions & 0 deletions tests/pass/shims/fs-symlink.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Symlink tests are separate since they don't in general work on a Windows host.
//@ignore-host-windows: creating symlinks requires admin permissions on Windows
//@ignore-target-windows: File handling is not implemented yet
//@compile-flags: -Zmiri-disable-isolation

use std::fs::{self, read_link, remove_file, File};
use std::io::{Read, Result};
use std::path::{Path, PathBuf};

#[path = "../../utils/mod.rs"]
mod utils;

/// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf {
let path = utils::tmp().join(filename);
// Clean the paths for robustness.
fs::remove_file(&path).ok();
path
}

/// Prepare like above, and also write some initial content to the file.
fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
let path = prepare(filename);
fs::write(&path, content).unwrap();
path
}

fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> {
// Test that the file metadata is correct.
let metadata = path.metadata()?;
// `path` should point to a file.
assert!(metadata.is_file());
// The size of the file must be equal to the number of written bytes.
assert_eq!(bytes.len() as u64, metadata.len());
Ok(())
}

fn main() {
let bytes = b"Hello, World!\n";
let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
let symlink_path = prepare("miri_test_fs_symlink.txt");

// Creating a symbolic link should succeed.
#[cfg(unix)]
std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(&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 (i.e., the file it points to) is correct.
check_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());
// Check that we can follow the link.
assert_eq!(read_link(&symlink_path).unwrap(), path);
// Removing symbolic link should succeed.
remove_file(&symlink_path).unwrap();

// Removing file should succeed.
remove_file(&path).unwrap();
}
43 changes: 4 additions & 39 deletions tests/pass/shims/fs.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
//@ignore-target-windows: File handling is not implemented yet
//@compile-flags: -Zmiri-disable-isolation

// If this test is failing for you locally, you can try
// 1. Deleting the files `/tmp/miri_*`
// 2. Setting `MIRI_TEMP` or `TMPDIR` to a different directory, without the `miri_*` files

#![feature(io_error_more)]
#![feature(io_error_uncategorized)]

use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::{
canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename,
self, canonicalize, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, rename,
File, OpenOptions,
};
use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write};
Expand All @@ -29,7 +25,6 @@ fn main() {
test_metadata();
test_file_set_len();
test_file_sync();
test_symlink();
test_errors();
test_rename();
test_directory();
Expand All @@ -41,23 +36,22 @@ fn main() {
fn prepare(filename: &str) -> PathBuf {
let path = utils::tmp().join(filename);
// Clean the paths for robustness.
remove_file(&path).ok();
fs::remove_file(&path).ok();
path
}

/// Prepare directory: compute directory name and make sure it does not exist.
fn prepare_dir(dirname: &str) -> PathBuf {
let path = utils::tmp().join(&dirname);
// Clean the directory for robustness.
remove_dir_all(&path).ok();
fs::remove_dir_all(&path).ok();
path
}

/// Prepare like above, and also write some initial content to the file.
fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
let path = prepare(filename);
let mut file = File::create(&path).unwrap();
file.write(content).unwrap();
fs::write(&path, content).unwrap();
path
}

Expand Down Expand Up @@ -223,35 +217,6 @@ fn test_file_sync() {
remove_file(&path).unwrap();
}

fn test_symlink() {
let bytes = b"Hello, World!\n";
let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);
let symlink_path = prepare("miri_test_fs_symlink.txt");

// Creating a symbolic link should succeed.
#[cfg(unix)]
std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(&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 (i.e., the file it points to) is correct.
check_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());
// Check that we can follow the link.
assert_eq!(read_link(&symlink_path).unwrap(), path);
// Removing symbolic link should succeed.
remove_file(&symlink_path).unwrap();

// Removing file should succeed.
remove_file(&path).unwrap();
}

fn test_errors() {
let bytes = b"Hello, World!\n";
let path = prepare("miri_test_fs_errors.txt");
Expand Down

0 comments on commit 304ccc5

Please sign in to comment.