]>
Commit | Line | Data |
---|---|---|
1b1a35ee XL |
1 | #![deny(unsafe_op_in_unsafe_fn)] |
2 | ||
6a06907d | 3 | use super::fd::WasiFd; |
2b03887a | 4 | use crate::ffi::{CStr, OsStr, OsString}; |
532ac7d7 | 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; | |
2b03887a | 15 | use crate::sys::common::small_c_string::run_path_with_cstr; |
532ac7d7 XL |
16 | use crate::sys::time::SystemTime; |
17 | use crate::sys::unsupported; | |
94222f64 | 18 | use crate::sys_common::{AsInner, FromInner, IntoInner}; |
532ac7d7 | 19 | |
3c0e092e | 20 | pub use crate::sys_common::fs::try_exists; |
532ac7d7 XL |
21 | |
22 | pub struct File { | |
23 | fd: WasiFd, | |
24 | } | |
25 | ||
26 | #[derive(Clone)] | |
27 | pub struct FileAttr { | |
60c5eb7d | 28 | meta: wasi::Filestat, |
532ac7d7 XL |
29 | } |
30 | ||
31 | pub 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 | ||
39 | struct ReadDirInner { | |
40 | root: PathBuf, | |
41 | dir: File, | |
42 | } | |
43 | ||
44 | pub struct DirEntry { | |
e1599b0c | 45 | meta: wasi::Dirent, |
532ac7d7 XL |
46 | name: Vec<u8>, |
47 | inner: Arc<ReadDirInner>, | |
48 | } | |
49 | ||
50 | #[derive(Clone, Debug, Default)] | |
51 | pub 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)] | |
63 | pub struct FilePermissions { | |
64 | readonly: bool, | |
65 | } | |
66 | ||
064997fb FG |
67 | #[derive(Copy, Clone, Debug, Default)] |
68 | pub 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)] |
74 | pub struct FileType { | |
60c5eb7d | 75 | bits: wasi::Filetype, |
532ac7d7 XL |
76 | } |
77 | ||
78 | #[derive(Debug)] | |
79 | pub struct DirBuilder {} | |
80 | ||
81 | impl 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 | ||
112 | impl 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 |
122 | impl 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 |
132 | impl 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 |
150 | impl 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 |
162 | impl 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 | ||
168 | impl 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 | ||
244 | impl 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 | ||
267 | impl 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 | ||
400 | impl 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 FG |
443 | pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { |
444 | crate::io::default_read_buf(|buf| self.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 | ||
500 | impl AsInner<WasiFd> for File { | |
501 | fn as_inner(&self) -> &WasiFd { | |
532ac7d7 XL |
502 | &self.fd |
503 | } | |
94222f64 | 504 | } |
532ac7d7 | 505 | |
94222f64 XL |
506 | impl IntoInner<WasiFd> for File { |
507 | fn into_inner(self) -> WasiFd { | |
532ac7d7 XL |
508 | self.fd |
509 | } | |
94222f64 | 510 | } |
532ac7d7 | 511 | |
94222f64 XL |
512 | impl FromInner<WasiFd> for File { |
513 | fn from_inner(fd: WasiFd) -> File { | |
514 | File { fd } | |
515 | } | |
516 | } | |
517 | ||
518 | impl AsFd for File { | |
519 | fn as_fd(&self) -> BorrowedFd<'_> { | |
520 | self.fd.as_fd() | |
521 | } | |
522 | } | |
523 | ||
524 | impl AsRawFd for File { | |
525 | fn as_raw_fd(&self) -> RawFd { | |
526 | self.fd.as_raw_fd() | |
527 | } | |
528 | } | |
529 | ||
530 | impl IntoRawFd for File { | |
531 | fn into_raw_fd(self) -> RawFd { | |
532 | self.fd.into_raw_fd() | |
532ac7d7 XL |
533 | } |
534 | } | |
535 | ||
94222f64 XL |
536 | impl 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 | ||
542 | impl 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 | ||
553 | impl 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 | ||
559 | pub 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 | ||
567 | pub 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 | ||
572 | pub 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 | ||
578 | pub 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 | ||
584 | pub 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 | ||
589 | pub fn readlink(p: &Path) -> io::Result<PathBuf> { | |
60c5eb7d | 590 | let (dir, file) = open_parent(p)?; |
532ac7d7 XL |
591 | read_link(&dir, &file) |
592 | } | |
593 | ||
594 | fn 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 |
625 | pub 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 |
630 | pub 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 | ||
637 | pub 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 | ||
642 | pub 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 |
647 | fn 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 | ||
652 | pub 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 | ||
658 | fn 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 | 697 | fn 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 | ||
746 | pub 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 | |
751 | pub 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 | |
760 | pub fn remove_dir_all(path: &Path) -> io::Result<()> { | |
761 | let (parent, path) = open_parent(path)?; | |
762 | remove_dir_all_recursive(&parent, &path) | |
763 | } | |
764 | ||
765 | fn 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 | } |