diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /library/std/src/sys/wasi/fs.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/std/src/sys/wasi/fs.rs')
-rw-r--r-- | library/std/src/sys/wasi/fs.rs | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/library/std/src/sys/wasi/fs.rs b/library/std/src/sys/wasi/fs.rs new file mode 100644 index 000000000..6614ae397 --- /dev/null +++ b/library/std/src/sys/wasi/fs.rs @@ -0,0 +1,798 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use super::fd::WasiFd; +use crate::ffi::{CStr, CString, OsStr, OsString}; +use crate::fmt; +use crate::io::{self, IoSlice, IoSliceMut, ReadBuf, SeekFrom}; +use crate::iter; +use crate::mem::{self, ManuallyDrop}; +use crate::os::raw::c_int; +use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::{Path, PathBuf}; +use crate::ptr; +use crate::sync::Arc; +use crate::sys::time::SystemTime; +use crate::sys::unsupported; +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +pub use crate::sys_common::fs::try_exists; + +pub struct File { + fd: WasiFd, +} + +#[derive(Clone)] +pub struct FileAttr { + meta: wasi::Filestat, +} + +pub struct ReadDir { + inner: Arc<ReadDirInner>, + cookie: Option<wasi::Dircookie>, + buf: Vec<u8>, + offset: usize, + cap: usize, +} + +struct ReadDirInner { + root: PathBuf, + dir: File, +} + +pub struct DirEntry { + meta: wasi::Dirent, + name: Vec<u8>, + inner: Arc<ReadDirInner>, +} + +#[derive(Clone, Debug, Default)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + dirflags: wasi::Lookupflags, + fdflags: wasi::Fdflags, + oflags: wasi::Oflags, + rights_base: Option<wasi::Rights>, + rights_inheriting: Option<wasi::Rights>, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + readonly: bool, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct FileTimes { + accessed: Option<wasi::Timestamp>, + modified: Option<wasi::Timestamp>, +} + +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +pub struct FileType { + bits: wasi::Filetype, +} + +#[derive(Debug)] +pub struct DirBuilder {} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.meta.size + } + + pub fn perm(&self) -> FilePermissions { + // not currently implemented in wasi yet + FilePermissions { readonly: false } + } + + pub fn file_type(&self) -> FileType { + FileType { bits: self.meta.filetype } + } + + pub fn modified(&self) -> io::Result<SystemTime> { + Ok(SystemTime::from_wasi_timestamp(self.meta.mtim)) + } + + pub fn accessed(&self) -> io::Result<SystemTime> { + Ok(SystemTime::from_wasi_timestamp(self.meta.atim)) + } + + pub fn created(&self) -> io::Result<SystemTime> { + Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) + } + + pub fn as_wasi(&self) -> &wasi::Filestat { + &self.meta + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + self.readonly + } + + pub fn set_readonly(&mut self, readonly: bool) { + self.readonly = readonly; + } +} + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.accessed = Some(t.to_wasi_timestamp_or_panic()); + } + + pub fn set_modified(&mut self, t: SystemTime) { + self.modified = Some(t.to_wasi_timestamp_or_panic()); + } +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.bits == wasi::FILETYPE_DIRECTORY + } + + pub fn is_file(&self) -> bool { + self.bits == wasi::FILETYPE_REGULAR_FILE + } + + pub fn is_symlink(&self) -> bool { + self.bits == wasi::FILETYPE_SYMBOLIC_LINK + } + + pub fn bits(&self) -> wasi::Filetype { + self.bits + } +} + +impl ReadDir { + fn new(dir: File, root: PathBuf) -> ReadDir { + ReadDir { + cookie: Some(0), + buf: vec![0; 128], + offset: 0, + cap: 0, + inner: Arc::new(ReadDirInner { dir, root }), + } + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReadDir").finish_non_exhaustive() + } +} + +impl Iterator for ReadDir { + type Item = io::Result<DirEntry>; + + fn next(&mut self) -> Option<io::Result<DirEntry>> { + loop { + // If we've reached the capacity of our buffer then we need to read + // some more from the OS, otherwise we pick up at our old offset. + let offset = if self.offset == self.cap { + let cookie = self.cookie.take()?; + match self.inner.dir.fd.readdir(&mut self.buf, cookie) { + Ok(bytes) => self.cap = bytes, + Err(e) => return Some(Err(e)), + } + self.offset = 0; + self.cookie = Some(cookie); + + // If we didn't actually read anything, this is in theory the + // end of the directory. + if self.cap == 0 { + self.cookie = None; + return None; + } + + 0 + } else { + self.offset + }; + let data = &self.buf[offset..self.cap]; + + // If we're not able to read a directory entry then that means it + // must have been truncated at the end of the buffer, so reset our + // offset so we can go back and reread into the buffer, picking up + // where we last left off. + let dirent_size = mem::size_of::<wasi::Dirent>(); + if data.len() < dirent_size { + assert!(self.cookie.is_some()); + assert!(self.buf.len() >= dirent_size); + self.offset = self.cap; + continue; + } + let (dirent, data) = data.split_at(dirent_size); + let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; + + // If the file name was truncated, then we need to reinvoke + // `readdir` so we truncate our buffer to start over and reread this + // descriptor. Note that if our offset is 0 that means the file name + // is massive and we need a bigger buffer. + if data.len() < dirent.d_namlen as usize { + if offset == 0 { + let amt_to_add = self.buf.capacity(); + self.buf.extend(iter::repeat(0).take(amt_to_add)); + } + assert!(self.cookie.is_some()); + self.offset = self.cap; + continue; + } + self.cookie = Some(dirent.d_next); + self.offset = offset + dirent_size + dirent.d_namlen as usize; + + let name = &data[..(dirent.d_namlen as usize)]; + + // These names are skipped on all other platforms, so let's skip + // them here too + if name == b"." || name == b".." { + continue; + } + + return Some(Ok(DirEntry { + meta: dirent, + name: name.to_vec(), + inner: self.inner.clone(), + })); + } + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + let name = OsStr::from_bytes(&self.name); + self.inner.root.join(name) + } + + pub fn file_name(&self) -> OsString { + OsString::from_vec(self.name.clone()) + } + + pub fn metadata(&self) -> io::Result<FileAttr> { + metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) + } + + pub fn file_type(&self) -> io::Result<FileType> { + Ok(FileType { bits: self.meta.d_type }) + } + + pub fn ino(&self) -> wasi::Inode { + self.meta.d_ino + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + let mut base = OpenOptions::default(); + base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW; + return base; + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + + pub fn write(&mut self, write: bool) { + self.write = write; + } + + pub fn truncate(&mut self, truncate: bool) { + self.oflag(wasi::OFLAGS_TRUNC, truncate); + } + + pub fn create(&mut self, create: bool) { + self.oflag(wasi::OFLAGS_CREAT, create); + } + + pub fn create_new(&mut self, create_new: bool) { + self.oflag(wasi::OFLAGS_EXCL, create_new); + self.oflag(wasi::OFLAGS_CREAT, create_new); + } + + pub fn directory(&mut self, directory: bool) { + self.oflag(wasi::OFLAGS_DIRECTORY, directory); + } + + fn oflag(&mut self, bit: wasi::Oflags, set: bool) { + if set { + self.oflags |= bit; + } else { + self.oflags &= !bit; + } + } + + pub fn append(&mut self, append: bool) { + self.append = append; + self.fdflag(wasi::FDFLAGS_APPEND, append); + } + + pub fn dsync(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_DSYNC, set); + } + + pub fn nonblock(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_NONBLOCK, set); + } + + pub fn rsync(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_RSYNC, set); + } + + pub fn sync(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_SYNC, set); + } + + fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) { + if set { + self.fdflags |= bit; + } else { + self.fdflags &= !bit; + } + } + + pub fn fs_rights_base(&mut self, rights: wasi::Rights) { + self.rights_base = Some(rights); + } + + pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) { + self.rights_inheriting = Some(rights); + } + + fn rights_base(&self) -> wasi::Rights { + if let Some(rights) = self.rights_base { + return rights; + } + + // If rights haven't otherwise been specified try to pick a reasonable + // set. This can always be overridden by users via extension traits, and + // implementations may give us fewer rights silently than we ask for. So + // given that, just look at `read` and `write` and bucket permissions + // based on that. + let mut base = 0; + if self.read { + base |= wasi::RIGHTS_FD_READ; + base |= wasi::RIGHTS_FD_READDIR; + } + if self.write || self.append { + base |= wasi::RIGHTS_FD_WRITE; + base |= wasi::RIGHTS_FD_DATASYNC; + base |= wasi::RIGHTS_FD_ALLOCATE; + base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE; + } + + // FIXME: some of these should probably be read-only or write-only... + base |= wasi::RIGHTS_FD_ADVISE; + base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS; + base |= wasi::RIGHTS_FD_FILESTAT_GET; + base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES; + base |= wasi::RIGHTS_FD_SEEK; + base |= wasi::RIGHTS_FD_SYNC; + base |= wasi::RIGHTS_FD_TELL; + base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY; + base |= wasi::RIGHTS_PATH_CREATE_FILE; + base |= wasi::RIGHTS_PATH_FILESTAT_GET; + base |= wasi::RIGHTS_PATH_LINK_SOURCE; + base |= wasi::RIGHTS_PATH_LINK_TARGET; + base |= wasi::RIGHTS_PATH_OPEN; + base |= wasi::RIGHTS_PATH_READLINK; + base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY; + base |= wasi::RIGHTS_PATH_RENAME_SOURCE; + base |= wasi::RIGHTS_PATH_RENAME_TARGET; + base |= wasi::RIGHTS_PATH_SYMLINK; + base |= wasi::RIGHTS_PATH_UNLINK_FILE; + base |= wasi::RIGHTS_POLL_FD_READWRITE; + + return base; + } + + fn rights_inheriting(&self) -> wasi::Rights { + self.rights_inheriting.unwrap_or_else(|| self.rights_base()) + } + + pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) { + self.dirflags = flags; + } +} + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { + let (dir, file) = open_parent(path)?; + open_at(&dir, &file, opts) + } + + pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> { + open_at(&self.fd, path, opts) + } + + pub fn file_attr(&self) -> io::Result<FileAttr> { + self.fd.filestat_get().map(|meta| FileAttr { meta }) + } + + pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { + metadata_at(&self.fd, flags, path) + } + + pub fn fsync(&self) -> io::Result<()> { + self.fd.sync() + } + + pub fn datasync(&self) -> io::Result<()> { + self.fd.datasync() + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + self.fd.filestat_set_size(size) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + self.read_vectored(&mut [IoSliceMut::new(buf)]) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + self.fd.read(bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + self.write_vectored(&[IoSlice::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + self.fd.write(bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn flush(&self) -> io::Result<()> { + Ok(()) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { + self.fd.seek(pos) + } + + pub fn duplicate(&self) -> io::Result<File> { + // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup + unsupported() + } + + pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { + // Permissions haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() + } + + pub fn set_times(&self, times: FileTimes) -> io::Result<()> { + self.fd.filestat_set_times( + times.accessed.unwrap_or(0), + times.modified.unwrap_or(0), + times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) + | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), + ) + } + + pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> { + read_link(&self.fd, file) + } +} + +impl AsInner<WasiFd> for File { + fn as_inner(&self) -> &WasiFd { + &self.fd + } +} + +impl IntoInner<WasiFd> for File { + fn into_inner(self) -> WasiFd { + self.fd + } +} + +impl FromInner<WasiFd> for File { + fn from_inner(fd: WasiFd) -> File { + File { fd } + } +} + +impl AsFd for File { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for File { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for File { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + +impl FromRawFd for File { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } } + } +} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder {} + } + + pub fn mkdir(&self, p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.create_directory(osstr2str(file.as_ref())?) + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("File").field("fd", &self.as_raw_fd()).finish() + } +} + +pub fn readdir(p: &Path) -> io::Result<ReadDir> { + let mut opts = OpenOptions::new(); + opts.directory(true); + opts.read(true); + let dir = File::open(p, &opts)?; + Ok(ReadDir::new(dir, p.to_path_buf())) +} + +pub fn unlink(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.unlink_file(osstr2str(file.as_ref())?) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + let (old, old_file) = open_parent(old)?; + let (new, new_file) = open_parent(new)?; + old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?) +} + +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + // Permissions haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() +} + +pub fn rmdir(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.remove_directory(osstr2str(file.as_ref())?) +} + +pub fn readlink(p: &Path) -> io::Result<PathBuf> { + let (dir, file) = open_parent(p)?; + read_link(&dir, &file) +} + +fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> { + // Try to get a best effort initial capacity for the vector we're going to + // fill. Note that if it's not a symlink we don't use a file to avoid + // allocating gigabytes if you read_link a huge movie file by accident. + // Additionally we add 1 to the initial size so if it doesn't change until + // when we call `readlink` the returned length will be less than the + // capacity, guaranteeing that we got all the data. + let meta = metadata_at(fd, 0, file)?; + let initial_size = if meta.file_type().is_symlink() { + (meta.size() as usize).saturating_add(1) + } else { + 1 // this'll fail in just a moment + }; + + // Now that we have an initial guess of how big to make our buffer, call + // `readlink` in a loop until it fails or reports it filled fewer bytes than + // we asked for, indicating we got everything. + let file = osstr2str(file.as_ref())?; + let mut destination = vec![0u8; initial_size]; + loop { + let len = fd.readlink(file, &mut destination)?; + if len < destination.len() { + destination.truncate(len); + destination.shrink_to_fit(); + return Ok(PathBuf::from(OsString::from_vec(destination))); + } + let amt_to_add = destination.len(); + destination.extend(iter::repeat(0).take(amt_to_add)); + } +} + +pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { + let (link, link_file) = open_parent(link)?; + link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?) +} + +pub fn link(original: &Path, link: &Path) -> io::Result<()> { + let (original, original_file) = open_parent(original)?; + let (link, link_file) = open_parent(link)?; + // Pass 0 as the flags argument, meaning don't follow symlinks. + original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?) +} + +pub fn stat(p: &Path) -> io::Result<FileAttr> { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) +} + +pub fn lstat(p: &Path) -> io::Result<FileAttr> { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, 0, &file) +} + +fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { + let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?; + Ok(FileAttr { meta }) +} + +pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> { + // This seems to not be in wasi's API yet, and we may need to end up + // emulating it ourselves. For now just return an error. + unsupported() +} + +fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> { + let fd = fd.open( + opts.dirflags, + osstr2str(path.as_ref())?, + opts.oflags, + opts.rights_base(), + opts.rights_inheriting(), + opts.fdflags, + )?; + Ok(File { fd }) +} + +/// Attempts to open a bare path `p`. +/// +/// WASI has no fundamental capability to do this. All syscalls and operations +/// are relative to already-open file descriptors. The C library, however, +/// manages a map of pre-opened file descriptors to their path, and then the C +/// library provides an API to look at this. In other words, when you want to +/// open a path `p`, you have to find a previously opened file descriptor in a +/// global table and then see if `p` is relative to that file descriptor. +/// +/// This function, if successful, will return two items: +/// +/// * The first is a `ManuallyDrop<WasiFd>`. This represents a pre-opened file +/// descriptor which we don't have ownership of, but we can use. You shouldn't +/// actually drop the `fd`. +/// +/// * The second is a path that should be a part of `p` and represents a +/// relative traversal from the file descriptor specified to the desired +/// location `p`. +/// +/// If successful you can use the returned file descriptor to perform +/// file-descriptor-relative operations on the path returned as well. The +/// `rights` argument indicates what operations are desired on the returned file +/// descriptor, and if successful the returned file descriptor should have the +/// appropriate rights for performing `rights` actions. +/// +/// Note that this can fail if `p` doesn't look like it can be opened relative +/// to any pre-opened file descriptor. +fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> { + let p = CString::new(p.as_os_str().as_bytes())?; + let mut buf = Vec::<u8>::with_capacity(512); + loop { + unsafe { + let mut relative_path = buf.as_ptr().cast(); + let mut abs_prefix = ptr::null(); + let fd = __wasilibc_find_relpath( + p.as_ptr(), + &mut abs_prefix, + &mut relative_path, + buf.capacity(), + ); + if fd == -1 { + if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) { + // Trigger the internal buffer resizing logic of `Vec` by requiring + // more space than the current capacity. + let cap = buf.capacity(); + buf.set_len(cap); + buf.reserve(1); + continue; + } + let msg = format!( + "failed to find a pre-opened file descriptor \ + through which {:?} could be opened", + p + ); + return Err(io::Error::new(io::ErrorKind::Uncategorized, msg)); + } + let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); + + return Ok(( + ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)), + PathBuf::from(OsString::from_vec(relative)), + )); + } + } + + extern "C" { + pub fn __wasilibc_find_relpath( + path: *const libc::c_char, + abs_prefix: *mut *const libc::c_char, + relative_path: *mut *const libc::c_char, + relative_path_len: libc::size_t, + ) -> libc::c_int; + } +} + +pub fn osstr2str(f: &OsStr) -> io::Result<&str> { + f.to_str() + .ok_or_else(|| io::const_io_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) +} + +pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { + use crate::fs::File; + + let mut reader = File::open(from)?; + let mut writer = File::create(to)?; + + io::copy(&mut reader, &mut writer) +} + +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + let (parent, path) = open_parent(path)?; + remove_dir_all_recursive(&parent, &path) +} + +fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { + // Open up a file descriptor for the directory itself. Note that we don't + // follow symlinks here and we specifically open directories. + // + // At the root invocation of this function this will correctly handle + // symlinks passed to the top-level `remove_dir_all`. At the recursive + // level this will double-check that after the `readdir` call deduced this + // was a directory it's still a directory by the time we open it up. + // + // If the opened file was actually a symlink then the symlink is deleted, + // not the directory recursively. + let mut opts = OpenOptions::new(); + opts.lookup_flags(0); + opts.directory(true); + opts.read(true); + let fd = open_at(parent, path, &opts)?; + if fd.file_attr()?.file_type().is_symlink() { + return parent.unlink_file(osstr2str(path.as_ref())?); + } + + // this "root" is only used by `DirEntry::path` which we don't use below so + // it's ok for this to be a bogus value + let dummy_root = PathBuf::new(); + + // Iterate over all the entries in this directory, and travel recursively if + // necessary + for entry in ReadDir::new(fd, dummy_root) { + let entry = entry?; + let path = crate::str::from_utf8(&entry.name).map_err(|_| { + io::const_io_error!(io::ErrorKind::Uncategorized, "invalid utf-8 file name found") + })?; + + if entry.file_type()?.is_dir() { + remove_dir_all_recursive(&entry.inner.dir.fd, path.as_ref())?; + } else { + entry.inner.dir.fd.unlink_file(path)?; + } + } + + // Once all this directory's contents are deleted it should be safe to + // delete the directory tiself. + parent.remove_directory(osstr2str(path.as_ref())?) +} |