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 get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
68 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
69 }
70
71 fn set_flags<'tcx>(
73 &self,
74 _flag: i32,
75 _ecx: &mut MiriInterpCx<'tcx>,
76 ) -> InterpResult<'tcx, Scalar> {
77 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
78 }
79
80 fn ioctl<'tcx>(
86 &self,
87 _op: Scalar,
88 _args: Varargs<'tcx, '_>,
89 _ecx: &mut MiriInterpCx<'tcx>,
90 ) -> InterpResult<'tcx, i32> {
91 throw_unsup_format!("cannot use ioctl on {}", self.name());
92 }
93
94 fn as_socket(
96 self: FileDescriptionRef<Self>,
97 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
98 None
99 }
100}
101
102impl UnixFileDescription for shims::files::Stdin {}
103impl UnixFileDescription for shims::files::Stdout {}
104impl UnixFileDescription for shims::files::Stderr {}
105impl UnixFileDescription for shims::files::NullOutput {}
106
107impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
108pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
109 fn close(&mut self, fd_num: FdNum) -> InterpResult<'tcx, Scalar> {
110 let this = self.eval_context_mut();
111
112 let Some(fd) = this.machine.fds.remove(fd_num) else {
113 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
114 };
115 let fd = fd.as_unix(); if this.tcx.sess.target.os == Os::Illumos {
117 if let Some(watched) = fd.readiness_watched() {
129 watched.remove_file_num_interests(fd.id(), fd_num);
130 }
131 }
132 drop(fd);
133 interp_ok(Scalar::from_i32(0))
136 }
137
138 fn dup(&mut self, old_fd_num: FdNum) -> InterpResult<'tcx, Scalar> {
139 let this = self.eval_context_mut();
140
141 let Some(fd) = this.machine.fds.get(old_fd_num) else {
142 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
143 };
144 interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
145 }
146
147 fn dup2(&mut self, old_fd_num: FdNum, new_fd_num: FdNum) -> InterpResult<'tcx, Scalar> {
148 let this = self.eval_context_mut();
149
150 let Some(fd) = this.machine.fds.get(old_fd_num) else {
151 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
152 };
153 if new_fd_num != old_fd_num {
154 if this.machine.fds.get(new_fd_num).is_some() {
155 let ret = this.close(new_fd_num)?;
157 assert!(ret.to_i32().unwrap() == 0);
158 }
159 let actual_fd_num = this.machine.fds.insert_with_min_num(fd, new_fd_num);
161 assert_eq!(actual_fd_num, new_fd_num);
162 }
163 interp_ok(Scalar::from_i32(new_fd_num))
164 }
165
166 fn flock(&mut self, fd_num: FdNum, op: i32) -> InterpResult<'tcx, Scalar> {
167 let this = self.eval_context_mut();
168 let Some(fd) = this.machine.fds.get(fd_num) else {
169 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
170 };
171
172 let lock_sh = this.eval_libc_i32("LOCK_SH");
174 let lock_ex = this.eval_libc_i32("LOCK_EX");
175 let lock_nb = this.eval_libc_i32("LOCK_NB");
176 let lock_un = this.eval_libc_i32("LOCK_UN");
177
178 use FlockOp::*;
179 let parsed_op = if op == lock_sh {
180 SharedLock { nonblocking: false }
181 } else if op == lock_sh | lock_nb {
182 SharedLock { nonblocking: true }
183 } else if op == lock_ex {
184 ExclusiveLock { nonblocking: false }
185 } else if op == lock_ex | lock_nb {
186 ExclusiveLock { nonblocking: true }
187 } else if op == lock_un {
188 Unlock
189 } else {
190 throw_unsup_format!("unsupported flags {:#x}", op);
191 };
192
193 let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?;
194 let result = result.map(|()| 0i32);
196 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
197 }
198
199 fn ioctl(
200 &mut self,
201 fd: &OpTy<'tcx>,
202 op: &OpTy<'tcx>,
203 varargs: Varargs<'tcx, '_>,
204 ) -> InterpResult<'tcx, Scalar> {
205 let this = self.eval_context_mut();
206
207 let fd = this.read_scalar(fd)?.to_i32()?;
208 let op = this.read_scalar(op)?;
209
210 let Some(fd) = this.machine.fds.get(fd) else {
211 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
212 };
213
214 let fioclex = this.eval_libc("FIOCLEX");
216 let fionclex = this.eval_libc("FIONCLEX");
217 if op == fioclex || op == fionclex {
218 return interp_ok(Scalar::from_i32(0));
220 }
221
222 let return_value = fd.as_unix().ioctl(op, varargs, this)?;
225 interp_ok(Scalar::from_i32(return_value))
226 }
227
228 fn fcntl(
229 &mut self,
230 fd_num: &OpTy<'tcx>,
231 cmd: &OpTy<'tcx>,
232 varargs: Varargs<'tcx, '_>,
233 ) -> InterpResult<'tcx, Scalar> {
234 let this = self.eval_context_mut();
235
236 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
237 let cmd = this.read_scalar(cmd)?.to_i32()?;
238
239 let f_getfd = this.eval_libc_i32("F_GETFD");
240 let f_dupfd = this.eval_libc_i32("F_DUPFD");
241 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
242 let f_getfl = this.eval_libc_i32("F_GETFL");
243 let f_setfl = this.eval_libc_i32("F_SETFL");
244
245 match cmd {
247 cmd if cmd == f_getfd => {
248 if !this.machine.fds.is_fd_num(fd_num) {
253 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
254 } else {
255 interp_ok(this.eval_libc("FD_CLOEXEC"))
256 }
257 }
258 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
259 let cmd_name = if cmd == f_dupfd {
264 "fcntl(fd, F_DUPFD, ...)"
265 } else {
266 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
267 };
268
269 let ([start], _) = this.check_varargs(shim_varargs![i32], varargs, cmd_name)?;
270 let start = this.read_scalar(start)?.to_i32()?;
271
272 if let Some(fd) = this.machine.fds.get(fd_num) {
273 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
274 } else {
275 this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
276 }
277 }
278 cmd if cmd == f_getfl => {
279 let Some(fd) = this.machine.fds.get(fd_num) else {
281 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
282 };
283
284 fd.as_unix().get_flags(this)
285 }
286 cmd if cmd == f_setfl => {
287 let Some(fd) = this.machine.fds.get(fd_num) else {
289 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
290 };
291
292 let ([flag], _) =
293 this.check_varargs(shim_varargs![i32], varargs, "fcntl(fd, F_SETFL, ...)")?;
294 let flag = this.read_scalar(flag)?.to_i32()?;
295
296 let ignored_flags = this.eval_libc_i32("O_RDONLY")
301 | this.eval_libc_i32("O_WRONLY")
302 | this.eval_libc_i32("O_RDWR")
303 | this.eval_libc_i32("O_CREAT")
304 | this.eval_libc_i32("O_EXCL")
305 | this.eval_libc_i32("O_NOCTTY")
306 | this.eval_libc_i32("O_TRUNC");
307
308 fd.as_unix().set_flags(flag & !ignored_flags, this)
309 }
310 cmd if this.tcx.sess.target.os == Os::MacOs
311 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
312 {
313 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
315 this.reject_in_isolation("`fcntl`", reject_with)?;
316 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
317 }
318
319 this.ffullsync_fd(fd_num)
320 }
321 cmd if this.tcx.sess.target.os == Os::MacOs
322 && this.frame_in_std()
323 && cmd == this.eval_libc_i32("F_GETPATH") =>
324 {
325 this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"))
327 }
328 cmd if this.tcx.sess.target.os == Os::FreeBsd
329 && this.frame_in_std()
330 && cmd == this.eval_libc_i32("F_KINFO") =>
331 {
332 this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"))
334 }
335 cmd => {
336 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
337 }
338 }
339 }
340
341 fn read(
347 &mut self,
348 fd_num: i32,
349 buf: Pointer,
350 count: u64,
351 offset: Option<i128>,
352 dest: &MPlaceTy<'tcx>,
353 ) -> InterpResult<'tcx> {
354 let this = self.eval_context_mut();
355
356 trace!("Reading from FD {}, size {}", fd_num, count);
359
360 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
362
363 let count = count
366 .min(u64::try_from(this.target_isize_max()).unwrap())
367 .min(u64::try_from(isize::MAX).unwrap());
368 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
372 trace!("read: FD not found");
373 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
374 };
375
376 trace!("read: FD mapped to {fd:?}");
377 let dest = dest.clone();
382 this.read_from_fd(
383 fd,
384 buf,
385 count,
386 offset,
387 callback!(
388 @capture<'tcx> {
389 count: usize,
390 dest: MPlaceTy<'tcx>,
391 }
392 |this, result: Result<usize, IoError>| {
393 match result {
394 Ok(read_size) => {
395 assert!(read_size <= count);
396 this.write_int(u64::try_from(read_size).unwrap(), &dest)
398 }
399 Err(e) => this.set_errno_and_return_neg1(e, &dest)
400 }}
401 ),
402 )
403 }
404
405 fn write(
406 &mut self,
407 fd_num: i32,
408 buf: Pointer,
409 count: u64,
410 offset: Option<i128>,
411 dest: &MPlaceTy<'tcx>,
412 ) -> InterpResult<'tcx> {
413 let this = self.eval_context_mut();
414
415 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
419
420 let count = count
423 .min(u64::try_from(this.target_isize_max()).unwrap())
424 .min(u64::try_from(isize::MAX).unwrap());
425 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
429 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
430 };
431
432 let dest = dest.clone();
433 this.write_to_fd(
434 fd,
435 buf,
436 count,
437 offset,
438 callback!(
439 @capture<'tcx> {
440 count: usize,
441 dest: MPlaceTy<'tcx>,
442 }
443 |this, result: Result<usize, IoError>| {
444 match result {
445 Ok(write_size) => {
446 assert!(write_size <= count);
447 this.write_int(u64::try_from(write_size).unwrap(), &dest)
449 }
450 Err(e) => this.set_errno_and_return_neg1(e, &dest)
451
452 }}
453 ),
454 )
455 }
456
457 fn readv(
462 &mut self,
463 fd: &OpTy<'tcx>,
464 iov: &OpTy<'tcx>,
465 iovcnt: &OpTy<'tcx>,
466 offset: Option<&OpTy<'tcx>>,
467 dest: &MPlaceTy<'tcx>,
468 ) -> InterpResult<'tcx> {
469 let this = self.eval_context_mut();
470
471 let fd = this.read_scalar(fd)?.to_i32()?;
472 let iov_ptr = this.read_pointer(iov)?;
473 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
474 let offset = if let Some(offset) = offset {
476 if matches!(this.tcx.sess.target.os, Os::Solaris) {
477 throw_unsup_format!(
478 "preadv: vectored reads with offsets aren't supported on Solaris"
479 )
480 }
481 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
482 } else {
483 None
484 };
485
486 let Some(fd) = this.machine.fds.get(fd) else {
488 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
489 };
490
491 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
492 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
493
494 let mut buffers = Vec::new();
496
497 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
498 while let Some((_idx, iovec)) = array.next(this)? {
499 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
500 let iov_len: u64 = this
501 .read_scalar(&iov_len_field)?
502 .to_int(iov_len_field.layout.size)?
503 .try_into()
504 .unwrap();
505
506 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
507 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
508
509 buffers.push((iov_base_ptr, iov_len));
510 }
511
512 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
513
514 let tmp_ptr: Pointer = this
516 .allocate_ptr(
517 Size::from_bytes(total_bytes),
518 Align::ONE,
519 MemoryKind::Stack,
520 AllocInit::Uninit,
521 )?
522 .into();
523
524 let dest = dest.clone();
525 this.read_from_fd(
526 fd,
527 tmp_ptr,
528 usize::try_from(total_bytes).unwrap(),
529 offset,
530 callback!(
531 @capture<'tcx> {
532 tmp_ptr: Pointer,
533 buffers: Vec<(Pointer, u64)>,
534 dest: MPlaceTy<'tcx>
535 } |this, result: Result<usize, IoError>| {
536 let bytes_read = match result {
537 Ok(size) => {
538 this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
539 u64::try_from(size).unwrap()
540 },
541 Err(e) => {
542 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
543 return this.set_errno_and_return_neg1(e, &dest)
544 }
545 };
546 let mut remaining_bytes = bytes_read;
547
548 for (buffer_ptr, buffer_len) in buffers {
552 let tmp_ptr_with_offset =
554 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
555
556 let copy_amount = buffer_len.min(remaining_bytes);
559 this.mem_copy(
560 tmp_ptr_with_offset,
561 buffer_ptr,
562 Size::from_bytes(copy_amount),
563 true,
567 )?;
568
569 remaining_bytes = remaining_bytes.strict_sub(copy_amount);
570 if remaining_bytes == 0 {
571 break;
573 }
574 }
575
576 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
577 }),
578 )
579 }
580
581 fn writev(
585 &mut self,
586 fd: &OpTy<'tcx>,
587 iov: &OpTy<'tcx>,
588 iovcnt: &OpTy<'tcx>,
589 offset: Option<&OpTy<'tcx>>,
590 dest: &MPlaceTy<'tcx>,
591 ) -> InterpResult<'tcx> {
592 let this = self.eval_context_mut();
593
594 let fd = this.read_scalar(fd)?.to_i32()?;
595 let iov_ptr = this.read_pointer(iov)?;
596 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
597 let offset = if let Some(offset) = offset {
599 if matches!(this.tcx.sess.target.os, Os::Solaris) {
600 throw_unsup_format!(
601 "pwritev: vectored writes with offsets aren't supported on Solaris"
602 )
603 }
604 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
605 } else {
606 None
607 };
608
609 let Some(fd) = this.machine.fds.get(fd) else {
611 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
612 };
613
614 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
615 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
616
617 let mut buffers = Vec::new();
619
620 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
621 while let Some((_idx, iovec)) = array.next(this)? {
622 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
623 let iov_len: u64 = this
624 .read_scalar(&iov_len_field)?
625 .to_int(iov_len_field.layout.size)?
626 .try_into()
627 .unwrap();
628
629 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
630 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
631
632 buffers.push((iov_base_ptr, iov_len));
633 }
634
635 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
636
637 let tmp_ptr: Pointer = this
639 .allocate_ptr(
640 Size::from_bytes(total_bytes),
641 Align::ONE,
642 MemoryKind::Stack,
643 AllocInit::Uninit,
644 )?
645 .into();
646
647 let mut bytes_copied: u64 = 0;
650 for (buffer_ptr, buffer_len) in buffers {
651 let tmp_ptr_with_offset =
653 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
654
655 this.mem_copy(
656 buffer_ptr,
657 tmp_ptr_with_offset,
658 Size::from_bytes(buffer_len),
659 true,
663 )?;
664
665 bytes_copied = bytes_copied.strict_add(buffer_len);
666 }
667
668 let dest = dest.clone();
669 this.write_to_fd(
671 fd,
672 tmp_ptr,
673 usize::try_from(total_bytes).unwrap(),
674 offset,
675 callback!(
676 @capture<'tcx> {
677 tmp_ptr: Pointer,
678 dest: MPlaceTy<'tcx>,
679 }
680 |this, result: Result<usize, IoError>| {
681 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
682 match result {
683 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
684 Err(e) => this.set_errno_and_return_neg1(e, &dest)
685 }
686 }),
687 )
688 }
689}
690
691impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
692trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
693 fn read_from_fd(
700 &mut self,
701 fd: DynFileDescriptionRef,
702 ptr: Pointer,
703 len: usize,
704 offset: Option<i128>,
705 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
706 ) -> InterpResult<'tcx> {
707 let this = self.eval_context_mut();
708
709 if len == 0 {
714 return finish.call(this, Ok(0));
715 }
716
717 let len = if this.machine.short_fd_operations
720 && fd.short_fd_operations()
721 && len >= 2
722 && this.machine.rng.get_mut().random()
723 {
724 len / 2 } else {
726 len
727 };
728
729 match offset {
730 None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
731 Some(offset) => {
732 let Ok(offset) = u64::try_from(offset) else {
733 return finish.call(this, Err(LibcError("EINVAL")));
734 };
735 fd.as_unix().pread(this.machine.communicate(), offset, ptr, len, this, finish)?
736 }
737 };
738 interp_ok(())
739 }
740
741 fn write_to_fd(
748 &mut self,
749 fd: DynFileDescriptionRef,
750 ptr: Pointer,
751 len: usize,
752 offset: Option<i128>,
753 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
754 ) -> InterpResult<'tcx> {
755 let this = self.eval_context_mut();
756
757 if len == 0 {
764 return finish.call(this, Ok(0));
766 }
767
768 let len = if this.machine.short_fd_operations
772 && fd.short_fd_operations()
773 && len >= 2
774 && this.machine.rng.get_mut().random()
775 {
776 len / 2
777 } else {
778 len
779 };
780
781 match offset {
782 None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
783 Some(offset) => {
784 let Ok(offset) = u64::try_from(offset) else {
785 return finish.call(this, Err(LibcError("EINVAL")));
786 };
787 fd.as_unix().pwrite(this.machine.communicate(), ptr, len, offset, this, finish)?
788 }
789 };
790 interp_ok(())
791 }
792}