wasmer_wasix/syscalls/wasi/
fd_close.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_close()`
22/// Close an open file descriptor
23/// For sockets this will flush the data before the socket is closed
24/// Inputs:
25/// - `Fd fd`
26///     A file descriptor mapping to an open file to close
27/// Errors:
28/// - `Errno::Isdir`
29///     If `fd` is a directory
30/// - `Errno::Badf`
31///     If `fd` is invalid or not open
32#[instrument(level = "trace", skip_all, fields(pid = ctx.data().process.pid().raw(), %fd), ret)]
33pub fn fd_close(mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Result<Errno, WasiError> {
34    WasiEnv::do_pending_operations(&mut ctx)?;
35
36    let env = ctx.data();
37    let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) };
38    let fd_entry = state.fs.get_fd(fd).ok();
39
40    // We don't want to allow programs that blindly close all FDs in a loop
41    // to be able to close pre-opens, as that breaks wasix-libc in rather
42    // spectacular fashion.
43    if let Some(pfd) = fd_entry.as_ref()
44        && !pfd.is_stdio
45        && pfd.inode.is_preopened
46    {
47        trace!("Skipping fd_close for pre-opened FD ({})", fd);
48        return Ok(Errno::Success);
49    }
50    // Keep stdio behavior unchanged: flush before close.
51    if fd <= __WASI_STDERR_FILENO {
52        match __asyncify_light(env, None, state.fs.flush(fd))? {
53            Ok(_) | Err(Errno::Isdir) | Err(Errno::Io) | Err(Errno::Access) => {}
54            Err(e) => {
55                return Ok(e);
56            }
57        }
58        wasi_try_ok!(state.fs.close_fd(fd));
59    } else {
60        // Capture the file handle before removing the fd, then close first.
61        // This avoids an fd-number reuse race where an async pre-close flush
62        // can end up closing a newly allocated descriptor with the same number.
63        let flush_target = fd_entry.as_ref().and_then(|fd_entry| {
64            let guard = fd_entry.inode.read();
65            match guard.deref() {
66                Kind::File {
67                    handle: Some(file), ..
68                } => Some(file.clone()),
69                _ => None,
70            }
71        });
72
73        wasi_try_ok!(state.fs.close_fd(fd));
74
75        if let Some(file) = flush_target {
76            match __asyncify_light(env, None, FlushPoller { file })? {
77                Ok(_) | Err(Errno::Isdir) | Err(Errno::Io) | Err(Errno::Access) => {}
78                Err(e) => {
79                    return Ok(e);
80                }
81            }
82        }
83    }
84
85    #[cfg(feature = "journal")]
86    if env.enable_journal {
87        JournalEffector::save_fd_close(&mut ctx, fd).map_err(|err| {
88            tracing::error!("failed to save close descriptor event - {}", err);
89            WasiError::Exit(ExitCode::from(Errno::Fault))
90        })?;
91    }
92
93    Ok(Errno::Success)
94}