feat: membrane remaining wasm host surfaces
This commit is contained in:
parent
c84fc86e6c
commit
fe83c52411
123
src/lib/sys.cpp
123
src/lib/sys.cpp
@ -21,6 +21,14 @@ int uce_host_task_spawn(const char* key, size_t key_len, uint64_t callback_id, d
|
||||
int uce_host_task_pid(const char* key, size_t key_len);
|
||||
int uce_host_task_kill(int pid, int sig);
|
||||
unsigned int uce_host_sleep_us(uint64_t usec);
|
||||
uint64_t uce_host_socket_connect(const char* host, size_t host_len, int port);
|
||||
void uce_host_socket_close(uint64_t sockfd);
|
||||
int uce_host_socket_write(uint64_t sockfd, const char* data, size_t data_len);
|
||||
size_t uce_host_socket_read(uint64_t sockfd, uint32_t max_length, uint32_t timeout, char* buf, size_t cap);
|
||||
int uce_host_server_start_http(const char* key, size_t key_len, const char* bind, size_t bind_len, const char* file, size_t file_len, const char* function, size_t function_len, const char* current, size_t current_len);
|
||||
int uce_host_server_stop(const char* key, size_t key_len);
|
||||
size_t uce_host_memcache_command(uint64_t sockfd, const char* command, size_t command_len, char* buf, size_t cap);
|
||||
size_t uce_host_mysql(const char* in, size_t in_len, char* out, size_t cap);
|
||||
}
|
||||
|
||||
static String wasm_current_unit_file()
|
||||
@ -137,10 +145,25 @@ String time_format_relative(u64 timestamp, String format_very_recent, u64 medium
|
||||
return(wasm_time_expand_delta(format_not_recent, timestamp, now_timestamp));
|
||||
}
|
||||
u64 time_parse(String time_String) { char* end = 0; unsigned long long v = strtoull(time_String.c_str(), &end, 10); return(end && *end == 0 ? (u64)v : 0); }
|
||||
u64 socket_connect(String host, short port) { (void)host; (void)port; return(0); }
|
||||
void socket_close(u64 sockfd) { (void)sockfd; }
|
||||
bool socket_write(u64 sockfd, String data) { (void)sockfd; (void)data; return(false); }
|
||||
String socket_read(u64 sockfd, u32 max_length, u32 timeout) { (void)sockfd; (void)max_length; (void)timeout; return(""); }
|
||||
u64 socket_connect(String host, short port)
|
||||
{
|
||||
return(uce_host_socket_connect(host.data(), host.size(), port));
|
||||
}
|
||||
void socket_close(u64 sockfd) { uce_host_socket_close(sockfd); }
|
||||
bool socket_write(u64 sockfd, String data)
|
||||
{
|
||||
return(uce_host_socket_write(sockfd, data.data(), data.size()) != 0);
|
||||
}
|
||||
String socket_read(u64 sockfd, u32 max_length, u32 timeout)
|
||||
{
|
||||
size_t required = uce_host_socket_read(sockfd, max_length, timeout, 0, 0);
|
||||
if(required == 0)
|
||||
return("");
|
||||
String content(required, 0);
|
||||
size_t got = uce_host_socket_read(sockfd, max_length, timeout, &content[0], required);
|
||||
content.resize(got <= required ? got : 0);
|
||||
return(content);
|
||||
}
|
||||
String ws_message() { return(context ? context->in : ""); }
|
||||
String ws_connection_id() { return(context ? context->resources.websocket_connection_id : ""); }
|
||||
String ws_scope() { return(context ? context->resources.websocket_scope : ""); }
|
||||
@ -156,12 +179,78 @@ String capture_backtrace_string(u32 max_frames, u32 skip_frames) { (void)max_fra
|
||||
String signal_name(int sig) { (void)sig; return(""); }
|
||||
String memcache_escape_key(String key) { return(key); }
|
||||
StringList memcache_escape_keys(StringList keys) { return(keys); }
|
||||
u64 memcache_connect(String host, short port) { (void)host; (void)port; return(0); }
|
||||
String memcache_command(u64 connection, String command) { (void)connection; (void)command; return(""); }
|
||||
bool memcache_set(u64 connection, String key, String value, u64 expires_in) { (void)connection; (void)key; (void)value; (void)expires_in; return(false); }
|
||||
bool memcache_delete(u64 connection, String key) { (void)connection; (void)key; return(false); }
|
||||
String memcache_get(u64 connection, String key, String default_value) { (void)connection; (void)key; return(default_value); }
|
||||
StringMap memcache_get_multiple(u64 connection, StringList keys) { (void)connection; (void)keys; return(StringMap()); }
|
||||
u64 memcache_connect(String host, short port)
|
||||
{
|
||||
if(host == "")
|
||||
host = "127.0.0.1";
|
||||
if(port == 0)
|
||||
port = 11211;
|
||||
return(socket_connect(host, port));
|
||||
}
|
||||
String memcache_command(u64 connection, String command)
|
||||
{
|
||||
size_t required = uce_host_memcache_command(connection, command.data(), command.size(), 0, 0);
|
||||
if(required == 0)
|
||||
return("");
|
||||
String content(required, 0);
|
||||
size_t got = uce_host_memcache_command(connection, command.data(), command.size(), &content[0], required);
|
||||
content.resize(got <= required ? got : 0);
|
||||
return(content);
|
||||
}
|
||||
bool memcache_set(u64 connection, String key, String value, u64 expires_in)
|
||||
{
|
||||
String response = memcache_command(
|
||||
connection,
|
||||
String("set ") + memcache_escape_key(key) + " 0 " + std::to_string(expires_in) + " " + std::to_string(value.length()) + "\r\n" + value
|
||||
);
|
||||
return("STORED" == trim(response));
|
||||
}
|
||||
bool memcache_delete(u64 connection, String key)
|
||||
{
|
||||
String response = memcache_command(connection, String("delete ") + memcache_escape_key(key));
|
||||
return("DELETED" == trim(response));
|
||||
}
|
||||
String memcache_get(u64 connection, String key, String default_value)
|
||||
{
|
||||
String res = memcache_command(connection, String("get ") + memcache_escape_key(key));
|
||||
String header_end = "\r\n";
|
||||
size_t header_pos = res.find(header_end);
|
||||
if(res.rfind("VALUE ", 0) != 0 || header_pos == String::npos)
|
||||
return(default_value);
|
||||
String header = res.substr(0, header_pos);
|
||||
StringList parts = split(header, " ");
|
||||
if(parts.size() < 4)
|
||||
return(default_value);
|
||||
u64 length = int_val(parts[3]);
|
||||
size_t data_pos = header_pos + header_end.size();
|
||||
if(res.size() < data_pos + length)
|
||||
return(default_value);
|
||||
return(res.substr(data_pos, length));
|
||||
}
|
||||
StringMap memcache_get_multiple(u64 connection, StringList keys)
|
||||
{
|
||||
StringMap result;
|
||||
String res = memcache_command(connection, String("get ") + join(memcache_escape_keys(keys), " "));
|
||||
while(res.rfind("VALUE ", 0) == 0)
|
||||
{
|
||||
size_t header_pos = res.find("\r\n");
|
||||
if(header_pos == String::npos)
|
||||
break;
|
||||
StringList parts = split(res.substr(0, header_pos), " ");
|
||||
if(parts.size() < 4)
|
||||
break;
|
||||
String key = parts[1];
|
||||
u64 length = int_val(parts[3]);
|
||||
size_t data_pos = header_pos + 2;
|
||||
if(res.size() < data_pos + length)
|
||||
break;
|
||||
result[key] = res.substr(data_pos, length);
|
||||
res = res.substr(data_pos + length);
|
||||
if(res.rfind("\r\n", 0) == 0)
|
||||
res = res.substr(2);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
void on_segfault(int sig) { (void)sig; }
|
||||
|
||||
static u64 wasm_next_task_callback_id = 1;
|
||||
@ -195,8 +284,18 @@ pid_t task_repeat(String key, f64 interval, std::function<void()> exec_after_spa
|
||||
pid_t task_pid(String key) { return((pid_t)uce_host_task_pid(key.data(), key.size())); }
|
||||
extern "C" unsigned int sleep(unsigned int seconds) { return(uce_host_sleep_us((uint64_t)seconds * 1000000ull)); }
|
||||
extern "C" int usleep(unsigned int usec) { uce_host_sleep_us(usec); return(0); }
|
||||
pid_t server_start_http(String key, String socket_fn_or_port, String call_uce_filename, String call_function) { (void)key; (void)socket_fn_or_port; (void)call_uce_filename; (void)call_function; return(0); }
|
||||
bool server_stop(String key) { (void)key; return(false); }
|
||||
pid_t server_start_http(String key, String socket_fn_or_port, String call_uce_filename, String call_function)
|
||||
{
|
||||
String current = wasm_current_unit_file();
|
||||
return((pid_t)uce_host_server_start_http(
|
||||
key.data(), key.size(),
|
||||
socket_fn_or_port.data(), socket_fn_or_port.size(),
|
||||
call_uce_filename.data(), call_uce_filename.size(),
|
||||
call_function.data(), call_function.size(),
|
||||
current.data(), current.size()
|
||||
));
|
||||
}
|
||||
bool server_stop(String key) { return(uce_host_server_stop(key.data(), key.size()) != 0); }
|
||||
StringMap default_config()
|
||||
{
|
||||
StringMap cfg;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user