]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/tests/pass-dep/shims/libc-misc.rs
2a4300fcd049ec2d2669d77800b8631cc9e7d90c
[rust.git] / src / tools / miri / tests / pass-dep / shims / libc-misc.rs
1 //@ignore-target-windows: No libc on Windows
2 //@compile-flags: -Zmiri-disable-isolation
3 #![feature(io_error_more)]
4
5 use std::fs::{remove_file, File};
6 use std::os::unix::io::AsRawFd;
7 use std::path::PathBuf;
8
9 fn tmp() -> PathBuf {
10     std::env::var("MIRI_TEMP")
11         .map(|tmp| {
12             // MIRI_TEMP is set outside of our emulated
13             // program, so it may have path separators that don't
14             // correspond to our target platform. We normalize them here
15             // before constructing a `PathBuf`
16             return PathBuf::from(tmp.replace("\\", "/"));
17         })
18         .unwrap_or_else(|_| std::env::temp_dir())
19 }
20
21 /// Test allocating variant of `realpath`.
22 fn test_posix_realpath_alloc() {
23     use std::ffi::OsString;
24     use std::ffi::{CStr, CString};
25     use std::os::unix::ffi::OsStrExt;
26     use std::os::unix::ffi::OsStringExt;
27
28     let buf;
29     let path = tmp().join("miri_test_libc_posix_realpath_alloc");
30     let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
31
32     // Cleanup before test.
33     remove_file(&path).ok();
34     // Create file.
35     drop(File::create(&path).unwrap());
36     unsafe {
37         let r = libc::realpath(c_path.as_ptr(), std::ptr::null_mut());
38         assert!(!r.is_null());
39         buf = CStr::from_ptr(r).to_bytes().to_vec();
40         libc::free(r as *mut _);
41     }
42     let canonical = PathBuf::from(OsString::from_vec(buf));
43     assert_eq!(path.file_name(), canonical.file_name());
44
45     // Cleanup after test.
46     remove_file(&path).unwrap();
47 }
48
49 /// Test non-allocating variant of `realpath`.
50 fn test_posix_realpath_noalloc() {
51     use std::ffi::{CStr, CString};
52     use std::os::unix::ffi::OsStrExt;
53
54     let path = tmp().join("miri_test_libc_posix_realpath_noalloc");
55     let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
56
57     let mut v = vec![0; libc::PATH_MAX as usize];
58
59     // Cleanup before test.
60     remove_file(&path).ok();
61     // Create file.
62     drop(File::create(&path).unwrap());
63     unsafe {
64         let r = libc::realpath(c_path.as_ptr(), v.as_mut_ptr());
65         assert!(!r.is_null());
66     }
67     let c = unsafe { CStr::from_ptr(v.as_ptr()) };
68     let canonical = PathBuf::from(c.to_str().expect("CStr to str"));
69
70     assert_eq!(path.file_name(), canonical.file_name());
71
72     // Cleanup after test.
73     remove_file(&path).unwrap();
74 }
75
76 /// Test failure cases for `realpath`.
77 fn test_posix_realpath_errors() {
78     use std::ffi::CString;
79     use std::io::ErrorKind;
80
81     // Test non-existent path returns an error.
82     let c_path = CString::new("./nothing_to_see_here").expect("CString::new failed");
83     let r = unsafe { libc::realpath(c_path.as_ptr(), std::ptr::null_mut()) };
84     assert!(r.is_null());
85     let e = std::io::Error::last_os_error();
86     assert_eq!(e.raw_os_error(), Some(libc::ENOENT));
87     assert_eq!(e.kind(), ErrorKind::NotFound);
88 }
89
90 #[cfg(target_os = "linux")]
91 fn test_posix_fadvise() {
92     use std::convert::TryInto;
93     use std::io::Write;
94
95     let path = tmp().join("miri_test_libc_posix_fadvise.txt");
96     // Cleanup before test
97     remove_file(&path).ok();
98
99     // Set up an open file
100     let mut file = File::create(&path).unwrap();
101     let bytes = b"Hello, World!\n";
102     file.write(bytes).unwrap();
103
104     // Test calling posix_fadvise on a file.
105     let result = unsafe {
106         libc::posix_fadvise(
107             file.as_raw_fd(),
108             0,
109             bytes.len().try_into().unwrap(),
110             libc::POSIX_FADV_DONTNEED,
111         )
112     };
113     drop(file);
114     remove_file(&path).unwrap();
115     assert_eq!(result, 0);
116 }
117
118 #[cfg(target_os = "linux")]
119 fn test_sync_file_range() {
120     use std::io::Write;
121
122     let path = tmp().join("miri_test_libc_sync_file_range.txt");
123     // Cleanup before test.
124     remove_file(&path).ok();
125
126     // Write to a file.
127     let mut file = File::create(&path).unwrap();
128     let bytes = b"Hello, World!\n";
129     file.write(bytes).unwrap();
130
131     // Test calling sync_file_range on the file.
132     let result_1 = unsafe {
133         libc::sync_file_range(
134             file.as_raw_fd(),
135             0,
136             0,
137             libc::SYNC_FILE_RANGE_WAIT_BEFORE
138                 | libc::SYNC_FILE_RANGE_WRITE
139                 | libc::SYNC_FILE_RANGE_WAIT_AFTER,
140         )
141     };
142     drop(file);
143
144     // Test calling sync_file_range on a file opened for reading.
145     let file = File::open(&path).unwrap();
146     let result_2 = unsafe {
147         libc::sync_file_range(
148             file.as_raw_fd(),
149             0,
150             0,
151             libc::SYNC_FILE_RANGE_WAIT_BEFORE
152                 | libc::SYNC_FILE_RANGE_WRITE
153                 | libc::SYNC_FILE_RANGE_WAIT_AFTER,
154         )
155     };
156     drop(file);
157
158     remove_file(&path).unwrap();
159     assert_eq!(result_1, 0);
160     assert_eq!(result_2, 0);
161 }
162
163 /// Tests whether each thread has its own `__errno_location`.
164 fn test_thread_local_errno() {
165     #[cfg(target_os = "linux")]
166     use libc::__errno_location;
167     #[cfg(any(target_os = "macos", target_os = "freebsd"))]
168     use libc::__error as __errno_location;
169
170     unsafe {
171         *__errno_location() = 0xBEEF;
172         std::thread::spawn(|| {
173             assert_eq!(*__errno_location(), 0);
174             *__errno_location() = 0xBAD1DEA;
175             assert_eq!(*__errno_location(), 0xBAD1DEA);
176         })
177         .join()
178         .unwrap();
179         assert_eq!(*__errno_location(), 0xBEEF);
180     }
181 }
182
183 /// Tests whether clock support exists at all
184 fn test_clocks() {
185     let mut tp = std::mem::MaybeUninit::<libc::timespec>::uninit();
186     let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) };
187     assert_eq!(is_error, 0);
188     let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, tp.as_mut_ptr()) };
189     assert_eq!(is_error, 0);
190     #[cfg(target_os = "linux")]
191     {
192         let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, tp.as_mut_ptr()) };
193         assert_eq!(is_error, 0);
194         let is_error =
195             unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()) };
196         assert_eq!(is_error, 0);
197     }
198     #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
199     {
200         let is_error = unsafe { libc::clock_gettime(libc::CLOCK_UPTIME_RAW, tp.as_mut_ptr()) };
201         assert_eq!(is_error, 0);
202     }
203 }
204
205 fn test_posix_gettimeofday() {
206     let mut tp = std::mem::MaybeUninit::<libc::timeval>::uninit();
207     let tz = std::ptr::null_mut::<libc::timezone>();
208     #[cfg(target_os = "macos")] // `tz` has a different type on macOS
209     let tz = tz as *mut libc::c_void;
210     let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz) };
211     assert_eq!(is_error, 0);
212     let tv = unsafe { tp.assume_init() };
213     assert!(tv.tv_sec > 0);
214     assert!(tv.tv_usec >= 0); // Theoretically this could be 0.
215
216     // Test that non-null tz returns an error.
217     let mut tz = std::mem::MaybeUninit::<libc::timezone>::uninit();
218     let tz_ptr = tz.as_mut_ptr();
219     #[cfg(target_os = "macos")] // `tz` has a different type on macOS
220     let tz_ptr = tz_ptr as *mut libc::c_void;
221     let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr) };
222     assert_eq!(is_error, -1);
223 }
224
225 fn test_isatty() {
226     // Testing whether our isatty shim returns the right value would require controlling whether
227     // these streams are actually TTYs, which is hard.
228     // For now, we just check that these calls are supported at all.
229     unsafe {
230         libc::isatty(libc::STDIN_FILENO);
231         libc::isatty(libc::STDOUT_FILENO);
232         libc::isatty(libc::STDERR_FILENO);
233
234         // But when we open a file, it is definitely not a TTY.
235         let path = tmp().join("notatty.txt");
236         // Cleanup before test.
237         remove_file(&path).ok();
238         let file = File::create(&path).unwrap();
239
240         assert_eq!(libc::isatty(file.as_raw_fd()), 0);
241         assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOTTY);
242
243         // Cleanup after test.
244         drop(file);
245         remove_file(&path).unwrap();
246     }
247 }
248
249 fn test_posix_mkstemp() {
250     use std::ffi::CString;
251     use std::ffi::OsStr;
252     use std::os::unix::ffi::OsStrExt;
253     use std::os::unix::io::FromRawFd;
254     use std::path::Path;
255
256     let valid_template = "fooXXXXXX";
257     // C needs to own this as `mkstemp(3)` says:
258     // "Since it will be modified, `template` must not be a string constant, but
259     // should be declared as a character array."
260     // There seems to be no `as_mut_ptr` on `CString` so we need to use `into_raw`.
261     let ptr = CString::new(valid_template).unwrap().into_raw();
262     let fd = unsafe { libc::mkstemp(ptr) };
263     // Take ownership back in Rust to not leak memory.
264     let slice = unsafe { CString::from_raw(ptr) };
265     assert!(fd > 0);
266     let osstr = OsStr::from_bytes(slice.to_bytes());
267     let path: &Path = osstr.as_ref();
268     let name = path.file_name().unwrap().to_string_lossy();
269     assert!(name.ne("fooXXXXXX"));
270     assert!(name.starts_with("foo"));
271     assert_eq!(name.len(), 9);
272     assert_eq!(
273         name.chars().skip(3).filter(char::is_ascii_alphanumeric).collect::<Vec<char>>().len(),
274         6
275     );
276     let file = unsafe { File::from_raw_fd(fd) };
277     assert!(file.set_len(0).is_ok());
278
279     let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"];
280     for t in invalid_templates {
281         let ptr = CString::new(t).unwrap().into_raw();
282         let fd = unsafe { libc::mkstemp(ptr) };
283         let _ = unsafe { CString::from_raw(ptr) };
284         // "On error, -1 is returned, and errno is set to
285         // indicate the error"
286         assert_eq!(fd, -1);
287         let e = std::io::Error::last_os_error();
288         assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
289         assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
290     }
291 }
292
293 fn main() {
294     test_posix_gettimeofday();
295     test_posix_mkstemp();
296
297     test_posix_realpath_alloc();
298     test_posix_realpath_noalloc();
299     test_posix_realpath_errors();
300
301     test_thread_local_errno();
302
303     test_isatty();
304     test_clocks();
305
306     #[cfg(target_os = "linux")]
307     {
308         test_posix_fadvise();
309         test_sync_file_range();
310     }
311 }