1use std::io;
5use std::io::ErrorKind;
6
7use rand::Rng;
8use rustc_abi::Size;
9use rustc_target::spec::Os;
10
11use crate::shims::files::FileDescription;
12use crate::shims::sig::check_min_vararg_count;
13use crate::shims::unix::linux_like::epoll::EpollEvents;
14use crate::shims::unix::*;
15use crate::*;
16
17#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18pub(crate) 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 epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> {
67 throw_unsup_format!("{}: epoll does not support this file description", self.name());
68 }
69}
70
71impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
72pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
73 fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
74 let this = self.eval_context_mut();
75
76 let Some(fd) = this.machine.fds.get(old_fd_num) else {
77 return this.set_last_error_and_return_i32(LibcError("EBADF"));
78 };
79 interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
80 }
81
82 fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scalar> {
83 let this = self.eval_context_mut();
84
85 let Some(fd) = this.machine.fds.get(old_fd_num) else {
86 return this.set_last_error_and_return_i32(LibcError("EBADF"));
87 };
88 if new_fd_num != old_fd_num {
89 if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
92 old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
94 }
95 }
96 interp_ok(Scalar::from_i32(new_fd_num))
97 }
98
99 fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
100 let this = self.eval_context_mut();
101 let Some(fd) = this.machine.fds.get(fd_num) else {
102 return this.set_last_error_and_return_i32(LibcError("EBADF"));
103 };
104
105 let lock_sh = this.eval_libc_i32("LOCK_SH");
107 let lock_ex = this.eval_libc_i32("LOCK_EX");
108 let lock_nb = this.eval_libc_i32("LOCK_NB");
109 let lock_un = this.eval_libc_i32("LOCK_UN");
110
111 use FlockOp::*;
112 let parsed_op = if op == lock_sh {
113 SharedLock { nonblocking: false }
114 } else if op == lock_sh | lock_nb {
115 SharedLock { nonblocking: true }
116 } else if op == lock_ex {
117 ExclusiveLock { nonblocking: false }
118 } else if op == lock_ex | lock_nb {
119 ExclusiveLock { nonblocking: true }
120 } else if op == lock_un {
121 Unlock
122 } else {
123 throw_unsup_format!("unsupported flags {:#x}", op);
124 };
125
126 let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
127 let result = result.map(|()| 0i32);
129 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
130 }
131
132 fn fcntl(
133 &mut self,
134 fd_num: &OpTy<'tcx>,
135 cmd: &OpTy<'tcx>,
136 varargs: &[OpTy<'tcx>],
137 ) -> InterpResult<'tcx, Scalar> {
138 let this = self.eval_context_mut();
139
140 let fd_num = this.read_scalar(fd_num)?.to_i32()?;
141 let cmd = this.read_scalar(cmd)?.to_i32()?;
142
143 let f_getfd = this.eval_libc_i32("F_GETFD");
144 let f_dupfd = this.eval_libc_i32("F_DUPFD");
145 let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
146 let f_getfl = this.eval_libc_i32("F_GETFL");
147 let f_setfl = this.eval_libc_i32("F_SETFL");
148
149 match cmd {
151 cmd if cmd == f_getfd => {
152 if !this.machine.fds.is_fd_num(fd_num) {
157 this.set_last_error_and_return_i32(LibcError("EBADF"))
158 } else {
159 interp_ok(this.eval_libc("FD_CLOEXEC"))
160 }
161 }
162 cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
163 let cmd_name = if cmd == f_dupfd {
168 "fcntl(fd, F_DUPFD, ...)"
169 } else {
170 "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
171 };
172
173 let [start] = check_min_vararg_count(cmd_name, varargs)?;
174 let start = this.read_scalar(start)?.to_i32()?;
175
176 if let Some(fd) = this.machine.fds.get(fd_num) {
177 interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
178 } else {
179 this.set_last_error_and_return_i32(LibcError("EBADF"))
180 }
181 }
182 cmd if cmd == f_getfl => {
183 let Some(fd) = this.machine.fds.get(fd_num) else {
185 return this.set_last_error_and_return_i32(LibcError("EBADF"));
186 };
187
188 fd.get_flags(this)
189 }
190 cmd if cmd == f_setfl => {
191 let Some(fd) = this.machine.fds.get(fd_num) else {
193 return this.set_last_error_and_return_i32(LibcError("EBADF"));
194 };
195
196 let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
197 let flag = this.read_scalar(flag)?.to_i32()?;
198
199 let ignored_flags = this.eval_libc_i32("O_RDONLY")
204 | this.eval_libc_i32("O_WRONLY")
205 | this.eval_libc_i32("O_RDWR")
206 | this.eval_libc_i32("O_CREAT")
207 | this.eval_libc_i32("O_EXCL")
208 | this.eval_libc_i32("O_NOCTTY")
209 | this.eval_libc_i32("O_TRUNC");
210
211 fd.set_flags(flag & !ignored_flags, this)
212 }
213 cmd if this.tcx.sess.target.os == Os::MacOs
214 && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
215 {
216 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
218 this.reject_in_isolation("`fcntl`", reject_with)?;
219 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
220 }
221
222 this.ffullsync_fd(fd_num)
223 }
224 cmd => {
225 throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
226 }
227 }
228 }
229
230 fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
231 let this = self.eval_context_mut();
232
233 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
234
235 let Some(fd) = this.machine.fds.remove(fd_num) else {
236 return this.set_last_error_and_return_i32(LibcError("EBADF"));
237 };
238 let result = fd.close_ref(this.machine.communicate(), this)?;
239 let result = result.map(|()| 0i32);
241 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
242 }
243
244 fn read(
250 &mut self,
251 fd_num: i32,
252 buf: Pointer,
253 count: u64,
254 offset: Option<i128>,
255 dest: &MPlaceTy<'tcx>,
256 ) -> InterpResult<'tcx> {
257 let this = self.eval_context_mut();
258
259 trace!("Reading from FD {}, size {}", fd_num, count);
262
263 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
265
266 let count = count
269 .min(u64::try_from(this.target_isize_max()).unwrap())
270 .min(u64::try_from(isize::MAX).unwrap());
271 let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
273
274 let Some(fd) = this.machine.fds.get(fd_num) else {
276 trace!("read: FD not found");
277 return this.set_last_error_and_return(LibcError("EBADF"), dest);
278 };
279
280 if count == 0 {
285 this.write_null(dest)?;
286 return interp_ok(());
287 }
288 let count = if this.machine.short_fd_operations
291 && fd.short_fd_operations()
292 && count >= 2
293 && this.machine.rng.get_mut().random()
294 {
295 count / 2 } else {
297 count
298 };
299
300 trace!("read: FD mapped to {fd:?}");
301 let finish = {
306 let dest = dest.clone();
307 callback!(
308 @capture<'tcx> {
309 count: usize,
310 dest: MPlaceTy<'tcx>,
311 }
312 |this, result: Result<usize, IoError>| {
313 match result {
314 Ok(read_size) => {
315 assert!(read_size <= count);
316 this.write_int(u64::try_from(read_size).unwrap(), &dest)
318 }
319 Err(e) => {
320 this.set_last_error_and_return(e, &dest)
321 }
322 }}
323 )
324 };
325 match offset {
326 None => fd.read(communicate, buf, count, this, finish)?,
327 Some(offset) => {
328 let Ok(offset) = u64::try_from(offset) else {
329 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
330 };
331 fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
332 }
333 };
334 interp_ok(())
335 }
336
337 fn write(
338 &mut self,
339 fd_num: i32,
340 buf: Pointer,
341 count: u64,
342 offset: Option<i128>,
343 dest: &MPlaceTy<'tcx>,
344 ) -> InterpResult<'tcx> {
345 let this = self.eval_context_mut();
346
347 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
351
352 let count = count
355 .min(u64::try_from(this.target_isize_max()).unwrap())
356 .min(u64::try_from(isize::MAX).unwrap());
357 let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
359
360 let Some(fd) = this.machine.fds.get(fd_num) else {
362 return this.set_last_error_and_return(LibcError("EBADF"), dest);
363 };
364
365 if count == 0 {
372 this.write_null(dest)?;
374 return interp_ok(());
375 }
376 let count = if this.machine.short_fd_operations
380 && fd.short_fd_operations()
381 && count >= 2
382 && this.machine.rng.get_mut().random()
383 {
384 count / 2
385 } else {
386 count
387 };
388
389 let finish = {
390 let dest = dest.clone();
391 callback!(
392 @capture<'tcx> {
393 count: usize,
394 dest: MPlaceTy<'tcx>,
395 }
396 |this, result: Result<usize, IoError>| {
397 match result {
398 Ok(write_size) => {
399 assert!(write_size <= count);
400 this.write_int(u64::try_from(write_size).unwrap(), &dest)
402 }
403 Err(e) => {
404 this.set_last_error_and_return(e, &dest)
405 }
406 }}
407 )
408 };
409 match offset {
410 None => fd.write(communicate, buf, count, this, finish)?,
411 Some(offset) => {
412 let Ok(offset) = u64::try_from(offset) else {
413 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
414 };
415 fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
416 }
417 };
418 interp_ok(())
419 }
420}