Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,18 +309,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}

/// Project to the given *named* field (which must be a struct or union type).
fn project_field_named<P: Projectable<'tcx, Provenance>>(
fn try_project_field_named<P: Projectable<'tcx, Provenance>>(
&self,
base: &P,
name: &str,
) -> InterpResult<'tcx, P> {
) -> InterpResult<'tcx, Option<P>> {
let this = self.eval_context_ref();
let adt = base.layout().ty.ty_adt_def().unwrap();
for (idx, field) in adt.non_enum_variant().fields.iter().enumerate() {
if field.name.as_str() == name {
return this.project_field(base, idx);
return interp_ok(Some(this.project_field(base, idx)?));
}
}
interp_ok(None)
}

/// Project to the given *named* field (which must be a struct or union type).
fn project_field_named<P: Projectable<'tcx, Provenance>>(
&self,
base: &P,
name: &str,
) -> InterpResult<'tcx, P> {
if let Some(field) = self.try_project_field_named(base, name)? {
return interp_ok(field);
}
bug!("No field named {} in type {}", name, base.layout().ty);
}

Expand All @@ -330,13 +342,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
base: &P,
name: &str,
) -> bool {
let adt = base.layout().ty.ty_adt_def().unwrap();
for field in adt.non_enum_variant().fields.iter() {
if field.name.as_str() == name {
return true;
}
}
false
self.try_project_field_named(base, name).unwrap().is_some()
}

/// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned,
Expand Down
45 changes: 31 additions & 14 deletions src/shims/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,10 +1048,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
}

fn linux_readdir64(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
fn linux_solarish_readdir64(
&mut self,
dirent_type: &str,
dirp_op: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();

this.assert_target_os("linux", "readdir64");
if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
}

let dirp = this.read_target_usize(dirp_op)?;

Expand All @@ -1070,29 +1076,40 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
Some(Ok(dir_entry)) => {
// Write the directory entry into a newly allocated buffer.
// The name is written with write_bytes, while the rest of the
// dirent64 struct is written using write_int_fields.
// dirent64 (or dirent) struct is written using write_int_fields.

// For reference:
// On Linux:
// 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],
// }
//
// On Solaris:
// pub struct dirent {
// pub d_ino: ino64_t,
// pub d_off: off64_t,
// pub d_reclen: c_ushort,
// pub d_name: [c_char; 3],
// }

let mut name = dir_entry.file_name(); // not a Path as there are no separators!
name.push("\0"); // Add a NUL terminator
let name_bytes = name.as_encoded_bytes();
let name_len = u64::try_from(name_bytes.len()).unwrap();

let dirent64_layout = this.libc_ty_layout("dirent64");
let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
let dirent_layout = this.libc_ty_layout(dirent_type);
let fields = &dirent_layout.fields;
let last_field = fields.count().strict_sub(1);
let d_name_offset = fields.offset(last_field).bytes();
let size = d_name_offset.strict_add(name_len);

let entry = this.allocate_ptr(
Size::from_bytes(size),
dirent64_layout.align.abi,
dirent_layout.align.abi,
MiriMemoryKind::Runtime.into(),
)?;
let entry: Pointer = entry.into();
Expand All @@ -1105,17 +1122,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let ino = 0u64;

let file_type = this.file_type_to_d_type(dir_entry.file_type())?;

this.write_int_fields_named(
&[
("d_ino", ino.into()),
("d_off", 0),
("d_reclen", size.into()),
("d_type", file_type.into()),
],
&this.ptr_to_mplace(entry, dirent64_layout),
&[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
&this.ptr_to_mplace(entry, dirent_layout),
)?;

if let Some(d_type) = this
.try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
{
this.write_int(file_type, &d_type)?;
}

let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;

Expand Down
2 changes: 1 addition & 1 deletion src/shims/unix/linux/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"readdir64" => {
let [dirp] =
this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
let result = this.linux_readdir64(dirp)?;
let result = this.linux_solarish_readdir64("dirent64", dirp)?;
this.write_scalar(result, dest)?;
}
"sync_file_range" => {
Expand Down
6 changes: 6 additions & 0 deletions src/shims/unix/solarish/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let result = this.macos_fbsd_solaris_fstat(fd, buf)?;
this.write_scalar(result, dest)?;
}
"readdir" => {
let [dirp] =
this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
let result = this.linux_solarish_readdir64("dirent", dirp)?;
this.write_scalar(result, dest)?;
}

// Miscellaneous
"___errno" => {
Expand Down
14 changes: 8 additions & 6 deletions tests/pass/shims/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@ fn main() {
test_file_sync();
test_errors();
test_rename();
// solarish needs to support readdir/readdir64 for these tests.
if cfg!(not(any(target_os = "solaris", target_os = "illumos"))) {
test_directory();
test_canonicalize();
}
test_directory();
test_canonicalize();
test_from_raw_os_error();
#[cfg(unix)]
test_pread_pwrite();
Expand Down Expand Up @@ -279,7 +276,12 @@ fn test_directory() {
.collect::<BTreeMap<_, _>>()
);
// Deleting the directory should fail, since it is not empty.
assert_eq!(ErrorKind::DirectoryNotEmpty, remove_dir(&dir_path).unwrap_err().kind());

// Solaris/Illumos `rmdir` call set errno to EEXIST if directory contains
// other entries than `.` and `..`.
// https://docs.oracle.com/cd/E86824_01/html/E54765/rmdir-2.html
let err = remove_dir(&dir_path).unwrap_err().kind();
assert!(matches!(err, ErrorKind::AlreadyExists | ErrorKind::DirectoryNotEmpty));
// Clean up the files in the directory
remove_file(&path_1).unwrap();
remove_file(&path_2).unwrap();
Expand Down
Loading