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 {
size(&self) -> u6483 pub fn size(&self) -> u64 {
84 self.meta.size
85 }
86
perm(&self) -> FilePermissions87 pub fn perm(&self) -> FilePermissions {
88 // not currently implemented in wasi yet
89 FilePermissions { readonly: false }
90 }
91
file_type(&self) -> FileType92 pub fn file_type(&self) -> FileType {
93 FileType {
94 bits: self.meta.filetype,
95 }
96 }
97
modified(&self) -> io::Result<SystemTime>98 pub fn modified(&self) -> io::Result<SystemTime> {
99 Ok(SystemTime::from_wasi_timestamp(self.meta.mtim))
100 }
101
accessed(&self) -> io::Result<SystemTime>102 pub fn accessed(&self) -> io::Result<SystemTime> {
103 Ok(SystemTime::from_wasi_timestamp(self.meta.atim))
104 }
105
created(&self) -> io::Result<SystemTime>106 pub fn created(&self) -> io::Result<SystemTime> {
107 Ok(SystemTime::from_wasi_timestamp(self.meta.ctim))
108 }
109
as_wasi(&self) -> &wasi::Filestat110 pub(crate) fn as_wasi(&self) -> &wasi::Filestat {
111 &self.meta
112 }
113 }
114
115 impl FilePermissions {
readonly(&self) -> bool116 pub fn readonly(&self) -> bool {
117 self.readonly
118 }
119
set_readonly(&mut self, readonly: bool)120 pub fn set_readonly(&mut self, readonly: bool) {
121 self.readonly = readonly;
122 }
123 }
124
125 impl FileTimes {
set_accessed(&mut self, t: SystemTime)126 pub fn set_accessed(&mut self, t: SystemTime) {
127 self.accessed = Some(t);
128 }
129
set_modified(&mut self, t: SystemTime)130 pub fn set_modified(&mut self, t: SystemTime) {
131 self.modified = Some(t);
132 }
133 }
134
135 impl FileType {
is_dir(&self) -> bool136 pub fn is_dir(&self) -> bool {
137 self.bits == wasi::FILETYPE_DIRECTORY
138 }
139
is_file(&self) -> bool140 pub fn is_file(&self) -> bool {
141 self.bits == wasi::FILETYPE_REGULAR_FILE
142 }
143
is_symlink(&self) -> bool144 pub fn is_symlink(&self) -> bool {
145 self.bits == wasi::FILETYPE_SYMBOLIC_LINK
146 }
147
bits(&self) -> wasi::Filetype148 pub(crate) fn bits(&self) -> wasi::Filetype {
149 self.bits
150 }
151 }
152
153 impl ReadDir {
new(dir: File, root: PathBuf) -> ReadDir154 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 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result166 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
next(&mut self) -> Option<io::Result<DirEntry>>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 {
path(&self) -> PathBuf248 pub fn path(&self) -> PathBuf {
249 let name = OsStr::from_bytes(&self.name);
250 self.inner.root.join(name)
251 }
252
file_name(&self) -> OsString253 pub fn file_name(&self) -> OsString {
254 OsString::from_vec(self.name.clone())
255 }
256
metadata(&self) -> io::Result<FileAttr>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
file_type(&self) -> io::Result<FileType>265 pub fn file_type(&self) -> io::Result<FileType> {
266 Ok(FileType {
267 bits: self.meta.d_type,
268 })
269 }
270
ino(&self) -> wasi::Inode271 pub fn ino(&self) -> wasi::Inode {
272 self.meta.d_ino
273 }
274 }
275
276 impl OpenOptions {
new() -> OpenOptions277 pub fn new() -> OpenOptions {
278 let mut base = OpenOptions::default();
279 base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW;
280 return base;
281 }
282
read(&mut self, read: bool)283 pub fn read(&mut self, read: bool) {
284 self.read = read;
285 }
286
write(&mut self, write: bool)287 pub fn write(&mut self, write: bool) {
288 self.write = write;
289 }
290
truncate(&mut self, truncate: bool)291 pub fn truncate(&mut self, truncate: bool) {
292 self.oflag(wasi::OFLAGS_TRUNC, truncate);
293 }
294
create(&mut self, create: bool)295 pub fn create(&mut self, create: bool) {
296 self.oflag(wasi::OFLAGS_CREAT, create);
297 }
298
create_new(&mut self, create_new: bool)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
directory(&mut self, directory: bool)304 pub fn directory(&mut self, directory: bool) {
305 self.oflag(wasi::OFLAGS_DIRECTORY, directory);
306 }
307
oflag(&mut self, bit: wasi::Oflags, set: bool)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
append(&mut self, append: bool)316 pub fn append(&mut self, append: bool) {
317 self.append = append;
318 self.fdflag(wasi::FDFLAGS_APPEND, append);
319 }
320
dsync(&mut self, set: bool)321 pub fn dsync(&mut self, set: bool) {
322 self.fdflag(wasi::FDFLAGS_DSYNC, set);
323 }
324
nonblock(&mut self, set: bool)325 pub fn nonblock(&mut self, set: bool) {
326 self.fdflag(wasi::FDFLAGS_NONBLOCK, set);
327 }
328
rsync(&mut self, set: bool)329 pub fn rsync(&mut self, set: bool) {
330 self.fdflag(wasi::FDFLAGS_RSYNC, set);
331 }
332
sync(&mut self, set: bool)333 pub fn sync(&mut self, set: bool) {
334 self.fdflag(wasi::FDFLAGS_SYNC, set);
335 }
336
fdflag(&mut self, bit: wasi::Fdflags, set: bool)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
fs_rights_base(&mut self, rights: wasi::Rights)345 pub fn fs_rights_base(&mut self, rights: wasi::Rights) {
346 self.rights_base = Some(rights);
347 }
348
fs_rights_inheriting(&mut self, rights: wasi::Rights)349 pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) {
350 self.rights_inheriting = Some(rights);
351 }
352
rights_base(&self) -> wasi::Rights353 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
rights_inheriting(&self) -> wasi::Rights400 fn rights_inheriting(&self) -> wasi::Rights {
401 self.rights_inheriting.unwrap_or_else(|| self.rights_base())
402 }
403
lookup_flags(&mut self, flags: wasi::Lookupflags)404 pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) {
405 self.dirflags = flags;
406 }
407 }
408
409 impl File {
open(path: &Path, opts: &OpenOptions) -> io::Result<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
open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File>415 pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
416 open_at(&self.fd, path, opts)
417 }
418
file_attr(&self) -> io::Result<FileAttr>419 pub fn file_attr(&self) -> io::Result<FileAttr> {
420 self.fd.filestat_get().map(|meta| FileAttr { meta })
421 }
422
metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr>423 pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> {
424 metadata_at(&self.fd, flags, path)
425 }
426
fsync(&self) -> io::Result<()>427 pub fn fsync(&self) -> io::Result<()> {
428 self.fd.sync()
429 }
430
datasync(&self) -> io::Result<()>431 pub fn datasync(&self) -> io::Result<()> {
432 self.fd.datasync()
433 }
434
truncate(&self, size: u64) -> io::Result<()>435 pub fn truncate(&self, size: u64) -> io::Result<()> {
436 self.fd.filestat_set_size(size)
437 }
438
read(&self, buf: &mut [u8]) -> io::Result<usize>439 pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
440 self.read_vectored(&mut [IoSliceMut::new(buf)])
441 }
442
read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize>443 pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
444 self.fd.read(bufs)
445 }
446
447 #[inline]
is_read_vectored(&self) -> bool448 pub fn is_read_vectored(&self) -> bool {
449 true
450 }
451
read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()>452 pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
453 self.fd.read_buf(cursor)
454 }
455
write(&self, buf: &[u8]) -> io::Result<usize>456 pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
457 self.write_vectored(&[IoSlice::new(buf)])
458 }
459
write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize>460 pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
461 self.fd.write(bufs)
462 }
463
464 #[inline]
is_write_vectored(&self) -> bool465 pub fn is_write_vectored(&self) -> bool {
466 true
467 }
468
flush(&self) -> io::Result<()>469 pub fn flush(&self) -> io::Result<()> {
470 Ok(())
471 }
472
seek(&self, pos: SeekFrom) -> io::Result<u64>473 pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
474 self.fd.seek(pos)
475 }
476
duplicate(&self) -> io::Result<File>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
set_permissions(&self, _perm: FilePermissions) -> io::Result<()>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
set_times(&self, times: FileTimes) -> io::Result<()>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
read_link(&self, file: &Path) -> io::Result<PathBuf>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]
as_inner(&self) -> &WasiFd511 fn as_inner(&self) -> &WasiFd {
512 &self.fd
513 }
514 }
515
516 impl IntoInner<WasiFd> for File {
into_inner(self) -> WasiFd517 fn into_inner(self) -> WasiFd {
518 self.fd
519 }
520 }
521
522 impl FromInner<WasiFd> for File {
from_inner(fd: WasiFd) -> File523 fn from_inner(fd: WasiFd) -> File {
524 File { fd }
525 }
526 }
527
528 impl AsFd for File {
as_fd(&self) -> BorrowedFd<'_>529 fn as_fd(&self) -> BorrowedFd<'_> {
530 self.fd.as_fd()
531 }
532 }
533
534 impl AsRawFd for File {
535 #[inline]
as_raw_fd(&self) -> RawFd536 fn as_raw_fd(&self) -> RawFd {
537 self.fd.as_raw_fd()
538 }
539 }
540
541 impl IntoRawFd for File {
into_raw_fd(self) -> RawFd542 fn into_raw_fd(self) -> RawFd {
543 self.fd.into_raw_fd()
544 }
545 }
546
547 impl FromRawFd for File {
from_raw_fd(raw_fd: RawFd) -> Self548 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 {
new() -> DirBuilder558 pub fn new() -> DirBuilder {
559 DirBuilder {}
560 }
561
mkdir(&self, p: &Path) -> io::Result<()>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 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result569 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
readdir(p: &Path) -> io::Result<ReadDir>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
unlink(p: &Path) -> io::Result<()>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
rename(old: &Path, new: &Path) -> io::Result<()>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
set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()>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
rmdir(p: &Path) -> io::Result<()>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
readlink(p: &Path) -> io::Result<PathBuf>610 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
611 let (dir, file) = open_parent(p)?;
612 read_link(&dir, &file)
613 }
614
read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf>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
symlink(original: &Path, link: &Path) -> io::Result<()>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
link(original: &Path, link: &Path) -> io::Result<()>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
stat(p: &Path) -> io::Result<FileAttr>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
lstat(p: &Path) -> io::Result<FileAttr>671 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
672 let (dir, file) = open_parent(p)?;
673 metadata_at(&dir, 0, &file)
674 }
675
metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr>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
canonicalize(_p: &Path) -> io::Result<PathBuf>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
open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File>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.
open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)>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
osstr2str(f: &OsStr) -> io::Result<&str>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
copy(from: &Path, to: &Path) -> io::Result<u64>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
remove_dir_all(path: &Path) -> io::Result<()>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
remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()>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