Skip to content

Commit 78bc89b

Browse files
committed
Implement readlink
Due to the truncating behavior of `readlink`, I was not able to directly use any of the existing C-cstring helper functions.
1 parent 00106b9 commit 78bc89b

File tree

4 files changed

+100
-16
lines changed

4 files changed

+100
-16
lines changed

src/shims/os_str.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,22 +118,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
118118
scalar: Scalar<Tag>,
119119
size: u64,
120120
) -> InterpResult<'tcx, (bool, u64)> {
121-
#[cfg(unix)]
122-
fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
123-
Ok(os_str.as_bytes())
124-
}
125-
#[cfg(not(unix))]
126-
fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
127-
// On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
128-
// intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
129-
// valid.
130-
os_str
131-
.to_str()
132-
.map(|s| s.as_bytes())
133-
.ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
134-
}
135121

136-
let bytes = os_str_to_bytes(os_str)?;
122+
let bytes = self.os_str_to_bytes(os_str)?;
137123
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null
138124
// terminator to memory using the `ptr` pointer would cause an out-of-bounds access.
139125
let string_length = u64::try_from(bytes.len()).unwrap();
@@ -265,4 +251,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
265251
let os_str = convert_path_separator(Cow::Borrowed(path.as_os_str()), &this.tcx.sess.target.target.target_os, Pathconversion::HostToTarget);
266252
this.write_os_str_to_wide_str(&os_str, scalar, size)
267253
}
254+
255+
#[cfg(unix)]
256+
fn os_str_to_bytes<'a>(&mut self, os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
257+
Ok(os_str.as_bytes())
258+
}
259+
260+
#[cfg(not(unix))]
261+
fn os_str_to_bytes<'a>(&mut self, os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
262+
// On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
263+
// intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
264+
// valid.
265+
os_str
266+
.to_str()
267+
.map(|s| s.as_bytes())
268+
.ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
269+
}
270+
268271
}

src/shims/posix/foreign_items.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
123123
let result = this.fdatasync(fd)?;
124124
this.write_scalar(Scalar::from_i32(result), dest)?;
125125
}
126+
"readlink" => {
127+
let &[pathname, buf, bufsize] = check_arg_count(args)?;
128+
let result = this.readlink(pathname, buf, bufsize)?;
129+
this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
130+
}
126131

127132
// Allocation
128133
"posix_memalign" => {

src/shims/posix/fs.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,39 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
13531353
this.handle_not_found()
13541354
}
13551355
}
1356+
1357+
fn readlink(
1358+
&mut self,
1359+
pathname_op: OpTy<'tcx, Tag>,
1360+
buf_op: OpTy<'tcx, Tag>,
1361+
bufsize_op: OpTy<'tcx, Tag>
1362+
) -> InterpResult<'tcx, i64> {
1363+
let this = self.eval_context_mut();
1364+
1365+
this.check_no_isolation("readlink")?;
1366+
1367+
let pathname = this.read_path_from_c_str(this.read_scalar(pathname_op)?.check_init()?)?;
1368+
let buf = this.read_scalar(buf_op)?.check_init()?;
1369+
let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1370+
1371+
let result = std::fs::read_link(pathname);
1372+
match result {
1373+
Ok(resolved) => {
1374+
let mut path_bytes = this.os_str_to_bytes(resolved.as_ref())?;
1375+
if path_bytes.len() > bufsize as usize {
1376+
path_bytes = &path_bytes[..(bufsize as usize)]
1377+
}
1378+
// 'readlink' truncates the resolved path if
1379+
// the provided buffer is not large enough
1380+
this.memory.write_bytes(buf, path_bytes.iter().copied())?;
1381+
Ok(path_bytes.len() as i64)
1382+
}
1383+
Err(e) => {
1384+
this.set_last_error_from_io_error(e)?;
1385+
Ok(-1)
1386+
}
1387+
}
1388+
}
13561389
}
13571390

13581391
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when

tests/run-pass/fs.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
// ignore-windows: File handling is not implemented yet
22
// compile-flags: -Zmiri-disable-isolation
33

4+
#![feature(rustc_private)]
5+
46
use std::fs::{
57
File, create_dir, OpenOptions, read_dir, remove_dir, remove_dir_all, remove_file, rename,
68
};
7-
use std::io::{Read, Write, ErrorKind, Result, Seek, SeekFrom};
9+
use std::ffi::CString;
10+
use std::io::{Read, Write, Error, ErrorKind, Result, Seek, SeekFrom};
811
use std::path::{PathBuf, Path};
912

13+
extern crate libc;
14+
15+
1016
fn main() {
1117
test_file();
1218
test_file_clone();
@@ -215,6 +221,43 @@ fn test_symlink() {
215221
let mut contents = Vec::new();
216222
symlink_file.read_to_end(&mut contents).unwrap();
217223
assert_eq!(bytes, contents.as_slice());
224+
225+
226+
#[cfg(unix)]
227+
{
228+
use std::os::unix::ffi::OsStrExt;
229+
230+
let expected_path = path.as_os_str().as_bytes();
231+
232+
// Test that the expected string gets written to a buffer of proper
233+
// length, and that a trailing null byte is not written
234+
let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
235+
let symlink_c_ptr = symlink_c_str.as_ptr();
236+
237+
// Make the buf one byte larger than it needs to be,
238+
// and check that the last byte is not overwritten
239+
let mut large_buf = vec![0xFF; expected_path.len() + 1];
240+
let res = unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) };
241+
assert_eq!(res, large_buf.len() as isize - 1);
242+
// Check that the resovled path was properly written into the buf
243+
assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
244+
assert_eq!(large_buf.last(), Some(&0xFF));
245+
246+
// Test that the resolved path is truncated if the provided buffer
247+
// is too small.
248+
let mut small_buf = [0u8; 2];
249+
let res = unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) };
250+
assert_eq!(res, small_buf.len() as isize);
251+
assert_eq!(small_buf, &expected_path[..small_buf.len()]);
252+
253+
// Test that we report a proper error for a missing path.
254+
let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
255+
let res = unsafe { libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) };
256+
assert_eq!(res, -1);
257+
assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
258+
}
259+
260+
218261
// Test that metadata of a symbolic link is correct.
219262
check_metadata(bytes, &symlink_path).unwrap();
220263
// Test that the metadata of a symbolic link is correct when not following it.

0 commit comments

Comments
 (0)