1 use crate::ffi::{CStr, CString, OsStr, OsString};
3 use crate::io::{self, IoSlice, IoSliceMut, SeekFrom};
5 use crate::mem::{self, ManuallyDrop};
6 use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
7 use crate::path::{Path, PathBuf};
10 use crate::sys::fd::{DirCookie, WasiFd};
11 use crate::sys::time::SystemTime;
12 use crate::sys::unsupported;
13 use crate::sys_common::FromInner;
15 pub use crate::sys_common::fs::copy;
16 pub use crate::sys_common::fs::remove_dir_all;
24 meta: libc::__wasi_filestat_t,
28 inner: Arc<ReadDirInner>,
29 cookie: Option<DirCookie>,
41 meta: libc::__wasi_dirent_t,
43 inner: Arc<ReadDirInner>,
46 #[derive(Clone, Debug, Default)]
47 pub struct OpenOptions {
50 dirflags: libc::__wasi_lookupflags_t,
51 fdflags: libc::__wasi_fdflags_t,
52 oflags: libc::__wasi_oflags_t,
53 rights_base: Option<libc::__wasi_rights_t>,
54 rights_inheriting: Option<libc::__wasi_rights_t>,
57 #[derive(Clone, PartialEq, Eq, Debug)]
58 pub struct FilePermissions {
62 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
64 bits: libc::__wasi_filetype_t,
68 pub struct DirBuilder {}
71 fn zero() -> FileAttr {
73 meta: unsafe { mem::zeroed() },
77 pub fn size(&self) -> u64 {
81 pub fn perm(&self) -> FilePermissions {
82 // not currently implemented in wasi yet
83 FilePermissions { readonly: false }
86 pub fn file_type(&self) -> FileType {
88 bits: self.meta.st_filetype,
92 pub fn modified(&self) -> io::Result<SystemTime> {
93 Ok(SystemTime::from_wasi_timestamp(self.meta.st_mtim))
96 pub fn accessed(&self) -> io::Result<SystemTime> {
97 Ok(SystemTime::from_wasi_timestamp(self.meta.st_atim))
100 pub fn created(&self) -> io::Result<SystemTime> {
101 Ok(SystemTime::from_wasi_timestamp(self.meta.st_ctim))
104 pub fn as_wasi(&self) -> &libc::__wasi_filestat_t {
109 impl FilePermissions {
110 pub fn readonly(&self) -> bool {
114 pub fn set_readonly(&mut self, readonly: bool) {
115 self.readonly = readonly;
120 pub fn is_dir(&self) -> bool {
121 self.bits == libc::__WASI_FILETYPE_DIRECTORY
124 pub fn is_file(&self) -> bool {
125 self.bits == libc::__WASI_FILETYPE_REGULAR_FILE
128 pub fn is_symlink(&self) -> bool {
129 self.bits == libc::__WASI_FILETYPE_SYMBOLIC_LINK
132 pub fn bits(&self) -> libc::__wasi_filetype_t {
137 impl fmt::Debug for ReadDir {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.debug_struct("ReadDir").finish()
143 impl Iterator for ReadDir {
144 type Item = io::Result<DirEntry>;
146 fn next(&mut self) -> Option<io::Result<DirEntry>> {
148 // If we've reached the capacity of our buffer then we need to read
149 // some more from the OS, otherwise we pick up at our old offset.
150 let offset = if self.offset == self.cap {
151 let cookie = self.cookie.take()?;
152 match self.inner.dir.fd.readdir(&mut self.buf, cookie) {
153 Ok(bytes) => self.cap = bytes,
154 Err(e) => return Some(Err(e)),
157 self.cookie = Some(cookie);
159 // If we didn't actually read anything, this is in theory the
160 // end of the directory.
170 let data = &self.buf[offset..self.cap];
172 // If we're not able to read a directory entry then that means it
173 // must have been truncated at the end of the buffer, so reset our
174 // offset so we can go back and reread into the buffer, picking up
175 // where we last left off.
176 let dirent_size = mem::size_of::<libc::__wasi_dirent_t>();
177 if data.len() < dirent_size {
178 assert!(self.cookie.is_some());
179 assert!(self.buf.len() >= dirent_size);
180 self.offset = self.cap;
183 let (dirent, data) = data.split_at(dirent_size);
185 unsafe { ptr::read_unaligned(dirent.as_ptr() as *const libc::__wasi_dirent_t) };
187 // If the file name was truncated, then we need to reinvoke
188 // `readdir` so we truncate our buffer to start over and reread this
189 // descriptor. Note that if our offset is 0 that means the file name
190 // is massive and we need a bigger buffer.
191 if data.len() < dirent.d_namlen as usize {
193 let amt_to_add = self.buf.capacity();
194 self.buf.extend(iter::repeat(0).take(amt_to_add));
196 assert!(self.cookie.is_some());
197 self.offset = self.cap;
200 self.cookie = Some(dirent.d_next);
201 self.offset = offset + dirent_size + dirent.d_namlen as usize;
203 let name = &data[..(dirent.d_namlen as usize)];
205 // These names are skipped on all other platforms, so let's skip
207 if name == b"." || name == b".." {
211 return Some(Ok(DirEntry {
214 inner: self.inner.clone(),
221 pub fn path(&self) -> PathBuf {
222 let name = OsStr::from_bytes(&self.name);
223 self.inner.root.join(name)
226 pub fn file_name(&self) -> OsString {
227 OsString::from_vec(self.name.clone())
230 pub fn metadata(&self) -> io::Result<FileAttr> {
234 OsStr::from_bytes(&self.name).as_ref(),
238 pub fn file_type(&self) -> io::Result<FileType> {
240 bits: self.meta.d_type,
244 pub fn ino(&self) -> libc::__wasi_inode_t {
250 pub fn new() -> OpenOptions {
251 let mut base = OpenOptions::default();
252 base.dirflags = libc::__WASI_LOOKUP_SYMLINK_FOLLOW;
256 pub fn read(&mut self, read: bool) {
260 pub fn write(&mut self, write: bool) {
264 pub fn truncate(&mut self, truncate: bool) {
265 self.oflag(libc::__WASI_O_TRUNC, truncate);
268 pub fn create(&mut self, create: bool) {
269 self.oflag(libc::__WASI_O_CREAT, create);
272 pub fn create_new(&mut self, create_new: bool) {
273 self.oflag(libc::__WASI_O_EXCL, create_new);
274 self.oflag(libc::__WASI_O_CREAT, create_new);
277 pub fn directory(&mut self, directory: bool) {
278 self.oflag(libc::__WASI_O_DIRECTORY, directory);
281 fn oflag(&mut self, bit: libc::__wasi_oflags_t, set: bool) {
289 pub fn append(&mut self, set: bool) {
290 self.fdflag(libc::__WASI_FDFLAG_APPEND, set);
293 pub fn dsync(&mut self, set: bool) {
294 self.fdflag(libc::__WASI_FDFLAG_DSYNC, set);
297 pub fn nonblock(&mut self, set: bool) {
298 self.fdflag(libc::__WASI_FDFLAG_NONBLOCK, set);
301 pub fn rsync(&mut self, set: bool) {
302 self.fdflag(libc::__WASI_FDFLAG_RSYNC, set);
305 pub fn sync(&mut self, set: bool) {
306 self.fdflag(libc::__WASI_FDFLAG_SYNC, set);
309 fn fdflag(&mut self, bit: libc::__wasi_fdflags_t, set: bool) {
313 self.fdflags &= !bit;
317 pub fn fs_rights_base(&mut self, rights: libc::__wasi_rights_t) {
318 self.rights_base = Some(rights);
321 pub fn fs_rights_inheriting(&mut self, rights: libc::__wasi_rights_t) {
322 self.rights_inheriting = Some(rights);
325 fn rights_base(&self) -> libc::__wasi_rights_t {
326 if let Some(rights) = self.rights_base {
330 // If rights haven't otherwise been specified try to pick a reasonable
331 // set. This can always be overridden by users via extension traits, and
332 // implementations may give us fewer rights silently than we ask for. So
333 // given that, just look at `read` and `write` and bucket permissions
337 base |= libc::__WASI_RIGHT_FD_READ;
338 base |= libc::__WASI_RIGHT_FD_READDIR;
341 base |= libc::__WASI_RIGHT_FD_WRITE;
342 base |= libc::__WASI_RIGHT_FD_DATASYNC;
343 base |= libc::__WASI_RIGHT_FD_ALLOCATE;
344 base |= libc::__WASI_RIGHT_FD_FILESTAT_SET_SIZE;
347 // FIXME: some of these should probably be read-only or write-only...
348 base |= libc::__WASI_RIGHT_FD_ADVISE;
349 base |= libc::__WASI_RIGHT_FD_FDSTAT_SET_FLAGS;
350 base |= libc::__WASI_RIGHT_FD_FILESTAT_SET_TIMES;
351 base |= libc::__WASI_RIGHT_FD_SEEK;
352 base |= libc::__WASI_RIGHT_FD_SYNC;
353 base |= libc::__WASI_RIGHT_FD_TELL;
354 base |= libc::__WASI_RIGHT_PATH_CREATE_DIRECTORY;
355 base |= libc::__WASI_RIGHT_PATH_CREATE_FILE;
356 base |= libc::__WASI_RIGHT_PATH_FILESTAT_GET;
357 base |= libc::__WASI_RIGHT_PATH_LINK_SOURCE;
358 base |= libc::__WASI_RIGHT_PATH_LINK_TARGET;
359 base |= libc::__WASI_RIGHT_PATH_OPEN;
360 base |= libc::__WASI_RIGHT_PATH_READLINK;
361 base |= libc::__WASI_RIGHT_PATH_REMOVE_DIRECTORY;
362 base |= libc::__WASI_RIGHT_PATH_RENAME_SOURCE;
363 base |= libc::__WASI_RIGHT_PATH_RENAME_TARGET;
364 base |= libc::__WASI_RIGHT_PATH_SYMLINK;
365 base |= libc::__WASI_RIGHT_PATH_UNLINK_FILE;
366 base |= libc::__WASI_RIGHT_POLL_FD_READWRITE;
371 fn rights_inheriting(&self) -> libc::__wasi_rights_t {
372 self.rights_inheriting.unwrap_or_else(|| self.rights_base())
375 pub fn lookup_flags(&mut self, flags: libc::__wasi_lookupflags_t) {
376 self.dirflags = flags;
381 pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
382 let (dir, file) = open_parent(path, libc::__WASI_RIGHT_PATH_OPEN)?;
383 open_at(&dir, &file, opts)
386 pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
387 open_at(&self.fd, path, opts)
390 pub fn file_attr(&self) -> io::Result<FileAttr> {
391 let mut ret = FileAttr::zero();
392 self.fd.filestat_get(&mut ret.meta)?;
398 flags: libc::__wasi_lookupflags_t,
400 ) -> io::Result<FileAttr> {
401 metadata_at(&self.fd, flags, path)
404 pub fn fsync(&self) -> io::Result<()> {
408 pub fn datasync(&self) -> io::Result<()> {
412 pub fn truncate(&self, size: u64) -> io::Result<()> {
413 self.fd.filestat_set_size(size)
416 pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
417 self.read_vectored(&mut [IoSliceMut::new(buf)])
420 pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
424 pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
425 self.write_vectored(&[IoSlice::new(buf)])
428 pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
432 pub fn flush(&self) -> io::Result<()> {
436 pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
440 pub fn duplicate(&self) -> io::Result<File> {
441 // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
445 pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
446 // Permissions haven't been fully figured out in wasi yet, so this is
451 pub fn fd(&self) -> &WasiFd {
455 pub fn into_fd(self) -> WasiFd {
459 pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
460 read_link(&self.fd, file)
464 impl FromInner<u32> for File {
465 fn from_inner(fd: u32) -> File {
468 fd: WasiFd::from_raw(fd),
475 pub fn new() -> DirBuilder {
479 pub fn mkdir(&self, p: &Path) -> io::Result<()> {
480 let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_CREATE_DIRECTORY)?;
481 dir.create_directory(file.as_os_str().as_bytes())
485 impl fmt::Debug for File {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 f.debug_struct("File")
488 .field("fd", &self.fd.as_raw())
493 pub fn readdir(p: &Path) -> io::Result<ReadDir> {
494 let mut opts = OpenOptions::new();
495 opts.directory(true);
497 let dir = File::open(p, &opts)?;
503 inner: Arc::new(ReadDirInner {
505 root: p.to_path_buf(),
510 pub fn unlink(p: &Path) -> io::Result<()> {
511 let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_UNLINK_FILE)?;
512 dir.unlink_file(file.as_os_str().as_bytes())
515 pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
516 let (old, old_file) = open_parent(old, libc::__WASI_RIGHT_PATH_RENAME_SOURCE)?;
517 let (new, new_file) = open_parent(new, libc::__WASI_RIGHT_PATH_RENAME_TARGET)?;
519 old_file.as_os_str().as_bytes(),
521 new_file.as_os_str().as_bytes(),
525 pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
526 // Permissions haven't been fully figured out in wasi yet, so this is
531 pub fn rmdir(p: &Path) -> io::Result<()> {
532 let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_REMOVE_DIRECTORY)?;
533 dir.remove_directory(file.as_os_str().as_bytes())
536 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
537 let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_READLINK)?;
538 read_link(&dir, &file)
541 fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> {
542 // Try to get a best effort initial capacity for the vector we're going to
543 // fill. Note that if it's not a symlink we don't use a file to avoid
544 // allocating gigabytes if you read_link a huge movie file by accident.
545 // Additionally we add 1 to the initial size so if it doesn't change until
546 // when we call `readlink` the returned length will be less than the
547 // capacity, guaranteeing that we got all the data.
548 let meta = metadata_at(fd, 0, file)?;
549 let initial_size = if meta.file_type().is_symlink() {
550 (meta.size() as usize).saturating_add(1)
552 1 // this'll fail in just a moment
555 // Now that we have an initial guess of how big to make our buffer, call
556 // `readlink` in a loop until it fails or reports it filled fewer bytes than
557 // we asked for, indicating we got everything.
558 let file = file.as_os_str().as_bytes();
559 let mut destination = vec![0u8; initial_size];
561 let len = fd.readlink(file, &mut destination)?;
562 if len < destination.len() {
563 destination.truncate(len);
564 destination.shrink_to_fit();
565 return Ok(PathBuf::from(OsString::from_vec(destination)));
567 let amt_to_add = destination.len();
568 destination.extend(iter::repeat(0).take(amt_to_add));
572 pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
573 let (dst, dst_file) = open_parent(dst, libc::__WASI_RIGHT_PATH_SYMLINK)?;
574 dst.symlink(src.as_os_str().as_bytes(), dst_file.as_os_str().as_bytes())
577 pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
578 let (src, src_file) = open_parent(src, libc::__WASI_RIGHT_PATH_LINK_SOURCE)?;
579 let (dst, dst_file) = open_parent(dst, libc::__WASI_RIGHT_PATH_LINK_TARGET)?;
581 libc::__WASI_LOOKUP_SYMLINK_FOLLOW,
582 src_file.as_os_str().as_bytes(),
584 dst_file.as_os_str().as_bytes(),
588 pub fn stat(p: &Path) -> io::Result<FileAttr> {
589 let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_FILESTAT_GET)?;
590 metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, &file)
593 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
594 let (dir, file) = open_parent(p, libc::__WASI_RIGHT_PATH_FILESTAT_GET)?;
595 metadata_at(&dir, 0, &file)
600 flags: libc::__wasi_lookupflags_t,
602 ) -> io::Result<FileAttr> {
603 let mut ret = FileAttr::zero();
604 fd.path_filestat_get(flags, path.as_os_str().as_bytes(), &mut ret.meta)?;
608 pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
609 // This seems to not be in wasi's API yet, and we may need to end up
610 // emulating it ourselves. For now just return an error.
614 fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
617 path.as_os_str().as_bytes(),
620 opts.rights_inheriting(),
626 /// Attempts to open a bare path `p`.
628 /// WASI has no fundamental capability to do this. All syscalls and operations
629 /// are relative to already-open file descriptors. The C library, however,
630 /// manages a map of preopened file descriptors to their path, and then the C
631 /// library provides an API to look at this. In other words, when you want to
632 /// open a path `p`, you have to find a previously opened file descriptor in a
633 /// global table and then see if `p` is relative to that file descriptor.
635 /// This function, if successful, will return two items:
637 /// * The first is a `ManuallyDrop<WasiFd>`. This represents a preopened file
638 /// descriptor which we don't have ownership of, but we can use. You shouldn't
639 /// actually drop the `fd`.
641 /// * The second is a path that should be a part of `p` and represents a
642 /// relative traversal from the file descriptor specified to the desired
645 /// If successful you can use the returned file descriptor to perform
646 /// file-descriptor-relative operations on the path returned as well. The
647 /// `rights` argument indicates what operations are desired on the returned file
648 /// descriptor, and if successful the returned file descriptor should have the
649 /// appropriate rights for performing `rights` actions.
651 /// Note that this can fail if `p` doesn't look like it can be opened relative
652 /// to any preopened file descriptor.
655 rights: libc::__wasi_rights_t,
656 ) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> {
657 let p = CString::new(p.as_os_str().as_bytes())?;
659 let mut ret = ptr::null();
660 let fd = __wasilibc_find_relpath(p.as_ptr(), rights, 0, &mut ret);
663 "failed to find a preopened file descriptor \
664 through which {:?} could be opened",
667 return Err(io::Error::new(io::ErrorKind::Other, msg));
669 let path = Path::new(OsStr::from_bytes(CStr::from_ptr(ret).to_bytes()));
671 // FIXME: right now `path` is a pointer into `p`, the `CString` above.
672 // When we return `p` is deallocated and we can't use it, so we need to
673 // currently separately allocate `path`. If this becomes an issue though
674 // we should probably turn this into a closure-taking interface or take
675 // `&CString` and then pass off `&Path` tied to the same lifetime.
676 let path = path.to_path_buf();
678 return Ok((ManuallyDrop::new(WasiFd::from_raw(fd as u32)), path));
681 // FIXME(rust-lang/libc#1314) use the `libc` crate for this when the API
682 // there is published
684 pub fn __wasilibc_find_relpath(
685 path: *const libc::c_char,
686 rights_base: libc::__wasi_rights_t,
687 rights_inheriting: libc::__wasi_rights_t,
688 relative_path: *mut *const libc::c_char,