]> git.lizzy.rs Git - rust.git/commitdiff
Implement `readlink`
authorAaron Hill <aa1ronham@gmail.com>
Mon, 28 Sep 2020 19:02:54 +0000 (15:02 -0400)
committerAaron Hill <aa1ronham@gmail.com>
Sun, 4 Oct 2020 16:06:51 +0000 (12:06 -0400)
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
src/shims/posix/foreign_items.rs
src/shims/posix/fs.rs
tests/run-pass/fs.rs

index 73dc9119a820f1c5d454d2ac4f3508a6041462e7..b3d40392ac685464d5e15412c16d9902eb8d09dc 100644 (file)
@@ -118,22 +118,8 @@ fn write_os_str_to_c_str(
         scalar: Scalar<Tag>,
         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())
+    }
+
 }
index 26c743b360e064844f11c8284780f99096333b81..177678f03d74cd482ca0ebc2b9e389cc6e9abf34 100644 (file)
@@ -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" => {
index 88597b4a39814ff97aac594f9ad905a67ee5db8a..a5b9642d0604ee839cb4c8bb36bd789e47321c9f 100644 (file)
@@ -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
index caa9bffc2bc860375b129dcc5e40b7eb4f3188cc..8f750847b203b1dcd3af47a92c0dbdc3fd2e5bd3 100644 (file)
@@ -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.