]>
Commit | Line | Data |
---|---|---|
1b1a35ee XL |
1 | #![deny(unsafe_op_in_unsafe_fn)] |
2 | ||
532ac7d7 XL |
3 | use crate::ffi::{CStr, CString, OsStr, OsString}; |
4 | use crate::fmt; | |
48663c56 | 5 | use crate::io::{self, IoSlice, IoSliceMut, SeekFrom}; |
532ac7d7 XL |
6 | use crate::iter; |
7 | use crate::mem::{self, ManuallyDrop}; | |
8 | use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; | |
9 | use crate::path::{Path, PathBuf}; | |
10 | use crate::ptr; | |
11 | use crate::sync::Arc; | |
e1599b0c | 12 | use crate::sys::fd::WasiFd; |
532ac7d7 XL |
13 | use crate::sys::time::SystemTime; |
14 | use crate::sys::unsupported; | |
15 | use crate::sys_common::FromInner; | |
16 | ||
532ac7d7 XL |
17 | pub use crate::sys_common::fs::remove_dir_all; |
18 | ||
19 | pub struct File { | |
20 | fd: WasiFd, | |
21 | } | |
22 | ||
23 | #[derive(Clone)] | |
24 | pub struct FileAttr { | |
60c5eb7d | 25 | meta: wasi::Filestat, |
532ac7d7 XL |
26 | } |
27 | ||
28 | pub struct ReadDir { | |
29 | inner: Arc<ReadDirInner>, | |
60c5eb7d | 30 | cookie: Option<wasi::Dircookie>, |
532ac7d7 XL |
31 | buf: Vec<u8>, |
32 | offset: usize, | |
33 | cap: usize, | |
34 | } | |
35 | ||
36 | struct ReadDirInner { | |
37 | root: PathBuf, | |
38 | dir: File, | |
39 | } | |
40 | ||
41 | pub struct DirEntry { | |
e1599b0c | 42 | meta: wasi::Dirent, |
532ac7d7 XL |
43 | name: Vec<u8>, |
44 | inner: Arc<ReadDirInner>, | |
45 | } | |
46 | ||
47 | #[derive(Clone, Debug, Default)] | |
48 | pub struct OpenOptions { | |
49 | read: bool, | |
50 | write: bool, | |
3dfed10e | 51 | append: bool, |
60c5eb7d XL |
52 | dirflags: wasi::Lookupflags, |
53 | fdflags: wasi::Fdflags, | |
54 | oflags: wasi::Oflags, | |
e1599b0c XL |
55 | rights_base: Option<wasi::Rights>, |
56 | rights_inheriting: Option<wasi::Rights>, | |
532ac7d7 XL |
57 | } |
58 | ||
59 | #[derive(Clone, PartialEq, Eq, Debug)] | |
60 | pub struct FilePermissions { | |
61 | readonly: bool, | |
62 | } | |
63 | ||
64 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] | |
65 | pub struct FileType { | |
60c5eb7d | 66 | bits: wasi::Filetype, |
532ac7d7 XL |
67 | } |
68 | ||
69 | #[derive(Debug)] | |
70 | pub struct DirBuilder {} | |
71 | ||
72 | impl FileAttr { | |
532ac7d7 | 73 | pub fn size(&self) -> u64 { |
60c5eb7d | 74 | self.meta.size |
532ac7d7 XL |
75 | } |
76 | ||
77 | pub fn perm(&self) -> FilePermissions { | |
78 | // not currently implemented in wasi yet | |
79 | FilePermissions { readonly: false } | |
80 | } | |
81 | ||
82 | pub fn file_type(&self) -> FileType { | |
60c5eb7d | 83 | FileType { bits: self.meta.filetype } |
532ac7d7 XL |
84 | } |
85 | ||
86 | pub fn modified(&self) -> io::Result<SystemTime> { | |
60c5eb7d | 87 | Ok(SystemTime::from_wasi_timestamp(self.meta.mtim)) |
532ac7d7 XL |
88 | } |
89 | ||
90 | pub fn accessed(&self) -> io::Result<SystemTime> { | |
60c5eb7d | 91 | Ok(SystemTime::from_wasi_timestamp(self.meta.atim)) |
532ac7d7 XL |
92 | } |
93 | ||
94 | pub fn created(&self) -> io::Result<SystemTime> { | |
60c5eb7d | 95 | Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) |
532ac7d7 XL |
96 | } |
97 | ||
60c5eb7d | 98 | pub fn as_wasi(&self) -> &wasi::Filestat { |
532ac7d7 XL |
99 | &self.meta |
100 | } | |
101 | } | |
102 | ||
103 | impl FilePermissions { | |
104 | pub fn readonly(&self) -> bool { | |
105 | self.readonly | |
106 | } | |
107 | ||
108 | pub fn set_readonly(&mut self, readonly: bool) { | |
109 | self.readonly = readonly; | |
110 | } | |
111 | } | |
112 | ||
113 | impl FileType { | |
114 | pub fn is_dir(&self) -> bool { | |
e1599b0c | 115 | self.bits == wasi::FILETYPE_DIRECTORY |
532ac7d7 XL |
116 | } |
117 | ||
118 | pub fn is_file(&self) -> bool { | |
e1599b0c | 119 | self.bits == wasi::FILETYPE_REGULAR_FILE |
532ac7d7 XL |
120 | } |
121 | ||
122 | pub fn is_symlink(&self) -> bool { | |
e1599b0c | 123 | self.bits == wasi::FILETYPE_SYMBOLIC_LINK |
532ac7d7 XL |
124 | } |
125 | ||
60c5eb7d | 126 | pub fn bits(&self) -> wasi::Filetype { |
532ac7d7 XL |
127 | self.bits |
128 | } | |
129 | } | |
130 | ||
131 | impl fmt::Debug for ReadDir { | |
132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
133 | f.debug_struct("ReadDir").finish() | |
134 | } | |
135 | } | |
136 | ||
137 | impl Iterator for ReadDir { | |
138 | type Item = io::Result<DirEntry>; | |
139 | ||
140 | fn next(&mut self) -> Option<io::Result<DirEntry>> { | |
141 | loop { | |
142 | // If we've reached the capacity of our buffer then we need to read | |
143 | // some more from the OS, otherwise we pick up at our old offset. | |
144 | let offset = if self.offset == self.cap { | |
145 | let cookie = self.cookie.take()?; | |
146 | match self.inner.dir.fd.readdir(&mut self.buf, cookie) { | |
147 | Ok(bytes) => self.cap = bytes, | |
148 | Err(e) => return Some(Err(e)), | |
149 | } | |
150 | self.offset = 0; | |
151 | self.cookie = Some(cookie); | |
152 | ||
153 | // If we didn't actually read anything, this is in theory the | |
154 | // end of the directory. | |
155 | if self.cap == 0 { | |
156 | self.cookie = None; | |
157 | return None; | |
158 | } | |
159 | ||
160 | 0 | |
161 | } else { | |
162 | self.offset | |
163 | }; | |
164 | let data = &self.buf[offset..self.cap]; | |
165 | ||
166 | // If we're not able to read a directory entry then that means it | |
167 | // must have been truncated at the end of the buffer, so reset our | |
168 | // offset so we can go back and reread into the buffer, picking up | |
169 | // where we last left off. | |
e1599b0c | 170 | let dirent_size = mem::size_of::<wasi::Dirent>(); |
532ac7d7 XL |
171 | if data.len() < dirent_size { |
172 | assert!(self.cookie.is_some()); | |
173 | assert!(self.buf.len() >= dirent_size); | |
174 | self.offset = self.cap; | |
175 | continue; | |
176 | } | |
177 | let (dirent, data) = data.split_at(dirent_size); | |
60c5eb7d | 178 | let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; |
532ac7d7 XL |
179 | |
180 | // If the file name was truncated, then we need to reinvoke | |
181 | // `readdir` so we truncate our buffer to start over and reread this | |
182 | // descriptor. Note that if our offset is 0 that means the file name | |
183 | // is massive and we need a bigger buffer. | |
184 | if data.len() < dirent.d_namlen as usize { | |
185 | if offset == 0 { | |
186 | let amt_to_add = self.buf.capacity(); | |
187 | self.buf.extend(iter::repeat(0).take(amt_to_add)); | |
188 | } | |
189 | assert!(self.cookie.is_some()); | |
190 | self.offset = self.cap; | |
191 | continue; | |
192 | } | |
193 | self.cookie = Some(dirent.d_next); | |
194 | self.offset = offset + dirent_size + dirent.d_namlen as usize; | |
195 | ||
196 | let name = &data[..(dirent.d_namlen as usize)]; | |
197 | ||
198 | // These names are skipped on all other platforms, so let's skip | |
199 | // them here too | |
200 | if name == b"." || name == b".." { | |
201 | continue; | |
202 | } | |
203 | ||
204 | return Some(Ok(DirEntry { | |
205 | meta: dirent, | |
206 | name: name.to_vec(), | |
207 | inner: self.inner.clone(), | |
208 | })); | |
209 | } | |
210 | } | |
211 | } | |
212 | ||
213 | impl DirEntry { | |
214 | pub fn path(&self) -> PathBuf { | |
215 | let name = OsStr::from_bytes(&self.name); | |
216 | self.inner.root.join(name) | |
217 | } | |
218 | ||
219 | pub fn file_name(&self) -> OsString { | |
220 | OsString::from_vec(self.name.clone()) | |
221 | } | |
222 | ||
223 | pub fn metadata(&self) -> io::Result<FileAttr> { | |
60c5eb7d | 224 | metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) |
532ac7d7 XL |
225 | } |
226 | ||
227 | pub fn file_type(&self) -> io::Result<FileType> { | |
60c5eb7d | 228 | Ok(FileType { bits: self.meta.d_type }) |
532ac7d7 XL |
229 | } |
230 | ||
e1599b0c | 231 | pub fn ino(&self) -> wasi::Inode { |
532ac7d7 XL |
232 | self.meta.d_ino |
233 | } | |
234 | } | |
235 | ||
236 | impl OpenOptions { | |
237 | pub fn new() -> OpenOptions { | |
238 | let mut base = OpenOptions::default(); | |
60c5eb7d | 239 | base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW; |
532ac7d7 XL |
240 | return base; |
241 | } | |
242 | ||
243 | pub fn read(&mut self, read: bool) { | |
244 | self.read = read; | |
245 | } | |
246 | ||
247 | pub fn write(&mut self, write: bool) { | |
248 | self.write = write; | |
249 | } | |
250 | ||
251 | pub fn truncate(&mut self, truncate: bool) { | |
60c5eb7d | 252 | self.oflag(wasi::OFLAGS_TRUNC, truncate); |
532ac7d7 XL |
253 | } |
254 | ||
255 | pub fn create(&mut self, create: bool) { | |
60c5eb7d | 256 | self.oflag(wasi::OFLAGS_CREAT, create); |
532ac7d7 XL |
257 | } |
258 | ||
259 | pub fn create_new(&mut self, create_new: bool) { | |
60c5eb7d XL |
260 | self.oflag(wasi::OFLAGS_EXCL, create_new); |
261 | self.oflag(wasi::OFLAGS_CREAT, create_new); | |
532ac7d7 XL |
262 | } |
263 | ||
264 | pub fn directory(&mut self, directory: bool) { | |
60c5eb7d | 265 | self.oflag(wasi::OFLAGS_DIRECTORY, directory); |
532ac7d7 XL |
266 | } |
267 | ||
60c5eb7d | 268 | fn oflag(&mut self, bit: wasi::Oflags, set: bool) { |
532ac7d7 XL |
269 | if set { |
270 | self.oflags |= bit; | |
271 | } else { | |
272 | self.oflags &= !bit; | |
273 | } | |
274 | } | |
275 | ||
3dfed10e XL |
276 | pub fn append(&mut self, append: bool) { |
277 | self.append = append; | |
278 | self.fdflag(wasi::FDFLAGS_APPEND, append); | |
532ac7d7 XL |
279 | } |
280 | ||
281 | pub fn dsync(&mut self, set: bool) { | |
60c5eb7d | 282 | self.fdflag(wasi::FDFLAGS_DSYNC, set); |
532ac7d7 XL |
283 | } |
284 | ||
285 | pub fn nonblock(&mut self, set: bool) { | |
60c5eb7d | 286 | self.fdflag(wasi::FDFLAGS_NONBLOCK, set); |
532ac7d7 XL |
287 | } |
288 | ||
289 | pub fn rsync(&mut self, set: bool) { | |
60c5eb7d | 290 | self.fdflag(wasi::FDFLAGS_RSYNC, set); |
532ac7d7 XL |
291 | } |
292 | ||
293 | pub fn sync(&mut self, set: bool) { | |
60c5eb7d | 294 | self.fdflag(wasi::FDFLAGS_SYNC, set); |
532ac7d7 XL |
295 | } |
296 | ||
60c5eb7d | 297 | fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) { |
532ac7d7 XL |
298 | if set { |
299 | self.fdflags |= bit; | |
300 | } else { | |
301 | self.fdflags &= !bit; | |
302 | } | |
303 | } | |
304 | ||
e1599b0c | 305 | pub fn fs_rights_base(&mut self, rights: wasi::Rights) { |
532ac7d7 XL |
306 | self.rights_base = Some(rights); |
307 | } | |
308 | ||
e1599b0c | 309 | pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) { |
532ac7d7 XL |
310 | self.rights_inheriting = Some(rights); |
311 | } | |
312 | ||
e1599b0c | 313 | fn rights_base(&self) -> wasi::Rights { |
532ac7d7 XL |
314 | if let Some(rights) = self.rights_base { |
315 | return rights; | |
316 | } | |
317 | ||
318 | // If rights haven't otherwise been specified try to pick a reasonable | |
319 | // set. This can always be overridden by users via extension traits, and | |
320 | // implementations may give us fewer rights silently than we ask for. So | |
321 | // given that, just look at `read` and `write` and bucket permissions | |
322 | // based on that. | |
323 | let mut base = 0; | |
324 | if self.read { | |
60c5eb7d XL |
325 | base |= wasi::RIGHTS_FD_READ; |
326 | base |= wasi::RIGHTS_FD_READDIR; | |
532ac7d7 | 327 | } |
3dfed10e | 328 | if self.write || self.append { |
60c5eb7d XL |
329 | base |= wasi::RIGHTS_FD_WRITE; |
330 | base |= wasi::RIGHTS_FD_DATASYNC; | |
331 | base |= wasi::RIGHTS_FD_ALLOCATE; | |
332 | base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE; | |
532ac7d7 XL |
333 | } |
334 | ||
335 | // FIXME: some of these should probably be read-only or write-only... | |
60c5eb7d XL |
336 | base |= wasi::RIGHTS_FD_ADVISE; |
337 | base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS; | |
3dfed10e | 338 | base |= wasi::RIGHTS_FD_FILESTAT_GET; |
60c5eb7d XL |
339 | base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES; |
340 | base |= wasi::RIGHTS_FD_SEEK; | |
341 | base |= wasi::RIGHTS_FD_SYNC; | |
342 | base |= wasi::RIGHTS_FD_TELL; | |
343 | base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY; | |
344 | base |= wasi::RIGHTS_PATH_CREATE_FILE; | |
345 | base |= wasi::RIGHTS_PATH_FILESTAT_GET; | |
346 | base |= wasi::RIGHTS_PATH_LINK_SOURCE; | |
347 | base |= wasi::RIGHTS_PATH_LINK_TARGET; | |
348 | base |= wasi::RIGHTS_PATH_OPEN; | |
349 | base |= wasi::RIGHTS_PATH_READLINK; | |
350 | base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY; | |
351 | base |= wasi::RIGHTS_PATH_RENAME_SOURCE; | |
352 | base |= wasi::RIGHTS_PATH_RENAME_TARGET; | |
353 | base |= wasi::RIGHTS_PATH_SYMLINK; | |
354 | base |= wasi::RIGHTS_PATH_UNLINK_FILE; | |
355 | base |= wasi::RIGHTS_POLL_FD_READWRITE; | |
532ac7d7 XL |
356 | |
357 | return base; | |
358 | } | |
359 | ||
e1599b0c | 360 | fn rights_inheriting(&self) -> wasi::Rights { |
532ac7d7 XL |
361 | self.rights_inheriting.unwrap_or_else(|| self.rights_base()) |
362 | } | |
363 | ||
60c5eb7d | 364 | pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) { |
532ac7d7 XL |
365 | self.dirflags = flags; |
366 | } | |
367 | } | |
368 | ||
369 | impl File { | |
370 | pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { | |
60c5eb7d | 371 | let (dir, file) = open_parent(path)?; |
532ac7d7 XL |
372 | open_at(&dir, &file, opts) |
373 | } | |
374 | ||
375 | pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> { | |
376 | open_at(&self.fd, path, opts) | |
377 | } | |
378 | ||
379 | pub fn file_attr(&self) -> io::Result<FileAttr> { | |
e1599b0c | 380 | self.fd.filestat_get().map(|meta| FileAttr { meta }) |
532ac7d7 XL |
381 | } |
382 | ||
60c5eb7d | 383 | pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { |
532ac7d7 XL |
384 | metadata_at(&self.fd, flags, path) |
385 | } | |
386 | ||
387 | pub fn fsync(&self) -> io::Result<()> { | |
388 | self.fd.sync() | |
389 | } | |
390 | ||
391 | pub fn datasync(&self) -> io::Result<()> { | |
392 | self.fd.datasync() | |
393 | } | |
394 | ||
395 | pub fn truncate(&self, size: u64) -> io::Result<()> { | |
396 | self.fd.filestat_set_size(size) | |
397 | } | |
398 | ||
399 | pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { | |
48663c56 | 400 | self.read_vectored(&mut [IoSliceMut::new(buf)]) |
532ac7d7 XL |
401 | } |
402 | ||
48663c56 | 403 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { |
532ac7d7 XL |
404 | self.fd.read(bufs) |
405 | } | |
406 | ||
f9f354fc XL |
407 | #[inline] |
408 | pub fn is_read_vectored(&self) -> bool { | |
409 | true | |
410 | } | |
411 | ||
532ac7d7 | 412 | pub fn write(&self, buf: &[u8]) -> io::Result<usize> { |
48663c56 | 413 | self.write_vectored(&[IoSlice::new(buf)]) |
532ac7d7 XL |
414 | } |
415 | ||
48663c56 | 416 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { |
532ac7d7 XL |
417 | self.fd.write(bufs) |
418 | } | |
419 | ||
f9f354fc XL |
420 | #[inline] |
421 | pub fn is_write_vectored(&self) -> bool { | |
422 | true | |
423 | } | |
424 | ||
532ac7d7 XL |
425 | pub fn flush(&self) -> io::Result<()> { |
426 | Ok(()) | |
427 | } | |
428 | ||
429 | pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { | |
430 | self.fd.seek(pos) | |
431 | } | |
432 | ||
433 | pub fn duplicate(&self) -> io::Result<File> { | |
434 | // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup | |
435 | unsupported() | |
436 | } | |
437 | ||
438 | pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { | |
439 | // Permissions haven't been fully figured out in wasi yet, so this is | |
440 | // likely temporary | |
441 | unsupported() | |
442 | } | |
443 | ||
444 | pub fn fd(&self) -> &WasiFd { | |
445 | &self.fd | |
446 | } | |
447 | ||
448 | pub fn into_fd(self) -> WasiFd { | |
449 | self.fd | |
450 | } | |
451 | ||
452 | pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> { | |
453 | read_link(&self.fd, file) | |
454 | } | |
455 | } | |
456 | ||
457 | impl FromInner<u32> for File { | |
458 | fn from_inner(fd: u32) -> File { | |
60c5eb7d | 459 | unsafe { File { fd: WasiFd::from_raw(fd) } } |
532ac7d7 XL |
460 | } |
461 | } | |
462 | ||
463 | impl DirBuilder { | |
464 | pub fn new() -> DirBuilder { | |
465 | DirBuilder {} | |
466 | } | |
467 | ||
468 | pub fn mkdir(&self, p: &Path) -> io::Result<()> { | |
60c5eb7d XL |
469 | let (dir, file) = open_parent(p)?; |
470 | dir.create_directory(osstr2str(file.as_ref())?) | |
532ac7d7 XL |
471 | } |
472 | } | |
473 | ||
474 | impl fmt::Debug for File { | |
475 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
60c5eb7d | 476 | f.debug_struct("File").field("fd", &self.fd.as_raw()).finish() |
532ac7d7 XL |
477 | } |
478 | } | |
479 | ||
480 | pub fn readdir(p: &Path) -> io::Result<ReadDir> { | |
481 | let mut opts = OpenOptions::new(); | |
482 | opts.directory(true); | |
483 | opts.read(true); | |
484 | let dir = File::open(p, &opts)?; | |
485 | Ok(ReadDir { | |
486 | cookie: Some(0), | |
487 | buf: vec![0; 128], | |
488 | offset: 0, | |
489 | cap: 0, | |
60c5eb7d | 490 | inner: Arc::new(ReadDirInner { dir, root: p.to_path_buf() }), |
532ac7d7 XL |
491 | }) |
492 | } | |
493 | ||
494 | pub fn unlink(p: &Path) -> io::Result<()> { | |
60c5eb7d XL |
495 | let (dir, file) = open_parent(p)?; |
496 | dir.unlink_file(osstr2str(file.as_ref())?) | |
532ac7d7 XL |
497 | } |
498 | ||
499 | pub fn rename(old: &Path, new: &Path) -> io::Result<()> { | |
60c5eb7d XL |
500 | let (old, old_file) = open_parent(old)?; |
501 | let (new, new_file) = open_parent(new)?; | |
502 | old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?) | |
532ac7d7 XL |
503 | } |
504 | ||
505 | pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { | |
506 | // Permissions haven't been fully figured out in wasi yet, so this is | |
507 | // likely temporary | |
508 | unsupported() | |
509 | } | |
510 | ||
511 | pub fn rmdir(p: &Path) -> io::Result<()> { | |
60c5eb7d XL |
512 | let (dir, file) = open_parent(p)?; |
513 | dir.remove_directory(osstr2str(file.as_ref())?) | |
532ac7d7 XL |
514 | } |
515 | ||
516 | pub fn readlink(p: &Path) -> io::Result<PathBuf> { | |
60c5eb7d | 517 | let (dir, file) = open_parent(p)?; |
532ac7d7 XL |
518 | read_link(&dir, &file) |
519 | } | |
520 | ||
521 | fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> { | |
522 | // Try to get a best effort initial capacity for the vector we're going to | |
523 | // fill. Note that if it's not a symlink we don't use a file to avoid | |
524 | // allocating gigabytes if you read_link a huge movie file by accident. | |
525 | // Additionally we add 1 to the initial size so if it doesn't change until | |
526 | // when we call `readlink` the returned length will be less than the | |
527 | // capacity, guaranteeing that we got all the data. | |
528 | let meta = metadata_at(fd, 0, file)?; | |
529 | let initial_size = if meta.file_type().is_symlink() { | |
530 | (meta.size() as usize).saturating_add(1) | |
531 | } else { | |
532 | 1 // this'll fail in just a moment | |
533 | }; | |
534 | ||
535 | // Now that we have an initial guess of how big to make our buffer, call | |
536 | // `readlink` in a loop until it fails or reports it filled fewer bytes than | |
537 | // we asked for, indicating we got everything. | |
60c5eb7d | 538 | let file = osstr2str(file.as_ref())?; |
532ac7d7 XL |
539 | let mut destination = vec![0u8; initial_size]; |
540 | loop { | |
541 | let len = fd.readlink(file, &mut destination)?; | |
542 | if len < destination.len() { | |
543 | destination.truncate(len); | |
544 | destination.shrink_to_fit(); | |
545 | return Ok(PathBuf::from(OsString::from_vec(destination))); | |
546 | } | |
547 | let amt_to_add = destination.len(); | |
548 | destination.extend(iter::repeat(0).take(amt_to_add)); | |
549 | } | |
550 | } | |
551 | ||
fc512014 XL |
552 | pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { |
553 | let (link, link_file) = open_parent(link)?; | |
554 | link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?) | |
532ac7d7 XL |
555 | } |
556 | ||
fc512014 XL |
557 | pub fn link(original: &Path, link: &Path) -> io::Result<()> { |
558 | let (original, original_file) = open_parent(original)?; | |
559 | let (link, link_file) = open_parent(link)?; | |
560 | original.link( | |
60c5eb7d | 561 | wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, |
fc512014 XL |
562 | osstr2str(original_file.as_ref())?, |
563 | &link, | |
564 | osstr2str(link_file.as_ref())?, | |
532ac7d7 XL |
565 | ) |
566 | } | |
567 | ||
568 | pub fn stat(p: &Path) -> io::Result<FileAttr> { | |
60c5eb7d XL |
569 | let (dir, file) = open_parent(p)?; |
570 | metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) | |
532ac7d7 XL |
571 | } |
572 | ||
573 | pub fn lstat(p: &Path) -> io::Result<FileAttr> { | |
60c5eb7d | 574 | let (dir, file) = open_parent(p)?; |
532ac7d7 XL |
575 | metadata_at(&dir, 0, &file) |
576 | } | |
577 | ||
60c5eb7d XL |
578 | fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { |
579 | let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?; | |
580 | Ok(FileAttr { meta }) | |
532ac7d7 XL |
581 | } |
582 | ||
583 | pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> { | |
584 | // This seems to not be in wasi's API yet, and we may need to end up | |
585 | // emulating it ourselves. For now just return an error. | |
586 | unsupported() | |
587 | } | |
588 | ||
589 | fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> { | |
590 | let fd = fd.open( | |
591 | opts.dirflags, | |
60c5eb7d | 592 | osstr2str(path.as_ref())?, |
532ac7d7 XL |
593 | opts.oflags, |
594 | opts.rights_base(), | |
595 | opts.rights_inheriting(), | |
596 | opts.fdflags, | |
597 | )?; | |
598 | Ok(File { fd }) | |
599 | } | |
600 | ||
601 | /// Attempts to open a bare path `p`. | |
602 | /// | |
603 | /// WASI has no fundamental capability to do this. All syscalls and operations | |
604 | /// are relative to already-open file descriptors. The C library, however, | |
3dfed10e | 605 | /// manages a map of pre-opened file descriptors to their path, and then the C |
532ac7d7 XL |
606 | /// library provides an API to look at this. In other words, when you want to |
607 | /// open a path `p`, you have to find a previously opened file descriptor in a | |
608 | /// global table and then see if `p` is relative to that file descriptor. | |
609 | /// | |
610 | /// This function, if successful, will return two items: | |
611 | /// | |
3dfed10e | 612 | /// * The first is a `ManuallyDrop<WasiFd>`. This represents a pre-opened file |
532ac7d7 XL |
613 | /// descriptor which we don't have ownership of, but we can use. You shouldn't |
614 | /// actually drop the `fd`. | |
615 | /// | |
616 | /// * The second is a path that should be a part of `p` and represents a | |
617 | /// relative traversal from the file descriptor specified to the desired | |
618 | /// location `p`. | |
619 | /// | |
620 | /// If successful you can use the returned file descriptor to perform | |
621 | /// file-descriptor-relative operations on the path returned as well. The | |
622 | /// `rights` argument indicates what operations are desired on the returned file | |
623 | /// descriptor, and if successful the returned file descriptor should have the | |
624 | /// appropriate rights for performing `rights` actions. | |
625 | /// | |
626 | /// Note that this can fail if `p` doesn't look like it can be opened relative | |
3dfed10e | 627 | /// to any pre-opened file descriptor. |
60c5eb7d | 628 | fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> { |
532ac7d7 XL |
629 | let p = CString::new(p.as_os_str().as_bytes())?; |
630 | unsafe { | |
631 | let mut ret = ptr::null(); | |
60c5eb7d | 632 | let fd = __wasilibc_find_relpath(p.as_ptr(), &mut ret); |
532ac7d7 XL |
633 | if fd == -1 { |
634 | let msg = format!( | |
3dfed10e | 635 | "failed to find a pre-opened file descriptor \ |
532ac7d7 XL |
636 | through which {:?} could be opened", |
637 | p | |
638 | ); | |
639 | return Err(io::Error::new(io::ErrorKind::Other, msg)); | |
640 | } | |
641 | let path = Path::new(OsStr::from_bytes(CStr::from_ptr(ret).to_bytes())); | |
642 | ||
643 | // FIXME: right now `path` is a pointer into `p`, the `CString` above. | |
644 | // When we return `p` is deallocated and we can't use it, so we need to | |
645 | // currently separately allocate `path`. If this becomes an issue though | |
646 | // we should probably turn this into a closure-taking interface or take | |
647 | // `&CString` and then pass off `&Path` tied to the same lifetime. | |
648 | let path = path.to_path_buf(); | |
649 | ||
650 | return Ok((ManuallyDrop::new(WasiFd::from_raw(fd as u32)), path)); | |
651 | } | |
60c5eb7d XL |
652 | |
653 | extern "C" { | |
654 | pub fn __wasilibc_find_relpath( | |
655 | path: *const libc::c_char, | |
656 | relative_path: *mut *const libc::c_char, | |
657 | ) -> libc::c_int; | |
658 | } | |
659 | } | |
660 | ||
661 | pub fn osstr2str(f: &OsStr) -> io::Result<&str> { | |
662 | f.to_str().ok_or_else(|| io::Error::new(io::ErrorKind::Other, "input must be utf-8")) | |
532ac7d7 | 663 | } |
74b04a01 XL |
664 | |
665 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | |
666 | use crate::fs::File; | |
667 | ||
668 | let mut reader = File::open(from)?; | |
669 | let mut writer = File::create(to)?; | |
670 | ||
671 | io::copy(&mut reader, &mut writer) | |
672 | } |