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::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 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: &[OpTy<'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 let arg = varargs.first();
194
195 let Some(fd) = this.machine.fds.get(fd) else {
196 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
197 };
198
199 let fioclex = this.eval_libc("FIOCLEX");
201 let fionclex = this.eval_libc("FIONCLEX");
202 if op == fioclex || op == fionclex {
203 return interp_ok(Scalar::from_i32(0));
205 }
206
207 let return_value = fd.as_unix(this).ioctl(op, arg, this)?;
210 interp_ok(Scalar::from_i32(return_value))
211 }
212
213 fn fcntl(
214 &mut self,
215 fd_num: &OpTy<'tcx>,
216 cmd: &OpTy<'tcx>,
217 varargs: &[OpTy<'tcx>],
218 ) -> InterpResult<'tcx, Scalar> {
219 let this = self.eval_context_mut();
220
221 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
222 let cmd = this.read_scalar(cmd)?.to_i32()?;
223
224 let f_getfd = this.eval_libc_i32("F_GETFD");
225 let f_dupfd = this.eval_libc_i32("F_DUPFD");
226 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
227 let f_getfl = this.eval_libc_i32("F_GETFL");
228 let f_setfl = this.eval_libc_i32("F_SETFL");
229
230 match cmd {
232 cmd if cmd == f_getfd => {
233 if !this.machine.fds.is_fd_num(fd_num) {
238 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
239 } else {
240 interp_ok(this.eval_libc("FD_CLOEXEC"))
241 }
242 }
243 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
244 let cmd_name = if cmd == f_dupfd {
249 "fcntl(fd, F_DUPFD, ...)"
250 } else {
251 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
252 };
253
254 let [start] = check_min_vararg_count(cmd_name, varargs)?;
255 let start = this.read_scalar(start)?.to_i32()?;
256
257 if let Some(fd) = this.machine.fds.get(fd_num) {
258 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
259 } else {
260 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
261 }
262 }
263 cmd if cmd == f_getfl => {
264 let Some(fd) = this.machine.fds.get(fd_num) else {
266 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
267 };
268
269 fd.get_flags(this)
270 }
271 cmd if cmd == f_setfl => {
272 let Some(fd) = this.machine.fds.get(fd_num) else {
274 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
275 };
276
277 let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
278 let flag = this.read_scalar(flag)?.to_i32()?;
279
280 let ignored_flags = this.eval_libc_i32("O_RDONLY")
285 | this.eval_libc_i32("O_WRONLY")
286 | this.eval_libc_i32("O_RDWR")
287 | this.eval_libc_i32("O_CREAT")
288 | this.eval_libc_i32("O_EXCL")
289 | this.eval_libc_i32("O_NOCTTY")
290 | this.eval_libc_i32("O_TRUNC");
291
292 fd.set_flags(flag & !ignored_flags, this)
293 }
294 cmd if this.tcx.sess.target.os == Os::MacOs
295 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
296 {
297 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
299 this.reject_in_isolation("`fcntl`", reject_with)?;
300 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
301 }
302
303 this.ffullsync_fd(fd_num)
304 }
305 cmd => {
306 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
307 }
308 }
309 }
310
311 fn read(
317 &mut self,
318 fd_num: i32,
319 buf: Pointer,
320 count: u64,
321 offset: Option<i128>,
322 dest: &MPlaceTy<'tcx>,
323 ) -> InterpResult<'tcx> {
324 let this = self.eval_context_mut();
325
326 trace!("Reading from FD {}, size {}", fd_num, count);
329
330 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
332
333 let count = count
336 .min(u64::try_from(this.target_isize_max()).unwrap())
337 .min(u64::try_from(isize::MAX).unwrap());
338 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
342 trace!("read: FD not found");
343 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
344 };
345
346 trace!("read: FD mapped to {fd:?}");
347 let dest = dest.clone();
352 this.read_from_fd(
353 fd,
354 buf,
355 count,
356 offset,
357 callback!(
358 @capture<'tcx> {
359 count: usize,
360 dest: MPlaceTy<'tcx>,
361 }
362 |this, result: Result<usize, IoError>| {
363 match result {
364 Ok(read_size) => {
365 assert!(read_size <= count);
366 this.write_int(u64::try_from(read_size).unwrap(), &dest)
368 }
369 Err(e) => this.set_errno_and_return_neg1(e, &dest)
370 }}
371 ),
372 )
373 }
374
375 fn write(
376 &mut self,
377 fd_num: i32,
378 buf: Pointer,
379 count: u64,
380 offset: Option<i128>,
381 dest: &MPlaceTy<'tcx>,
382 ) -> InterpResult<'tcx> {
383 let this = self.eval_context_mut();
384
385 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
389
390 let count = count
393 .min(u64::try_from(this.target_isize_max()).unwrap())
394 .min(u64::try_from(isize::MAX).unwrap());
395 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
399 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
400 };
401
402 let dest = dest.clone();
403 this.write_to_fd(
404 fd,
405 buf,
406 count,
407 offset,
408 callback!(
409 @capture<'tcx> {
410 count: usize,
411 dest: MPlaceTy<'tcx>,
412 }
413 |this, result: Result<usize, IoError>| {
414 match result {
415 Ok(write_size) => {
416 assert!(write_size <= count);
417 this.write_int(u64::try_from(write_size).unwrap(), &dest)
419 }
420 Err(e) => this.set_errno_and_return_neg1(e, &dest)
421
422 }}
423 ),
424 )
425 }
426
427 fn readv(
432 &mut self,
433 fd: &OpTy<'tcx>,
434 iov: &OpTy<'tcx>,
435 iovcnt: &OpTy<'tcx>,
436 offset: Option<&OpTy<'tcx>>,
437 dest: &MPlaceTy<'tcx>,
438 ) -> InterpResult<'tcx> {
439 let this = self.eval_context_mut();
440
441 let fd = this.read_scalar(fd)?.to_i32()?;
442 let iov_ptr = this.read_pointer(iov)?;
443 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
444 let offset = if let Some(offset) = offset {
446 if matches!(this.tcx.sess.target.os, Os::Solaris) {
447 throw_unsup_format!(
448 "preadv: vectored reads with offsets aren't supported on Solaris"
449 )
450 }
451 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
452 } else {
453 None
454 };
455
456 let Some(fd) = this.machine.fds.get(fd) else {
458 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
459 };
460
461 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
462 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
463
464 let mut buffers = Vec::new();
466
467 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
468 while let Some((_idx, iovec)) = array.next(this)? {
469 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
470 let iov_len: u64 = this
471 .read_scalar(&iov_len_field)?
472 .to_int(iov_len_field.layout.size)?
473 .try_into()
474 .unwrap();
475
476 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
477 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
478
479 buffers.push((iov_base_ptr, iov_len));
480 }
481
482 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
483
484 let tmp_ptr: Pointer = this
486 .allocate_ptr(
487 Size::from_bytes(total_bytes),
488 Align::ONE,
489 MemoryKind::Stack,
490 AllocInit::Uninit,
491 )?
492 .into();
493
494 let dest = dest.clone();
495 this.read_from_fd(
496 fd,
497 tmp_ptr,
498 usize::try_from(total_bytes).unwrap(),
499 offset,
500 callback!(
501 @capture<'tcx> {
502 tmp_ptr: Pointer,
503 buffers: Vec<(Pointer, u64)>,
504 dest: MPlaceTy<'tcx>
505 } |this, result: Result<usize, IoError>| {
506 let bytes_read = match result {
507 Ok(size) => {
508 this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
509 u64::try_from(size).unwrap()
510 },
511 Err(e) => {
512 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
513 return this.set_errno_and_return_neg1(e, &dest)
514 }
515 };
516 let mut remaining_bytes = bytes_read;
517
518 for (buffer_ptr, buffer_len) in buffers {
522 let tmp_ptr_with_offset =
524 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
525
526 let copy_amount = buffer_len.min(remaining_bytes);
529 this.mem_copy(
530 tmp_ptr_with_offset,
531 buffer_ptr,
532 Size::from_bytes(copy_amount),
533 true,
537 )?;
538
539 remaining_bytes = remaining_bytes.strict_sub(copy_amount);
540 if remaining_bytes == 0 {
541 break;
543 }
544 }
545
546 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
547 }),
548 )
549 }
550
551 fn writev(
555 &mut self,
556 fd: &OpTy<'tcx>,
557 iov: &OpTy<'tcx>,
558 iovcnt: &OpTy<'tcx>,
559 offset: Option<&OpTy<'tcx>>,
560 dest: &MPlaceTy<'tcx>,
561 ) -> InterpResult<'tcx> {
562 let this = self.eval_context_mut();
563
564 let fd = this.read_scalar(fd)?.to_i32()?;
565 let iov_ptr = this.read_pointer(iov)?;
566 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
567 let offset = if let Some(offset) = offset {
569 if matches!(this.tcx.sess.target.os, Os::Solaris) {
570 throw_unsup_format!(
571 "pwritev: vectored writes with offsets aren't supported on Solaris"
572 )
573 }
574 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
575 } else {
576 None
577 };
578
579 let Some(fd) = this.machine.fds.get(fd) else {
581 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
582 };
583
584 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
585 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
586
587 let mut buffers = Vec::new();
589
590 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
591 while let Some((_idx, iovec)) = array.next(this)? {
592 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
593 let iov_len: u64 = this
594 .read_scalar(&iov_len_field)?
595 .to_int(iov_len_field.layout.size)?
596 .try_into()
597 .unwrap();
598
599 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
600 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
601
602 buffers.push((iov_base_ptr, iov_len));
603 }
604
605 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
606
607 let tmp_ptr: Pointer = this
609 .allocate_ptr(
610 Size::from_bytes(total_bytes),
611 Align::ONE,
612 MemoryKind::Stack,
613 AllocInit::Uninit,
614 )?
615 .into();
616
617 let mut bytes_copied: u64 = 0;
620 for (buffer_ptr, buffer_len) in buffers {
621 let tmp_ptr_with_offset =
623 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
624
625 this.mem_copy(
626 buffer_ptr,
627 tmp_ptr_with_offset,
628 Size::from_bytes(buffer_len),
629 true,
633 )?;
634
635 bytes_copied = bytes_copied.strict_add(buffer_len);
636 }
637
638 let dest = dest.clone();
639 this.write_to_fd(
641 fd,
642 tmp_ptr,
643 usize::try_from(total_bytes).unwrap(),
644 offset,
645 callback!(
646 @capture<'tcx> {
647 tmp_ptr: Pointer,
648 dest: MPlaceTy<'tcx>,
649 }
650 |this, result: Result<usize, IoError>| {
651 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
652 match result {
653 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
654 Err(e) => this.set_errno_and_return_neg1(e, &dest)
655 }
656 }),
657 )
658 }
659}
660
661impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
662trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
663 fn read_from_fd(
670 &mut self,
671 fd: DynFileDescriptionRef,
672 ptr: Pointer,
673 len: usize,
674 offset: Option<i128>,
675 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
676 ) -> InterpResult<'tcx> {
677 let this = self.eval_context_mut();
678
679 if len == 0 {
684 return finish.call(this, Ok(0));
685 }
686
687 let len = if this.machine.short_fd_operations
690 && fd.short_fd_operations()
691 && len >= 2
692 && this.machine.rng.get_mut().random()
693 {
694 len / 2 } else {
696 len
697 };
698
699 match offset {
700 None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
701 Some(offset) => {
702 let Ok(offset) = u64::try_from(offset) else {
703 return finish.call(this, Err(LibcError("EINVAL")));
704 };
705 fd.as_unix(this).pread(
706 this.machine.communicate(),
707 offset,
708 ptr,
709 len,
710 this,
711 finish,
712 )?
713 }
714 };
715 interp_ok(())
716 }
717
718 fn write_to_fd(
725 &mut self,
726 fd: DynFileDescriptionRef,
727 ptr: Pointer,
728 len: usize,
729 offset: Option<i128>,
730 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
731 ) -> InterpResult<'tcx> {
732 let this = self.eval_context_mut();
733
734 if len == 0 {
741 return finish.call(this, Ok(0));
743 }
744
745 let len = if this.machine.short_fd_operations
749 && fd.short_fd_operations()
750 && len >= 2
751 && this.machine.rng.get_mut().random()
752 {
753 len / 2
754 } else {
755 len
756 };
757
758 match offset {
759 None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
760 Some(offset) => {
761 let Ok(offset) = u64::try_from(offset) else {
762 return finish.call(this, Err(LibcError("EINVAL")));
763 };
764 fd.as_unix(this).pwrite(
765 this.machine.communicate(),
766 ptr,
767 len,
768 offset,
769 this,
770 finish,
771 )?
772 }
773 };
774 interp_ok(())
775 }
776}