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 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, EpollEvents> {
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 communicate = this.machine.communicate();
320
321 let Some(fd) = this.machine.fds.get(fd_num) else {
323 trace!("read: FD not found");
324 return this.set_last_error_and_return(LibcError("EBADF"), dest);
325 };
326
327 if count == 0 {
332 this.write_null(dest)?;
333 return interp_ok(());
334 }
335 let count = if this.machine.short_fd_operations
338 && fd.short_fd_operations()
339 && count >= 2
340 && this.machine.rng.get_mut().random()
341 {
342 count / 2 } else {
344 count
345 };
346
347 trace!("read: FD mapped to {fd:?}");
348 let finish = {
353 let dest = dest.clone();
354 callback!(
355 @capture<'tcx> {
356 count: usize,
357 dest: MPlaceTy<'tcx>,
358 }
359 |this, result: Result<usize, IoError>| {
360 match result {
361 Ok(read_size) => {
362 assert!(read_size <= count);
363 this.write_int(u64::try_from(read_size).unwrap(), &dest)
365 }
366 Err(e) => {
367 this.set_last_error_and_return(e, &dest)
368 }
369 }}
370 )
371 };
372 match offset {
373 None => fd.read(communicate, buf, count, this, finish)?,
374 Some(offset) => {
375 let Ok(offset) = u64::try_from(offset) else {
376 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
377 };
378 fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
379 }
380 };
381 interp_ok(())
382 }
383
384 fn write(
385 &mut self,
386 fd_num: i32,
387 buf: Pointer,
388 count: u64,
389 offset: Option<i128>,
390 dest: &MPlaceTy<'tcx>,
391 ) -> InterpResult<'tcx> {
392 let this = self.eval_context_mut();
393
394 this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
398
399 let count = count
402 .min(u64::try_from(this.target_isize_max()).unwrap())
403 .min(u64::try_from(isize::MAX).unwrap());
404 let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
406
407 let Some(fd) = this.machine.fds.get(fd_num) else {
409 return this.set_last_error_and_return(LibcError("EBADF"), dest);
410 };
411
412 if count == 0 {
419 this.write_null(dest)?;
421 return interp_ok(());
422 }
423 let count = if this.machine.short_fd_operations
427 && fd.short_fd_operations()
428 && count >= 2
429 && this.machine.rng.get_mut().random()
430 {
431 count / 2
432 } else {
433 count
434 };
435
436 let finish = {
437 let dest = dest.clone();
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) => {
451 this.set_last_error_and_return(e, &dest)
452 }
453 }}
454 )
455 };
456 match offset {
457 None => fd.write(communicate, buf, count, this, finish)?,
458 Some(offset) => {
459 let Ok(offset) = u64::try_from(offset) else {
460 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
461 };
462 fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
463 }
464 };
465 interp_ok(())
466 }
467}