]> git.lizzy.rs Git - rust.git/blob - src/shims/fs.rs
Fix dirent layout for macOS
[rust.git] / src / shims / fs.rs
1 use std::collections::BTreeMap;
2 use std::collections::HashMap;
3 use std::convert::{TryFrom, TryInto};
4 use std::fs::{read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir};
5 use std::io::{Read, Seek, SeekFrom, Write};
6 use std::path::PathBuf;
7 use std::time::SystemTime;
8
9 use rustc::ty::layout::{Align, LayoutOf, Size};
10
11 use crate::stacked_borrows::Tag;
12 use crate::*;
13 use helpers::immty_from_uint_checked;
14 use shims::time::system_time_to_duration;
15
16 #[derive(Debug)]
17 pub struct FileHandle {
18     file: File,
19     writable: bool,
20 }
21
22 #[derive(Debug, Default)]
23 pub struct FileHandler {
24     handles: BTreeMap<i32, FileHandle>,
25 }
26
27 // fd numbers 0, 1, and 2 are reserved for stdin, stdout, and stderr
28 const MIN_NORMAL_FILE_FD: i32 = 3;
29
30 impl FileHandler {
31     fn insert_fd(&mut self, file_handle: FileHandle) -> i32 {
32         self.insert_fd_with_min_fd(file_handle, 0)
33     }
34
35     fn insert_fd_with_min_fd(&mut self, file_handle: FileHandle, min_fd: i32) -> i32 {
36         let min_fd = std::cmp::max(min_fd, MIN_NORMAL_FILE_FD);
37
38         // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
39         // between used FDs, the find_map combinator will return it. If the first such unused FD
40         // is after all other used FDs, the find_map combinator will return None, and we will use
41         // the FD following the greatest FD thus far.
42         let candidate_new_fd = self
43             .handles
44             .range(min_fd..)
45             .zip(min_fd..)
46             .find_map(|((fd, _fh), counter)| {
47                 if *fd != counter {
48                     // There was a gap in the fds stored, return the first unused one
49                     // (note that this relies on BTreeMap iterating in key order)
50                     Some(counter)
51                 } else {
52                     // This fd is used, keep going
53                     None
54                 }
55             });
56         let new_fd = candidate_new_fd.unwrap_or_else(|| {
57             // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
58             // maximum fd in the map
59             self.handles.last_entry().map(|entry| entry.key() + 1).unwrap_or(min_fd)
60         });
61
62         self.handles.insert(new_fd, file_handle).unwrap_none();
63         new_fd
64     }
65 }
66
67 impl<'mir, 'tcx> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
68 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
69     /// Emulate `stat` or `lstat` on the `macos` platform. This function is not intended to be
70     /// called directly from `emulate_foreign_item_by_name`, so it does not check if isolation is
71     /// disabled or if the target platform is the correct one. Please use `macos_stat` or
72     /// `macos_lstat` instead.
73     fn macos_stat_or_lstat(
74         &mut self,
75         follow_symlink: bool,
76         path_op: OpTy<'tcx, Tag>,
77         buf_op: OpTy<'tcx, Tag>,
78     ) -> InterpResult<'tcx, i32> {
79         let this = self.eval_context_mut();
80
81         let path_scalar = this.read_scalar(path_op)?.not_undef()?;
82         let path: PathBuf = this.read_os_str_from_c_str(path_scalar)?.into();
83
84         let metadata = match FileMetadata::from_path(this, path, follow_symlink)? {
85             Some(metadata) => metadata,
86             None => return Ok(-1),
87         };
88         this.macos_stat_write_buf(metadata, buf_op)
89     }
90
91     fn macos_stat_write_buf(
92         &mut self,
93         metadata: FileMetadata,
94         buf_op: OpTy<'tcx, Tag>,
95     ) -> InterpResult<'tcx, i32> {
96         let this = self.eval_context_mut();
97
98         let mode: u16 = metadata.mode.to_u16()?;
99
100         let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
101         let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
102         let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
103
104         let dev_t_layout = this.libc_ty_layout("dev_t")?;
105         let mode_t_layout = this.libc_ty_layout("mode_t")?;
106         let nlink_t_layout = this.libc_ty_layout("nlink_t")?;
107         let ino_t_layout = this.libc_ty_layout("ino_t")?;
108         let uid_t_layout = this.libc_ty_layout("uid_t")?;
109         let gid_t_layout = this.libc_ty_layout("gid_t")?;
110         let time_t_layout = this.libc_ty_layout("time_t")?;
111         let long_layout = this.libc_ty_layout("c_long")?;
112         let off_t_layout = this.libc_ty_layout("off_t")?;
113         let blkcnt_t_layout = this.libc_ty_layout("blkcnt_t")?;
114         let blksize_t_layout = this.libc_ty_layout("blksize_t")?;
115         let uint32_t_layout = this.libc_ty_layout("uint32_t")?;
116
117         // We need to add 32 bits of padding after `st_rdev` if we are on a 64-bit platform.
118         let pad_layout = if this.tcx.sess.target.ptr_width == 64 {
119             uint32_t_layout
120         } else {
121             this.layout_of(this.tcx.mk_unit())?
122         };
123
124         let imms = [
125             immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev
126             immty_from_uint_checked(mode, mode_t_layout)?, // st_mode
127             immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink
128             immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino
129             immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid
130             immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid
131             immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev
132             immty_from_uint_checked(0u128, pad_layout)?, // padding for 64-bit targets
133             immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime
134             immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec
135             immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime
136             immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec
137             immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime
138             immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec
139             immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime
140             immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec
141             immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size
142             immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks
143             immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize
144             immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags
145             immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen
146         ];
147
148         let buf = this.deref_operand(buf_op)?;
149         this.write_packed_immediates(buf, &imms)?;
150
151         Ok(0)
152     }
153
154     /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
155     /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
156     /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
157     /// types (like `read`, that returns an `i64`).
158     fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
159         let this = self.eval_context_mut();
160         let ebadf = this.eval_libc("EBADF")?;
161         this.set_last_error(ebadf)?;
162         Ok((-1).into())
163     }
164
165     fn file_type_to_d_type(&mut self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
166         let this = self.eval_context_mut();
167         match file_type {
168             Ok(file_type) => {
169                 if file_type.is_dir() {
170                     Ok(this.eval_libc("DT_DIR")?.to_u8()? as i32)
171                 } else if file_type.is_file() {
172                     Ok(this.eval_libc("DT_REG")?.to_u8()? as i32)
173                 } else if file_type.is_symlink() {
174                     Ok(this.eval_libc("DT_LNK")?.to_u8()? as i32)
175                 } else {
176                     #[cfg(unix)]
177                     {
178                         use std::os::unix::fs::FileTypeExt;
179                         if file_type.is_block_device() {
180                             Ok(this.eval_libc("DT_BLK")?.to_u8()? as i32)
181                         } else if file_type.is_char_device() {
182                             Ok(this.eval_libc("DT_CHR")?.to_u8()? as i32)
183                         } else if file_type.is_fifo() {
184                             Ok(this.eval_libc("DT_FIFO")?.to_u8()? as i32)
185                         } else if file_type.is_socket() {
186                             Ok(this.eval_libc("DT_SOCK")?.to_u8()? as i32)
187                         } else {
188                             Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()? as i32)
189                         }
190                     }
191                     #[cfg(not(unix))]
192                     Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()? as i32)
193                 }
194             }
195             Err(e) => return match e.raw_os_error() {
196                 Some(error) => Ok(error),
197                 None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
198             }
199         }
200     }
201 }
202
203 #[derive(Debug, Default)]
204 pub struct DirHandler {
205     streams: HashMap<Pointer<Tag>, ReadDir>,
206 }
207
208 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
209 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
210     fn open(
211         &mut self,
212         path_op: OpTy<'tcx, Tag>,
213         flag_op: OpTy<'tcx, Tag>,
214     ) -> InterpResult<'tcx, i32> {
215         let this = self.eval_context_mut();
216
217         this.check_no_isolation("open")?;
218
219         let flag = this.read_scalar(flag_op)?.to_i32()?;
220
221         let mut options = OpenOptions::new();
222
223         let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
224         let o_wronly = this.eval_libc_i32("O_WRONLY")?;
225         let o_rdwr = this.eval_libc_i32("O_RDWR")?;
226         // The first two bits of the flag correspond to the access mode in linux, macOS and
227         // windows. We need to check that in fact the access mode flags for the current platform
228         // only use these two bits, otherwise we are in an unsupported platform and should error.
229         if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
230             throw_unsup_format!("Access mode flags on this platform are unsupported");
231         }
232         let mut writable = true;
233
234         // Now we check the access mode
235         let access_mode = flag & 0b11;
236
237         if access_mode == o_rdonly {
238             writable = false;
239             options.read(true);
240         } else if access_mode == o_wronly {
241             options.write(true);
242         } else if access_mode == o_rdwr {
243             options.read(true).write(true);
244         } else {
245             throw_unsup_format!("Unsupported access mode {:#x}", access_mode);
246         }
247         // We need to check that there aren't unsupported options in `flag`. For this we try to
248         // reproduce the content of `flag` in the `mirror` variable using only the supported
249         // options.
250         let mut mirror = access_mode;
251
252         let o_append = this.eval_libc_i32("O_APPEND")?;
253         if flag & o_append != 0 {
254             options.append(true);
255             mirror |= o_append;
256         }
257         let o_trunc = this.eval_libc_i32("O_TRUNC")?;
258         if flag & o_trunc != 0 {
259             options.truncate(true);
260             mirror |= o_trunc;
261         }
262         let o_creat = this.eval_libc_i32("O_CREAT")?;
263         if flag & o_creat != 0 {
264             options.create(true);
265             mirror |= o_creat;
266         }
267         let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
268         if flag & o_cloexec != 0 {
269             // We do not need to do anything for this flag because `std` already sets it.
270             // (Technically we do not support *not* setting this flag, but we ignore that.)
271             mirror |= o_cloexec;
272         }
273         // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
274         // then we throw an error.
275         if flag != mirror {
276             throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
277         }
278
279         let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
280
281         let fd = options.open(&path).map(|file| {
282             let fh = &mut this.machine.file_handler;
283             fh.insert_fd(FileHandle { file, writable })
284         });
285
286         this.try_unwrap_io_result(fd)
287     }
288
289     fn fcntl(
290         &mut self,
291         fd_op: OpTy<'tcx, Tag>,
292         cmd_op: OpTy<'tcx, Tag>,
293         start_op: Option<OpTy<'tcx, Tag>>,
294     ) -> InterpResult<'tcx, i32> {
295         let this = self.eval_context_mut();
296
297         this.check_no_isolation("fcntl")?;
298
299         let fd = this.read_scalar(fd_op)?.to_i32()?;
300         let cmd = this.read_scalar(cmd_op)?.to_i32()?;
301         // We only support getting the flags for a descriptor.
302         if cmd == this.eval_libc_i32("F_GETFD")? {
303             // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
304             // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
305             // always sets this flag when opening a file. However we still need to check that the
306             // file itself is open.
307             if this.machine.file_handler.handles.contains_key(&fd) {
308                 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
309             } else {
310                 this.handle_not_found()
311             }
312         } else if cmd == this.eval_libc_i32("F_DUPFD")?
313             || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
314         {
315             // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
316             // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
317             // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
318             // thus they can share the same implementation here.
319             if fd < MIN_NORMAL_FILE_FD {
320                 throw_unsup_format!("Duplicating file descriptors for stdin, stdout, or stderr is not supported")
321             }
322             let start_op = start_op.ok_or_else(|| {
323                 err_unsup_format!(
324                     "fcntl with command F_DUPFD or F_DUPFD_CLOEXEC requires a third argument"
325                 )
326             })?;
327             let start = this.read_scalar(start_op)?.to_i32()?;
328             let fh = &mut this.machine.file_handler;
329             let (file_result, writable) = match fh.handles.get(&fd) {
330                 Some(FileHandle { file, writable }) => (file.try_clone(), *writable),
331                 None => return this.handle_not_found(),
332             };
333             let fd_result = file_result.map(|duplicated| {
334                 fh.insert_fd_with_min_fd(FileHandle { file: duplicated, writable }, start)
335             });
336             this.try_unwrap_io_result(fd_result)
337         } else {
338             throw_unsup_format!("The {:#x} command is not supported for `fcntl`)", cmd);
339         }
340     }
341
342     fn close(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
343         let this = self.eval_context_mut();
344
345         this.check_no_isolation("close")?;
346
347         let fd = this.read_scalar(fd_op)?.to_i32()?;
348
349         if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.remove(&fd) {
350             // We sync the file if it was opened in a mode different than read-only.
351             if writable {
352                 // `File::sync_all` does the checks that are done when closing a file. We do this to
353                 // to handle possible errors correctly.
354                 let result = this.try_unwrap_io_result(file.sync_all().map(|_| 0i32));
355                 // Now we actually close the file.
356                 drop(file);
357                 // And return the result.
358                 result
359             } else {
360                 // We drop the file, this closes it but ignores any errors produced when closing
361                 // it. This is done because `File::sync_all` cannot be done over files like
362                 // `/dev/urandom` which are read-only. Check
363                 // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
364                 // discussion.
365                 drop(file);
366                 Ok(0)
367             }
368         } else {
369             this.handle_not_found()
370         }
371     }
372
373     fn read(
374         &mut self,
375         fd_op: OpTy<'tcx, Tag>,
376         buf_op: OpTy<'tcx, Tag>,
377         count_op: OpTy<'tcx, Tag>,
378     ) -> InterpResult<'tcx, i64> {
379         let this = self.eval_context_mut();
380
381         this.check_no_isolation("read")?;
382
383         let fd = this.read_scalar(fd_op)?.to_i32()?;
384         let buf = this.read_scalar(buf_op)?.not_undef()?;
385         let count = this.read_scalar(count_op)?.to_machine_usize(&*this.tcx)?;
386
387         // Check that the *entire* buffer is actually valid memory.
388         this.memory.check_ptr_access(
389             buf,
390             Size::from_bytes(count),
391             Align::from_bytes(1).unwrap(),
392         )?;
393
394         // We cap the number of read bytes to the largest value that we are able to fit in both the
395         // host's and target's `isize`. This saves us from having to handle overflows later.
396         let count = count.min(this.isize_max() as u64).min(isize::max_value() as u64);
397
398         if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
399             // This can never fail because `count` was capped to be smaller than
400             // `isize::max_value()`.
401             let count = isize::try_from(count).unwrap();
402             // We want to read at most `count` bytes. We are sure that `count` is not negative
403             // because it was a target's `usize`. Also we are sure that its smaller than
404             // `usize::max_value()` because it is a host's `isize`.
405             let mut bytes = vec![0; count as usize];
406             let result = file
407                 .read(&mut bytes)
408                 // `File::read` never returns a value larger than `count`, so this cannot fail.
409                 .map(|c| i64::try_from(c).unwrap());
410
411             match result {
412                 Ok(read_bytes) => {
413                     // If reading to `bytes` did not fail, we write those bytes to the buffer.
414                     this.memory.write_bytes(buf, bytes)?;
415                     Ok(read_bytes)
416                 }
417                 Err(e) => {
418                     this.set_last_error_from_io_error(e)?;
419                     Ok(-1)
420                 }
421             }
422         } else {
423             this.handle_not_found()
424         }
425     }
426
427     fn write(
428         &mut self,
429         fd_op: OpTy<'tcx, Tag>,
430         buf_op: OpTy<'tcx, Tag>,
431         count_op: OpTy<'tcx, Tag>,
432     ) -> InterpResult<'tcx, i64> {
433         let this = self.eval_context_mut();
434
435         this.check_no_isolation("write")?;
436
437         let fd = this.read_scalar(fd_op)?.to_i32()?;
438         let buf = this.read_scalar(buf_op)?.not_undef()?;
439         let count = this.read_scalar(count_op)?.to_machine_usize(&*this.tcx)?;
440
441         // Check that the *entire* buffer is actually valid memory.
442         this.memory.check_ptr_access(
443             buf,
444             Size::from_bytes(count),
445             Align::from_bytes(1).unwrap(),
446         )?;
447
448         // We cap the number of written bytes to the largest value that we are able to fit in both the
449         // host's and target's `isize`. This saves us from having to handle overflows later.
450         let count = count.min(this.isize_max() as u64).min(isize::max_value() as u64);
451
452         if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
453             let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
454             let result = file.write(&bytes).map(|c| i64::try_from(c).unwrap());
455             this.try_unwrap_io_result(result)
456         } else {
457             this.handle_not_found()
458         }
459     }
460
461     fn lseek64(
462         &mut self,
463         fd_op: OpTy<'tcx, Tag>,
464         offset_op: OpTy<'tcx, Tag>,
465         whence_op: OpTy<'tcx, Tag>,
466     ) -> InterpResult<'tcx, i64> {
467         let this = self.eval_context_mut();
468
469         this.check_no_isolation("lseek64")?;
470
471         let fd = this.read_scalar(fd_op)?.to_i32()?;
472         let offset = this.read_scalar(offset_op)?.to_i64()?;
473         let whence = this.read_scalar(whence_op)?.to_i32()?;
474
475         let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
476             SeekFrom::Start(offset as u64)
477         } else if whence == this.eval_libc_i32("SEEK_CUR")? {
478             SeekFrom::Current(offset)
479         } else if whence == this.eval_libc_i32("SEEK_END")? {
480             SeekFrom::End(offset)
481         } else {
482             let einval = this.eval_libc("EINVAL")?;
483             this.set_last_error(einval)?;
484             return Ok(-1);
485         };
486
487         if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
488             let result = file.seek(seek_from).map(|offset| offset as i64);
489             this.try_unwrap_io_result(result)
490         } else {
491             this.handle_not_found()
492         }
493     }
494
495     fn unlink(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
496         let this = self.eval_context_mut();
497
498         this.check_no_isolation("unlink")?;
499
500         let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
501
502         let result = remove_file(path).map(|_| 0);
503
504         this.try_unwrap_io_result(result)
505     }
506
507     fn symlink(
508         &mut self,
509         target_op: OpTy<'tcx, Tag>,
510         linkpath_op: OpTy<'tcx, Tag>
511     ) -> InterpResult<'tcx, i32> {
512         #[cfg(target_family = "unix")]
513         fn create_link(src: PathBuf, dst: PathBuf) -> std::io::Result<()> {
514             std::os::unix::fs::symlink(src, dst)
515         }
516
517         #[cfg(target_family = "windows")]
518         fn create_link(src: PathBuf, dst: PathBuf) -> std::io::Result<()> {
519             use std::os::windows::fs;
520             if src.is_dir() {
521                 fs::symlink_dir(src, dst)
522             } else {
523                 fs::symlink_file(src, dst)
524             }
525         }
526
527         let this = self.eval_context_mut();
528
529         this.check_no_isolation("symlink")?;
530
531         let target = this.read_os_str_from_c_str(this.read_scalar(target_op)?.not_undef()?)?.into();
532         let linkpath = this.read_os_str_from_c_str(this.read_scalar(linkpath_op)?.not_undef()?)?.into();
533
534         this.try_unwrap_io_result(create_link(target, linkpath).map(|_| 0))
535     }
536
537     fn macos_stat(
538         &mut self,
539         path_op: OpTy<'tcx, Tag>,
540         buf_op: OpTy<'tcx, Tag>,
541     ) -> InterpResult<'tcx, i32> {
542         let this = self.eval_context_mut();
543         this.check_no_isolation("stat")?;
544         this.assert_platform("macos", "stat");
545         // `stat` always follows symlinks.
546         this.macos_stat_or_lstat(true, path_op, buf_op)
547     }
548
549     // `lstat` is used to get symlink metadata.
550     fn macos_lstat(
551         &mut self,
552         path_op: OpTy<'tcx, Tag>,
553         buf_op: OpTy<'tcx, Tag>,
554     ) -> InterpResult<'tcx, i32> {
555         let this = self.eval_context_mut();
556         this.check_no_isolation("lstat")?;
557         this.assert_platform("macos", "lstat");
558         this.macos_stat_or_lstat(false, path_op, buf_op)
559     }
560
561     fn macos_fstat(
562         &mut self,
563         fd_op: OpTy<'tcx, Tag>,
564         buf_op: OpTy<'tcx, Tag>,
565     ) -> InterpResult<'tcx, i32> {
566         let this = self.eval_context_mut();
567
568         this.check_no_isolation("fstat")?;
569         this.assert_platform("macos", "fstat");
570
571         let fd = this.read_scalar(fd_op)?.to_i32()?;
572
573         let metadata = match FileMetadata::from_fd(this, fd)? {
574             Some(metadata) => metadata,
575             None => return Ok(-1),
576         };
577         this.macos_stat_write_buf(metadata, buf_op)
578     }
579
580     fn linux_statx(
581         &mut self,
582         dirfd_op: OpTy<'tcx, Tag>,    // Should be an `int`
583         pathname_op: OpTy<'tcx, Tag>, // Should be a `const char *`
584         flags_op: OpTy<'tcx, Tag>,    // Should be an `int`
585         _mask_op: OpTy<'tcx, Tag>,    // Should be an `unsigned int`
586         statxbuf_op: OpTy<'tcx, Tag>, // Should be a `struct statx *`
587     ) -> InterpResult<'tcx, i32> {
588         let this = self.eval_context_mut();
589
590         this.check_no_isolation("statx")?;
591         this.assert_platform("linux", "statx");
592
593         let statxbuf_scalar = this.read_scalar(statxbuf_op)?.not_undef()?;
594         let pathname_scalar = this.read_scalar(pathname_op)?.not_undef()?;
595
596         // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
597         if this.is_null(statxbuf_scalar)? || this.is_null(pathname_scalar)? {
598             let efault = this.eval_libc("EFAULT")?;
599             this.set_last_error(efault)?;
600             return Ok(-1);
601         }
602
603         // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
604         // proper `MemPlace` and then write the results of this function to it. However, the
605         // `syscall` function is untyped. This means that all the `statx` parameters are provided
606         // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
607         // `statxbuf_op` by using the `libc::statx` struct type.
608         let statxbuf_place = {
609             // FIXME: This long path is required because `libc::statx` is an struct and also a
610             // function and `resolve_path` is returning the latter.
611             let statx_ty = this
612                 .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])?
613                 .monomorphic_ty(*this.tcx);
614             let statxbuf_ty = this.tcx.mk_mut_ptr(statx_ty);
615             let statxbuf_layout = this.layout_of(statxbuf_ty)?;
616             let statxbuf_imm = ImmTy::from_scalar(statxbuf_scalar, statxbuf_layout);
617             this.ref_to_mplace(statxbuf_imm)?
618         };
619
620         let path: PathBuf = this.read_os_str_from_c_str(pathname_scalar)?.into();
621         // `flags` should be a `c_int` but the `syscall` function provides an `isize`.
622         let flags: i32 =
623             this.read_scalar(flags_op)?.to_machine_isize(&*this.tcx)?.try_into().map_err(|e| {
624                 err_unsup_format!("Failed to convert pointer sized operand to integer: {}", e)
625             })?;
626         let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
627         // `dirfd` should be a `c_int` but the `syscall` function provides an `isize`.
628         let dirfd: i32 =
629             this.read_scalar(dirfd_op)?.to_machine_isize(&*this.tcx)?.try_into().map_err(|e| {
630                 err_unsup_format!("Failed to convert pointer sized operand to integer: {}", e)
631             })?;
632         // We only support:
633         // * interpreting `path` as an absolute directory,
634         // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
635         // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
636         // set.
637         // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
638         // found this error, please open an issue reporting it.
639         if !(
640             path.is_absolute() ||
641             dirfd == this.eval_libc_i32("AT_FDCWD")? ||
642             (path.as_os_str().is_empty() && empty_path_flag)
643         ) {
644             throw_unsup_format!(
645                 "Using statx is only supported with absolute paths, relative paths with the file \
646                 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
647                 file descriptor"
648             )
649         }
650
651         // the `_mask_op` paramter specifies the file information that the caller requested.
652         // However `statx` is allowed to return information that was not requested or to not
653         // return information that was requested. This `mask` represents the information we can
654         // actually provide in any host platform.
655         let mut mask =
656             this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
657
658         // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
659         // symbolic links.
660         let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
661
662         // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
663         // represented by dirfd, whether it's a directory or otherwise.
664         let metadata = if path.as_os_str().is_empty() && empty_path_flag {
665             FileMetadata::from_fd(this, dirfd)?
666         } else {
667             FileMetadata::from_path(this, path, follow_symlink)?
668         };
669         let metadata = match metadata {
670             Some(metadata) => metadata,
671             None => return Ok(-1),
672         };
673
674         // The `mode` field specifies the type of the file and the permissions over the file for
675         // the owner, its group and other users. Given that we can only provide the file type
676         // without using platform specific methods, we only set the bits corresponding to the file
677         // type. This should be an `__u16` but `libc` provides its values as `u32`.
678         let mode: u16 = metadata
679             .mode
680             .to_u32()?
681             .try_into()
682             .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
683
684         // We need to set the corresponding bits of `mask` if the access, creation and modification
685         // times were available. Otherwise we let them be zero.
686         let (access_sec, access_nsec) = metadata.accessed.map(|tup| {
687             mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
688             InterpResult::Ok(tup)
689         }).unwrap_or(Ok((0, 0)))?;
690
691         let (created_sec, created_nsec) = metadata.created.map(|tup| {
692             mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
693             InterpResult::Ok(tup)
694         }).unwrap_or(Ok((0, 0)))?;
695
696         let (modified_sec, modified_nsec) = metadata.modified.map(|tup| {
697             mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
698             InterpResult::Ok(tup)
699         }).unwrap_or(Ok((0, 0)))?;
700
701         let __u32_layout = this.libc_ty_layout("__u32")?;
702         let __u64_layout = this.libc_ty_layout("__u64")?;
703         let __u16_layout = this.libc_ty_layout("__u16")?;
704
705         // Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a
706         // zero for the unavailable fields.
707         let imms = [
708             immty_from_uint_checked(mask, __u32_layout)?, // stx_mask
709             immty_from_uint_checked(0u128, __u32_layout)?, // stx_blksize
710             immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
711             immty_from_uint_checked(0u128, __u32_layout)?, // stx_nlink
712             immty_from_uint_checked(0u128, __u32_layout)?, // stx_uid
713             immty_from_uint_checked(0u128, __u32_layout)?, // stx_gid
714             immty_from_uint_checked(mode, __u16_layout)?, // stx_mode
715             immty_from_uint_checked(0u128, __u16_layout)?, // statx padding
716             immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino
717             immty_from_uint_checked(metadata.size, __u64_layout)?, // stx_size
718             immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks
719             immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
720             immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec
721             immty_from_uint_checked(access_nsec, __u32_layout)?, // stx_atime.tv_nsec
722             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
723             immty_from_uint_checked(created_sec, __u64_layout)?, // stx_btime.tv_sec
724             immty_from_uint_checked(created_nsec, __u32_layout)?, // stx_btime.tv_nsec
725             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
726             immty_from_uint_checked(0u128, __u64_layout)?, // stx_ctime.tv_sec
727             immty_from_uint_checked(0u128, __u32_layout)?, // stx_ctime.tv_nsec
728             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
729             immty_from_uint_checked(modified_sec, __u64_layout)?, // stx_mtime.tv_sec
730             immty_from_uint_checked(modified_nsec, __u32_layout)?, // stx_mtime.tv_nsec
731             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
732             immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_major
733             immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_minor
734             immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_major
735             immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_minor
736         ];
737
738         this.write_packed_immediates(statxbuf_place, &imms)?;
739
740         Ok(0)
741     }
742
743     fn rename(
744         &mut self,
745         oldpath_op: OpTy<'tcx, Tag>,
746         newpath_op: OpTy<'tcx, Tag>,
747     ) -> InterpResult<'tcx, i32> {
748         let this = self.eval_context_mut();
749
750         this.check_no_isolation("rename")?;
751
752         let oldpath_scalar = this.read_scalar(oldpath_op)?.not_undef()?;
753         let newpath_scalar = this.read_scalar(newpath_op)?.not_undef()?;
754
755         if this.is_null(oldpath_scalar)? || this.is_null(newpath_scalar)? {
756             let efault = this.eval_libc("EFAULT")?;
757             this.set_last_error(efault)?;
758             return Ok(-1);
759         }
760
761         let oldpath = this.read_os_str_from_c_str(oldpath_scalar)?;
762         let newpath = this.read_os_str_from_c_str(newpath_scalar)?;
763
764         let result = rename(oldpath, newpath).map(|_| 0);
765
766         this.try_unwrap_io_result(result)
767     }
768
769     fn mkdir(
770         &mut self,
771         path_op: OpTy<'tcx, Tag>,
772         mode_op: OpTy<'tcx, Tag>,
773     ) -> InterpResult<'tcx, i32> {
774         let this = self.eval_context_mut();
775
776         this.check_no_isolation("mkdir")?;
777
778         #[cfg(target_os = "linux")]
779         let mode = this.read_scalar(mode_op)?.to_u32()?;
780         #[cfg(not(target_os = "linux"))]
781         let mode = this.read_scalar(mode_op)?.not_undef()?.to_u16()?;
782
783         let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
784
785         let mut builder = DirBuilder::new();
786         #[cfg(target_family = "unix")]
787         {
788             use std::os::unix::fs::DirBuilderExt;
789             builder.mode(mode.into());
790         }
791         #[cfg(not(target_family = "unix"))]
792         let _mode = mode;
793         let result = builder.create(path).map(|_| 0i32);
794
795         this.try_unwrap_io_result(result)
796     }
797
798     fn rmdir(
799         &mut self,
800         path_op: OpTy<'tcx, Tag>,
801     ) -> InterpResult<'tcx, i32> {
802         let this = self.eval_context_mut();
803
804         this.check_no_isolation("rmdir")?;
805
806         let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
807
808         let result = remove_dir(path).map(|_| 0i32);
809
810         this.try_unwrap_io_result(result)
811     }
812
813     fn opendir(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
814         let this = self.eval_context_mut();
815
816         this.check_no_isolation("opendir")?;
817
818         let name = this.read_os_str_from_c_str(this.read_scalar(name_op)?.not_undef()?)?;
819
820         let result = read_dir(name);
821
822         match result {
823             Ok(dir_iter) => {
824                 let size = 1;
825                 let kind = MiriMemoryKind::Env;
826                 let align = this.min_align(size, kind);
827                 let dir_ptr = this.memory.allocate(Size::from_bytes(size), align, kind.into());
828                 let prev = this
829                     .machine
830                     .dir_handler
831                     .streams
832                     .insert(dir_ptr, dir_iter);
833                 if let Some(_) = prev {
834                     throw_unsup_format!("The pointer allocated for opendir was already registered by a previous call to opendir")
835                 } else {
836                     Ok(Scalar::Ptr(dir_ptr))
837                 }
838             }
839             Err(e) => {
840                 this.set_last_error_from_io_error(e)?;
841                 Ok(Scalar::from_int(0, this.memory.pointer_size()))
842             }
843         }
844     }
845
846     fn readdir64_r(
847         &mut self,
848         dirp_op: OpTy<'tcx, Tag>,
849         entry_op: OpTy<'tcx, Tag>,
850         result_op: OpTy<'tcx, Tag>,
851     ) -> InterpResult<'tcx, i32> {
852         let this = self.eval_context_mut();
853
854         this.check_no_isolation("readdir64_r")?;
855
856         let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
857
858         let entry_ptr = this.force_ptr(this.read_scalar(entry_op)?.not_undef()?)?;
859         let dirent64_layout = this.libc_ty_layout("dirent64")?;
860         this.memory.check_ptr_access(
861             Scalar::Ptr(entry_ptr),
862             dirent64_layout.size,
863             dirent64_layout.align.abi,
864         )?;
865
866         if let Some(dir_iter) = this.machine.dir_handler.streams.get_mut(&dirp) {
867             match dir_iter.next() {
868                 Some(Ok(dir_entry)) => {
869                     // write into entry, write pointer to result, return 0 on success
870                     let entry_place = this.deref_operand(entry_op)?;
871                     let ino64_t_layout = this.libc_ty_layout("ino64_t")?;
872                     let off64_t_layout = this.libc_ty_layout("off64_t")?;
873                     let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
874                     let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
875
876                     let name_offset = dirent64_layout.details.fields.offset(4);
877                     let name_ptr = entry_ptr.offset(name_offset, this)?;
878
879                     #[cfg(unix)]
880                     let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
881                     #[cfg(not(unix))]
882                     let ino = 0;
883
884                     #[cfg(unix)]
885                     let file_name = dir_entry.file_name();
886                     #[cfg(unix)]
887                     let file_name = std::os::unix::ffi::OsStrExt::as_bytes(file_name.as_os_str());
888                     #[cfg(not(unix))]
889                     let file_name = b"";
890
891                     let file_type = this.file_type_to_d_type(dir_entry.file_type())? as u128;
892
893                     let imms = [
894                         immty_from_uint_checked(ino, ino64_t_layout)?, // d_ino
895                         immty_from_uint_checked(0u128, off64_t_layout)?, // d_off
896                         immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
897                         immty_from_uint_checked(file_type, c_uchar_layout)?, // d_type
898                     ];
899                     this.write_packed_immediates(entry_place, &imms)?;
900                     this.memory.write_bytes(Scalar::Ptr(name_ptr), file_name.iter().copied())?;
901
902                     let result_place = this.deref_operand(result_op)?;
903                     this.write_scalar(this.read_scalar(entry_op)?, result_place.into())?;
904
905                     Ok(0)
906                 }
907                 None => {
908                     // end of stream: return 0, assign *result=NULL
909                     this.write_null(this.deref_operand(result_op)?.into())?;
910                     Ok(0)
911                 }
912                 Some(Err(e)) => match e.raw_os_error() {
913                     // return positive error number on error
914                     Some(error) => Ok(error),
915                     None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
916                 }
917             }
918         } else {
919             throw_unsup_format!("The DIR pointer passed to readdir64_r did not come from opendir")
920         }
921     }
922
923     fn readdir_r(
924         &mut self,
925         dirp_op: OpTy<'tcx, Tag>,
926         entry_op: OpTy<'tcx, Tag>,
927         result_op: OpTy<'tcx, Tag>,
928     ) -> InterpResult<'tcx, i32> {
929         let this = self.eval_context_mut();
930
931         this.check_no_isolation("readdir_r")?;
932
933         let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
934
935         let entry_ptr = this.force_ptr(this.read_scalar(entry_op)?.not_undef()?)?;
936         let dirent_layout = this.libc_ty_layout("dirent")?;
937         this.memory.check_ptr_access(
938             Scalar::Ptr(entry_ptr),
939             dirent_layout.size,
940             dirent_layout.align.abi,
941         )?;
942
943         if let Some(dir_iter) = this.machine.dir_handler.streams.get_mut(&dirp) {
944             match dir_iter.next() {
945                 Some(Ok(dir_entry)) => {
946                     // write into entry, write pointer to result, return 0 on success
947                     let entry_place = this.deref_operand(entry_op)?;
948                     let ino_t_layout = this.libc_ty_layout("ino_t")?;
949                     let off_t_layout = this.libc_ty_layout("off_t")?;
950                     let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
951                     let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
952
953                     let name_offset = dirent_layout.details.fields.offset(5);
954                     let name_ptr = entry_ptr.offset(name_offset, this)?;
955
956                     #[cfg(unix)]
957                     let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
958                     #[cfg(not(unix))]
959                     let ino = 0;
960
961                     #[cfg(unix)]
962                     let file_name = dir_entry.file_name();
963                     #[cfg(unix)]
964                     let file_name = std::os::unix::ffi::OsStrExt::as_bytes(file_name.as_os_str());
965                     #[cfg(not(unix))]
966                     let file_name = b"";
967
968                     let file_type = this.file_type_to_d_type(dir_entry.file_type())? as u128;
969
970                     let imms = [
971                         immty_from_uint_checked(ino, ino_t_layout)?, // d_ino
972                         immty_from_uint_checked(0u128, off_t_layout)?, // d_seekoff
973                         immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
974                         immty_from_uint_checked(file_name.len() as u128, c_ushort_layout)?, // d_namlen
975                         immty_from_uint_checked(file_type, c_uchar_layout)?, // d_type
976                     ];
977                     this.write_packed_immediates(entry_place, &imms)?;
978                     this.memory.write_bytes(Scalar::Ptr(name_ptr), file_name.iter().copied())?;
979
980                     let result_place = this.deref_operand(result_op)?;
981                     this.write_scalar(this.read_scalar(entry_op)?, result_place.into())?;
982
983                     Ok(0)
984                 }
985                 None => {
986                     // end of stream: return 0, assign *result=NULL
987                     this.write_null(this.deref_operand(result_op)?.into())?;
988                     Ok(0)
989                 }
990                 Some(Err(e)) => match e.raw_os_error() {
991                     // return positive error number on error
992                     Some(error) => Ok(error),
993                     None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
994                 }
995             }
996         } else {
997             throw_unsup_format!("The DIR pointer passed to readdir_r did not come from opendir")
998         }
999     }
1000
1001     fn closedir(&mut self, dirp_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1002         let this = self.eval_context_mut();
1003
1004         this.check_no_isolation("closedir")?;
1005
1006         let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
1007
1008         if let Some(dir_iter) = this.machine.dir_handler.streams.remove(&dirp) {
1009             drop(dir_iter);
1010             this.memory.deallocate(dirp, None, MiriMemoryKind::Env.into())?;
1011             Ok(0)
1012         } else {
1013             this.handle_not_found()
1014         }
1015     }
1016 }
1017
1018 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1019 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1020 /// epoch.
1021 fn extract_sec_and_nsec<'tcx>(
1022     time: std::io::Result<SystemTime>
1023 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1024     time.ok().map(|time| {
1025         let duration = system_time_to_duration(&time)?;
1026         Ok((duration.as_secs(), duration.subsec_nanos()))
1027     }).transpose()
1028 }
1029
1030 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1031 /// shims.
1032 struct FileMetadata {
1033     mode: Scalar<Tag>,
1034     size: u64,
1035     created: Option<(u64, u32)>,
1036     accessed: Option<(u64, u32)>,
1037     modified: Option<(u64, u32)>,
1038 }
1039
1040 impl FileMetadata {
1041     fn from_path<'tcx, 'mir>(
1042         ecx: &mut MiriEvalContext<'mir, 'tcx>,
1043         path: PathBuf,
1044         follow_symlink: bool
1045     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1046         let metadata = if follow_symlink {
1047             std::fs::metadata(path)
1048         } else {
1049             std::fs::symlink_metadata(path)
1050         };
1051
1052         FileMetadata::from_meta(ecx, metadata)
1053     }
1054
1055     fn from_fd<'tcx, 'mir>(
1056         ecx: &mut MiriEvalContext<'mir, 'tcx>,
1057         fd: i32,
1058     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1059         let option = ecx.machine.file_handler.handles.get(&fd);
1060         let file = match option {
1061             Some(FileHandle { file, writable: _ }) => file,
1062             None => return ecx.handle_not_found().map(|_: i32| None),
1063         };
1064         let metadata = file.metadata();
1065
1066         FileMetadata::from_meta(ecx, metadata)
1067     }
1068
1069     fn from_meta<'tcx, 'mir>(
1070         ecx: &mut MiriEvalContext<'mir, 'tcx>,
1071         metadata: Result<std::fs::Metadata, std::io::Error>,
1072     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1073         let metadata = match metadata {
1074             Ok(metadata) => metadata,
1075             Err(e) => {
1076                 ecx.set_last_error_from_io_error(e)?;
1077                 return Ok(None);
1078             }
1079         };
1080
1081         let file_type = metadata.file_type();
1082
1083         let mode_name = if file_type.is_file() {
1084             "S_IFREG"
1085         } else if file_type.is_dir() {
1086             "S_IFDIR"
1087         } else {
1088             "S_IFLNK"
1089         };
1090
1091         let mode = ecx.eval_libc(mode_name)?;
1092
1093         let size = metadata.len();
1094
1095         let created = extract_sec_and_nsec(metadata.created())?;
1096         let accessed = extract_sec_and_nsec(metadata.accessed())?;
1097         let modified = extract_sec_and_nsec(metadata.modified())?;
1098
1099         // FIXME: Provide more fields using platform specific methods.
1100         Ok(Some(FileMetadata { mode, size, created, accessed, modified }))
1101     }
1102 }