]>
Commit | Line | Data |
---|---|---|
1b1a35ee XL |
1 | #![deny(unsafe_op_in_unsafe_fn)] |
2 | ||
6a06907d | 3 | use super::fd::WasiFd; |
532ac7d7 XL |
4 | use crate::ffi::{CStr, CString, OsStr, OsString}; |
5 | use crate::fmt; | |
a2a8927a | 6 | use crate::io::{self, IoSlice, IoSliceMut, ReadBuf, SeekFrom}; |
532ac7d7 XL |
7 | use crate::iter; |
8 | use crate::mem::{self, ManuallyDrop}; | |
94222f64 | 9 | use crate::os::raw::c_int; |
532ac7d7 | 10 | use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; |
94222f64 | 11 | use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; |
532ac7d7 XL |
12 | use crate::path::{Path, PathBuf}; |
13 | use crate::ptr; | |
14 | use crate::sync::Arc; | |
532ac7d7 XL |
15 | use crate::sys::time::SystemTime; |
16 | use crate::sys::unsupported; | |
94222f64 | 17 | use crate::sys_common::{AsInner, FromInner, IntoInner}; |
532ac7d7 | 18 | |
3c0e092e | 19 | pub use crate::sys_common::fs::try_exists; |
532ac7d7 XL |
20 | |
21 | pub struct File { | |
22 | fd: WasiFd, | |
23 | } | |
24 | ||
25 | #[derive(Clone)] | |
26 | pub struct FileAttr { | |
60c5eb7d | 27 | meta: wasi::Filestat, |
532ac7d7 XL |
28 | } |
29 | ||
30 | pub 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 | ||
38 | struct ReadDirInner { | |
39 | root: PathBuf, | |
40 | dir: File, | |
41 | } | |
42 | ||
43 | pub struct DirEntry { | |
e1599b0c | 44 | meta: wasi::Dirent, |
532ac7d7 XL |
45 | name: Vec<u8>, |
46 | inner: Arc<ReadDirInner>, | |
47 | } | |
48 | ||
49 | #[derive(Clone, Debug, Default)] | |
50 | pub 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)] | |
62 | pub struct FilePermissions { | |
63 | readonly: bool, | |
64 | } | |
65 | ||
66 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] | |
67 | pub struct FileType { | |
60c5eb7d | 68 | bits: wasi::Filetype, |
532ac7d7 XL |
69 | } |
70 | ||
71 | #[derive(Debug)] | |
72 | pub struct DirBuilder {} | |
73 | ||
74 | impl 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 | ||
105 | impl 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 | ||
115 | impl 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 |
133 | impl 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 |
145 | impl 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 | ||
151 | impl 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 | ||
227 | impl 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 | ||
250 | impl 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 | ||
383 | impl 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 | ||
467 | impl AsInner<WasiFd> for File { | |
468 | fn as_inner(&self) -> &WasiFd { | |
532ac7d7 XL |
469 | &self.fd |
470 | } | |
94222f64 | 471 | } |
532ac7d7 | 472 | |
94222f64 XL |
473 | impl IntoInner<WasiFd> for File { |
474 | fn into_inner(self) -> WasiFd { | |
532ac7d7 XL |
475 | self.fd |
476 | } | |
94222f64 | 477 | } |
532ac7d7 | 478 | |
94222f64 XL |
479 | impl FromInner<WasiFd> for File { |
480 | fn from_inner(fd: WasiFd) -> File { | |
481 | File { fd } | |
482 | } | |
483 | } | |
484 | ||
485 | impl AsFd for File { | |
486 | fn as_fd(&self) -> BorrowedFd<'_> { | |
487 | self.fd.as_fd() | |
488 | } | |
489 | } | |
490 | ||
491 | impl AsRawFd for File { | |
492 | fn as_raw_fd(&self) -> RawFd { | |
493 | self.fd.as_raw_fd() | |
494 | } | |
495 | } | |
496 | ||
497 | impl IntoRawFd for File { | |
498 | fn into_raw_fd(self) -> RawFd { | |
499 | self.fd.into_raw_fd() | |
532ac7d7 XL |
500 | } |
501 | } | |
502 | ||
94222f64 XL |
503 | impl 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 | ||
509 | impl 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 | ||
520 | impl 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 | ||
526 | pub 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 | ||
534 | pub 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 | ||
539 | pub 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 | ||
545 | pub 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 | ||
551 | pub 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 | ||
556 | pub fn readlink(p: &Path) -> io::Result<PathBuf> { | |
60c5eb7d | 557 | let (dir, file) = open_parent(p)?; |
532ac7d7 XL |
558 | read_link(&dir, &file) |
559 | } | |
560 | ||
561 | fn 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 |
592 | pub 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 |
597 | pub 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 | ||
604 | pub 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 | ||
609 | pub 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 |
614 | fn 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 | ||
619 | pub 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 | ||
625 | fn 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 | 664 | fn 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 | ||
712 | pub 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 | |
717 | pub 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 | |
726 | pub fn remove_dir_all(path: &Path) -> io::Result<()> { | |
727 | let (parent, path) = open_parent(path)?; | |
728 | remove_dir_all_recursive(&parent, &path) | |
729 | } | |
730 | ||
731 | fn 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 | } |