]> git.lizzy.rs Git - rust.git/blobdiff - src/shims/fs.rs
avoid using unchecked casts or arithmetic
[rust.git] / src / shims / fs.rs
index aa562926686dc542aa133475c45968cb7ba8d8b5..7c755143f2e81407bdbce5d43b74ff58b03f0d36 100644 (file)
@@ -1,15 +1,16 @@
 use std::collections::BTreeMap;
 use std::convert::{TryFrom, TryInto};
-use std::fs::{remove_file, rename, File, OpenOptions};
+use std::fs::{read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir};
 use std::io::{Read, Seek, SeekFrom, Write};
 use std::path::PathBuf;
 use std::time::SystemTime;
 
+use rustc_data_structures::fx::FxHashMap;
 use rustc::ty::layout::{Align, LayoutOf, Size};
 
 use crate::stacked_borrows::Tag;
 use crate::*;
-use helpers::immty_from_uint_checked;
+use helpers::{immty_from_int_checked, immty_from_uint_checked};
 use shims::time::system_time_to_duration;
 
 #[derive(Debug)]
@@ -55,7 +56,7 @@ fn insert_fd_with_min_fd(&mut self, file_handle: FileHandle, min_fd: i32) -> i32
         let new_fd = candidate_new_fd.unwrap_or_else(|| {
             // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
             // maximum fd in the map
-            self.handles.last_entry().map(|entry| entry.key() + 1).unwrap_or(min_fd)
+            self.handles.last_entry().map(|entry| entry.key().checked_add(1).unwrap()).unwrap_or(min_fd)
         });
 
         self.handles.insert(new_fd, file_handle).unwrap_none();
@@ -160,6 +161,82 @@ fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
         this.set_last_error(ebadf)?;
         Ok((-1).into())
     }
+
+    fn file_type_to_d_type(&mut self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        match file_type {
+            Ok(file_type) => {
+                if file_type.is_dir() {
+                    Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
+                } else if file_type.is_file() {
+                    Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
+                } else if file_type.is_symlink() {
+                    Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
+                } else {
+                    // Certain file types are only supported when the host is a Unix system.
+                    // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
+                    // DT_UNKNOWN sooner.
+
+                    #[cfg(unix)]
+                    {
+                        use std::os::unix::fs::FileTypeExt;
+                        if file_type.is_block_device() {
+                            Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
+                        } else if file_type.is_char_device() {
+                            Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
+                        } else if file_type.is_fifo() {
+                            Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
+                        } else if file_type.is_socket() {
+                            Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
+                        } else {
+                            Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
+                        }
+                    }
+                    #[cfg(not(unix))]
+                    Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
+                }
+            }
+            Err(e) => return match e.raw_os_error() {
+                Some(error) => Ok(error),
+                None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct DirHandler {
+    /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
+    /// and closedir.
+    ///
+    /// When opendir is called, a directory iterator is created on the host for the target
+    /// directory, and an entry is stored in this hash map, indexed by an ID which represents
+    /// the directory stream. When readdir is called, the directory stream ID is used to look up
+    /// the corresponding ReadDir iterator from this map, and information from the next
+    /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
+    /// the map.
+    streams: FxHashMap<u64, ReadDir>,
+    /// ID number to be used by the next call to opendir
+    next_id: u64,
+}
+
+impl DirHandler {
+    fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
+        let id = self.next_id;
+        self.next_id += 1;
+        self.streams.insert(id, read_dir).unwrap_none();
+        id
+    }
+}
+
+impl Default for DirHandler {
+    fn default() -> DirHandler {
+        DirHandler {
+            streams: FxHashMap::default(),
+            // Skip 0 as an ID, because it looks like a null pointer to libc
+            next_id: 1,
+        }
+    }
 }
 
 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
@@ -350,15 +427,15 @@ fn read(
 
         // We cap the number of read bytes to the largest value that we are able to fit in both the
         // host's and target's `isize`. This saves us from having to handle overflows later.
-        let count = count.min(this.isize_max() as u64).min(isize::max_value() as u64);
+        let count = count.min(this.isize_max() as u64).min(isize::MAX as u64);
 
         if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
             // This can never fail because `count` was capped to be smaller than
-            // `isize::max_value()`.
+            // `isize::MAX`.
             let count = isize::try_from(count).unwrap();
             // We want to read at most `count` bytes. We are sure that `count` is not negative
             // because it was a target's `usize`. Also we are sure that its smaller than
-            // `usize::max_value()` because it is a host's `isize`.
+            // `usize::MAX` because it is a host's `isize`.
             let mut bytes = vec![0; count as usize];
             let result = file
                 .read(&mut bytes)
@@ -404,7 +481,7 @@ fn write(
 
         // We cap the number of written bytes to the largest value that we are able to fit in both the
         // host's and target's `isize`. This saves us from having to handle overflows later.
-        let count = count.min(this.isize_max() as u64).min(isize::max_value() as u64);
+        let count = count.min(this.isize_max() as u64).min(isize::MAX as u64);
 
         if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
             let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
@@ -430,7 +507,7 @@ fn lseek64(
         let whence = this.read_scalar(whence_op)?.to_i32()?;
 
         let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
-            SeekFrom::Start(offset as u64)
+            SeekFrom::Start(u64::try_from(offset).unwrap())
         } else if whence == this.eval_libc_i32("SEEK_CUR")? {
             SeekFrom::Current(offset)
         } else if whence == this.eval_libc_i32("SEEK_END")? {
@@ -442,7 +519,7 @@ fn lseek64(
         };
 
         if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
-            let result = file.seek(seek_from).map(|offset| offset as i64);
+            let result = file.seek(seek_from).map(|offset| i64::try_from(offset).unwrap());
             this.try_unwrap_io_result(result)
         } else {
             this.handle_not_found()
@@ -722,6 +799,269 @@ fn rename(
 
         this.try_unwrap_io_result(result)
     }
+
+    fn mkdir(
+        &mut self,
+        path_op: OpTy<'tcx, Tag>,
+        mode_op: OpTy<'tcx, Tag>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("mkdir")?;
+
+        let _mode = if this.tcx.sess.target.target.target_os.as_str() == "macos" {
+            u32::from(this.read_scalar(mode_op)?.not_undef()?.to_u16()?)
+        } else {
+            this.read_scalar(mode_op)?.to_u32()?
+        };
+
+        let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
+
+        let mut builder = DirBuilder::new();
+
+        // If the host supports it, forward on the mode of the directory
+        // (i.e. permission bits and the sticky bit)
+        #[cfg(target_family = "unix")]
+        {
+            use std::os::unix::fs::DirBuilderExt;
+            builder.mode(_mode.into());
+        }
+
+        let result = builder.create(path).map(|_| 0i32);
+
+        this.try_unwrap_io_result(result)
+    }
+
+    fn rmdir(
+        &mut self,
+        path_op: OpTy<'tcx, Tag>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("rmdir")?;
+
+        let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
+
+        let result = remove_dir(path).map(|_| 0i32);
+
+        this.try_unwrap_io_result(result)
+    }
+
+    fn opendir(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("opendir")?;
+
+        let name = this.read_os_str_from_c_str(this.read_scalar(name_op)?.not_undef()?)?;
+
+        let result = read_dir(name);
+
+        match result {
+            Ok(dir_iter) => {
+                let id = this.machine.dir_handler.insert_new(dir_iter);
+
+                // The libc API for opendir says that this method returns a pointer to an opaque
+                // structure, but we are returning an ID number. Thus, pass it as a scalar of
+                // pointer width.
+                Ok(Scalar::from_machine_usize(id, this))
+            }
+            Err(e) => {
+                this.set_last_error_from_io_error(e)?;
+                Ok(Scalar::from_machine_usize(0, this))
+            }
+        }
+    }
+
+    fn linux_readdir64_r(
+        &mut self,
+        dirp_op: OpTy<'tcx, Tag>,
+        entry_op: OpTy<'tcx, Tag>,
+        result_op: OpTy<'tcx, Tag>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("readdir64_r")?;
+        this.assert_platform("linux", "readdir64_r");
+
+        let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
+
+        let dir_iter = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
+            err_unsup_format!("The DIR pointer passed to readdir64_r did not come from opendir")
+        })?;
+        match dir_iter.next() {
+            Some(Ok(dir_entry)) => {
+                // Write into entry, write pointer to result, return 0 on success.
+                // The name is written with write_os_str_to_c_str, while the rest of the
+                // dirent64 struct is written using write_packed_immediates.
+
+                // For reference:
+                // pub struct dirent64 {
+                //     pub d_ino: ino64_t,
+                //     pub d_off: off64_t,
+                //     pub d_reclen: c_ushort,
+                //     pub d_type: c_uchar,
+                //     pub d_name: [c_char; 256],
+                // }
+
+                let entry_place = this.deref_operand(entry_op)?;
+                let name_place = this.mplace_field(entry_place, 4)?;
+
+                let file_name = dir_entry.file_name();
+                let (name_fits, _) = this.write_os_str_to_c_str(
+                    &file_name,
+                    name_place.ptr,
+                    name_place.layout.size.bytes(),
+                )?;
+                if !name_fits {
+                    throw_unsup_format!("A directory entry had a name too large to fit in libc::dirent64");
+                }
+
+                let entry_place = this.deref_operand(entry_op)?;
+                let ino64_t_layout = this.libc_ty_layout("ino64_t")?;
+                let off64_t_layout = this.libc_ty_layout("off64_t")?;
+                let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
+                let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
+
+                // If the host is a Unix system, fill in the inode number with its real value.
+                // If not, use 0 as a fallback value.
+                #[cfg(unix)]
+                let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
+                #[cfg(not(unix))]
+                let ino = 0u64;
+
+                let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
+
+                let imms = [
+                    immty_from_uint_checked(ino, ino64_t_layout)?, // d_ino
+                    immty_from_uint_checked(0u128, off64_t_layout)?, // d_off
+                    immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
+                    immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
+                ];
+                this.write_packed_immediates(entry_place, &imms)?;
+
+                let result_place = this.deref_operand(result_op)?;
+                this.write_scalar(this.read_scalar(entry_op)?, result_place.into())?;
+
+                Ok(0)
+            }
+            None => {
+                // end of stream: return 0, assign *result=NULL
+                this.write_null(this.deref_operand(result_op)?.into())?;
+                Ok(0)
+            }
+            Some(Err(e)) => match e.raw_os_error() {
+                // return positive error number on error
+                Some(error) => Ok(error),
+                None => {
+                    throw_unsup_format!("The error {} couldn't be converted to a return value", e)
+                }
+            },
+        }
+    }
+
+    fn macos_readdir_r(
+        &mut self,
+        dirp_op: OpTy<'tcx, Tag>,
+        entry_op: OpTy<'tcx, Tag>,
+        result_op: OpTy<'tcx, Tag>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("readdir_r")?;
+        this.assert_platform("macos", "readdir_r");
+
+        let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
+
+        let dir_iter = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
+            err_unsup_format!("The DIR pointer passed to readdir_r did not come from opendir")
+        })?;
+        match dir_iter.next() {
+            Some(Ok(dir_entry)) => {
+                // Write into entry, write pointer to result, return 0 on success.
+                // The name is written with write_os_str_to_c_str, while the rest of the
+                // dirent struct is written using write_packed_Immediates.
+
+                // For reference:
+                // pub struct dirent {
+                //     pub d_ino: u64,
+                //     pub d_seekoff: u64,
+                //     pub d_reclen: u16,
+                //     pub d_namlen: u16,
+                //     pub d_type: u8,
+                //     pub d_name: [c_char; 1024],
+                // }
+
+                let entry_place = this.deref_operand(entry_op)?;
+                let name_place = this.mplace_field(entry_place, 5)?;
+
+                let file_name = dir_entry.file_name();
+                let (name_fits, file_name_len) = this.write_os_str_to_c_str(
+                    &file_name,
+                    name_place.ptr,
+                    name_place.layout.size.bytes(),
+                )?;
+                if !name_fits {
+                    throw_unsup_format!("A directory entry had a name too large to fit in libc::dirent");
+                }
+
+                let entry_place = this.deref_operand(entry_op)?;
+                let ino_t_layout = this.libc_ty_layout("ino_t")?;
+                let off_t_layout = this.libc_ty_layout("off_t")?;
+                let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
+                let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
+
+                // If the host is a Unix system, fill in the inode number with its real value.
+                // If not, use 0 as a fallback value.
+                #[cfg(unix)]
+                let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
+                #[cfg(not(unix))]
+                let ino = 0u64;
+
+                let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
+
+                let imms = [
+                    immty_from_uint_checked(ino, ino_t_layout)?, // d_ino
+                    immty_from_uint_checked(0u128, off_t_layout)?, // d_seekoff
+                    immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
+                    immty_from_uint_checked(file_name_len, c_ushort_layout)?, // d_namlen
+                    immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
+                ];
+                this.write_packed_immediates(entry_place, &imms)?;
+
+                let result_place = this.deref_operand(result_op)?;
+                this.write_scalar(this.read_scalar(entry_op)?, result_place.into())?;
+
+                Ok(0)
+            }
+            None => {
+                // end of stream: return 0, assign *result=NULL
+                this.write_null(this.deref_operand(result_op)?.into())?;
+                Ok(0)
+            }
+            Some(Err(e)) => match e.raw_os_error() {
+                // return positive error number on error
+                Some(error) => Ok(error),
+                None => {
+                    throw_unsup_format!("The error {} couldn't be converted to a return value", e)
+                }
+            },
+        }
+    }
+
+    fn closedir(&mut self, dirp_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        this.check_no_isolation("closedir")?;
+
+        let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
+
+        if let Some(dir_iter) = this.machine.dir_handler.streams.remove(&dirp) {
+            drop(dir_iter);
+            Ok(0)
+        } else {
+            this.handle_not_found()
+        }
+    }
 }
 
 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when