Skip to content

Commit a3ea1cb

Browse files
committed
Auto merge of #1101 - christianpoveda:stat-shim, r=RalfJung
Add statx shim for linux target This is an attempt to fix: #999 (for linux only) Currently there is one problem that I haven't been able to solve. `std::fs::metadata` fails because the creation time is not available even though it is provided in the shim code. In order to inform the caller that the field was provided, the `stx_flag` field must have the bits of `STATX_BTIME` set (which they are). The creation time is in the `stx_btime` field of the `statx` struct (see [1]). The relevant code in `libstd` is here (probably?): https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs#L322 Another important point is that we are just providing the fields that are available in "all" platforms (this is, without using any platform specific traits or so). This can be improved later. References: [1] Man page: http://man7.org/linux/man-pages/man2/statx.2.html [2] libc `statx` struct: https://docs.rs/libc/0.2.63/libc/struct.statx.html Edit: The problem is that my filesystem is not providing it and I thought all filesystems could provide it. I changed the code so it only provides those dates if they are available. now we are ready to go. r? @RalfJung @oli-obk
2 parents a7809bf + b0c7625 commit a3ea1cb

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)