Skip to content

Commit b0c7625

Browse files
committed
add statx shim for linux
1 parent 878bb4d commit b0c7625

File tree

6 files changed

+303
-37
lines changed

6 files changed

+303
-37
lines changed

src/helpers.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,3 +509,45 @@ fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
509509
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
510510
Ok(&OsStr::new(s))
511511
}
512+
513+
// FIXME: change `ImmTy::from_int` so it returns an `InterpResult` instead and remove this
514+
// function.
515+
pub fn immty_from_int_checked<'tcx>(
516+
int: impl Into<i128>,
517+
layout: TyLayout<'tcx>,
518+
) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> {
519+
let int = int.into();
520+
// If `int` does not fit in `size` bits, we error instead of letting
521+
// `ImmTy::from_int` panic.
522+
let size = layout.size;
523+
let truncated = truncate(int as u128, size);
524+
if sign_extend(truncated, size) as i128 != int {
525+
throw_unsup_format!(
526+
"Signed value {:#x} does not fit in {} bits",
527+
int,
528+
size.bits()
529+
)
530+
}
531+
Ok(ImmTy::from_int(int, layout))
532+
}
533+
534+
// FIXME: change `ImmTy::from_uint` so it returns an `InterpResult` instead and remove this
535+
// function.
536+
pub fn immty_from_uint_checked<'tcx>(
537+
int: impl Into<u128>,
538+
layout: TyLayout<'tcx>,
539+
) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> {
540+
let int = int.into();
541+
// If `int` does not fit in `size` bits, we error instead of letting
542+
// `ImmTy::from_int` panic.
543+
let size = layout.size;
544+
if truncate(int, size) != int {
545+
throw_unsup_format!(
546+
"Unsigned value {:#x} does not fit in {} bits",
547+
int,
548+
size.bits()
549+
)
550+
}
551+
Ok(ImmTy::from_uint(int, layout))
552+
}
553+

src/shims/env.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ impl EnvVars {
2020
ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
2121
mut excluded_env_vars: Vec<String>,
2222
) {
23-
// Exclude `TERM` var to avoid terminfo trying to open the termcap file.
24-
excluded_env_vars.push("TERM".to_owned());
23+
24+
// FIXME: this can be removed when we have the `stat64` shim for macos.
25+
if ecx.tcx.sess.target.target.target_os.to_lowercase() != "linux" {
26+
// Exclude `TERM` var to avoid terminfo trying to open the termcap file.
27+
excluded_env_vars.push("TERM".to_owned());
28+
}
2529

2630
if ecx.machine.communicate {
2731
for (name, value) in env::vars() {

src/shims/foreign_items.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
303303
.expect("Failed to get libc::SYS_getrandom")
304304
.to_machine_usize(this)?;
305305

306-
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
307-
// is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
306+
let sys_statx = this
307+
.eval_path_scalar(&["libc", "SYS_statx"])?
308+
.expect("Failed to get libc::SYS_statx")
309+
.to_machine_usize(this)?;
310+
308311
match this.read_scalar(args[0])?.to_machine_usize(this)? {
312+
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
313+
// is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
309314
id if id == sys_getrandom => {
310315
// The first argument is the syscall id,
311316
// so skip over it.
312317
linux_getrandom(this, &args[1..], dest)?;
313318
}
319+
id if id == sys_statx => {
320+
// The first argument is the syscall id,
321+
// so skip over it.
322+
let result = this.statx(args[1], args[2], args[3], args[4], args[5])?;
323+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
324+
}
314325
id => throw_unsup_format!("miri does not support syscall ID {}", id),
315326
}
316327
}

src/shims/fs.rs

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use std::collections::HashMap;
2-
use std::convert::TryFrom;
2+
use std::convert::{TryInto, TryFrom};
33
use std::fs::{remove_file, File, OpenOptions};
44
use std::io::{Read, Write};
5+
use std::path::PathBuf;
6+
use std::time::SystemTime;
57

6-
use rustc::ty::layout::{Size, Align};
8+
use rustc::ty::layout::{Size, Align, LayoutOf};
79

810
use crate::stacked_borrows::Tag;
911
use crate::*;
12+
use helpers::immty_from_uint_checked;
13+
use shims::time::system_time_to_duration;
1014

1115
#[derive(Debug)]
1216
pub struct FileHandle {
@@ -98,7 +102,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
98102

99103
let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
100104

101-
let fd = options.open(path).map(|file| {
105+
let fd = options.open(&path).map(|file| {
102106
let mut fh = &mut this.machine.file_handler;
103107
fh.low += 1;
104108
fh.handles.insert(fh.low, FileHandle { file }).unwrap_none();
@@ -257,6 +261,181 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
257261
this.try_unwrap_io_result(result)
258262
}
259263

264+
fn statx(
265+
&mut self,
266+
dirfd_op: OpTy<'tcx, Tag>, // Should be an `int`
267+
pathname_op: OpTy<'tcx, Tag>, // Should be a `const char *`
268+
flags_op: OpTy<'tcx, Tag>, // Should be an `int`
269+
_mask_op: OpTy<'tcx, Tag>, // Should be an `unsigned int`
270+
statxbuf_op: OpTy<'tcx, Tag> // Should be a `struct statx *`
271+
) -> InterpResult<'tcx, i32> {
272+
let this = self.eval_context_mut();
273+
274+
this.check_no_isolation("statx")?;
275+
276+
let statxbuf_scalar = this.read_scalar(statxbuf_op)?.not_undef()?;
277+
let pathname_scalar = this.read_scalar(pathname_op)?.not_undef()?;
278+
279+
// If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
280+
if this.is_null(statxbuf_scalar)? || this.is_null(pathname_scalar)? {
281+
let efault = this.eval_libc("EFAULT")?;
282+
this.set_last_error(efault)?;
283+
return Ok(-1);
284+
}
285+
286+
// Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
287+
// proper `MemPlace` and then write the results of this function to it. However, the
288+
// `syscall` function is untyped. This means that all the `statx` parameters are provided
289+
// as `isize`s instead of having the proper types. Thus, we have to recover the layout of
290+
// `statxbuf_op` by using the `libc::statx` struct type.
291+
let statxbuf_place = {
292+
// FIXME: This long path is required because `libc::statx` is an struct and also a
293+
// function and `resolve_path` is returning the latter.
294+
let statx_ty = this
295+
.resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])?
296+
.ty(*this.tcx);
297+
let statxbuf_ty = this.tcx.mk_mut_ptr(statx_ty);
298+
let statxbuf_layout = this.layout_of(statxbuf_ty)?;
299+
let statxbuf_imm = ImmTy::from_scalar(statxbuf_scalar, statxbuf_layout);
300+
this.ref_to_mplace(statxbuf_imm)?
301+
};
302+
303+
let path: PathBuf = this.read_os_str_from_c_str(pathname_scalar)?.into();
304+
// `flags` should be a `c_int` but the `syscall` function provides an `isize`.
305+
let flags: i32 = this
306+
.read_scalar(flags_op)?
307+
.to_machine_isize(&*this.tcx)?
308+
.try_into()
309+
.map_err(|e| err_unsup_format!(
310+
"Failed to convert pointer sized operand to integer: {}",
311+
e
312+
))?;
313+
// `dirfd` should be a `c_int` but the `syscall` function provides an `isize`.
314+
let dirfd: i32 = this
315+
.read_scalar(dirfd_op)?
316+
.to_machine_isize(&*this.tcx)?
317+
.try_into()
318+
.map_err(|e| err_unsup_format!(
319+
"Failed to convert pointer sized operand to integer: {}",
320+
e
321+
))?;
322+
// we only support interpreting `path` as an absolute directory or as a directory relative
323+
// to `dirfd` when the latter is `AT_FDCWD`. The behavior of `statx` with a relative path
324+
// and a directory file descriptor other than `AT_FDCWD` is specified but it cannot be
325+
// tested from `libstd`. If you found this error, please open an issue reporting it.
326+
if !(path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")?)
327+
{
328+
throw_unsup_format!(
329+
"Using statx with a relative path and a file descriptor different from `AT_FDCWD` is not supported"
330+
)
331+
}
332+
333+
// the `_mask_op` paramter specifies the file information that the caller requested.
334+
// However `statx` is allowed to return information that was not requested or to not
335+
// return information that was requested. This `mask` represents the information we can
336+
// actually provide in any host platform.
337+
let mut mask =
338+
this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
339+
340+
// If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
341+
// symbolic links.
342+
let metadata = if flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? != 0 {
343+
// FIXME: metadata for symlinks need testing.
344+
std::fs::symlink_metadata(path)
345+
} else {
346+
std::fs::metadata(path)
347+
};
348+
349+
let metadata = match metadata {
350+
Ok(metadata) => metadata,
351+
Err(e) => {
352+
this.set_last_error_from_io_error(e)?;
353+
return Ok(-1);
354+
}
355+
};
356+
357+
let file_type = metadata.file_type();
358+
359+
let mode_name = if file_type.is_file() {
360+
"S_IFREG"
361+
} else if file_type.is_dir() {
362+
"S_IFDIR"
363+
} else {
364+
"S_IFLNK"
365+
};
366+
367+
// The `mode` field specifies the type of the file and the permissions over the file for
368+
// the owner, its group and other users. Given that we can only provide the file type
369+
// without using platform specific methods, we only set the bits corresponding to the file
370+
// type. This should be an `__u16` but `libc` provides its values as `u32`.
371+
let mode: u16 = this.eval_libc(mode_name)?
372+
.to_u32()?
373+
.try_into()
374+
.unwrap_or_else(|_| bug!("libc contains bad value for `{}` constant", mode_name));
375+
376+
let size = metadata.len();
377+
378+
let (access_sec, access_nsec) = extract_sec_and_nsec(
379+
metadata.accessed(),
380+
&mut mask,
381+
this.eval_libc("STATX_ATIME")?.to_u32()?
382+
)?;
383+
384+
let (created_sec, created_nsec) = extract_sec_and_nsec(
385+
metadata.created(),
386+
&mut mask,
387+
this.eval_libc("STATX_BTIME")?.to_u32()?
388+
)?;
389+
390+
let (modified_sec, modified_nsec) = extract_sec_and_nsec(
391+
metadata.modified(),
392+
&mut mask,
393+
this.eval_libc("STATX_MTIME")?.to_u32()?
394+
)?;
395+
396+
let __u32_layout = this.libc_ty_layout("__u32")?;
397+
let __u64_layout = this.libc_ty_layout("__u64")?;
398+
let __u16_layout = this.libc_ty_layout("__u16")?;
399+
400+
// Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a
401+
// zero for the unavailable fields.
402+
// FIXME: Provide more fields using platform specific methods.
403+
let imms = [
404+
immty_from_uint_checked(mask, __u32_layout)?, // stx_mask
405+
immty_from_uint_checked(0u128, __u32_layout)?, // stx_blksize
406+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
407+
immty_from_uint_checked(0u128, __u32_layout)?, // stx_nlink
408+
immty_from_uint_checked(0u128, __u32_layout)?, // stx_uid
409+
immty_from_uint_checked(0u128, __u32_layout)?, // stx_gid
410+
immty_from_uint_checked(mode, __u16_layout)?, // stx_mode
411+
immty_from_uint_checked(0u128, __u16_layout)?, // statx padding
412+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino
413+
immty_from_uint_checked(size, __u64_layout)?, // stx_size
414+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks
415+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
416+
immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec
417+
immty_from_uint_checked(access_nsec, __u32_layout)?, // stx_atime.tv_nsec
418+
immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
419+
immty_from_uint_checked(created_sec, __u64_layout)?, // stx_btime.tv_sec
420+
immty_from_uint_checked(created_nsec, __u32_layout)?, // stx_btime.tv_nsec
421+
immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
422+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_ctime.tv_sec
423+
immty_from_uint_checked(0u128, __u32_layout)?, // stx_ctime.tv_nsec
424+
immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
425+
immty_from_uint_checked(modified_sec, __u64_layout)?, // stx_mtime.tv_sec
426+
immty_from_uint_checked(modified_nsec, __u32_layout)?, // stx_mtime.tv_nsec
427+
immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
428+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_major
429+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_minor
430+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_major
431+
immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_minor
432+
];
433+
434+
this.write_packed_immediates(&statxbuf_place, &imms)?;
435+
436+
Ok(0)
437+
}
438+
260439
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
261440
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
262441
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
@@ -268,3 +447,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
268447
Ok((-1).into())
269448
}
270449
}
450+
451+
// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch, and
452+
// then sets the `mask` bits determined by `flag` when `time` is Ok. If `time` is an error, it
453+
// returns `(0, 0)` without setting any bits.
454+
fn extract_sec_and_nsec<'tcx>(time: std::io::Result<SystemTime>, mask: &mut u32, flag: u32) -> InterpResult<'tcx, (u64, u32)> {
455+
if let Ok(time) = time {
456+
let duration = system_time_to_duration(&time)?;
457+
*mask |= flag;
458+
Ok((duration.as_secs(), duration.subsec_nanos()))
459+
} else {
460+
Ok((0, 0))
461+
}
462+
}

src/shims/time.rs

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,19 @@
11
use std::time::{Duration, SystemTime};
22

3-
use rustc::ty::layout::TyLayout;
4-
53
use crate::stacked_borrows::Tag;
64
use crate::*;
5+
use helpers::immty_from_int_checked;
76

8-
// Returns the time elapsed between now and the unix epoch as a `Duration` and the sign of the time
9-
// interval
7+
// Returns the time elapsed between now and the unix epoch as a `Duration`.
108
fn get_time<'tcx>() -> InterpResult<'tcx, Duration> {
11-
SystemTime::now()
12-
.duration_since(SystemTime::UNIX_EPOCH)
13-
.map_err(|_| err_unsup_format!("Times before the Unix epoch are not supported").into())
9+
system_time_to_duration(&SystemTime::now())
1410
}
1511

16-
fn int_to_immty_checked<'tcx>(
17-
int: i128,
18-
layout: TyLayout<'tcx>,
19-
) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> {
20-
// If `int` does not fit in `size` bits, we error instead of letting
21-
// `ImmTy::from_int` panic.
22-
let size = layout.size;
23-
let truncated = truncate(int as u128, size);
24-
if sign_extend(truncated, size) as i128 != int {
25-
throw_unsup_format!(
26-
"Signed value {:#x} does not fit in {} bits",
27-
int,
28-
size.bits()
29-
)
30-
}
31-
Ok(ImmTy::from_int(int, layout))
12+
// Returns the time elapsed between the provided time and the unix epoch as a `Duration`.
13+
pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> {
14+
time
15+
.duration_since(SystemTime::UNIX_EPOCH)
16+
.map_err(|_| err_unsup_format!("Times before the Unix epoch are not supported").into())
3217
}
3318

3419
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
@@ -57,8 +42,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
5742
let tv_nsec = duration.subsec_nanos() as i128;
5843

5944
let imms = [
60-
int_to_immty_checked(tv_sec, this.libc_ty_layout("time_t")?)?,
61-
int_to_immty_checked(tv_nsec, this.libc_ty_layout("c_long")?)?,
45+
immty_from_int_checked(tv_sec, this.libc_ty_layout("time_t")?)?,
46+
immty_from_int_checked(tv_nsec, this.libc_ty_layout("c_long")?)?,
6247
];
6348

6449
this.write_packed_immediates(&tp, &imms)?;
@@ -89,8 +74,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
8974
let tv_usec = duration.subsec_micros() as i128;
9075

9176
let imms = [
92-
int_to_immty_checked(tv_sec, this.libc_ty_layout("time_t")?)?,
93-
int_to_immty_checked(tv_usec, this.libc_ty_layout("suseconds_t")?)?,
77+
immty_from_int_checked(tv_sec, this.libc_ty_layout("time_t")?)?,
78+
immty_from_int_checked(tv_usec, this.libc_ty_layout("suseconds_t")?)?,
9479
];
9580

9681
this.write_packed_immediates(&tv, &imms)?;

0 commit comments

Comments
 (0)