wasmer_wasix/syscalls/wasi/
fd_renumber.rs

1use super::*;
2use crate::syscalls::*;
3use std::{future::Future, pin::Pin, sync::Arc, task::Context, task::Poll};
4use virtual_fs::VirtualFile;
5
6struct FlushPoller {
7    file: Arc<std::sync::RwLock<Box<dyn VirtualFile + Send + Sync>>>,
8}
9
10impl Future for FlushPoller {
11    type Output = Result<(), Errno>;
12
13    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14        let mut file = self.file.write().unwrap();
15        Pin::new(file.as_mut())
16            .poll_flush(cx)
17            .map_err(|_| Errno::Io)
18    }
19}
20
21/// ### `fd_renumber()`
22/// Atomically copy file descriptor
23/// Inputs:
24/// - `Fd from`
25///     File descriptor to copy
26/// - `Fd to`
27///     Location to copy file descriptor to
28#[instrument(level = "trace", skip_all, fields(%from, %to), ret)]
29pub fn fd_renumber(
30    mut ctx: FunctionEnvMut<'_, WasiEnv>,
31    from: WasiFd,
32    to: WasiFd,
33) -> Result<Errno, WasiError> {
34    WasiEnv::do_pending_operations(&mut ctx)?;
35
36    let ret = fd_renumber_internal(&mut ctx, from, to)?;
37    let env = ctx.data();
38
39    if ret == Errno::Success {
40        #[cfg(feature = "journal")]
41        if env.enable_journal {
42            JournalEffector::save_fd_renumber(&mut ctx, from, to).map_err(|err| {
43                tracing::error!("failed to save file descriptor renumber event - {}", err);
44                WasiError::Exit(ExitCode::from(Errno::Fault))
45            })?;
46        }
47    }
48
49    Ok(ret)
50}
51
52pub(crate) fn fd_renumber_internal(
53    ctx: &mut FunctionEnvMut<'_, WasiEnv>,
54    from: WasiFd,
55    to: WasiFd,
56) -> Result<Errno, WasiError> {
57    if from == to {
58        return Ok(Errno::Success);
59    }
60    let env = ctx.data();
61    let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) };
62
63    // Hold a single write lock for both the remove and insert to prevent
64    // another thread from allocating into the target slot between the two
65    // operations.
66    let old_fd;
67    {
68        let mut fd_map = state.fs.fd_map.write().unwrap();
69
70        // Validate the source first. If `from` is invalid we must not mutate `to`.
71        let fd_entry = wasi_try_ok!(fd_map.get(from).ok_or(Errno::Badf));
72
73        // Never allow renumbering over preopens.
74        if let Some(target_fd) = fd_map.get(to)
75            && !target_fd.is_stdio
76            && target_fd.inode.is_preopened
77        {
78            warn!("Refusing fd_renumber({from}, {to}) because FD {to} is pre-opened");
79            return Ok(Errno::Notsup);
80        }
81
82        let new_fd_entry = Fd {
83            inner: FdInner {
84                offset: fd_entry.inner.offset.clone(),
85                rights: fd_entry.inner.rights_inheriting,
86                fd_flags: {
87                    let mut f = fd_entry.inner.fd_flags;
88                    f.set(Fdflagsext::CLOEXEC, false);
89                    f
90                },
91                ..fd_entry.inner
92            },
93            inode: fd_entry.inode.clone(),
94            ..*fd_entry
95        };
96
97        // Remove the target FD under the same lock (replaces the separate
98        // close_fd call which would acquire its own lock).
99        old_fd = fd_map.remove(to);
100
101        if !fd_map.insert(true, to, new_fd_entry) {
102            panic!("Internal error: expected FD {to} to be free after closing in fd_renumber");
103        }
104    }
105    // Flush and drop the old FD outside the lock. The flush is best-effort:
106    // failures are intentionally ignored so fd_renumber result depends only on
107    // descriptor map updates and validation.
108    let flush_target = old_fd.as_ref().and_then(|fd_entry| {
109        let guard = fd_entry.inode.read();
110        match guard.deref() {
111            Kind::File {
112                handle: Some(file), ..
113            } => Some(file.clone()),
114            _ => None,
115        }
116    });
117    drop(old_fd);
118
119    if let Some(file) = flush_target {
120        let _ = __asyncify_light(env, None, FlushPoller { file })?;
121    }
122
123    Ok(Errno::Success)
124}