Coverage for wasmtime/_wasi.py: 92%
98 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-08-20 19:29 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-08-20 19:29 +0000
1import ctypes
2import errno
3from ctypes import POINTER, c_char, c_char_p, cast, CFUNCTYPE, c_void_p
4from enum import Enum
5from os import PathLike
6from typing import Iterable, List, Union, Callable
8from wasmtime import Managed, WasmtimeError
10from . import _ffi as ffi
11from ._config import setter_property
12from ._slab import Slab
15def _encode_path(path: Union[str, bytes, PathLike]) -> bytes:
16 if isinstance(path, (bytes, str)):
17 path2 = path
18 else:
19 path2 = path.__fspath__()
20 if isinstance(path2, bytes):
21 return path2
22 return path2.encode('utf8')
25CustomOutput = Callable[[bytes], Union[int, None]]
26CUSTOM_OUTPUTS: Slab[CustomOutput] = Slab()
29class WasiConfig(Managed["ctypes._Pointer[ffi.wasi_config_t]"]):
31 def __init__(self) -> None:
32 self._set_ptr(ffi.wasi_config_new())
34 def _delete(self, ptr: "ctypes._Pointer[ffi.wasi_config_t]") -> None:
35 ffi.wasi_config_delete(ptr)
37 @setter_property
38 def argv(self, argv: List[str]) -> None:
39 """
40 Explicitly configure the `argv` for this WASI configuration
41 """
42 ptrs = to_char_array(argv)
43 if not ffi.wasi_config_set_argv(self.ptr(), len(argv), ptrs):
44 raise WasmtimeError("failed to configure argv")
46 def inherit_argv(self) -> None:
47 ffi.wasi_config_inherit_argv(self.ptr())
49 @setter_property
50 def env(self, pairs: Iterable[Iterable]) -> None:
51 """
52 Configure environment variables to be returned for this WASI
53 configuration.
55 The `pairs` provided must be an iterable list of key/value pairs of
56 environment variables.
57 """
58 names = []
59 values = []
60 for name, value in pairs:
61 names.append(name)
62 values.append(value)
63 name_ptrs = to_char_array(names)
64 value_ptrs = to_char_array(values)
65 if not ffi.wasi_config_set_env(self.ptr(), len(names), name_ptrs, value_ptrs):
66 raise WasmtimeError("failed to configure environment")
68 def inherit_env(self) -> None:
69 """
70 Configures the environment variables available within WASI to be those
71 in this own process's environment. All environment variables are
72 inherited.
73 """
74 ffi.wasi_config_inherit_env(self.ptr())
76 @setter_property
77 def stdin_file(self, path: Union[str, bytes, PathLike]) -> None:
78 """
79 Configures a file to be used as the stdin stream of this WASI
80 configuration.
82 Reads of the stdin stream will read the path specified.
84 The file must already exist on the filesystem. If it cannot be
85 opened then `WasmtimeError` is raised.
86 """
88 res = ffi.wasi_config_set_stdin_file(
89 self.ptr(), c_char_p(_encode_path(path)))
90 if not res:
91 raise WasmtimeError("failed to set stdin file")
93 def inherit_stdin(self) -> None:
94 """
95 Configures this own process's stdin to be used as the WASI program's
96 stdin.
98 Reads of the stdin stream will read this process's stdin.
99 """
100 ffi.wasi_config_inherit_stdin(self.ptr())
102 @setter_property
103 def stdout_file(self, path: str) -> None:
104 """
105 Configures a file to be used as the stdout stream of this WASI
106 configuration.
108 Writes to stdout will be written to the file specified.
110 The file specified will be created if it doesn't exist, or truncated if
111 it already exists. It must be available to open for writing. If it
112 cannot be opened for writing then `WasmtimeError` is raised.
113 """
114 res = ffi.wasi_config_set_stdout_file(
115 self.ptr(), c_char_p(_encode_path(path)))
116 if not res:
117 raise WasmtimeError("failed to set stdout file")
119 @setter_property
120 def stdout_custom(self, callback: CustomOutput) -> None:
121 """
122 Sets a custom `callback` that is invoked whenever stdout is written to.
123 """
124 ffi.wasi_config_set_stdout_custom(
125 self.ptr(), custom_call,
126 CUSTOM_OUTPUTS.allocate(callback), custom_finalize)
128 def inherit_stdout(self) -> None:
129 """
130 Configures this own process's stdout to be used as the WASI program's
131 stdout.
133 Writes to stdout stream will write to this process's stdout.
134 """
135 ffi.wasi_config_inherit_stdout(self.ptr())
137 @setter_property
138 def stderr_file(self, path: str) -> None:
139 """
140 Configures a file to be used as the stderr stream of this WASI
141 configuration.
143 Writes to stderr will be written to the file specified.
145 The file specified will be created if it doesn't exist, or truncated if
146 it already exists. It must be available to open for writing. If it
147 cannot be opened for writing then `WasmtimeError` is raised.
148 """
149 res = ffi.wasi_config_set_stderr_file(
150 self.ptr(), c_char_p(_encode_path(path)))
151 if not res:
152 raise WasmtimeError("failed to set stderr file")
154 @setter_property
155 def stderr_custom(self, callback: CustomOutput) -> None:
156 """
157 Sets a custom `callback` that is invoked whenever stderr is written to.
158 """
159 ffi.wasi_config_set_stderr_custom(
160 self.ptr(), custom_call,
161 CUSTOM_OUTPUTS.allocate(callback), custom_finalize)
163 def inherit_stderr(self) -> None:
164 """
165 Configures this own process's stderr to be used as the WASI program's
166 stderr.
168 Writes to stderr stream will write to this process's stderr.
169 """
170 ffi.wasi_config_inherit_stderr(self.ptr())
172 def preopen_dir(self, path: str, guest_path: str, fs_mutable: bool = True) -> None:
173 """
174 Allows the WASI program to access the directory at `path` using the
175 path `guest_path` within the WASI program.
177 `dir_perms` specifies the permissions that wasm will have to operate on
178 `guest_path`. This can be used, for example, to provide readonly access to a
179 directory.
181 `file_perms` specifies the maximum set of permissions that can be used for
182 any file in this directory.
183 """
184 path_ptr = c_char_p(path.encode('utf-8'))
185 guest_path_ptr = c_char_p(guest_path.encode('utf-8'))
186 if not ffi.wasi_config_preopen_dir(self.ptr(), path_ptr, guest_path_ptr, fs_mutable):
187 raise WasmtimeError('failed to add preopen dir')
190def to_char_array(strings: List[str]) -> "ctypes._Pointer[ctypes._Pointer[c_char]]":
191 ptrs = (c_char_p * len(strings))()
192 for i, s in enumerate(strings):
193 ptrs[i] = c_char_p(s.encode('utf-8'))
194 return cast(ptrs, POINTER(POINTER(c_char)))
197@CFUNCTYPE(ctypes.c_ssize_t, c_void_p, POINTER(ctypes.c_ubyte), ctypes.c_size_t)
198def custom_call(idx, ptr, size): # type: ignore
199 try:
200 ty = ctypes.c_uint8 * size
201 arg = bytes(ty.from_address(ctypes.addressof(ptr.contents)))
202 ret = CUSTOM_OUTPUTS.get(idx or 0)(arg)
203 if ret is None:
204 return size
205 return ret
206 except Exception as e:
207 print('failed custom output, required to catch exception:', e)
208 return -errno.EIO
211@CFUNCTYPE(None, c_void_p)
212def custom_finalize(idx): # type: ignore
213 if CUSTOM_OUTPUTS:
214 CUSTOM_OUTPUTS.deallocate(idx or 0)
215 return None