feat: membrane remaining wasm host surfaces
This commit is contained in:
@@ -35,6 +35,9 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
@@ -345,11 +348,15 @@ public:
|
||||
// Host-owned resource handle table (§3.1): connections opened by the guest
|
||||
// live here and are closed when the workspace drops at request end.
|
||||
std::vector<SQLite*> sqlite_handles;
|
||||
std::vector<MySQL*> mysql_handles;
|
||||
~WasmWorkspace()
|
||||
{
|
||||
for(auto* db : sqlite_handles)
|
||||
if(db)
|
||||
delete db; // ~SQLite disconnects
|
||||
for(auto* db : mysql_handles)
|
||||
if(db)
|
||||
delete db; // ~MySQL disconnects
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -359,6 +366,10 @@ public:
|
||||
// input bytes) and replayed on the second without re-running the op.
|
||||
String staged_hostcall_input;
|
||||
String staged_hostcall_result;
|
||||
String staged_socket_read_key;
|
||||
String staged_socket_read_result;
|
||||
String staged_memcache_key;
|
||||
String staged_memcache_result;
|
||||
bool hostcall_staged(const String& input, String& out)
|
||||
{
|
||||
if(!staged_hostcall_input.empty() && input == staged_hostcall_input)
|
||||
@@ -1193,6 +1204,120 @@ private:
|
||||
results[0] = Val(ok ? (int32_t)1 : (int32_t)0);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_zip")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String encoded;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
|
||||
String out;
|
||||
if(!self->hostcall_staged(encoded, out))
|
||||
{
|
||||
DValue request, response;
|
||||
String decode_error;
|
||||
try
|
||||
{
|
||||
if(ucb_decode(encoded, request, &decode_error))
|
||||
{
|
||||
String op = request["op"].to_string();
|
||||
if(op == "list")
|
||||
{
|
||||
String path = self->resolve_guest_file(request["path"].to_string());
|
||||
if(path == "") throw std::runtime_error("zip_list: path is outside wasm file policy");
|
||||
response["result"] = zip_list(path);
|
||||
}
|
||||
else if(op == "read")
|
||||
{
|
||||
String path = self->resolve_guest_file(request["path"].to_string());
|
||||
if(path == "") throw std::runtime_error("zip_read: path is outside wasm file policy");
|
||||
response["result"] = zip_read(path, request["entry"].to_string());
|
||||
}
|
||||
else if(op == "create")
|
||||
{
|
||||
String path = self->resolve_guest_write(request["path"].to_string(), "");
|
||||
if(path == "") throw std::runtime_error("zip_create: path is outside wasm file policy");
|
||||
DValue* entries = request.key("entries");
|
||||
response["ok"].set_bool(zip_create(path, entries ? *entries : DValue()));
|
||||
}
|
||||
else if(op == "extract")
|
||||
{
|
||||
String path = self->resolve_guest_file(request["path"].to_string());
|
||||
String destination = self->resolve_guest_write(request["destination"].to_string(), "");
|
||||
if(path == "" || destination == "") throw std::runtime_error("zip_extract: path is outside wasm file policy");
|
||||
response["ok"].set_bool(zip_extract(path, destination));
|
||||
}
|
||||
else if(op == "gz_compress")
|
||||
response["result"] = gz_compress(request["src"].to_string());
|
||||
else if(op == "gz_uncompress")
|
||||
response["result"] = gz_uncompress(request["src"].to_string());
|
||||
}
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
response["error"] = e.what();
|
||||
}
|
||||
out = ucb_encode(response);
|
||||
self->hostcall_stage(encoded, out);
|
||||
}
|
||||
u32 cap = (u32)args[3].i32();
|
||||
int32_t buf = args[2].i32();
|
||||
if(buf != 0 && cap >= out.size())
|
||||
self->hostcall_write(buf, out);
|
||||
results[0] = Val((int32_t)out.size());
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_units")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String encoded;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
|
||||
String out;
|
||||
if(!self->hostcall_staged(encoded, out))
|
||||
{
|
||||
DValue request, response;
|
||||
String decode_error;
|
||||
try
|
||||
{
|
||||
if(ucb_decode(encoded, request, &decode_error))
|
||||
{
|
||||
String op = request["op"].to_string();
|
||||
if(op == "info")
|
||||
response["result"] = unit_info(request["path"].to_string());
|
||||
else if(op == "list")
|
||||
{
|
||||
StringList paths = units_list();
|
||||
for(auto& path : paths)
|
||||
{
|
||||
DValue item;
|
||||
item = path;
|
||||
response["result"].push(item);
|
||||
}
|
||||
}
|
||||
else if(op == "compile")
|
||||
response["ok"].set_bool(unit_compile(request["path"].to_string()));
|
||||
else if(op == "call")
|
||||
{
|
||||
DValue* param = request.key("param");
|
||||
ob_start();
|
||||
DValue* result = unit_call(request["file"].to_string(), request["function"].to_string(), param);
|
||||
response["output"] = ob_get_close();
|
||||
if(result)
|
||||
response["result"] = *result;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
response["error"] = e.what();
|
||||
}
|
||||
out = ucb_encode(response);
|
||||
self->hostcall_stage(encoded, out);
|
||||
}
|
||||
u32 cap = (u32)args[3].i32();
|
||||
int32_t buf = args[2].i32();
|
||||
if(buf != 0 && cap >= out.size())
|
||||
self->hostcall_write(buf, out);
|
||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
||||
results[0] = Val((int32_t)out.size());
|
||||
return(std::monostate());
|
||||
}));
|
||||
#ifdef UCE_WASM_HOST_CONNECTORS
|
||||
if(mod == "env" && name == "uce_host_sqlite")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
@@ -1260,6 +1385,99 @@ private:
|
||||
results[0] = Val((int32_t)out.size());
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_memcache_command")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String command;
|
||||
self->hostcall_read(args[1].i32(), args[2].i32(), command);
|
||||
String key = "memcache:" + std::to_string((u64)args[0].i64()) + ":" + command;
|
||||
u32 cap = (u32)args[4].i32();
|
||||
int32_t buf = args[3].i32();
|
||||
String out;
|
||||
if(buf != 0 && self->staged_memcache_key == key)
|
||||
{
|
||||
out = self->staged_memcache_result;
|
||||
self->staged_memcache_key = "";
|
||||
self->staged_memcache_result = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
::socket_write((u64)args[0].i64(), command + "\r\n");
|
||||
out = ::socket_read((u64)args[0].i64());
|
||||
if(buf == 0)
|
||||
{
|
||||
self->staged_memcache_key = key;
|
||||
self->staged_memcache_result = out;
|
||||
}
|
||||
}
|
||||
if(buf != 0 && cap >= out.size())
|
||||
self->hostcall_write(buf, out);
|
||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
||||
results[0] = Val((int32_t)out.size());
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_mysql")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String encoded;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
|
||||
String out;
|
||||
if(!self->hostcall_staged(encoded, out))
|
||||
{
|
||||
DValue request, response;
|
||||
String decode_error;
|
||||
if(ucb_decode(encoded, request, &decode_error))
|
||||
{
|
||||
String op = request["op"].to_string();
|
||||
if(op == "connect")
|
||||
{
|
||||
MySQL* db = new MySQL();
|
||||
bool ok = db->connect(request["host"].to_string(), request["username"].to_string(), request["password"].to_string());
|
||||
u64 handle = 0;
|
||||
if(ok && db->connection)
|
||||
{
|
||||
self->mysql_handles.push_back(db);
|
||||
handle = self->mysql_handles.size();
|
||||
}
|
||||
response["handle"] = (f64)handle;
|
||||
response["error_code"] = (f64)db->_preload_next_error_code;
|
||||
response["statement_info"] = db->error();
|
||||
if(handle == 0)
|
||||
delete db;
|
||||
}
|
||||
else if(op == "escape")
|
||||
{
|
||||
String quote = request["quote_char"].to_string();
|
||||
response["result"] = mysql_escape(request["raw"].to_string(), quote.size() ? quote[0] : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
u64 handle = request["handle"].to_u64();
|
||||
MySQL* db = (handle >= 1 && handle <= self->mysql_handles.size())
|
||||
? self->mysql_handles[(size_t)handle - 1] : 0;
|
||||
if(op == "query" && db)
|
||||
{
|
||||
response["result"] = db->query(request["query"].to_string());
|
||||
response["insert_id"] = (f64)db->insert_id;
|
||||
response["affected"] = (f64)db->affected_rows;
|
||||
response["error_code"] = (f64)db->_preload_next_error_code;
|
||||
response["statement_info"] = db->error();
|
||||
}
|
||||
else if(op == "disconnect" && db)
|
||||
{
|
||||
delete db;
|
||||
self->mysql_handles[(size_t)handle - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
out = ucb_encode(response);
|
||||
self->hostcall_stage(encoded, out);
|
||||
}
|
||||
u32 cap = (u32)args[3].i32();
|
||||
int32_t buf = args[2].i32();
|
||||
if(buf != 0 && cap >= out.size())
|
||||
self->hostcall_write(buf, out);
|
||||
results[0] = Val((int32_t)out.size());
|
||||
return(std::monostate());
|
||||
}));
|
||||
#endif
|
||||
if(mod == "env" && name == "uce_host_file_unlink")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
|
||||
@@ -1271,6 +1489,110 @@ private:
|
||||
::unlink(resolved.c_str());
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_socket_connect")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String host;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), host);
|
||||
int fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(fd >= 0)
|
||||
{
|
||||
struct sockaddr_in addr = {0};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons((short)args[2].i32());
|
||||
addr.sin_addr.s_addr = inet_addr(host.c_str());
|
||||
if(::connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
else if(fd == 0)
|
||||
{
|
||||
int moved = ::dup(fd);
|
||||
::close(fd);
|
||||
fd = moved;
|
||||
}
|
||||
if(fd > 0 && context)
|
||||
context->resources.sockets.push_back(fd);
|
||||
}
|
||||
results[0] = Val((int64_t)(fd > 0 ? fd : 0));
|
||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_socket_close")
|
||||
return(add([](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
|
||||
::socket_close((u64)args[0].i64());
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_socket_write")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String data;
|
||||
self->hostcall_read(args[1].i32(), args[2].i32(), data);
|
||||
results[0] = Val(::socket_write((u64)args[0].i64(), data) ? (int32_t)1 : (int32_t)0);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_socket_read")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
u64 sockfd = (u64)args[0].i64();
|
||||
u32 max_length = (u32)args[1].i32();
|
||||
u32 timeout = (u32)args[2].i32();
|
||||
int32_t buf = args[3].i32();
|
||||
u32 cap = (u32)args[4].i32();
|
||||
String key = std::to_string(sockfd) + ":" + std::to_string(max_length) + ":" + std::to_string(timeout);
|
||||
String out;
|
||||
if(buf != 0 && self->staged_socket_read_key == key)
|
||||
{
|
||||
out = self->staged_socket_read_result;
|
||||
self->staged_socket_read_key = "";
|
||||
self->staged_socket_read_result = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
out = ::socket_read(sockfd, max_length, timeout);
|
||||
if(buf == 0)
|
||||
{
|
||||
self->staged_socket_read_key = key;
|
||||
self->staged_socket_read_result = out;
|
||||
}
|
||||
}
|
||||
if(buf != 0 && cap >= out.size())
|
||||
self->hostcall_write(buf, out);
|
||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
||||
results[0] = Val((int32_t)out.size());
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_server_start_http")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String key, bind, file, function, current;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), key);
|
||||
self->hostcall_read(args[2].i32(), args[3].i32(), bind);
|
||||
self->hostcall_read(args[4].i32(), args[5].i32(), file);
|
||||
self->hostcall_read(args[6].i32(), args[7].i32(), function);
|
||||
self->hostcall_read(args[8].i32(), args[9].i32(), current);
|
||||
String resolved = self->resolve_guest_file(file, current);
|
||||
pid_t pid = 0;
|
||||
try
|
||||
{
|
||||
if(resolved != "")
|
||||
pid = ::server_start_http(key, bind, resolved, function);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
fprintf(stderr, "[wasm server] start failed for key '%s': %s\n", key.c_str(), e.what());
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
fprintf(stderr, "[wasm server] start failed for key '%s'\n", key.c_str());
|
||||
}
|
||||
results[0] = Val((int32_t)pid);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_server_stop")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String key;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), key);
|
||||
results[0] = Val(::server_stop(key) ? (int32_t)1 : (int32_t)0);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_task_spawn")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String key;
|
||||
|
||||
Reference in New Issue
Block a user