]>
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; | |
f2b60f7d | 6 | use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, 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 | ||
064997fb FG |
66 | #[derive(Copy, Clone, Debug, Default)] |
67 | pub struct FileTimes { | |
68 | accessed: Option<wasi::Timestamp>, | |
69 | modified: Option<wasi::Timestamp>, | |
70 | } | |
71 | ||
532ac7d7 XL |
72 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] |
73 | pub struct FileType { | |
60c5eb7d | 74 | bits: wasi::Filetype, |
532ac7d7 XL |
75 | } |
76 | ||
77 | #[derive(Debug)] | |
78 | pub struct DirBuilder {} | |
79 | ||
80 | impl 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 | ||
111 | impl 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 |
121 | impl 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 |
131 | impl 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 |
149 | impl 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 |
161 | impl 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 | ||
167 | impl 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 | ||
243 | impl 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 | ||
266 | impl 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 | ||
399 | impl 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 | ||
492 | impl AsInner<WasiFd> for File { | |
493 | fn as_inner(&self) -> &WasiFd { | |
532ac7d7 XL |
494 | &self.fd |
495 | } | |
94222f64 | 496 | } |
532ac7d7 | 497 | |
94222f64 XL |
498 | impl IntoInner<WasiFd> for File { |
499 | fn into_inner(self) -> WasiFd { | |
532ac7d7 XL |
500 | self.fd |
501 | } | |
94222f64 | 502 | } |
532ac7d7 | 503 | |
94222f64 XL |
504 | impl FromInner<WasiFd> for File { |
505 | fn from_inner(fd: WasiFd) -> File { | |
506 | File { fd } | |
507 | } | |
508 | } | |
509 | ||
510 | impl AsFd for File { | |
511 | fn as_fd(&self) -> BorrowedFd<'_> { | |
512 | self.fd.as_fd() | |
513 | } | |
514 | } | |
515 | ||
516 | impl AsRawFd for File { | |
517 | fn as_raw_fd(&self) -> RawFd { | |
518 | self.fd.as_raw_fd() | |
519 | } | |
520 | } | |
521 | ||
522 | impl IntoRawFd for File { | |
523 | fn into_raw_fd(self) -> RawFd { | |
524 | self.fd.into_raw_fd() | |
532ac7d7 XL |
525 | } |
526 | } | |
527 | ||
94222f64 XL |
528 | impl 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 | ||
534 | impl 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 | ||
545 | impl 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 | ||
551 | pub 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 | ||
559 | pub 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 | ||
564 | pub 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 | ||
570 | pub 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 | ||
576 | pub 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 | ||
581 | pub fn readlink(p: &Path) -> io::Result<PathBuf> { | |
60c5eb7d | 582 | let (dir, file) = open_parent(p)?; |
532ac7d7 XL |
583 | read_link(&dir, &file) |
584 | } | |
585 | ||
586 | fn 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 |
617 | pub 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 |
622 | pub 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 | ||
629 | pub 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 | ||
634 | pub 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 |
639 | fn 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 | ||
644 | pub 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 | ||
650 | fn 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 | 689 | fn 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 | ||
737 | pub 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 | |
742 | pub 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 | |
751 | pub fn remove_dir_all(path: &Path) -> io::Result<()> { | |
752 | let (parent, path) = open_parent(path)?; | |
753 | remove_dir_all_recursive(&parent, &path) | |
754 | } | |
755 | ||
756 | fn 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 | } |