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