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 = wasi_try_ok!(state.fs.get_fd(fd));
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 !fd_entry.is_stdio && fd_entry.inode.is_preopened {
44        trace!("Skipping fd_close for pre-opened FD ({})", fd);
45        return Ok(Errno::Success);
46    }
47    // Keep stdio behavior unchanged: flush before close.
48    if fd <= __WASI_STDERR_FILENO {
49        match __asyncify_light(env, None, state.fs.flush(fd))? {
50            Ok(_) | Err(Errno::Isdir) | Err(Errno::Io) | Err(Errno::Access) => {}
51            Err(e) => {
52                return Ok(e);
53            }
54        }
55        wasi_try_ok!(state.fs.close_fd(fd));
56    } else {
57        // Capture the file handle before removing the fd, then close first.
58        // This avoids an fd-number reuse race where an async pre-close flush
59        // can end up closing a newly allocated descriptor with the same number.
60        let flush_target = {
61            let guard = fd_entry.inode.read();
62            match guard.deref() {
63                Kind::File {
64                    handle: Some(file), ..
65                } => Some(file.clone()),
66                _ => None,
67            }
68        };
69
70        wasi_try_ok!(state.fs.close_fd(fd));
71
72        if let Some(file) = flush_target {
73            match __asyncify_light(env, None, FlushPoller { file })? {
74                Ok(_) | Err(Errno::Isdir) | Err(Errno::Io) | Err(Errno::Access) => {}
75                Err(e) => {
76                    return Ok(e);
77                }
78            }
79        }
80    }
81
82    #[cfg(feature = "journal")]
83    if env.enable_journal {
84        JournalEffector::save_fd_close(&mut ctx, fd).map_err(|err| {
85            tracing::error!("failed to save close descriptor event - {}", err);
86            WasiError::Exit(ExitCode::from(Errno::Fault))
87        })?;
88    }
89
90    Ok(Errno::Success)
91}