1use std::io;
5use std::io::ErrorKind;
6
7use rand::Rng;
8use rustc_abi::Size;
9
10use crate::shims::files::FileDescription;
11use crate::shims::sig::check_min_vararg_count;
12use crate::shims::unix::linux_like::epoll::EpollReadyEvents;
13use crate::shims::unix::*;
14use crate::*;
15
16#[derive(Debug, Clone, Copy, Eq, PartialEq)]
17pub(crate) enum FlockOp {
18 SharedLock { nonblocking: bool },
19 ExclusiveLock { nonblocking: bool },
20 Unlock,
21}
22
23pub trait UnixFileDescription: FileDescription {
25 fn pread<'tcx>(
29 &self,
30 _communicate_allowed: bool,
31 _offset: u64,
32 _ptr: Pointer,
33 _len: usize,
34 _ecx: &mut MiriInterpCx<'tcx>,
35 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
36 ) -> InterpResult<'tcx> {
37 throw_unsup_format!("cannot pread from {}", self.name());
38 }
39
40 fn pwrite<'tcx>(
45 &self,
46 _communicate_allowed: bool,
47 _ptr: Pointer,
48 _len: usize,
49 _offset: u64,
50 _ecx: &mut MiriInterpCx<'tcx>,
51 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
52 ) -> InterpResult<'tcx> {
53 throw_unsup_format!("cannot pwrite to {}", self.name());
54 }
55
56 fn flock<'tcx>(
57 &self,
58 _communicate_allowed: bool,
59 _op: FlockOp,
60 ) -> InterpResult<'tcx, io::Result<()>> {
61 throw_unsup_format!("cannot flock {}", self.name());
62 }
63
64 fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
66 throw_unsup_format!("{}: epoll does not support this file description", self.name());
67 }
68}
69
70impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
71pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
72 fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
73 let this = self.eval_context_mut();
74
75 let Some(fd) = this.machine.fds.get(old_fd_num) else {
76 return this.set_last_error_and_return_i32(LibcError("EBADF"));
77 };
78 interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
79 }
80
81 fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scalar> {
82 let this = self.eval_context_mut();
83
84 let Some(fd) = this.machine.fds.get(old_fd_num) else {
85 return this.set_last_error_and_return_i32(LibcError("EBADF"));
86 };
87 if new_fd_num != old_fd_num {
88 if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
91 old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
93 }
94 }
95 interp_ok(Scalar::from_i32(new_fd_num))
96 }
97
98 fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
99 let this = self.eval_context_mut();
100 let Some(fd) = this.machine.fds.get(fd_num) else {
101 return this.set_last_error_and_return_i32(LibcError("EBADF"));
102 };
103
104 let lock_sh = this.eval_libc_i32("LOCK_SH");
106 let lock_ex = this.eval_libc_i32("LOCK_EX");
107 let lock_nb = this.eval_libc_i32("LOCK_NB");
108 let lock_un = this.eval_libc_i32("LOCK_UN");
109
110 use FlockOp::*;
111 let parsed_op = if op == lock_sh {
112 SharedLock { nonblocking: false }
113 } else if op == lock_sh | lock_nb {
114 SharedLock { nonblocking: true }
115 } else if op == lock_ex {
116 ExclusiveLock { nonblocking: false }
117 } else if op == lock_ex | lock_nb {
118 ExclusiveLock { nonblocking: true }
119 } else if op == lock_un {
120 Unlock
121 } else {
122 throw_unsup_format!("unsupported flags {:#x}", op);
123 };
124
125 let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
126 let result = result.map(|()| 0i32);
128 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
129 }
130
131 fn fcntl(
132 &mut self,
133 fd_num: &OpTy<'tcx>,
134 cmd: &OpTy<'tcx>,
135 varargs: &[OpTy<'tcx>],
136 ) -> InterpResult<'tcx, Scalar> {
137 let this = self.eval_context_mut();
138
139 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
140 let cmd = this.read_scalar(cmd)?.to_i32()?;
141
142 let f_getfd = this.eval_libc_i32("F_GETFD");
143 let f_dupfd = this.eval_libc_i32("F_DUPFD");
144 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
145 let f_getfl = this.eval_libc_i32("F_GETFL");
146 let f_setfl = this.eval_libc_i32("F_SETFL");
147
148 match cmd {
150 cmd if cmd == f_getfd => {
151 if !this.machine.fds.is_fd_num(fd_num) {
156 this.set_last_error_and_return_i32(LibcError("EBADF"))
157 } else {
158 interp_ok(this.eval_libc("FD_CLOEXEC"))
159 }
160 }
161 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
162 let cmd_name = if cmd == f_dupfd {
167 "fcntl(fd, F_DUPFD, ...)"
168 } else {
169 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
170 };
171
172 let [start] = check_min_vararg_count(cmd_name, varargs)?;
173 let start = this.read_scalar(start)?.to_i32()?;
174
175 if let Some(fd) = this.machine.fds.get(fd_num) {
176 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
177 } else {
178 this.set_last_error_and_return_i32(LibcError("EBADF"))
179 }
180 }
181 cmd if cmd == f_getfl => {
182 let Some(fd) = this.machine.fds.get(fd_num) else {
184 return this.set_last_error_and_return_i32(LibcError("EBADF"));
185 };
186
187 fd.get_flags(this)
188 }
189 cmd if cmd == f_setfl => {
190 let Some(fd) = this.machine.fds.get(fd_num) else {
192 return this.set_last_error_and_return_i32(LibcError("EBADF"));
193 };
194
195 let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
196 let flag = this.read_scalar(flag)?.to_i32()?;
197
198 fd.set_flags(flag, this)
199 }
200 cmd if this.tcx.sess.target.os == "macos"
201 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
202 {
203 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
205 this.reject_in_isolation("`fcntl`", reject_with)?;
206 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
207 }
208
209 this.ffullsync_fd(fd_num)
210 }
211 cmd => {
212 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
213 }
214 }
215 }
216
217 fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
218 let this = self.eval_context_mut();
219
220 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
221
222 let Some(fd) = this.machine.fds.remove(fd_num) else {
223 return this.set_last_error_and_return_i32(LibcError("EBADF"));
224 };
225 let result = fd.close_ref(this.machine.communicate(), this)?;
226 let result = result.map(|()| 0i32);
228 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
229 }
230
231 fn read(
237 &mut self,
238 fd_num: i32,
239 buf: Pointer,
240 count: u64,
241 offset: Option<i128>,
242 dest: &MPlaceTy<'tcx>,
243 ) -> InterpResult<'tcx> {
244 let this = self.eval_context_mut();
245
246 trace!("Reading from FD {}, size {}", fd_num, count);
249
250 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
252
253 let count = count
256 .min(u64::try_from(this.target_isize_max()).unwrap())
257 .min(u64::try_from(isize::MAX).unwrap());
258 let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
260
261 let Some(fd) = this.machine.fds.get(fd_num) else {
263 trace!("read: FD not found");
264 return this.set_last_error_and_return(LibcError("EBADF"), dest);
265 };
266
267 let count =
270 if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
271 count / 2
272 } else {
273 count
274 };
275
276 trace!("read: FD mapped to {fd:?}");
277 let finish = {
282 let dest = dest.clone();
283 callback!(
284 @capture<'tcx> {
285 count: usize,
286 dest: MPlaceTy<'tcx>,
287 }
288 |this, result: Result<usize, IoError>| {
289 match result {
290 Ok(read_size) => {
291 assert!(read_size <= count);
292 this.write_int(u64::try_from(read_size).unwrap(), &dest)
294 }
295 Err(e) => {
296 this.set_last_error_and_return(e, &dest)
297 }
298 }}
299 )
300 };
301 match offset {
302 None => fd.read(communicate, buf, count, this, finish)?,
303 Some(offset) => {
304 let Ok(offset) = u64::try_from(offset) else {
305 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
306 };
307 fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
308 }
309 };
310 interp_ok(())
311 }
312
313 fn write(
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 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
327
328 let count = count
331 .min(u64::try_from(this.target_isize_max()).unwrap())
332 .min(u64::try_from(isize::MAX).unwrap());
333 let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
335
336 let Some(fd) = this.machine.fds.get(fd_num) else {
338 return this.set_last_error_and_return(LibcError("EBADF"), dest);
339 };
340
341 let count =
344 if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
345 count / 2
346 } else {
347 count
348 };
349
350 let finish = {
351 let dest = dest.clone();
352 callback!(
353 @capture<'tcx> {
354 count: usize,
355 dest: MPlaceTy<'tcx>,
356 }
357 |this, result: Result<usize, IoError>| {
358 match result {
359 Ok(write_size) => {
360 assert!(write_size <= count);
361 this.write_int(u64::try_from(write_size).unwrap(), &dest)
363 }
364 Err(e) => {
365 this.set_last_error_and_return(e, &dest)
366 }
367 }}
368 )
369 };
370 match offset {
371 None => fd.write(communicate, buf, count, this, finish)?,
372 Some(offset) => {
373 let Ok(offset) = u64::try_from(offset) else {
374 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
375 };
376 fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
377 }
378 };
379 interp_ok(())
380 }
381}