use std::collections::BTreeMap;
-use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
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)]
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();
match file_type {
Ok(file_type) => {
if file_type.is_dir() {
- Ok(this.eval_libc("DT_DIR")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
} else if file_type.is_file() {
- Ok(this.eval_libc("DT_REG")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
} else if file_type.is_symlink() {
- Ok(this.eval_libc("DT_LNK")?.to_u8()? as i32)
+ 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()? as i32)
+ Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
} else if file_type.is_char_device() {
- Ok(this.eval_libc("DT_CHR")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
} else if file_type.is_fifo() {
- Ok(this.eval_libc("DT_FIFO")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
} else if file_type.is_socket() {
- Ok(this.eval_libc("DT_SOCK")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
} else {
- Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
}
}
#[cfg(not(unix))]
- Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()? as i32)
+ Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
}
}
Err(e) => return match e.raw_os_error() {
}
}
-#[derive(Debug, Default)]
+#[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 new allocation is made, a directory iterator is created on the
- /// host for the target directory, and an entry is stored in this hash map, indexed by a
- /// pointer to the allocation which represents the directory stream. When readdir is called,
- /// the directory stream pointer is used to look up the corresponding ReadDir iterator from
- /// this HashMap, and information from the next directory entry is returned. When closedir is
- /// called, the ReadDir iterator is removed from this HashMap, and the allocation that
- /// represented the directory stream is deallocated.
- streams: HashMap<Pointer<Tag>, ReadDir>,
+ /// 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> {}
// 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)
// 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))?;
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")? {
};
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()
this.check_no_isolation("mkdir")?;
- let _mode = if this.tcx.sess.target.target.target_os.to_lowercase() == "macos" {
- this.read_scalar(mode_op)?.not_undef()?.to_u16()? as u32
+ 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()?
};
match result {
Ok(dir_iter) => {
- let size = 1;
- let kind = MiriMemoryKind::Env;
- let align = this.min_align(size, kind);
- let dir_ptr = this.memory.allocate(Size::from_bytes(size), align, kind.into());
- let prev = this
- .machine
- .dir_handler
- .streams
- .insert(dir_ptr, dir_iter);
- if let Some(_) = prev {
- panic!("The pointer allocated for opendir was already registered by a previous call to opendir")
- } else {
- Ok(Scalar::Ptr(dir_ptr))
- }
+ 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_int(0, this.memory.pointer_size()))
+ Ok(Scalar::from_machine_usize(0, this))
}
}
}
- fn readdir64_r(
+ fn linux_readdir64_r(
&mut self,
dirp_op: OpTy<'tcx, Tag>,
entry_op: OpTy<'tcx, Tag>,
let this = self.eval_context_mut();
this.check_no_isolation("readdir64_r")?;
+ this.assert_platform("linux", "readdir64_r");
- let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
-
- let entry_ptr = this.force_ptr(this.read_scalar(entry_op)?.not_undef()?)?;
- let dirent64_layout = this.libc_ty_layout("dirent64")?;
+ 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")
// The name is written with write_os_str_to_c_str, while the rest of the
// dirent64 struct is written using write_packed_immediates.
- let name_offset = dirent64_layout.details.fields.offset(4);
- let name_ptr = entry_ptr.offset(name_offset, this)?;
+ // 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, Scalar::Ptr(name_ptr), 256)?;
+ let (name_fits, _) = this.write_os_str_to_c_str(
+ &file_name,
+ name_place.ptr,
+ name_place.layout.size.bytes(),
+ )?;
if !name_fits {
- panic!("A directory entry had a name too large to fit in libc::dirent64");
+ 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 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 = 0;
+ let ino = 0u64;
- let file_type = this.file_type_to_d_type(dir_entry.file_type())? as u128;
+ 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_uint_checked(file_type, c_uchar_layout)?, // d_type
+ immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
];
this.write_packed_immediates(entry_place, &imms)?;
}
}
- fn readdir_r(
+ fn macos_readdir_r(
&mut self,
dirp_op: OpTy<'tcx, Tag>,
entry_op: OpTy<'tcx, Tag>,
let this = self.eval_context_mut();
this.check_no_isolation("readdir_r")?;
+ this.assert_platform("macos", "readdir_r");
- let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
-
- let entry_ptr = this.force_ptr(this.read_scalar(entry_op)?.not_undef()?)?;
- let dirent_layout = this.libc_ty_layout("dirent")?;
+ 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")
// The name is written with write_os_str_to_c_str, while the rest of the
// dirent struct is written using write_packed_Immediates.
- let name_offset = dirent_layout.details.fields.offset(5);
- let name_ptr = entry_ptr.offset(name_offset, this)?;
+ // 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 = this.write_os_str_to_c_str(&file_name, Scalar::Ptr(name_ptr), 1024)?;
+ 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 {
- panic!("A directory entry had a name too large to fit in libc::dirent");
+ 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 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 = 0;
-
- let file_name_len = this.os_str_length_as_c_str(&file_name)? as u128;
+ let ino = 0u64;
- let file_type = this.file_type_to_d_type(dir_entry.file_type())? as u128;
+ 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_uint_checked(file_type, c_uchar_layout)?, // d_type
+ immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
];
this.write_packed_immediates(entry_place, &imms)?;
this.check_no_isolation("closedir")?;
- let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
+ 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);
- this.memory.deallocate(dirp, None, MiriMemoryKind::Env.into())?;
Ok(0)
} else {
this.handle_not_found()