wasmer_wasix/syscalls/wasi/
fd_renumber.rs

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