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