|
| 1 | +//@ignore-target-windows: no libc on Windows |
| 2 | +//@compile-flags: -Zmiri-disable-isolation |
| 3 | + |
| 4 | +#![feature(io_error_more)] |
| 5 | +#![feature(io_error_uncategorized)] |
| 6 | + |
| 7 | +use std::convert::TryInto; |
| 8 | +use std::ffi::CString; |
| 9 | +use std::fs::{canonicalize, remove_file, File}; |
| 10 | +use std::io::{Error, ErrorKind, Write}; |
| 11 | +use std::os::unix::ffi::OsStrExt; |
| 12 | +use std::path::PathBuf; |
| 13 | + |
| 14 | +fn main() { |
| 15 | + test_dup_stdout_stderr(); |
| 16 | + test_canonicalize_too_long(); |
| 17 | + test_readlink(); |
| 18 | + test_file_open_unix_allow_two_args(); |
| 19 | + test_file_open_unix_needs_three_args(); |
| 20 | + test_file_open_unix_extra_third_arg(); |
| 21 | +} |
| 22 | + |
| 23 | +fn tmp() -> PathBuf { |
| 24 | + std::env::var("MIRI_TEMP") |
| 25 | + .map(|tmp| { |
| 26 | + // MIRI_TEMP is set outside of our emulated |
| 27 | + // program, so it may have path separators that don't |
| 28 | + // correspond to our target platform. We normalize them here |
| 29 | + // before constructing a `PathBuf` |
| 30 | + |
| 31 | + #[cfg(windows)] |
| 32 | + return PathBuf::from(tmp.replace("/", "\\")); |
| 33 | + |
| 34 | + #[cfg(not(windows))] |
| 35 | + return PathBuf::from(tmp.replace("\\", "/")); |
| 36 | + }) |
| 37 | + .unwrap_or_else(|_| std::env::temp_dir()) |
| 38 | +} |
| 39 | + |
| 40 | +/// Prepare: compute filename and make sure the file does not exist. |
| 41 | +fn prepare(filename: &str) -> PathBuf { |
| 42 | + let path = tmp().join(filename); |
| 43 | + // Clean the paths for robustness. |
| 44 | + remove_file(&path).ok(); |
| 45 | + path |
| 46 | +} |
| 47 | + |
| 48 | +/// Prepare like above, and also write some initial content to the file. |
| 49 | +fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf { |
| 50 | + let path = prepare(filename); |
| 51 | + let mut file = File::create(&path).unwrap(); |
| 52 | + file.write(content).unwrap(); |
| 53 | + path |
| 54 | +} |
| 55 | + |
| 56 | +fn test_file_open_unix_allow_two_args() { |
| 57 | + let path = prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]); |
| 58 | + |
| 59 | + let mut name = path.into_os_string(); |
| 60 | + name.push("\0"); |
| 61 | + let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>(); |
| 62 | + let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY) }; |
| 63 | +} |
| 64 | + |
| 65 | +fn test_file_open_unix_needs_three_args() { |
| 66 | + let path = prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]); |
| 67 | + |
| 68 | + let mut name = path.into_os_string(); |
| 69 | + name.push("\0"); |
| 70 | + let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>(); |
| 71 | + let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT, 0o666) }; |
| 72 | +} |
| 73 | + |
| 74 | +fn test_file_open_unix_extra_third_arg() { |
| 75 | + let path = prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]); |
| 76 | + |
| 77 | + let mut name = path.into_os_string(); |
| 78 | + name.push("\0"); |
| 79 | + let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>(); |
| 80 | + let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY, 42) }; |
| 81 | +} |
| 82 | + |
| 83 | +fn test_dup_stdout_stderr() { |
| 84 | + let bytes = b"hello dup fd\n"; |
| 85 | + unsafe { |
| 86 | + let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0); |
| 87 | + let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0); |
| 88 | + libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len()); |
| 89 | + libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len()); |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +fn test_canonicalize_too_long() { |
| 94 | + // Make sure we get an error for long paths. |
| 95 | + let too_long = "x/".repeat(libc::PATH_MAX.try_into().unwrap()); |
| 96 | + assert!(canonicalize(too_long).is_err()); |
| 97 | +} |
| 98 | + |
| 99 | +fn test_readlink() { |
| 100 | + let bytes = b"Hello, World!\n"; |
| 101 | + let path = prepare_with_content("miri_test_fs_link_target.txt", bytes); |
| 102 | + let expected_path = path.as_os_str().as_bytes(); |
| 103 | + |
| 104 | + let symlink_path = prepare("miri_test_fs_symlink.txt"); |
| 105 | + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); |
| 106 | + |
| 107 | + // Test that the expected string gets written to a buffer of proper |
| 108 | + // length, and that a trailing null byte is not written. |
| 109 | + let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap(); |
| 110 | + let symlink_c_ptr = symlink_c_str.as_ptr(); |
| 111 | + |
| 112 | + // Make the buf one byte larger than it needs to be, |
| 113 | + // and check that the last byte is not overwritten. |
| 114 | + let mut large_buf = vec![0xFF; expected_path.len() + 1]; |
| 115 | + let res = |
| 116 | + unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) }; |
| 117 | + // Check that the resovled path was properly written into the buf. |
| 118 | + assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path); |
| 119 | + assert_eq!(large_buf.last(), Some(&0xFF)); |
| 120 | + assert_eq!(res, large_buf.len() as isize - 1); |
| 121 | + |
| 122 | + // Test that the resolved path is truncated if the provided buffer |
| 123 | + // is too small. |
| 124 | + let mut small_buf = [0u8; 2]; |
| 125 | + let res = |
| 126 | + unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) }; |
| 127 | + assert_eq!(small_buf, &expected_path[..small_buf.len()]); |
| 128 | + assert_eq!(res, small_buf.len() as isize); |
| 129 | + |
| 130 | + // Test that we report a proper error for a missing path. |
| 131 | + let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap(); |
| 132 | + let res = unsafe { |
| 133 | + libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) |
| 134 | + }; |
| 135 | + assert_eq!(res, -1); |
| 136 | + assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound); |
| 137 | +} |
0 commit comments