11import ctypes
2- from ctypes import POINTER , c_char , c_char_p , cast
2+ import errno
3+ from ctypes import POINTER , c_char , c_char_p , cast , CFUNCTYPE , c_void_p
34from enum import Enum
45from os import PathLike
5- from typing import Iterable , List , Union
6+ from typing import Iterable , List , Union , Callable
67
78from wasmtime import Managed , WasmtimeError
89
910from . import _ffi as ffi
1011from ._config import setter_property
12+ from ._func import Slab
1113
1214
1315def _encode_path (path : Union [str , bytes , PathLike ]) -> bytes :
@@ -29,6 +31,11 @@ class FilePerms(Enum):
2931 WRITE_ONLY = ffi .wasi_file_perms_flags .WASMTIME_WASI_FILE_PERMS_WRITE .value
3032 READ_WRITE = ffi .wasi_file_perms_flags .WASMTIME_WASI_FILE_PERMS_READ .value | ffi .wasi_file_perms_flags .WASMTIME_WASI_FILE_PERMS_WRITE .value
3133
34+
35+ CustomOutput = Callable [[bytes ], Union [int , None ]]
36+ CUSTOM_OUTPUTS : Slab [CustomOutput ] = Slab ()
37+
38+
3239class WasiConfig (Managed ["ctypes._Pointer[ffi.wasi_config_t]" ]):
3340
3441 def __init__ (self ) -> None :
@@ -119,6 +126,15 @@ def stdout_file(self, path: str) -> None:
119126 if not res :
120127 raise WasmtimeError ("failed to set stdout file" )
121128
129+ @setter_property
130+ def stdout_custom (self , callback : CustomOutput ) -> None :
131+ """
132+ Sets a custom `callback` that is invoked whenever stdout is written to.
133+ """
134+ ffi .wasi_config_set_stdout_custom (
135+ self .ptr (), custom_call ,
136+ CUSTOM_OUTPUTS .allocate (callback ), custom_finalize )
137+
122138 def inherit_stdout (self ) -> None :
123139 """
124140 Configures this own process's stdout to be used as the WASI program's
@@ -145,6 +161,15 @@ def stderr_file(self, path: str) -> None:
145161 if not res :
146162 raise WasmtimeError ("failed to set stderr file" )
147163
164+ @setter_property
165+ def stderr_custom (self , callback : CustomOutput ) -> None :
166+ """
167+ Sets a custom `callback` that is invoked whenever stderr is written to.
168+ """
169+ ffi .wasi_config_set_stderr_custom (
170+ self .ptr (), custom_call ,
171+ CUSTOM_OUTPUTS .allocate (callback ), custom_finalize )
172+
148173 def inherit_stderr (self ) -> None :
149174 """
150175 Configures this own process's stderr to be used as the WASI program's
@@ -177,3 +202,24 @@ def to_char_array(strings: List[str]) -> "ctypes._Pointer[ctypes._Pointer[c_char
177202 for i , s in enumerate (strings ):
178203 ptrs [i ] = c_char_p (s .encode ('utf-8' ))
179204 return cast (ptrs , POINTER (POINTER (c_char )))
205+
206+
207+ @CFUNCTYPE (ctypes .c_ssize_t , c_void_p , POINTER (ctypes .c_ubyte ), ctypes .c_size_t )
208+ def custom_call (idx , ptr , size ): # type: ignore
209+ try :
210+ ty = ctypes .c_uint8 * size
211+ arg = bytes (ty .from_address (ctypes .addressof (ptr .contents )))
212+ ret = CUSTOM_OUTPUTS .get (idx or 0 )(arg )
213+ if ret is None :
214+ return size
215+ return ret
216+ except Exception as e :
217+ print ('failed custom output, required to catch exception:' , e )
218+ return - errno .EIO
219+
220+
221+ @CFUNCTYPE (None , c_void_p )
222+ def custom_finalize (idx ): # type: ignore
223+ if CUSTOM_OUTPUTS :
224+ CUSTOM_OUTPUTS .deallocate (idx or 0 )
225+ return None
0 commit comments