1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::sync::atomic::AtomicUsize;

use virtual_fs::Pipe;

use super::*;
use crate::syscalls::*;

// Used to make pipe end names unique. This is necessary since we use
// a hash of the name to calculate inode numbers. The actual number
// has no other meaning.
static PIPE_NUMBER: AtomicUsize = AtomicUsize::new(0);

/// ### `fd_pipe()`
/// Creates ta pipe that feeds data between two file handles
/// Output:
/// - `Fd`
///     First file handle that represents the read end of the pipe
/// - `Fd`
///     Second file handle that represents the write end of the pipe
#[instrument(level = "trace", skip_all, fields(read_fd = field::Empty, write_fd = field::Empty), ret)]
pub fn fd_pipe<M: MemorySize>(
    mut ctx: FunctionEnvMut<'_, WasiEnv>,
    ro_read_fd: WasmPtr<WasiFd, M>,
    ro_write_fd: WasmPtr<WasiFd, M>,
) -> Result<Errno, WasiError> {
    let (read_fd, write_fd) = wasi_try_ok!(fd_pipe_internal(&mut ctx, None, None));
    let env = ctx.data();

    #[cfg(feature = "journal")]
    if env.enable_journal {
        JournalEffector::save_fd_pipe(&mut ctx, read_fd, write_fd).map_err(|err| {
            tracing::error!("failed to save create pipe event - {}", err);
            WasiError::Exit(ExitCode::from(Errno::Fault))
        })?;
    }

    let env = ctx.data();
    let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };

    Span::current()
        .record("read_fd", read_fd)
        .record("write_fd", write_fd);

    wasi_try_mem_ok!(ro_read_fd.write(&memory, read_fd));
    wasi_try_mem_ok!(ro_write_fd.write(&memory, write_fd));

    Ok(Errno::Success)
}

pub fn fd_pipe_internal(
    ctx: &mut FunctionEnvMut<'_, WasiEnv>,
    with_read_fd: Option<WasiFd>,
    with_write_fd: Option<WasiFd>,
) -> Result<(WasiFd, WasiFd), Errno> {
    let env = ctx.data();
    let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
    let (tx, rx) = Pipe::new().split();

    // FIXME: since a hash of the inode name is used to calculate the inode number, this may
    // or may not break journals that include pipes and are compacted.
    let pipe_no = PIPE_NUMBER.fetch_add(1, Ordering::SeqCst);

    let rx_inode = state.fs.create_inode_with_default_stat(
        inodes,
        Kind::PipeRx { rx },
        false,
        format!("pipe{pipe_no}-rx").into(),
    );
    let tx_inode = state.fs.create_inode_with_default_stat(
        inodes,
        Kind::PipeTx { tx },
        false,
        format!("pipe{pipe_no}-tx").into(),
    );

    let rights = Rights::FD_SYNC
        | Rights::FD_DATASYNC
        | Rights::POLL_FD_READWRITE
        | Rights::SOCK_SEND
        | Rights::FD_FDSTAT_SET_FLAGS
        | Rights::FD_FILESTAT_GET;

    let read_rights = rights | Rights::FD_READ;
    let write_rights = rights | Rights::FD_WRITE;

    let read_fd = if let Some(fd) = with_read_fd {
        state
            .fs
            .with_fd(
                read_rights,
                read_rights,
                Fdflags::empty(),
                Fdflagsext::empty(),
                0,
                rx_inode,
                fd,
            )
            .map(|()| fd)?
    } else {
        state.fs.create_fd(
            read_rights,
            read_rights,
            Fdflags::empty(),
            Fdflagsext::empty(),
            0,
            rx_inode,
        )?
    };

    let write_fd = if let Some(fd) = with_write_fd {
        state
            .fs
            .with_fd(
                write_rights,
                write_rights,
                Fdflags::empty(),
                Fdflagsext::empty(),
                0,
                tx_inode,
                fd,
            )
            .map(|()| fd)?
    } else {
        state.fs.create_fd(
            write_rights,
            write_rights,
            Fdflags::empty(),
            Fdflagsext::empty(),
            0,
            tx_inode,
        )?
    };

    Ok((read_fd, write_fd))
}