]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/wasi/fs.rs
Rename ErrorKind::Unknown to Uncategorized.
[rust.git] / library / std / src / sys / wasi / fs.rs
1 #![deny(unsafe_op_in_unsafe_fn)]
2
3 use super::fd::WasiFd;
4 use crate::ffi::{CStr, CString, OsStr, OsString};
5 use crate::fmt;
6 use crate::io::{self, IoSlice, IoSliceMut, SeekFrom};
7 use crate::iter;
8 use crate::mem::{self, ManuallyDrop};
9 use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
10 use crate::path::{Path, PathBuf};
11 use crate::ptr;
12 use crate::sync::Arc;
13 use crate::sys::time::SystemTime;
14 use crate::sys::unsupported;
15 use crate::sys_common::FromInner;
16
17 pub use crate::sys_common::fs::{remove_dir_all, try_exists};
18
19 pub struct File {
20     fd: WasiFd,
21 }
22
23 #[derive(Clone)]
24 pub struct FileAttr {
25     meta: wasi::Filestat,
26 }
27
28 pub struct ReadDir {
29     inner: Arc<ReadDirInner>,
30     cookie: Option<wasi::Dircookie>,
31     buf: Vec<u8>,
32     offset: usize,
33     cap: usize,
34 }
35
36 struct ReadDirInner {
37     root: PathBuf,
38     dir: File,
39 }
40
41 pub struct DirEntry {
42     meta: wasi::Dirent,
43     name: Vec<u8>,
44     inner: Arc<ReadDirInner>,
45 }
46
47 #[derive(Clone, Debug, Default)]
48 pub struct OpenOptions {
49     read: bool,
50     write: bool,
51     append: bool,
52     dirflags: wasi::Lookupflags,
53     fdflags: wasi::Fdflags,
54     oflags: wasi::Oflags,
55     rights_base: Option<wasi::Rights>,
56     rights_inheriting: Option<wasi::Rights>,
57 }
58
59 #[derive(Clone, PartialEq, Eq, Debug)]
60 pub struct FilePermissions {
61     readonly: bool,
62 }
63
64 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
65 pub struct FileType {
66     bits: wasi::Filetype,
67 }
68
69 #[derive(Debug)]
70 pub struct DirBuilder {}
71
72 impl FileAttr {
73     pub fn size(&self) -> u64 {
74         self.meta.size
75     }
76
77     pub fn perm(&self) -> FilePermissions {
78         // not currently implemented in wasi yet
79         FilePermissions { readonly: false }
80     }
81
82     pub fn file_type(&self) -> FileType {
83         FileType { bits: self.meta.filetype }
84     }
85
86     pub fn modified(&self) -> io::Result<SystemTime> {
87         Ok(SystemTime::from_wasi_timestamp(self.meta.mtim))
88     }
89
90     pub fn accessed(&self) -> io::Result<SystemTime> {
91         Ok(SystemTime::from_wasi_timestamp(self.meta.atim))
92     }
93
94     pub fn created(&self) -> io::Result<SystemTime> {
95         Ok(SystemTime::from_wasi_timestamp(self.meta.ctim))
96     }
97
98     pub fn as_wasi(&self) -> &wasi::Filestat {
99         &self.meta
100     }
101 }
102
103 impl FilePermissions {
104     pub fn readonly(&self) -> bool {
105         self.readonly
106     }
107
108     pub fn set_readonly(&mut self, readonly: bool) {
109         self.readonly = readonly;
110     }
111 }
112
113 impl FileType {
114     pub fn is_dir(&self) -> bool {
115         self.bits == wasi::FILETYPE_DIRECTORY
116     }
117
118     pub fn is_file(&self) -> bool {
119         self.bits == wasi::FILETYPE_REGULAR_FILE
120     }
121
122     pub fn is_symlink(&self) -> bool {
123         self.bits == wasi::FILETYPE_SYMBOLIC_LINK
124     }
125
126     pub fn bits(&self) -> wasi::Filetype {
127         self.bits
128     }
129 }
130
131 impl fmt::Debug for ReadDir {
132     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133         f.debug_struct("ReadDir").finish_non_exhaustive()
134     }
135 }
136
137 impl Iterator for ReadDir {
138     type Item = io::Result<DirEntry>;
139
140     fn next(&mut self) -> Option<io::Result<DirEntry>> {
141         loop {
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)),
149                 }
150                 self.offset = 0;
151                 self.cookie = Some(cookie);
152
153                 // If we didn't actually read anything, this is in theory the
154                 // end of the directory.
155                 if self.cap == 0 {
156                     self.cookie = None;
157                     return None;
158                 }
159
160                 0
161             } else {
162                 self.offset
163             };
164             let data = &self.buf[offset..self.cap];
165
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;
175                 continue;
176             }
177             let (dirent, data) = data.split_at(dirent_size);
178             let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) };
179
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 {
185                 if offset == 0 {
186                     let amt_to_add = self.buf.capacity();
187                     self.buf.extend(iter::repeat(0).take(amt_to_add));
188                 }
189                 assert!(self.cookie.is_some());
190                 self.offset = self.cap;
191                 continue;
192             }
193             self.cookie = Some(dirent.d_next);
194             self.offset = offset + dirent_size + dirent.d_namlen as usize;
195
196             let name = &data[..(dirent.d_namlen as usize)];
197
198             // These names are skipped on all other platforms, so let's skip
199             // them here too
200             if name == b"." || name == b".." {
201                 continue;
202             }
203
204             return Some(Ok(DirEntry {
205                 meta: dirent,
206                 name: name.to_vec(),
207                 inner: self.inner.clone(),
208             }));
209         }
210     }
211 }
212
213 impl DirEntry {
214     pub fn path(&self) -> PathBuf {
215         let name = OsStr::from_bytes(&self.name);
216         self.inner.root.join(name)
217     }
218
219     pub fn file_name(&self) -> OsString {
220         OsString::from_vec(self.name.clone())
221     }
222
223     pub fn metadata(&self) -> io::Result<FileAttr> {
224         metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref())
225     }
226
227     pub fn file_type(&self) -> io::Result<FileType> {
228         Ok(FileType { bits: self.meta.d_type })
229     }
230
231     pub fn ino(&self) -> wasi::Inode {
232         self.meta.d_ino
233     }
234 }
235
236 impl OpenOptions {
237     pub fn new() -> OpenOptions {
238         let mut base = OpenOptions::default();
239         base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW;
240         return base;
241     }
242
243     pub fn read(&mut self, read: bool) {
244         self.read = read;
245     }
246
247     pub fn write(&mut self, write: bool) {
248         self.write = write;
249     }
250
251     pub fn truncate(&mut self, truncate: bool) {
252         self.oflag(wasi::OFLAGS_TRUNC, truncate);
253     }
254
255     pub fn create(&mut self, create: bool) {
256         self.oflag(wasi::OFLAGS_CREAT, create);
257     }
258
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);
262     }
263
264     pub fn directory(&mut self, directory: bool) {
265         self.oflag(wasi::OFLAGS_DIRECTORY, directory);
266     }
267
268     fn oflag(&mut self, bit: wasi::Oflags, set: bool) {
269         if set {
270             self.oflags |= bit;
271         } else {
272             self.oflags &= !bit;
273         }
274     }
275
276     pub fn append(&mut self, append: bool) {
277         self.append = append;
278         self.fdflag(wasi::FDFLAGS_APPEND, append);
279     }
280
281     pub fn dsync(&mut self, set: bool) {
282         self.fdflag(wasi::FDFLAGS_DSYNC, set);
283     }
284
285     pub fn nonblock(&mut self, set: bool) {
286         self.fdflag(wasi::FDFLAGS_NONBLOCK, set);
287     }
288
289     pub fn rsync(&mut self, set: bool) {
290         self.fdflag(wasi::FDFLAGS_RSYNC, set);
291     }
292
293     pub fn sync(&mut self, set: bool) {
294         self.fdflag(wasi::FDFLAGS_SYNC, set);
295     }
296
297     fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) {
298         if set {
299             self.fdflags |= bit;
300         } else {
301             self.fdflags &= !bit;
302         }
303     }
304
305     pub fn fs_rights_base(&mut self, rights: wasi::Rights) {
306         self.rights_base = Some(rights);
307     }
308
309     pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) {
310         self.rights_inheriting = Some(rights);
311     }
312
313     fn rights_base(&self) -> wasi::Rights {
314         if let Some(rights) = self.rights_base {
315             return rights;
316         }
317
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
322         // based on that.
323         let mut base = 0;
324         if self.read {
325             base |= wasi::RIGHTS_FD_READ;
326             base |= wasi::RIGHTS_FD_READDIR;
327         }
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;
333         }
334
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;
356
357         return base;
358     }
359
360     fn rights_inheriting(&self) -> wasi::Rights {
361         self.rights_inheriting.unwrap_or_else(|| self.rights_base())
362     }
363
364     pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) {
365         self.dirflags = flags;
366     }
367 }
368
369 impl File {
370     pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
371         let (dir, file) = open_parent(path)?;
372         open_at(&dir, &file, opts)
373     }
374
375     pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
376         open_at(&self.fd, path, opts)
377     }
378
379     pub fn file_attr(&self) -> io::Result<FileAttr> {
380         self.fd.filestat_get().map(|meta| FileAttr { meta })
381     }
382
383     pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> {
384         metadata_at(&self.fd, flags, path)
385     }
386
387     pub fn fsync(&self) -> io::Result<()> {
388         self.fd.sync()
389     }
390
391     pub fn datasync(&self) -> io::Result<()> {
392         self.fd.datasync()
393     }
394
395     pub fn truncate(&self, size: u64) -> io::Result<()> {
396         self.fd.filestat_set_size(size)
397     }
398
399     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
400         self.read_vectored(&mut [IoSliceMut::new(buf)])
401     }
402
403     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
404         self.fd.read(bufs)
405     }
406
407     #[inline]
408     pub fn is_read_vectored(&self) -> bool {
409         true
410     }
411
412     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
413         self.write_vectored(&[IoSlice::new(buf)])
414     }
415
416     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
417         self.fd.write(bufs)
418     }
419
420     #[inline]
421     pub fn is_write_vectored(&self) -> bool {
422         true
423     }
424
425     pub fn flush(&self) -> io::Result<()> {
426         Ok(())
427     }
428
429     pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
430         self.fd.seek(pos)
431     }
432
433     pub fn duplicate(&self) -> io::Result<File> {
434         // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
435         unsupported()
436     }
437
438     pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
439         // Permissions haven't been fully figured out in wasi yet, so this is
440         // likely temporary
441         unsupported()
442     }
443
444     pub fn fd(&self) -> &WasiFd {
445         &self.fd
446     }
447
448     pub fn into_fd(self) -> WasiFd {
449         self.fd
450     }
451
452     pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
453         read_link(&self.fd, file)
454     }
455 }
456
457 impl FromInner<u32> for File {
458     fn from_inner(fd: u32) -> File {
459         unsafe { File { fd: WasiFd::from_raw(fd) } }
460     }
461 }
462
463 impl DirBuilder {
464     pub fn new() -> DirBuilder {
465         DirBuilder {}
466     }
467
468     pub fn mkdir(&self, p: &Path) -> io::Result<()> {
469         let (dir, file) = open_parent(p)?;
470         dir.create_directory(osstr2str(file.as_ref())?)
471     }
472 }
473
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()
477     }
478 }
479
480 pub fn readdir(p: &Path) -> io::Result<ReadDir> {
481     let mut opts = OpenOptions::new();
482     opts.directory(true);
483     opts.read(true);
484     let dir = File::open(p, &opts)?;
485     Ok(ReadDir {
486         cookie: Some(0),
487         buf: vec![0; 128],
488         offset: 0,
489         cap: 0,
490         inner: Arc::new(ReadDirInner { dir, root: p.to_path_buf() }),
491     })
492 }
493
494 pub fn unlink(p: &Path) -> io::Result<()> {
495     let (dir, file) = open_parent(p)?;
496     dir.unlink_file(osstr2str(file.as_ref())?)
497 }
498
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())?)
503 }
504
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
507     // likely temporary
508     unsupported()
509 }
510
511 pub fn rmdir(p: &Path) -> io::Result<()> {
512     let (dir, file) = open_parent(p)?;
513     dir.remove_directory(osstr2str(file.as_ref())?)
514 }
515
516 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
517     let (dir, file) = open_parent(p)?;
518     read_link(&dir, &file)
519 }
520
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)
531     } else {
532         1 // this'll fail in just a moment
533     };
534
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];
540     loop {
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)));
546         }
547         let amt_to_add = destination.len();
548         destination.extend(iter::repeat(0).take(amt_to_add));
549     }
550 }
551
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())?)
555 }
556
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())?)
562 }
563
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)
567 }
568
569 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
570     let (dir, file) = open_parent(p)?;
571     metadata_at(&dir, 0, &file)
572 }
573
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 })
577 }
578
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.
582     unsupported()
583 }
584
585 fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
586     let fd = fd.open(
587         opts.dirflags,
588         osstr2str(path.as_ref())?,
589         opts.oflags,
590         opts.rights_base(),
591         opts.rights_inheriting(),
592         opts.fdflags,
593     )?;
594     Ok(File { fd })
595 }
596
597 /// Attempts to open a bare path `p`.
598 ///
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.
605 ///
606 /// This function, if successful, will return two items:
607 ///
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`.
611 ///
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
614 ///   location `p`.
615 ///
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.
621 ///
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);
627     loop {
628         unsafe {
629             let mut relative_path = buf.as_ptr().cast();
630             let mut abs_prefix = ptr::null();
631             let fd = __wasilibc_find_relpath(
632                 p.as_ptr(),
633                 &mut abs_prefix,
634                 &mut relative_path,
635                 buf.capacity(),
636             );
637             if fd == -1 {
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();
642                     buf.set_len(cap);
643                     buf.reserve(1);
644                     continue;
645                 }
646                 let msg = format!(
647                     "failed to find a pre-opened file descriptor \
648                      through which {:?} could be opened",
649                     p
650                 );
651                 return Err(io::Error::new(io::ErrorKind::Uncategorized, msg));
652             }
653             let relative = CStr::from_ptr(relative_path).to_bytes().to_vec();
654
655             return Ok((
656                 ManuallyDrop::new(WasiFd::from_raw(fd as u32)),
657                 PathBuf::from(OsString::from_vec(relative)),
658             ));
659         }
660     }
661
662     extern "C" {
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,
668         ) -> libc::c_int;
669     }
670 }
671
672 pub fn osstr2str(f: &OsStr) -> io::Result<&str> {
673     f.to_str()
674         .ok_or_else(|| io::Error::new_const(io::ErrorKind::Uncategorized, &"input must be utf-8"))
675 }
676
677 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
678     use crate::fs::File;
679
680     let mut reader = File::open(from)?;
681     let mut writer = File::create(to)?;
682
683     io::copy(&mut reader, &mut writer)
684 }