diff --git a/src/shims/foreign_items/posix/linux.rs b/src/shims/foreign_items/posix/linux.rs index b00704e47a02..a32f0fa60678 100644 --- a/src/shims/foreign_items/posix/linux.rs +++ b/src/shims/foreign_items/posix/linux.rs @@ -34,6 +34,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let result = this.linux_readdir64_r(args[0], args[1], args[2])?; this.write_scalar(Scalar::from_i32(result), dest)?; } + "ftruncate64" => { + let result = this.ftruncate64(args[0], args[1])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } // Linux-only "posix_fadvise" => { let _fd = this.read_scalar(args[0])?.to_i32()?; diff --git a/src/shims/foreign_items/posix/macos.rs b/src/shims/foreign_items/posix/macos.rs index 125b6b768530..dd3dba6ec07c 100644 --- a/src/shims/foreign_items/posix/macos.rs +++ b/src/shims/foreign_items/posix/macos.rs @@ -44,6 +44,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let result = this.macos_readdir_r(args[0], args[1], args[2])?; this.write_scalar(Scalar::from_i32(result), dest)?; } + "ftruncate" => { + let result = this.ftruncate64(args[0], args[1])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } // Environment related shims "_NSGetEnviron" => { diff --git a/src/shims/fs.rs b/src/shims/fs.rs index 3e20f5f97288..ea0b998c2e3d 100644 --- a/src/shims/fs.rs +++ b/src/shims/fs.rs @@ -1062,6 +1062,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.handle_not_found() } } + + fn ftruncate64( + &mut self, fd_op: OpTy<'tcx, Tag>, + length_op: OpTy<'tcx, Tag>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.check_no_isolation("ftruncate64")?; + + let fd = this.read_scalar(fd_op)?.to_i32()?; + let length = this.read_scalar(length_op)?.to_i64()?; + if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get_mut(&fd) { + if *writable { + if let Ok(length) = length.try_into() { + let result = file.set_len(length); + this.try_unwrap_io_result(result.map(|_| 0i32)) + } else { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + Ok(-1) + } + } else { + // The file is not writable + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + Ok(-1) + } + } else { + this.handle_not_found() + } + } } /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when diff --git a/tests/run-pass/fs.rs b/tests/run-pass/fs.rs index d8b6e5b44575..1a139de8148a 100644 --- a/tests/run-pass/fs.rs +++ b/tests/run-pass/fs.rs @@ -13,6 +13,7 @@ fn main() { test_file_create_new(); test_seek(); test_metadata(); + test_file_set_len(); test_symlink(); test_errors(); test_rename(); @@ -155,6 +156,32 @@ fn test_metadata() { remove_file(&path).unwrap(); } +fn test_file_set_len() { + let bytes = b"Hello, World!\n"; + let path = prepare_with_content("miri_test_fs_set_len.txt", bytes); + + // Test extending the file + let mut file = OpenOptions::new().read(true).write(true).open(&path).unwrap(); + let bytes_extended = b"Hello, World!\n\x00\x00\x00\x00\x00\x00"; + file.set_len(20).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes_extended, contents.as_slice()); + + // Test truncating the file + file.seek(SeekFrom::Start(0)).unwrap(); + file.set_len(10).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(&bytes[..10], contents.as_slice()); + + // Can't use set_len on a file not opened for writing + let file = OpenOptions::new().read(true).open(&path).unwrap(); + assert_eq!(ErrorKind::InvalidInput, file.set_len(14).unwrap_err().kind()); + + remove_file(&path).unwrap(); +} + fn test_symlink() { let bytes = b"Hello, World!\n"; let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);