1 #![deny(unsafe_op_in_unsafe_fn)] 2 3 use super::fd::WasiFd; 4 use crate::std::ffi::{CStr, OsStr, OsString}; 5 use crate::std::fmt; 6 use crate::std::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; 7 use crate::std::iter; 8 use crate::std::mem::{self, ManuallyDrop}; 9 use crate::std::os::raw::c_int; 10 use crate::std::os::wasi::ffi::{OsStrExt, OsStringExt}; 11 use crate::std::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; 12 use crate::std::path::{Path, PathBuf}; 13 use crate::std::ptr; 14 use crate::std::sync::Arc; 15 use crate::std::sys::common::small_c_string::run_path_with_cstr; 16 use crate::std::sys::time::SystemTime; 17 use crate::std::sys::unsupported; 18 use crate::std::sys_common::{AsInner, FromInner, IntoInner}; 19 use dlibc; 20 21 pub use crate::std::sys_common::fs::try_exists; 22 23 pub struct File { 24 fd: WasiFd, 25 } 26 27 #[derive(Clone)] 28 pub struct FileAttr { 29 meta: wasi::Filestat, 30 } 31 32 pub struct ReadDir { 33 inner: Arc<ReadDirInner>, 34 cookie: Option<wasi::Dircookie>, 35 buf: Vec<u8>, 36 offset: usize, 37 cap: usize, 38 } 39 40 struct ReadDirInner { 41 root: PathBuf, 42 dir: File, 43 } 44 45 pub struct DirEntry { 46 meta: wasi::Dirent, 47 name: Vec<u8>, 48 inner: Arc<ReadDirInner>, 49 } 50 51 #[derive(Clone, Debug, Default)] 52 pub struct OpenOptions { 53 read: bool, 54 write: bool, 55 append: bool, 56 dirflags: wasi::Lookupflags, 57 fdflags: wasi::Fdflags, 58 oflags: wasi::Oflags, 59 rights_base: Option<wasi::Rights>, 60 rights_inheriting: Option<wasi::Rights>, 61 } 62 63 #[derive(Clone, PartialEq, Eq, Debug)] 64 pub struct FilePermissions { 65 readonly: bool, 66 } 67 68 #[derive(Copy, Clone, Debug, Default)] 69 pub struct FileTimes { 70 accessed: Option<SystemTime>, 71 modified: Option<SystemTime>, 72 } 73 74 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] 75 pub struct FileType { 76 bits: wasi::Filetype, 77 } 78 79 #[derive(Debug)] 80 pub struct DirBuilder {} 81 82 impl FileAttr { 83 pub fn size(&self) -> u64 { 84 self.meta.size 85 } 86 87 pub fn perm(&self) -> FilePermissions { 88 // not currently implemented in wasi yet 89 FilePermissions { readonly: false } 90 } 91 92 pub fn file_type(&self) -> FileType { 93 FileType { 94 bits: self.meta.filetype, 95 } 96 } 97 98 pub fn modified(&self) -> io::Result<SystemTime> { 99 Ok(SystemTime::from_wasi_timestamp(self.meta.mtim)) 100 } 101 102 pub fn accessed(&self) -> io::Result<SystemTime> { 103 Ok(SystemTime::from_wasi_timestamp(self.meta.atim)) 104 } 105 106 pub fn created(&self) -> io::Result<SystemTime> { 107 Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) 108 } 109 110 pub(crate) fn as_wasi(&self) -> &wasi::Filestat { 111 &self.meta 112 } 113 } 114 115 impl FilePermissions { 116 pub fn readonly(&self) -> bool { 117 self.readonly 118 } 119 120 pub fn set_readonly(&mut self, readonly: bool) { 121 self.readonly = readonly; 122 } 123 } 124 125 impl FileTimes { 126 pub fn set_accessed(&mut self, t: SystemTime) { 127 self.accessed = Some(t); 128 } 129 130 pub fn set_modified(&mut self, t: SystemTime) { 131 self.modified = Some(t); 132 } 133 } 134 135 impl FileType { 136 pub fn is_dir(&self) -> bool { 137 self.bits == wasi::FILETYPE_DIRECTORY 138 } 139 140 pub fn is_file(&self) -> bool { 141 self.bits == wasi::FILETYPE_REGULAR_FILE 142 } 143 144 pub fn is_symlink(&self) -> bool { 145 self.bits == wasi::FILETYPE_SYMBOLIC_LINK 146 } 147 148 pub(crate) fn bits(&self) -> wasi::Filetype { 149 self.bits 150 } 151 } 152 153 impl ReadDir { 154 fn new(dir: File, root: PathBuf) -> ReadDir { 155 ReadDir { 156 cookie: Some(0), 157 buf: vec![0; 128], 158 offset: 0, 159 cap: 0, 160 inner: Arc::new(ReadDirInner { dir, root }), 161 } 162 } 163 } 164 165 impl fmt::Debug for ReadDir { 166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 167 f.debug_struct("ReadDir").finish_non_exhaustive() 168 } 169 } 170 171 impl Iterator for ReadDir { 172 type Item = io::Result<DirEntry>; 173 174 fn next(&mut self) -> Option<io::Result<DirEntry>> { 175 loop { 176 // If we've reached the capacity of our buffer then we need to read 177 // some more from the OS, otherwise we pick up at our old offset. 178 let offset = if self.offset == self.cap { 179 let cookie = self.cookie.take()?; 180 match self.inner.dir.fd.readdir(&mut self.buf, cookie) { 181 Ok(bytes) => self.cap = bytes, 182 Err(e) => return Some(Err(e)), 183 } 184 self.offset = 0; 185 self.cookie = Some(cookie); 186 187 // If we didn't actually read anything, this is in theory the 188 // end of the directory. 189 if self.cap == 0 { 190 self.cookie = None; 191 return None; 192 } 193 194 0 195 } else { 196 self.offset 197 }; 198 let data = &self.buf[offset..self.cap]; 199 200 // If we're not able to read a directory entry then that means it 201 // must have been truncated at the end of the buffer, so reset our 202 // offset so we can go back and reread into the buffer, picking up 203 // where we last left off. 204 let dirent_size = mem::size_of::<wasi::Dirent>(); 205 if data.len() < dirent_size { 206 assert!(self.cookie.is_some()); 207 assert!(self.buf.len() >= dirent_size); 208 self.offset = self.cap; 209 continue; 210 } 211 let (dirent, data) = data.split_at(dirent_size); 212 let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; 213 214 // If the file name was truncated, then we need to reinvoke 215 // `readdir` so we truncate our buffer to start over and reread this 216 // descriptor. Note that if our offset is 0 that means the file name 217 // is massive and we need a bigger buffer. 218 if data.len() < dirent.d_namlen as usize { 219 if offset == 0 { 220 let amt_to_add = self.buf.capacity(); 221 self.buf.extend(iter::repeat(0).take(amt_to_add)); 222 } 223 assert!(self.cookie.is_some()); 224 self.offset = self.cap; 225 continue; 226 } 227 self.cookie = Some(dirent.d_next); 228 self.offset = offset + dirent_size + dirent.d_namlen as usize; 229 230 let name = &data[..(dirent.d_namlen as usize)]; 231 232 // These names are skipped on all other platforms, so let's skip 233 // them here too 234 if name == b"." || name == b".." { 235 continue; 236 } 237 238 return Some(Ok(DirEntry { 239 meta: dirent, 240 name: name.to_vec(), 241 inner: self.inner.clone(), 242 })); 243 } 244 } 245 } 246 247 impl DirEntry { 248 pub fn path(&self) -> PathBuf { 249 let name = OsStr::from_bytes(&self.name); 250 self.inner.root.join(name) 251 } 252 253 pub fn file_name(&self) -> OsString { 254 OsString::from_vec(self.name.clone()) 255 } 256 257 pub fn metadata(&self) -> io::Result<FileAttr> { 258 metadata_at( 259 &self.inner.dir.fd, 260 0, 261 OsStr::from_bytes(&self.name).as_ref(), 262 ) 263 } 264 265 pub fn file_type(&self) -> io::Result<FileType> { 266 Ok(FileType { 267 bits: self.meta.d_type, 268 }) 269 } 270 271 pub fn ino(&self) -> wasi::Inode { 272 self.meta.d_ino 273 } 274 } 275 276 impl OpenOptions { 277 pub fn new() -> OpenOptions { 278 let mut base = OpenOptions::default(); 279 base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW; 280 return base; 281 } 282 283 pub fn read(&mut self, read: bool) { 284 self.read = read; 285 } 286 287 pub fn write(&mut self, write: bool) { 288 self.write = write; 289 } 290 291 pub fn truncate(&mut self, truncate: bool) { 292 self.oflag(wasi::OFLAGS_TRUNC, truncate); 293 } 294 295 pub fn create(&mut self, create: bool) { 296 self.oflag(wasi::OFLAGS_CREAT, create); 297 } 298 299 pub fn create_new(&mut self, create_new: bool) { 300 self.oflag(wasi::OFLAGS_EXCL, create_new); 301 self.oflag(wasi::OFLAGS_CREAT, create_new); 302 } 303 304 pub fn directory(&mut self, directory: bool) { 305 self.oflag(wasi::OFLAGS_DIRECTORY, directory); 306 } 307 308 fn oflag(&mut self, bit: wasi::Oflags, set: bool) { 309 if set { 310 self.oflags |= bit; 311 } else { 312 self.oflags &= !bit; 313 } 314 } 315 316 pub fn append(&mut self, append: bool) { 317 self.append = append; 318 self.fdflag(wasi::FDFLAGS_APPEND, append); 319 } 320 321 pub fn dsync(&mut self, set: bool) { 322 self.fdflag(wasi::FDFLAGS_DSYNC, set); 323 } 324 325 pub fn nonblock(&mut self, set: bool) { 326 self.fdflag(wasi::FDFLAGS_NONBLOCK, set); 327 } 328 329 pub fn rsync(&mut self, set: bool) { 330 self.fdflag(wasi::FDFLAGS_RSYNC, set); 331 } 332 333 pub fn sync(&mut self, set: bool) { 334 self.fdflag(wasi::FDFLAGS_SYNC, set); 335 } 336 337 fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) { 338 if set { 339 self.fdflags |= bit; 340 } else { 341 self.fdflags &= !bit; 342 } 343 } 344 345 pub fn fs_rights_base(&mut self, rights: wasi::Rights) { 346 self.rights_base = Some(rights); 347 } 348 349 pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) { 350 self.rights_inheriting = Some(rights); 351 } 352 353 fn rights_base(&self) -> wasi::Rights { 354 if let Some(rights) = self.rights_base { 355 return rights; 356 } 357 358 // If rights haven't otherwise been specified try to pick a reasonable 359 // set. This can always be overridden by users via extension traits, and 360 // implementations may give us fewer rights silently than we ask for. So 361 // given that, just look at `read` and `write` and bucket permissions 362 // based on that. 363 let mut base = 0; 364 if self.read { 365 base |= wasi::RIGHTS_FD_READ; 366 base |= wasi::RIGHTS_FD_READDIR; 367 } 368 if self.write || self.append { 369 base |= wasi::RIGHTS_FD_WRITE; 370 base |= wasi::RIGHTS_FD_DATASYNC; 371 base |= wasi::RIGHTS_FD_ALLOCATE; 372 base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE; 373 } 374 375 // FIXME: some of these should probably be read-only or write-only... 376 base |= wasi::RIGHTS_FD_ADVISE; 377 base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS; 378 base |= wasi::RIGHTS_FD_FILESTAT_GET; 379 base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES; 380 base |= wasi::RIGHTS_FD_SEEK; 381 base |= wasi::RIGHTS_FD_SYNC; 382 base |= wasi::RIGHTS_FD_TELL; 383 base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY; 384 base |= wasi::RIGHTS_PATH_CREATE_FILE; 385 base |= wasi::RIGHTS_PATH_FILESTAT_GET; 386 base |= wasi::RIGHTS_PATH_LINK_SOURCE; 387 base |= wasi::RIGHTS_PATH_LINK_TARGET; 388 base |= wasi::RIGHTS_PATH_OPEN; 389 base |= wasi::RIGHTS_PATH_READLINK; 390 base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY; 391 base |= wasi::RIGHTS_PATH_RENAME_SOURCE; 392 base |= wasi::RIGHTS_PATH_RENAME_TARGET; 393 base |= wasi::RIGHTS_PATH_SYMLINK; 394 base |= wasi::RIGHTS_PATH_UNLINK_FILE; 395 base |= wasi::RIGHTS_POLL_FD_READWRITE; 396 397 return base; 398 } 399 400 fn rights_inheriting(&self) -> wasi::Rights { 401 self.rights_inheriting.unwrap_or_else(|| self.rights_base()) 402 } 403 404 pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) { 405 self.dirflags = flags; 406 } 407 } 408 409 impl File { 410 pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { 411 let (dir, file) = open_parent(path)?; 412 open_at(&dir, &file, opts) 413 } 414 415 pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> { 416 open_at(&self.fd, path, opts) 417 } 418 419 pub fn file_attr(&self) -> io::Result<FileAttr> { 420 self.fd.filestat_get().map(|meta| FileAttr { meta }) 421 } 422 423 pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { 424 metadata_at(&self.fd, flags, path) 425 } 426 427 pub fn fsync(&self) -> io::Result<()> { 428 self.fd.sync() 429 } 430 431 pub fn datasync(&self) -> io::Result<()> { 432 self.fd.datasync() 433 } 434 435 pub fn truncate(&self, size: u64) -> io::Result<()> { 436 self.fd.filestat_set_size(size) 437 } 438 439 pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { 440 self.read_vectored(&mut [IoSliceMut::new(buf)]) 441 } 442 443 pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { 444 self.fd.read(bufs) 445 } 446 447 #[inline] 448 pub fn is_read_vectored(&self) -> bool { 449 true 450 } 451 452 pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { 453 self.fd.read_buf(cursor) 454 } 455 456 pub fn write(&self, buf: &[u8]) -> io::Result<usize> { 457 self.write_vectored(&[IoSlice::new(buf)]) 458 } 459 460 pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { 461 self.fd.write(bufs) 462 } 463 464 #[inline] 465 pub fn is_write_vectored(&self) -> bool { 466 true 467 } 468 469 pub fn flush(&self) -> io::Result<()> { 470 Ok(()) 471 } 472 473 pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { 474 self.fd.seek(pos) 475 } 476 477 pub fn duplicate(&self) -> io::Result<File> { 478 // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup 479 unsupported() 480 } 481 482 pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { 483 // Permissions haven't been fully figured out in wasi yet, so this is 484 // likely temporary 485 unsupported() 486 } 487 488 pub fn set_times(&self, times: FileTimes) -> io::Result<()> { 489 let to_timestamp = |time: Option<SystemTime>| { 490 match time { 491 Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), 492 Some(_) => Err(io::const_io_error!(io::ErrorKind::InvalidInput, "timestamp is too large to set as a file time")), 493 None => Ok(0), 494 } 495 }; 496 self.fd.filestat_set_times( 497 to_timestamp(times.accessed)?, 498 to_timestamp(times.modified)?, 499 times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) 500 | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), 501 ) 502 } 503 504 pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> { 505 read_link(&self.fd, file) 506 } 507 } 508 509 impl AsInner<WasiFd> for File { 510 #[inline] 511 fn as_inner(&self) -> &WasiFd { 512 &self.fd 513 } 514 } 515 516 impl IntoInner<WasiFd> for File { 517 fn into_inner(self) -> WasiFd { 518 self.fd 519 } 520 } 521 522 impl FromInner<WasiFd> for File { 523 fn from_inner(fd: WasiFd) -> File { 524 File { fd } 525 } 526 } 527 528 impl AsFd for File { 529 fn as_fd(&self) -> BorrowedFd<'_> { 530 self.fd.as_fd() 531 } 532 } 533 534 impl AsRawFd for File { 535 #[inline] 536 fn as_raw_fd(&self) -> RawFd { 537 self.fd.as_raw_fd() 538 } 539 } 540 541 impl IntoRawFd for File { 542 fn into_raw_fd(self) -> RawFd { 543 self.fd.into_raw_fd() 544 } 545 } 546 547 impl FromRawFd for File { 548 unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { 549 unsafe { 550 Self { 551 fd: FromRawFd::from_raw_fd(raw_fd), 552 } 553 } 554 } 555 } 556 557 impl DirBuilder { 558 pub fn new() -> DirBuilder { 559 DirBuilder {} 560 } 561 562 pub fn mkdir(&self, p: &Path) -> io::Result<()> { 563 let (dir, file) = open_parent(p)?; 564 dir.create_directory(osstr2str(file.as_ref())?) 565 } 566 } 567 568 impl fmt::Debug for File { 569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 570 f.debug_struct("File") 571 .field("fd", &self.as_raw_fd()) 572 .finish() 573 } 574 } 575 576 pub fn readdir(p: &Path) -> io::Result<ReadDir> { 577 let mut opts = OpenOptions::new(); 578 opts.directory(true); 579 opts.read(true); 580 let dir = File::open(p, &opts)?; 581 Ok(ReadDir::new(dir, p.to_path_buf())) 582 } 583 584 pub fn unlink(p: &Path) -> io::Result<()> { 585 let (dir, file) = open_parent(p)?; 586 dir.unlink_file(osstr2str(file.as_ref())?) 587 } 588 589 pub fn rename(old: &Path, new: &Path) -> io::Result<()> { 590 let (old, old_file) = open_parent(old)?; 591 let (new, new_file) = open_parent(new)?; 592 old.rename( 593 osstr2str(old_file.as_ref())?, 594 &new, 595 osstr2str(new_file.as_ref())?, 596 ) 597 } 598 599 pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { 600 // Permissions haven't been fully figured out in wasi yet, so this is 601 // likely temporary 602 unsupported() 603 } 604 605 pub fn rmdir(p: &Path) -> io::Result<()> { 606 let (dir, file) = open_parent(p)?; 607 dir.remove_directory(osstr2str(file.as_ref())?) 608 } 609 610 pub fn readlink(p: &Path) -> io::Result<PathBuf> { 611 let (dir, file) = open_parent(p)?; 612 read_link(&dir, &file) 613 } 614 615 fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> { 616 // Try to get a best effort initial capacity for the vector we're going to 617 // fill. Note that if it's not a symlink we don't use a file to avoid 618 // allocating gigabytes if you read_link a huge movie file by accident. 619 // Additionally we add 1 to the initial size so if it doesn't change until 620 // when we call `readlink` the returned length will be less than the 621 // capacity, guaranteeing that we got all the data. 622 let meta = metadata_at(fd, 0, file)?; 623 let initial_size = if meta.file_type().is_symlink() { 624 (meta.size() as usize).saturating_add(1) 625 } else { 626 1 // this'll fail in just a moment 627 }; 628 629 // Now that we have an initial guess of how big to make our buffer, call 630 // `readlink` in a loop until it fails or reports it filled fewer bytes than 631 // we asked for, indicating we got everything. 632 let file = osstr2str(file.as_ref())?; 633 let mut destination = vec![0u8; initial_size]; 634 loop { 635 let len = fd.readlink(file, &mut destination)?; 636 if len < destination.len() { 637 destination.truncate(len); 638 destination.shrink_to_fit(); 639 return Ok(PathBuf::from(OsString::from_vec(destination))); 640 } 641 let amt_to_add = destination.len(); 642 destination.extend(iter::repeat(0).take(amt_to_add)); 643 } 644 } 645 646 pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { 647 let (link, link_file) = open_parent(link)?; 648 link.symlink( 649 osstr2str(original.as_ref())?, 650 osstr2str(link_file.as_ref())?, 651 ) 652 } 653 654 pub fn link(original: &Path, link: &Path) -> io::Result<()> { 655 let (original, original_file) = open_parent(original)?; 656 let (link, link_file) = open_parent(link)?; 657 // Pass 0 as the flags argument, meaning don't follow symlinks. 658 original.link( 659 0, 660 osstr2str(original_file.as_ref())?, 661 &link, 662 osstr2str(link_file.as_ref())?, 663 ) 664 } 665 666 pub fn stat(p: &Path) -> io::Result<FileAttr> { 667 let (dir, file) = open_parent(p)?; 668 metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) 669 } 670 671 pub fn lstat(p: &Path) -> io::Result<FileAttr> { 672 let (dir, file) = open_parent(p)?; 673 metadata_at(&dir, 0, &file) 674 } 675 676 fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { 677 let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?; 678 Ok(FileAttr { meta }) 679 } 680 681 pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> { 682 // This seems to not be in wasi's API yet, and we may need to end up 683 // emulating it ourselves. For now just return an error. 684 unsupported() 685 } 686 687 fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> { 688 let fd = fd.open( 689 opts.dirflags, 690 osstr2str(path.as_ref())?, 691 opts.oflags, 692 opts.rights_base(), 693 opts.rights_inheriting(), 694 opts.fdflags, 695 )?; 696 Ok(File { fd }) 697 } 698 699 /// Attempts to open a bare path `p`. 700 /// 701 /// WASI has no fundamental capability to do this. All syscalls and operations 702 /// are relative to already-open file descriptors. The C library, however, 703 /// manages a map of pre-opened file descriptors to their path, and then the C 704 /// library provides an API to look at this. In other words, when you want to 705 /// open a path `p`, you have to find a previously opened file descriptor in a 706 /// global table and then see if `p` is relative to that file descriptor. 707 /// 708 /// This function, if successful, will return two items: 709 /// 710 /// * The first is a `ManuallyDrop<WasiFd>`. This represents a pre-opened file 711 /// descriptor which we don't have ownership of, but we can use. You shouldn't 712 /// actually drop the `fd`. 713 /// 714 /// * The second is a path that should be a part of `p` and represents a 715 /// relative traversal from the file descriptor specified to the desired 716 /// location `p`. 717 /// 718 /// If successful you can use the returned file descriptor to perform 719 /// file-descriptor-relative operations on the path returned as well. The 720 /// `rights` argument indicates what operations are desired on the returned file 721 /// descriptor, and if successful the returned file descriptor should have the 722 /// appropriate rights for performing `rights` actions. 723 /// 724 /// Note that this can fail if `p` doesn't look like it can be opened relative 725 /// to any pre-opened file descriptor. 726 fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> { 727 run_path_with_cstr(p, |p| { 728 let mut buf = Vec::<u8>::with_capacity(512); 729 loop { 730 unsafe { 731 let mut relative_path = buf.as_ptr().cast(); 732 let mut abs_prefix = ptr::null(); 733 let fd = __wasilibc_find_relpath( 734 p.as_ptr(), 735 &mut abs_prefix, 736 &mut relative_path, 737 buf.capacity(), 738 ); 739 if fd == -1 { 740 if io::Error::last_os_error().raw_os_error() == Some(dlibc::ENOMEM) { 741 // Trigger the internal buffer resizing logic of `Vec` by requiring 742 // more space than the current capacity. 743 let cap = buf.capacity(); 744 buf.set_len(cap); 745 buf.reserve(1); 746 continue; 747 } 748 let msg = format!( 749 "failed to find a pre-opened file descriptor \ 750 through which {:?} could be opened", 751 p 752 ); 753 return Err(io::Error::new(io::ErrorKind::Uncategorized, msg)); 754 } 755 let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); 756 757 return Ok(( 758 ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)), 759 PathBuf::from(OsString::from_vec(relative)), 760 )); 761 } 762 } 763 764 extern "C" { 765 pub fn __wasilibc_find_relpath( 766 path: *const dlibc::c_char, 767 abs_prefix: *mut *const dlibc::c_char, 768 relative_path: *mut *const dlibc::c_char, 769 relative_path_len: dlibc::size_t, 770 ) -> dlibc::c_int; 771 } 772 }) 773 } 774 775 pub fn osstr2str(f: &OsStr) -> io::Result<&str> { 776 f.to_str() 777 .ok_or_else(|| io::const_io_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) 778 } 779 780 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { 781 use crate::std::fs::File; 782 783 let mut reader = File::open(from)?; 784 let mut writer = File::create(to)?; 785 786 io::copy(&mut reader, &mut writer) 787 } 788 789 pub fn remove_dir_all(path: &Path) -> io::Result<()> { 790 let (parent, path) = open_parent(path)?; 791 remove_dir_all_recursive(&parent, &path) 792 } 793 794 fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { 795 // Open up a file descriptor for the directory itself. Note that we don't 796 // follow symlinks here and we specifically open directories. 797 // 798 // At the root invocation of this function this will correctly handle 799 // symlinks passed to the top-level `remove_dir_all`. At the recursive 800 // level this will double-check that after the `readdir` call deduced this 801 // was a directory it's still a directory by the time we open it up. 802 // 803 // If the opened file was actually a symlink then the symlink is deleted, 804 // not the directory recursively. 805 let mut opts = OpenOptions::new(); 806 opts.lookup_flags(0); 807 opts.directory(true); 808 opts.read(true); 809 let fd = open_at(parent, path, &opts)?; 810 if fd.file_attr()?.file_type().is_symlink() { 811 return parent.unlink_file(osstr2str(path.as_ref())?); 812 } 813 814 // this "root" is only used by `DirEntry::path` which we don't use below so 815 // it's ok for this to be a bogus value 816 let dummy_root = PathBuf::new(); 817 818 // Iterate over all the entries in this directory, and travel recursively if 819 // necessary 820 for entry in ReadDir::new(fd, dummy_root) { 821 let entry = entry?; 822 let path = crate::std::str::from_utf8(&entry.name).map_err(|_| { 823 io::const_io_error!( 824 io::ErrorKind::Uncategorized, 825 "invalid utf-8 file name found" 826 ) 827 })?; 828 829 if entry.file_type()?.is_dir() { 830 remove_dir_all_recursive(&entry.inner.dir.fd, path.as_ref())?; 831 } else { 832 entry.inner.dir.fd.unlink_file(path)?; 833 } 834 } 835 836 // Once all this directory's contents are deleted it should be safe to 837 // delete the directory tiself. 838 parent.remove_directory(osstr2str(path.as_ref())?) 839 } 840