1use std::io;
5use std::io::ErrorKind;
6
7use rand::RngExt;
8use rustc_abi::{Align, Size};
9use rustc_target::spec::Os;
10
11use crate::shims::FileDescriptionRef;
12use crate::shims::files::{DynFileDescriptionRef, FileDescription};
13use crate::shims::sig::check_min_vararg_count;
14use crate::shims::unix::socket::UnixSocketFileDescription;
15use crate::shims::unix::*;
16use crate::*;
17
18#[derive(Debug, Clone, Copy, Eq, PartialEq)]
19pub enum FlockOp {
20 SharedLock { nonblocking: bool },
21 ExclusiveLock { nonblocking: bool },
22 Unlock,
23}
24
25pub trait UnixFileDescription: FileDescription {
27 fn pread<'tcx>(
31 &self,
32 _communicate_allowed: bool,
33 _offset: u64,
34 _ptr: Pointer,
35 _len: usize,
36 _ecx: &mut MiriInterpCx<'tcx>,
37 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
38 ) -> InterpResult<'tcx> {
39 throw_unsup_format!("cannot pread from {}", self.name());
40 }
41
42 fn pwrite<'tcx>(
47 &self,
48 _communicate_allowed: bool,
49 _ptr: Pointer,
50 _len: usize,
51 _offset: u64,
52 _ecx: &mut MiriInterpCx<'tcx>,
53 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
54 ) -> InterpResult<'tcx> {
55 throw_unsup_format!("cannot pwrite to {}", self.name());
56 }
57
58 fn flock<'tcx>(
59 &self,
60 _communicate_allowed: bool,
61 _op: FlockOp,
62 ) -> InterpResult<'tcx, io::Result<()>> {
63 throw_unsup_format!("cannot flock {}", self.name());
64 }
65
66 fn ioctl<'tcx>(
72 &self,
73 _op: Scalar,
74 _arg: Option<&OpTy<'tcx>>,
75 _ecx: &mut MiriInterpCx<'tcx>,
76 ) -> InterpResult<'tcx, i32> {
77 throw_unsup_format!("cannot use ioctl on {}", self.name());
78 }
79
80 fn as_socket<'tcx>(
82 self: FileDescriptionRef<Self>,
83 _ecx: &MiriInterpCx<'tcx>,
84 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
85 None
86 }
87}
88
89impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
90pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
91 fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
92 let this = self.eval_context_mut();
93
94 let Some(fd) = this.machine.fds.get(old_fd_num) else {
95 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
96 };
97 interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
98 }
99
100 fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scalar> {
101 let this = self.eval_context_mut();
102
103 let Some(fd) = this.machine.fds.get(old_fd_num) else {
104 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
105 };
106 if new_fd_num != old_fd_num {
107 if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
110 old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
112 }
113 }
114 interp_ok(Scalar::from_i32(new_fd_num))
115 }
116
117 fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
118 let this = self.eval_context_mut();
119 let Some(fd) = this.machine.fds.get(fd_num) else {
120 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
121 };
122
123 let lock_sh = this.eval_libc_i32("LOCK_SH");
125 let lock_ex = this.eval_libc_i32("LOCK_EX");
126 let lock_nb = this.eval_libc_i32("LOCK_NB");
127 let lock_un = this.eval_libc_i32("LOCK_UN");
128
129 use FlockOp::*;
130 let parsed_op = if op == lock_sh {
131 SharedLock { nonblocking: false }
132 } else if op == lock_sh | lock_nb {
133 SharedLock { nonblocking: true }
134 } else if op == lock_ex {
135 ExclusiveLock { nonblocking: false }
136 } else if op == lock_ex | lock_nb {
137 ExclusiveLock { nonblocking: true }
138 } else if op == lock_un {
139 Unlock
140 } else {
141 throw_unsup_format!("unsupported flags {:#x}", op);
142 };
143
144 let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
145 let result = result.map(|()| 0i32);
147 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
148 }
149
150 fn ioctl(
151 &mut self,
152 fd: &OpTy<'tcx>,
153 op: &OpTy<'tcx>,
154 varargs: &[OpTy<'tcx>],
155 ) -> InterpResult<'tcx, Scalar> {
156 let this = self.eval_context_mut();
157
158 let fd = this.read_scalar(fd)?.to_i32()?;
159 let op = this.read_scalar(op)?;
160 let arg = varargs.first();
164
165 let Some(fd) = this.machine.fds.get(fd) else {
166 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
167 };
168
169 let fioclex = this.eval_libc("FIOCLEX");
171 let fionclex = this.eval_libc("FIONCLEX");
172 if op == fioclex || op == fionclex {
173 return interp_ok(Scalar::from_i32(0));
175 }
176
177 let return_value = fd.as_unix(this).ioctl(op, arg, this)?;
180 interp_ok(Scalar::from_i32(return_value))
181 }
182
183 fn fcntl(
184 &mut self,
185 fd_num: &OpTy<'tcx>,
186 cmd: &OpTy<'tcx>,
187 varargs: &[OpTy<'tcx>],
188 ) -> InterpResult<'tcx, Scalar> {
189 let this = self.eval_context_mut();
190
191 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
192 let cmd = this.read_scalar(cmd)?.to_i32()?;
193
194 let f_getfd = this.eval_libc_i32("F_GETFD");
195 let f_dupfd = this.eval_libc_i32("F_DUPFD");
196 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
197 let f_getfl = this.eval_libc_i32("F_GETFL");
198 let f_setfl = this.eval_libc_i32("F_SETFL");
199
200 match cmd {
202 cmd if cmd == f_getfd => {
203 if !this.machine.fds.is_fd_num(fd_num) {
208 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
209 } else {
210 interp_ok(this.eval_libc("FD_CLOEXEC"))
211 }
212 }
213 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
214 let cmd_name = if cmd == f_dupfd {
219 "fcntl(fd, F_DUPFD, ...)"
220 } else {
221 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
222 };
223
224 let [start] = check_min_vararg_count(cmd_name, varargs)?;
225 let start = this.read_scalar(start)?.to_i32()?;
226
227 if let Some(fd) = this.machine.fds.get(fd_num) {
228 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
229 } else {
230 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
231 }
232 }
233 cmd if cmd == f_getfl => {
234 let Some(fd) = this.machine.fds.get(fd_num) else {
236 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
237 };
238
239 fd.get_flags(this)
240 }
241 cmd if cmd == f_setfl => {
242 let Some(fd) = this.machine.fds.get(fd_num) else {
244 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
245 };
246
247 let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
248 let flag = this.read_scalar(flag)?.to_i32()?;
249
250 let ignored_flags = this.eval_libc_i32("O_RDONLY")
255 | this.eval_libc_i32("O_WRONLY")
256 | this.eval_libc_i32("O_RDWR")
257 | this.eval_libc_i32("O_CREAT")
258 | this.eval_libc_i32("O_EXCL")
259 | this.eval_libc_i32("O_NOCTTY")
260 | this.eval_libc_i32("O_TRUNC");
261
262 fd.set_flags(flag & !ignored_flags, this)
263 }
264 cmd if this.tcx.sess.target.os == Os::MacOs
265 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
266 {
267 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
269 this.reject_in_isolation("`fcntl`", reject_with)?;
270 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
271 }
272
273 this.ffullsync_fd(fd_num)
274 }
275 cmd => {
276 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
277 }
278 }
279 }
280
281 fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
282 let this = self.eval_context_mut();
283
284 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
285
286 let Some(fd) = this.machine.fds.remove(fd_num) else {
287 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
288 };
289 let result = fd.close_ref(this.machine.communicate(), this)?;
290 let result = result.map(|()| 0i32);
292 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
293 }
294
295 fn read(
301 &mut self,
302 fd_num: i32,
303 buf: Pointer,
304 count: u64,
305 offset: Option<i128>,
306 dest: &MPlaceTy<'tcx>,
307 ) -> InterpResult<'tcx> {
308 let this = self.eval_context_mut();
309
310 trace!("Reading from FD {}, size {}", fd_num, count);
313
314 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
316
317 let count = count
320 .min(u64::try_from(this.target_isize_max()).unwrap())
321 .min(u64::try_from(isize::MAX).unwrap());
322 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
326 trace!("read: FD not found");
327 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
328 };
329
330 trace!("read: FD mapped to {fd:?}");
331 let dest = dest.clone();
336 this.read_from_fd(
337 fd,
338 buf,
339 count,
340 offset,
341 callback!(
342 @capture<'tcx> {
343 count: usize,
344 dest: MPlaceTy<'tcx>,
345 }
346 |this, result: Result<usize, IoError>| {
347 match result {
348 Ok(read_size) => {
349 assert!(read_size <= count);
350 this.write_int(u64::try_from(read_size).unwrap(), &dest)
352 }
353 Err(e) => this.set_errno_and_return_neg1(e, &dest)
354 }}
355 ),
356 )
357 }
358
359 fn write(
360 &mut self,
361 fd_num: i32,
362 buf: Pointer,
363 count: u64,
364 offset: Option<i128>,
365 dest: &MPlaceTy<'tcx>,
366 ) -> InterpResult<'tcx> {
367 let this = self.eval_context_mut();
368
369 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
373
374 let count = count
377 .min(u64::try_from(this.target_isize_max()).unwrap())
378 .min(u64::try_from(isize::MAX).unwrap());
379 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
383 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
384 };
385
386 let dest = dest.clone();
387 this.write_to_fd(
388 fd,
389 buf,
390 count,
391 offset,
392 callback!(
393 @capture<'tcx> {
394 count: usize,
395 dest: MPlaceTy<'tcx>,
396 }
397 |this, result: Result<usize, IoError>| {
398 match result {
399 Ok(write_size) => {
400 assert!(write_size <= count);
401 this.write_int(u64::try_from(write_size).unwrap(), &dest)
403 }
404 Err(e) => this.set_errno_and_return_neg1(e, &dest)
405
406 }}
407 ),
408 )
409 }
410
411 fn readv(
416 &mut self,
417 fd: &OpTy<'tcx>,
418 iov: &OpTy<'tcx>,
419 iovcnt: &OpTy<'tcx>,
420 offset: Option<&OpTy<'tcx>>,
421 dest: &MPlaceTy<'tcx>,
422 ) -> InterpResult<'tcx> {
423 let this = self.eval_context_mut();
424
425 let fd = this.read_scalar(fd)?.to_i32()?;
426 let iov_ptr = this.read_pointer(iov)?;
427 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
428 let offset = if let Some(offset) = offset {
430 if matches!(this.tcx.sess.target.os, Os::Solaris) {
431 throw_unsup_format!(
432 "preadv: vectored reads with offsets aren't supported on Solaris"
433 )
434 }
435 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
436 } else {
437 None
438 };
439
440 let Some(fd) = this.machine.fds.get(fd) else {
442 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
443 };
444
445 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
446 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
447
448 let mut buffers = Vec::new();
450
451 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
452 while let Some((_idx, iovec)) = array.next(this)? {
453 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
454 let iov_len: u64 = this
455 .read_scalar(&iov_len_field)?
456 .to_int(iov_len_field.layout.size)?
457 .try_into()
458 .unwrap();
459
460 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
461 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
462
463 buffers.push((iov_base_ptr, iov_len));
464 }
465
466 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
467
468 let tmp_ptr: Pointer = this
470 .allocate_ptr(
471 Size::from_bytes(total_bytes),
472 Align::ONE,
473 MemoryKind::Stack,
474 AllocInit::Uninit,
475 )?
476 .into();
477
478 let dest = dest.clone();
479 this.read_from_fd(
480 fd,
481 tmp_ptr,
482 usize::try_from(total_bytes).unwrap(),
483 offset,
484 callback!(
485 @capture<'tcx> {
486 tmp_ptr: Pointer,
487 buffers: Vec<(Pointer, u64)>,
488 dest: MPlaceTy<'tcx>
489 } |this, result: Result<usize, IoError>| {
490 let bytes_read = match result {
491 Ok(size) => {
492 this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
493 u64::try_from(size).unwrap()
494 },
495 Err(e) => {
496 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
497 return this.set_errno_and_return_neg1(e, &dest)
498 }
499 };
500 let mut remaining_bytes = bytes_read;
501
502 for (buffer_ptr, buffer_len) in buffers {
506 let tmp_ptr_with_offset =
508 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
509
510 let copy_amount = buffer_len.min(remaining_bytes);
513 this.mem_copy(
514 tmp_ptr_with_offset,
515 buffer_ptr,
516 Size::from_bytes(copy_amount),
517 true,
521 )?;
522
523 remaining_bytes = remaining_bytes.strict_sub(copy_amount);
524 if remaining_bytes == 0 {
525 break;
527 }
528 }
529
530 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
531 }),
532 )
533 }
534
535 fn writev(
539 &mut self,
540 fd: &OpTy<'tcx>,
541 iov: &OpTy<'tcx>,
542 iovcnt: &OpTy<'tcx>,
543 offset: Option<&OpTy<'tcx>>,
544 dest: &MPlaceTy<'tcx>,
545 ) -> InterpResult<'tcx> {
546 let this = self.eval_context_mut();
547
548 let fd = this.read_scalar(fd)?.to_i32()?;
549 let iov_ptr = this.read_pointer(iov)?;
550 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
551 let offset = if let Some(offset) = offset {
553 if matches!(this.tcx.sess.target.os, Os::Solaris) {
554 throw_unsup_format!(
555 "pwritev: vectored writes with offsets aren't supported on Solaris"
556 )
557 }
558 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
559 } else {
560 None
561 };
562
563 let Some(fd) = this.machine.fds.get(fd) else {
565 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
566 };
567
568 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
569 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
570
571 let mut buffers = Vec::new();
573
574 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
575 while let Some((_idx, iovec)) = array.next(this)? {
576 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
577 let iov_len: u64 = this
578 .read_scalar(&iov_len_field)?
579 .to_int(iov_len_field.layout.size)?
580 .try_into()
581 .unwrap();
582
583 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
584 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
585
586 buffers.push((iov_base_ptr, iov_len));
587 }
588
589 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
590
591 let tmp_ptr: Pointer = this
593 .allocate_ptr(
594 Size::from_bytes(total_bytes),
595 Align::ONE,
596 MemoryKind::Stack,
597 AllocInit::Uninit,
598 )?
599 .into();
600
601 let mut bytes_copied: u64 = 0;
604 for (buffer_ptr, buffer_len) in buffers {
605 let tmp_ptr_with_offset =
607 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
608
609 this.mem_copy(
610 buffer_ptr,
611 tmp_ptr_with_offset,
612 Size::from_bytes(buffer_len),
613 true,
617 )?;
618
619 bytes_copied = bytes_copied.strict_add(buffer_len);
620 }
621
622 let dest = dest.clone();
623 this.write_to_fd(
625 fd,
626 tmp_ptr,
627 usize::try_from(total_bytes).unwrap(),
628 offset,
629 callback!(
630 @capture<'tcx> {
631 tmp_ptr: Pointer,
632 dest: MPlaceTy<'tcx>,
633 }
634 |this, result: Result<usize, IoError>| {
635 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
636 match result {
637 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
638 Err(e) => this.set_errno_and_return_neg1(e, &dest)
639 }
640 }),
641 )
642 }
643}
644
645impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
646trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
647 fn read_from_fd(
654 &mut self,
655 fd: DynFileDescriptionRef,
656 ptr: Pointer,
657 len: usize,
658 offset: Option<i128>,
659 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
660 ) -> InterpResult<'tcx> {
661 let this = self.eval_context_mut();
662
663 if len == 0 {
668 return finish.call(this, Ok(0));
669 }
670
671 let len = if this.machine.short_fd_operations
674 && fd.short_fd_operations()
675 && len >= 2
676 && this.machine.rng.get_mut().random()
677 {
678 len / 2 } else {
680 len
681 };
682
683 match offset {
684 None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
685 Some(offset) => {
686 let Ok(offset) = u64::try_from(offset) else {
687 return finish.call(this, Err(LibcError("EINVAL")));
688 };
689 fd.as_unix(this).pread(
690 this.machine.communicate(),
691 offset,
692 ptr,
693 len,
694 this,
695 finish,
696 )?
697 }
698 };
699 interp_ok(())
700 }
701
702 fn write_to_fd(
709 &mut self,
710 fd: DynFileDescriptionRef,
711 ptr: Pointer,
712 len: usize,
713 offset: Option<i128>,
714 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
715 ) -> InterpResult<'tcx> {
716 let this = self.eval_context_mut();
717
718 if len == 0 {
725 return finish.call(this, Ok(0));
727 }
728
729 let len = if this.machine.short_fd_operations
733 && fd.short_fd_operations()
734 && len >= 2
735 && this.machine.rng.get_mut().random()
736 {
737 len / 2
738 } else {
739 len
740 };
741
742 match offset {
743 None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
744 Some(offset) => {
745 let Ok(offset) = u64::try_from(offset) else {
746 return finish.call(this, Err(LibcError("EINVAL")));
747 };
748 fd.as_unix(this).pwrite(
749 this.machine.communicate(),
750 ptr,
751 len,
752 offset,
753 this,
754 finish,
755 )?
756 }
757 };
758 interp_ok(())
759 }
760}