numcodecs_wasm_host_reproducible/
stdio.rs

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
use std::{fmt, io::Write, sync::OnceLock};

use wasm_component_layer::{
    AsContextMut, Func, FuncType, InterfaceIdentifier, Linker, ListType, PackageIdentifier,
    PackageName, Value, ValueType,
};

pub fn add_to_linker(linker: &mut Linker, mut ctx: impl AsContextMut) -> Result<(), anyhow::Error> {
    let WasiSandboxedStdioInterface {
        stdio: simple_stdio_interface,
    } = WasiSandboxedStdioInterface::get();

    let simple_stdio_instance = linker.define_instance(simple_stdio_interface.clone())?;

    simple_stdio_instance.define_func(
        "write-stdout",
        OutputStream::Stdout.create_write_func(ctx.as_context_mut()),
    )?;
    simple_stdio_instance.define_func(
        "flush-stdout",
        OutputStream::Stdout.create_flush_func(ctx.as_context_mut()),
    )?;
    simple_stdio_instance.define_func(
        "write-stderr",
        OutputStream::Stderr.create_write_func(ctx.as_context_mut()),
    )?;
    simple_stdio_instance.define_func(
        "flush-stderr",
        OutputStream::Stderr.create_flush_func(ctx.as_context_mut()),
    )?;

    Ok(())
}

#[derive(Copy, Clone)]
enum OutputStream {
    Stdout,
    Stderr,
}

impl OutputStream {
    fn create_write_func(self, ctx: impl AsContextMut) -> Func {
        Func::new(
            ctx,
            FuncType::new([ValueType::List(ListType::new(ValueType::U8))], []),
            move |_ctx, args, results| {
                let [Value::List(contents)] = args else {
                    anyhow::bail!("invalid wasi-sandboxed:io/stdio#write-{self} arguments");
                };
                let Ok(contents) = contents.typed::<u8>() else {
                    anyhow::bail!("invalid wasi-sandboxed:io/stdio#write-{self} argument type");
                };

                anyhow::ensure!(
                    results.is_empty(),
                    "invalid wasi-sandboxed:io/stdio#write-{self} results"
                );

                if let Err(err) = match self {
                    Self::Stdout => std::io::stdout().write_all(contents),
                    Self::Stderr => std::io::stderr().write_all(contents),
                } {
                    error!(
                        "Failed to write {} byte{} to {self}: {err}",
                        contents.len(),
                        if contents.len() == 1 { "" } else { "s" }
                    );
                }

                Ok(())
            },
        )
    }

    fn create_flush_func(self, ctx: impl AsContextMut) -> Func {
        Func::new(ctx, FuncType::new([], []), move |_ctx, args, results| {
            anyhow::ensure!(
                args.is_empty(),
                "invalid wasi-sandboxed:io/stdio#flush-{self} arguments"
            );

            anyhow::ensure!(
                results.is_empty(),
                "invalid wasi-sandboxed:io/stdio#flush-{self} results"
            );

            if let Err(err) = match self {
                Self::Stdout => std::io::stdout().flush(),
                Self::Stderr => std::io::stderr().flush(),
            } {
                error!("Failed to flush {self}: {err}");
            }

            Ok(())
        })
    }
}

impl fmt::Display for OutputStream {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(match self {
            Self::Stdout => "stdout",
            Self::Stderr => "stderr",
        })
    }
}

#[non_exhaustive]
pub struct WasiSandboxedStdioInterface {
    pub stdio: InterfaceIdentifier,
}

impl WasiSandboxedStdioInterface {
    #[must_use]
    pub fn get() -> &'static Self {
        static WASI_SANDBOXED_STDIO_INTERFACE: OnceLock<WasiSandboxedStdioInterface> =
            OnceLock::new();

        WASI_SANDBOXED_STDIO_INTERFACE.get_or_init(|| Self {
            stdio: InterfaceIdentifier::new(
                PackageIdentifier::new(
                    PackageName::new("wasi-sandboxed", "io"),
                    Some(semver::Version::new(0, 2, 3)),
                ),
                "stdio",
            ),
        })
    }
}