feat: membrane remaining wasm host surfaces
This commit is contained in:
@@ -117,14 +117,13 @@ static bool wasm_backend_native_fallback_uncached(Request* context, const String
|
||||
// - regex_* (host PCRE2 hostcall), xml_*/yaml_*/markdown_* (compiled in),
|
||||
// - unit_render()/component() (host resolver).
|
||||
// What remains is genuinely host-owned / native-only for now:
|
||||
// - zip, sockets/custom servers, memcache, mysql;
|
||||
// - unit_call + compiler/unit introspection (need the native toolchain).
|
||||
// Background tasks and sleep/usleep are host-owned but now have membrane
|
||||
// hostcalls, so they intentionally do not fallback here.
|
||||
// - zip pages that use C++ try/catch around zip_* error cases;
|
||||
// - direct compiler_load_shared_unit() access to native SharedUnit internals.
|
||||
// Background tasks, sleep/usleep, sockets/custom servers, memcache, mysql,
|
||||
// and unit_call/unit_compile/unit_info/units_list are host-owned but now have
|
||||
// membrane hostcalls, so they intentionally do not fallback here.
|
||||
StringList native_only_tokens = {
|
||||
"zip_", "socket_", "server_start_http(", "server_stop(",
|
||||
"memcache_", "mysql_", "unit_call(",
|
||||
"unit_compile(", "unit_info(", "units_list(", "compiler_load_shared_unit("
|
||||
"zip_", "compiler_load_shared_unit("
|
||||
};
|
||||
for(auto& token : native_only_tokens)
|
||||
if(source.find(token) != String::npos)
|
||||
|
||||
+309
-17
@@ -10,32 +10,323 @@
|
||||
#include "../lib/mysql-connector.h"
|
||||
#include "../lib/sqlite-connector.h"
|
||||
|
||||
// ---- W3 connector membrane stubs -------------------------------------------
|
||||
// Generated units reference the connector class surface through uce_lib.h, so
|
||||
// the core must define it. Until the real hostcall connectors land (W5 parity
|
||||
// scope: the starter uses no database), every operation fails cleanly with an
|
||||
// explanatory error instead of trapping.
|
||||
// ---- W3 connector membranes -----------------------------------------------
|
||||
// sqlite/mysql run host-side (the host links the native connectors and owns the
|
||||
// connections in per-workspace handle tables). UCEB1-marshalled hostcalls carry
|
||||
// operation requests/responses; `connection` holds the host handle (>0).
|
||||
|
||||
static const char* WASM_DB_UNAVAILABLE =
|
||||
"database connectors are not yet available in the wasm workspace";
|
||||
"database connector is not available in the wasm workspace";
|
||||
|
||||
extern "C" size_t uce_host_mysql(const char* in, size_t in_len, char* out, size_t cap);
|
||||
extern "C" size_t uce_host_zip(const char* in, size_t in_len, char* out, size_t cap);
|
||||
extern "C" size_t uce_host_units(const char* in, size_t in_len, char* out, size_t cap);
|
||||
|
||||
static DValue wasm_sized_hostcall(DValue request, size_t (*hostcall)(const char*, size_t, char*, size_t))
|
||||
{
|
||||
String encoded = ucb_encode(request);
|
||||
size_t need = hostcall(encoded.data(), encoded.size(), 0, 0);
|
||||
if(need == 0)
|
||||
return(DValue());
|
||||
String buffer(need, 0);
|
||||
size_t got = hostcall(encoded.data(), encoded.size(), &buffer[0], need);
|
||||
if(got == 0 || got > need)
|
||||
return(DValue());
|
||||
DValue response;
|
||||
String error;
|
||||
ucb_decode(String(buffer.data(), got), response, &error);
|
||||
return(response);
|
||||
}
|
||||
|
||||
static DValue wasm_zip_call(DValue request)
|
||||
{
|
||||
DValue response = wasm_sized_hostcall(request, uce_host_zip);
|
||||
return(response);
|
||||
}
|
||||
|
||||
DValue zip_list(String zip_file_name)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "list";
|
||||
request["path"] = zip_file_name;
|
||||
DValue response = wasm_zip_call(request);
|
||||
DValue* result = response.key("result");
|
||||
return(result ? *result : DValue());
|
||||
}
|
||||
|
||||
String zip_read(String zip_file_name, String entry_name)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "read";
|
||||
request["path"] = zip_file_name;
|
||||
request["entry"] = entry_name;
|
||||
DValue response = wasm_zip_call(request);
|
||||
return(response["result"].to_string());
|
||||
}
|
||||
|
||||
bool zip_create(String zip_file_name, DValue entries)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "create";
|
||||
request["path"] = zip_file_name;
|
||||
request["entries"] = entries;
|
||||
DValue response = wasm_zip_call(request);
|
||||
return(response["ok"].to_bool());
|
||||
}
|
||||
|
||||
bool zip_extract(String zip_file_name, String destination_directory)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "extract";
|
||||
request["path"] = zip_file_name;
|
||||
request["destination"] = destination_directory;
|
||||
DValue response = wasm_zip_call(request);
|
||||
return(response["ok"].to_bool());
|
||||
}
|
||||
|
||||
String gz_compress(String src)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "gz_compress";
|
||||
request["src"] = src;
|
||||
DValue response = wasm_zip_call(request);
|
||||
return(response["result"].to_string());
|
||||
}
|
||||
|
||||
String gz_uncompress(String compressed)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "gz_uncompress";
|
||||
request["src"] = compressed;
|
||||
DValue response = wasm_zip_call(request);
|
||||
return(response["result"].to_string());
|
||||
}
|
||||
|
||||
static DValue wasm_units_call(DValue request)
|
||||
{
|
||||
return(wasm_sized_hostcall(request, uce_host_units));
|
||||
}
|
||||
|
||||
DValue unit_info(String path)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "info";
|
||||
request["path"] = path;
|
||||
DValue response = wasm_units_call(request);
|
||||
DValue* result = response.key("result");
|
||||
return(result ? *result : DValue());
|
||||
}
|
||||
|
||||
StringList units_list()
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "list";
|
||||
DValue response = wasm_units_call(request);
|
||||
StringList result;
|
||||
DValue* items = response.key("result");
|
||||
if(items)
|
||||
items->each([&](const DValue& value, String) { result.push_back(value.to_string()); });
|
||||
return(result);
|
||||
}
|
||||
|
||||
bool unit_compile(String path)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "compile";
|
||||
request["path"] = path;
|
||||
DValue response = wasm_units_call(request);
|
||||
return(response["ok"].to_bool());
|
||||
}
|
||||
|
||||
static DValue wasm_unit_call_result;
|
||||
DValue* unit_call(String file_name, String function_name, DValue* call_param)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "call";
|
||||
request["file"] = file_name;
|
||||
request["function"] = function_name;
|
||||
if(call_param)
|
||||
request["param"] = *call_param;
|
||||
DValue response = wasm_units_call(request);
|
||||
String output = response["output"].to_string();
|
||||
if(output != "")
|
||||
print(output);
|
||||
DValue* result = response.key("result");
|
||||
if(result)
|
||||
{
|
||||
wasm_unit_call_result = *result;
|
||||
return(&wasm_unit_call_result);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path, bool opt_so_optional)
|
||||
{
|
||||
(void)context; (void)file_name; (void)current_path; (void)opt_so_optional;
|
||||
return(0);
|
||||
}
|
||||
|
||||
static DValue wasm_mysql_call(DValue request)
|
||||
{
|
||||
return(wasm_sized_hostcall(request, uce_host_mysql));
|
||||
}
|
||||
|
||||
bool MySQL::connect(String host, String username, String password)
|
||||
{
|
||||
(void)host; (void)username; (void)password;
|
||||
connection = 0;
|
||||
statement_info = WASM_DB_UNAVAILABLE;
|
||||
DValue request;
|
||||
request["op"] = "connect";
|
||||
request["host"] = host;
|
||||
request["username"] = username;
|
||||
request["password"] = password;
|
||||
DValue response = wasm_mysql_call(request);
|
||||
u64 handle = response["handle"].to_u64();
|
||||
connection = (void*)(uintptr_t)handle;
|
||||
_preload_next_error_code = (u32)response["error_code"].to_u64();
|
||||
statement_info = response["statement_info"].to_string();
|
||||
return(handle != 0 && _preload_next_error_code == 0);
|
||||
}
|
||||
|
||||
void MySQL::disconnect()
|
||||
{
|
||||
if(connection)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "disconnect";
|
||||
request["handle"] = (f64)(uintptr_t)connection;
|
||||
wasm_mysql_call(request);
|
||||
connection = 0;
|
||||
}
|
||||
}
|
||||
|
||||
String MySQL::error()
|
||||
{
|
||||
String result = statement_info;
|
||||
statement_info = "";
|
||||
_preload_next_error_code = 0;
|
||||
return(result);
|
||||
}
|
||||
|
||||
String MySQL::escape(String raw, char quote_char) { return(mysql_escape(raw, quote_char)); }
|
||||
|
||||
String mysql_escape(String raw, char quote_char)
|
||||
{
|
||||
DValue request;
|
||||
request["op"] = "escape";
|
||||
request["raw"] = raw;
|
||||
request["quote_char"] = String(quote_char > 0 ? 1 : 0, quote_char);
|
||||
DValue response = wasm_mysql_call(request);
|
||||
DValue* result = response.key("result");
|
||||
return(result ? result->to_string() : raw);
|
||||
}
|
||||
|
||||
String MySQL::parse_query_parameters(String query, StringMap map)
|
||||
{
|
||||
String result;
|
||||
query.append(1, ' ');
|
||||
|
||||
u8 mode = 0;
|
||||
char quote = 0;
|
||||
String identifier;
|
||||
for(u32 i = 0; i < query.length(); i++)
|
||||
{
|
||||
char c = query[i];
|
||||
if(mode == 0)
|
||||
{
|
||||
if(c == ':')
|
||||
{
|
||||
mode = 1;
|
||||
identifier = "";
|
||||
}
|
||||
else if(c == '"' || c == '\'')
|
||||
{
|
||||
result.append(1, c);
|
||||
mode = 2;
|
||||
quote = c;
|
||||
}
|
||||
else
|
||||
result.append(1, c);
|
||||
}
|
||||
else if(mode == 1)
|
||||
{
|
||||
if(isalnum(c))
|
||||
identifier.append(1, c);
|
||||
else
|
||||
{
|
||||
result.append(escape(map[identifier]));
|
||||
result.append(1, c);
|
||||
mode = 0;
|
||||
}
|
||||
}
|
||||
else if(mode == 2)
|
||||
{
|
||||
if(c == quote)
|
||||
mode = 0;
|
||||
result.append(1, c);
|
||||
}
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
static bool wasm_mysql_has_unquoted_positional_placeholder(String query)
|
||||
{
|
||||
bool quoted = false;
|
||||
char quote = 0;
|
||||
bool escaped = false;
|
||||
for(u32 i = 0; i < query.length(); i++)
|
||||
{
|
||||
char c = query[i];
|
||||
if(quoted)
|
||||
{
|
||||
if(escaped)
|
||||
{
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if(c == '\\')
|
||||
{
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if(c == quote)
|
||||
quoted = false;
|
||||
continue;
|
||||
}
|
||||
if(c == '\'' || c == '"')
|
||||
{
|
||||
quoted = true;
|
||||
quote = c;
|
||||
continue;
|
||||
}
|
||||
if(c == '?')
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
void MySQL::disconnect() { connection = 0; }
|
||||
String MySQL::error() { return(WASM_DB_UNAVAILABLE); }
|
||||
String MySQL::escape(String raw, char quote_char) { (void)quote_char; return(raw); }
|
||||
String MySQL::parse_query_parameters(String query, StringMap m) { (void)m; return(query); }
|
||||
DValue MySQL::query(String q) { (void)q; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
|
||||
DValue MySQL::query(String q, StringMap params) { (void)q; (void)params; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
|
||||
DValue MySQL::get_pending_result() { return(DValue()); }
|
||||
DValue MySQL::query(String q)
|
||||
{
|
||||
if(wasm_mysql_has_unquoted_positional_placeholder(q))
|
||||
{
|
||||
_preload_next_error_code = 2000;
|
||||
statement_info = "mysql positional ? placeholders are not supported; use named :name placeholders";
|
||||
return(DValue());
|
||||
}
|
||||
DValue request;
|
||||
request["op"] = "query";
|
||||
request["handle"] = (f64)(uintptr_t)connection;
|
||||
request["query"] = q;
|
||||
DValue response = wasm_mysql_call(request);
|
||||
insert_id = response["insert_id"].to_u64();
|
||||
affected_rows = (u32)response["affected"].to_u64();
|
||||
_preload_next_error_code = (u32)response["error_code"].to_u64();
|
||||
statement_info = response["statement_info"].to_string();
|
||||
DValue* result = response.key("result");
|
||||
return(result ? *result : DValue());
|
||||
}
|
||||
|
||||
String mysql_escape(String raw, char quote_char) { (void)quote_char; return(raw); }
|
||||
DValue MySQL::query(String q, StringMap params) { return(query(parse_query_parameters(q, params))); }
|
||||
DValue MySQL::get_pending_result() { return(DValue()); }
|
||||
|
||||
// sqlite runs host-side (the host links libsqlite and owns the connections in
|
||||
// a per-workspace handle table). One UCEB1-marshalled hostcall carries
|
||||
@@ -129,6 +420,7 @@ DValue sqlite_query(SQLite* db, String q, const StringMap& params) { return(db ?
|
||||
u64 sqlite_insert_id(SQLite* db) { return(db ? db->insert_id : 0); }
|
||||
u32 sqlite_affected_rows(SQLite* db) { return(db ? db->affected_rows : 0); }
|
||||
void cleanup_sqlite_connections() { }
|
||||
void cleanup_mysql_connections() { }
|
||||
|
||||
static ServerState wasm_server;
|
||||
static Request wasm_request;
|
||||
|
||||
@@ -7,6 +7,16 @@ uce_host_task_spawn
|
||||
uce_host_task_pid
|
||||
uce_host_task_kill
|
||||
uce_host_sleep_us
|
||||
uce_host_socket_connect
|
||||
uce_host_socket_close
|
||||
uce_host_socket_write
|
||||
uce_host_socket_read
|
||||
uce_host_server_start_http
|
||||
uce_host_server_stop
|
||||
uce_host_memcache_command
|
||||
uce_host_mysql
|
||||
uce_host_zip
|
||||
uce_host_units
|
||||
uce_host_component_resolve
|
||||
uce_host_file_exists
|
||||
uce_host_file_read
|
||||
|
||||
@@ -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