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::files::{DynFileDescriptionRef, FileDescription};
12use crate::shims::sig::check_min_vararg_count;
13use crate::shims::unix::linux_like::epoll::EpollReadiness;
14use crate::shims::unix::*;
15use crate::*;
16
17#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18pub enum FlockOp {
19 SharedLock { nonblocking: bool },
20 ExclusiveLock { nonblocking: bool },
21 Unlock,
22}
23
24pub trait UnixFileDescription: FileDescription {
26 fn pread<'tcx>(
30 &self,
31 _communicate_allowed: bool,
32 _offset: u64,
33 _ptr: Pointer,
34 _len: usize,
35 _ecx: &mut MiriInterpCx<'tcx>,
36 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
37 ) -> InterpResult<'tcx> {
38 throw_unsup_format!("cannot pread from {}", self.name());
39 }
40
41 fn pwrite<'tcx>(
46 &self,
47 _communicate_allowed: bool,
48 _ptr: Pointer,
49 _len: usize,
50 _offset: u64,
51 _ecx: &mut MiriInterpCx<'tcx>,
52 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
53 ) -> InterpResult<'tcx> {
54 throw_unsup_format!("cannot pwrite to {}", self.name());
55 }
56
57 fn flock<'tcx>(
58 &self,
59 _communicate_allowed: bool,
60 _op: FlockOp,
61 ) -> InterpResult<'tcx, io::Result<()>> {
62 throw_unsup_format!("cannot flock {}", self.name());
63 }
64
65 fn ioctl<'tcx>(
71 &self,
72 _op: Scalar,
73 _arg: Option<&OpTy<'tcx>>,
74 _ecx: &mut MiriInterpCx<'tcx>,
75 ) -> InterpResult<'tcx, i32> {
76 throw_unsup_format!("cannot use ioctl on {}", self.name());
77 }
78
79 fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> {
81 throw_unsup_format!("{}: epoll does not support this file description", self.name());
82 }
83}
84
85impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
86pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
87 fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
88 let this = self.eval_context_mut();
89
90 let Some(fd) = this.machine.fds.get(old_fd_num) else {
91 return this.set_last_error_and_return_i32(LibcError("EBADF"));
92 };
93 interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
94 }
95
96 fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scalar> {
97 let this = self.eval_context_mut();
98
99 let Some(fd) = this.machine.fds.get(old_fd_num) else {
100 return this.set_last_error_and_return_i32(LibcError("EBADF"));
101 };
102 if new_fd_num != old_fd_num {
103 if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
106 old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
108 }
109 }
110 interp_ok(Scalar::from_i32(new_fd_num))
111 }
112
113 fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
114 let this = self.eval_context_mut();
115 let Some(fd) = this.machine.fds.get(fd_num) else {
116 return this.set_last_error_and_return_i32(LibcError("EBADF"));
117 };
118
119 let lock_sh = this.eval_libc_i32("LOCK_SH");
121 let lock_ex = this.eval_libc_i32("LOCK_EX");
122 let lock_nb = this.eval_libc_i32("LOCK_NB");
123 let lock_un = this.eval_libc_i32("LOCK_UN");
124
125 use FlockOp::*;
126 let parsed_op = if op == lock_sh {
127 SharedLock { nonblocking: false }
128 } else if op == lock_sh | lock_nb {
129 SharedLock { nonblocking: true }
130 } else if op == lock_ex {
131 ExclusiveLock { nonblocking: false }
132 } else if op == lock_ex | lock_nb {
133 ExclusiveLock { nonblocking: true }
134 } else if op == lock_un {
135 Unlock
136 } else {
137 throw_unsup_format!("unsupported flags {:#x}", op);
138 };
139
140 let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
141 let result = result.map(|()| 0i32);
143 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
144 }
145
146 fn ioctl(
147 &mut self,
148 fd: &OpTy<'tcx>,
149 op: &OpTy<'tcx>,
150 varargs: &[OpTy<'tcx>],
151 ) -> InterpResult<'tcx, Scalar> {
152 let this = self.eval_context_mut();
153
154 let fd = this.read_scalar(fd)?.to_i32()?;
155 let op = this.read_scalar(op)?;
156 let arg = varargs.first();
160
161 let Some(fd) = this.machine.fds.get(fd) else {
162 return this.set_last_error_and_return_i32(LibcError("EBADF"));
163 };
164
165 let fioclex = this.eval_libc("FIOCLEX");
167 let fionclex = this.eval_libc("FIONCLEX");
168 if op == fioclex || op == fionclex {
169 return interp_ok(Scalar::from_i32(0));
171 }
172
173 let return_value = fd.as_unix(this).ioctl(op, arg, this)?;
176 interp_ok(Scalar::from_i32(return_value))
177 }
178
179 fn fcntl(
180 &mut self,
181 fd_num: &OpTy<'tcx>,
182 cmd: &OpTy<'tcx>,
183 varargs: &[OpTy<'tcx>],
184 ) -> InterpResult<'tcx, Scalar> {
185 let this = self.eval_context_mut();
186
187 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
188 let cmd = this.read_scalar(cmd)?.to_i32()?;
189
190 let f_getfd = this.eval_libc_i32("F_GETFD");
191 let f_dupfd = this.eval_libc_i32("F_DUPFD");
192 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
193 let f_getfl = this.eval_libc_i32("F_GETFL");
194 let f_setfl = this.eval_libc_i32("F_SETFL");
195
196 match cmd {
198 cmd if cmd == f_getfd => {
199 if !this.machine.fds.is_fd_num(fd_num) {
204 this.set_last_error_and_return_i32(LibcError("EBADF"))
205 } else {
206 interp_ok(this.eval_libc("FD_CLOEXEC"))
207 }
208 }
209 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
210 let cmd_name = if cmd == f_dupfd {
215 "fcntl(fd, F_DUPFD, ...)"
216 } else {
217 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
218 };
219
220 let [start] = check_min_vararg_count(cmd_name, varargs)?;
221 let start = this.read_scalar(start)?.to_i32()?;
222
223 if let Some(fd) = this.machine.fds.get(fd_num) {
224 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
225 } else {
226 this.set_last_error_and_return_i32(LibcError("EBADF"))
227 }
228 }
229 cmd if cmd == f_getfl => {
230 let Some(fd) = this.machine.fds.get(fd_num) else {
232 return this.set_last_error_and_return_i32(LibcError("EBADF"));
233 };
234
235 fd.get_flags(this)
236 }
237 cmd if cmd == f_setfl => {
238 let Some(fd) = this.machine.fds.get(fd_num) else {
240 return this.set_last_error_and_return_i32(LibcError("EBADF"));
241 };
242
243 let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
244 let flag = this.read_scalar(flag)?.to_i32()?;
245
246 let ignored_flags = this.eval_libc_i32("O_RDONLY")
251 | this.eval_libc_i32("O_WRONLY")
252 | this.eval_libc_i32("O_RDWR")
253 | this.eval_libc_i32("O_CREAT")
254 | this.eval_libc_i32("O_EXCL")
255 | this.eval_libc_i32("O_NOCTTY")
256 | this.eval_libc_i32("O_TRUNC");
257
258 fd.set_flags(flag & !ignored_flags, this)
259 }
260 cmd if this.tcx.sess.target.os == Os::MacOs
261 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
262 {
263 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
265 this.reject_in_isolation("`fcntl`", reject_with)?;
266 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
267 }
268
269 this.ffullsync_fd(fd_num)
270 }
271 cmd => {
272 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
273 }
274 }
275 }
276
277 fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
278 let this = self.eval_context_mut();
279
280 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
281
282 let Some(fd) = this.machine.fds.remove(fd_num) else {
283 return this.set_last_error_and_return_i32(LibcError("EBADF"));
284 };
285 let result = fd.close_ref(this.machine.communicate(), this)?;
286 let result = result.map(|()| 0i32);
288 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
289 }
290
291 fn read(
297 &mut self,
298 fd_num: i32,
299 buf: Pointer,
300 count: u64,
301 offset: Option<i128>,
302 dest: &MPlaceTy<'tcx>,
303 ) -> InterpResult<'tcx> {
304 let this = self.eval_context_mut();
305
306 trace!("Reading from FD {}, size {}", fd_num, count);
309
310 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
312
313 let count = count
316 .min(u64::try_from(this.target_isize_max()).unwrap())
317 .min(u64::try_from(isize::MAX).unwrap());
318 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
322 trace!("read: FD not found");
323 return this.set_last_error_and_return(LibcError("EBADF"), dest);
324 };
325
326 trace!("read: FD mapped to {fd:?}");
327 let dest = dest.clone();
332 this.read_from_fd(
333 fd,
334 buf,
335 count,
336 offset,
337 callback!(
338 @capture<'tcx> {
339 count: usize,
340 dest: MPlaceTy<'tcx>,
341 }
342 |this, result: Result<usize, IoError>| {
343 match result {
344 Ok(read_size) => {
345 assert!(read_size <= count);
346 this.write_int(u64::try_from(read_size).unwrap(), &dest)
348 }
349 Err(e) => this.set_last_error_and_return(e, &dest)
350 }}
351 ),
352 )
353 }
354
355 fn write(
356 &mut self,
357 fd_num: i32,
358 buf: Pointer,
359 count: u64,
360 offset: Option<i128>,
361 dest: &MPlaceTy<'tcx>,
362 ) -> InterpResult<'tcx> {
363 let this = self.eval_context_mut();
364
365 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
369
370 let count = count
373 .min(u64::try_from(this.target_isize_max()).unwrap())
374 .min(u64::try_from(isize::MAX).unwrap());
375 let count = usize::try_from(count).unwrap(); let Some(fd) = this.machine.fds.get(fd_num) else {
379 return this.set_last_error_and_return(LibcError("EBADF"), dest);
380 };
381
382 let dest = dest.clone();
383 this.write_to_fd(
384 fd,
385 buf,
386 count,
387 offset,
388 callback!(
389 @capture<'tcx> {
390 count: usize,
391 dest: MPlaceTy<'tcx>,
392 }
393 |this, result: Result<usize, IoError>| {
394 match result {
395 Ok(write_size) => {
396 assert!(write_size <= count);
397 this.write_int(u64::try_from(write_size).unwrap(), &dest)
399 }
400 Err(e) => this.set_last_error_and_return(e, &dest)
401
402 }}
403 ),
404 )
405 }
406
407 fn readv(
412 &mut self,
413 fd: &OpTy<'tcx>,
414 iov: &OpTy<'tcx>,
415 iovcnt: &OpTy<'tcx>,
416 offset: Option<&OpTy<'tcx>>,
417 dest: &MPlaceTy<'tcx>,
418 ) -> InterpResult<'tcx> {
419 let this = self.eval_context_mut();
420
421 let fd = this.read_scalar(fd)?.to_i32()?;
422 let iov_ptr = this.read_pointer(iov)?;
423 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
424 let offset = if let Some(offset) = offset {
426 if matches!(this.tcx.sess.target.os, Os::Solaris) {
427 throw_unsup_format!(
428 "preadv: vectored reads with offsets aren't supported on Solaris"
429 )
430 }
431 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
432 } else {
433 None
434 };
435
436 let Some(fd) = this.machine.fds.get(fd) else {
438 return this.set_last_error_and_return(LibcError("EBADF"), dest);
439 };
440
441 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
442 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
443
444 let mut buffers = Vec::new();
446
447 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
448 while let Some((_idx, iovec)) = array.next(this)? {
449 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
450 let iov_len: u64 = this
451 .read_scalar(&iov_len_field)?
452 .to_int(iov_len_field.layout.size)?
453 .try_into()
454 .unwrap();
455
456 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
457 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
458
459 buffers.push((iov_base_ptr, iov_len));
460 }
461
462 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
463
464 let tmp_ptr: Pointer = this
466 .allocate_ptr(
467 Size::from_bytes(total_bytes),
468 Align::ONE,
469 MemoryKind::Stack,
470 AllocInit::Uninit,
471 )?
472 .into();
473
474 let dest = dest.clone();
475 this.read_from_fd(
476 fd,
477 tmp_ptr,
478 usize::try_from(total_bytes).unwrap(),
479 offset,
480 callback!(
481 @capture<'tcx> {
482 tmp_ptr: Pointer,
483 buffers: Vec<(Pointer, u64)>,
484 dest: MPlaceTy<'tcx>
485 } |this, result: Result<usize, IoError>| {
486 let bytes_read = match result {
487 Ok(size) => {
488 this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
489 u64::try_from(size).unwrap()
490 },
491 Err(e) => return this.set_last_error_and_return(e, &dest)
492 };
493 let mut remaining_bytes = bytes_read;
494
495 for (buffer_ptr, buffer_len) in buffers {
499 let tmp_ptr_with_offset =
501 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
502
503 let copy_amount = buffer_len.min(remaining_bytes);
506 this.mem_copy(
507 tmp_ptr_with_offset,
508 buffer_ptr,
509 Size::from_bytes(copy_amount),
510 true,
514 )?;
515
516 remaining_bytes = remaining_bytes.strict_sub(copy_amount);
517 if remaining_bytes == 0 {
518 break;
520 }
521 }
522
523 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
524 }),
525 )
526 }
527
528 fn writev(
532 &mut self,
533 fd: &OpTy<'tcx>,
534 iov: &OpTy<'tcx>,
535 iovcnt: &OpTy<'tcx>,
536 offset: Option<&OpTy<'tcx>>,
537 dest: &MPlaceTy<'tcx>,
538 ) -> InterpResult<'tcx> {
539 let this = self.eval_context_mut();
540
541 let fd = this.read_scalar(fd)?.to_i32()?;
542 let iov_ptr = this.read_pointer(iov)?;
543 let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
544 let offset = if let Some(offset) = offset {
546 if matches!(this.tcx.sess.target.os, Os::Solaris) {
547 throw_unsup_format!(
548 "pwritev: vectored writes with offsets aren't supported on Solaris"
549 )
550 }
551 Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
552 } else {
553 None
554 };
555
556 let Some(fd) = this.machine.fds.get(fd) else {
558 return this.set_last_error_and_return(LibcError("EBADF"), dest);
559 };
560
561 let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
562 let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
563
564 let mut buffers = Vec::new();
566
567 let mut array = this.project_array_fields(&iov_ptr_mplace)?;
568 while let Some((_idx, iovec)) = array.next(this)? {
569 let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
570 let iov_len: u64 = this
571 .read_scalar(&iov_len_field)?
572 .to_int(iov_len_field.layout.size)?
573 .try_into()
574 .unwrap();
575
576 let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
577 let iov_base_ptr = this.read_pointer(&iov_base_field)?;
578
579 buffers.push((iov_base_ptr, iov_len));
580 }
581
582 let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
583
584 let tmp_ptr: Pointer = this
586 .allocate_ptr(
587 Size::from_bytes(total_bytes),
588 Align::ONE,
589 MemoryKind::Stack,
590 AllocInit::Uninit,
591 )?
592 .into();
593
594 let mut bytes_copied: u64 = 0;
597 for (buffer_ptr, buffer_len) in buffers {
598 let tmp_ptr_with_offset =
600 this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
601
602 this.mem_copy(
603 buffer_ptr,
604 tmp_ptr_with_offset,
605 Size::from_bytes(buffer_len),
606 true,
610 )?;
611
612 bytes_copied = bytes_copied.strict_add(buffer_len);
613 }
614
615 let dest = dest.clone();
616 this.write_to_fd(
618 fd,
619 tmp_ptr,
620 usize::try_from(total_bytes).unwrap(),
621 offset,
622 callback!(
623 @capture<'tcx> {
624 tmp_ptr: Pointer,
625 dest: MPlaceTy<'tcx>,
626 }
627 |this, result: Result<usize, IoError>| {
628 this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
629 match result {
630 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
631 Err(e) => this.set_last_error_and_return(e, &dest)
632 }
633 }),
634 )
635 }
636}
637
638impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
639trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
640 fn read_from_fd(
647 &mut self,
648 fd: DynFileDescriptionRef,
649 ptr: Pointer,
650 len: usize,
651 offset: Option<i128>,
652 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
653 ) -> InterpResult<'tcx> {
654 let this = self.eval_context_mut();
655
656 if len == 0 {
661 return finish.call(this, Ok(0));
662 }
663
664 let len = if this.machine.short_fd_operations
667 && fd.short_fd_operations()
668 && len >= 2
669 && this.machine.rng.get_mut().random()
670 {
671 len / 2 } else {
673 len
674 };
675
676 match offset {
677 None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
678 Some(offset) => {
679 let Ok(offset) = u64::try_from(offset) else {
680 return finish.call(this, Err(LibcError("EINVAL")));
681 };
682 fd.as_unix(this).pread(
683 this.machine.communicate(),
684 offset,
685 ptr,
686 len,
687 this,
688 finish,
689 )?
690 }
691 };
692 interp_ok(())
693 }
694
695 fn write_to_fd(
702 &mut self,
703 fd: DynFileDescriptionRef,
704 ptr: Pointer,
705 len: usize,
706 offset: Option<i128>,
707 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
708 ) -> InterpResult<'tcx> {
709 let this = self.eval_context_mut();
710
711 if len == 0 {
718 return finish.call(this, Ok(0));
720 }
721
722 let len = if this.machine.short_fd_operations
726 && fd.short_fd_operations()
727 && len >= 2
728 && this.machine.rng.get_mut().random()
729 {
730 len / 2
731 } else {
732 len
733 };
734
735 match offset {
736 None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
737 Some(offset) => {
738 let Ok(offset) = u64::try_from(offset) else {
739 return finish.call(this, Err(LibcError("EINVAL")));
740 };
741 fd.as_unix(this).pwrite(
742 this.machine.communicate(),
743 ptr,
744 len,
745 offset,
746 this,
747 finish,
748 )?
749 }
750 };
751 interp_ok(())
752 }
753}