From 78bc89b4fc3713869e421048a439e4ca7c6c1bfe Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 28 Sep 2020 15:02:54 -0400 Subject: [PATCH] Implement `readlink` Due to the truncating behavior of `readlink`, I was not able to directly use any of the existing C-cstring helper functions. --- src/shims/os_str.rs | 33 ++++++++++++----------- src/shims/posix/foreign_items.rs | 5 ++++ src/shims/posix/fs.rs | 33 +++++++++++++++++++++++ tests/run-pass/fs.rs | 45 +++++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/shims/os_str.rs b/src/shims/os_str.rs index 73dc9119a82..b3d40392ac6 100644 --- a/src/shims/os_str.rs +++ b/src/shims/os_str.rs @@ -118,22 +118,8 @@ fn write_os_str_to_c_str( scalar: Scalar, size: u64, ) -> InterpResult<'tcx, (bool, u64)> { - #[cfg(unix)] - fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { - Ok(os_str.as_bytes()) - } - #[cfg(not(unix))] - fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { - // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the - // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually - // valid. - os_str - .to_str() - .map(|s| s.as_bytes()) - .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into()) - } - let bytes = os_str_to_bytes(os_str)?; + let bytes = self.os_str_to_bytes(os_str)?; // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null // terminator to memory using the `ptr` pointer would cause an out-of-bounds access. let string_length = u64::try_from(bytes.len()).unwrap(); @@ -265,4 +251,21 @@ fn write_path_to_wide_str( let os_str = convert_path_separator(Cow::Borrowed(path.as_os_str()), &this.tcx.sess.target.target.target_os, Pathconversion::HostToTarget); this.write_os_str_to_wide_str(&os_str, scalar, size) } + + #[cfg(unix)] + fn os_str_to_bytes<'a>(&mut self, os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { + Ok(os_str.as_bytes()) + } + + #[cfg(not(unix))] + fn os_str_to_bytes<'a>(&mut self, os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { + // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the + // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually + // valid. + os_str + .to_str() + .map(|s| s.as_bytes()) + .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into()) + } + } diff --git a/src/shims/posix/foreign_items.rs b/src/shims/posix/foreign_items.rs index 26c743b360e..177678f03d7 100644 --- a/src/shims/posix/foreign_items.rs +++ b/src/shims/posix/foreign_items.rs @@ -123,6 +123,11 @@ fn emulate_foreign_item_by_name( let result = this.fdatasync(fd)?; this.write_scalar(Scalar::from_i32(result), dest)?; } + "readlink" => { + let &[pathname, buf, bufsize] = check_arg_count(args)?; + let result = this.readlink(pathname, buf, bufsize)?; + this.write_scalar(Scalar::from_machine_isize(result, this), dest)?; + } // Allocation "posix_memalign" => { diff --git a/src/shims/posix/fs.rs b/src/shims/posix/fs.rs index 88597b4a398..a5b9642d060 100644 --- a/src/shims/posix/fs.rs +++ b/src/shims/posix/fs.rs @@ -1353,6 +1353,39 @@ fn sync_file_range( this.handle_not_found() } } + + fn readlink( + &mut self, + pathname_op: OpTy<'tcx, Tag>, + buf_op: OpTy<'tcx, Tag>, + bufsize_op: OpTy<'tcx, Tag> + ) -> InterpResult<'tcx, i64> { + let this = self.eval_context_mut(); + + this.check_no_isolation("readlink")?; + + let pathname = this.read_path_from_c_str(this.read_scalar(pathname_op)?.check_init()?)?; + let buf = this.read_scalar(buf_op)?.check_init()?; + let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?; + + let result = std::fs::read_link(pathname); + match result { + Ok(resolved) => { + let mut path_bytes = this.os_str_to_bytes(resolved.as_ref())?; + if path_bytes.len() > bufsize as usize { + path_bytes = &path_bytes[..(bufsize as usize)] + } + // 'readlink' truncates the resolved path if + // the provided buffer is not large enough + this.memory.write_bytes(buf, path_bytes.iter().copied())?; + Ok(path_bytes.len() as i64) + } + Err(e) => { + this.set_last_error_from_io_error(e)?; + Ok(-1) + } + } + } } /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when diff --git a/tests/run-pass/fs.rs b/tests/run-pass/fs.rs index caa9bffc2bc..8f750847b20 100644 --- a/tests/run-pass/fs.rs +++ b/tests/run-pass/fs.rs @@ -1,12 +1,18 @@ // ignore-windows: File handling is not implemented yet // compile-flags: -Zmiri-disable-isolation +#![feature(rustc_private)] + use std::fs::{ File, create_dir, OpenOptions, read_dir, remove_dir, remove_dir_all, remove_file, rename, }; -use std::io::{Read, Write, ErrorKind, Result, Seek, SeekFrom}; +use std::ffi::CString; +use std::io::{Read, Write, Error, ErrorKind, Result, Seek, SeekFrom}; use std::path::{PathBuf, Path}; +extern crate libc; + + fn main() { test_file(); test_file_clone(); @@ -215,6 +221,43 @@ fn test_symlink() { let mut contents = Vec::new(); symlink_file.read_to_end(&mut contents).unwrap(); assert_eq!(bytes, contents.as_slice()); + + + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + + let expected_path = path.as_os_str().as_bytes(); + + // 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()) }; + assert_eq!(res, large_buf.len() as isize - 1); + // Check that the resovled path was properly written into the buf + assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path); + assert_eq!(large_buf.last(), Some(&0xFF)); + + // 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!(res, small_buf.len() as isize); + assert_eq!(small_buf, &expected_path[..small_buf.len()]); + + // 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); + } + + // Test that metadata of a symbolic link is correct. check_metadata(bytes, &symlink_path).unwrap(); // Test that the metadata of a symbolic link is correct when not following it. -- 2.44.0