wasmer_wasix/syscalls/wasix/
path_open2.rs

1use super::*;
2use crate::VIRTUAL_ROOT_FD;
3use crate::fs::{FdList, WasiFs};
4use crate::syscalls::*;
5
6/// ### `path_open()`
7/// Open file located at the given path
8/// Inputs:
9/// - `Fd dirfd`
10///     The fd corresponding to the directory that the file is in
11/// - `LookupFlags dirflags`
12///     Flags specifying how the path will be resolved
13/// - `char *path`
14///     The path of the file or directory to open
15/// - `u32 path_len`
16///     The length of the `path` string
17/// - `Oflags o_flags`
18///     How the file will be opened
19/// - `Rights fs_rights_base`
20///     The rights of the created file descriptor
21/// - `Rights fs_rightsinheriting`
22///     The rights of file descriptors derived from the created file descriptor
23/// - `Fdflags fs_flags`
24///     The flags of the file descriptor
25/// Output:
26/// - `Fd* fd`
27///     The new file descriptor
28/// Possible Errors:
29/// - `Errno::Access`, `Errno::Badf`, `Errno::Fault`, `Errno::Fbig?`, `Errno::Inval`, `Errno::Io`, `Errno::Loop`, `Errno::Mfile`, `Errno::Nametoolong?`, `Errno::Nfile`, `Errno::Noent`, `Errno::Notdir`, `Errno::Rofs`, and `Errno::Notcapable`
30#[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    /* TODO: find actual upper bound on name size (also this is a path, not a name :think-fish:) */
53    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    // o_flags:
63    // - __WASI_O_CREAT (create if it does not exist)
64    // - __WASI_O_DIRECTORY (fail if not dir)
65    // - __WASI_O_EXCL (fail if file exists)
66    // - __WASI_O_TRUNC (truncate size to 0)
67
68    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
117/// Open or create a filesystem object in the WASIX POSIX guest namespace.
118///
119/// This function sits on top of `WasiFs::get_inode_at_path()`, so it must
120/// preserve that resolver's guest-path contract: raw syscall paths are POSIX
121/// paths where `/` is the only separator, absolute paths start from
122/// `VIRTUAL_ROOT_FD`, and intermediate symlinks are always resolved. Host paths
123/// only enter after an inode has been resolved to a backing `Kind::Dir` or
124/// `Kind::File`.
125///
126/// `dirflags` control lookup, not file-open mode. In particular,
127/// `__WASI_LOOKUP_SYMLINK_FOLLOW` decides whether the final component may be
128/// followed if it is a symlink. This matches the usual `openat`/`O_NOFOLLOW`
129/// shape: a symlink in the middle of the path is still traversed, but a final
130/// symlink with lookup-follow disabled is not opened as a symlink object. WASIX
131/// has no "open the symlink itself as a file" mode here, so a terminal symlink
132/// with no follow returns `Errno::Loop`. With lookup-follow enabled, the symlink
133/// target is resolved and `path_open_internal` restarts against that target.
134///
135/// `o_flags` and `fs_flags` apply after lookup. They decide whether the target
136/// must already exist, whether a missing final component should be created,
137/// whether the opened object must be a directory, and whether the backing file
138/// should be truncated or appended. POSIX trailing-slash semantics still matter
139/// at this layer: opening `file/` is `Errno::Notdir`, and creating `new_file/`
140/// is rejected before the backing opener can normalize the slash away.
141///
142/// The create path resolves only the parent with the requested symlink policy,
143/// then appends the new final component under that resolved parent. That keeps
144/// creation through symlinked directories working while still letting the final
145/// component be genuinely new. If the backing filesystem reports
146/// `AlreadyExists` after the resolver reported `Noent`, this may indicate that a
147/// symlink escaped the sandbox, so the error is mapped to `Errno::Perm`.
148///
149/// File inodes are also the cache boundary for open handles. The resolver may
150/// materialize a `Kind::File` with no handle, and this function opens the
151/// backing file when a descriptor is requested. Regular file handles are shared
152/// per inode when possible, and reopened with stronger rights when a later open
153/// requires write/create/truncate access that the existing handle may not have.
154///
155/// The outer `Result` reports runtime faults such as memory or trap-style WASI
156/// errors. The inner `Result<WasiFd, Errno>` is the syscall result that should
157/// be returned to the guest.
158pub(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    // ASSUMPTION: open rights apply recursively
239    if !working_dir.inner.rights.contains(Rights::PATH_OPEN) {
240        return Ok(Err(Errno::Access));
241    }
242
243    let mut open_flags = 0;
244    // TODO: traverse rights of dirs properly
245    // COMMENTED OUT: WASI isn't giving appropriate rights here when opening
246    //              TODO: look into this; file a bug report if this is a bug
247    //
248    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    // Maximum rights: whatever the parent fd may delegate
257    // Minimum rights: whatever rights the caller requested or the open mode implies
258    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            // append, truncate, and create all require the permission to write
267            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        // The parent is a directory, which is why these options
300        // aren't inherited from the parent (append / truncate doesn't work on directories)
301        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    // Regular files share a single inode-level handle across all WASIX file
312    // descriptors, so prefer opening that shared handle with duplex access.
313    // That lets a later read-only fd keep working after an earlier write-only
314    // open (and vice versa). If the backing filesystem denies duplex access,
315    // fall back to the narrower requested mode.
316    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        // Phase A: path resolution only — symlink follow recurses without committing.
341        {
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        // Open-mode inputs derived from syscall args only (no inode-state decisions).
390        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        // Phase B: fd_map first; every inode-dependent decision under lock. For regular
418        // files keep inode write through insert_fd so handle install and acquire_handle()
419        // cannot interleave with close on this inode.
420        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                // Install or refresh the shared inode handle before checking for special
443                // stdio paths (/dev/stdin, /dev/stdout, /dev/stderr). DeviceFile stubs
444                // only report get_special_fd() once the backing open has run.
445                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        // less-happy path, we have to try to create the file
550        if o_flags.contains(Oflags::CREATE) {
551            if o_flags.contains(Oflags::DIRECTORY) {
552                return Ok(Err(Errno::Notdir));
553            }
554
555            // Trailing slash matters. But the underlying opener normalizes it away later.
556            if path.ends_with('/') {
557                return Ok(Err(Errno::Isdir));
558            }
559
560            // The follow-style lookup above may have failed because the final
561            // component is a symlink whose target does not exist yet. POSIX
562            // create opens should follow that final symlink and create the
563            // target, while O_EXCL still treats the symlink itself as an
564            // existing path.
565            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            // strip end file name
623
624            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            // Host create, inode wiring, and fd insert run under one fd_map write lock so
642            // no other thread can observe the inode (or clear its handle) without a map entry.
643            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    // TODO: check and reduce these
733    // TODO: ensure a mutable fd to root can never be opened
734    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}