1use super::*;
2use crate::VIRTUAL_ROOT_FD;
3use crate::fs::{FdList, WasiFs};
4use crate::syscalls::*;
5
6#[instrument(level = "trace", skip_all, fields(%dirfd, path = field::Empty, follow_symlinks = field::Empty, ret_fd = field::Empty), ret)]
31pub fn path_open2<M: MemorySize>(
32 mut ctx: FunctionEnvMut<'_, WasiEnv>,
33 dirfd: WasiFd,
34 dirflags: LookupFlags,
35 path: WasmPtr<u8, M>,
36 path_len: M::Offset,
37 o_flags: Oflags,
38 fs_rights_base: Rights,
39 fs_rights_inheriting: Rights,
40 fs_flags: Fdflags,
41 fd_flags: Fdflagsext,
42 fd: WasmPtr<WasiFd, M>,
43) -> Result<Errno, WasiError> {
44 WasiEnv::do_pending_operations(&mut ctx)?;
45
46 if dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 {
47 Span::current().record("follow_symlinks", true);
48 }
49 let env = ctx.data();
50 let (memory, mut state, mut inodes) =
51 unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
52 let path_len64: u64 = path_len.into();
54 if path_len64 > 1024u64 * 1024u64 {
55 return Ok(Errno::Nametoolong);
56 }
57
58 if path_len64 == 0 {
59 return Ok(Errno::Noent);
60 }
61
62 let path_string = unsafe { get_input_str_ok!(&memory, path, path_len) };
69 Span::current().record("path", path_string.as_str());
70
71 let out_fd = wasi_try_ok!(path_open_internal(
72 ctx.data(),
73 dirfd,
74 dirflags,
75 &path_string,
76 o_flags,
77 fs_rights_base,
78 fs_rights_inheriting,
79 fs_flags,
80 fd_flags,
81 None,
82 )?);
83 let env = ctx.data();
84
85 #[cfg(feature = "journal")]
86 if env.enable_journal {
87 JournalEffector::save_path_open(
88 &mut ctx,
89 out_fd,
90 dirfd,
91 dirflags,
92 path_string,
93 o_flags,
94 fs_rights_base,
95 fs_rights_inheriting,
96 fs_flags,
97 fd_flags,
98 )
99 .map_err(|err| {
100 tracing::error!("failed to save unlink event - {}", err);
101 WasiError::Exit(ExitCode::from(Errno::Fault))
102 })?;
103 }
104
105 let env = ctx.data();
106 let (memory, mut state, mut inodes) =
107 unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
108
109 Span::current().record("ret_fd", out_fd);
110
111 let fd_ref = fd.deref(&memory);
112 wasi_try_mem_ok!(fd_ref.write(out_fd));
113
114 Ok(Errno::Success)
115}
116
117pub(crate) fn path_open_internal(
159 env: &WasiEnv,
160 dirfd: WasiFd,
161 dirflags: LookupFlags,
162 path: &str,
163 o_flags: Oflags,
164 fs_rights_base: Rights,
165 fs_rights_inheriting: Rights,
166 fs_flags: Fdflags,
167 fd_flags: Fdflagsext,
168 with_fd: Option<WasiFd>,
169) -> Result<Result<WasiFd, Errno>, WasiError> {
170 path_open_internal_with_symlink_depth(
171 env,
172 dirfd,
173 dirflags,
174 path,
175 o_flags,
176 fs_rights_base,
177 fs_rights_inheriting,
178 fs_flags,
179 fd_flags,
180 with_fd,
181 0,
182 )
183}
184
185#[allow(clippy::too_many_arguments)]
186fn path_open_internal_with_symlink_depth(
187 env: &WasiEnv,
188 dirfd: WasiFd,
189 dirflags: LookupFlags,
190 path: &str,
191 o_flags: Oflags,
192 fs_rights_base: Rights,
193 fs_rights_inheriting: Rights,
194 fs_flags: Fdflags,
195 fd_flags: Fdflagsext,
196 with_fd: Option<WasiFd>,
197 symlink_depth: u32,
198) -> Result<Result<WasiFd, Errno>, WasiError> {
199 fn implied_fd_rights(has_read_access: bool, has_write_access: bool) -> Rights {
200 let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK;
201
202 if has_read_access {
203 rights |= Rights::FD_READ | Rights::FD_FILESTAT_GET;
204 }
205
206 if has_write_access {
207 rights |= Rights::FD_DATASYNC
208 | Rights::FD_FDSTAT_SET_FLAGS
209 | Rights::FD_WRITE
210 | Rights::FD_SYNC
211 | Rights::FD_ALLOCATE
212 | Rights::FD_FILESTAT_GET
213 | Rights::FD_FILESTAT_SET_SIZE
214 | Rights::FD_FILESTAT_SET_TIMES;
215 }
216
217 rights
218 }
219
220 let state = env.state.deref();
221 let inodes = &state.inodes;
222 let follow_symlinks = dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0;
223 let effective_dirfd = if path.starts_with('/') {
224 VIRTUAL_ROOT_FD
225 } else {
226 dirfd
227 };
228 let path_arg = std::path::PathBuf::from(path);
229 let working_dir = match state.fs.get_fd(effective_dirfd) {
230 Ok(fd) => fd,
231 Err(err) => return Ok(Err(err)),
232 };
233 let maybe_inode = state
234 .fs
235 .get_inode_at_path(inodes, effective_dirfd, path, follow_symlinks);
236 let working_dir_rights_inheriting = working_dir.inner.rights_inheriting;
237
238 if !working_dir.inner.rights.contains(Rights::PATH_OPEN) {
240 return Ok(Err(Errno::Access));
241 }
242
243 let mut open_flags = 0;
244 let has_read_access = fs_rights_base.contains(Rights::FD_READ);
249 let has_write_access = fs_rights_base.contains(Rights::FD_WRITE)
250 || fs_flags.contains(Fdflags::APPEND)
251 || o_flags.contains(Oflags::TRUNC)
252 || o_flags.contains(Oflags::CREATE);
253 let requested_base_rights =
254 fs_rights_base | implied_fd_rights(has_read_access, has_write_access);
255
256 let adjusted_rights = requested_base_rights & working_dir_rights_inheriting;
259 let adjusted_rights_inheriting = fs_rights_inheriting & working_dir_rights_inheriting;
260 let mut open_options = state.fs_new_open_options();
261
262 let target_rights = match maybe_inode {
263 Ok(_) => {
264 let write_permission = adjusted_rights.contains(Rights::FD_WRITE);
265
266 let (append_permission, truncate_permission, create_permission) = if write_permission {
268 (
269 fs_flags.contains(Fdflags::APPEND),
270 o_flags.contains(Oflags::TRUNC),
271 o_flags.contains(Oflags::CREATE),
272 )
273 } else {
274 (false, false, false)
275 };
276
277 virtual_fs::OpenOptionsConfig {
278 read: adjusted_rights.contains(Rights::FD_READ),
279 write: write_permission,
280 create_new: create_permission && o_flags.contains(Oflags::EXCL),
281 create: create_permission,
282 append: append_permission,
283 truncate: truncate_permission,
284 }
285 }
286 Err(_) => virtual_fs::OpenOptionsConfig {
287 append: fs_flags.contains(Fdflags::APPEND),
288 write: adjusted_rights.contains(Rights::FD_WRITE),
289 read: adjusted_rights.contains(Rights::FD_READ),
290 create_new: o_flags.contains(Oflags::CREATE) && o_flags.contains(Oflags::EXCL),
291 create: o_flags.contains(Oflags::CREATE),
292 truncate: o_flags.contains(Oflags::TRUNC),
293 },
294 };
295
296 let parent_rights = virtual_fs::OpenOptionsConfig {
297 read: working_dir.inner.rights.contains(Rights::FD_READ),
298 write: working_dir.inner.rights.contains(Rights::FD_WRITE),
299 create_new: true,
302 create: true,
303 append: true,
304 truncate: true,
305 };
306
307 let minimum_rights = target_rights.minimum_rights(&parent_rights);
308
309 open_options.options(minimum_rights.clone());
310
311 let open_shared_file_handle =
317 |path: &std::path::Path,
318 requested_config: virtual_fs::OpenOptionsConfig,
319 shared_config: virtual_fs::OpenOptionsConfig|
320 -> Result<Box<dyn VirtualFile + Send + Sync + 'static>, Errno> {
321 let mut open_options = state.fs_new_open_options();
322 open_options.options(shared_config.clone());
323 match open_options.open(path) {
324 Ok(handle) => Ok(handle),
325 Err(FsError::PermissionDenied)
326 if shared_config.read != requested_config.read
327 || shared_config.write != requested_config.write =>
328 {
329 let mut open_options = state.fs_new_open_options();
330 open_options.options(requested_config);
331 open_options.open(path).map_err(fs_error_into_wasi_err)
332 }
333 Err(err) => Err(fs_error_into_wasi_err(err)),
334 }
335 };
336
337 let orig_path = path;
338
339 if let Ok(inode) = maybe_inode {
340 {
342 let guard = inode.read();
343 if let Kind::Symlink {
344 symlink_kind,
345 path_to_symlink,
346 relative_path,
347 } = guard.deref()
348 {
349 if !follow_symlinks {
350 return Ok(Err(Errno::Loop));
351 }
352
353 let (resolved_base_fd, resolved_path) = match state.fs.resolve_symlink_target_path(
354 *symlink_kind,
355 path_to_symlink,
356 relative_path,
357 ) {
358 Ok(resolved) => resolved,
359 Err(err) => return Ok(Err(err)),
360 };
361 let next_symlink_depth = symlink_depth + 1;
362 if next_symlink_depth > MAX_SYMLINKS {
363 return Ok(Err(Errno::Loop));
364 }
365 let resolved_path = crate::fs::PosixPath::from_path(&resolved_path)
366 .as_str()
367 .to_owned();
368 drop(guard);
369 return path_open_internal_with_symlink_depth(
370 env,
371 resolved_base_fd,
372 __WASI_LOOKUP_SYMLINK_FOLLOW,
373 &resolved_path,
374 o_flags,
375 fs_rights_base,
376 fs_rights_inheriting,
377 fs_flags,
378 fd_flags,
379 with_fd,
380 next_symlink_depth,
381 );
382 }
383 }
384
385 if o_flags.contains(Oflags::EXCL) && o_flags.contains(Oflags::CREATE) {
386 return Ok(Err(Errno::Exist));
387 }
388
389 let file_requested_config = open_options
391 .write(minimum_rights.write)
392 .create(minimum_rights.create)
393 .append(false)
394 .truncate(minimum_rights.truncate)
395 .get_config();
396 let file_shared_config = virtual_fs::OpenOptionsConfig {
397 read: true,
398 write: true,
399 ..file_requested_config.clone()
400 };
401 let requires_stronger_handle =
402 minimum_rights.write || minimum_rights.truncate || minimum_rights.create;
403 let mut file_open_flags = open_flags;
404 if minimum_rights.read {
405 file_open_flags |= Fd::READ;
406 }
407 if minimum_rights.write {
408 file_open_flags |= Fd::WRITE;
409 }
410 if minimum_rights.create {
411 file_open_flags |= Fd::CREATE;
412 }
413 if minimum_rights.truncate {
414 file_open_flags |= Fd::TRUNCATE;
415 }
416
417 let mut fd_map = state.fs.fd_map.write().unwrap();
421 let mut guard = inode.write();
422 let out_fd = match guard.deref_mut() {
423 Kind::File {
424 handle,
425 path,
426 fd: Some(special_fd),
427 ..
428 } => {
429 assert!(handle.is_some());
430 *special_fd
431 }
432 Kind::File {
433 handle,
434 path,
435 fd: None,
436 ..
437 } => {
438 if o_flags.contains(Oflags::DIRECTORY) || orig_path.ends_with('/') {
439 return Ok(Err(Errno::Notdir));
440 }
441
442 if handle.is_none() || requires_stronger_handle {
446 let file = wasi_try_ok_ok!(open_shared_file_handle(
447 path.as_path(),
448 file_requested_config.clone(),
449 file_shared_config.clone(),
450 ));
451 if handle.is_none() {
452 *handle = Some(Arc::new(std::sync::RwLock::new(file)));
453 } else {
454 let mut existing = handle.as_ref().unwrap().write().unwrap();
455 *existing = file;
456 }
457 }
458
459 if let Some(file_handle) = handle.as_ref()
460 && let Some(special_fd) = {
461 let file = file_handle.read().unwrap();
462 file.get_special_fd()
463 }
464 {
465 drop(guard);
466 let dup_fd = wasi_try_ok_ok!(WasiFs::clone_fd_locked(
467 &state.fs,
468 &mut fd_map,
469 special_fd,
470 0,
471 None,
472 ));
473 trace!(%dup_fd);
474 return Ok(Ok(dup_fd));
475 }
476
477 let out_fd = wasi_try_ok_ok!(insert_fd_locked(
478 &mut fd_map,
479 state,
480 adjusted_rights,
481 adjusted_rights_inheriting,
482 fs_flags,
483 fd_flags,
484 file_open_flags,
485 inode.clone(),
486 with_fd,
487 ));
488 drop(guard);
489 out_fd
490 }
491 Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"),
492 Kind::Root { .. } => {
493 if !o_flags.contains(Oflags::DIRECTORY) {
494 return Ok(Err(Errno::Isdir));
495 }
496 drop(guard);
497 wasi_try_ok_ok!(insert_fd_locked(
498 &mut fd_map,
499 state,
500 adjusted_rights,
501 adjusted_rights_inheriting,
502 fs_flags,
503 fd_flags,
504 open_flags,
505 inode,
506 with_fd,
507 ))
508 }
509 Kind::Dir { .. } => {
510 if fs_rights_base.contains(Rights::FD_WRITE) {
511 return Ok(Err(Errno::Isdir));
512 }
513 drop(guard);
514 wasi_try_ok_ok!(insert_fd_locked(
515 &mut fd_map,
516 state,
517 adjusted_rights,
518 adjusted_rights_inheriting,
519 fs_flags,
520 fd_flags,
521 open_flags,
522 inode,
523 with_fd,
524 ))
525 }
526 Kind::Socket { .. }
527 | Kind::PipeTx { .. }
528 | Kind::PipeRx { .. }
529 | Kind::DuplexPipe { .. }
530 | Kind::EventNotifications { .. }
531 | Kind::Epoll { .. } => {
532 drop(guard);
533 wasi_try_ok_ok!(insert_fd_locked(
534 &mut fd_map,
535 state,
536 adjusted_rights,
537 adjusted_rights_inheriting,
538 fs_flags,
539 fd_flags,
540 open_flags,
541 inode,
542 with_fd,
543 ))
544 }
545 Kind::Symlink { .. } => return Ok(Err(Errno::Loop)),
546 };
547 Ok(Ok(out_fd))
548 } else {
549 if o_flags.contains(Oflags::CREATE) {
551 if o_flags.contains(Oflags::DIRECTORY) {
552 return Ok(Err(Errno::Notdir));
553 }
554
555 if path.ends_with('/') {
557 return Ok(Err(Errno::Isdir));
558 }
559
560 if follow_symlinks {
566 let final_symlink_target_lookup =
567 state
568 .fs
569 .get_inode_at_path(inodes, effective_dirfd, path, false);
570 let final_symlink_target = match final_symlink_target_lookup {
571 Ok(inode) => {
572 let guard = inode.read();
573 match guard.deref() {
574 Kind::Symlink {
575 symlink_kind,
576 path_to_symlink,
577 relative_path,
578 } => {
579 match state.fs.resolve_symlink_target_path(
580 *symlink_kind,
581 path_to_symlink,
582 relative_path,
583 ) {
584 Ok(resolved) => Some(resolved),
585 Err(err) => return Ok(Err(err)),
586 }
587 }
588 _ => None,
589 }
590 }
591 Err(_) => None,
592 };
593
594 if let Some((resolved_base_fd, resolved_path)) = final_symlink_target {
595 if o_flags.contains(Oflags::EXCL) {
596 return Ok(Err(Errno::Exist));
597 }
598
599 let resolved_path = crate::fs::PosixPath::from_path(&resolved_path)
600 .as_str()
601 .to_owned();
602 let next_symlink_depth = symlink_depth + 1;
603 if next_symlink_depth > MAX_SYMLINKS {
604 return Ok(Err(Errno::Loop));
605 }
606 return path_open_internal_with_symlink_depth(
607 env,
608 resolved_base_fd,
609 __WASI_LOOKUP_SYMLINK_FOLLOW,
610 &resolved_path,
611 o_flags,
612 fs_rights_base,
613 fs_rights_inheriting,
614 fs_flags,
615 fd_flags,
616 with_fd,
617 next_symlink_depth,
618 );
619 }
620 }
621
622 let (parent_inode, new_entity_name) =
625 wasi_try_ok_ok!(state.fs.get_parent_inode_at_path(
626 inodes,
627 effective_dirfd,
628 &path_arg,
629 follow_symlinks
630 ));
631 let new_file_host_path = {
632 let guard = parent_inode.read();
633 match guard.deref() {
634 Kind::Dir { path, .. } => crate::fs::PosixPath::from_path(path)
635 .join(&crate::fs::PosixPath::new(&new_entity_name))
636 .into_path_buf(),
637 Kind::Root { .. } => return Ok(Err(Errno::Perm)),
638 _ => return Ok(Err(Errno::Notdir)),
639 }
640 };
641 let mut fd_map = state.fs.fd_map.write().unwrap();
644
645 let requested_config = open_options
646 .read(minimum_rights.read)
647 .append(minimum_rights.append)
648 .write(minimum_rights.write)
649 .create_new(true)
650 .get_config();
651 let shared_config = virtual_fs::OpenOptionsConfig {
652 read: true,
653 write: true,
654 ..requested_config.clone()
655 };
656
657 if minimum_rights.read {
658 open_flags |= Fd::READ;
659 }
660 if minimum_rights.write {
661 open_flags |= Fd::WRITE;
662 }
663 if minimum_rights.create_new {
664 open_flags |= Fd::CREATE;
665 }
666 if minimum_rights.truncate {
667 open_flags |= Fd::TRUNCATE;
668 }
669
670 let handle = match open_shared_file_handle(
671 new_file_host_path.as_path(),
672 requested_config,
673 shared_config,
674 ) {
675 Ok(handle) => Some(handle),
676 Err(err) => {
677 if err == Errno::Exist {
678 return Ok(Err(Errno::Perm));
679 }
680 return Ok(Err(err));
681 }
682 };
683
684 let new_inode = {
685 let kind = Kind::File {
686 handle: handle.map(|a| Arc::new(std::sync::RwLock::new(a))),
687 path: new_file_host_path,
688 fd: None,
689 };
690 wasi_try_ok_ok!(
691 state
692 .fs
693 .create_inode(inodes, kind, false, new_entity_name.clone())
694 )
695 };
696
697 {
698 let mut guard = parent_inode.write();
699 if let Kind::Dir { entries, .. } = guard.deref_mut() {
700 entries.insert(new_entity_name, new_inode.clone());
701 }
702 }
703
704 Ok(Ok(wasi_try_ok_ok!(insert_fd_locked(
705 &mut fd_map,
706 state,
707 adjusted_rights,
708 adjusted_rights_inheriting,
709 fs_flags,
710 fd_flags,
711 open_flags,
712 new_inode,
713 with_fd,
714 ))))
715 } else {
716 Ok(Err(maybe_inode.unwrap_err()))
717 }
718 }
719}
720
721fn insert_fd_locked(
722 fd_map: &mut FdList,
723 _state: &WasiState,
724 adjusted_rights: Rights,
725 adjusted_rights_inheriting: Rights,
726 fs_flags: Fdflags,
727 fd_flags: Fdflagsext,
728 open_flags: u16,
729 inode: InodeGuard,
730 with_fd: Option<WasiFd>,
731) -> Result<WasiFd, Errno> {
732 WasiFs::insert_fd_locked(
735 fd_map,
736 adjusted_rights,
737 adjusted_rights_inheriting,
738 fs_flags,
739 fd_flags,
740 open_flags,
741 inode,
742 with_fd,
743 with_fd.is_some(),
744 )
745}