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, FdNum, FileDescription};
13use crate::shims::sig::Varargs;
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 _args: Varargs<'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 close(&mut self, fd_num: FdNum) -> InterpResult<'tcx, Scalar> {
92 let this = self.eval_context_mut();
93
94 let Some(fd) = this.machine.fds.remove(fd_num) else {
95 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
96 };
97 if this.tcx.sess.target.os == Os::Illumos {
98 if let Some(watched) = fd.readiness_watched() {
110 watched.remove_file_num_interests(fd.id(), fd_num);
111 }
112 }
113 drop(fd);
114 interp_ok(Scalar::from_i32(0))
117 }
118
119 fn dup(&mut self, old_fd_num: FdNum) -> InterpResult<'tcx, Scalar> {
120 let this = self.eval_context_mut();
121
122 let Some(fd) = this.machine.fds.get(old_fd_num) else {
123 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
124 };
125 interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
126 }
127
128 fn dup2(&mut self, old_fd_num: FdNum, new_fd_num: FdNum) -> InterpResult<'tcx, Scalar> {
129 let this = self.eval_context_mut();
130
131 let Some(fd) = this.machine.fds.get(old_fd_num) else {
132 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
133 };
134 if new_fd_num != old_fd_num {
135 if this.machine.fds.get(new_fd_num).is_some() {
136 let ret = this.close(new_fd_num)?;
138 assert!(ret.to_i32().unwrap() == 0);
139 }
140 let actual_fd_num = this.machine.fds.insert_with_min_num(fd, new_fd_num);
142 assert_eq!(actual_fd_num, new_fd_num);
143 }
144 interp_ok(Scalar::from_i32(new_fd_num))
145 }
146
147 fn flock(&mut self, fd_num: FdNum, op: i32) -> InterpResult<'tcx, Scalar> {
148 let this = self.eval_context_mut();
149 let Some(fd) = this.machine.fds.get(fd_num) else {
150 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
151 };
152
153 let lock_sh = this.eval_libc_i32("LOCK_SH");
155 let lock_ex = this.eval_libc_i32("LOCK_EX");
156 let lock_nb = this.eval_libc_i32("LOCK_NB");
157 let lock_un = this.eval_libc_i32("LOCK_UN");
158
159 use FlockOp::*;
160 let parsed_op = if op == lock_sh {
161 SharedLock { nonblocking: false }
162 } else if op == lock_sh | lock_nb {
163 SharedLock { nonblocking: true }
164 } else if op == lock_ex {
165 ExclusiveLock { nonblocking: false }
166 } else if op == lock_ex | lock_nb {
167 ExclusiveLock { nonblocking: true }
168 } else if op == lock_un {
169 Unlock
170 } else {
171 throw_unsup_format!("unsupported flags {:#x}", op);
172 };
173
174 let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
175 let result = result.map(|()| 0i32);
177 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
178 }
179
180 fn ioctl(
181 &mut self,
182 fd: &OpTy<'tcx>,
183 op: &OpTy<'tcx>,
184 varargs: Varargs<'tcx, '_>,
185 ) -> InterpResult<'tcx, Scalar> {
186 let this = self.eval_context_mut();
187
188 let fd = this.read_scalar(fd)?.to_i32()?;
189 let op = this.read_scalar(op)?;
190
191 let Some(fd) = this.machine.fds.get(fd) else {
192 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
193 };
194
195 let fioclex = this.eval_libc("FIOCLEX");
197 let fionclex = this.eval_libc("FIONCLEX");
198 if op == fioclex || op == fionclex {
199 return interp_ok(Scalar::from_i32(0));
201 }
202
203 let return_value = fd.as_unix(this).ioctl(op, varargs, this)?;
206 interp_ok(Scalar::from_i32(return_value))
207 }
208
209 fn fcntl(
210 &mut self,
211 fd_num: &OpTy<'tcx>,
212 cmd: &OpTy<'tcx>,
213 varargs: Varargs<'tcx, '_>,
214 ) -> InterpResult<'tcx, Scalar> {
215 let this = self.eval_context_mut();
216
217 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
218 let cmd = this.read_scalar(cmd)?.to_i32()?;
219
220 let f_getfd = this.eval_libc_i32("F_GETFD");
221 let f_dupfd = this.eval_libc_i32("F_DUPFD");
222 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
223 let f_getfl = this.eval_libc_i32("F_GETFL");
224 let f_setfl = this.eval_libc_i32("F_SETFL");
225
226 match cmd {
228 cmd if cmd == f_getfd => {
229 if !this.machine.fds.is_fd_num(fd_num) {
234 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
235 } else {
236 interp_ok(this.eval_libc("FD_CLOEXEC"))
237 }
238 }
239 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
240 let cmd_name = if cmd == f_dupfd {
245 "fcntl(fd, F_DUPFD, ...)"
246 } else {
247 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
248 };
249
250 let ([start], _) = this.check_varargs(shim_varargs![i32], varargs, cmd_name)?;
251 let start = this.read_scalar(start)?.to_i32()?;
252
253 if let Some(fd) = this.machine.fds.get(fd_num) {
254 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
255 } else {
256 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
257 }
258 }
259 cmd if cmd == f_getfl => {
260 let Some(fd) = this.machine.fds.get(fd_num) else {
262 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
263 };
264
265 fd.get_flags(this)
266 }
267 cmd if cmd == f_setfl => {
268 let Some(fd) = this.machine.fds.get(fd_num) else {
270 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
271 };
272
273 let ([flag], _) =
274 this.check_varargs(shim_varargs![i32], varargs, "fcntl(fd, F_SETFL, ...)")?;
275 let flag = this.read_scalar(flag)?.to_i32()?;
276
277 let ignored_flags = this.eval_libc_i32("O_RDONLY")
282 | this.eval_libc_i32("O_WRONLY")
283 | this.eval_libc_i32("O_RDWR")
284 | this.eval_libc_i32("O_CREAT")
285 | this.eval_libc_i32("O_EXCL")
286 | this.eval_libc_i32("O_NOCTTY")
287 | this.eval_libc_i32("O_TRUNC");
288
289 fd.set_flags(flag & !ignored_flags, this)
290 }
291 cmd if this.tcx.sess.target.os == Os::MacOs
292 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
293 {
294 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
296 this.reject_in_isolation("`fcntl`", reject_with)?;
297 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
298 }
299
300 this.ffullsync_fd(fd_num)
301 }
302 cmd => {
303 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
304 }
305 }
306 }
307
308 fn read(
314 &mut self,
315 fd_num: i32,
316 buf: Pointer,
317 count: u64,
318 offset: Option<i128>,
319 dest: &MPlaceTy<'tcx>,
320 ) -> InterpResult<'tcx> {
321 let this = self.eval_context_mut();
322
323 trace!("Reading from FD {}, size {}", fd_num, count);
326
327 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
329
330 let count = count
333 .min(u64::try_from(this.target_isize_max()).unwrap())
334 .min(u64::try_from(isize::MAX).unwrap());
335 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
339 trace!("read: FD not found");
340 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
341 };
342
343 trace!("read: FD mapped to {fd:?}");
344 let dest = dest.clone();
349 this.read_from_fd(
350 fd,
351 buf,
352 count,
353 offset,
354 callback!(
355 @capture<'tcx> {
356 count: usize,
357 dest: MPlaceTy<'tcx>,
358 }
359 |this, result: Result<usize, IoError>| {
360 match result {
361 Ok(read_size) => {
362 assert!(read_size <= count);
363 this.write_int(u64::try_from(read_size).unwrap(), &dest)
365 }
366 Err(e) => this.set_errno_and_return_neg1(e, &dest)
367 }}
368 ),
369 )
370 }
371
372 fn write(
373 &mut self,
374 fd_num: i32,
375 buf: Pointer,
376 count: u64,
377 offset: Option<i128>,
378 dest: &MPlaceTy<'tcx>,
379 ) -> InterpResult<'tcx> {
380 let this = self.eval_context_mut();
381
382 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
386
387 let count = count
390 .min(u64::try_from(this.target_isize_max()).unwrap())
391 .min(u64::try_from(isize::MAX).unwrap());
392 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
396 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
397 };
398
399 let dest = dest.clone();
400 this.write_to_fd(
401 fd,
402 buf,
403 count,
404 offset,
405 callback!(
406 @capture<'tcx> {
407 count: usize,
408 dest: MPlaceTy<'tcx>,
409 }
410 |this, result: Result<usize, IoError>| {
411 match result {
412 Ok(write_size) => {
413 assert!(write_size <= count);
414 this.write_int(u64::try_from(write_size).unwrap(), &dest)
416 }
417 Err(e) => this.set_errno_and_return_neg1(e, &dest)
418
419 }}
420 ),
421 )
422 }
423
424 fn readv(
429 &mut self,
430 fd: &OpTy<'tcx>,
431 iov: &OpTy<'tcx>,
432 iovcnt: &OpTy<'tcx>,
433 offset: Option<&OpTy<'tcx>>,
434 dest: &MPlaceTy<'tcx>,
435 ) -> InterpResult<'tcx> {
436 let this = self.eval_context_mut();
437
438 let fd = this.read_scalar(fd)?.to_i32()?;
439 let iov_ptr = this.read_pointer(iov)?;
440 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
441 let offset = if let Some(offset) = offset {
443 if matches!(this.tcx.sess.target.os, Os::Solaris) {
444 throw_unsup_format!(
445 "preadv: vectored reads with offsets aren't supported on Solaris"
446 )
447 }
448 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
449 } else {
450 None
451 };
452
453 let Some(fd) = this.machine.fds.get(fd) else {
455 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
456 };
457
458 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
459 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
460
461 let mut buffers = Vec::new();
463
464 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
465 while let Some((_idx, iovec)) = array.next(this)? {
466 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
467 let iov_len: u64 = this
468 .read_scalar(&iov_len_field)?
469 .to_int(iov_len_field.layout.size)?
470 .try_into()
471 .unwrap();
472
473 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
474 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
475
476 buffers.push((iov_base_ptr, iov_len));
477 }
478
479 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
480
481 let tmp_ptr: Pointer = this
483 .allocate_ptr(
484 Size::from_bytes(total_bytes),
485 Align::ONE,
486 MemoryKind::Stack,
487 AllocInit::Uninit,
488 )?
489 .into();
490
491 let dest = dest.clone();
492 this.read_from_fd(
493 fd,
494 tmp_ptr,
495 usize::try_from(total_bytes).unwrap(),
496 offset,
497 callback!(
498 @capture<'tcx> {
499 tmp_ptr: Pointer,
500 buffers: Vec<(Pointer, u64)>,
501 dest: MPlaceTy<'tcx>
502 } |this, result: Result<usize, IoError>| {
503 let bytes_read = match result {
504 Ok(size) => {
505 this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
506 u64::try_from(size).unwrap()
507 },
508 Err(e) => {
509 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
510 return this.set_errno_and_return_neg1(e, &dest)
511 }
512 };
513 let mut remaining_bytes = bytes_read;
514
515 for (buffer_ptr, buffer_len) in buffers {
519 let tmp_ptr_with_offset =
521 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
522
523 let copy_amount = buffer_len.min(remaining_bytes);
526 this.mem_copy(
527 tmp_ptr_with_offset,
528 buffer_ptr,
529 Size::from_bytes(copy_amount),
530 true,
534 )?;
535
536 remaining_bytes = remaining_bytes.strict_sub(copy_amount);
537 if remaining_bytes == 0 {
538 break;
540 }
541 }
542
543 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
544 }),
545 )
546 }
547
548 fn writev(
552 &mut self,
553 fd: &OpTy<'tcx>,
554 iov: &OpTy<'tcx>,
555 iovcnt: &OpTy<'tcx>,
556 offset: Option<&OpTy<'tcx>>,
557 dest: &MPlaceTy<'tcx>,
558 ) -> InterpResult<'tcx> {
559 let this = self.eval_context_mut();
560
561 let fd = this.read_scalar(fd)?.to_i32()?;
562 let iov_ptr = this.read_pointer(iov)?;
563 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
564 let offset = if let Some(offset) = offset {
566 if matches!(this.tcx.sess.target.os, Os::Solaris) {
567 throw_unsup_format!(
568 "pwritev: vectored writes with offsets aren't supported on Solaris"
569 )
570 }
571 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
572 } else {
573 None
574 };
575
576 let Some(fd) = this.machine.fds.get(fd) else {
578 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
579 };
580
581 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
582 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
583
584 let mut buffers = Vec::new();
586
587 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
588 while let Some((_idx, iovec)) = array.next(this)? {
589 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
590 let iov_len: u64 = this
591 .read_scalar(&iov_len_field)?
592 .to_int(iov_len_field.layout.size)?
593 .try_into()
594 .unwrap();
595
596 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
597 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
598
599 buffers.push((iov_base_ptr, iov_len));
600 }
601
602 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
603
604 let tmp_ptr: Pointer = this
606 .allocate_ptr(
607 Size::from_bytes(total_bytes),
608 Align::ONE,
609 MemoryKind::Stack,
610 AllocInit::Uninit,
611 )?
612 .into();
613
614 let mut bytes_copied: u64 = 0;
617 for (buffer_ptr, buffer_len) in buffers {
618 let tmp_ptr_with_offset =
620 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
621
622 this.mem_copy(
623 buffer_ptr,
624 tmp_ptr_with_offset,
625 Size::from_bytes(buffer_len),
626 true,
630 )?;
631
632 bytes_copied = bytes_copied.strict_add(buffer_len);
633 }
634
635 let dest = dest.clone();
636 this.write_to_fd(
638 fd,
639 tmp_ptr,
640 usize::try_from(total_bytes).unwrap(),
641 offset,
642 callback!(
643 @capture<'tcx> {
644 tmp_ptr: Pointer,
645 dest: MPlaceTy<'tcx>,
646 }
647 |this, result: Result<usize, IoError>| {
648 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
649 match result {
650 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
651 Err(e) => this.set_errno_and_return_neg1(e, &dest)
652 }
653 }),
654 )
655 }
656}
657
658impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
659trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
660 fn read_from_fd(
667 &mut self,
668 fd: DynFileDescriptionRef,
669 ptr: Pointer,
670 len: usize,
671 offset: Option<i128>,
672 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
673 ) -> InterpResult<'tcx> {
674 let this = self.eval_context_mut();
675
676 if len == 0 {
681 return finish.call(this, Ok(0));
682 }
683
684 let len = if this.machine.short_fd_operations
687 && fd.short_fd_operations()
688 && len >= 2
689 && this.machine.rng.get_mut().random()
690 {
691 len / 2 } else {
693 len
694 };
695
696 match offset {
697 None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
698 Some(offset) => {
699 let Ok(offset) = u64::try_from(offset) else {
700 return finish.call(this, Err(LibcError("EINVAL")));
701 };
702 fd.as_unix(this).pread(
703 this.machine.communicate(),
704 offset,
705 ptr,
706 len,
707 this,
708 finish,
709 )?
710 }
711 };
712 interp_ok(())
713 }
714
715 fn write_to_fd(
722 &mut self,
723 fd: DynFileDescriptionRef,
724 ptr: Pointer,
725 len: usize,
726 offset: Option<i128>,
727 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
728 ) -> InterpResult<'tcx> {
729 let this = self.eval_context_mut();
730
731 if len == 0 {
738 return finish.call(this, Ok(0));
740 }
741
742 let len = if this.machine.short_fd_operations
746 && fd.short_fd_operations()
747 && len >= 2
748 && this.machine.rng.get_mut().random()
749 {
750 len / 2
751 } else {
752 len
753 };
754
755 match offset {
756 None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
757 Some(offset) => {
758 let Ok(offset) = u64::try_from(offset) else {
759 return finish.call(this, Err(LibcError("EINVAL")));
760 };
761 fd.as_unix(this).pwrite(
762 this.machine.communicate(),
763 ptr,
764 len,
765 offset,
766 this,
767 finish,
768 )?
769 }
770 };
771 interp_ok(())
772 }
773}