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