1 #![deny(unsafe_op_in_unsafe_fn)]
4 use crate::ffi::{CStr, CString, OsStr, OsString};
6 use crate::io::{self, IoSlice, IoSliceMut, SeekFrom};
8 use crate::mem::{self, ManuallyDrop};
9 use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
10 use crate::path::{Path, PathBuf};
13 use crate::sys::time::SystemTime;
14 use crate::sys::unsupported;
15 use crate::sys_common::FromInner;
17 pub use crate::sys_common::fs::{remove_dir_all, try_exists};
29 inner: Arc<ReadDirInner>,
30 cookie: Option<wasi::Dircookie>,
44 inner: Arc<ReadDirInner>,
47 #[derive(Clone, Debug, Default)]
48 pub struct OpenOptions {
52 dirflags: wasi::Lookupflags,
53 fdflags: wasi::Fdflags,
55 rights_base: Option<wasi::Rights>,
56 rights_inheriting: Option<wasi::Rights>,
59 #[derive(Clone, PartialEq, Eq, Debug)]
60 pub struct FilePermissions {
64 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
70 pub struct DirBuilder {}
73 pub fn size(&self) -> u64 {
77 pub fn perm(&self) -> FilePermissions {
78 // not currently implemented in wasi yet
79 FilePermissions { readonly: false }
82 pub fn file_type(&self) -> FileType {
83 FileType { bits: self.meta.filetype }
86 pub fn modified(&self) -> io::Result<SystemTime> {
87 Ok(SystemTime::from_wasi_timestamp(self.meta.mtim))
90 pub fn accessed(&self) -> io::Result<SystemTime> {
91 Ok(SystemTime::from_wasi_timestamp(self.meta.atim))
94 pub fn created(&self) -> io::Result<SystemTime> {
95 Ok(SystemTime::from_wasi_timestamp(self.meta.ctim))
98 pub fn as_wasi(&self) -> &wasi::Filestat {
103 impl FilePermissions {
104 pub fn readonly(&self) -> bool {
108 pub fn set_readonly(&mut self, readonly: bool) {
109 self.readonly = readonly;
114 pub fn is_dir(&self) -> bool {
115 self.bits == wasi::FILETYPE_DIRECTORY
118 pub fn is_file(&self) -> bool {
119 self.bits == wasi::FILETYPE_REGULAR_FILE
122 pub fn is_symlink(&self) -> bool {
123 self.bits == wasi::FILETYPE_SYMBOLIC_LINK
126 pub fn bits(&self) -> wasi::Filetype {
131 impl fmt::Debug for ReadDir {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.debug_struct("ReadDir").finish_non_exhaustive()
137 impl Iterator for ReadDir {
138 type Item = io::Result<DirEntry>;
140 fn next(&mut self) -> Option<io::Result<DirEntry>> {
142 // If we've reached the capacity of our buffer then we need to read
143 // some more from the OS, otherwise we pick up at our old offset.
144 let offset = if self.offset == self.cap {
145 let cookie = self.cookie.take()?;
146 match self.inner.dir.fd.readdir(&mut self.buf, cookie) {
147 Ok(bytes) => self.cap = bytes,
148 Err(e) => return Some(Err(e)),
151 self.cookie = Some(cookie);
153 // If we didn't actually read anything, this is in theory the
154 // end of the directory.
164 let data = &self.buf[offset..self.cap];
166 // If we're not able to read a directory entry then that means it
167 // must have been truncated at the end of the buffer, so reset our
168 // offset so we can go back and reread into the buffer, picking up
169 // where we last left off.
170 let dirent_size = mem::size_of::<wasi::Dirent>();
171 if data.len() < dirent_size {
172 assert!(self.cookie.is_some());
173 assert!(self.buf.len() >= dirent_size);
174 self.offset = self.cap;
177 let (dirent, data) = data.split_at(dirent_size);
178 let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) };
180 // If the file name was truncated, then we need to reinvoke
181 // `readdir` so we truncate our buffer to start over and reread this
182 // descriptor. Note that if our offset is 0 that means the file name
183 // is massive and we need a bigger buffer.
184 if data.len() < dirent.d_namlen as usize {
186 let amt_to_add = self.buf.capacity();
187 self.buf.extend(iter::repeat(0).take(amt_to_add));
189 assert!(self.cookie.is_some());
190 self.offset = self.cap;
193 self.cookie = Some(dirent.d_next);
194 self.offset = offset + dirent_size + dirent.d_namlen as usize;
196 let name = &data[..(dirent.d_namlen as usize)];
198 // These names are skipped on all other platforms, so let's skip
200 if name == b"." || name == b".." {
204 return Some(Ok(DirEntry {
207 inner: self.inner.clone(),
214 pub fn path(&self) -> PathBuf {
215 let name = OsStr::from_bytes(&self.name);
216 self.inner.root.join(name)
219 pub fn file_name(&self) -> OsString {
220 OsString::from_vec(self.name.clone())
223 pub fn metadata(&self) -> io::Result<FileAttr> {
224 metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref())
227 pub fn file_type(&self) -> io::Result<FileType> {
228 Ok(FileType { bits: self.meta.d_type })
231 pub fn ino(&self) -> wasi::Inode {
237 pub fn new() -> OpenOptions {
238 let mut base = OpenOptions::default();
239 base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW;
243 pub fn read(&mut self, read: bool) {
247 pub fn write(&mut self, write: bool) {
251 pub fn truncate(&mut self, truncate: bool) {
252 self.oflag(wasi::OFLAGS_TRUNC, truncate);
255 pub fn create(&mut self, create: bool) {
256 self.oflag(wasi::OFLAGS_CREAT, create);
259 pub fn create_new(&mut self, create_new: bool) {
260 self.oflag(wasi::OFLAGS_EXCL, create_new);
261 self.oflag(wasi::OFLAGS_CREAT, create_new);
264 pub fn directory(&mut self, directory: bool) {
265 self.oflag(wasi::OFLAGS_DIRECTORY, directory);
268 fn oflag(&mut self, bit: wasi::Oflags, set: bool) {
276 pub fn append(&mut self, append: bool) {
277 self.append = append;
278 self.fdflag(wasi::FDFLAGS_APPEND, append);
281 pub fn dsync(&mut self, set: bool) {
282 self.fdflag(wasi::FDFLAGS_DSYNC, set);
285 pub fn nonblock(&mut self, set: bool) {
286 self.fdflag(wasi::FDFLAGS_NONBLOCK, set);
289 pub fn rsync(&mut self, set: bool) {
290 self.fdflag(wasi::FDFLAGS_RSYNC, set);
293 pub fn sync(&mut self, set: bool) {
294 self.fdflag(wasi::FDFLAGS_SYNC, set);
297 fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) {
301 self.fdflags &= !bit;
305 pub fn fs_rights_base(&mut self, rights: wasi::Rights) {
306 self.rights_base = Some(rights);
309 pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) {
310 self.rights_inheriting = Some(rights);
313 fn rights_base(&self) -> wasi::Rights {
314 if let Some(rights) = self.rights_base {
318 // If rights haven't otherwise been specified try to pick a reasonable
319 // set. This can always be overridden by users via extension traits, and
320 // implementations may give us fewer rights silently than we ask for. So
321 // given that, just look at `read` and `write` and bucket permissions
325 base |= wasi::RIGHTS_FD_READ;
326 base |= wasi::RIGHTS_FD_READDIR;
328 if self.write || self.append {
329 base |= wasi::RIGHTS_FD_WRITE;
330 base |= wasi::RIGHTS_FD_DATASYNC;
331 base |= wasi::RIGHTS_FD_ALLOCATE;
332 base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE;
335 // FIXME: some of these should probably be read-only or write-only...
336 base |= wasi::RIGHTS_FD_ADVISE;
337 base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS;
338 base |= wasi::RIGHTS_FD_FILESTAT_GET;
339 base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES;
340 base |= wasi::RIGHTS_FD_SEEK;
341 base |= wasi::RIGHTS_FD_SYNC;
342 base |= wasi::RIGHTS_FD_TELL;
343 base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY;
344 base |= wasi::RIGHTS_PATH_CREATE_FILE;
345 base |= wasi::RIGHTS_PATH_FILESTAT_GET;
346 base |= wasi::RIGHTS_PATH_LINK_SOURCE;
347 base |= wasi::RIGHTS_PATH_LINK_TARGET;
348 base |= wasi::RIGHTS_PATH_OPEN;
349 base |= wasi::RIGHTS_PATH_READLINK;
350 base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY;
351 base |= wasi::RIGHTS_PATH_RENAME_SOURCE;
352 base |= wasi::RIGHTS_PATH_RENAME_TARGET;
353 base |= wasi::RIGHTS_PATH_SYMLINK;
354 base |= wasi::RIGHTS_PATH_UNLINK_FILE;
355 base |= wasi::RIGHTS_POLL_FD_READWRITE;
360 fn rights_inheriting(&self) -> wasi::Rights {
361 self.rights_inheriting.unwrap_or_else(|| self.rights_base())
364 pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) {
365 self.dirflags = flags;
370 pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
371 let (dir, file) = open_parent(path)?;
372 open_at(&dir, &file, opts)
375 pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
376 open_at(&self.fd, path, opts)
379 pub fn file_attr(&self) -> io::Result<FileAttr> {
380 self.fd.filestat_get().map(|meta| FileAttr { meta })
383 pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> {
384 metadata_at(&self.fd, flags, path)
387 pub fn fsync(&self) -> io::Result<()> {
391 pub fn datasync(&self) -> io::Result<()> {
395 pub fn truncate(&self, size: u64) -> io::Result<()> {
396 self.fd.filestat_set_size(size)
399 pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
400 self.read_vectored(&mut [IoSliceMut::new(buf)])
403 pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
408 pub fn is_read_vectored(&self) -> bool {
412 pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
413 self.write_vectored(&[IoSlice::new(buf)])
416 pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
421 pub fn is_write_vectored(&self) -> bool {
425 pub fn flush(&self) -> io::Result<()> {
429 pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
433 pub fn duplicate(&self) -> io::Result<File> {
434 // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
438 pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
439 // Permissions haven't been fully figured out in wasi yet, so this is
444 pub fn fd(&self) -> &WasiFd {
448 pub fn into_fd(self) -> WasiFd {
452 pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
453 read_link(&self.fd, file)
457 impl FromInner<u32> for File {
458 fn from_inner(fd: u32) -> File {
459 unsafe { File { fd: WasiFd::from_raw(fd) } }
464 pub fn new() -> DirBuilder {
468 pub fn mkdir(&self, p: &Path) -> io::Result<()> {
469 let (dir, file) = open_parent(p)?;
470 dir.create_directory(osstr2str(file.as_ref())?)
474 impl fmt::Debug for File {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 f.debug_struct("File").field("fd", &self.fd.as_raw()).finish()
480 pub fn readdir(p: &Path) -> io::Result<ReadDir> {
481 let mut opts = OpenOptions::new();
482 opts.directory(true);
484 let dir = File::open(p, &opts)?;
490 inner: Arc::new(ReadDirInner { dir, root: p.to_path_buf() }),
494 pub fn unlink(p: &Path) -> io::Result<()> {
495 let (dir, file) = open_parent(p)?;
496 dir.unlink_file(osstr2str(file.as_ref())?)
499 pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
500 let (old, old_file) = open_parent(old)?;
501 let (new, new_file) = open_parent(new)?;
502 old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?)
505 pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
506 // Permissions haven't been fully figured out in wasi yet, so this is
511 pub fn rmdir(p: &Path) -> io::Result<()> {
512 let (dir, file) = open_parent(p)?;
513 dir.remove_directory(osstr2str(file.as_ref())?)
516 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
517 let (dir, file) = open_parent(p)?;
518 read_link(&dir, &file)
521 fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> {
522 // Try to get a best effort initial capacity for the vector we're going to
523 // fill. Note that if it's not a symlink we don't use a file to avoid
524 // allocating gigabytes if you read_link a huge movie file by accident.
525 // Additionally we add 1 to the initial size so if it doesn't change until
526 // when we call `readlink` the returned length will be less than the
527 // capacity, guaranteeing that we got all the data.
528 let meta = metadata_at(fd, 0, file)?;
529 let initial_size = if meta.file_type().is_symlink() {
530 (meta.size() as usize).saturating_add(1)
532 1 // this'll fail in just a moment
535 // Now that we have an initial guess of how big to make our buffer, call
536 // `readlink` in a loop until it fails or reports it filled fewer bytes than
537 // we asked for, indicating we got everything.
538 let file = osstr2str(file.as_ref())?;
539 let mut destination = vec![0u8; initial_size];
541 let len = fd.readlink(file, &mut destination)?;
542 if len < destination.len() {
543 destination.truncate(len);
544 destination.shrink_to_fit();
545 return Ok(PathBuf::from(OsString::from_vec(destination)));
547 let amt_to_add = destination.len();
548 destination.extend(iter::repeat(0).take(amt_to_add));
552 pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
553 let (link, link_file) = open_parent(link)?;
554 link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?)
557 pub fn link(original: &Path, link: &Path) -> io::Result<()> {
558 let (original, original_file) = open_parent(original)?;
559 let (link, link_file) = open_parent(link)?;
560 // Pass 0 as the flags argument, meaning don't follow symlinks.
561 original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?)
564 pub fn stat(p: &Path) -> io::Result<FileAttr> {
565 let (dir, file) = open_parent(p)?;
566 metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file)
569 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
570 let (dir, file) = open_parent(p)?;
571 metadata_at(&dir, 0, &file)
574 fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> {
575 let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?;
576 Ok(FileAttr { meta })
579 pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
580 // This seems to not be in wasi's API yet, and we may need to end up
581 // emulating it ourselves. For now just return an error.
585 fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
588 osstr2str(path.as_ref())?,
591 opts.rights_inheriting(),
597 /// Attempts to open a bare path `p`.
599 /// WASI has no fundamental capability to do this. All syscalls and operations
600 /// are relative to already-open file descriptors. The C library, however,
601 /// manages a map of pre-opened file descriptors to their path, and then the C
602 /// library provides an API to look at this. In other words, when you want to
603 /// open a path `p`, you have to find a previously opened file descriptor in a
604 /// global table and then see if `p` is relative to that file descriptor.
606 /// This function, if successful, will return two items:
608 /// * The first is a `ManuallyDrop<WasiFd>`. This represents a pre-opened file
609 /// descriptor which we don't have ownership of, but we can use. You shouldn't
610 /// actually drop the `fd`.
612 /// * The second is a path that should be a part of `p` and represents a
613 /// relative traversal from the file descriptor specified to the desired
616 /// If successful you can use the returned file descriptor to perform
617 /// file-descriptor-relative operations on the path returned as well. The
618 /// `rights` argument indicates what operations are desired on the returned file
619 /// descriptor, and if successful the returned file descriptor should have the
620 /// appropriate rights for performing `rights` actions.
622 /// Note that this can fail if `p` doesn't look like it can be opened relative
623 /// to any pre-opened file descriptor.
624 fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> {
625 let p = CString::new(p.as_os_str().as_bytes())?;
626 let mut buf = Vec::<u8>::with_capacity(512);
629 let mut relative_path = buf.as_ptr().cast();
630 let mut abs_prefix = ptr::null();
631 let fd = __wasilibc_find_relpath(
638 if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) {
639 // Trigger the internal buffer resizing logic of `Vec` by requiring
640 // more space than the current capacity.
641 let cap = buf.capacity();
647 "failed to find a pre-opened file descriptor \
648 through which {:?} could be opened",
651 return Err(io::Error::new(io::ErrorKind::Uncategorized, msg));
653 let relative = CStr::from_ptr(relative_path).to_bytes().to_vec();
656 ManuallyDrop::new(WasiFd::from_raw(fd as u32)),
657 PathBuf::from(OsString::from_vec(relative)),
663 pub fn __wasilibc_find_relpath(
664 path: *const libc::c_char,
665 abs_prefix: *mut *const libc::c_char,
666 relative_path: *mut *const libc::c_char,
667 relative_path_len: libc::size_t,
672 pub fn osstr2str(f: &OsStr) -> io::Result<&str> {
674 .ok_or_else(|| io::Error::new_const(io::ErrorKind::Uncategorized, &"input must be utf-8"))
677 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
680 let mut reader = File::open(from)?;
681 let mut writer = File::create(to)?;
683 io::copy(&mut reader, &mut writer)