diff --git a/lang/el-compiler/runtime/el_platform_win.h b/lang/el-compiler/runtime/el_platform_win.h new file mode 100644 index 0000000..88204a0 --- /dev/null +++ b/lang/el-compiler/runtime/el_platform_win.h @@ -0,0 +1,117 @@ +#ifndef EL_PLATFORM_WIN_H +#define EL_PLATFORM_WIN_H +/* + * el_platform_win.h — Windows OS-boundary shim for el_runtime.c. + * + * Branch: feat/windows-el-runtime. Included ONLY when _WIN32 is defined; the POSIX build is + * untouched. Goal: let el_runtime.c (a BSD-sockets / dlfcn / fork host) compile and link with + * mingw-w64 into a native neuron.exe, with no behavioural change to the Linux/macOS build. + * + * What it maps: + * - sockets : winsock2 (same call names: socket/bind/listen/accept/recv/send/setsockopt). + * Sockets close with closesocket() (see el_closesocket), and the stack must be + * started once with WSAStartup — done automatically via a load-time constructor. + * - dlsym : el_runtime.c uses dlsym(RTLD_DEFAULT, name) to resolve callback/tool symbols + * exported by the main module. Windows equivalent: GetProcAddress on the process + * module. Link the soul with -Wl,--export-all-symbols so the symbols are findable. + * - popen : mapped to _popen/_pclose. + * - threads : UNCHANGED. mingw-w64 ships winpthreads, so + -lpthread just work. + */ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include +#include + +/* Portable headers mingw-w64 provides (verified present). */ +#include +#include +#include +#include +#include +#include /* strcasecmp */ +#include +#include +#include +#include /* mingw-w64 provides gettimeofday here */ +#include +#include +#include +#include +#include +#include + +/* ── socket close ─────────────────────────────────────────────────────────── */ +/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX + build defines the same helper as close() so the call sites are identical across platforms. */ +static inline int el_closesocket(SOCKET s) { return closesocket(s); } + +/* ── winsock init (once, at load) ─────────────────────────────────────────── */ +static void el__win_net_init(void) { + static int inited = 0; + if (!inited) { WSADATA w; WSAStartup(MAKEWORD(2, 2), &w); inited = 1; } +} +__attribute__((constructor)) static void el__win_ctor(void) { el__win_net_init(); } + +/* ── dlsym → GetProcAddress ───────────────────────────────────────────────── */ +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT ((void*)0) +#endif +static inline void* el_win_dlsym(void* handle, const char* name) { + (void)handle; + return (void*)(uintptr_t)GetProcAddress(GetModuleHandleA(NULL), name); +} +#define dlsym(h, n) el_win_dlsym((h), (n)) + +/* ── popen / pclose ───────────────────────────────────────────────────────── */ +#define popen _popen +#define pclose _pclose + +/* ── misc POSIX → Win32 shims ─────────────────────────────────────────────── */ +#include /* _mkdir */ +#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */ +#define timegm _mkgmtime /* UTC tm → time_t */ + +/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */ +static inline int setenv(const char* name, const char* value, int overwrite) { + (void)overwrite; + return _putenv_s(name, value ? value : ""); +} +static inline int unsetenv(const char* name) { + /* _putenv_s(name, "") sets VAR="" rather than removing it. + * SetEnvironmentVariableA(name, NULL) truly deletes it from the Win32 + * env block; then we sync the CRT cache with _putenv("NAME="). */ + SetEnvironmentVariableA(name, NULL); + size_t len = strlen(name); + char *buf = (char*)malloc(len + 2); + if (!buf) return -1; + memcpy(buf, name, len); + buf[len] = '='; + buf[len + 1] = '\0'; + _putenv(buf); + free(buf); + return 0; +} + +/* nanosleep — not available in MSVC/UCRT; approximate with Sleep(). */ +static inline int el_nanosleep(const struct timespec *req, struct timespec *rem) { + (void)rem; + DWORD ms = (DWORD)((req->tv_sec * 1000ULL) + (req->tv_nsec / 1000000ULL)); + Sleep(ms ? ms : 1); + return 0; +} +#define nanosleep(req, rem) el_nanosleep((req), (rem)) + +/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */ +static inline struct tm* localtime_r(const time_t* t, struct tm* out) { + return localtime_s(out, t) == 0 ? out : (struct tm*)0; +} +static inline struct tm* gmtime_r(const time_t* t, struct tm* out) { + return gmtime_s(out, t) == 0 ? out : (struct tm*)0; +} + +#endif /* EL_PLATFORM_WIN_H */ diff --git a/lang/el-compiler/runtime/el_runtime.c b/lang/el-compiler/runtime/el_runtime.c index 2f4c839..6b8cbe7 100644 --- a/lang/el-compiler/runtime/el_runtime.c +++ b/lang/el-compiler/runtime/el_runtime.c @@ -21,6 +21,10 @@ #include "el_runtime.h" +#ifdef _WIN32 +/* Windows OS-boundary shim (winsock/dlsym/popen). Threading stays on (winpthreads). */ +#include "el_platform_win.h" +#else #include #include /* strcasecmp */ #include @@ -43,6 +47,10 @@ #include #include #include /* getrusage — memory guard */ +/* On POSIX, sockets close with the same close() as files; el_platform_win.h supplies the Windows + variant. Defined here so the socket call sites are identical across platforms. */ +static inline int el_closesocket(int s) { return close(s); } +#endif #ifdef HAVE_CURL #include #endif @@ -182,6 +190,7 @@ el_val_t println(el_val_t s) { const char* str = EL_CSTR(s); if (str) puts(str); else puts(""); + fflush(stdout); /* prevent startup logs from silently buffering when stdout→file */ return 0; } @@ -1054,7 +1063,6 @@ el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el #define HTTP_MAX_CONNS 64 -typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body); typedef struct { char* name; @@ -1529,12 +1537,20 @@ static void http_send_response(int fd, const char* body) { } typedef struct { +#ifdef _WIN32 + SOCKET fd; +#else int fd; +#endif } HttpWorkerArg; static void* http_worker(void* arg) { HttpWorkerArg* a = (HttpWorkerArg*)arg; +#ifdef _WIN32 + SOCKET fd = a->fd; +#else int fd = a->fd; +#endif free(a); char *method = NULL, *path = NULL, *body = NULL; if (http_read_request(fd, &method, &path, &body, NULL) == 0) { @@ -1566,7 +1582,7 @@ static void* http_worker(void* arg) { free(response); } free(method); free(path); free(body); - close(fd); + el_closesocket(fd); /* release a slot */ pthread_mutex_lock(&_http_conn_mu); _http_conn_active--; @@ -1588,22 +1604,26 @@ el_val_t http_serve(el_val_t port, el_val_t handler) { int sock = socket(AF_INET6, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return 0; } int yes = 1; int no = 0; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes)); + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&no, sizeof(no)); struct sockaddr_in6 addr; memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons((uint16_t)p); if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - perror("bind"); close(sock); return 0; + perror("bind"); el_closesocket(sock); return 0; } - if (listen(sock, 64) < 0) { perror("listen"); close(sock); return 0; } + if (listen(sock, 64) < 0) { perror("listen"); el_closesocket(sock); return 0; } fprintf(stderr, "[http] listening on [::]:%d (dual-stack)\n", p); while (1) { struct sockaddr_in6 cli; socklen_t clen = sizeof(cli); +#ifdef _WIN32 + SOCKET cfd = accept(sock, (struct sockaddr*)&cli, &clen); +#else int cfd = accept(sock, (struct sockaddr*)&cli, &clen); +#endif if (cfd < 0) { if (errno == EINTR) continue; perror("accept"); break; @@ -1615,11 +1635,11 @@ el_val_t http_serve(el_val_t port, el_val_t handler) { _http_conn_active++; pthread_mutex_unlock(&_http_conn_mu); HttpWorkerArg* arg = malloc(sizeof(HttpWorkerArg)); - if (!arg) { close(cfd); continue; } + if (!arg) { el_closesocket(cfd); continue; } arg->fd = cfd; pthread_t tid; if (pthread_create(&tid, NULL, http_worker, arg) != 0) { - close(cfd); free(arg); + el_closesocket(cfd); free(arg); pthread_mutex_lock(&_http_conn_mu); _http_conn_active--; pthread_cond_signal(&_http_conn_cv); @@ -1628,7 +1648,7 @@ el_val_t http_serve(el_val_t port, el_val_t handler) { } pthread_detach(tid); } - close(sock); + el_closesocket(sock); return 0; } @@ -1649,8 +1669,6 @@ el_val_t http_serve(el_val_t port, el_val_t handler) { * separate active-handler slot, separate dlsym fallback. Mixing v1 and v2 * handlers in the same process is fine — they don't share the active slot. */ -typedef el_val_t (*http_handler4_fn)(el_val_t method, el_val_t path, - el_val_t headers_map, el_val_t body); typedef struct { char* name; @@ -1786,7 +1804,11 @@ static el_val_t http_build_headers_map(const char* hdr_block) { static void* http_worker_v2(void* arg) { HttpWorkerArg* a = (HttpWorkerArg*)arg; +#ifdef _WIN32 + SOCKET fd = a->fd; +#else int fd = a->fd; +#endif free(a); char *method = NULL, *path = NULL, *body = NULL, *hdr_block = NULL; if (http_read_request(fd, &method, &path, &body, &hdr_block) == 0) { @@ -1816,7 +1838,7 @@ static void* http_worker_v2(void* arg) { free(response); } free(method); free(path); free(body); free(hdr_block); - close(fd); + el_closesocket(fd); pthread_mutex_lock(&_http_conn_mu); _http_conn_active--; pthread_cond_signal(&_http_conn_cv); @@ -1838,22 +1860,26 @@ el_val_t http_serve_v2(el_val_t port, el_val_t handler) { int sock = socket(AF_INET6, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return 0; } int yes = 1; int no = 0; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes)); + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&no, sizeof(no)); struct sockaddr_in6 addr; memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons((uint16_t)p); if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - perror("bind"); close(sock); return 0; + perror("bind"); el_closesocket(sock); return 0; } - if (listen(sock, 64) < 0) { perror("listen"); close(sock); return 0; } + if (listen(sock, 64) < 0) { perror("listen"); el_closesocket(sock); return 0; } fprintf(stderr, "[http v2] listening on [::]:%d (dual-stack)\n", p); while (1) { struct sockaddr_in6 cli; socklen_t clen = sizeof(cli); +#ifdef _WIN32 + SOCKET cfd = accept(sock, (struct sockaddr*)&cli, &clen); +#else int cfd = accept(sock, (struct sockaddr*)&cli, &clen); +#endif if (cfd < 0) { if (errno == EINTR) continue; perror("accept"); break; @@ -1865,11 +1891,11 @@ el_val_t http_serve_v2(el_val_t port, el_val_t handler) { _http_conn_active++; pthread_mutex_unlock(&_http_conn_mu); HttpWorkerArg* arg = malloc(sizeof(HttpWorkerArg)); - if (!arg) { close(cfd); continue; } + if (!arg) { el_closesocket(cfd); continue; } arg->fd = cfd; pthread_t tid; if (pthread_create(&tid, NULL, http_worker_v2, arg) != 0) { - close(cfd); free(arg); + el_closesocket(cfd); free(arg); pthread_mutex_lock(&_http_conn_mu); _http_conn_active--; pthread_cond_signal(&_http_conn_cv); @@ -1878,7 +1904,7 @@ el_val_t http_serve_v2(el_val_t port, el_val_t handler) { } pthread_detach(tid); } - close(sock); + el_closesocket(sock); return 0; } @@ -2129,6 +2155,23 @@ el_val_t exec(el_val_t cmdv) { el_val_t exec_bg(el_val_t cmdv) { const char* cmd = EL_CSTR(cmdv); if (!cmd || !*cmd) return el_wrap_str(el_strdup("")); +#ifdef _WIN32 + /* Windows: no fork/exec. Launch a detached `cmd /c ` with no console window via + CreateProcess (DETACHED_PROCESS | CREATE_NO_WINDOW). Returns the PID as a string, "" on fail. + Mirrors the POSIX branch: child runs independently, caller is not blocked. */ + char cmdline[8192]; + snprintf(cmdline, sizeof(cmdline), "cmd.exe /c %s", cmd); + STARTUPINFOA si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); + PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); + BOOL ok = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, + DETACHED_PROCESS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi); + if (!ok) return el_wrap_str(el_strdup("")); + char pidbuf[32]; + snprintf(pidbuf, sizeof(pidbuf), "%lu", (unsigned long)pi.dwProcessId); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return el_wrap_str(el_strdup(pidbuf)); +#else pid_t pid = fork(); if (pid < 0) { /* fork failed */ @@ -2151,6 +2194,7 @@ el_val_t exec_bg(el_val_t cmdv) { char pidbuf[32]; snprintf(pidbuf, sizeof(pidbuf), "%d", (int)pid); return el_wrap_str(el_strdup(pidbuf)); +#endif } el_val_t fs_list(el_val_t pathv) { @@ -4478,7 +4522,12 @@ static int _el_decompose_earth(el_caltime_t* ct, struct tm* tm_out, int* abbr_le localtime_r(&s, &tm); *tm_out = tm; if (abbr_buf && abbr_cap > 0) { + /* mingw's struct tm has no tm_zone (BSD/glibc extension); no abbrev available there. */ +#ifdef _WIN32 + const char* z_str = ""; +#else const char* z_str = tm.tm_zone ? tm.tm_zone : ""; +#endif size_t n = strlen(z_str); if (n >= abbr_cap) n = abbr_cap - 1; memcpy(abbr_buf, z_str, n); @@ -5832,6 +5881,10 @@ el_val_t getpid_now(void) { * Returns 0 always (the only non-return path is the exit() branch). */ el_val_t el_mem_check(void) { +#ifdef _WIN32 + /* getrusage is POSIX-only — memory guard disabled on Windows. */ + return 0; +#else /* Read limit from env; default 512 MB. */ long limit_mb = 512; const char *env_val = getenv("ELC_MAX_MEM_MB"); @@ -5857,6 +5910,7 @@ el_val_t el_mem_check(void) { exit(1); } return 0; +#endif } /* ── args() — command-line argument access ────────────────────────────────── @@ -6031,6 +6085,14 @@ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal, #define ENGRAM_LAYER_DOMAIN 2u #define ENGRAM_LAYER_IMPRINT 3u #define ENGRAM_LAYER_SUIT 4u +#define ENGRAM_LAYER_ACCUMULATION 5u +/* New user-facing nodes (memories, knowledge, conversations) are created in the + * accumulation layer — the top of the consciousness stack, the engram the user + * sees; every layer below shapes behavior but is hidden from the user (Layered + * Consciousness architecture, app 64/064,262). ENGRAM_LAYER_DEFAULT stays + * core-identity ON PURPOSE: it is the fallback home for LEGACY nodes loaded from + * snapshots without a layer_id, so existing data (the originator corpus) is + * never migrated out of its established layer. New != legacy. */ #define ENGRAM_LAYER_DEFAULT ENGRAM_LAYER_CORE_IDENTITY /* Pass 3 override floor. Layer 0 nodes that received any background @@ -6050,6 +6112,10 @@ static double engram_type_threshold(const char* node_type, const char* tier) { if (strcmp(tier, "Lesson") == 0) return 0.25; } if (node_type) { + /* Knowledge nodes: Canonical/Lesson handled by tier checks above. + * Procedural-tier Knowledge (activation_count>=50 migration): 0.20. + * (2026-06-29 self-review — mirrors release runtime fix) */ + if (strcmp(node_type, "Knowledge") == 0) return 0.20; if (strcmp(node_type, "Belief") == 0) return 0.30; if (strcmp(node_type, "Entity") == 0) return 0.30; } @@ -6208,6 +6274,20 @@ static void engram_init_layers(EngramStore* g) { .transparent = 0, .injectable = 1 }; + /* Layer 5 — accumulation. The TOP of the consciousness stack: the default + * home for all new user-facing nodes. This is the engram the user sees; + * every layer below shapes behavior but is hidden from the user. Not + * injectable — it is the persistent user accumulation, not a swappable + * overlay. transparent=0: its content is surfaced to introspection (it is + * the user's own knowledge/memory), unlike the lower behavioral layers. */ + g->layers[g->layer_count++] = (EngramLayer){ + .layer_id = ENGRAM_LAYER_ACCUMULATION, + .name = el_strdup_persist("accumulation"), + .activation_priority = 50, + .suppressible = 1, + .transparent = 0, + .injectable = 0 + }; } static EngramStore* engram_get(void) { @@ -6322,7 +6402,9 @@ static void engram_grow_edges(void) { static char* engram_new_id(void) { el_val_t v = uuid_new(); const char* s = EL_CSTR(v); - return el_strdup(s ? s : ""); + /* Persistent: node ids live in the global store; an arena (el_strdup) id is + * freed at el_request_end(), corrupting the node after the creating request. */ + return el_strdup_persist(s ? s : ""); } /* Convert a node into an ElMap of its fields. */ @@ -6399,11 +6481,44 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) { n->last_activated = now; n->created_at = now; n->updated_at = now; - n->layer_id = ENGRAM_LAYER_DEFAULT; + n->layer_id = ENGRAM_LAYER_ACCUMULATION; /* new user-facing node → top layer */ g->node_count++; return el_wrap_str(el_strdup(n->id)); } +/* engram_is_valid_utf8 — return 1 if s is valid UTF-8, 0 if it contains invalid bytes. + * Rejects overlong encodings, surrogate halves, and byte sequences > 4 bytes. */ +static int engram_is_valid_utf8(const char* s) { + if (!s) return 1; + const unsigned char* p = (const unsigned char*)s; + while (*p) { + if (*p < 0x80) { + /* ASCII */ + p++; + } else if ((*p & 0xE0) == 0xC0) { + /* 2-byte sequence */ + if ((p[1] & 0xC0) != 0x80) return 0; + if ((*p & 0xFE) == 0xC0) return 0; /* overlong */ + p += 2; + } else if ((*p & 0xF0) == 0xE0) { + /* 3-byte sequence */ + if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) return 0; + if (*p == 0xE0 && (p[1] & 0xE0) == 0x80) return 0; /* overlong */ + if (*p == 0xED && (p[1] & 0xE0) == 0xA0) return 0; /* surrogate */ + p += 3; + } else if ((*p & 0xF8) == 0xF0) { + /* 4-byte sequence */ + if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80) return 0; + if (*p == 0xF0 && (p[1] & 0xF0) == 0x80) return 0; /* overlong */ + if (*p > 0xF4) return 0; /* above U+10FFFF */ + p += 4; + } else { + return 0; + } + } + return 1; +} + el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, el_val_t salience, el_val_t importance, el_val_t confidence, el_val_t tier, el_val_t tags) { @@ -6417,12 +6532,24 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, const char* lb = EL_CSTR(label); const char* ti = EL_CSTR(tier); const char* tg = EL_CSTR(tags); - n->content = el_strdup(c ? c : ""); - n->node_type = el_strdup(nt && *nt ? nt : "Memory"); - n->label = el_strdup(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : "")); - n->tier = el_strdup(ti && *ti ? ti : "Working"); - n->tags = el_strdup(tg ? tg : ""); - n->metadata = el_strdup("{}"); + /* UTF-8 guard: reject content with invalid UTF-8 bytes. Persisting invalid + * UTF-8 garbles JSON snapshots and corrupts every subsequent node read. */ + if (c && !engram_is_valid_utf8(c)) { + fprintf(stderr, "[engram] REJECTED node write — content contains invalid UTF-8 (label=%s)\n", + lb ? lb : "(null)"); + return EL_STR(""); + } + /* Persistent (el_strdup_persist, NOT el_strdup): these strings are owned by the + * persistent global node store. el_strdup tracks into the per-request arena, which + * el_request_end() frees when the creating HTTP request completes — leaving the + * stored node with dangling pointers (corrupted ids, "saved but never listed"). + * This is the root cause of the hallucinated/lost-saves class of bugs. */ + n->content = el_strdup_persist(c ? c : ""); + n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory"); + n->label = el_strdup_persist(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : "")); + n->tier = el_strdup_persist(ti && *ti ? ti : "Working"); + n->tags = el_strdup_persist(tg ? tg : ""); + n->metadata = el_strdup_persist("{}"); n->salience = engram_decode_score(salience); n->importance = engram_decode_score(importance); n->confidence = engram_decode_score(confidence); @@ -6435,7 +6562,7 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, n->last_activated = now; n->created_at = now; n->updated_at = now; - n->layer_id = ENGRAM_LAYER_DEFAULT; + n->layer_id = ENGRAM_LAYER_ACCUMULATION; /* new user-facing node → top layer */ g->node_count++; return el_wrap_str(el_strdup(n->id)); } @@ -7365,13 +7492,28 @@ el_val_t engram_save(el_val_t path) { jb_putc(&b, '}'); } jb_puts(&b, "]}"); - FILE* f = fopen(p, "wb"); - if (!f) { free(b.buf); return 0; } + { + struct stat _st; + if (stat(p, &_st) == 0 && _st.st_size > 200000 && + (uint64_t)b.len < (uint64_t)_st.st_size / 16) { + fprintf(stderr, "[engram_save] REFUSED sparse write: new %zu vs existing %lld (<1/16) protecting %s\n", + b.len, (long long)_st.st_size, p); + free(b.buf); return 0; + } + } + size_t _plen = strlen(p); + char* _tmp = (char*)malloc(_plen + 5); + if (!_tmp) { free(b.buf); return 0; } + memcpy(_tmp, p, _plen); memcpy(_tmp + _plen, ".tmp", 5); + FILE* f = fopen(_tmp, "wb"); + if (!f) { free(_tmp); free(b.buf); return 0; } size_t w = fwrite(b.buf, 1, b.len, f); - fclose(f); - int ok = (w == b.len); - free(b.buf); - return ok ? 1 : 0; + int wok = (w == b.len); + if (wok) { fflush(f); fsync(fileno(f)); } + fclose(f); free(b.buf); + if (!wok) { unlink(_tmp); free(_tmp); return 0; } + if (rename(_tmp, p) != 0) { unlink(_tmp); free(_tmp); return 0; } + free(_tmp); return 1; } /* Helper: extract a string field from a JSON object substring. */ diff --git a/lang/el-compiler/runtime/el_runtime.h b/lang/el-compiler/runtime/el_runtime.h index 4ee01d8..f64bf63 100644 --- a/lang/el-compiler/runtime/el_runtime.h +++ b/lang/el-compiler/runtime/el_runtime.h @@ -52,6 +52,12 @@ typedef int64_t el_val_t; +/* HTTP request-handler function-pointer types. Public because soul modules (routes/chat/etc.) + * register handlers across translation units; previously defined only inside el_runtime.c, which + * made cross-module references (and the Windows build) fail. Home in the shared header. */ +typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body); +typedef el_val_t (*http_handler4_fn)(el_val_t method, el_val_t path, el_val_t body, el_val_t headers); + #define EL_STR(s) ((el_val_t)(uintptr_t)(s)) #define EL_CSTR(v) ((const char*)(uintptr_t)(v)) #define EL_INT(v) (v) diff --git a/ui/vessels/el-html/src/main.elh b/ui/vessels/el-html/src/main.elh new file mode 100644 index 0000000..a443dfe --- /dev/null +++ b/ui/vessels/el-html/src/main.elh @@ -0,0 +1,42 @@ +// auto-generated by elc --emit-header — do not edit +extern fn el_escape(s: String) -> String +extern fn el_text(s: String) -> String +extern fn el_attr(name: String, value: String) -> String +extern fn el_div(attrs: String, children: String) -> String +extern fn el_section(attrs: String, children: String) -> String +extern fn el_article(attrs: String, children: String) -> String +extern fn el_header(attrs: String, children: String) -> String +extern fn el_footer(attrs: String, children: String) -> String +extern fn el_main(attrs: String, children: String) -> String +extern fn el_nav(attrs: String, children: String) -> String +extern fn el_aside(attrs: String, children: String) -> String +extern fn el_ul(attrs: String, children: String) -> String +extern fn el_ol(attrs: String, children: String) -> String +extern fn el_li(attrs: String, children: String) -> String +extern fn el_p(attrs: String, children: String) -> String +extern fn el_span(attrs: String, children: String) -> String +extern fn el_form(attrs: String, children: String) -> String +extern fn el_h1(attrs: String, text: String) -> String +extern fn el_h2(attrs: String, text: String) -> String +extern fn el_h3(attrs: String, text: String) -> String +extern fn el_h4(attrs: String, text: String) -> String +extern fn el_button(attrs: String, label: String) -> String +extern fn el_a(href: String, attrs: String, children: String) -> String +extern fn el_input(type_attr: String, attrs: String) -> String +extern fn el_textarea(attrs: String, value: String) -> String +extern fn el_label(for_id: String, attrs: String, children: String) -> String +extern fn el_img(src: String, alt: String, attrs: String) -> String +extern fn el_video(attrs: String, children: String) -> String +extern fn el_strong(children: String) -> String +extern fn el_em(children: String) -> String +extern fn el_code(children: String) -> String +extern fn el_pre(attrs: String, children: String) -> String +extern fn el_hr() -> String +extern fn el_br() -> String +extern fn el_html_doc(lang: String, head_html: String, body_html: String) -> String +extern fn el_meta(name: String, content: String) -> String +extern fn el_meta_charset(charset: String) -> String +extern fn el_link_stylesheet(href: String) -> String +extern fn el_script_src(src: String, defer_load: Bool) -> String +extern fn el_script_inline(js: String) -> String +extern fn el_title(text: String) -> String