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 if count == 0 {
272 this.write_null(dest)?;
273 return interp_ok(());
274 }
275 let count = if this.machine.short_fd_operations
278 && fd.short_fd_operations()
279 && count >= 2
280 && this.machine.rng.get_mut().random()
281 {
282 count / 2 } else {
284 count
285 };
286
287 trace!("read: FD mapped to {fd:?}");
288 let finish = {
293 let dest = dest.clone();
294 callback!(
295 @capture<'tcx> {
296 count: usize,
297 dest: MPlaceTy<'tcx>,
298 }
299 |this, result: Result<usize, IoError>| {
300 match result {
301 Ok(read_size) => {
302 assert!(read_size <= count);
303 this.write_int(u64::try_from(read_size).unwrap(), &dest)
305 }
306 Err(e) => {
307 this.set_last_error_and_return(e, &dest)
308 }
309 }}
310 )
311 };
312 match offset {
313 None => fd.read(communicate, buf, count, this, finish)?,
314 Some(offset) => {
315 let Ok(offset) = u64::try_from(offset) else {
316 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
317 };
318 fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
319 }
320 };
321 interp_ok(())
322 }
323
324 fn write(
325 &mut self,
326 fd_num: i32,
327 buf: Pointer,
328 count: u64,
329 offset: Option<i128>,
330 dest: &MPlaceTy<'tcx>,
331 ) -> InterpResult<'tcx> {
332 let this = self.eval_context_mut();
333
334 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
338
339 let count = count
342 .min(u64::try_from(this.target_isize_max()).unwrap())
343 .min(u64::try_from(isize::MAX).unwrap());
344 let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
346
347 let Some(fd) = this.machine.fds.get(fd_num) else {
349 return this.set_last_error_and_return(LibcError("EBADF"), dest);
350 };
351
352 if count == 0 {
359 this.write_null(dest)?;
361 return interp_ok(());
362 }
363 let count = if this.machine.short_fd_operations
367 && fd.short_fd_operations()
368 && count >= 2
369 && this.machine.rng.get_mut().random()
370 {
371 count / 2
372 } else {
373 count
374 };
375
376 let finish = {
377 let dest = dest.clone();
378 callback!(
379 @capture<'tcx> {
380 count: usize,
381 dest: MPlaceTy<'tcx>,
382 }
383 |this, result: Result<usize, IoError>| {
384 match result {
385 Ok(write_size) => {
386 assert!(write_size <= count);
387 this.write_int(u64::try_from(write_size).unwrap(), &dest)
389 }
390 Err(e) => {
391 this.set_last_error_and_return(e, &dest)
392 }
393 }}
394 )
395 };
396 match offset {
397 None => fd.write(communicate, buf, count, this, finish)?,
398 Some(offset) => {
399 let Ok(offset) = u64::try_from(offset) else {
400 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
401 };
402 fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
403 }
404 };
405 interp_ok(())
406 }
407}