Skip to content

Commit e7f5c4f

Browse files
committed
Auto merge of #1151 - divergentdave:statx-AT_EMPTY_PATH, r=RalfJung
Add support for AT_EMPTY_PATH to statx shim This enables use of `File::metadata()`.
2 parents aff1e43 + ef154df commit e7f5c4f

File tree

5 files changed

+142
-69
lines changed

5 files changed

+142
-69
lines changed

src/helpers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
339339
// different values into a struct.
340340
fn write_packed_immediates(
341341
&mut self,
342-
place: &MPlaceTy<'tcx, Tag>,
342+
place: MPlaceTy<'tcx, Tag>,
343343
imms: &[ImmTy<'tcx, Tag>],
344344
) -> InterpResult<'tcx> {
345345
let this = self.eval_context_mut();

src/shims/foreign_items.rs

+5
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
509509
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
510510
}
511511

512+
"fstat$INODE64" => {
513+
let result = this.fstat(args[0], args[1])?;
514+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
515+
}
516+
512517
"clock_gettime" => {
513518
let result = this.clock_gettime(args[0], args[1])?;
514519
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;

src/shims/fs.rs

+132-66
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
328328
this.stat_or_lstat(false, path_op, buf_op)
329329
}
330330

331+
fn fstat(
332+
&mut self,
333+
fd_op: OpTy<'tcx, Tag>,
334+
buf_op: OpTy<'tcx, Tag>,
335+
) -> InterpResult<'tcx, i32> {
336+
let this = self.eval_context_mut();
337+
338+
this.check_no_isolation("fstat")?;
339+
340+
if this.tcx.sess.target.target.target_os.to_lowercase() != "macos" {
341+
throw_unsup_format!("The `fstat` shim is only available for `macos` targets.")
342+
}
343+
344+
let fd = this.read_scalar(fd_op)?.to_i32()?;
345+
346+
let metadata = match FileMetadata::from_fd(this, fd)? {
347+
Some(metadata) => metadata,
348+
None => return Ok(-1),
349+
};
350+
stat_macos_write_buf(this, metadata, buf_op)
351+
}
352+
331353
fn stat_or_lstat(
332354
&mut self,
333355
follow_symlink: bool,
@@ -343,66 +365,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
343365
let path_scalar = this.read_scalar(path_op)?.not_undef()?;
344366
let path: PathBuf = this.read_os_str_from_c_str(path_scalar)?.into();
345367

346-
let buf = this.deref_operand(buf_op)?;
347-
348-
let metadata = match FileMetadata::new(this, path, follow_symlink)? {
368+
let metadata = match FileMetadata::from_path(this, path, follow_symlink)? {
349369
Some(metadata) => metadata,
350370
None => return Ok(-1),
351371
};
352-
353-
let mode: u16 = metadata.mode.to_u16()?;
354-
355-
let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
356-
let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
357-
let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
358-
359-
let dev_t_layout = this.libc_ty_layout("dev_t")?;
360-
let mode_t_layout = this.libc_ty_layout("mode_t")?;
361-
let nlink_t_layout = this.libc_ty_layout("nlink_t")?;
362-
let ino_t_layout = this.libc_ty_layout("ino_t")?;
363-
let uid_t_layout = this.libc_ty_layout("uid_t")?;
364-
let gid_t_layout = this.libc_ty_layout("gid_t")?;
365-
let time_t_layout = this.libc_ty_layout("time_t")?;
366-
let long_layout = this.libc_ty_layout("c_long")?;
367-
let off_t_layout = this.libc_ty_layout("off_t")?;
368-
let blkcnt_t_layout = this.libc_ty_layout("blkcnt_t")?;
369-
let blksize_t_layout = this.libc_ty_layout("blksize_t")?;
370-
let uint32_t_layout = this.libc_ty_layout("uint32_t")?;
371-
372-
// We need to add 32 bits of padding after `st_rdev` if we are on a 64-bit platform.
373-
let pad_layout = if this.tcx.sess.target.ptr_width == 64 {
374-
uint32_t_layout
375-
} else {
376-
this.layout_of(this.tcx.mk_unit())?
377-
};
378-
379-
let imms = [
380-
immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev
381-
immty_from_uint_checked(mode, mode_t_layout)?, // st_mode
382-
immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink
383-
immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino
384-
immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid
385-
immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid
386-
immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev
387-
immty_from_uint_checked(0u128, pad_layout)?, // padding for 64-bit targets
388-
immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime
389-
immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec
390-
immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime
391-
immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec
392-
immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime
393-
immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec
394-
immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime
395-
immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec
396-
immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size
397-
immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks
398-
immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize
399-
immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags
400-
immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen
401-
];
402-
403-
this.write_packed_immediates(&buf, &imms)?;
404-
405-
Ok(0)
372+
stat_macos_write_buf(this, metadata, buf_op)
406373
}
407374

408375
fn statx(
@@ -454,18 +421,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
454421
this.read_scalar(flags_op)?.to_machine_isize(&*this.tcx)?.try_into().map_err(|e| {
455422
err_unsup_format!("Failed to convert pointer sized operand to integer: {}", e)
456423
})?;
424+
let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
457425
// `dirfd` should be a `c_int` but the `syscall` function provides an `isize`.
458426
let dirfd: i32 =
459427
this.read_scalar(dirfd_op)?.to_machine_isize(&*this.tcx)?.try_into().map_err(|e| {
460428
err_unsup_format!("Failed to convert pointer sized operand to integer: {}", e)
461429
})?;
462-
// we only support interpreting `path` as an absolute directory or as a directory relative
463-
// to `dirfd` when the latter is `AT_FDCWD`. The behavior of `statx` with a relative path
464-
// and a directory file descriptor other than `AT_FDCWD` is specified but it cannot be
465-
// tested from `libstd`. If you found this error, please open an issue reporting it.
466-
if !(path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")?) {
430+
// We only support:
431+
// * interpreting `path` as an absolute directory,
432+
// * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
433+
// * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
434+
// set.
435+
// Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
436+
// found this error, please open an issue reporting it.
437+
if !(
438+
path.is_absolute() ||
439+
dirfd == this.eval_libc_i32("AT_FDCWD")? ||
440+
(path.as_os_str().is_empty() && empty_path_flag)
441+
) {
467442
throw_unsup_format!(
468-
"Using statx with a relative path and a file descriptor different from `AT_FDCWD` is not supported"
443+
"Using statx is only supported with absolute paths, relative paths with the file \
444+
descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
445+
file descriptor"
469446
)
470447
}
471448

@@ -480,7 +457,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
480457
// symbolic links.
481458
let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
482459

483-
let metadata = match FileMetadata::new(this, path, follow_symlink)? {
460+
// If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
461+
// represented by dirfd, whether it's a directory or otherwise.
462+
let metadata = if path.as_os_str().is_empty() && empty_path_flag {
463+
FileMetadata::from_fd(this, dirfd)?
464+
} else {
465+
FileMetadata::from_path(this, path, follow_symlink)?
466+
};
467+
let metadata = match metadata {
484468
Some(metadata) => metadata,
485469
None => return Ok(-1),
486470
};
@@ -549,7 +533,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
549533
immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_minor
550534
];
551535

552-
this.write_packed_immediates(&statxbuf_place, &imms)?;
536+
this.write_packed_immediates(statxbuf_place, &imms)?;
553537

554538
Ok(0)
555539
}
@@ -589,7 +573,7 @@ struct FileMetadata {
589573
}
590574

591575
impl FileMetadata {
592-
fn new<'tcx, 'mir>(
576+
fn from_path<'tcx, 'mir>(
593577
ecx: &mut MiriEvalContext<'mir, 'tcx>,
594578
path: PathBuf,
595579
follow_symlink: bool
@@ -600,6 +584,27 @@ impl FileMetadata {
600584
std::fs::symlink_metadata(path)
601585
};
602586

587+
FileMetadata::from_meta(ecx, metadata)
588+
}
589+
590+
fn from_fd<'tcx, 'mir>(
591+
ecx: &mut MiriEvalContext<'mir, 'tcx>,
592+
fd: i32,
593+
) -> InterpResult<'tcx, Option<FileMetadata>> {
594+
let option = ecx.machine.file_handler.handles.get(&fd);
595+
let handle = match option {
596+
Some(handle) => handle,
597+
None => return ecx.handle_not_found().map(|_: i32| None),
598+
};
599+
let metadata = handle.file.metadata();
600+
601+
FileMetadata::from_meta(ecx, metadata)
602+
}
603+
604+
fn from_meta<'tcx, 'mir>(
605+
ecx: &mut MiriEvalContext<'mir, 'tcx>,
606+
metadata: Result<std::fs::Metadata, std::io::Error>,
607+
) -> InterpResult<'tcx, Option<FileMetadata>> {
603608
let metadata = match metadata {
604609
Ok(metadata) => metadata,
605610
Err(e) => {
@@ -630,3 +635,64 @@ impl FileMetadata {
630635
Ok(Some(FileMetadata { mode, size, created, accessed, modified }))
631636
}
632637
}
638+
639+
fn stat_macos_write_buf<'tcx, 'mir>(
640+
ecx: &mut MiriEvalContext<'mir, 'tcx>,
641+
metadata: FileMetadata,
642+
buf_op: OpTy<'tcx, Tag>,
643+
) -> InterpResult<'tcx, i32> {
644+
let mode: u16 = metadata.mode.to_u16()?;
645+
646+
let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
647+
let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
648+
let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
649+
650+
let dev_t_layout = ecx.libc_ty_layout("dev_t")?;
651+
let mode_t_layout = ecx.libc_ty_layout("mode_t")?;
652+
let nlink_t_layout = ecx.libc_ty_layout("nlink_t")?;
653+
let ino_t_layout = ecx.libc_ty_layout("ino_t")?;
654+
let uid_t_layout = ecx.libc_ty_layout("uid_t")?;
655+
let gid_t_layout = ecx.libc_ty_layout("gid_t")?;
656+
let time_t_layout = ecx.libc_ty_layout("time_t")?;
657+
let long_layout = ecx.libc_ty_layout("c_long")?;
658+
let off_t_layout = ecx.libc_ty_layout("off_t")?;
659+
let blkcnt_t_layout = ecx.libc_ty_layout("blkcnt_t")?;
660+
let blksize_t_layout = ecx.libc_ty_layout("blksize_t")?;
661+
let uint32_t_layout = ecx.libc_ty_layout("uint32_t")?;
662+
663+
// We need to add 32 bits of padding after `st_rdev` if we are on a 64-bit platform.
664+
let pad_layout = if ecx.tcx.sess.target.ptr_width == 64 {
665+
uint32_t_layout
666+
} else {
667+
ecx.layout_of(ecx.tcx.mk_unit())?
668+
};
669+
670+
let imms = [
671+
immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev
672+
immty_from_uint_checked(mode, mode_t_layout)?, // st_mode
673+
immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink
674+
immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino
675+
immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid
676+
immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid
677+
immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev
678+
immty_from_uint_checked(0u128, pad_layout)?, // padding for 64-bit targets
679+
immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime
680+
immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec
681+
immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime
682+
immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec
683+
immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime
684+
immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec
685+
immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime
686+
immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec
687+
immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size
688+
immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks
689+
immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize
690+
immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags
691+
immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen
692+
];
693+
694+
let buf = ecx.deref_operand(buf_op)?;
695+
ecx.write_packed_immediates(buf, &imms)?;
696+
697+
Ok(0)
698+
}

src/shims/time.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4545
immty_from_int_checked(tv_nsec, this.libc_ty_layout("c_long")?)?,
4646
];
4747

48-
this.write_packed_immediates(&tp, &imms)?;
48+
this.write_packed_immediates(tp, &imms)?;
4949

5050
Ok(0)
5151
}
@@ -77,7 +77,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
7777
immty_from_int_checked(tv_usec, this.libc_ty_layout("suseconds_t")?)?,
7878
];
7979

80-
this.write_packed_immediates(&tv, &imms)?;
80+
this.write_packed_immediates(tv, &imms)?;
8181

8282
Ok(0)
8383
}

tests/run-pass/fs.rs

+2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ fn main() {
2929
let mut file = File::create(&path).unwrap();
3030
// Writing 0 bytes should not change the file contents.
3131
file.write(&mut []).unwrap();
32+
assert_eq!(file.metadata().unwrap().len(), 0);
3233

3334
file.write(bytes).unwrap();
35+
assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
3436
// Test opening, reading and closing a file.
3537
let mut file = File::open(&path).unwrap();
3638
let mut contents = Vec::new();

0 commit comments

Comments
 (0)