// W3 — production wasm workspace runtime + membrane (WASM-PROPOSAL §6, §7). // // One workspace per request: a fresh Wasmtime store holding the core module // instance (production uce_lib carve-out, owns memory/allocator/DValue) plus unit // PIC side modules loaded lazily — including mid-request via the // uce_host_component_resolve hostcall, which is how component()/unit_render() // inside the guest trigger dynamic loading. // // Designed for amalgamation-include (like uce_lib.cpp): the including TU must // provide String/DValue/ucb_encode/ucb_decode (types.cpp + dvalue.cpp) and // wasm_trace.h. W4 includes this from the FastCGI worker build; the W3 CLI // driver (w3_driver.cpp) is the first consumer. // // Loader rules carried from the spike phases, now enforced as policy: // - import discipline: units may import only env / GOT.mem / GOT.func // (no WASI — the core is zero-WASI for UCE's own calls, and units get // everything from the core); units defining allocator symbols are rejected // - GOT.mem of a PIC module's data exports are __memory_base-relative // offsets; the loader adds the owning module's base (Phase 0 erratum) // - GOT.func resolves host-side: the target function's funcref is placed // into the shared table and the slot index becomes the GOT value // - export name collisions: core wins, then first-loading unit wins // (mirrors native dlopen global-symbol interposition for identical // vague-linkage template instantiations) // - uce.abi stamp must match the core ABI version; fail loudly, never guess #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct WasmDylinkInfo { u32 mem_size = 0; u32 mem_align = 0; u32 table_size = 0; u32 table_align = 0; bool found = false; }; struct WasmAbiInfo { u32 version = 0; String toolchain; bool found = false; }; struct WasmUnitModule { String source_path; // absolute .uce path String wasm_path; // artifact in the unit cache time_t mtime = 0; WasmDylinkInfo dylink; WasmAbiInfo abi; std::optional module; }; struct WasmWorkerConfig { String core_wasm_path = "bin/wasm/core.wasm"; String site_root; // absolute String cache_root = "/tmp/uce/work"; std::vector write_roots; // absolute prefixes the write membrane allows int64_t memory_limit = 512ll * 1024 * 1024; u32 table_headroom = 4096; u64 epoch_deadline_ticks = 200; // ticker period × ticks = CPU budget bool verbose = false; // uce_host_* names (bare, without the "uce_host_" prefix) the sysadmin has // disabled via UCE_HOSTCALL_BLOCKLIST. A blocked hostcall resolves to a trap // stub at workspace birth (see make_host_import); empty = feature off. std::set hostcall_blocklist; }; struct WasmResponse { bool ok = false; bool handler_present = true; // false → unit has no handler for the requested kind (404) String body; DValue meta; // status / headers / cookies / session String error; // collapsed trace or loader error when !ok u64 workspace_birth_us = 0; u64 component_resolve_count = 0; u64 component_resolve_total_us = 0; }; static u64 wasm_file_lock_timeout_ms() { const char* raw = getenv("UCE_FILE_LOCK_TIMEOUT_MS"); if(!raw || !*raw) return(2000); char* end = 0; unsigned long long parsed = strtoull(raw, &end, 10); return(end == raw ? 2000 : (u64)parsed); } static u64 wasm_monotonic_ms() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return((u64)ts.tv_sec * 1000ull + (u64)ts.tv_nsec / 1000000ull); } static int wasm_open_locked_file(const String& file_name, int flags, int lock_type, bool truncate_after_lock) { int fd = open(file_name.c_str(), flags, 0644); if(fd < 0) return(-1); fcntl(fd, F_SETFD, FD_CLOEXEC); u64 timeout = wasm_file_lock_timeout_ms(); u64 deadline = wasm_monotonic_ms() + timeout; while(true) { if(flock(fd, lock_type | LOCK_NB) == 0) { if(truncate_after_lock && ftruncate(fd, 0) != 0) { flock(fd, LOCK_UN); close(fd); return(-1); } return(fd); } if(errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) { close(fd); return(-1); } if(timeout == 0 || wasm_monotonic_ms() >= deadline) { fprintf(stderr, "[wasm] file lock timeout after %llums: %s\n", (unsigned long long)timeout, file_name.c_str()); close(fd); return(-1); } usleep(10000); } } static bool wasm_fd_write_all(int fd, const char* data, size_t remaining, u64* written_out) { u64 total = 0; while(remaining > 0) { ssize_t n = write(fd, data, remaining); if(n < 0) { if(errno == EINTR) continue; return(false); } if(n == 0) return(false); data += n; remaining -= (size_t)n; total += (u64)n; } if(written_out) *written_out = total; return(true); } // ---- file-backed async job registry + bounded process execution ---------- static String uce_job_root() { const char* env = getenv("UCE_JOB_ROOT"); String root = (env && *env) ? String(env) : String("/run/uce/jobs"); std::error_code ec; std::filesystem::create_directories(root, ec); if(ec) { root = "/tmp/uce/jobs"; std::filesystem::create_directories(root, ec); } return(root); } static String uce_job_path(u64 id) { return(uce_job_root() + "/" + std::to_string(id)); } static String uce_read_text(const String& path) { std::ifstream in(path, std::ios::binary); if(!in) return(""); std::ostringstream ss; ss << in.rdbuf(); return(ss.str()); } static void uce_write_text(const String& path, const String& data) { std::ofstream out(path, std::ios::binary|std::ios::trunc); out.write(data.data(), (std::streamsize)data.size()); } static u64 uce_job_new(const String& kind) { String root = uce_job_root(); std::error_code ec; std::filesystem::create_directories(root, ec); u64 seed = ((u64)time(0) << 32) ^ ((u64)getpid() << 16) ^ (u64)rand(); for(int i = 0; i < 100; i++) { u64 id = seed ^ (wasm_monotonic_ms() + (u64)i * 0x9e3779b97f4a7c15ull); String dir = root + "/" + std::to_string(id); if(mkdir(dir.c_str(), 0700) == 0) { uce_write_text(dir + "/kind", kind); uce_write_text(dir + "/created", std::to_string((u64)time(0))); uce_write_text(dir + "/state", "pending"); return(id); } } return(0); } static void uce_job_reap() { String root = uce_job_root(); u64 now = (u64)time(0); u64 ttl = 3600; if(const char* raw = getenv("UCE_JOB_TTL_SECONDS")) { char* e=0; unsigned long long v=strtoull(raw,&e,10); if(e!=raw && v>0) ttl=(u64)v; } std::error_code ec; for(auto& e : std::filesystem::directory_iterator(root, ec)) { if(!e.is_directory()) continue; u64 created = strtoull(uce_read_text(e.path().string()+"/created").c_str(), 0, 10); if(created > 0 && now > created + ttl) std::filesystem::remove_all(e.path(), ec); } } static DValue uce_process_exec(String cmd, String input, StringMap env, u64 timeout_ms) { DValue r; r["exit_code"] = (f64)-1; r["stdout"] = ""; r["stderr"] = ""; r["timed_out"].set_bool(false); if(timeout_ms == 0) timeout_ms = 5000; int inpipe[2], outpipe[2], errpipe[2]; if(pipe(inpipe) || pipe(outpipe) || pipe(errpipe)) { r["stderr"]="pipe failed"; return(r); } pid_t pid = fork(); if(pid == 0) { dup2(inpipe[0], 0); dup2(outpipe[1], 1); dup2(errpipe[1], 2); close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]); for(auto& kv : env) setenv(kv.first.c_str(), kv.second.c_str(), 1); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); _exit(127); } close(inpipe[0]); close(outpipe[1]); close(errpipe[1]); fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0) | O_NONBLOCK); fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0) | O_NONBLOCK); size_t input_off = 0; bool in_open = true, out_open = true, err_open = true; int status = 0; bool exited = false; u64 deadline = wasm_monotonic_ms() + timeout_ms; while(out_open || err_open || !exited) { if(!exited) { pid_t w = waitpid(pid, &status, WNOHANG); if(w == pid) exited = true; } if(in_open) { if(input_off < input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off += (size_t)n; else if(n<0 && errno!=EINTR) { close(inpipe[1]); in_open=false; } } else { close(inpipe[1]); in_open=false; } } char buf[4096]; ssize_t n; while((n=read(outpipe[0], buf, sizeof(buf))) > 0) r["stdout"] = r["stdout"].to_string() + String(buf, n); if(n == 0 && out_open) { close(outpipe[0]); out_open=false; } while((n=read(errpipe[0], buf, sizeof(buf))) > 0) r["stderr"] = r["stderr"].to_string() + String(buf, n); if(n == 0 && err_open) { close(errpipe[0]); err_open=false; } if(!exited && wasm_monotonic_ms() >= deadline) { r["timed_out"].set_bool(true); kill(pid, SIGKILL); waitpid(pid, &status, 0); exited = true; } if((out_open || err_open || !exited)) usleep(10000); } if(WIFEXITED(status)) { r["exit_code"] = (f64)WEXITSTATUS(status); r["timed_out"].set_bool(false); } else if(WIFSIGNALED(status)) r["exit_code"] = (f64)(128 + WTERMSIG(status)); return(r); } static DValue uce_shell_exec_spec(const DValue& spec) { return(uce_process_exec(spec.key("cmd") ? spec.key("cmd")->to_string() : String(""), spec.key("stdin") ? spec.key("stdin")->to_string() : String(""), spec.key("env") ? spec.key("env")->to_stringmap() : StringMap(), spec.key("timeout_ms") ? spec.key("timeout_ms")->to_u64(5000) : 5000)); } static void uce_job_finish(u64 id, DValue result, String final_state="done") { String dir = uce_job_path(id); uce_write_text(dir + "/result.tmp", ucb_encode(result)); rename((dir + "/result.tmp").c_str(), (dir + "/result").c_str()); uce_write_text(dir + "/state", final_state); } static u64 uce_shell_spawn_spec(const DValue& spec) { uce_job_reap(); u64 id = uce_job_new("shell"); if(!id) return(0); pid_t pid = fork(); if(pid == 0) { setsid(); uce_write_text(uce_job_path(id) + "/worker_pid", std::to_string((long long)getpid())); uce_write_text(uce_job_path(id) + "/state", "running"); DValue result = uce_shell_exec_spec(spec); uce_job_finish(id, result, "done"); _exit(0); } if(pid < 0) { DValue r; r["error"]="fork failed"; uce_job_finish(id,r,"failed"); return(id); } uce_write_text(uce_job_path(id) + "/worker_pid", std::to_string((long long)pid)); uce_write_text(uce_job_path(id) + "/state", "running"); return(id); } static DValue uce_exec_argv_capture(std::vector argv, String input, u64 timeout_ms) { DValue r; r["exit_code"]=(f64)-1; r["stdout"]=""; r["stderr"]=""; r["timed_out"].set_bool(false); if(argv.empty()) { r["stderr"]="empty argv"; return(r); } if(timeout_ms == 0) timeout_ms = 5000; int inpipe[2], outpipe[2], errpipe[2]; if(pipe(inpipe)||pipe(outpipe)||pipe(errpipe)) { r["stderr"]="pipe failed"; return(r); } pid_t pid=fork(); if(pid==0) { dup2(inpipe[0],0); dup2(outpipe[1],1); dup2(errpipe[1],2); close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]); std::vector args; for(auto& a: argv) args.push_back((char*)a.c_str()); args.push_back(0); execvp(args[0], args.data()); _exit(127); } close(inpipe[0]); close(outpipe[1]); close(errpipe[1]); fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0)|O_NONBLOCK); fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0)|O_NONBLOCK); size_t input_off=0; bool in_open=true,out_open=true,err_open=true,exited=false; int status=0; u64 deadline=wasm_monotonic_ms()+timeout_ms; while(out_open || err_open || !exited) { if(!exited) { pid_t w=waitpid(pid,&status,WNOHANG); if(w==pid) exited=true; } if(in_open) { if(input_off0) input_off+=(size_t)n; else if(n<0 && errno!=EINTR) { close(inpipe[1]); in_open=false; } } else { close(inpipe[1]); in_open=false; } } char buf[4096]; ssize_t n; while((n=read(outpipe[0],buf,sizeof(buf)))>0) r["stdout"] = r["stdout"].to_string()+String(buf,n); if(n==0&&out_open){close(outpipe[0]);out_open=false;} while((n=read(errpipe[0],buf,sizeof(buf)))>0) r["stderr"] = r["stderr"].to_string()+String(buf,n); if(n==0&&err_open){close(errpipe[0]);err_open=false;} if(!exited && wasm_monotonic_ms() >= deadline) { r["timed_out"].set_bool(true); kill(pid,SIGKILL); waitpid(pid,&status,0); exited=true; } if(out_open || err_open || !exited) usleep(10000); } if(WIFEXITED(status)) { r["exit_code"]=(f64)WEXITSTATUS(status); r["timed_out"].set_bool(false); } else if(WIFSIGNALED(status)) r["exit_code"]=(f64)(128+WTERMSIG(status)); return(r); } static bool uce_header_name_safe(String name) { if(name=="") return(false); for(unsigned char c: name) if(!(isalnum(c)||c=='-'||c=='_')) return(false); return(true); } static DValue uce_http_request_value(const DValue& req) { DValue r; r["status"]=(f64)0; r["headers"].set_array(); r["body"]=""; r["error"]=""; const DValue* method_value = req.key("method"); const DValue* url_value = req.key("url"); const DValue* timeout_value = req.key("timeout_ms"); const DValue* follow_redirects_value = req.key("follow_redirects"); const DValue* headers_value = req.key("headers"); const DValue* body_value = req.key("body"); String method = method_value ? to_upper(method_value->to_string()) : String("GET"); String url = url_value ? url_value->to_string() : String(""); if(url=="" || url.find('\0')!=String::npos) { r["error"]="missing url"; return(r); } if(method=="") method="GET"; u64 timeout_ms = timeout_value ? timeout_value->to_u64(5000) : 5000; std::vector argv = {"curl", "-sS", "--http1.0", "-X", method, "-D", "-", "-w", "\nUCE_HTTP_STATUS:%{http_code}", "--max-time", std::to_string(std::max(1, timeout_ms / 1000))}; if(follow_redirects_value && follow_redirects_value->to_bool()) argv.push_back("-L"); if(headers_value) headers_value->each([&](const DValue& v, String k){ if(uce_header_name_safe(k)) { argv.push_back("-H"); argv.push_back(k + ": " + replace(replace(v.to_string(), "\r", " "), "\n", " ")); } }); String body = body_value ? body_value->to_string() : String(""); if(body_value) { argv.push_back("--data-binary"); argv.push_back("@-"); } argv.push_back(url); if(access("/usr/bin/curl", X_OK)!=0 && access("/bin/curl", X_OK)!=0) { r["error"]="curl binary not found in runtime PATH"; return(r); } DValue pr = uce_exec_argv_capture(argv, body, timeout_ms); String out = pr["stdout"].to_string(); String marker="\nUCE_HTTP_STATUS:"; size_t mp=out.rfind(marker); if(mp!=String::npos) { r["status"]=(f64)strtoull(out.c_str()+mp+marker.size(),0,10); out=out.substr(0,mp); } else r["error"]="curl did not report status"; String sep="\r\n\r\n"; size_t hp=out.rfind(sep); size_t sep_len=4; if(hp==String::npos) { sep="\n\n"; hp=out.rfind(sep); sep_len=2; } String hdrs = hp==String::npos ? String("") : out.substr(0,hp); r["body"] = hp==String::npos ? out : out.substr(hp+sep_len); DValue headers; for(String line: split(replace(hdrs,"\r",""), "\n")) { size_t c=line.find(':'); if(c!=String::npos) headers[trim(line.substr(0,c))] = trim(line.substr(c+1)); } r["headers"] = headers; if(pr["exit_code"].to_s64() != 0 && r["error"].to_string()=="") r["error"] = trim(pr["stderr"].to_string()); return(r); } static u64 uce_http_spawn_spec(const DValue& req) { uce_job_reap(); u64 id=uce_job_new("http"); if(!id) return(0); pid_t pid=fork(); if(pid==0) { setsid(); uce_write_text(uce_job_path(id)+"/worker_pid", std::to_string((long long)getpid())); uce_write_text(uce_job_path(id)+"/state", "running"); DValue result=uce_http_request_value(req); uce_job_finish(id,result,result["error"].to_string()==""?"done":"failed"); _exit(0); } if(pid<0) { DValue r; r["error"]="fork failed"; uce_job_finish(id,r,"failed"); return(id); } uce_write_text(uce_job_path(id)+"/worker_pid", std::to_string((long long)pid)); uce_write_text(uce_job_path(id)+"/state", "running"); return(id); } static DValue uce_job_status_value(u64 id) { DValue r; String dir=uce_job_path(id); r["job_id"]=(f64)id; if(id==0 || !std::filesystem::is_directory(dir)) { r["state"]="missing"; return(r); } String state=trim(uce_read_text(dir+"/state")); if(state=="") state="pending"; r["state"]=state; r["kind"]=trim(uce_read_text(dir+"/kind")); r["pid"]=(f64)strtoull(uce_read_text(dir+"/worker_pid").c_str(),0,10); r["done"].set_bool(state=="done"||state=="failed"||state=="cancelled"); return(r); } static DValue uce_job_result_value(u64 id, u64 timeout_ms) { u64 deadline = wasm_monotonic_ms() + timeout_ms; while(timeout_ms > 0 && wasm_monotonic_ms() < deadline) { DValue st = uce_job_status_value(id); if(st["done"].to_bool()) break; usleep(10000); } DValue r = uce_job_status_value(id); String encoded = uce_read_text(uce_job_path(id)+"/result"); if(encoded != "") { DValue decoded; String err; if(ucb_decode(encoded, decoded, &err)) r["result"] = decoded; } return(r); } static bool uce_job_cancel_value(u64 id) { DValue st = uce_job_status_value(id); if(st["state"].to_string()=="missing") return(false); String state = st["state"].to_string(); if(state == "done" || state == "failed" || state == "cancelled") return(false); pid_t pid = (pid_t)st["pid"].to_u64(0); if(pid > 0) kill(-pid, SIGKILL); DValue result; result["cancelled"].set_bool(true); uce_job_finish(id, result, "cancelled"); return(true); } // ---- module byte parsing (hardened; carried from the phase 3 spike) ------- // included into both w3_driver.cpp and the native server TU (via backend.cpp); // file-scope helpers are static so each TU gets its own copy with no clash static bool wasm_read_uleb(const std::vector& buf, size_t& pos, size_t end, u64& out) { out = 0; u32 shift = 0; while(true) { if(pos >= end || shift >= 64) return(false); u8 byte = buf[pos++]; out |= ((u64)(byte & 0x7f)) << shift; if((byte & 0x80) == 0) return(true); shift += 7; } } static bool wasm_parse_sections(const std::vector& bytes, WasmDylinkInfo& dylink, WasmAbiInfo& abi, String& error) { if(bytes.size() < 8 || memcmp(bytes.data(), "\0asm", 4) != 0) { error = "not a wasm module"; return(false); } if(!(bytes[4] == 1 && bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0)) { error = "unsupported wasm binary version"; return(false); } size_t pos = 8; while(pos < bytes.size()) { u8 section_id = bytes[pos++]; u64 size = 0; if(!wasm_read_uleb(bytes, pos, bytes.size(), size) || size > bytes.size() - pos) { error = "malformed wasm section header"; return(false); } size_t end = pos + (size_t)size; if(section_id == 0) { u64 name_len = 0; size_t cursor = pos; if(!wasm_read_uleb(bytes, cursor, end, name_len) || name_len > end - cursor) { error = "malformed custom section name"; return(false); } String name((const char*)bytes.data() + cursor, (size_t)name_len); cursor += (size_t)name_len; if(name == "dylink.0") { while(cursor < end) { u8 sub = bytes[cursor++]; u64 sub_len = 0; if(!wasm_read_uleb(bytes, cursor, end, sub_len) || sub_len > end - cursor) { error = "malformed dylink.0 subsection"; return(false); } size_t sub_end = cursor + (size_t)sub_len; if(sub == 1) // WASM_DYLINK_MEM_INFO { u64 v[4]; for(int i = 0; i < 4; i++) if(!wasm_read_uleb(bytes, cursor, sub_end, v[i])) { error = "malformed dylink.0 mem_info"; return(false); } if(dylink.found) { error = "duplicate dylink.0 mem_info"; return(false); } if(v[1] >= 31 || v[3] >= 31) { error = "unsupported dylink alignment"; return(false); } dylink.mem_size = (u32)v[0]; dylink.mem_align = (u32)v[1]; dylink.table_size = (u32)v[2]; dylink.table_align = (u32)v[3]; dylink.found = true; } cursor = sub_end; } } else if(name == "uce.abi") { String text((const char*)bytes.data() + cursor, end - cursor); abi.found = true; size_t line_start = 0; while(line_start < text.size()) { size_t line_end = text.find('\n', line_start); if(line_end == String::npos) line_end = text.size(); String line = text.substr(line_start, line_end - line_start); if(line.rfind("unit_abi_version=", 0) == 0) abi.version = (u32)strtoul(line.c_str() + 17, 0, 10); if(line.rfind("toolchain=", 0) == 0) abi.toolchain = line.substr(10); line_start = line_end + 1; } } } pos = end; } return(true); } static bool wasm_read_file(const String& path, std::vector& out) { std::ifstream in(path, std::ios::binary); if(!in) return(false); in.seekg(0, std::ios::end); std::streamoff n = in.tellg(); if(n < 0) return(false); in.seekg(0, std::ios::beg); out.resize((size_t)n); if(n == 0) return(true); in.read((char*)out.data(), n); return((bool)in); } // ---- worker (per process): engine + compiled module caches ---------------- class WasmWorker { public: WasmWorkerConfig cfg; explicit WasmWorker(WasmWorkerConfig config) : cfg(std::move(config)), engine(make_engine()) { } String init() { std::vector bytes; if(!wasm_read_file(cfg.core_wasm_path, bytes)) return("cannot read core module: " + cfg.core_wasm_path); WasmAbiInfo abi_ignored; String parse_error; if(!wasm_parse_sections(bytes, core_dylink, abi_ignored, parse_error)) return("core module: " + parse_error); String core_cached_path = cached_wasm_path(cfg.core_wasm_path); String compile_error; auto compiled_or_cached = load_or_compile_cached_module(engine, core_cached_path, cfg.core_wasm_path, bytes, compile_error); if(!compiled_or_cached) return("core module compile failed: " + compile_error); core_module.emplace(std::move(*compiled_or_cached)); return(""); } wasmtime::Engine engine; std::optional core_module; WasmDylinkInfo core_dylink; // unit artifact path in the W2 cache: cache_root + + /.wasm String unit_wasm_path(const String& source_path) const { return(cfg.cache_root + source_path + ".wasm"); } std::shared_ptr unit_module(const String& source_path, String& error) { String wasm_path = unit_wasm_path(source_path); struct stat st; if(stat(wasm_path.c_str(), &st) != 0) { error = "no wasm artifact for " + source_path + " (expected " + wasm_path + ")"; return(nullptr); } auto cached = module_cache.find(wasm_path); if(cached != module_cache.end() && cached->second->mtime == st.st_mtime) return(cached->second); auto unit = std::make_shared(); unit->source_path = source_path; unit->wasm_path = wasm_path; unit->mtime = st.st_mtime; std::vector bytes; if(!wasm_read_file(wasm_path, bytes)) { error = "cannot read " + wasm_path; return(nullptr); } if(!wasm_parse_sections(bytes, unit->dylink, unit->abi, error)) { error = wasm_path + ": " + error; return(nullptr); } if(!unit->dylink.found) { error = wasm_path + ": missing dylink.0 mem_info (not a PIC side module)"; return(nullptr); } if(!unit->abi.found) { error = wasm_path + ": missing uce.abi stamp"; return(nullptr); } String unit_cached_path = cached_wasm_path(wasm_path); String compile_error; auto compiled_or_cached = load_or_compile_cached_module(engine, unit_cached_path, wasm_path, bytes, compile_error); if(!compiled_or_cached) { error = wasm_path + ": compile failed: " + compile_error; return(nullptr); } unit->module.emplace(std::move(*compiled_or_cached)); module_cache[wasm_path] = unit; return(unit); } private: static String cached_wasm_path(const String& wasm_path) { if(wasm_path.size() >= 5 && wasm_path.rfind(".wasm", wasm_path.size() - 5) == wasm_path.size() - 5) return(wasm_path.substr(0, wasm_path.size() - 5) + ".cwasm"); return(wasm_path + ".cwasm"); } static std::optional load_or_compile_cached_module(wasmtime::Engine& engine, const String& cached_path, const String& wasm_path, std::vector& bytes, String& compile_error) { struct stat wasm_stat; struct stat cached_stat; if(stat(cached_path.c_str(), &cached_stat) == 0 && stat(wasm_path.c_str(), &wasm_stat) == 0) { if(cached_stat.st_mtime > wasm_stat.st_mtime) { auto deserialized = wasmtime::Module::deserialize_file(engine, cached_path); if(deserialized) return(deserialized.ok()); } } auto compiled = wasmtime::Module::compile(engine, bytes); if(!compiled) { compile_error = String(compiled.err().message()); return(std::nullopt); } auto result = compiled.ok(); auto serialized = result.serialize(); if(serialized) { String tmp = cached_path + "." + std::to_string((long long)getpid()) + ".tmp"; auto data = serialized.ok(); { std::ofstream out(tmp, std::ios::binary | std::ios::trunc); if(out) { out.write((const char*)data.data(), (std::streamsize)data.size()); if(out) { out.flush(); out.close(); if(std::rename(tmp.c_str(), cached_path.c_str()) != 0) (void)std::remove(tmp.c_str()); } } else (void)std::remove(tmp.c_str()); } } return(result); } static wasmtime::Engine make_engine() { wasmtime::Config config; config.epoch_interruption(true); // CRITICAL: the host (linux_fastcgi.cpp) installs its own SIGSEGV/SIGILL/ // SIGBUS handlers per request (install_request_fault_handlers) with plain // signal(), which clobbers Wasmtime's trap-handling handlers. With // signals_based_traps on, a guest `unreachable`/OOB surfaces as a host // signal that the native on_segfault catches and abort()s the worker — // a 502 instead of a clean wasm trap. Disabling it makes Cranelift emit // explicit trap checks: guest traps stay pure wasm traps returned as // Result errors, never a host signal, so the two never collide. config.signals_based_traps(false); return(wasmtime::Engine(std::move(config))); } std::map> module_cache; }; // ---- workspace (per request) ---------------------------------------------- class WasmWorkspace { public: WasmWorker& worker; wasmtime::Store store; u64 workspace_birth_us = 0; u64 component_resolve_count = 0; u64 component_resolve_total_us = 0; struct FileHandle { int fd = -1; bool writable = false; }; std::vector file_handles; struct RequestPerfSnapshot { u64 worker_pid = 0; u64 parent_pid = 0; u64 request_count = 0; f64 time_init = 0; f64 time_start = 0; bool active = false; } request_perf; explicit WasmWorkspace(WasmWorker& w) : worker(w), store(w.engine) { } void set_perf_snapshot(u64 worker_pid, u64 parent_pid, u64 request_count, f64 time_init, f64 time_start) { request_perf.worker_pid = worker_pid; request_perf.parent_pid = parent_pid; request_perf.request_count = request_count; request_perf.time_init = time_init; request_perf.time_start = time_start; request_perf.active = true; } #ifdef UCE_WASM_HOST_CONNECTORS // Host-owned resource handle table (§3.1): connections opened by the guest // live here and are closed when the workspace drops at request end. This is // the wasm-side enforcement of request-scoped DB lifecycle; app code should // never cache these opaque handles across requests. std::vector sqlite_handles; std::vector mysql_handles; #endif ~WasmWorkspace() { for(auto& h : file_handles) { if(h.fd >= 0) { flock(h.fd, LOCK_UN); close(h.fd); h.fd = -1; } } #ifdef UCE_WASM_HOST_CONNECTORS for(auto* db : sqlite_handles) if(db) delete db; // ~SQLite disconnects for(auto* db : mysql_handles) if(db) delete db; // ~MySQL disconnects #endif } // The guest calls a sized hostcall twice (buf=0 to learn the length, then // to fetch). For side-effecting ops (sqlite) re-executing on the fetch is // wrong, so the result is staged on the first call (keyed on the exact // 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) { out = staged_hostcall_result; staged_hostcall_input = ""; staged_hostcall_result = ""; return(true); } return(false); } void hostcall_stage(const String& input, const String& out) { staged_hostcall_input = input; staged_hostcall_result = out; } // resolve-kind values shared with the guest core (src/wasm/core.cpp) // must match WasmResolveKind in src/wasm/core.cpp String birth() { auto cx = ctx(); store.limiter(worker.cfg.memory_limit, -1, -1, -1, -1); cx.set_epoch_deadline(worker.cfg.epoch_deadline_ticks); auto& module = *worker.core_module; std::vector imports; for(auto import_type : module.imports()) { String mod(import_type.module()); String name(import_type.name()); auto extern_type = wasmtime::ExternType::from_import(import_type); if(auto* table_ref = std::get_if(&extern_type)) { if(name.rfind("__indirect_function_table", 0) != 0) return("core imports unexpected table " + mod + "." + name); u32 core_min = table_ref->min(); u32 total = core_min + worker.cfg.table_headroom; wasmtime::TableType table_type(wasmtime::ValType::funcref(), total, total); auto created = wasmtime::Table::create(cx, table_type, wasmtime::Val(std::optional())); if(!created) return("table create failed: " + String(created.err().message())); table.emplace(created.ok()); table_next_free = core_min; imports.push_back(*table); continue; } auto* func_ref = std::get_if(&extern_type); if(!func_ref) return("core has unexpected non-func import " + mod + "." + name); wasmtime::FuncType func_type(*func_ref); imports.push_back(make_host_import(cx, mod, name, func_type)); } if(!table) return("core does not import __indirect_function_table — rebuild core with --import-table"); auto created = wasmtime::Instance::create(cx, module, imports); if(!created) return("core instantiation failed: " + trap_text(created.err())); core.emplace(created.ok()); auto exported_memory = core->get(cx, "memory"); if(!exported_memory || !std::get_if(&*exported_memory)) return("core does not export memory"); memory.emplace(std::get(*exported_memory)); String error = call_core("_initialize", {}, 0); if(error != "") return(error); int32_t rc = 0; error = call_core("uce_wasm_core_init", {}, &rc); if(error != "") return(error); if(rc != 0) return("uce_wasm_core_init returned " + std::to_string(rc)); int32_t core_abi = 0; error = call_core("uce_wasm_core_abi_version", {}, &core_abi); if(error != "") return(error); abi_version = (u32)core_abi; error = call_core("uce_wasm_request", {}, &request_ptr); if(error != "") return(error); if(request_ptr == 0) return("core returned null Request*"); return(""); } String apply_context(const DValue& context_tree) { String encoded = ucb_encode(context_tree); int32_t guest_ptr = 0; String error = call_core("uce_alloc", { (int32_t)encoded.size() }, &guest_ptr); if(error != "") return(error); if(guest_ptr == 0) return("guest uce_alloc failed for context buffer"); error = guest_write((u32)guest_ptr, encoded); if(error != "") return(error); int32_t rc = 0; error = call_core("uce_wasm_apply_context", { guest_ptr, (int32_t)encoded.size() }, &rc); if(error != "") return(error); call_core("uce_free", { guest_ptr }, 0); if(rc != 0) return("uce_wasm_apply_context returned " + std::to_string(rc)); return(""); } // Invoke the entry unit through a named handler ("render"/"cli"/"websocket"/ // "serve_http:named"). The handler is just an export name — same resolve + // ONCE + dispatch path as a component. handler_present, when provided, // reports whether the unit exports it (caller maps a missing render to an // empty body, a missing cli/serve handler to a 404). String invoke_entry(const String& entry_source_path, const String& handler, bool* handler_present = 0) { entry_dir = dir_of(entry_source_path); size_t unit_index = 0; String error = load_unit(entry_source_path, unit_index); if(error != "") return(error); bool present = (bool)unit_func(unit_index, handler_export_symbol(handler)); if(handler_present) *handler_present = present; if(!present) return(""); // Invoke through the core: it resolves the same handler and runs ONCE + // dispatch (unit is pre-loaded above; resolution is cached). The core // entry takes two guest buffers — the unit path and the handler name. auto entry = core_func("uce_wasm_invoke_entry"); if(!entry) return("core does not export uce_wasm_invoke_entry"); int32_t path_ptr = 0, handler_ptr = 0; error = call_core("uce_alloc", { (int32_t)entry_source_path.size() }, &path_ptr); if(error != "" || path_ptr == 0) return(error == "" ? String("guest uce_alloc failed for entry path") : error); error = guest_write((u32)path_ptr, entry_source_path); if(error == "") { error = call_core("uce_alloc", { (int32_t)handler.size() }, &handler_ptr); if(error == "" && handler_ptr == 0) error = "guest uce_alloc failed for handler"; } if(error == "") error = guest_write((u32)handler_ptr, handler); if(error != "") { if(path_ptr) call_core("uce_free", { path_ptr }, 0); if(handler_ptr) call_core("uce_free", { handler_ptr }, 0); return(error); } auto result = entry->call(ctx(), { wasmtime::Val(path_ptr), wasmtime::Val((int32_t)entry_source_path.size()), wasmtime::Val(handler_ptr), wasmtime::Val((int32_t)handler.size()) }); call_core("uce_free", { path_ptr }, 0); call_core("uce_free", { handler_ptr }, 0); if(!result) return(trap_text(result.err())); return(""); } String collect(WasmResponse& response) { String error = call_core("uce_wasm_finish_output", {}, 0); if(error != "") return(error); error = call_core("uce_wasm_finish_response_meta", {}, 0); if(error != "") return(error); int32_t body_ptr = 0, body_len = 0, meta_ptr = 0, meta_len = 0; if((error = call_core("uce_wasm_output_data", {}, &body_ptr)) != "") return(error); if((error = call_core("uce_wasm_output_size", {}, &body_len)) != "") return(error); if((error = call_core("uce_wasm_response_meta_data", {}, &meta_ptr)) != "") return(error); if((error = call_core("uce_wasm_response_meta_size", {}, &meta_len)) != "") return(error); error = guest_read((u32)body_ptr, (u32)body_len, response.body); if(error != "") return(error); String meta_encoded; error = guest_read((u32)meta_ptr, (u32)meta_len, meta_encoded); if(error != "") return(error); String decode_error; if(!ucb_decode(meta_encoded, response.meta, &decode_error)) return("response meta decode failed: " + decode_error); return(""); } private: std::optional core; std::optional memory; std::optional table; u32 table_next_free = 0; u32 abi_version = 0; int32_t request_ptr = 0; String entry_dir; struct LoadedUnit { std::shared_ptr mod; std::optional instance; u32 memory_base = 0; }; std::vector units; std::map units_by_source; std::map handler_slots; // source + ":" + symbol → table slot std::vector host_funcs; // keep host imports alive wasmtime::Store::Context ctx() { return(wasmtime::Store::Context(store)); } static String dir_of(const String& path) { auto pos = path.find_last_of('/'); return(pos == String::npos ? String("") : path.substr(0, pos)); } static String trap_text(const wasmtime::TrapError& error) { return(wasm_trace_collapse(String(error.message()))); } // ---- guest memory access (pointer re-derived per call: it moves) ------ String guest_write(u32 ptr, const String& data) { auto span = memory->data(ctx()); if((size_t)ptr + data.size() > span.size()) return("guest write out of bounds"); memcpy(span.data() + ptr, data.data(), data.size()); return(""); } String guest_read(u32 ptr, u32 len, String& out) { auto span = memory->data(ctx()); if((size_t)ptr + len > span.size()) return("guest read out of bounds"); out.assign((const char*)span.data() + ptr, len); return(""); } // ---- calls ------------------------------------------------------------- std::optional core_func(const String& name) { auto exported = core->get(ctx(), std::string_view(name)); if(!exported) return(std::nullopt); if(auto* func = std::get_if(&*exported)) return(*func); return(std::nullopt); } std::optional unit_func(size_t unit_index, const String& name) { auto exported = units[unit_index].instance->get(ctx(), std::string_view(name)); if(!exported) return(std::nullopt); if(auto* func = std::get_if(&*exported)) return(*func); return(std::nullopt); } String call_core(const String& name, std::vector argv, int32_t* result_out) { auto func = core_func(name); if(!func) return("core does not export " + name); std::vector args; for(auto value : argv) args.push_back(wasmtime::Val(value)); auto result = func->call(ctx(), args); if(!result) return(trap_text(result.err())); auto values = result.ok(); if(result_out) *result_out = values.empty() ? 0 : values[0].i32(); return(""); } // ---- symbol registry (core first, then units in load order) ----------- std::optional resolve_func(const String& name) { if(auto func = core_func(name)) return(func); for(size_t i = 0; i < units.size(); i++) if(auto func = unit_func(i, name)) return(func); return(std::nullopt); } // data symbol: value of the exported i32 global + owning module's base // (core is non-PIC → base 0; PIC units export __memory_base-relative) bool resolve_data(const String& name, u32& address_out) { auto from_core = core->get(ctx(), std::string_view(name)); if(from_core) if(auto* global = std::get_if(&*from_core)) { address_out = (u32)global->get(ctx()).i32(); return(true); } for(size_t i = 0; i < units.size(); i++) { auto exported = units[i].instance->get(ctx(), std::string_view(name)); if(exported) if(auto* global = std::get_if(&*exported)) { address_out = units[i].memory_base + (u32)global->get(ctx()).i32(); return(true); } } return(false); } String place_funcref(const wasmtime::Func& func, u32& slot_out) { auto cx = ctx(); if(table_next_free >= table->size(cx)) return("funcref table headroom exhausted"); auto set = table->set(cx, table_next_free, wasmtime::Val(std::optional(func))); if(!set) return("table set failed: " + String(set.err().message())); slot_out = table_next_free++; return(""); } // ---- unit loading (the §6 sequence) ------------------------------------ String load_unit(const String& source_path, size_t& unit_index_out) { auto known = units_by_source.find(source_path); if(known != units_by_source.end()) { unit_index_out = known->second; return(""); } String error; auto mod = worker.unit_module(source_path, error); if(!mod) return(error); if(mod->abi.version != abi_version) return(mod->wasm_path + ": uce.abi version " + std::to_string(mod->abi.version) + " does not match core ABI " + std::to_string(abi_version)); auto cx = ctx(); auto& module = *mod->module; // base allocation u32 align = 1u << mod->dylink.mem_align; int32_t raw_base = 0; error = call_core("malloc", { (int32_t)(mod->dylink.mem_size + align) }, &raw_base); if(error != "") return(error); if(raw_base == 0) return("core malloc failed for unit data segment"); u32 memory_base = ((u32)raw_base + (align - 1)) & ~(align - 1); u32 table_base = table_next_free; if(mod->dylink.table_size > table->size(cx) - table_next_free) return("funcref table headroom exhausted by " + source_path); table_next_free += mod->dylink.table_size; // import resolution std::vector imports; std::vector> deferred_got; for(auto import_type : module.imports()) { String mod_name(import_type.module()); String name(import_type.name()); auto extern_type = wasmtime::ExternType::from_import(import_type); bool is_func_import = std::get_if(&extern_type) != 0; if(mod_name == "env" && name == "memory") { imports.push_back(*memory); continue; } if(mod_name == "env" && name == "__indirect_function_table") { imports.push_back(*table); continue; } if(mod_name == "env" && name == "__stack_pointer") { auto sp = core->get(cx, "__stack_pointer"); if(!sp || !std::get_if(&*sp)) return("core does not export __stack_pointer"); imports.push_back(std::get(*sp)); continue; } if(mod_name == "env" && (name == "__memory_base" || name == "__table_base")) { int32_t value = name == "__memory_base" ? (int32_t)memory_base : (int32_t)table_base; wasmtime::GlobalType global_type(wasmtime::ValType::i32(), false); auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val(value)); if(!global) return("global create failed: " + String(global.err().message())); imports.push_back(global.ok()); continue; } if(mod_name == "env" && is_func_import) { auto func = resolve_func(name); if(!func) return(source_path + ": unresolved import env." + wasm_trace_demangle(name)); imports.push_back(*func); continue; } if(mod_name == "GOT.mem") { wasmtime::GlobalType global_type(wasmtime::ValType::i32(), true); u32 address = 0; if(resolve_data(name, address)) { auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val((int32_t)address)); if(!global) return("global create failed: " + String(global.err().message())); imports.push_back(global.ok()); } else { // provisional; self-resolved from the unit's own export // (plus its memory base) after instantiation auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val((int32_t)0)); if(!global) return("global create failed: " + String(global.err().message())); deferred_got.push_back({ name, global.ok() }); imports.push_back(deferred_got.back().second); } continue; } if(mod_name == "GOT.func") { auto func = resolve_func(name); if(!func) return(source_path + ": unresolved GOT.func." + wasm_trace_demangle(name)); u32 slot = 0; error = place_funcref(*func, slot); if(error != "") return(error); wasmtime::GlobalType global_type(wasmtime::ValType::i32(), true); auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val((int32_t)slot)); if(!global) return("global create failed: " + String(global.err().message())); imports.push_back(global.ok()); continue; } return(source_path + ": import policy violation: " + mod_name + "." + name); } auto created = wasmtime::Instance::create(cx, module, imports); if(!created) return(source_path + ": instantiation failed: " + trap_text(created.err())); LoadedUnit unit; unit.mod = mod; unit.instance.emplace(created.ok()); unit.memory_base = memory_base; units.push_back(std::move(unit)); size_t unit_index = units.size() - 1; units_by_source[source_path] = unit_index; unit_index_out = unit_index; // deferred GOT: the unit's own data exports are module-relative — // add this unit's memory base (Phase 0 FINDINGS erratum) for(auto& [name, got] : deferred_got) { auto own = units[unit_index].instance->get(cx, std::string_view(name)); if(!own || !std::get_if(&*own)) return(source_path + ": GOT.mem." + name + " defined neither by core nor by any unit"); u32 offset = (u32)std::get(*own).get(cx).i32(); auto set = got.set(cx, wasmtime::Val((int32_t)(memory_base + offset))); if(!set) return("GOT patch failed: " + String(set.err().message())); } // init sequence, then bind this unit's context to the request if(auto relocs = unit_func(unit_index, "__wasm_apply_data_relocs")) { auto result = relocs->call(ctx(), {}); if(!result) return(trap_text(result.err())); } if(auto ctors = unit_func(unit_index, "__wasm_call_ctors")) { auto result = ctors->call(ctx(), {}); if(!result) return(trap_text(result.err())); } if(auto set_request = unit_func(unit_index, "__uce_set_current_request")) { auto result = set_request->call(ctx(), { wasmtime::Val(request_ptr) }); if(!result) return(trap_text(result.err())); } if(worker.cfg.verbose) fprintf(stderr, "[wasm] loaded %s (mem_base=%u table_base=%u)\n", source_path.c_str(), memory_base, table_base); // The epoch budget is a guest-CPU watchdog, but the ticker is wall-clock // and host-side module compilation here (lazy, mid-render, possibly many // units) burns it without the guest running. Reset the deadline after a // load so the budget measures guest execution between membrane crossings, // not our compile time. A genuine runaway loop makes no loads, so it // still trips the deadline. ctx().set_epoch_deadline(worker.cfg.epoch_deadline_ticks); return(""); } // ---- component target resolution (host side of the membrane) ---------- static String normalize_component_path(String name) { // mirrors component_normalize_path in compiler.cpp / core.cpp if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce") return(name); return(name + ".uce"); } static bool file_exists_host(const String& path) { struct stat st; return(stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)); } static bool dir_exists_host(const String& path) { struct stat st; return(stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); } // Keep cwd host behavior local to this process but guard it with the same // write-root policy we use for file writes (plus a single parity fallback). String resolve_guest_cwd_set(const String& raw) { if(raw == "" || raw.find('\0') != String::npos) return(""); String raw_target = raw; if(raw.rfind("/", 0) != 0) { String cwd = ::cwd_get(); if(cwd == "") return(""); raw_target = cwd + "/" + raw; } char resolved[PATH_MAX]; if(!realpath(raw_target.c_str(), resolved)) return(""); String resolved_target(resolved); if(!dir_exists_host(resolved_target)) return(""); // Policy: allow only roots we already expose for writable filesystem access. std::vector roots; roots.push_back(worker.cfg.site_root); for(auto& root : worker.cfg.write_roots) roots.push_back(root); for(auto& root : roots) { if(root == "") continue; char root_real[PATH_MAX]; if(!realpath(root.c_str(), root_real)) continue; String canonical_root(root_real); if(resolved_target == canonical_root) return(resolved_target); if(canonical_root != "/" && resolved_target.rfind(canonical_root + "/", 0) == 0) return(resolved_target); } // Parity/fallback: allow returning to the process start directory so // legacy behavior is not silently broken for existing native/cached flows. String start_directory = ::process_start_directory(); if(start_directory != "" && resolved_target == start_directory) return(resolved_target); return(""); } String resolve_source_path(const String& file_name, const String& current_unit) { std::vector bases; if(file_name.rfind("/", 0) == 0) bases.push_back(""); // absolute target if(entry_dir != "") bases.push_back(entry_dir + "/"); if(current_unit != "") { String current_dir = dir_of(current_unit); if(current_dir != "" && current_dir != entry_dir) bases.push_back(current_dir + "/"); } bases.push_back(worker.cfg.site_root + "/"); for(auto& base : bases) { std::vector candidates; candidates.push_back(base + file_name); candidates.push_back(base + normalize_component_path(file_name)); if(file_name.rfind("components/", 0) != 0 && base != "") { candidates.push_back(base + "components/" + file_name); candidates.push_back(base + "components/" + normalize_component_path(file_name)); } for(auto& candidate : candidates) if(file_exists_host(candidate)) return(candidate); } return(""); } // guest file access policy: only inside the site tree, resolved against // the entry unit's directory first (the native cwd convention), then the // site root; containment checked on the canonicalized path String resolve_guest_file(const String& raw, const String& current_unit = "", bool allow_dir = false) { if(raw == "" || raw.find('\0') != String::npos) return(""); std::vector candidates; if(raw.rfind("/", 0) == 0) candidates.push_back(raw); else { String current_dir = current_unit != "" ? dir_of(current_unit) : String(""); if(current_dir != "") candidates.push_back(current_dir + "/" + raw); if(entry_dir != "" && entry_dir != current_dir) candidates.push_back(entry_dir + "/" + raw); candidates.push_back(worker.cfg.site_root + "/" + raw); } // readable roots = the site tree plus the writable scratch dirs (a page // can read back what it is allowed to write, e.g. /tmp), canonicalized. std::vector read_roots; read_roots.push_back(worker.cfg.site_root); for(auto& root : worker.cfg.write_roots) read_roots.push_back(root); std::vector root_prefixes; for(auto& root : read_roots) { char root_real[4096]; if(root != "" && realpath(root.c_str(), root_real)) root_prefixes.push_back(String(root_real)); } for(auto& candidate : candidates) { char resolved[4096]; if(!realpath(candidate.c_str(), resolved)) continue; String path(resolved); bool allowed = false; for(auto& prefix : root_prefixes) if(path == prefix || path.rfind(prefix + "/", 0) == 0) { allowed = true; break; } if(!allowed) continue; if(file_exists_host(path) || (allow_dir && dir_exists_host(path))) return(path); } return(""); } // write membrane policy: resolve the target (absolute, or relative to the // current unit / site root) and allow it only if its parent directory // canonicalizes under one of the configured write roots (site tree + the // runtime scratch dirs). The file itself need not exist yet. String resolve_guest_write(const String& raw, const String& current_unit) { if(raw == "" || raw.find('\0') != String::npos) return(""); String target; if(raw.rfind("/", 0) == 0) target = raw; else { String current_dir = current_unit != "" ? dir_of(current_unit) : String(""); target = (current_dir != "" ? current_dir : worker.cfg.site_root) + "/" + raw; } String parent = dir_of(target); String base = parent.size() < target.size() ? target.substr(parent.size() + 1) : String(""); if(base == "" || base == "." || base == "..") return(""); char parent_real[4096]; if(!realpath(parent.c_str(), parent_real)) return(""); String resolved_parent(parent_real); for(auto& root : worker.cfg.write_roots) { char root_real[4096]; if(root == "" || !realpath(root.c_str(), root_real)) continue; String root_prefix(root_real); if(resolved_parent == root_prefix || resolved_parent.rfind(root_prefix + "/", 0) == 0) return(resolved_parent + "/" + base); } return(""); } static String sanitize_symbol_suffix(const String& raw) { // mirrors ascii_safe_name in functionlib.cpp String result; for(auto c : raw) if(isalnum((unsigned char)c) || c == '_') result.push_back(c); return(result); } // __uce_[_] for a handler spec like "component:CARD" / "render". static String handler_export_symbol(const String& handler) { String export_prefix = "export:"; if(handler.rfind(export_prefix, 0) == 0) return(handler.substr(export_prefix.size())); auto colon = handler.find(":"); String symbol = "__uce_" + (colon == String::npos ? handler : handler.substr(0, colon)); if(colon != String::npos) symbol += "_" + sanitize_symbol_suffix(handler.substr(colon + 1)); return(symbol); } // hostcall body: uce_host_component_resolve(unit, handler, current) → slot. // `handler` names the export ("render", "component:CARD", "cli", // "serve_http:named", "once") or is "exists" (probe only, loads nothing). int32_t component_resolve(const String& target, const String& handler, const String& current_unit, String& resolved_out) { auto probe_start = std::chrono::steady_clock::now(); auto record_probe = [&]() { component_resolve_count += 1; component_resolve_total_us += (u64)std::chrono::duration_cast( std::chrono::steady_clock::now() - probe_start).count(); }; String file_name = target; if(file_name == "" && current_unit != "") file_name = current_unit; if(file_name == "") { record_probe(); return(0); } String resolved = resolve_source_path(file_name, current_unit); if(resolved == "") { record_probe(); return(0); } resolved_out = resolved; if(handler == "exists") { record_probe(); return(1); } if(!file_exists_host(worker.unit_wasm_path(resolved)) || compiler_unit_needs_recompile(context, resolved, 0)) get_shared_unit(context, resolved); size_t unit_index = 0; String error = load_unit(resolved, unit_index); if(error != "") { fprintf(stderr, "[wasm] component load failed: %s\n", error.c_str()); record_probe(); return(0); } String symbol = handler_export_symbol(handler); String slot_key = resolved + ":" + symbol; auto cached = handler_slots.find(slot_key); if(cached != handler_slots.end()) { record_probe(); return((int32_t)cached->second); } auto handler_fn = unit_func(unit_index, symbol); if(!handler_fn) { // ONCE is optional per unit; a missing __uce_once is not an error if(handler != "once") fprintf(stderr, "[wasm] %s does not export %s\n", resolved.c_str(), symbol.c_str()); record_probe(); return(0); } u32 slot = 0; error = place_funcref(*handler_fn, slot); if(error != "") { fprintf(stderr, "[wasm] %s\n", error.c_str()); record_probe(); return(0); } handler_slots[slot_key] = slot; record_probe(); return((int32_t)slot); } String run_task_callback(u64 callback_id) { auto runner = core_func("uce_wasm_task_run"); if(!runner) return("core does not export uce_wasm_task_run"); auto result = runner->call(ctx(), { wasmtime::Val((int64_t)callback_id) }); if(!result) return(trap_text(result.err())); return(""); } // ---- host imports for the core ----------------------------------------- wasmtime::Extern make_host_import(wasmtime::Store::Context cx, const String& mod, const String& name, const wasmtime::FuncType& func_type) { using namespace wasmtime; WasmWorkspace* self = this; auto add = [&](auto&& callback) -> Extern { Func func(cx, func_type, callback); host_funcs.push_back(func); return(host_funcs.back()); }; // Hostcall blocklist (UCE_HOSTCALL_BLOCKLIST): a sysadmin-disabled hostcall // resolves to a trap stub instead of its real implementation, so a unit // invoking it fails at runtime into the configurable error page. The // decision is made once per import at workspace birth — no per-call cost, // and zero cost when nothing is blocked. A small core set stays exempt so // the runtime itself cannot be bricked. if(mod == "env" && !worker.cfg.hostcall_blocklist.empty() && name.rfind("uce_host_", 0) == 0) { static const std::set non_blockable = { "component_resolve" }; String bare = name.substr(9); if(worker.cfg.hostcall_blocklist.count(bare) && !non_blockable.count(bare)) { std::string blocked(name); return(add([blocked](Caller, Span, Span) -> Result { return(Trap("UCE_POLICY_BLOCKED:" + blocked)); })); } } if(mod == "env" && name == "uce_host_time") return(add([](Caller, Span, Span results) -> Result { results[0] = Val((int64_t)::time(0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_time_precise") return(add([](Caller, Span, Span results) -> Result { results[0] = Val(time_precise()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_request_perf") return(add([self](Caller, Span args, Span results) -> Result { DValue response; if(self->request_perf.active) { f64 now = time_precise(); response["worker_pid"] = (f64)self->request_perf.worker_pid; response["parent_pid"] = (f64)self->request_perf.parent_pid; response["request_count"] = (f64)self->request_perf.request_count; if(self->request_perf.time_start > 0 && self->request_perf.time_init > 0) response["accept_us"] = (f64)((self->request_perf.time_start - self->request_perf.time_init) * 1000000.0); if(self->request_perf.time_start > 0) response["running_us"] = (f64)((now - self->request_perf.time_start) * 1000000.0); if(self->request_perf.time_init > 0) response["total_us"] = (f64)((now - self->request_perf.time_init) * 1000000.0); if(self->workspace_birth_us > 0) response["workspace_birth_us"] = (f64)self->workspace_birth_us; } String encoded = ucb_encode(response); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); if(buf != 0 && cap >= encoded.size()) self->hostcall_write(buf, encoded); results[0] = Val((int32_t)encoded.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_env") return(add([self](Caller caller, Span args, Span results) -> Result { String key, value; if(self->hostcall_read(args[0].i32(), args[1].i32(), key) == "") if(const char* raw = getenv(key.c_str())) value = raw; u32 cap = (u32)args[3].i32(); if(value.size() && cap >= value.size()) self->hostcall_write(args[2].i32(), value); results[0] = Val((int32_t)value.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_random") return(add([self](Caller, Span args, Span results) -> Result { u32 len = (u32)args[1].i32(); String bytes(len, 0); FILE* urandom = fopen("/dev/urandom", "rb"); if(urandom) { size_t got = fread(&bytes[0], 1, len, urandom); fclose(urandom); bytes.resize(got); } self->hostcall_write(args[0].i32(), bytes); results[0] = Val((int32_t)bytes.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_sha256") return(add([self](Caller, Span args, Span results) -> Result { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); String out=sha256_native(in); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_sha256_hex") return(add([self](Caller, Span args, Span results) -> Result { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); String out=sha256_hex_native(in); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_hmac_sha256") return(add([self](Caller, Span args, Span results) -> Result { String key,in; self->hostcall_read(args[0].i32(), args[1].i32(), key); self->hostcall_read(args[2].i32(), args[3].i32(), in); String out=hmac_sha256_native(key,in); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_hmac_sha256_hex") return(add([self](Caller, Span args, Span results) -> Result { String key,in; self->hostcall_read(args[0].i32(), args[1].i32(), key); self->hostcall_read(args[2].i32(), args[3].i32(), in); String out=hmac_sha256_hex_native(key,in); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_base64_encode") return(add([self](Caller, Span args, Span results) -> Result { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); String out=base64_encode(in); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_base64_decode") return(add([self](Caller, Span args, Span results) -> Result { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); bool ok=false; String out=base64_decode(in, ok); if(!ok) out=""; u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_crypto_equal") return(add([self](Caller, Span args, Span results) -> Result { String a,b; self->hostcall_read(args[0].i32(), args[1].i32(), a); self->hostcall_read(args[2].i32(), args[3].i32(), b); results[0]=Val((int32_t)(crypto_equal_native(a,b)?1:0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_log") return(add([self](Caller, Span args, Span) -> Result { String text; self->hostcall_read(args[1].i32(), args[2].i32(), text); fprintf(stderr, "[guest log %d] %.*s\n", args[0].i32(), (int)text.size(), text.data()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_shell_exec") return(add([self](Caller caller, Span args, Span results) -> Result { String cmd; self->hostcall_read(args[0].i32(), args[1].i32(), cmd); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); String out; String stage_key = "shell:" + cmd; if(!self->hostcall_staged(stage_key, out)) { out = ::shell_exec(cmd); if(buf == 0) self->hostcall_stage(stage_key, 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_http_request") return(add([self](Caller caller, Span args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); String out; String stage_key="http:"+encoded; if(!self->hostcall_staged(stage_key,out)) { DValue req,response; String err; if(ucb_decode(encoded,req,&err)) response=uce_http_request_value(req); else response["error"]="http_request decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); } if(buf&&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_http_request_async") return(add([self](Caller, Span args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue req; String err; u64 id=0; if(ucb_decode(encoded,req,&err)) id=uce_http_spawn_spec(req); results[0]=Val((int64_t)id); return(std::monostate()); })); if(mod == "env" && name == "uce_host_shell_exec_dv") return(add([self](Caller caller, Span args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); String out; String stage_key="shell_dv:"+encoded; if(!self->hostcall_staged(stage_key,out)) { DValue spec, response; String err; if(ucb_decode(encoded,spec,&err)) response=uce_shell_exec_spec(spec); else response["error"]="shell_exec spec decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); } if(buf&&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_shell_spawn") return(add([self](Caller, Span args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue spec; String err; u64 id=0; if(ucb_decode(encoded,spec,&err)) id=uce_shell_spawn_spec(spec); results[0]=Val((int64_t)id); return(std::monostate()); })); if(mod == "env" && name == "uce_host_job_status") return(add([self](Caller, Span args, Span results) -> Result { String out=ucb_encode(uce_job_status_value((u64)args[0].i64())); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_job_result") return(add([self](Caller, Span args, Span results) -> Result { String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), 100)); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_job_await") return(add([self](Caller caller, Span args, Span results) -> Result { u64 timeout=std::min((u64)args[1].i64(), 30000); String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), timeout)); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&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_job_cancel") return(add([](Caller, Span args, Span results) -> Result { results[0]=Val((int32_t)(uce_job_cancel_value((u64)args[0].i64())?1:0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_path_real") return(add([self](Caller, Span args, Span results) -> Result { String path; self->hostcall_read(args[0].i32(), args[1].i32(), path); String resolved = ::path_real(path); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); if(buf != 0 && cap >= resolved.size()) self->hostcall_write(buf, resolved); results[0] = Val((int32_t)resolved.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_path_is_within") return(add([self](Caller, Span args, Span results) -> Result { String path, root; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), root); results[0] = Val(::path_is_within(path, root) ? (int32_t)1 : (int32_t)0); return(std::monostate()); })); if(mod == "env" && name == "uce_host_cwd_get") return(add([self](Caller, Span args, Span results) -> Result { String cwd = ::cwd_get(); u32 cap = (u32)args[1].i32(); int32_t buf = args[0].i32(); if(buf != 0 && cap >= cwd.size()) self->hostcall_write(buf, cwd); results[0] = Val((int32_t)cwd.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_cwd_set") return(add([self](Caller, Span args, Span results) -> Result { String path; self->hostcall_read(args[0].i32(), args[1].i32(), path); String resolved = self->resolve_guest_cwd_set(path); results[0] = Val(::chdir(resolved.c_str()) == 0 ? (int32_t)1 : (int32_t)0); return(std::monostate()); })); if(mod == "env" && name == "uce_host_process_start_directory") return(add([self](Caller, Span args, Span results) -> Result { String cwd = ::process_start_directory(); u32 cap = (u32)args[1].i32(); int32_t buf = args[0].i32(); if(buf != 0 && cap >= cwd.size()) self->hostcall_write(buf, cwd); results[0] = Val((int32_t)cwd.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_last_trap_trace") return(add([](Caller, Span args, Span results) -> Result { (void)args; results[0] = Val((int32_t)0); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_exists") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_file(path, current); if(self->worker.cfg.verbose) fprintf(stderr, "[wasm] file_exists(%s, current=%s) -> %s\n", path.c_str(), current.c_str(), resolved.c_str()); results[0] = Val(resolved != "" ? (int32_t)1 : (int32_t)0); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_mkdir") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_write(path, current); int ok = 0; if(resolved != "") ok = (::mkdir(resolved.c_str(), 0777) == 0 || errno == EEXIST) ? 1 : 0; results[0] = Val((int32_t)ok); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_mtime") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_file(path, current); int64_t mtime = 0; struct stat st; if(resolved != "" && stat(resolved.c_str(), &st) == 0) mtime = (int64_t)st.st_mtime; results[0] = Val((int64_t)mtime); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_read") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String stage_key = "file_read:" + path + "\0" + current; String content; if(!self->hostcall_staged(stage_key, content)) { String resolved = self->resolve_guest_file(path, current); content = resolved == "" ? String("") : ::file_get_contents(resolved); self->hostcall_stage(stage_key, content); } u32 cap = (u32)args[5].i32(); int32_t buf = args[4].i32(); // length-query convention: no copy unless the buffer fits if(buf != 0 && cap >= content.size()) self->hostcall_write(buf, content); results[0] = Val((int32_t)content.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_list") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_file(path, current, true /*allow_dir*/); String listing; if(resolved != "") { std::vector names; if(DIR* d = opendir(resolved.c_str())) { while(struct dirent* e = readdir(d)) { String n = e->d_name; if(n != "." && n != "..") names.push_back(n); } closedir(d); } // match the native ls -1 convention: bare names, sorted std::sort(names.begin(), names.end()); listing = join(names, "\n"); } u32 cap = (u32)args[5].i32(); int32_t buf = args[4].i32(); // length-query convention: no copy unless the buffer fits if(buf != 0 && cap >= listing.size()) self->hostcall_write(buf, listing); results[0] = Val((int32_t)listing.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_write") return(add([self](Caller, Span args, Span results) -> Result { String path, current, content; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); self->hostcall_read(args[4].i32(), args[5].i32(), content); bool append = args[6].i32() != 0; String resolved = self->resolve_guest_write(path, current); bool ok = false; if(resolved != "") ok = append ? file_append(resolved, content) : file_put_contents(resolved, content); else if(self->worker.cfg.verbose) fprintf(stderr, "[wasm] file_write denied: %s\n", path.c_str()); results[0] = Val(ok ? (int32_t)1 : (int32_t)0); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_open") return(add([self](Caller, Span args, Span results) -> Result { String path, current, mode; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); self->hostcall_read(args[4].i32(), args[5].i32(), mode); String resolved; int flags = O_RDONLY; int lock_type = LOCK_SH; bool writable = false; bool truncate_after_lock = false; if(mode == "r") resolved = self->resolve_guest_file(path, current); else if(mode == "w") { resolved = self->resolve_guest_write(path, current); flags = O_RDWR | O_CREAT; lock_type = LOCK_EX; writable = true; truncate_after_lock = true; } else if(mode == "a") { resolved = self->resolve_guest_write(path, current); flags = O_RDWR | O_CREAT | O_APPEND; lock_type = LOCK_EX; writable = true; } else if(mode == "r+") { resolved = self->resolve_guest_write(path, current); flags = O_RDWR; lock_type = LOCK_EX; writable = true; } uint64_t handle = 0; if(resolved != "") { int fd = wasm_open_locked_file(resolved, flags, lock_type, truncate_after_lock); if(fd >= 0) { if(mode == "a") lseek(fd, 0, SEEK_END); self->file_handles.push_back({fd, writable}); handle = self->file_handles.size(); } } results[0] = Val((int64_t)handle); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_handle_read") return(add([self](Caller, Span args, Span results) -> Result { u64 handle = (u64)args[0].i64(); u64 len = (u64)args[1].i64(); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); String out; String stage_key = "file_handle_read:" + std::to_string(handle) + ":" + std::to_string(len); if(!self->hostcall_staged(stage_key, out)) { if(handle >= 1 && handle <= self->file_handles.size()) { int fd = self->file_handles[(size_t)handle - 1].fd; if(fd >= 0 && len > 0) { out.resize((size_t)std::min(len, 16ull * 1024ull * 1024ull)); ssize_t n = read(fd, &out[0], out.size()); out.resize(n > 0 ? (size_t)n : 0); } } if(buf == 0) self->hostcall_stage(stage_key, out); } 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_file_handle_pread") return(add([self](Caller, Span args, Span results) -> Result { u64 handle = (u64)args[0].i64(); u64 offset = (u64)args[1].i64(); u64 len = (u64)args[2].i64(); u32 cap = (u32)args[4].i32(); int32_t buf = args[3].i32(); String out; String stage_key = "file_handle_pread:" + std::to_string(handle) + ":" + std::to_string(offset) + ":" + std::to_string(len); if(!self->hostcall_staged(stage_key, out)) { if(handle >= 1 && handle <= self->file_handles.size()) { int fd = self->file_handles[(size_t)handle - 1].fd; if(fd >= 0 && len > 0) { out.resize((size_t)std::min(len, 16ull * 1024ull * 1024ull)); ssize_t n = pread(fd, &out[0], out.size(), (off_t)offset); out.resize(n > 0 ? (size_t)n : 0); } } if(buf == 0) self->hostcall_stage(stage_key, out); } 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_file_handle_write") return(add([self](Caller, Span args, Span results) -> Result { String data; self->hostcall_read(args[1].i32(), args[2].i32(), data); u64 handle = (u64)args[0].i64(); u64 written = 0; if(handle >= 1 && handle <= self->file_handles.size()) { auto& h = self->file_handles[(size_t)handle - 1]; if(h.fd >= 0 && h.writable) wasm_fd_write_all(h.fd, data.data(), data.size(), &written); } results[0] = Val((int64_t)written); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_handle_pwrite") return(add([self](Caller, Span args, Span results) -> Result { String data; self->hostcall_read(args[2].i32(), args[3].i32(), data); u64 handle = (u64)args[0].i64(); u64 offset = (u64)args[1].i64(); u64 written = 0; if(handle >= 1 && handle <= self->file_handles.size()) { auto& h = self->file_handles[(size_t)handle - 1]; if(h.fd >= 0 && h.writable) { while(written < data.size()) { ssize_t n = pwrite(h.fd, data.data() + written, data.size() - written, (off_t)(offset + written)); if(n < 0 && errno == EINTR) continue; if(n <= 0) break; written += (u64)n; } } } results[0] = Val((int64_t)written); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_handle_seek") return(add([self](Caller, Span args, Span results) -> Result { u64 handle = (u64)args[0].i64(); s64 pos = -1; if(handle >= 1 && handle <= self->file_handles.size()) { int fd = self->file_handles[(size_t)handle - 1].fd; if(fd >= 0) pos = (s64)lseek(fd, (off_t)args[1].i64(), args[2].i32()); } results[0] = Val((int64_t)pos); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_handle_tell") return(add([self](Caller, Span args, Span results) -> Result { u64 handle = (u64)args[0].i64(); s64 pos = -1; if(handle >= 1 && handle <= self->file_handles.size()) { int fd = self->file_handles[(size_t)handle - 1].fd; if(fd >= 0) pos = (s64)lseek(fd, 0, SEEK_CUR); } results[0] = Val((int64_t)pos); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_handle_close") return(add([self](Caller, Span args, Span) -> Result { u64 handle = (u64)args[0].i64(); if(handle >= 1 && handle <= self->file_handles.size()) { auto& h = self->file_handles[(size_t)handle - 1]; if(h.fd >= 0) { flock(h.fd, LOCK_UN); close(h.fd); h.fd = -1; } } return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_stat") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_file(path, current, true); DValue r; struct stat st; r["exists"].set_bool(resolved != "" && lstat(resolved.c_str(), &st) == 0); if(r["exists"].to_bool()) { r["size"]=(f64)st.st_size; r["mtime"]=(f64)st.st_mtime; r["ctime"]=(f64)st.st_ctime; r["mode"]=(f64)(st.st_mode & 07777); r["is_dir"].set_bool(S_ISDIR(st.st_mode)); r["is_file"].set_bool(S_ISREG(st.st_mode)); r["is_symlink"].set_bool(S_ISLNK(st.st_mode)); } else { r["size"]=(f64)0; r["mtime"]=(f64)0; r["ctime"]=(f64)0; r["mode"]=(f64)0; r["is_dir"].set_bool(false); r["is_file"].set_bool(false); r["is_symlink"].set_bool(false); } String out = ucb_encode(r); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf && cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_dir_list") return(add([self](Caller, Span args, Span results) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_file(path, current, true); DValue list; list.set_array(); if(resolved != "") { std::vector names; if(DIR* d=opendir(resolved.c_str())) { while(struct dirent* e=readdir(d)) { String n=e->d_name; if(n!="."&&n!="..") names.push_back(n); } closedir(d); } std::sort(names.begin(), names.end()); for(auto& n:names) { String p=resolved+"/"+n; struct stat st; DValue item; item["name"]=n; if(lstat(p.c_str(), &st)==0) { item["size"]=(f64)st.st_size; item["mtime"]=(f64)st.st_mtime; item["type"]=S_ISDIR(st.st_mode)?"dir":S_ISLNK(st.st_mode)?"symlink":S_ISREG(st.st_mode)?"file":"other"; } list.push(item); } } String out=ucb_encode(list); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_rename") return(add([self](Caller, Span args, Span results) -> Result { String from,to,current; self->hostcall_read(args[0].i32(),args[1].i32(),from); self->hostcall_read(args[2].i32(),args[3].i32(),to); self->hostcall_read(args[4].i32(),args[5].i32(),current); String rf=self->resolve_guest_write(from,current), rt=self->resolve_guest_write(to,current); results[0]=Val((int32_t)(rf!=""&&rt!=""&&rename(rf.c_str(),rt.c_str())==0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_copy") return(add([self](Caller, Span args, Span results) -> Result { String from,to,current; self->hostcall_read(args[0].i32(),args[1].i32(),from); self->hostcall_read(args[2].i32(),args[3].i32(),to); self->hostcall_read(args[4].i32(),args[5].i32(),current); String rf=self->resolve_guest_file(from,current), rt=self->resolve_guest_write(to,current); bool ok=false; if(rf!=""&&rt!="") { std::ifstream in(rf, std::ios::binary); std::ofstream out(rt, std::ios::binary|std::ios::trunc); out< args, Span results) -> Result { String path,current; self->hostcall_read(args[0].i32(),args[1].i32(),path); self->hostcall_read(args[2].i32(),args[3].i32(),current); String r=self->resolve_guest_write(path,current); results[0]=Val((int32_t)(r!=""&&truncate(r.c_str(),(off_t)args[4].i64())==0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_dir_remove") return(add([self](Caller, Span args, Span results) -> Result { String path,current; self->hostcall_read(args[0].i32(),args[1].i32(),path); self->hostcall_read(args[2].i32(),args[3].i32(),current); String r=self->resolve_guest_write(path,current); bool rec=args[4].i32()!=0; bool ok=false; if(r!="") { if(rec) ok=std::filesystem::remove_all(r)>0; else ok=::rmdir(r.c_str())==0; } results[0]=Val((int32_t)ok); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_temp") return(add([self](Caller, Span args, Span results) -> Result { String prefix,current; self->hostcall_read(args[0].i32(),args[1].i32(),prefix); self->hostcall_read(args[2].i32(),args[3].i32(),current); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); String out; String stage_key="file_temp:"+prefix+"\0"+current; if(!self->hostcall_staged(stage_key,out)) { if(prefix=="") prefix="/tmp/uce-temp"; String templ=self->resolve_guest_write(prefix+"XXXXXX",current); if(templ!="") { std::vector t(templ.begin(), templ.end()); t.push_back(0); int fd=mkstemp(t.data()); if(fd>=0) { close(fd); out=t.data(); } } if(buf==0) self->hostcall_stage(stage_key,out); } if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_chmod") return(add([self](Caller, Span args, Span results) -> Result { String path,current; self->hostcall_read(args[0].i32(),args[1].i32(),path); self->hostcall_read(args[2].i32(),args[3].i32(),current); String r=self->resolve_guest_write(path,current); results[0]=Val((int32_t)(r!=""&&chmod(r.c_str(),(mode_t)args[4].i32())==0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_symlink") return(add([self](Caller, Span args, Span results) -> Result { String target,linkpath,current; self->hostcall_read(args[0].i32(),args[1].i32(),target); self->hostcall_read(args[2].i32(),args[3].i32(),linkpath); self->hostcall_read(args[4].i32(),args[5].i32(),current); String rt=self->resolve_guest_file(target,current), rl=self->resolve_guest_write(linkpath,current); results[0]=Val((int32_t)(rt!=""&&rl!=""&&symlink(rt.c_str(),rl.c_str())==0)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_file_fsync") return(add([self](Caller, Span args, Span results) -> Result { u64 handle=(u64)args[0].i64(); bool ok=false; if(handle>=1&&handle<=self->file_handles.size()) { int fd=self->file_handles[(size_t)handle-1].fd; ok=fd>=0&&fsync(fd)==0; } results[0]=Val((int32_t)ok); return(std::monostate()); })); if(mod == "env" && name == "uce_host_zip") return(add([self](Caller, Span args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); String out; String stage_key = "zip:" + encoded; if(!self->hostcall_staged(stage_key, 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); if(buf == 0) self->hostcall_stage(stage_key, out); } 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 args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); String out; String stage_key = "units:" + encoded; if(!self->hostcall_staged(stage_key, 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); if(buf == 0) self->hostcall_stage(stage_key, 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()); })); #ifdef UCE_WASM_HOST_CONNECTORS if(mod == "env" && name == "uce_host_sqlite") return(add([self](Caller, Span args, Span results) -> Result { // {op,handle,path,query,params} in → result out. The native // connector runs host-side; connections live in the workspace // handle table (handle = 1-based index). String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); String out; String stage_key = "sqlite:" + encoded; // run the op once across the length-query + fetch pair if(!self->hostcall_staged(stage_key, out)) { DValue request, response; String decode_error; if(ucb_decode(encoded, request, &decode_error)) { String op = request["op"].to_string(); if(op == "connect") { SQLite* db = new SQLite(); db->connect(request["path"].to_string()); u64 handle = 0; if(db->connection) { self->sqlite_handles.push_back(db); handle = self->sqlite_handles.size(); } response["handle"] = (f64)handle; response["error_code"] = (f64)db->error_code; response["statement_info"] = db->error(); if(handle == 0) delete db; } else { u64 handle = request["handle"].to_u64(); SQLite* db = (handle >= 1 && handle <= self->sqlite_handles.size()) ? self->sqlite_handles[(size_t)handle - 1] : 0; if(op == "query" && db) { StringMap params; DValue* p = request.key("params"); if(p) p->each([&](const DValue& value, String key) { params[key] = value.to_string(); }); response["result"] = db->query(request["query"].to_string(), params); response["insert_id"] = (f64)db->insert_id; response["affected"] = (f64)db->affected_rows; response["error_code"] = (f64)db->error_code; response["statement_info"] = db->error(); } else if(op == "disconnect" && db) { delete db; self->sqlite_handles[(size_t)handle - 1] = 0; } } } out = ucb_encode(response); if(buf == 0) self->hostcall_stage(stage_key, out); } 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_memcache_command") return(add([self](Caller caller, Span args, Span results) -> Result { 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 args, Span results) -> Result { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap = (u32)args[3].i32(); int32_t buf = args[2].i32(); String out; String stage_key = "mysql:" + encoded; if(!self->hostcall_staged(stage_key, 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); if(buf == 0) self->hostcall_stage(stage_key, out); } 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 args, Span) -> Result { String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current); String resolved = self->resolve_guest_write(path, current); if(resolved != "") ::unlink(resolved.c_str()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_socket_connect") return(add([self](Caller caller, Span args, Span results) -> Result { 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 args, Span) -> Result { ::socket_close((u64)args[0].i64()); return(std::monostate()); })); if(mod == "env" && name == "uce_host_socket_write") return(add([self](Caller, Span args, Span results) -> Result { 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 args, Span results) -> Result { 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 args, Span results) -> Result { 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 args, Span results) -> Result { 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 args, Span results) -> Result { String key; self->hostcall_read(args[0].i32(), args[1].i32(), key); u64 callback_id = (u64)args[2].i64(); f64 interval = args[3].f64(); u64 timeout = (u64)args[4].i64(); bool repeat = args[5].i32() != 0; // task()/task_repeat() fork and invoke this lambda only in the child // before the hostcall stack unwinds, so `self` points to the child's // copy of this per-request workspace. The parent request can return and // destroy its workspace without invalidating the child copy. auto run_callback = [self, callback_id]() { String error = self->run_task_callback(callback_id); if(error != "") fprintf(stderr, "[wasm task] callback failed: %s\n", error.c_str()); }; pid_t pid = 0; try { if(!repeat || (interval > 0 && std::isfinite(interval))) pid = repeat ? ::task_repeat(key, interval, run_callback, timeout) : ::task(key, run_callback, timeout); } catch(const std::exception& e) { fprintf(stderr, "[wasm task] spawn failed for key '%s': %s\n", key.c_str(), e.what()); } catch(...) { fprintf(stderr, "[wasm task] spawn failed for key '%s'\n", key.c_str()); } results[0] = Val((int32_t)pid); return(std::monostate()); })); if(mod == "env" && name == "uce_host_task_pid") return(add([self](Caller, Span args, Span results) -> Result { String key; self->hostcall_read(args[0].i32(), args[1].i32(), key); results[0] = Val((int32_t)::task_pid(key)); return(std::monostate()); })); if(mod == "env" && name == "uce_host_task_kill") return(add([](Caller, Span args, Span results) -> Result { results[0] = Val((int32_t)::task_kill((pid_t)args[0].i32(), args[1].i32())); return(std::monostate()); })); if(mod == "env" && name == "uce_host_sleep_us") return(add([self](Caller caller, Span args, Span results) -> Result { u64 usec = (u64)args[0].i64(); while(usec >= 1000000ull) { unsigned int remaining = ::sleep((unsigned int)(usec / 1000000ull)); if(remaining != 0) { caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0] = Val((int32_t)remaining); return(std::monostate()); } usec %= 1000000ull; } if(usec > 0) ::usleep((useconds_t)usec); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0] = Val((int32_t)0); return(std::monostate()); })); if(mod == "env" && name == "uce_host_regex") return(add([self](Caller, Span args, Span results) -> Result { // {op,pattern,subject,flags,replacement} in (UCEB2) → result out. // PCRE2 lives host-side; this runs the native regex_*. String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue request; String decode_error; DValue response; if(ucb_decode(encoded, request, &decode_error)) { String op = request["op"].to_string(); String pattern = request["pattern"].to_string(); String subject = request["subject"].to_string(); String flags = request["flags"].to_string(); if(op == "match") response["bool"].set_bool(regex_match(pattern, subject, flags)); else if(op == "search") response["tree"] = regex_search(pattern, subject, flags); else if(op == "search_all") response["tree"] = regex_search_all(pattern, subject, flags); else if(op == "replace") response["text"] = regex_replace(pattern, request["replacement"].to_string(), subject, flags); else if(op == "split") for(auto& part : regex_split(pattern, subject, flags)) { DValue value; value = part; response["list"].push(value); } } String out = ucb_encode(response); 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_component_resolve") return(add([self](Caller caller, Span args, Span results) -> Result { String target, handler, current, resolved; self->hostcall_read(args[0].i32(), args[1].i32(), target); self->hostcall_read(args[2].i32(), args[3].i32(), handler); self->hostcall_read(args[4].i32(), args[5].i32(), current); int32_t slot = self->component_resolve(target, handler, current, resolved); u32 cap = (u32)args[7].i32(); if(cap > 0) { if(resolved.size() >= cap) resolved = resolved.substr(0, cap - 1); resolved.push_back('\0'); self->hostcall_write(args[6].i32(), resolved); } results[0] = Val(slot); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); return(std::monostate()); })); // anything else (wasi-libc residue): a named trap — tolerated as long // as it is never called String label = mod + "." + name; return(add([label](Caller, Span, Span) -> Result { return(wasmtime::Trap("unimplemented host import called: " + std::string(label))); })); } String hostcall_read(int32_t ptr, int32_t len, String& out) { return(guest_read((u32)ptr, (u32)len, out)); } void hostcall_write(int32_t ptr, const String& data) { guest_write((u32)ptr, data); } }; // ---- public entry: one request through one workspace ----------------------- inline WasmResponse wasm_worker_serve(WasmWorker& worker, const DValue& context_tree, const String& entry_source_path, const String& handler = "render", const Request* request = 0) { WasmResponse response; WasmWorkspace workspace(worker); if(request) workspace.set_perf_snapshot(my_pid, (u64)parent_pid, request->server ? request->server->request_count : 0, request->stats.time_init, request->stats.time_start); auto birth_start = std::chrono::steady_clock::now(); String error = workspace.birth(); workspace.workspace_birth_us = (u64)std::chrono::duration_cast( std::chrono::steady_clock::now() - birth_start).count(); if(error == "") error = workspace.apply_context(context_tree); if(error == "") error = workspace.invoke_entry(entry_source_path, handler, &response.handler_present); if(error == "") error = workspace.collect(response); response.workspace_birth_us = workspace.workspace_birth_us; response.component_resolve_count = workspace.component_resolve_count; response.component_resolve_total_us = workspace.component_resolve_total_us; if(error != "") { response.ok = false; response.error = error; return(response); } response.ok = true; return(response); }