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