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(
118 env: &WasiEnv,
119 dirfd: WasiFd,
120 dirflags: LookupFlags,
121 path: &str,
122 o_flags: Oflags,
123 fs_rights_base: Rights,
124 fs_rights_inheriting: Rights,
125 fs_flags: Fdflags,
126 fd_flags: Fdflagsext,
127 with_fd: Option<WasiFd>,
128) -> Result<Result<WasiFd, Errno>, WasiError> {
129 fn implied_fd_rights(has_read_access: bool, has_write_access: bool) -> Rights {
130 let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK;
131
132 if has_read_access {
133 rights |= Rights::FD_READ | Rights::FD_FILESTAT_GET;
134 }
135
136 if has_write_access {
137 rights |= Rights::FD_DATASYNC
138 | Rights::FD_FDSTAT_SET_FLAGS
139 | Rights::FD_WRITE
140 | Rights::FD_SYNC
141 | Rights::FD_ALLOCATE
142 | Rights::FD_FILESTAT_GET
143 | Rights::FD_FILESTAT_SET_SIZE
144 | Rights::FD_FILESTAT_SET_TIMES;
145 }
146
147 rights
148 }
149
150 let state = env.state.deref();
151 let inodes = &state.inodes;
152 let follow_symlinks = dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0;
153 let effective_dirfd = if path.starts_with('/') {
154 VIRTUAL_ROOT_FD
155 } else {
156 dirfd
157 };
158 let path_arg = std::path::PathBuf::from(path);
159 let working_dir = match state.fs.get_fd(effective_dirfd) {
160 Ok(fd) => fd,
161 Err(err) => return Ok(Err(err)),
162 };
163 let maybe_inode = state
164 .fs
165 .get_inode_at_path(inodes, effective_dirfd, path, follow_symlinks);
166 let working_dir_rights_inheriting = working_dir.inner.rights_inheriting;
167
168 if !working_dir.inner.rights.contains(Rights::PATH_OPEN) {
170 return Ok(Err(Errno::Access));
171 }
172
173 let mut open_flags = 0;
174 let has_read_access = fs_rights_base.contains(Rights::FD_READ);
179 let has_write_access = fs_rights_base.contains(Rights::FD_WRITE)
180 || fs_flags.contains(Fdflags::APPEND)
181 || o_flags.contains(Oflags::TRUNC)
182 || o_flags.contains(Oflags::CREATE);
183 let requested_base_rights =
184 fs_rights_base | implied_fd_rights(has_read_access, has_write_access);
185
186 let adjusted_rights = requested_base_rights & working_dir_rights_inheriting;
189 let adjusted_rights_inheriting = fs_rights_inheriting & working_dir_rights_inheriting;
190 let mut open_options = state.fs_new_open_options();
191
192 let target_rights = match maybe_inode {
193 Ok(_) => {
194 let write_permission = adjusted_rights.contains(Rights::FD_WRITE);
195
196 let (append_permission, truncate_permission, create_permission) = if write_permission {
198 (
199 fs_flags.contains(Fdflags::APPEND),
200 o_flags.contains(Oflags::TRUNC),
201 o_flags.contains(Oflags::CREATE),
202 )
203 } else {
204 (false, false, false)
205 };
206
207 virtual_fs::OpenOptionsConfig {
208 read: adjusted_rights.contains(Rights::FD_READ),
209 write: write_permission,
210 create_new: create_permission && o_flags.contains(Oflags::EXCL),
211 create: create_permission,
212 append: append_permission,
213 truncate: truncate_permission,
214 }
215 }
216 Err(_) => virtual_fs::OpenOptionsConfig {
217 append: fs_flags.contains(Fdflags::APPEND),
218 write: adjusted_rights.contains(Rights::FD_WRITE),
219 read: adjusted_rights.contains(Rights::FD_READ),
220 create_new: o_flags.contains(Oflags::CREATE) && o_flags.contains(Oflags::EXCL),
221 create: o_flags.contains(Oflags::CREATE),
222 truncate: o_flags.contains(Oflags::TRUNC),
223 },
224 };
225
226 let parent_rights = virtual_fs::OpenOptionsConfig {
227 read: working_dir.inner.rights.contains(Rights::FD_READ),
228 write: working_dir.inner.rights.contains(Rights::FD_WRITE),
229 create_new: true,
232 create: true,
233 append: true,
234 truncate: true,
235 };
236
237 let minimum_rights = target_rights.minimum_rights(&parent_rights);
238
239 open_options.options(minimum_rights.clone());
240
241 let open_shared_file_handle =
247 |path: &std::path::Path,
248 requested_config: virtual_fs::OpenOptionsConfig,
249 shared_config: virtual_fs::OpenOptionsConfig|
250 -> Result<Box<dyn VirtualFile + Send + Sync + 'static>, Errno> {
251 let mut open_options = state.fs_new_open_options();
252 open_options.options(shared_config.clone());
253 match open_options.open(path) {
254 Ok(handle) => Ok(handle),
255 Err(FsError::PermissionDenied)
256 if shared_config.read != requested_config.read
257 || shared_config.write != requested_config.write =>
258 {
259 let mut open_options = state.fs_new_open_options();
260 open_options.options(requested_config);
261 open_options.open(path).map_err(fs_error_into_wasi_err)
262 }
263 Err(err) => Err(fs_error_into_wasi_err(err)),
264 }
265 };
266
267 let orig_path = path;
268
269 if let Ok(inode) = maybe_inode {
270 {
272 let guard = inode.read();
273 if let Kind::Symlink {
274 base_po_dir,
275 path_to_symlink,
276 relative_path,
277 } = guard.deref()
278 {
279 let (resolved_base_fd, resolved_path) = if relative_path.is_absolute() {
280 (VIRTUAL_ROOT_FD, relative_path.clone())
281 } else {
282 let mut resolved_path = path_to_symlink.clone();
283 resolved_path.pop();
284 resolved_path.push(relative_path);
285 (*base_po_dir, resolved_path)
286 };
287 drop(guard);
288 return path_open_internal(
289 env,
290 resolved_base_fd,
291 __WASI_LOOKUP_SYMLINK_FOLLOW,
292 &resolved_path.to_string_lossy(),
293 o_flags,
294 fs_rights_base,
295 fs_rights_inheriting,
296 fs_flags,
297 fd_flags,
298 with_fd,
299 );
300 }
301 }
302
303 if o_flags.contains(Oflags::EXCL) && o_flags.contains(Oflags::CREATE) {
304 return Ok(Err(Errno::Exist));
305 }
306
307 let file_requested_config = open_options
309 .write(minimum_rights.write)
310 .create(minimum_rights.create)
311 .append(false)
312 .truncate(minimum_rights.truncate)
313 .get_config();
314 let file_shared_config = virtual_fs::OpenOptionsConfig {
315 read: true,
316 write: true,
317 ..file_requested_config.clone()
318 };
319 let requires_stronger_handle =
320 minimum_rights.write || minimum_rights.truncate || minimum_rights.create;
321 let mut file_open_flags = open_flags;
322 if minimum_rights.read {
323 file_open_flags |= Fd::READ;
324 }
325 if minimum_rights.write {
326 file_open_flags |= Fd::WRITE;
327 }
328 if minimum_rights.create {
329 file_open_flags |= Fd::CREATE;
330 }
331 if minimum_rights.truncate {
332 file_open_flags |= Fd::TRUNCATE;
333 }
334
335 let mut fd_map = state.fs.fd_map.write().unwrap();
339 let mut guard = inode.write();
340 let out_fd = match guard.deref_mut() {
341 Kind::File {
342 handle,
343 path,
344 fd: Some(special_fd),
345 ..
346 } => {
347 assert!(handle.is_some());
348 *special_fd
349 }
350 Kind::File {
351 handle,
352 path,
353 fd: None,
354 ..
355 } => {
356 if o_flags.contains(Oflags::DIRECTORY) || orig_path.ends_with('/') {
357 return Ok(Err(Errno::Notdir));
358 }
359
360 if handle.is_none() || requires_stronger_handle {
364 let file = wasi_try_ok_ok!(open_shared_file_handle(
365 path.as_path(),
366 file_requested_config.clone(),
367 file_shared_config.clone(),
368 ));
369 if handle.is_none() {
370 *handle = Some(Arc::new(std::sync::RwLock::new(file)));
371 } else {
372 let mut existing = handle.as_ref().unwrap().write().unwrap();
373 *existing = file;
374 }
375 }
376
377 if let Some(file_handle) = handle.as_ref()
378 && let Some(special_fd) = {
379 let file = file_handle.read().unwrap();
380 file.get_special_fd()
381 }
382 {
383 drop(guard);
384 let dup_fd = wasi_try_ok_ok!(WasiFs::clone_fd_locked(
385 &state.fs,
386 &mut fd_map,
387 special_fd,
388 0,
389 None,
390 ));
391 trace!(%dup_fd);
392 return Ok(Ok(dup_fd));
393 }
394
395 let out_fd = wasi_try_ok_ok!(insert_fd_locked(
396 &mut fd_map,
397 state,
398 adjusted_rights,
399 adjusted_rights_inheriting,
400 fs_flags,
401 fd_flags,
402 file_open_flags,
403 inode.clone(),
404 with_fd,
405 ));
406 drop(guard);
407 out_fd
408 }
409 Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"),
410 Kind::Root { .. } => {
411 if !o_flags.contains(Oflags::DIRECTORY) {
412 return Ok(Err(Errno::Isdir));
413 }
414 drop(guard);
415 wasi_try_ok_ok!(insert_fd_locked(
416 &mut fd_map,
417 state,
418 adjusted_rights,
419 adjusted_rights_inheriting,
420 fs_flags,
421 fd_flags,
422 open_flags,
423 inode,
424 with_fd,
425 ))
426 }
427 Kind::Dir { .. } => {
428 if fs_rights_base.contains(Rights::FD_WRITE) {
429 return Ok(Err(Errno::Isdir));
430 }
431 drop(guard);
432 wasi_try_ok_ok!(insert_fd_locked(
433 &mut fd_map,
434 state,
435 adjusted_rights,
436 adjusted_rights_inheriting,
437 fs_flags,
438 fd_flags,
439 open_flags,
440 inode,
441 with_fd,
442 ))
443 }
444 Kind::Socket { .. }
445 | Kind::PipeTx { .. }
446 | Kind::PipeRx { .. }
447 | Kind::DuplexPipe { .. }
448 | Kind::EventNotifications { .. }
449 | Kind::Epoll { .. } => {
450 drop(guard);
451 wasi_try_ok_ok!(insert_fd_locked(
452 &mut fd_map,
453 state,
454 adjusted_rights,
455 adjusted_rights_inheriting,
456 fs_flags,
457 fd_flags,
458 open_flags,
459 inode,
460 with_fd,
461 ))
462 }
463 Kind::Symlink { .. } => unreachable!("symlinks are resolved in phase A"),
464 };
465 Ok(Ok(out_fd))
466 } else {
467 if o_flags.contains(Oflags::CREATE) {
469 if o_flags.contains(Oflags::DIRECTORY) {
470 return Ok(Err(Errno::Notdir));
471 }
472
473 if path.ends_with('/') {
475 return Ok(Err(Errno::Isdir));
476 }
477
478 let (parent_inode, new_entity_name) =
481 wasi_try_ok_ok!(state.fs.get_parent_inode_at_path(
482 inodes,
483 effective_dirfd,
484 &path_arg,
485 follow_symlinks
486 ));
487 let new_file_host_path = {
488 let guard = parent_inode.read();
489 match guard.deref() {
490 Kind::Dir { path, .. } => {
491 let mut new_path = path.clone();
492 new_path.push(&new_entity_name);
493 new_path
494 }
495 Kind::Root { .. } => {
496 let mut new_path = std::path::PathBuf::new();
497 new_path.push(&new_entity_name);
498 new_path
499 }
500 _ => return Ok(Err(Errno::Notdir)),
501 }
502 };
503 let mut fd_map = state.fs.fd_map.write().unwrap();
506
507 let requested_config = open_options
508 .read(minimum_rights.read)
509 .append(minimum_rights.append)
510 .write(minimum_rights.write)
511 .create_new(true)
512 .get_config();
513 let shared_config = virtual_fs::OpenOptionsConfig {
514 read: true,
515 write: true,
516 ..requested_config.clone()
517 };
518
519 if minimum_rights.read {
520 open_flags |= Fd::READ;
521 }
522 if minimum_rights.write {
523 open_flags |= Fd::WRITE;
524 }
525 if minimum_rights.create_new {
526 open_flags |= Fd::CREATE;
527 }
528 if minimum_rights.truncate {
529 open_flags |= Fd::TRUNCATE;
530 }
531
532 let handle = match open_shared_file_handle(
533 new_file_host_path.as_path(),
534 requested_config,
535 shared_config,
536 ) {
537 Ok(handle) => Some(handle),
538 Err(err) => {
539 if err == Errno::Exist {
540 return Ok(Err(Errno::Perm));
541 }
542 return Ok(Err(err));
543 }
544 };
545
546 let new_inode = {
547 let kind = Kind::File {
548 handle: handle.map(|a| Arc::new(std::sync::RwLock::new(a))),
549 path: new_file_host_path,
550 fd: None,
551 };
552 wasi_try_ok_ok!(
553 state
554 .fs
555 .create_inode(inodes, kind, false, new_entity_name.clone())
556 )
557 };
558
559 {
560 let mut guard = parent_inode.write();
561 if let Kind::Dir { entries, .. } = guard.deref_mut() {
562 entries.insert(new_entity_name, new_inode.clone());
563 }
564 }
565
566 Ok(Ok(wasi_try_ok_ok!(insert_fd_locked(
567 &mut fd_map,
568 state,
569 adjusted_rights,
570 adjusted_rights_inheriting,
571 fs_flags,
572 fd_flags,
573 open_flags,
574 new_inode,
575 with_fd,
576 ))))
577 } else {
578 Ok(Err(maybe_inode.unwrap_err()))
579 }
580 }
581}
582
583fn insert_fd_locked(
584 fd_map: &mut FdList,
585 _state: &WasiState,
586 adjusted_rights: Rights,
587 adjusted_rights_inheriting: Rights,
588 fs_flags: Fdflags,
589 fd_flags: Fdflagsext,
590 open_flags: u16,
591 inode: InodeGuard,
592 with_fd: Option<WasiFd>,
593) -> Result<WasiFd, Errno> {
594 WasiFs::insert_fd_locked(
597 fd_map,
598 adjusted_rights,
599 adjusted_rights_inheriting,
600 fs_flags,
601 fd_flags,
602 open_flags,
603 inode,
604 with_fd,
605 with_fd.is_some(),
606 )
607}