All ports should be at parity now.

This commit is contained in:
Scott Duensing 2026-07-07 19:37:42 -05:00
parent 7a5744f619
commit 90634e4244
106 changed files with 67412 additions and 1228 deletions

59
API.md
View file

@ -33,6 +33,18 @@ etc. -- see the README and `src/calog.h`.)
`calogExport`) accept a first-class function value in every engine. A function value handed
*to* a script (say, returned by `calogCall`) is invoked directly -- except in my-basic, which
calls it with `calogInvoke(fn, ...args)`.
- **Binary data in my-basic.** my-basic strings are text (NUL-terminated), so a calog string that
carries an embedded NUL arrives as a distinct **byte-buffer** value instead (a NUL-free string stays an
ordinary string, unchanged). Byte buffers are length-carrying and binary-safe; work with them via
`byteLen(b) -> int`, `byteAt(b, i: int) -> int`, `byteSlice(b, start: int, count: int) -> bytes`,
`byteConcat(...parts) -> bytes` (each part a byte buffer or a string), `strToByte(s: string) -> bytes`,
and `byteToStr(b) -> string`. `+` concatenates byte buffers (and `string + bytes`), and `=`/`<>` compare
them by content. A byte buffer egresses back to a native as a full-length binary string. Every other
engine's strings are already binary-safe, so this applies to my-basic only.
- **Sandboxing (my-basic).** A my-basic context honors the same per-context limits as Lua/JS -- the native
allow-list, a wall-clock time budget, and a memory cap (an over-budget or runaway script is retired).
`INPUT` never reads host stdin: it yields an empty line, so a script takes input through natives like
every other engine.
- **Availability.** Every library in this reference is compiled into `bin/calog`: crypto,
json, kv, fs, time, timer, export, pubsub, task, net (TCP / UDP / ENet), db (SQLite /
PostgreSQL / MySQL), http, and ssh. ssh needs a reachable server; http needs a reachable
@ -161,29 +173,42 @@ POSIX filesystem access. A failed operation raises a catchable script error carr
Minimal HTTP/1.1 client over `http://` and `https://`. Each call is its own connection
(`Connection: close`). 3xx redirects are followed (up to 16, to break loops). `https://`
verifies the server certificate against the system CA store by default.
verifies the server certificate against the host's native trust store by default: the Windows
system certificate stores, the macOS keychain (on a build made with the Apple SDK), or the
well-known CA-bundle files on Linux/BSD. Overrides, highest priority first: `CALOG_CA_BUNDLE`
(authoritative -- trust exactly that PEM bundle, so it can pin to a private CA), then the
OpenSSL-standard `SSL_CERT_FILE` / `SSL_CERT_DIR`. If no trust anchors can be found, a verified
request fails with a clear error rather than a misleading per-certificate failure.
| Function | Description |
|---|---|
| `httpGet(url: string) -> map` | GET a URL, following redirects. Returns `{status, body, headers}` (headers keyed by lowercased name). |
| `httpRequest(opts: map) -> map` | `opts` is `{method (default "GET"), url, headers (map), body, insecure (bool), maxRedirects (int, default 16; 0 = don't follow)}`. `insecure=true` skips TLS verification. Returns `{status, body, headers}`. |
## httpd
## httpd (a script, not a native library)
A polyglot HTTP server: route handlers are function values (in any engine), invoked on their owning
context. Re-registering a route replaces the handler live (hot reload). v1 serves HTTP/1.1 with
`Connection: close`, one request in flight at a time; TLS and WebSocket are not yet included.
The HTTP/1.1 + WebSocket server lives entirely in **`examples/httpd.lua`** -- there is no C httpd. It
is built on the generic `tcp*` transport above (with the `tls` option for HTTPS/WSS) plus the crypto
natives (`cryptoHashSha1` + `cryptoBase64Encode` for the WebSocket handshake), which is the calog
thesis: systems primitives in C, protocol in a script. It runs as-is on any engine with binary-safe
strings (all but my-basic, whose strings are text); a my-basic port would use the byte-buffer type and
its `byte*` helpers for the masked/binary frames instead of string operations.
| Function | Description |
| --- | --- |
| `httpdListen(port [, opts]) -> serverHandle` | Bind + listen; starts the acceptor. `opts`: `{ host, backlog, maxBody }`. |
| `httpdRoute(serverHandle, method, path, handler)` | Register a handler. `method` `"*"` = any; exact-path match; re-registering replaces (hot reload). |
| `httpdUnroute(serverHandle, method, path)` | Remove a route. |
| `httpdStop(serverHandle)` | Stop accepting, close, release handlers. |
```lua
local httpd = dofile("examples/httpd.lua") -- or paste/require the module
local s = httpd.new()
s:route("GET", "/hi", function(req) return "hello " .. req.path end) -- string body => 200
s:route("GET", "/made", function(req) return { status = 201, body = "x" } end) -- map => custom status
s:websocket("/ws", function(msg) return "echo: " .. msg.message end) -- reply string, or nil
s:serve(8080) -- opts: { tls, cert, key, keep = fn, acceptTimeout }
```
The handler receives a request map `{ method, path, query, headers (map, lowercased names), body }`
and returns a response: a map `{ status (default 200), headers (map), body (string) }`, a bare string
(=> 200 with that body), or nil (=> 204). Bodies are binary-safe.
A route handler receives `{ method, path, query, headers (lowercased), body }` and returns a map
`{ status, headers, body }`, a bare string (=> 200), or nil (=> 204). The server does HTTP/1.1
keep-alive; re-registering a path hot-swaps its handler. A WebSocket handler receives
`{ path, message }` per inbound text/binary frame and returns a text reply or nil (request/reply
model; no server-push). Concurrency follows the actor model: one context per accept loop (serial),
or share the listener handle across several contexts for parallelism.
## json
@ -216,8 +241,8 @@ TCP and UDP:
| Function | Description |
|---|---|
| `tcpConnect(host: string, port: int) -> handle` | Connect to a TCP server. |
| `tcpListen(port: int) -> handle` | Listen on a TCP port. |
| `tcpAccept(handle: handle) -> handle` | Block for a client; returns a connection handle. |
| `tcpListen(port: int [, opts: map]) -> handle` | Listen on a TCP port. `opts`: `{ tls (bool), cert (PEM path), key (PEM path) }` -- with `tls`, every accepted connection is a TLS server session (used by the HTTPS/WSS server script). |
| `tcpAccept(handle: handle [, timeoutMs: int]) -> handle \| nil` | Block for a client (completing the TLS handshake for a TLS listener); returns a connection handle. With `timeoutMs`, returns nil if none arrives in time, so an accept loop can re-check its own stop condition. |
| `tcpSend(handle: handle, data: string) -> int` | Send all of `data`; returns bytes sent. |
| `tcpRecv(handle: handle, maxBytes: int) -> string \| nil` | Read up to `maxBytes`; nil at end of stream. |
| `tcpClose(handle: handle)` | Close a socket. |
@ -243,7 +268,7 @@ Run a subprocess and capture its output (POSIX only; started with `posix_spawn`,
| Function | Description |
| --- | --- |
| `procRun(argv: list(string) [, opts: map]) -> map` | Spawn `argv[0]`, wait, and return `{ exit: int, stdout: string, stderr: string }` (exit is the negative signal number if killed). `opts`: `{ stdin: string, cwd: string, env: map (string->string; replaces the environment) }`. Blocks the caller's context thread; stdin is fed while stdout/stderr are drained, so a large transfer cannot deadlock. |
| `procRun(argv: list(string) [, opts: map]) -> map` | Spawn `argv[0]`, wait, and return `{ exit: int, stdout: string, stderr: string }` (on POSIX, exit is the negative signal number if the child was killed; on Windows it is the process exit code). `opts`: `{ stdin: string, cwd: string, env: map (string->string; replaces the environment) }`. Blocks the caller's context thread; stdin is fed while stdout/stderr are drained, so a large transfer cannot deadlock. POSIX spawns with `posix_spawn`; Windows spawns with `CreateProcess`. |
## pubsub

View file

@ -340,7 +340,7 @@ $(PCRE2LIB):
BINS = bin/testBroker bin/testLua bin/testMyBasic bin/testPolyglot bin/testActor bin/testHooks \
bin/testEngineLua bin/testEngineMyBasic bin/testSquirrel bin/testEngineSquirrel bin/testJs bin/testEngineJs \
bin/testEngineBerry bin/testEngineS7 bin/testEngineWren bin/testEngineMruby bin/testEngineTcl bin/testEngineJanet bin/testLoad bin/testDb bin/testNet bin/testTask bin/testExport bin/testJson bin/testXml bin/testTime bin/testFs bin/testCrypto bin/testKv bin/testTimer bin/testPubsub bin/testHttp bin/testHttps bin/testArchive bin/testUtil bin/testSandbox bin/testTrace bin/testHttpd bin/embed bin/calog
bin/testEngineBerry bin/testEngineS7 bin/testEngineWren bin/testEngineMruby bin/testEngineTcl bin/testEngineJanet bin/testLoad bin/testDb bin/testNet bin/testTask bin/testExport bin/testJson bin/testXml bin/testTime bin/testFs bin/testCrypto bin/testKv bin/testTimer bin/testPubsub bin/testHttp bin/testHttps bin/testArchive bin/testUtil bin/testSandbox bin/testTrace bin/testHttpdLua bin/embed bin/calog
all: $(BINS)
@ -352,7 +352,7 @@ $(STRICTOBJ): obj/%.o: %.c | obj
$(CC) $(COREFLAGS) $(INC) -c -o $@ $<
# strict C, threaded
THREADOBJ = obj/context.o obj/mybasicEngine.o obj/testActor.o obj/testEngineLua.o obj/testEngineSquirrel.o obj/testEngineJs.o obj/testEngineMyBasic.o obj/testEngineBerry.o obj/testEngineS7.o obj/testEngineWren.o obj/testEngineMruby.o obj/testEngineTcl.o obj/testEngineJanet.o obj/calogHandle.o obj/testDb.o obj/testNet.o obj/testTask.o obj/calogExport.o obj/testExport.o obj/calogJson.o obj/testJson.o obj/calogFs.o obj/testFs.o obj/calogTime.o obj/testTime.o obj/calogKv.o obj/testKv.o obj/testCrypto.o obj/calogTimer.o obj/testTimer.o obj/calogPubsub.o obj/testPubsub.o obj/testHttp.o obj/testSsh.o obj/calogCsv.o obj/calogProc.o obj/testSandbox.o obj/testTrace.o obj/calogHttpd.o obj/testHttpd.o obj/testHooks.o
THREADOBJ = obj/context.o obj/mybasicEngine.o obj/testActor.o obj/testEngineLua.o obj/testEngineSquirrel.o obj/testEngineJs.o obj/testEngineMyBasic.o obj/testEngineBerry.o obj/testEngineS7.o obj/testEngineWren.o obj/testEngineMruby.o obj/testEngineTcl.o obj/testEngineJanet.o obj/calogHandle.o obj/testDb.o obj/testNet.o obj/testTask.o obj/calogExport.o obj/testExport.o obj/calogJson.o obj/testJson.o obj/calogFs.o obj/testFs.o obj/calogTime.o obj/testTime.o obj/calogKv.o obj/testKv.o obj/testCrypto.o obj/calogTimer.o obj/testTimer.o obj/calogPubsub.o obj/testPubsub.o obj/testHttp.o obj/testSsh.o obj/calogCsv.o obj/calogProc.o obj/testSandbox.o obj/testTrace.o obj/testHttpdLua.o obj/testHooks.o
$(THREADOBJ): obj/%.o: %.c | obj
$(CC) $(COREFLAGS) $(INC) -pthread -c -o $@ $<
@ -449,7 +449,7 @@ $(DBADP): obj/%.o: %.c | obj
# always enabled -- unlike the DB clients, ENet needs no server)
NETADP = obj/calogNet.o
$(NETADP): obj/%.o: %.c | obj
$(CC) $(COREFLAGS) $(INC) $(ENETINC) -pthread -c -o $@ $<
$(CC) $(COREFLAGS) $(INC) $(ENETINC) $(OSSLINC) -pthread -c -o $@ $<
# task library: strict C with every engine name compiled in (it references each engine
# vtable under its CALOG_WITH_* guard), so a task-using binary links all engine archives.
@ -468,7 +468,7 @@ obj/testPolyglot.o: testPolyglot.c | obj
$(CC) $(ADPFLAGS) $(INC) $(LUAINC) $(MBINC) -DMB_DOUBLE_FLOAT -c -o $@ $<
# ---- vendored engine objects (also land in obj/) ----
obj/ourBasic.o: $(MBDIR)/ourBasic.c | obj
obj/ourBasic.o: $(MBDIR)/ourBasic.c $(MBDIR)/ourBasic.h | obj
$(CC) $(MBFLAGS) -c -o $@ $<
obj/%.o: $(LUADIR)/src/%.c | obj
@ -557,8 +557,8 @@ bin/testDb: obj/testDb.o obj/calogDb.o obj/calogHandle.o lib/libcalog.a lib/libl
# network library test: calog + the network library + vendored ENet, driven from Lua
# (TCP/UDP/ENet loopback).
bin/testNet: obj/testNet.o $(NETADP) obj/calogHandle.o lib/libcalog.a lib/liblua.a lib/libenet.a | bin
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS)
bin/testNet: obj/testNet.o $(NETADP) obj/calogHandle.o lib/libcalog.a lib/liblua.a lib/libenet.a $(SSLARCH) | bin
$(CC) $(LDFLAGS) -pthread -o $@ $(filter-out $(SSLARCH),$^) $(LUALIBS) $(SSLARCH)
# --- optional Postgres + MySQL DB-client tests. They pull in the vendored client archives
# + OpenSSL (PG archives need a link group), and their live round-trips need a running
@ -642,7 +642,7 @@ bin/embed: obj/embed.o lib/libcalog.a lib/libquickjs.a | bin
# binary can drop the MySQL backend (see LICENSE.md). libssh2 precedes OpenSSL (it depends on
# it); SSLARCH follows the archives that reference it.
bin/calog: obj/calogMain.o \
obj/calogArchive.o obj/calogCrypto.o obj/calogCsv.o obj/calogDbFull.o obj/calogExport.o obj/calogFs.o obj/calogHttp.o obj/calogHttpd.o obj/calogJson.o obj/calogKv.o obj/calogNet.o obj/calogProc.o obj/calogPubsub.o obj/calogRegex.o obj/calogSsh.o obj/calogTask.o obj/calogTime.o obj/calogTimer.o obj/calogXml.o obj/calogHandle.o \
obj/calogArchive.o obj/calogCrypto.o obj/calogCsv.o obj/calogDbFull.o obj/calogExport.o obj/calogFs.o obj/calogHttp.o obj/calogJson.o obj/calogKv.o obj/calogNet.o obj/calogProc.o obj/calogPubsub.o obj/calogRegex.o obj/calogSsh.o obj/calogTask.o obj/calogTime.o obj/calogTimer.o obj/calogXml.o obj/calogHandle.o \
lib/libcalog.a lib/liblua.a lib/libquickjs.a lib/libsquirrel.a lib/libmybasic.a lib/libberry.a lib/libs7.a lib/libwren.a lib/libjanet.a $(MRUBYLIB) $(TCLLIB) \
lib/libsqlite3.a lib/libenet.a $(LIBSSH2LIB) $(ARCHIVELIBS) $(PCRE2LIB) $(DBARCH) | bin
$(CC) $(LDFLAGS) $(RELEASELD) -pthread -o $@ $(filter-out $(DBARCH),$^) -Wl,--start-group $(PGARCHIVES) -Wl,--end-group $(MYSQLARCH) $(SSLARCH) $(CXXLIB) $(DLLIB) -lm -lpthread $(SOCKETLIBS)
@ -738,14 +738,16 @@ bin/testArchive: obj/testArchive.o obj/calogArchive.o obj/calogHandle.o lib/libc
bin/testUtil: obj/testUtil.o obj/calogCsv.o obj/calogProc.o obj/calogRegex.o lib/libcalog.a lib/liblua.a $(PCRE2LIB) | bin
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS)
bin/testSandbox: obj/testSandbox.o lib/libcalog.a lib/liblua.a lib/libquickjs.a | bin
bin/testSandbox: obj/testSandbox.o lib/libcalog.a lib/liblua.a lib/libquickjs.a lib/libmybasic.a | bin
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS)
bin/testTrace: obj/testTrace.o obj/calogExport.o lib/libcalog.a lib/liblua.a lib/libquickjs.a | bin
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS)
bin/testHttpd: obj/testHttpd.o obj/calogHttpd.o obj/calogHandle.o lib/libcalog.a lib/liblua.a lib/libquickjs.a | bin
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS)
# The httpd-as-a-script (examples/httpd.lua): protocol in Lua over the tcp* transport (calogNet, which
# needs libenet) + the crypto natives (calogCrypto, which needs OpenSSL).
bin/testHttpdLua: obj/testHttpdLua.o obj/calogNet.o obj/calogCrypto.o obj/calogHandle.o lib/libcalog.a lib/liblua.a lib/libenet.a $(SSLARCH) | bin
$(CC) $(LDFLAGS) -pthread -o $@ $(filter-out $(SSLARCH),$^) $(LUALIBS) $(SSLARCH)
bin/testHttps: obj/testHttps.o obj/calogHttp.o lib/libcalog.a lib/liblua.a $(SSLARCH) | bin
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS) $(DLLIB)
@ -766,7 +768,7 @@ obj bin lib:
test: all
./bin/testBroker && ./bin/testLua && ./bin/testMyBasic && ./bin/testPolyglot && \
./bin/testActor && ./bin/testHooks && ./bin/testEngineLua && ./bin/testEngineMyBasic && ./bin/testSquirrel && ./bin/testEngineSquirrel && \
./bin/testJs && ./bin/testEngineJs && ./bin/testEngineBerry && ./bin/testEngineS7 && ./bin/testEngineWren && ./bin/testEngineMruby && ./bin/testEngineTcl && ./bin/testEngineJanet && ./bin/testLoad && ./bin/testDb && ./bin/testNet && ./bin/testTask && ./bin/testExport && ./bin/testJson && ./bin/testXml && ./bin/testTime && ./bin/testFs && ./bin/testCrypto && ./bin/testKv && ./bin/testTimer && ./bin/testPubsub && ./bin/testHttp && ./bin/testHttps && ./bin/testArchive && ./bin/testUtil && ./bin/testSandbox && ./bin/testTrace && ./bin/testHttpd
./bin/testJs && ./bin/testEngineJs && ./bin/testEngineBerry && ./bin/testEngineS7 && ./bin/testEngineWren && ./bin/testEngineMruby && ./bin/testEngineTcl && ./bin/testEngineJanet && ./bin/testLoad && ./bin/testDb && ./bin/testNet && ./bin/testTask && ./bin/testExport && ./bin/testJson && ./bin/testXml && ./bin/testTime && ./bin/testFs && ./bin/testCrypto && ./bin/testKv && ./bin/testTimer && ./bin/testPubsub && ./bin/testHttp && ./bin/testHttps && ./bin/testArchive && ./bin/testUtil && ./bin/testSandbox && ./bin/testTrace && ./bin/testHttpdLua
# ThreadSanitizer build of the actor core and the Lua engine path (cannot combine
# with ASan). Recompiled from source under TSan; the vendored Lua objects are
@ -888,10 +890,48 @@ tsanlibs: lib/liblua.a | bin
setarch -R ./bin/test$${n}Tsan || exit 1; \
done
# ThreadSanitizer over the httpd path: the tcp transport (calogNet, including the TLS handshake in
# netTlsAccept) + the crypto natives, driven by examples/httpd.lua on two server context threads while
# the host thread hammers them with HTTP / HTTPS / WebSocket clients. The vendored libs (liblua,
# libenet, OpenSSL) link un-sanitized -- TSan instruments calog's own code and intercepts the pthread
# calls across the boundary, so races in the net handle table / actor layer / TLS setup surface here.
tsanhttpd: lib/liblua.a lib/libenet.a $(SSLARCH) | bin
$(CC) $(TSANFLAGS) $(INC) $(LUAINC) $(ENETINC) $(OSSLINC) -o bin/testHttpdLuaTsan \
tests/testHttpdLua.c libs/calogNet.c libs/calogCrypto.c libs/calogHandle.c \
src/lua/luaEngine.c src/lua/luaAdapter.c $(TSANCORE) \
lib/liblua.a lib/libenet.a $(SSLARCH) $(LUALIBS)
setarch -R ./bin/testHttpdLuaTsan
# libFuzzer harnesses for the untrusted-input parsers (JSON, CSV, XML). clang-only: libcalog.a is
# gcc-ASan and cannot share a process with clang's fuzzer/ASan runtime, so the small core is
# compiled fresh from source alongside the one generic driver (tests/fuzzParser.c), which
# #include's the parser under test and drives its native on the raw fuzz bytes.
# make fuzz -- build bin/fuzz{Json,Csv,Xml}
# make fuzz-smoke -- build then run each briefly (a crash exits nonzero); manual regression gate
# ./bin/fuzzJson dir -- open-ended fuzzing, accumulating a corpus in dir/
FUZZCC = clang
FUZZFLAGS = -g -O1 -std=c11 -D_GNU_SOURCE -fsanitize=fuzzer,address,undefined -Isrc -Ilibs
FUZZCORE = tests/fuzzParser.c src/value.c src/broker.c src/context.c
FUZZSECS ?= 20
bin/fuzzJson: tests/fuzzParser.c libs/calogJson.c | bin
$(FUZZCC) $(FUZZFLAGS) -DFUZZ_INCLUDE='"calogJson.c"' -DFUZZ_NATIVE=jsonParseNative $(FUZZCORE) -lm -o $@
bin/fuzzCsv: tests/fuzzParser.c libs/calogCsv.c | bin
$(FUZZCC) $(FUZZFLAGS) -DFUZZ_INCLUDE='"calogCsv.c"' -DFUZZ_NATIVE=csvParse $(FUZZCORE) -lm -o $@
bin/fuzzXml: tests/fuzzParser.c libs/calogXml.c $(LIBXML2LIB) | bin
$(FUZZCC) $(FUZZFLAGS) $(LIBXML2INC) -DLIBXML_STATIC -DFUZZ_INCLUDE='"calogXml.c"' -DFUZZ_NATIVE=xmlParseNative $(FUZZCORE) $(LIBXML2LIB) -lm -o $@
fuzz: bin/fuzzJson bin/fuzzCsv bin/fuzzXml
fuzz-smoke: fuzz
@for f in Json Csv Xml; do echo "== fuzz-smoke $$f ($(FUZZSECS)s) =="; ./bin/fuzz$$f -max_total_time=$(FUZZSECS) -print_final_stats=1 || exit 1; done
clean:
rm -rf obj bin lib
-include $(wildcard obj/*.d)
-include $(wildcard obj/rel/*.d)
.PHONY: all test tsan tsansq tsanjs tsanmb tsanberry tsans7 tsanwren tsanmruby tsantcl tsanjanet tsanlibs release clean
.PHONY: all test tsan tsansq tsanjs tsanmb tsanberry tsans7 tsanwren tsanmruby tsantcl tsanjanet tsanlibs tsanhttpd release fuzz fuzz-smoke clean

View file

@ -181,12 +181,78 @@ Everything lands under `build/cross/` (gitignored); the vendored sources and the
(`libc++`, no `-ldl`, SIGPIPE, native pthreads + BSD sockets).
- **Archive stack** (libarchive + zlib/bzip2/lz4/zstd/xz): **musl run-verified, Windows
build-verified** via `tools/crossArchive.sh` (zig). The musl `testArchive` is fully static and
passes all 21 checks on the host (real compress/decompress through every codec); the Windows build
passes all 22 checks on the host -- real compress/decompress through every codec **and an xar
read+write round-trip**: the musl target now cross-builds OpenSSL (xar's MD5/SHA1) and libxml2 (its
XML TOC), with iconv coming from musl's own libc, so its libarchive has full xar. The Windows build
is a valid `testArchive.exe` (PE32+) linked against vendored winpthreads + the CNG/XmlLite system
libs. This was the one CMake-heavy vendored stack, so it is the strongest cross-build evidence
after the core. The XAR format (native builds enable it via libxml2 + OpenSSL + iconv) is **not**
in the cross build -- it is a native-only feature until those three are cross-built too.
- The remaining heavy vendored libraries (OpenSSL, Tcl, mruby, libssh2, MariaDB, PostgreSQL) still
need a per-target build to reach a *full-featured* calog on macOS/Windows; the cross-build proves
the core broker + engines + socket + archive layers, which is where all the portability risk lived.
No code rewrite is expected -- the OS surface is only threads + sockets + DNS, all abstracted.
after the core. On **Windows**, xar needs no vendored XML/crypto/iconv at all: libarchive
auto-uses the system **XmlLite** (its TOC) + **CNG/bcrypt** (its MD5/SHA1), both in the mingw
sysroot, so the Windows `testArchive.exe` already has full xar. One musl gotcha worth noting:
libarchive's `FIND_PATH(iconv.h)` returns the host `/usr/include` (glibc), whose headers break the
musl compile, so the script pins `ICONV_INCLUDE_DIR` to an empty dir and lets zig cc resolve musl's
own `iconv.h` implicitly.
- **Full Windows CLI cross-built (build-verified).** Every heavy vendored dependency now
cross-compiles for `x86_64-windows-gnu` via zig -- OpenSSL, libxml2, PCRE2, libssh2, MariaDB, and
the three hard ones **PostgreSQL/libpq, Tcl, mruby** (libpq needed a patched out-of-tree copy;
see `tools/crossWinFull.sh`). The complete `calog.exe` links **all 10 engines + all 20 libraries**
into a valid PE32+ that imports only Windows system DLLs (no vendored DLLs). Behavior-neutral
`#ifdef _WIN32` guards in calog's own source cover the small OS differences -- `calogFs` (mingw
1-arg `mkdir`, `O_BINARY`), `calogHttp` (no `SIGPIPE`), `calogInternal` (a `memmem` fallback),
`calogPlatform` (`__declspec(thread)` -> `_Thread_local`, which also fixes a latent Windows
thread-safety bug clang had been silently dropping), and `calogNet` (`SO_EXCLUSIVEADDRUSE` for
listeners instead of the hijack-prone `SO_REUSEADDR`). Two libraries have a full Windows backend
rather than a POSIX one: **`procRun`** spawns with `CreateProcess` + pipes (stdin fed on a helper
thread while stdout/stderr drain, since anonymous pipes have no `poll`), and the **HTTPS client**
reads trust anchors from the Windows `ROOT`/`CA` system certificate stores via CryptoAPI
(`CertOpenSystemStore`) instead of a Unix CA-bundle path. All are behavior-neutral off Windows; the
Linux suite stays green. Run-verification still needs a Windows host or wine.
- **Full macOS CLI cross-built for both archs (build-verified).** Every heavy vendored dependency
now cross-compiles for `x86_64-apple-darwin` and `arm64-apple-darwin` via zig, whose libSystem
stub supplies libc / pthreads / BSD sockets / getaddrinfo / dlopen / iconv -- so no Apple SDK is
needed for calog's OS surface (see `tools/crossMacFull.sh`). The complete `calog` links **all 10
engines + all 20 libraries** into a valid Mach-O per arch (`build/cross/mac-x64/calog` x86_64,
`build/cross/mac-arm64/calog` arm64), importing only libSystem -- no vendored dylibs, no `-ldl`.
Two additive `#if defined(__APPLE__)` guards are in calog's own source: `calogTimer` waits with a
**relative** `pthread_cond_timedwait_relative_np` timeout on Darwin, because macOS pthreads have no
`pthread_condattr_setclock` to pin the condvar to `CLOCK_MONOTONIC` (the relative wait preserves the
monotonic scheduling intent across wall-clock changes); and `calogHttp`'s HTTPS client reads trust
anchors from the **macOS keychain** via the Security framework (`SecTrustCopyAnchorCertificates`).
Both are behavior-neutral off Darwin, and the Linux suite stays green. Run-verification still needs a
Mac.
- **macOS Keychain HTTPS trust needs the Apple SDK (optional).** calog's OS surface (libc / pthreads /
BSD sockets / getaddrinfo / dlopen / iconv) is all in zig's libSystem stub, so the *runner* needs no
SDK. But the keychain trust path uses the **Security** + **CoreFoundation** frameworks, whose headers
zig does not ship. When an SDK is present, `tools/crossMacFull.sh` compiles `calogHttp` with
`-DCALOG_MAC_KEYCHAIN_TRUST` against the SDK framework headers; without one it omits keychain trust
and HTTPS falls back to `SSL_CERT_FILE` / `CALOG_CA_BUNDLE`. Point `CALOG_MACSDK` at a `MacOSX*.sdk`
directory to enable it. **Linker gotcha:** zig 0.16's Mach-O linker *segfaults* parsing the real
multi-target SDK `.tbd` files, so calog links against hand-written minimal stubs
(`tools/macStubs/{CoreFoundation,Security}.tbd`) that export only the handful of symbols the keychain
code calls -- the binary still imports the real system frameworks by install-name at runtime. Both
arch binaries are valid Mach-O importing `Security.framework` + `CoreFoundation.framework`
(build-verified; run-verification still needs a Mac).
- **Reproducible dependency builds (`tools/crossDeps.sh`).** The full-CLI scripts above consume
heavy pinned deps under `build/cross/<target>/`; `crossDeps.sh` rebuilds them from `vendor/` source
with a single zig toolchain, so the cross build is reproducible from a clean checkout rather than
depending on ad-hoc pins. Usage: `[ZIG=/path/to/zig] ./tools/crossDeps.sh <win|mac-x64|mac-arm64>
[dep ...]` (it regenerates the zig wrappers + CMake toolchain file first, so nothing under
`build/cross/` need pre-exist). It builds, in dependency order: the codecs (zlib/bzip2/lz4/zstd),
xz, OpenSSL (mac also gets a BSD-`ar` **repack**, since zig's Mach-O linker cannot read OpenSSL's
GNU-`ar` archives), libxml2, PCRE2, libssh2, MariaDB, PostgreSQL/libpq (the patched out-of-tree
copy: mingw `.obj`->`.o`, a real `ar` rule for `libpq.a`, and `pg_pthread_*` symbol localization on
Windows; libSystem pthreads and no patch on mac), Tcl, mruby, winpthreads (Windows only), and
libarchive (mac = xar via the pinned OpenSSL+libxml2; Windows = codecs only, xar via system
XmlLite/CNG). **VERIFIED build-clean from vendor source for ALL THREE targets, all 12 deps** (`OUT=`
builds into a scratch dir without clobbering the pins): `mac-x64` and `mac-arm64` each build the full
set in one clean invocation (OpenSSL confirmed Mach-O + `!<arch>` BSD repack); `win` builds the full
set including the mingw-only bits -- winpthreads, the patched libpq (`.obj`->`.o`, real `ar` rule,
`pg_pthread_*` localization, plus the Windows syslibs `-lws2_32 -lcrypt32 -lsecur32 -lbcrypt ...` so
configure's `-lcrypto` link test passes), and the two `.rc` resources (libxml2's + libpq's
`win32ver.rc`) compiled via a `zig rc` **windres shim** that handles both CMake's positional args and
PostgreSQL's `-i/-o/--include-dir` form. The win CMake toolchain sets `CMAKE_RC_COMPILER` to that
shim.
- **Tcl re-vendored to close the last gap (2026-07-06).** `vendor/tcl` had been trimmed to the native
unix/linux build set (no `win/`, no `macosx/`), so cross-Tcl could not build from vendored source.
The official tcl9.0.4 `win/` + `macosx/` subdirs are now restored, so all three ports build Tcl from
`vendor/`; the native Linux build is unchanged (still `testEngineTcl` 13/0). All ports now match.

169
design.md
View file

@ -1015,9 +1015,16 @@ Added:
With the patch, Wren too reads maps back in. (Documented in LICENSE.md; re-apply if the
amalgamation is regenerated.)
**Deliberately not "fixed" (inherent to the engine's value model):** MY-BASIC 32-bit ints
(over 2^31 range-checked to an error, not silently truncated), MY-BASIC NUL-in-string
truncation and serialize-at-load, and JS/Wren int64 above 2^53 (IEEE doubles -- JS *could*
**Closed in the fork since:** MY-BASIC ints are now **64-bit** (`int_t` widened to `long long`
in vendor/ourbasic, so calog's full int64 range round-trips; the old >2^31 clamp is gone), and
function-into-script works on MY-BASIC too (a host callable enters as a usertype-ref, invoked with
`calogInvoke(fn, ...)` or a bare name).
**Deliberately not "fixed" (inherent to the engine's value model):** MY-BASIC NUL-in-string
truncation (its strings are `char*`/`strlen`, so binary data is cut at the first NUL -- this is
what stops MY-BASIC from hosting the WebSocket/binary httpd script the way the 8 binary-safe engines
do; fixing it means a length-carrying string type + every string builtin, a separate project) and
serialize-at-load, and JS/Wren int64 above 2^53 (IEEE doubles -- JS *could*
emit a BigInt but that breaks arithmetic mixing with Number, a worse trap than the
documented precision edge). `WREN_MAX_CALL_ARITY` (16) is pinned to Wren's own engine
limit (`MAX_PARAMETERS`) and can't be raised. `MB_BANK_SIZE` (the MY-BASIC native cap) was
@ -1123,3 +1130,159 @@ calog does not init); there is no file/socket IO -- scripts use calog's `fs*`/`n
command callable, a cross-thread Tcl callback via `calogCallback`, the error handler, three concurrent
interpreters), ASan-clean; `tsantcl` clean; bare-name exports and cross-engine both ways; `make test`
30/30.
## 21. Janet -- the tenth engine (a Lisp)
**Janet** (`.janet`, `calogJanetEngine`) is the tenth engine and returns to the single-file amalgamation
pattern of s7 and Wren rather than the engine-run build of mruby and Tcl: Janet 1.41.2 ships as one
`janet.c` (plus `janet.h` and `janetconf.h`), compiled at `-O2` with `-DJANET_NO_NET`,
`-DJANET_NO_PROCESSES`, and `-DJANET_NO_DYNAMIC_MODULES` -- the language core only, with the net,
subprocess, and dynamic-module subsystems trimmed out, so scripts do IO through calog's
`fs*`/`net*`/`http*` natives like every other engine. The amalgamation compiles to a single object
(`janet.o`, ~4.8 MB unstripped at `-O2`, dead-stripped at link).
Concurrency is again the qualifying property. Janet's entire runtime state is a `JANET_THREAD_LOCAL
janet_vm` (`__thread` on gcc), so each context runs `janet_init`/`janet_deinit` on its own thread and N
contexts are N fully independent VMs that share nothing -- calog's actor model with no adaptation.
`tsanjanet` is the GATE-A race check: N thread-local VMs on N threads must never alias.
Values marshal both ways through `CalogValueT`. Janet numbers are IEEE doubles with no separate
script-level integer type worth preserving past 2^53, so calog ints cross as `janet_wrap_number` and
egress through the canonical double classifier (an exact integer becomes an int, else a real) -- the same
2^53 ceiling as Lua/JS/Wren. `nil` round-trips. Strings are the bright spot: Janet strings are
length-prefixed, so egress reads `janet_string_length` and a `JANET_BUFFER` reads its `->count` --
binary-safe, embedded NUL bytes and all, where Tcl fell back to byte-arrays and my-basic cannot do it at
all yet. Symbols and keywords also egress as strings. Aggregates map `JanetArray` and `JanetTuple` ->
list, and `JanetTable` and `JanetStruct` -> map (a keyed record's integer-keyed part lands at numeric
table keys).
Dispatch is the Janet-specific twist. A `JanetCFunction` carries no user data, so a bare cfunction
cannot recover which native it backs -- so calog does not use cfunctions at all. Every exposed native is
instead a Janet abstract value (`gNativeType`) whose `JanetAbstractType.call` handler dispatches through
`calogCall` by the name kept in the abstract's payload; it is `janet_def`'d into the core env under that
name, so `cryptoUuid` is callable bare, and the context rides in the payload with no global. At VM
creation the engine walks the broker registry (`calogForEach`) and installs every entry this way. The
abstracts are deliberately not registered with `janet_register_abstract_type` -- calling and GC need no
registration, and the type's pointer identity is enough to recognize calog's own values on the way back
out.
Callables cross both ways on that same abstract mechanism. A foreign `CalogFnT` pushed in becomes a
`gForeignType` abstract whose `.call` invokes `calogFnInvoke` (the fn is `calogFnRetain`'d and released
by the abstract's `.gc` at collection), so a script calls it like any function. A Janet function handed
out is wrapped as a `CalogFnT` and the `JanetFunction` is `janet_gcroot`'d so the collector keeps it
alive until the callable's last reference drops (`janetScriptRelease` unroots it, on the owner thread).
A value coming back that is already one of our `gForeignType` abstracts is unwrapped straight to its
`CalogFnT` by pointer-identity on the type, so a callback round-trips with no wrapper layer -- reaching
`psSubscribe`/`timerAfter`/`calogExport` like the rest. The error model is fiber-native: a failed native
or marshal calls `janet_panic` (a longjmp back to the nearest fiber), and because it never returns each
handler frees the heap `CalogValueT` arguments it owns and copies any message into a stack buffer before
panicking. Janet collects only at VM instruction boundaries, so C-side marshalling never races the GC;
the one value that must outlive a VM call -- a function crossing out -- is the one explicitly rooted.
**The honest limit is bare-name exports.** Janet is not a hook engine: it resolves symbols at compile
time and offers no runtime unbound-symbol hook, unlike Tcl's `unknown`, s7's `*unbound-variable-hook*`,
Ruby's `method_missing`, or the fork my-basic needed. Natives -- and any exports already registered when
the VM opens -- are installed as real `janet_def` bindings and so ARE callable bare; but a function
exported by another engine AFTER this VM opened cannot be reached by bare name, and is called through the
`calogCall` native (`(calogCall "name" ...args)`) instead.
**Verified**: `testEngineJanet` (14 checks -- a host native receiving its argument, a default native on
the host thread versus an inline native on the script's own thread, int and binary-safe string fields
read both from a materialized record table and from a table the script built, a foreign function value
invoked from the script, a Janet function captured as a `CalogFnT` and invoked cross-thread back to its
owner, the error handler naming the failing context, and three concurrent thread-local VMs all
dispatching to the host), ASan-clean; `tsanjanet` clean; cross-engine both ways; the full `make test`
suite (38/38) passes with Janet included.
## 22. my-basic binary data -- the byte-buffer type (closing the last string limit)
my-basic was the one engine whose strings could not carry a calog string with embedded NUL bytes: its
`MB_DT_STRING` is a NUL-terminated C `char*` with no length, and section 2.5 recorded the loss as "embedded
NUL truncates". Rather than rewrite the ~19.6k-line vendored interpreter's string, pool, comparison, and
UTF-8 builtin paths -- a high-risk change whose only clean form is full, and which would force a
bytes-vs-codepoints decision on `LEN`/`MID` -- calog adds a distinct **byte-buffer type** and leaves the
string path untouched. Binary data and text are different things (the Python-3 `str`/`bytes` split), and
my-basic's own extension protocol makes the byte type almost entirely an adapter concern.
The type is an `MB_DT_USERTYPE_REF` (the refcounted usertype-ref my-basic already offers, enabled in this
build) whose payload is `{ uint8_t* data; size_t length; }` -- length-carrying and NUL-safe. The VM
already dispatches the ref's hooks, so the whole type lives in `mybasicAdapter.c`: a dtor frees the buffer,
a clone deep-copies it, a hash (FNV-1a) and a cmp (memcmp plus a length tiebreak) make it a
content-addressed dict key, a fmt renders `bytes[N]` for `PRINT`, and an `MB_MF_ADD` meta-operator override
makes `+` concatenate byte buffers -- and `string + bytes`, since `_core_add` consults the meta-operator
before its string path. `_clone_usertype_ref` copies the operator table, so `+` survives assignment. The
calog boundary picks the representation by content: a calog string that contains an embedded NUL ingresses
as a byte buffer (a NUL-bearing blob is not a valid my-basic C-string); a NUL-free string stays an ordinary
my-basic string, so existing text scripts are wholly unaffected. On egress a byte buffer becomes a
length-carrying calog string, faithful to the last byte. Scripts get `byteLen`, `byteAt`, `byteSlice`,
`byteConcat` (which also accepts strings, for building a response from text headers and a binary body),
and `strToByte`/`byteToStr`; `+` concatenation and `=`/`<>` content comparison work as operators.
One vendored change was required -- a fourth, small and guarded, patch to the calog fork.
`_instruct_obj_op_obj`, the operator behind `=` `<>` `<` `>` `<=` `>=`, compared two same-type values by
their raw representation (pointer identity for a usertype-ref). It now routes through a helper that uses
the ref's cmp hook when one is present and falls back to the historical raw compare otherwise, so byte
buffers compare by content while every other type is unchanged (a NULL cmp -- e.g. a foreign-fn ref --
keeps identity). The adapter also gained two ownership fixes, both surfaced by ASan and an adversarial
review. First, a popped usertype-ref handed to a native was not released by the marshalling argument loop
(only `LIST`/`DICT` were), so any byte buffer -- or foreign callable -- passed as a native argument
leaked; the loop now disposes usertype-refs too. Second, the byte natives disposed a popped argument
unconditionally on their type-error paths -- but a popped my-basic string is a *borrowed* interior
pointer (only a collection, routine, or usertype-ref is an owned reference), so `byteLen("abc")` would
double-free it; a single `mbDisposePopped` helper now encodes the own-vs-borrow rule at every site. The
byte payload carries a tag so egress and the compare hook can tell a byte buffer from the adapter's other
usertype-ref (a foreign callable) and never misread one as the other.
**Verified**: `testEngineMyBasic` grew from 8 to 20 checks -- egress of a 3-byte `a\0b` blob at full
length, `byteLen`/`byteAt` reading the embedded NUL, `+` concatenation preserving an interior NUL, a text
prefix concatenated with a byte body, `=` comparing byte buffers by content (equal, and differing only
past the NUL), a byte buffer used as a content-addressed dict key, `byteSlice` across the NUL, `byteConcat`
joining buffers and strings, an egress/ingress round-trip through a native, and a `strToByte`/`byteToStr`
text round-trip -- ASan+UBSan-clean, `tsanmb` clean, and the byte-native type-error paths driven under
ASan (no double-free of a borrowed string, no leak of a refused collection). `make test` green.
## 23. my-basic sandbox parity -- memory cap, time budget, and INPUT
Section 2.5 and `testSandbox` recorded that per-context memory and wall-clock limits applied to Lua and
QuickJS only -- their allocators/interrupts take per-state userdata, while the other engines (my-basic
included) got only the engine-agnostic native allow-list. Since the fork is calog's own, my-basic now
enforces all three, entirely in the adapter plus one VM typedef.
**Time budget.** my-basic calls a per-statement hook (`_prev_stepped`) before every statement; the
adapter installs one (`mb_debug_set_stepped_handler`) for a limited context. The hook checks the
wall-clock deadline (every `MB_STEP_TIME_CHECK` statements, to amortise the clock read) and, once past it,
calls `calogCurrentRetire()` and returns an error that unwinds the run -- the my-basic analogue of Lua's
instruction-count `luaTimeHook`. Unlimited contexts install no hook, so the common path keeps its
per-statement cost at zero.
**Memory cap.** This one needed care. my-basic's memory manager (`mb_set_memory_manager`) is
process-global and receives only a size, and `mb_malloc` `mb_assert`s that allocation never fails -- so an
allocator that returned NULL on over-budget would crash, not error (the very reason memory caps were
"Lua/QuickJS only"). Instead the adapter wraps the global allocator with a COUNTING one that never
refuses: every allocation carries a small header recording its size and owning context, charged through a
thread-local pointer to the running context's limit state (each context runs create/run/destroy on one
dedicated thread, so the thread-local is exact and `memUsed` needs no atomics), and the cap is enforced at
the next statement boundary by the same step hook. The bound is therefore statement-granular -- a single
statement can transiently overshoot before the next boundary retires the context -- rather than Lua's
exact-allocation, but the outcome is the same: an over-budget context is retired with its error at the
handler. The allocator is installed once, before the first `mb_init`, so its header is present on every
my-basic allocation uniformly (an unlimited context is simply never charged).
**A latent crash fixed on the way (fork patch #5).** The alloc-stat size tag `mb_mem_tag_t` was
`unsigned short`, and `mb_malloc` returns NULL -- which the caller then dereferences -- for any size that
does not fit the tag. A single allocation over 65535 bytes (a >64 KB string or array) crashed the host: a
sandbox escape worse than any missing limit, and independent of the new cap. Widening the tag to 64-bit
removes it -- together with the memory-manager callback's size parameter, which was still `unsigned`: an
adversarial review caught that the tag widening alone was incomplete, since `mb_malloc` passes `size +
tag` through that `unsigned` param, so a >4 GiB request would truncate into a tiny buffer the caller then
filled at full size (a heap overflow instead of the old NULL-crash). The memory-cap test (doubling a
string past 2 MiB) drives this path.
**INPUT.** my-basic's `INPUT` fell back to `mb_gets` -> `fgets(stdin)`, which would block the context
thread and read host input. The adapter installs an inputer that yields an empty line, so I/O stays on
calog's natives like every other engine.
**Verified**: `testSandbox` gained the my-basic trio alongside the existing Lua/JS cases -- the allow-list
denies a forbidden native, a runaway `WHILE 1` loop is retired on its wall-clock budget, and a string
doubling past a 2 MiB cap is retired -- ASan+UBSan-clean, `tsanmb` clean, and driven end-to-end through
`bin/calog` (INPUT yields empty without reading stdin; a 160 KB string builds without crashing).

316
examples/httpd.lua Normal file
View file

@ -0,0 +1,316 @@
-- httpd.lua -- a polyglot-server-in-a-script for calog. The whole HTTP/1.1 protocol -- request
-- parsing, routing, keep-alive, response shaping, and the WebSocket upgrade + frame codec -- lives
-- here in Lua. The only native surface it stands on is calog's generic TCP transport (tcpListen /
-- tcpAccept / tcpRecv / tcpSend / tcpClose from calogNet) and two crypto helpers (cryptoHashSha1 +
-- cryptoBase64Encode + cryptoHexDecode from calogCrypto) for the WebSocket handshake. There is no C
-- HTTP code behind it; this is the calog thesis -- systems primitives in C, protocol in a script.
--
-- Concurrency follows the actor model: one context running server:serve(port) handles connections
-- serially (fine for control/webhook endpoints). For parallelism, share the listener handle across
-- several contexts, each calling tcpAccept on it -- the OS load-balances. Binary-safe strings are
-- required (WebSocket masks and binary bodies carry NUL bytes), so this runs on any calog engine
-- except my-basic, whose char*/strlen strings truncate at the first NUL.
--
-- Usage:
-- local s = httpd.new()
-- s:route("GET", "/hi", function(req) return "hello " .. req.path end)
-- s:route("GET", "/made", function(req) return { status = 201, body = "created" } end)
-- s:websocket("/ws", function(msg) return "echo: " .. msg.message end) -- returns a text reply or nil
-- s:serve(8080) -- opts: { keep = fn, acceptTimeout = ms }
local httpd = {}
httpd.__index = httpd
local WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
local HEADER_MAX = 64 * 1024
local BODY_MAX = 16 * 1024 * 1024
local RECV_CHUNK = 8192
local ACCEPT_POLL = 200 -- ms; the accept loop wakes this often to re-check its keep predicate
local REASON = {
[101] = "Switching Protocols", [200] = "OK", [201] = "Created", [204] = "No Content",
[301] = "Moved Permanently", [302] = "Found", [400] = "Bad Request", [401] = "Unauthorized",
[403] = "Forbidden", [404] = "Not Found", [405] = "Method Not Allowed", [500] = "Internal Server Error",
}
function httpd.new()
return setmetatable({ routes = {}, wsRoutes = {} }, httpd)
end
function httpd:route(method, path, handler)
self.routes[method:upper() .. " " .. path] = handler
end
function httpd:websocket(path, handler)
self.wsRoutes[path] = handler
end
-- Read one request off the connection, starting from any bytes `buf` already holds from a previous
-- (pipelined) read. Returns (request, leftoverBuffer) or nil on close/malformed. request is
-- { method, path, query, version, headers, body }; leftoverBuffer feeds the next keep-alive read.
local function readRequest(conn, buf)
buf = buf or ""
local headerEnd = nil
while true do
headerEnd = buf:find("\r\n\r\n", 1, true)
if headerEnd then break end
if #buf > HEADER_MAX then return nil end
local chunk = tcpRecv(conn, RECV_CHUNK)
if not chunk then return nil end
buf = buf .. chunk
end
local head = buf:sub(1, headerEnd - 1)
local rest = buf:sub(headerEnd + 4)
local method, target, version = head:match("^(%S+)%s+(%S+)%s+(%S+)")
if not method then return nil end
local path, query = target:match("^([^?]*)%??(.*)$")
local headers = {}
for line in head:gmatch("[^\r\n]+") do
local k, v = line:match("^([^:]+):%s*(.-)%s*$")
if k then
k = k:lower()
-- A conflicting duplicate Content-Length is a CL.CL smuggling vector: reject it.
if k == "content-length" and headers[k] and headers[k] ~= v then return nil end
headers[k] = v
end
end
-- We frame bodies by Content-Length only. A Transfer-Encoding (chunked) request would desync with
-- the leftover-buffer keep-alive path (a TE.CL smuggle), so reject it rather than mis-frame it.
if headers["transfer-encoding"] then return nil end
-- Content-Length is DIGIT-only per RFC 7230; reject the hex/scientific/signed/float forms tonumber
-- would otherwise accept (a body-length desync / smuggling risk), and reject anything over the cap.
local clen = 0
local clRaw = headers["content-length"]
if clRaw then
if not clRaw:match("^%d+$") then return nil end
local n = tonumber(clRaw)
if not n or n > BODY_MAX then return nil end
clen = math.tointeger(n) or 0
end
while #rest < clen do
local chunk = tcpRecv(conn, RECV_CHUNK)
if not chunk then return nil end
rest = rest .. chunk
end
local req = {
method = method:upper(), path = path, query = query, version = version,
headers = headers, body = rest:sub(1, clen),
}
return req, rest:sub(clen + 1) -- leftover: bytes of the next pipelined request, if any
end
-- HTTP/1.1 keeps the connection alive unless "Connection: close"; HTTP/1.0 closes unless keep-alive.
local function wantsKeepAlive(req)
local conn = (req.headers["connection"] or ""):lower()
if req.version == "HTTP/1.1" then
return not conn:find("close", 1, true)
end
return conn:find("keep%-alive") ~= nil
end
-- Turn a handler's return value into an HTTP response and send it. nil -> 204; a string -> 200 with
-- that body; a table -> { status = 200, headers = {}, body = "" }.
local function writeResponse(conn, resp, keepAlive)
local status, body, extra = 200, "", nil
if resp == nil then
status = 204
elseif type(resp) == "string" then
body = resp
elseif type(resp) == "table" then
status = resp.status or 200
body = resp.body or ""
extra = resp.headers
end
local out = { string.format("HTTP/1.1 %d %s\r\nContent-Length: %d\r\nConnection: %s\r\n",
status, REASON[status] or "Status", #body, keepAlive and "keep-alive" or "close") }
if extra then
for k, v in pairs(extra) do out[#out + 1] = k .. ": " .. v .. "\r\n" end
end
out[#out + 1] = "\r\n"
out[#out + 1] = body
tcpSend(conn, table.concat(out)) -- raises on a broken connection; httpd:handle's pcall catches it
end
-- ---- WebSocket (RFC 6455) --------------------------------------------------------------------
-- Sec-WebSocket-Accept = base64(SHA1(clientKey + magic GUID)). cryptoHashSha1 returns hex, so decode
-- it back to the 20 raw bytes before base64 -- all via calog's crypto natives, no C here.
local function wsHandshake(conn, req)
local key = req.headers["sec-websocket-key"]
if not key then return false end
local accept = cryptoBase64Encode(cryptoHexDecode(cryptoHashSha1(key .. WS_MAGIC)))
local resp = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\n" ..
"Connection: Upgrade\r\nSec-WebSocket-Accept: " .. accept .. "\r\n\r\n"
tcpSend(conn, resp)
return true
end
-- Write one unmasked server frame (FIN set); server-to-client frames are never masked.
local function wsFrame(conn, opcode, payload)
local len = #payload
local header
if len < 126 then
header = string.char(0x80 | opcode, len)
elseif len < 65536 then
header = string.char(0x80 | opcode, 126, (len >> 8) & 0xFF, len & 0xFF)
else
local b = { 0x80 | opcode, 127 }
for i = 7, 0, -1 do b[#b + 1] = (len >> (i * 8)) & 0xFF end
header = string.char(table.unpack(b))
end
tcpSend(conn, header .. payload)
end
-- The message loop: read masked client frames, REASSEMBLE fragmented messages (a data frame with
-- FIN=0 followed by continuation frames, opcode 0x0, until FIN=1), hand each complete text/binary
-- message to the handler as { path, message }, send back any string it returns, answer pings, and
-- stop on close/error. `buf` seeds it with any bytes already read past the upgrade request.
local function wsServe(conn, path, handler, buf)
buf = buf or ""
local function need(n)
while #buf < n do
local chunk = tcpRecv(conn, RECV_CHUNK)
if not chunk then return false end
buf = buf .. chunk
end
return true
end
local msg = nil -- accumulated payload of an in-progress fragmented message
local msgOp = nil -- opcode (0x1 text / 0x2 binary) of that message's first frame
while true do
if not need(2) then return end
local b1, b2 = buf:byte(1), buf:byte(2)
local fin = (b1 & 0x80) ~= 0
local opcode = b1 & 0x0F
local masked = (b2 & 0x80) ~= 0
local len = b2 & 0x7F
local pos = 3
if len == 126 then
if not need(4) then return end
len = (buf:byte(3) << 8) | buf:byte(4)
pos = 5
elseif len == 127 then
if not need(10) then return end
len = 0
for i = 3, 10 do len = (len << 8) | buf:byte(i) end
pos = 11
end
-- len is a signed Lua integer: a 64-bit length with bit 63 set reads NEGATIVE, so guard both
-- ends before trusting it (else it slips past the cap and spins the loop). Client frames MUST
-- be masked.
if not masked or len < 0 or len > BODY_MAX then return end
if not need(pos + 3) then return end
local mask = { buf:byte(pos), buf:byte(pos + 1), buf:byte(pos + 2), buf:byte(pos + 3) }
pos = pos + 4
if not need(pos + len - 1) then return end
local raw = buf:sub(pos, pos + len - 1)
buf = buf:sub(pos + len)
local bytes = {}
for i = 1, len do bytes[i] = string.char(raw:byte(i) ~ mask[((i - 1) & 3) + 1]) end
local payload = table.concat(bytes)
if opcode == 0x8 then
wsFrame(conn, 0x8, "")
return
elseif opcode == 0x9 then
wsFrame(conn, 0xA, payload) -- ping -> pong (control frames may interleave)
elseif opcode == 0xA then
-- pong: ignore
elseif opcode == 0x0 or opcode == 0x1 or opcode == 0x2 then
if opcode == 0x0 then
if not msgOp then return end -- continuation with no start frame: protocol error
msg = msg .. payload
else
if msgOp then return end -- new data frame mid-message: protocol error
msgOp = opcode
msg = payload
end
if #msg > BODY_MAX then return end
if fin then
local ok, reply = pcall(handler, { path = path, message = msg })
if ok and type(reply) == "string" then
wsFrame(conn, 0x1, reply)
end
msg, msgOp = nil, nil
end
else
return -- unknown opcode: fail the connection
end
end
end
-- Serve one connection to completion, then ALWAYS close it. The whole per-connection loop runs under
-- pcall: calogNet raises a Lua error on a socket failure (it does not return nil), so a client that
-- drops mid-request tears down only this connection, never the accept loop. A route handler that
-- errors becomes a 500; a WebSocket handler that errors just drops that reply (see wsServe).
function httpd:handle(conn)
pcall(function()
local buf = ""
while true do
local req
req, buf = readRequest(conn, buf)
if not req then break end
local upgrade = (req.headers["upgrade"] or ""):lower():find("websocket", 1, true)
local connhdr = (req.headers["connection"] or ""):lower():find("upgrade", 1, true)
if upgrade and connhdr then
local handler = self.wsRoutes[req.path]
if handler and wsHandshake(conn, req) then
wsServe(conn, req.path, handler, buf) -- hand over any bytes past the upgrade request
end
return -- the connection is now a (closed) WebSocket, not keep-alive HTTP
end
local keepAlive = wantsKeepAlive(req)
local handler = self.routes[req.method .. " " .. req.path] or self.routes["* " .. req.path]
if handler then
local ok, resp = pcall(handler, req)
if ok then
writeResponse(conn, resp, keepAlive)
else
writeResponse(conn, { status = 500, body = "Internal Server Error" }, keepAlive)
end
else
writeResponse(conn, { status = 404, body = "Not Found" }, keepAlive)
end
if not keepAlive then break end
end
end)
tcpClose(conn)
end
-- Bind `port` and serve until opts.keep() (if given) returns false. opts.acceptTimeout bounds each
-- accept so the keep predicate is polled even with no traffic.
function httpd:serve(port, opts)
opts = opts or {}
-- TLS is opt-in via opts.tls + opts.cert + opts.key; pass a clean map to the transport (never the
-- whole opts table, which may carry Lua function fields like keep/onReady that are not map values).
local srv
if opts.tls then
srv = tcpListen(port, { tls = true, cert = opts.cert, key = opts.key })
else
srv = tcpListen(port)
end
if not srv then return false end
if opts.onReady then opts.onReady() end
local keep = opts.keep
local timeout = opts.acceptTimeout or ACCEPT_POLL
while (keep == nil) or keep() do
-- tcpAccept RAISES on a failed/timed-out TLS handshake (a bad ClientHello, a health-check probe,
-- a stalled client). Catch it here so one bad connection cannot terminate the accept loop.
local ok, conn = pcall(tcpAccept, srv, timeout)
if ok and conn then self:handle(conn) end
end
tcpClose(srv)
return true
end
return httpd

View file

@ -35,7 +35,4 @@
// Register the archive/compression natives on a runtime. Idempotent across runtimes.
int32_t calogArchiveRegister(CalogT *calog);
// Release the shared handle table once the last registered runtime unregisters.
void calogArchiveShutdown(void);
#endif

View file

@ -34,8 +34,4 @@
// process-wide connection registry). Returns calogOkE or an error.
int32_t calogDbRegister(CalogT *calog);
// Close any still-open connections and free the process-wide DB registry. Call it AFTER
// the runtime is torn down (calogDestroy), since it invalidates the natives' state.
void calogDbShutdown(void);
#endif

View file

@ -35,12 +35,4 @@
// Register the export natives on a runtime. Idempotent across runtimes (shared registry).
int32_t calogExportRegister(CalogT *calog);
// Release every exported function (once the last registered runtime unregisters). Unlike the
// other libraries, call this while the exporting contexts are still ALIVE -- before you close
// them and before calogDestroy -- because an exported function is a live reference into its
// owner's interpreter; releasing it after that context is gone would touch freed memory. The
// static registry bookkeeping itself is intentionally never freed (the natives stay callable
// until calogDestroy), so this is safe to call and re-register across runtimes.
void calogExportShutdown(void);
#endif

View file

@ -18,6 +18,27 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef _WIN32
#include <io.h> // _chmod (clear the read-only bit before deleting)
#endif
// Windows (mingw/MSVCRT) opens files in TEXT mode by default -- read() stops at Ctrl-Z and CRLF<->LF
// translation corrupts data. O_BINARY forces byte-for-byte I/O there; it is 0 (no-op) on POSIX, so
// the file natives are binary-safe on all three ports.
#ifndef O_BINARY
#define O_BINARY 0
#endif
// mingw's `struct stat` carries a 32-bit st_size (LLP64), so a file >= 2 GiB reports a truncated
// size; the _stat64 variant has a 64-bit st_size. POSIX stat is already 64-bit on the supported
// targets, so this only redirects the Windows build.
#ifdef _WIN32
typedef struct _stat64 CalogStatT;
#define calogStat _stat64
#else
typedef struct stat CalogStatT;
#define calogStat stat
#endif
// fsRead's starting buffer capacity when fstat's st_size reports 0 (procfs/sysfs/FIFO-style
// files, whose content is read regardless by growing this buffer to EOF).
@ -168,7 +189,12 @@ static int32_t fsMkdirNative(CalogValueT *args, int32_t argCount, CalogValueT *r
return calogFail(result, calogErrArgE, "fsMkdir expects (path)");
}
path = args[0].as.s.bytes;
#ifdef _WIN32
// The Win32 CRT mkdir takes no mode argument (the mode is meaningless on Windows ACLs).
if (mkdir(path) != 0) {
#else
if (mkdir(path, 0777) != 0) {
#endif
// An already-existing directory is success; anything else (including a non-directory
// squatting on the name) is the failure the caller sees.
if (errno == EEXIST && stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
@ -189,7 +215,7 @@ static int32_t fsPutFile(const char *path, const char *bytes, int64_t length, bo
int flags;
int saved;
flags = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC);
flags = O_WRONLY | O_CREAT | O_BINARY | (append ? O_APPEND : O_TRUNC);
fd = open(path, flags, 0666);
if (fd < 0) {
return fsFail(result, errno);
@ -236,7 +262,7 @@ static int32_t fsReadNative(CalogValueT *args, int32_t argCount, CalogValueT *re
return calogFail(result, calogErrArgE, "fsRead expects (path)");
}
path = args[0].as.s.bytes;
fd = open(path, O_RDONLY);
fd = open(path, O_RDONLY | O_BINARY);
if (fd < 0) {
return fsFail(result, errno);
}
@ -297,12 +323,19 @@ static int32_t fsRemoveNative(CalogValueT *args, int32_t argCount, CalogValueT *
if (argCount != 1 || args[0].type != calogStringE) {
return calogFail(result, calogErrArgE, "fsRemove expects (path)");
}
// unlink refuses a directory (EISDIR on Linux, EPERM elsewhere); remove an empty
// directory with rmdir instead, so fsRemove deletes either a file or an empty directory.
// unlink refuses a directory (EISDIR on Linux, EPERM on some POSIX, EACCES on Windows); remove an
// empty directory with rmdir instead, so fsRemove deletes either a file or an empty directory.
if (unlink(args[0].as.s.bytes) == 0) {
return calogOkE;
}
if ((errno == EISDIR || errno == EPERM) && rmdir(args[0].as.s.bytes) == 0) {
#ifdef _WIN32
// Windows _unlink refuses a read-only file with EACCES; clear the read-only bit and retry (an
// EACCES from another open handle is a genuine OS limitation and still fails below).
if (errno == EACCES && _chmod(args[0].as.s.bytes, _S_IWRITE) == 0 && unlink(args[0].as.s.bytes) == 0) {
return calogOkE;
}
#endif
if ((errno == EISDIR || errno == EPERM || errno == EACCES) && rmdir(args[0].as.s.bytes) == 0) {
return calogOkE;
}
return fsFail(result, errno);
@ -310,7 +343,7 @@ static int32_t fsRemoveNative(CalogValueT *args, int32_t argCount, CalogValueT *
static int32_t fsStatNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
struct stat st;
CalogStatT st;
CalogValueT value;
CalogAggT *agg;
const char *path;
@ -322,7 +355,7 @@ static int32_t fsStatNative(CalogValueT *args, int32_t argCount, CalogValueT *re
return calogFail(result, calogErrArgE, "fsStat expects (path)");
}
path = args[0].as.s.bytes;
if (stat(path, &st) != 0) {
if (calogStat(path, &st) != 0) {
// A missing path is not an error: report nil so callers can probe with fsStat.
if (errno == ENOENT) {
return calogOkE;

View file

@ -21,9 +21,31 @@
#include <strings.h>
#include <sys/time.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
// Native trust-store access is platform specific. Windows reads the system certificate stores via
// wincrypt.h, whose macros collide with OpenSSL's X.509 API, so they are undefined right after
// inclusion. macOS (only when built against the Apple SDK) reads the keychain via the Security
// framework. Every POSIX target also probes the well-known CA-bundle files, which needs unistd.h.
#if defined(_WIN32)
#include <windows.h>
#include <wincrypt.h>
#undef X509_NAME
#undef X509_EXTENSIONS
#undef PKCS7_ISSUER_AND_SERIAL
#undef PKCS7_SIGNER_INFO
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#else
#include <unistd.h>
#if defined(__APPLE__) && defined(CALOG_MAC_KEYCHAIN_TRUST)
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#endif
#endif
#define HTTP_BUF_INITIAL 256
#define HTTP_READ_CHUNK 16384
#define HTTP_MAX_RESPONSE (64 * 1024 * 1024)
@ -92,6 +114,16 @@ static bool httpHasBadByte(const char *bytes, size_t length, bool rejectSpace
static bool httpHeaderIn(const char *name, int64_t len, const char *const *list);
static int32_t httpHexVal(char c);
static bool httpIsRedirect(int32_t code);
#if defined(__APPLE__) && defined(CALOG_MAC_KEYCHAIN_TRUST)
static bool httpLoadMacRoots(SSL_CTX *ctx);
#endif
#ifndef _WIN32
static bool httpLoadProbedPaths(SSL_CTX *ctx);
#endif
static bool httpLoadTrustStore(SSL_CTX *ctx);
#if defined(_WIN32)
static bool httpLoadWindowsRoots(SSL_CTX *ctx);
#endif
static char httpLower(char c);
static int32_t httpMapSetAgg(CalogAggT *map, const char *key, CalogAggT *inner);
static bool httpMethodHasBody(const char *method);
@ -112,11 +144,18 @@ static int httpWriteAuthority(char *out, size_t cap, const char *scheme, con
int32_t calogHttpRegister(CalogT *calog) {
// Winsock must be started before socket()/getaddrinfo() on Windows. calogNet/calogSsh also do
// this, but an embedder that registers ONLY http would otherwise have no working sockets there
// (WSAStartup reference-counts, so calling it from every ...Register is safe).
calogPlatformNetInit();
// SSL_write/SSL_shutdown (and plain send()) can hit a broken pipe if the peer resets or
// closes mid-write; with the default disposition that raises SIGPIPE and kills the whole
// broker process. The plain path already passes MSG_NOSIGNAL, but OpenSSL's socket BIO does
// not, so ignore SIGPIPE process-wide (idempotent; safe to call from every ...Register).
#ifndef _WIN32
// Windows has no SIGPIPE (a reset/closed peer surfaces as a normal send/recv error instead).
signal(SIGPIPE, SIG_IGN);
#endif
calogRegisterInline(calog, "httpGet", httpGetNative, NULL);
calogRegisterInline(calog, "httpRequest", httpRequestNative, NULL);
return calogOkE;
@ -424,7 +463,9 @@ static bool httpConnectTimeout(CalogSocketT fd, const struct sockaddr *addr, soc
int rc;
int err;
socklen_t errLen;
#ifndef _WIN32
struct pollfd pfd;
#endif
// A fresh socket starts blocking; drive a bounded connect by flipping it non-blocking, polling
// for writability, then restoring blocking mode (portable across BSD sockets and Winsock).
@ -440,6 +481,27 @@ static bool httpConnectTimeout(CalogSocketT fd, const struct sockaddr *addr, soc
calogSockSetNonblock(fd, 0);
return false;
}
#ifdef _WIN32
// WSAPoll does not reliably report a FAILED connect on older Windows (it can block for the full
// timeout), so use select(), whose exceptfds set flags a refused/unreachable peer immediately.
{
fd_set writeSet;
fd_set exceptSet;
struct timeval tv;
FD_ZERO(&writeSet);
FD_ZERO(&exceptSet);
FD_SET(fd, &writeSet);
FD_SET(fd, &exceptSet);
tv.tv_sec = timeoutSec;
tv.tv_usec = 0;
rc = select(0, NULL, &writeSet, &exceptSet, &tv);
if (rc <= 0 || FD_ISSET(fd, &exceptSet)) {
calogSockSetNonblock(fd, 0);
return false; // timeout, error, or connect refused/unreachable
}
}
#else
pfd.fd = fd;
pfd.events = POLLOUT;
rc = calogPoll(&pfd, 1, timeoutSec * 1000);
@ -447,6 +509,7 @@ static bool httpConnectTimeout(CalogSocketT fd, const struct sockaddr *addr, soc
calogSockSetNonblock(fd, 0);
return false; // timeout or poll error
}
#endif
errLen = sizeof(err);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errLen) < 0 || err != 0) {
calogSockSetNonblock(fd, 0);
@ -907,6 +970,209 @@ static bool httpIsRedirect(int32_t code) {
}
#if defined(__APPLE__) && defined(CALOG_MAC_KEYCHAIN_TRUST)
// Load trust anchors from the macOS system keychain via the Security framework. This needs the
// Apple SDK, so calog's SDK-less cross build leaves it disabled (falling back to
// httpLoadProbedPaths); it is compiled only for a native macOS build made with
// -DCALOG_MAC_KEYCHAIN_TRUST that links -framework Security -framework CoreFoundation. Each anchor
// is DER, so it round-trips through OpenSSL's d2i_X509 into ctx's store. Returns true once any
// anchor was added.
static bool httpLoadMacRoots(SSL_CTX *ctx) {
X509_STORE *store;
CFArrayRef anchors;
CFIndex count;
CFIndex i;
bool loaded;
store = SSL_CTX_get_cert_store(ctx);
anchors = NULL;
loaded = false;
if (SecTrustCopyAnchorCertificates(&anchors) != errSecSuccess || anchors == NULL) {
return false;
}
count = CFArrayGetCount(anchors);
for (i = 0; i < count; i++) {
SecCertificateRef cert;
CFDataRef der;
const unsigned char *bytes;
X509 *x;
cert = (SecCertificateRef)CFArrayGetValueAtIndex(anchors, i);
if (cert == NULL) {
continue;
}
der = SecCertificateCopyData(cert);
if (der == NULL) {
continue;
}
bytes = CFDataGetBytePtr(der);
x = d2i_X509(NULL, &bytes, (long)CFDataGetLength(der));
CFRelease(der);
if (x == NULL) {
continue;
}
if (X509_STORE_add_cert(store, x) == 1) {
loaded = true;
}
X509_free(x);
}
CFRelease(anchors);
ERR_clear_error();
return loaded;
}
#endif
#ifndef _WIN32
// Load trust anchors from the CA-bundle files (and hash directories) that Linux distributions and
// the BSDs/macOS ship. The well-known locations are tried in order -- one bundle is enough (a
// system generally has exactly one), but a hash directory is also tried for distros that ship only
// that. access() gates each try so a missing path does not push errors onto OpenSSL's error queue.
// Returns true once any anchors loaded. (CALOG_CA_BUNDLE / SSL_CERT_* overrides are handled, ahead
// of this probe, by httpLoadTrustStore.)
static bool httpLoadProbedPaths(SSL_CTX *ctx) {
static const char *const files[] = {
"/etc/ssl/certs/ca-certificates.crt", // Debian, Ubuntu, Arch, Gentoo, Alpine
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora, RHEL, CentOS
"/etc/ssl/ca-bundle.pem", // openSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/ssl/cert.pem", // Alpine, macOS, OpenBSD, FreeBSD
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD (ports)
"/etc/openssl/certs/ca-certificates.crt" // NetBSD
};
static const char *const dirs[] = {
"/etc/ssl/certs", // Debian, openSUSE (hashed)
"/etc/pki/tls/certs", // Fedora, RHEL
"/system/etc/security/cacerts" // Android
};
size_t i;
bool loaded;
loaded = false;
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
if (access(files[i], R_OK) != 0) {
continue;
}
if (SSL_CTX_load_verify_locations(ctx, files[i], NULL) == 1) {
loaded = true;
break;
}
}
if (!loaded) {
for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) {
if (access(dirs[i], R_OK) != 0) {
continue;
}
if (SSL_CTX_load_verify_locations(ctx, NULL, dirs[i]) == 1) {
loaded = true;
break;
}
}
}
return loaded;
}
#endif
// Populate ctx's certificate trust store from the host's native trust configuration. OpenSSL's
// compiled-in default paths point at the vendored build prefix, which does not exist at runtime, so
// they resolve nothing on their own; instead we consult the operating system directly (the Windows
// system stores, the macOS keychain, or the distro CA-bundle files). Overrides are honored ahead of
// the native store, in priority order: CALOG_CA_BUNDLE (authoritative and EXCLUSIVE -- trust only
// that bundle, so it can pin to a private CA, and fail closed if it cannot be loaded), then the
// OpenSSL-standard SSL_CERT_FILE / SSL_CERT_DIR. Returns true only if a trust source actually
// loaded (not merely because an override variable is set), so a verified request can fail with a
// clear "no trust store" error instead of a misleading per-certificate verification failure.
static bool httpLoadTrustStore(SSL_CTX *ctx) {
const char *pin;
const char *envFile;
const char *envDir;
bool loaded;
// A pinned bundle is authoritative and exclusive: trust EXACTLY it and nothing else, and fail
// closed if it will not load. Load return value (not mere presence of the variable) decides.
pin = getenv("CALOG_CA_BUNDLE");
if (pin != NULL) {
return SSL_CTX_load_verify_locations(ctx, pin, NULL) == 1;
}
loaded = false;
// The OpenSSL-standard operator override. Load each explicitly and confirm it, so a broken
// SSL_CERT_FILE still surfaces the clear no-trust-store error rather than counting as loaded.
// (An SSL_CERT_DIR is a lazy hash-dir lookup, so its load cannot be pre-confirmed; a set-but-
// empty directory is the one case that still counts as loaded, which is fail-closed-safe.)
envFile = getenv("SSL_CERT_FILE");
if (envFile != NULL && SSL_CTX_load_verify_locations(ctx, envFile, NULL) == 1) {
loaded = true;
}
envDir = getenv("SSL_CERT_DIR");
if (envDir != NULL && SSL_CTX_load_verify_locations(ctx, NULL, envDir) == 1) {
loaded = true;
}
#if defined(_WIN32)
if (httpLoadWindowsRoots(ctx)) {
loaded = true;
}
#else
#if defined(__APPLE__) && defined(CALOG_MAC_KEYCHAIN_TRUST)
if (httpLoadMacRoots(ctx)) {
loaded = true;
}
#endif
if (httpLoadProbedPaths(ctx)) {
loaded = true;
}
#endif
return loaded;
}
#if defined(_WIN32)
// Load trust anchors from the Windows system certificate stores (ROOT = trusted roots, CA =
// intermediates). Each store certificate is DER, so it round-trips through OpenSSL's d2i_X509 into
// ctx's store. Returns true once any certificate was added.
static bool httpLoadWindowsRoots(SSL_CTX *ctx) {
static const char *const stores[] = { "ROOT", "CA" };
X509_STORE *store;
size_t i;
bool loaded;
store = SSL_CTX_get_cert_store(ctx);
loaded = false;
for (i = 0; i < sizeof(stores) / sizeof(stores[0]); i++) {
HCERTSTORE sys;
PCCERT_CONTEXT cert;
sys = CertOpenSystemStoreA(0, stores[i]);
if (sys == NULL) {
continue;
}
cert = NULL;
while ((cert = CertEnumCertificatesInStore(sys, cert)) != NULL) {
const unsigned char *der;
X509 *x;
der = cert->pbCertEncoded;
x = d2i_X509(NULL, &der, (long)cert->cbCertEncoded);
if (x == NULL) {
continue;
}
// X509_STORE_add_cert up-refs on success, so our reference is always released here; a
// duplicate (already present) returns 0 and is simply skipped.
if (X509_STORE_add_cert(store, x) == 1) {
loaded = true;
}
X509_free(x);
}
CertCloseStore(sys, 0);
}
// Duplicate-add attempts push errors onto OpenSSL's thread-local queue; clear them so a later
// SSL_get_error is not misled by this bookkeeping.
ERR_clear_error();
return loaded;
}
#endif
static char httpLower(char c) {
if (c >= 'A' && c <= 'Z') {
return (char)(c - 'A' + 'a');
@ -1470,12 +1736,14 @@ static int32_t httpTlsHandshake(HttpConnT *conn, const HttpUrlT *url, CalogValue
return calogFail(result, calogErrUnsupportedE, "http: TLS context creation failed");
}
if (conn->verify) {
// Authenticate the server: require a certificate chain to a trusted CA. Load the system
// CA store plus OpenSSL's compiled-in defaults; with no trust anchors the handshake
// fails closed rather than open. (An https request can pass insecure=true to skip this.)
// Authenticate the server: require a certificate chain to a CA trusted by the host, loaded
// from its native trust configuration. With no anchors available the handshake would fail
// with a misleading per-certificate error, so surface a clear one here instead. (An https
// request can pass insecure=true to skip verification.)
SSL_CTX_set_verify(conn->ctx, SSL_VERIFY_PEER, NULL);
(void)SSL_CTX_set_default_verify_paths(conn->ctx);
(void)SSL_CTX_load_verify_locations(conn->ctx, "/etc/ssl/certs/ca-certificates.crt", "/etc/ssl/certs");
if (!httpLoadTrustStore(conn->ctx)) {
return calogFail(result, calogErrUnsupportedE, "http: no system CA trust store found (set SSL_CERT_FILE or CALOG_CA_BUNDLE, or pass insecure=true)");
}
}
conn->ssl = SSL_new(conn->ctx);
if (conn->ssl == NULL) {

View file

@ -1,806 +0,0 @@
// calogHttpd.c -- calog polyglot HTTP server (see calogHttpd.h). Each server owns an acceptor
// thread; per request it parses the message, finds the matching route, invokes that route's handler
// (a CalogFnT, so the call marshals to the handler's owning context thread and runs there), and
// writes the response. Routes are guarded by a mutex; re-registering replaces the handler live.
// v1: HTTP/1.1, Connection: close, one request in flight at a time. No TLS or WebSocket yet.
#define _GNU_SOURCE
#include "calogHttpd.h"
#include "calogHandle.h"
#include "calogInternal.h"
#include "calogPlatform.h"
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define HTTPD_TYPE_SERVER 1u
#define HTTPD_HEADER_MAX (64 * 1024)
#define HTTPD_BODY_MAX (16 * 1024 * 1024)
typedef struct HttpdBufT {
char *data;
size_t len;
size_t cap;
} HttpdBufT;
typedef struct RouteT {
char *method; // uppercase, or "*" for any
char *path; // exact match (query stripped)
CalogFnT *handler;
struct RouteT *next;
} RouteT;
typedef struct ServerT {
CalogSocketT listenFd;
pthread_t acceptor;
bool acceptorStarted;
pthread_mutex_t routesMutex;
RouteT *routes;
_Atomic bool stop;
int64_t maxBody;
} ServerT;
typedef struct HttpdLibT {
CalogHandleTableT *handles;
int32_t refCount;
} HttpdLibT;
typedef struct HttpdNativeT {
const char *name;
CalogNativeFnT fn;
} HttpdNativeT;
static pthread_mutex_t gHttpdMutex = PTHREAD_MUTEX_INITIALIZER;
static HttpdLibT *gHttpdLib = NULL;
static void *httpdAcceptor(void *arg);
static int32_t httpdBufAppend(HttpdBufT *buffer, const void *bytes, size_t length);
static void httpdCloser(uint32_t type, void *resource);
static const char *httpdFindHeader(const char *headers, size_t headerLen, const char *name, size_t *valueLen);
static CalogFnT *httpdMatchRoute(ServerT *server, const char *method, const char *path);
static int32_t httpdListen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t httpdParseRequest(ServerT *server, const char *buffer, size_t headerLen, const char *body, size_t bodyLen, CalogValueT *out);
static bool httpdReadRequest(ServerT *server, CalogSocketT fd, HttpdBufT *buffer, size_t *headerLen, size_t *bodyLen);
static const char *httpdReason(int64_t status);
static int32_t httpdRoute(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void httpdServe(ServerT *server, CalogSocketT fd);
static int32_t httpdStop(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t httpdUnroute(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void httpdWriteResponse(CalogSocketT fd, const CalogValueT *response);
static void httpdWriteStatus(HttpdBufT *out, int64_t status, int64_t bodyLen);
static const HttpdNativeT gHttpdNatives[] = {
{ "httpdListen", httpdListen },
{ "httpdRoute", httpdRoute },
{ "httpdUnroute", httpdUnroute },
{ "httpdStop", httpdStop },
};
int32_t calogHttpdRegister(CalogT *calog) {
HttpdLibT *lib;
int32_t status;
size_t index;
pthread_mutex_lock(&gHttpdMutex);
if (gHttpdLib == NULL) {
HttpdLibT *created;
if (calogPlatformNetInit() != 0) {
pthread_mutex_unlock(&gHttpdMutex);
return calogErrUnsupportedE;
}
created = (HttpdLibT *)calloc(1, sizeof(*created));
if (created == NULL) {
calogPlatformNetShutdown();
pthread_mutex_unlock(&gHttpdMutex);
return calogErrOomE;
}
created->handles = calogHandleTableCreate();
if (created->handles == NULL) {
free(created);
calogPlatformNetShutdown();
pthread_mutex_unlock(&gHttpdMutex);
return calogErrOomE;
}
gHttpdLib = created;
}
gHttpdLib->refCount++;
lib = gHttpdLib;
pthread_mutex_unlock(&gHttpdMutex);
status = calogOkE;
for (index = 0; index < sizeof(gHttpdNatives) / sizeof(gHttpdNatives[0]); index++) {
status = calogRegisterInline(calog, gHttpdNatives[index].name, gHttpdNatives[index].fn, lib);
if (status != calogOkE) {
break;
}
}
if (status != calogOkE) {
calogHttpdShutdown();
return status;
}
return calogAtDestroy(calog, calogHttpdShutdown, calogDestroyBeforeContextsE);
}
void calogHttpdShutdown(void) {
pthread_mutex_lock(&gHttpdMutex);
if (gHttpdLib == NULL) {
pthread_mutex_unlock(&gHttpdMutex);
return;
}
gHttpdLib->refCount--;
if (gHttpdLib->refCount <= 0) {
calogHandleTableDestroy(gHttpdLib->handles, httpdCloser);
calogPlatformNetShutdown();
free(gHttpdLib);
gHttpdLib = NULL;
}
pthread_mutex_unlock(&gHttpdMutex);
}
// The acceptor thread: accept a connection, serve it to completion, repeat, until stopped. The
// listen socket is non-blocking and gated by poll() with a short timeout, so the loop notices `stop`
// promptly -- closing the socket from another thread does NOT reliably wake a thread in accept().
static void *httpdAcceptor(void *arg) {
ServerT *server;
server = (ServerT *)arg;
while (!atomic_load(&server->stop)) {
struct pollfd pfd;
CalogSocketT client;
pfd.fd = server->listenFd;
pfd.events = POLLIN;
pfd.revents = 0;
if (calogPoll(&pfd, 1, 200) <= 0) {
continue; // timeout or error -> re-check stop
}
client = accept(server->listenFd, NULL, NULL);
if (client == CALOG_INVALID_SOCKET) {
continue;
}
httpdServe(server, client);
calogSockClose(client);
}
return NULL;
}
static int32_t httpdBufAppend(HttpdBufT *buffer, const void *bytes, size_t length) {
if (buffer->len + length > buffer->cap) {
size_t wanted;
char *grown;
wanted = buffer->cap ? buffer->cap * 2 : 4096;
while (wanted < buffer->len + length) {
wanted *= 2;
}
grown = (char *)realloc(buffer->data, wanted);
if (grown == NULL) {
return calogErrOomE;
}
buffer->data = grown;
buffer->cap = wanted;
}
memcpy(buffer->data + buffer->len, bytes, length);
buffer->len += length;
return calogOkE;
}
static void httpdCloser(uint32_t type, void *resource) {
ServerT *server;
RouteT *route;
if (type != HTTPD_TYPE_SERVER) {
return;
}
server = (ServerT *)resource;
atomic_store(&server->stop, true);
if (server->acceptorStarted) {
pthread_join(server->acceptor, NULL); // exits within one poll timeout of stop
}
if (server->listenFd != CALOG_INVALID_SOCKET) {
calogSockClose(server->listenFd);
server->listenFd = CALOG_INVALID_SOCKET;
}
route = server->routes;
while (route != NULL) {
RouteT *next;
next = route->next;
calogFnRelease(route->handler);
free(route->method);
free(route->path);
free(route);
route = next;
}
pthread_mutex_destroy(&server->routesMutex);
free(server);
}
// Case-insensitive lookup of a header value within the raw header block (NUL-free scan bounded by
// headerLen). Returns a pointer to the value (trimmed of leading spaces) + its length, or NULL.
static const char *httpdFindHeader(const char *headers, size_t headerLen, const char *name, size_t *valueLen) {
size_t nameLen;
size_t i;
nameLen = strlen(name);
for (i = 0; i + nameLen + 1 < headerLen; i++) {
if ((i == 0 || headers[i - 1] == '\n') && strncasecmp(headers + i, name, nameLen) == 0 && headers[i + nameLen] == ':') {
size_t v;
size_t end;
v = i + nameLen + 1;
while (v < headerLen && (headers[v] == ' ' || headers[v] == '\t')) {
v++;
}
end = v;
while (end < headerLen && headers[end] != '\r' && headers[end] != '\n') {
end++;
}
*valueLen = end - v;
return headers + v;
}
}
*valueLen = 0;
return NULL;
}
static int32_t httpdListen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
HttpdLibT *lib;
ServerT *server;
struct addrinfo hints;
struct addrinfo *res;
struct addrinfo *rp;
CalogSocketT fd;
char portBuffer[8];
int64_t port;
int64_t handle;
int yes;
lib = (HttpdLibT *)userData;
calogValueNil(result);
if (argCount < 1 || argCount > 2 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "httpdListen expects (port [, opts])");
}
if (argCount == 2 && args[1].type != calogAggE) {
return calogFail(result, calogErrArgE, "httpdListen: opts must be a map");
}
port = args[0].as.i;
if (port < 0 || port > 65535) {
return calogFail(result, calogErrArgE, "httpdListen: port out of range");
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
snprintf(portBuffer, sizeof(portBuffer), "%u", (unsigned int)port);
if (getaddrinfo(NULL, portBuffer, &hints, &res) != 0) {
return calogFail(result, calogErrArgE, "httpdListen: could not resolve the bind address");
}
fd = CALOG_INVALID_SOCKET;
yes = 1;
for (rp = res; rp != NULL; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == CALOG_INVALID_SOCKET) {
continue;
}
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
if (bind(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0 && listen(fd, 64) == 0) {
break;
}
calogSockClose(fd);
fd = CALOG_INVALID_SOCKET;
}
freeaddrinfo(res);
if (fd == CALOG_INVALID_SOCKET) {
return calogFail(result, calogErrArgE, "httpdListen: could not bind/listen on the port");
}
server = (ServerT *)calloc(1, sizeof(*server));
if (server == NULL) {
calogSockClose(fd);
return calogFail(result, calogErrOomE, "httpdListen: out of memory");
}
calogSockSetNonblock(fd, 1); // non-blocking + poll-gated accept, so the acceptor can be stopped
server->listenFd = fd;
server->maxBody = HTTPD_BODY_MAX;
pthread_mutex_init(&server->routesMutex, NULL);
handle = calogHandleAdd(lib->handles, HTTPD_TYPE_SERVER, server);
if (handle == 0) {
calogSockClose(fd);
pthread_mutex_destroy(&server->routesMutex);
free(server);
return calogFail(result, calogErrOomE, "httpdListen: out of memory");
}
if (pthread_create(&server->acceptor, NULL, httpdAcceptor, server) != 0) {
calogHandleRemove(lib->handles, handle, HTTPD_TYPE_SERVER);
httpdCloser(HTTPD_TYPE_SERVER, server);
return calogFail(result, calogErrUnsupportedE, "httpdListen: could not start the acceptor thread");
}
server->acceptorStarted = true;
calogValueInt(result, handle);
return calogOkE;
}
// Find the handler for method+path (RETAINED, so it survives a concurrent unroute), or NULL.
static CalogFnT *httpdMatchRoute(ServerT *server, const char *method, const char *path) {
RouteT *route;
CalogFnT *handler;
handler = NULL;
pthread_mutex_lock(&server->routesMutex);
for (route = server->routes; route != NULL; route = route->next) {
if ((strcmp(route->method, "*") == 0 || strcmp(route->method, method) == 0) && strcmp(route->path, path) == 0) {
handler = route->handler;
calogFnRetain(handler);
break;
}
}
pthread_mutex_unlock(&server->routesMutex);
return handler;
}
// Parse the request line + headers + body into a request map { method, path, query, headers, body }.
static int32_t httpdParseRequest(ServerT *server, const char *buffer, size_t headerLen, const char *body, size_t bodyLen, CalogValueT *out) {
CalogAggT *map;
CalogAggT *headers;
const char *p;
const char *lineEnd;
const char *methodEnd;
const char *target;
const char *targetEnd;
const char *query;
size_t i;
int32_t status;
(void)server;
calogValueNil(out);
// Request line: METHOD SP TARGET SP HTTP/x.y
p = buffer;
lineEnd = memchr(buffer, '\n', headerLen);
if (lineEnd == NULL) {
return calogErrArgE;
}
methodEnd = memchr(p, ' ', (size_t)(lineEnd - p));
if (methodEnd == NULL) {
return calogErrArgE;
}
target = methodEnd + 1;
targetEnd = memchr(target, ' ', (size_t)(lineEnd - target));
if (targetEnd == NULL) {
targetEnd = lineEnd;
}
query = memchr(target, '?', (size_t)(targetEnd - target));
status = calogAggCreate(&map, calogMapE);
if (status != calogOkE) {
return status;
}
status = calogMapSetStr(map, "method", p, (int64_t)(methodEnd - p));
if (status == calogOkE) {
const char *pathEnd;
pathEnd = query != NULL ? query : targetEnd;
status = calogMapSetStr(map, "path", target, (int64_t)(pathEnd - target));
}
if (status == calogOkE) {
status = calogMapSetStr(map, "query", query != NULL ? query + 1 : "", query != NULL ? (int64_t)(targetEnd - query - 1) : 0);
}
if (status == calogOkE) {
status = calogMapSetStr(map, "body", bodyLen > 0 ? body : "", (int64_t)bodyLen);
}
if (status != calogOkE) {
calogAggFree(map);
return status;
}
// Headers: each "Name: Value" line after the request line; names lowercased into a map.
status = calogAggCreate(&headers, calogMapE);
if (status != calogOkE) {
calogAggFree(map);
return status;
}
i = (size_t)(lineEnd - buffer) + 1;
while (i < headerLen) {
const char *colon;
const char *nl;
size_t lineLen;
char nameLower[128];
size_t nameLen;
size_t v;
size_t valueEnd;
size_t k;
nl = memchr(buffer + i, '\n', headerLen - i);
lineLen = nl != NULL ? (size_t)(nl - (buffer + i)) : (headerLen - i);
if (lineLen == 0 || (lineLen == 1 && buffer[i] == '\r')) {
break;
}
colon = memchr(buffer + i, ':', lineLen);
if (colon != NULL) {
nameLen = (size_t)(colon - (buffer + i));
if (nameLen >= sizeof(nameLower)) {
nameLen = sizeof(nameLower) - 1;
}
for (k = 0; k < nameLen; k++) {
char c;
c = buffer[i + k];
nameLower[k] = (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c;
}
nameLower[nameLen] = '\0';
v = (size_t)(colon - buffer) + 1;
while (v < i + lineLen && (buffer[v] == ' ' || buffer[v] == '\t')) {
v++;
}
valueEnd = i + lineLen;
while (valueEnd > v && (buffer[valueEnd - 1] == '\r' || buffer[valueEnd - 1] == ' ')) {
valueEnd--;
}
status = calogMapSetStr(headers, nameLower, buffer + v, (int64_t)(valueEnd - v));
if (status != calogOkE) {
calogAggFree(headers);
calogAggFree(map);
return status;
}
}
if (nl == NULL) {
break;
}
i += lineLen + 1;
}
{
CalogValueT headersKey;
CalogValueT headersValue;
calogValueAgg(&headersValue, headers);
if (calogValueString(&headersKey, "headers", 7) != calogOkE) {
calogValueFree(&headersValue);
calogAggFree(map);
return calogErrOomE;
}
status = calogAggSet(map, &headersKey, &headersValue);
if (status != calogOkE) {
calogAggFree(map);
return status;
}
}
calogValueAgg(out, map);
return calogOkE;
}
// Read the request headers (until CRLFCRLF) then the body (per Content-Length). Returns false on a
// closed/oversized/malformed request.
static bool httpdReadRequest(ServerT *server, CalogSocketT fd, HttpdBufT *buffer, size_t *headerLen, size_t *bodyLen) {
const char *marker;
const char *value;
size_t valueLen;
size_t headerEnd;
int64_t contentLength;
size_t have;
marker = NULL;
while (buffer->len < HTTPD_HEADER_MAX) {
char chunk[8192];
ssize_t got;
got = recv(fd, chunk, sizeof(chunk), 0);
if (got <= 0) {
return false;
}
if (httpdBufAppend(buffer, chunk, (size_t)got) != calogOkE) {
return false;
}
marker = memmem(buffer->data, buffer->len, "\r\n\r\n", 4);
if (marker != NULL) {
break;
}
}
if (marker == NULL) {
return false;
}
headerEnd = (size_t)(marker - buffer->data) + 4;
*headerLen = headerEnd;
contentLength = 0;
value = httpdFindHeader(buffer->data, headerEnd, "content-length", &valueLen);
if (value != NULL) {
char tmp[24];
if (valueLen >= sizeof(tmp)) {
return false;
}
memcpy(tmp, value, valueLen);
tmp[valueLen] = '\0';
contentLength = strtoll(tmp, NULL, 10);
if (contentLength < 0 || contentLength > server->maxBody) {
return false;
}
}
have = buffer->len - headerEnd; // body bytes already read past the header
while ((int64_t)have < contentLength) {
char chunk[8192];
ssize_t got;
got = recv(fd, chunk, sizeof(chunk), 0);
if (got <= 0) {
return false;
}
if (httpdBufAppend(buffer, chunk, (size_t)got) != calogOkE) {
return false;
}
have += (size_t)got;
}
*bodyLen = (size_t)contentLength;
return true;
}
static const char *httpdReason(int64_t status) {
switch (status) {
case 200: return "OK";
case 201: return "Created";
case 204: return "No Content";
case 301: return "Moved Permanently";
case 302: return "Found";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 500: return "Internal Server Error";
default: return "Status";
}
}
static int32_t httpdRoute(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
HttpdLibT *lib;
ServerT *server;
RouteT *route;
RouteT *existing;
char *method;
char *path;
size_t i;
lib = (HttpdLibT *)userData;
calogValueNil(result);
if (argCount != 4 || args[0].type != calogIntE || args[1].type != calogStringE || args[2].type != calogStringE || args[3].type != calogFnE) {
return calogFail(result, calogErrArgE, "httpdRoute expects (server, method, path, handler)");
}
server = (ServerT *)calogHandleGet(lib->handles, args[0].as.i, HTTPD_TYPE_SERVER);
if (server == NULL) {
return calogFail(result, calogErrArgE, "httpdRoute: invalid server handle");
}
method = strdup(args[1].as.s.bytes);
path = strdup(args[2].as.s.bytes);
if (method == NULL || path == NULL) {
free(method);
free(path);
return calogFail(result, calogErrOomE, "httpdRoute: out of memory");
}
for (i = 0; method[i] != '\0'; i++) {
if (method[i] >= 'a' && method[i] <= 'z') {
method[i] = (char)(method[i] - 'a' + 'A');
}
}
pthread_mutex_lock(&server->routesMutex);
// Re-registering the same method+path replaces the handler live (hot reload).
for (existing = server->routes; existing != NULL; existing = existing->next) {
if (strcmp(existing->method, method) == 0 && strcmp(existing->path, path) == 0) {
calogFnRelease(existing->handler);
calogFnRetain(args[3].as.fn);
existing->handler = args[3].as.fn;
pthread_mutex_unlock(&server->routesMutex);
free(method);
free(path);
return calogOkE;
}
}
route = (RouteT *)calloc(1, sizeof(*route));
if (route == NULL) {
pthread_mutex_unlock(&server->routesMutex);
free(method);
free(path);
return calogFail(result, calogErrOomE, "httpdRoute: out of memory");
}
calogFnRetain(args[3].as.fn);
route->method = method;
route->path = path;
route->handler = args[3].as.fn;
route->next = server->routes;
server->routes = route;
pthread_mutex_unlock(&server->routesMutex);
return calogOkE;
}
// Serve one connection: read + parse the request, invoke the matching handler on its owning context
// thread, write the response.
static void httpdServe(ServerT *server, CalogSocketT fd) {
HttpdBufT buffer;
CalogValueT request;
CalogValueT response;
CalogFnT *handler;
CalogValueT *method;
CalogValueT *path;
size_t headerLen;
size_t bodyLen;
CalogValueT methodKey;
CalogValueT pathKey;
memset(&buffer, 0, sizeof(buffer));
if (!httpdReadRequest(server, fd, &buffer, &headerLen, &bodyLen)) {
free(buffer.data);
return;
}
if (httpdParseRequest(server, buffer.data, headerLen, buffer.data + headerLen, bodyLen, &request) != calogOkE) {
free(buffer.data);
return;
}
free(buffer.data);
// Look up the handler by the parsed method + path.
method = NULL;
path = NULL;
if (calogValueString(&methodKey, "method", 6) == calogOkE) {
method = calogAggGet(request.as.agg, &methodKey);
calogValueFree(&methodKey);
}
if (calogValueString(&pathKey, "path", 4) == calogOkE) {
path = calogAggGet(request.as.agg, &pathKey);
calogValueFree(&pathKey);
}
handler = (method != NULL && path != NULL) ? httpdMatchRoute(server, method->as.s.bytes, path->as.s.bytes) : NULL;
if (handler == NULL) {
static const char notFound[] = "HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nNot Found";
send(fd, notFound, sizeof(notFound) - 1, CALOG_MSG_NOSIGNAL);
calogValueFree(&request);
return;
}
calogValueNil(&response);
if (calogFnInvoke(handler, &request, 1, &response) != calogOkE) {
static const char err[] = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 21\r\nConnection: close\r\n\r\nInternal Server Error";
send(fd, err, sizeof(err) - 1, CALOG_MSG_NOSIGNAL);
} else {
httpdWriteResponse(fd, &response);
}
calogFnRelease(handler);
calogValueFree(&response);
calogValueFree(&request);
}
static int32_t httpdStop(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
HttpdLibT *lib;
ServerT *server;
lib = (HttpdLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "httpdStop expects (server)");
}
server = (ServerT *)calogHandleRemove(lib->handles, args[0].as.i, HTTPD_TYPE_SERVER);
if (server == NULL) {
return calogFail(result, calogErrArgE, "httpdStop: invalid server handle");
}
httpdCloser(HTTPD_TYPE_SERVER, server);
return calogOkE;
}
static int32_t httpdUnroute(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
HttpdLibT *lib;
ServerT *server;
RouteT **link;
char method[16];
size_t i;
lib = (HttpdLibT *)userData;
calogValueNil(result);
if (argCount != 3 || args[0].type != calogIntE || args[1].type != calogStringE || args[2].type != calogStringE) {
return calogFail(result, calogErrArgE, "httpdUnroute expects (server, method, path)");
}
server = (ServerT *)calogHandleGet(lib->handles, args[0].as.i, HTTPD_TYPE_SERVER);
if (server == NULL) {
return calogFail(result, calogErrArgE, "httpdUnroute: invalid server handle");
}
snprintf(method, sizeof(method), "%s", args[1].as.s.bytes);
for (i = 0; method[i] != '\0'; i++) {
if (method[i] >= 'a' && method[i] <= 'z') {
method[i] = (char)(method[i] - 'a' + 'A');
}
}
pthread_mutex_lock(&server->routesMutex);
for (link = &server->routes; *link != NULL; link = &(*link)->next) {
RouteT *route;
route = *link;
if (strcmp(route->method, method) == 0 && strcmp(route->path, args[2].as.s.bytes) == 0) {
*link = route->next;
calogFnRelease(route->handler);
free(route->method);
free(route->path);
free(route);
break;
}
}
pthread_mutex_unlock(&server->routesMutex);
return calogOkE;
}
// Turn a handler's return value into an HTTP response and write it. nil -> 204; a string -> 200 with
// that body; a map -> { status (default 200), headers (map), body (string) }.
static void httpdWriteResponse(CalogSocketT fd, const CalogValueT *response) {
HttpdBufT out;
const char *body;
int64_t bodyLen;
int64_t status;
memset(&out, 0, sizeof(out));
body = "";
bodyLen = 0;
status = 200;
if (response->type == calogNilE) {
status = 204;
} else if (response->type == calogStringE) {
body = response->as.s.bytes;
bodyLen = response->as.s.length;
} else if (response->type == calogAggE && calogAggIsKeyed(response->as.agg)) {
CalogValueT key;
CalogValueT *field;
if (calogValueString(&key, "status", 6) == calogOkE) {
field = calogAggGet(response->as.agg, &key);
calogValueFree(&key);
if (field != NULL && field->type == calogIntE) {
status = field->as.i;
}
}
if (calogValueString(&key, "body", 4) == calogOkE) {
field = calogAggGet(response->as.agg, &key);
calogValueFree(&key);
if (field != NULL && field->type == calogStringE) {
body = field->as.s.bytes;
bodyLen = field->as.s.length;
}
}
httpdWriteStatus(&out, status, bodyLen);
// Custom headers, if any (Content-Length + Connection are set by httpdWriteStatus).
if (calogValueString(&key, "headers", 7) == calogOkE) {
field = calogAggGet(response->as.agg, &key);
calogValueFree(&key);
if (field != NULL && field->type == calogAggE && calogAggIsKeyed(field->as.agg)) {
int64_t h;
for (h = 0; h < field->as.agg->pairCount; h++) {
if (field->as.agg->pairs[h].key.type == calogStringE && field->as.agg->pairs[h].value.type == calogStringE) {
httpdBufAppend(&out, field->as.agg->pairs[h].key.as.s.bytes, (size_t)field->as.agg->pairs[h].key.as.s.length);
httpdBufAppend(&out, ": ", 2);
httpdBufAppend(&out, field->as.agg->pairs[h].value.as.s.bytes, (size_t)field->as.agg->pairs[h].value.as.s.length);
httpdBufAppend(&out, "\r\n", 2);
}
}
}
}
httpdBufAppend(&out, "\r\n", 2);
httpdBufAppend(&out, body, (size_t)bodyLen);
send(fd, out.data, out.len, CALOG_MSG_NOSIGNAL);
free(out.data);
return;
}
httpdWriteStatus(&out, status, bodyLen);
httpdBufAppend(&out, "\r\n", 2);
httpdBufAppend(&out, body, (size_t)bodyLen);
send(fd, out.data, out.len, CALOG_MSG_NOSIGNAL);
free(out.data);
}
// Write the status line + the always-present Content-Length and Connection: close headers.
static void httpdWriteStatus(HttpdBufT *out, int64_t status, int64_t bodyLen) {
char header[128];
int n;
n = snprintf(header, sizeof(header), "HTTP/1.1 %lld %s\r\nContent-Length: %lld\r\nConnection: close\r\n", (long long)status, httpdReason(status), (long long)bodyLen);
if (n > 0) {
httpdBufAppend(out, header, (size_t)n);
}
}

View file

@ -1,33 +0,0 @@
// calogHttpd.h -- calog polyglot HTTP server: serve routes whose handlers are script functions.
//
// A script registers route handlers (function values, in ANY engine); the server accepts
// connections on its own thread and, per request, invokes the matching handler ON ITS OWNING
// CONTEXT THREAD (via the callable machinery), then writes the returned response. Because a handler
// is an ordinary calog function value, re-registering a route replaces it live -- hot reload -- and
// different routes can be written in different languages and still share helpers via calogExport.
//
// httpdListen(port [, optsMap]) -> serverHandle opts: { host, backlog, maxBody }
// httpdRoute(serverHandle, method, path, handler) method "*" = any; exact-path match; re-register replaces
// httpdUnroute(serverHandle, method, path)
// httpdStop(serverHandle) stop accepting, close, release handlers
//
// The handler receives a request map { method, path, query, headers (map, lowercased names), body }
// and returns a response: a map { status (default 200), headers (map), body (string) }, a bare
// string (=> 200 with that body), or nil (=> 204 No Content). Bodies are binary-safe.
//
// v1 serves HTTP/1.1 with Connection: close and one request in flight at a time (a handler runs to
// completion before the next connection is accepted). TLS (https) and WebSocket are not yet included.
#ifndef CALOG_HTTPD_H
#define CALOG_HTTPD_H
#include "calog.h"
// Register the httpd natives on a runtime. Idempotent across runtimes.
int32_t calogHttpdRegister(CalogT *calog);
// Stop every server and release route handlers (once the last registered runtime unregisters).
// Call while the handler-owning contexts are still alive, before calogDestroy.
void calogHttpdShutdown(void);
#endif

View file

@ -21,10 +21,4 @@
// Register the kv natives on a runtime. Idempotent across runtimes (shared registry).
int32_t calogKvRegister(CalogT *calog);
// Free every stored key + value (once the last registered runtime unregisters). Like
// calogExportShutdown, call this BEFORE calogDestroy. The static registry bookkeeping is
// intentionally never freed -- the natives stay callable right up until calogDestroy tears
// the broker down -- so a native call after shutdown is safe (it just sees an empty store).
void calogKvShutdown(void);
#endif

View file

@ -11,6 +11,7 @@
#include "calogPlatform.h"
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@ -18,6 +19,9 @@
#include <enet/enet.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
// Handle type tags, distinct across the whole registry so a stray handle of the wrong kind
// fails to resolve (e.g. a listener passed to tcpSend).
#define NET_TYPE_TCP 1u
@ -32,8 +36,14 @@
#define NET_PORT_MAX 65535
// Upper bound (ms) on a TLS server handshake, so a client that opens the socket but never sends a
// ClientHello cannot pin the accepting thread forever.
#define NET_TLS_HANDSHAKE_MS 10000
typedef struct NetSocketT {
CalogSocketT fd;
SSL *ssl; // non-NULL: a TLS connection (accepted from a TLS listener)
SSL_CTX *tlsCtx; // non-NULL: a TLS listener, owning the server context
} NetSocketT;
// Process-wide network library state shared by every runtime that registers the natives.
@ -61,9 +71,12 @@ static void netCloser(uint32_t type, void *resource);
static int32_t netOpenBound(uint16_t port, int socktype, bool doListen, CalogValueT *result, CalogSocketT *fdOut);
static bool netPortOk(int64_t port);
static int netResolve(const char *host, uint16_t port, int socktype, bool passive, struct addrinfo **out);
static const CalogValueT *netOptField(const CalogValueT *map, const char *name);
static int32_t netSocketClose(NetLibT *lib, int64_t handleId, uint32_t type1, uint32_t type2, CalogValueT *result, const char *message);
static void netSocketFree(NetSocketT *sock);
static int32_t netStore(NetLibT *lib, CalogSocketT fd, uint32_t type, CalogValueT *result);
static bool netTlsAccept(SSL *ssl, CalogSocketT fd, int timeoutMs);
static SSL_CTX *netTlsServerContext(const CalogValueT *opts, const char **errOut);
static int32_t tcpAccept(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpConnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -102,6 +115,12 @@ int32_t calogNetRegister(CalogT *calog) {
int32_t status;
size_t nativeIndex;
// A TLS listener writes with SSL_write, whose OpenSSL socket BIO issues a bare write() with no
// MSG_NOSIGNAL; on Linux (where SO_NOSIGPIPE does not exist) a peer reset would raise SIGPIPE and
// kill the process. Ignore it process-wide (idempotent; the plain send() path uses MSG_NOSIGNAL).
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
pthread_mutex_lock(&gNetLibMutex);
if (gNetLib == NULL) {
NetLibT *newLib;
@ -210,7 +229,18 @@ static int32_t netOpenBound(uint16_t port, int socktype, bool doListen, CalogVal
if (fd == CALOG_INVALID_SOCKET) {
continue;
}
#if defined(_WIN32)
// On Windows SO_REUSEADDR lets an unrelated process bind (and hijack) a port already in
// use; SO_EXCLUSIVEADDRUSE is the correct hardening for a server listener. A client or UDP
// bind (doListen == false) needs neither, so it is left at the default.
if (doListen) {
setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&yes, sizeof(yes));
}
#else
// POSIX SO_REUSEADDR only relaxes the TIME_WAIT restriction (fast listener restart) and
// carries no hijack hazard, so it is applied to every bind.
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
#endif
if (bind(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
break;
}
@ -271,14 +301,115 @@ static int32_t netSocketClose(NetLibT *lib, int64_t handleId, uint32_t type1, ui
}
// Drive the TLS server handshake to completion under a TOTAL wall-clock deadline (timeoutMs), on a
// non-blocking socket gated by calogPoll. This bounds every stall vector -- a silent client, a
// slow-drip client that dribbles bytes to keep resetting a per-read timeout, AND a client that stalls
// the server's own writes (a ServerHello/Certificate that never drains) -- none of which a per-recv
// SO_RCVTIMEO would catch. Restores blocking mode on success so tcpRecv/tcpSend behave normally.
// Returns false on timeout or a hard handshake error (the caller then closes the socket).
static bool netTlsAccept(SSL *ssl, CalogSocketT fd, int timeoutMs) {
int64_t deadline;
deadline = (int64_t)calogMonotonicMillis() + timeoutMs;
calogSockSetNonblock(fd, 1);
for (;;) {
struct pollfd pfd;
int64_t remaining;
int rc;
int err;
rc = SSL_accept(ssl);
if (rc == 1) {
calogSockSetNonblock(fd, 0);
return true;
}
err = SSL_get_error(ssl, rc);
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
return false;
}
remaining = deadline - (int64_t)calogMonotonicMillis();
if (remaining <= 0) {
return false;
}
pfd.fd = fd;
pfd.events = (short)((err == SSL_ERROR_WANT_WRITE) ? POLLOUT : POLLIN);
pfd.revents = 0;
if (calogPoll(&pfd, 1, (int)(remaining > INT32_MAX ? INT32_MAX : remaining)) <= 0) {
return false;
}
}
}
// Close the fd and release the NetSocketT. Shared by netSocketClose and the handle-table
// teardown path (netCloser).
static void netSocketFree(NetSocketT *sock) {
// The fd has a single owner: the socket BIO is set BIO_NOCLOSE in tcpAccept, so SSL_free never
// touches it and calogSockClose below is the one and only close. We deliberately do NOT call
// SSL_shutdown here -- writing the close_notify alert can block on an unresponsive peer with a
// full send buffer, stalling teardown; a truncating (dirty) close is acceptable for a server.
if (sock->ssl != NULL) {
SSL_free(sock->ssl);
}
if (sock->tlsCtx != NULL) {
SSL_CTX_free(sock->tlsCtx);
}
calogSockClose(sock->fd);
free(sock);
}
// Look up a string-keyed field in an opts map, or NULL (option names are ASCII).
static const CalogValueT *netOptField(const CalogValueT *map, const char *name) {
CalogValueT key;
CalogValueT *field;
if (calogValueString(&key, name, (int64_t)strlen(name)) != calogOkE) {
return NULL;
}
field = calogAggGet(map->as.agg, &key);
calogValueFree(&key);
return field;
}
// Build a server-side SSL_CTX from opts { cert = "chain.pem", key = "key.pem" }. On failure returns
// NULL and points *errOut at a static message.
static SSL_CTX *netTlsServerContext(const CalogValueT *opts, const char **errOut) {
SSL_CTX *ctx;
const CalogValueT *cert;
const CalogValueT *key;
cert = netOptField(opts, "cert");
key = netOptField(opts, "key");
if (cert == NULL || cert->type != calogStringE || key == NULL || key->type != calogStringE) {
*errOut = "tcpListen: tls requires cert and key file paths";
return NULL;
}
ctx = SSL_CTX_new(TLS_server_method());
if (ctx == NULL) {
*errOut = "tcpListen: could not create the TLS context";
return NULL;
}
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
if (SSL_CTX_use_certificate_chain_file(ctx, cert->as.s.bytes) != 1) {
SSL_CTX_free(ctx);
*errOut = "tcpListen: could not load the TLS certificate";
return NULL;
}
if (SSL_CTX_use_PrivateKey_file(ctx, key->as.s.bytes, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
*errOut = "tcpListen: could not load the TLS private key";
return NULL;
}
if (SSL_CTX_check_private_key(ctx) != 1) {
SSL_CTX_free(ctx);
*errOut = "tcpListen: certificate and key do not match";
return NULL;
}
return ctx;
}
// Wrap an open fd in a handle-table entry, transferring ownership. On failure the fd is
// closed. Sets result to the new integer handle on success.
static int32_t netStore(NetLibT *lib, CalogSocketT fd, uint32_t type, CalogValueT *result) {
@ -290,8 +421,11 @@ static int32_t netStore(NetLibT *lib, CalogSocketT fd, uint32_t type, CalogValue
calogSockClose(fd);
return calogFail(result, calogErrOomE, "out of memory");
}
sock->fd = fd;
handle = calogHandleAdd(lib->handles, type, sock);
sock->fd = fd;
sock->ssl = NULL;
sock->tlsCtx = NULL;
calogSockNoSigpipe(fd); // macOS: no MSG_NOSIGNAL, so guard broken-pipe writes per-socket
handle = calogHandleAdd(lib->handles, type, sock);
if (handle == 0) {
calogSockClose(fd);
free(sock);
@ -309,18 +443,64 @@ static int32_t tcpAccept(CalogValueT *args, int32_t argCount, CalogValueT *resul
lib = (NetLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "tcpAccept expects (listenerHandle)");
if (argCount < 1 || argCount > 2 || args[0].type != calogIntE || (argCount == 2 && args[1].type != calogIntE)) {
return calogFail(result, calogErrArgE, "tcpAccept expects (listenerHandle [, timeoutMs])");
}
listener = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_TCP_LISTEN);
if (listener == NULL) {
return calogFail(result, calogErrArgE, "tcpAccept: invalid listener handle");
}
// Optional timeout: poll-gate the accept so a script's accept loop wakes periodically to re-check
// its own stop condition (a blocking accept would pin the context thread until a connection lands).
if (argCount == 2) {
struct pollfd pfd;
int timeout;
int ready;
timeout = args[1].as.i < 0 ? 0 : (args[1].as.i > INT32_MAX ? INT32_MAX : (int)args[1].as.i);
pfd.fd = listener->fd;
pfd.events = POLLIN;
pfd.revents = 0;
ready = calogPoll(&pfd, 1, timeout);
if (ready <= 0) {
return calogOkE; // timeout (nil result) -- the script loop decides whether to keep going
}
}
fd = accept(listener->fd, NULL, NULL);
if (fd == CALOG_INVALID_SOCKET) {
return calogFail(result, calogErrArgE, calogSockErrStr());
}
return netStore(lib, fd, NET_TYPE_TCP, result);
if (listener->tlsCtx == NULL) {
return netStore(lib, fd, NET_TYPE_TCP, result);
}
// TLS listener: complete the server handshake, then attach the session to the stored connection.
// The socket BIO is BIO_NOCLOSE so the NetSocketT stays the single fd owner (see netSocketFree).
{
SSL *ssl;
NetSocketT *sock;
int32_t status;
ssl = SSL_new(listener->tlsCtx);
if (ssl == NULL) {
calogSockClose(fd);
return calogFail(result, calogErrOomE, "tcpAccept: could not create the TLS session");
}
SSL_set_fd(ssl, (int)fd);
(void)BIO_set_close(SSL_get_rbio(ssl), BIO_NOCLOSE);
// Bound the handshake by a TOTAL deadline on a non-blocking socket, so a stalled/slow/malicious
// client cannot pin this accepting thread (see netTlsAccept).
if (!netTlsAccept(ssl, fd, NET_TLS_HANDSHAKE_MS)) {
SSL_free(ssl);
calogSockClose(fd);
return calogFail(result, calogErrArgE, "tcpAccept: TLS handshake failed or timed out");
}
status = netStore(lib, fd, NET_TYPE_TCP, result);
if (status != calogOkE) {
SSL_free(ssl); // netStore closed fd; the NOCLOSE BIO leaves it untouched
return status;
}
sock = (NetSocketT *)calogHandleGet(lib->handles, result->as.i, NET_TYPE_TCP);
sock->ssl = ssl;
return calogOkE;
}
}
@ -376,23 +556,61 @@ static int32_t tcpConnect(CalogValueT *args, int32_t argCount, CalogValueT *resu
static int32_t tcpListen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
NetLibT *lib;
CalogSocketT fd;
int32_t status;
NetLibT *lib;
const CalogValueT *opts;
SSL_CTX *ctx;
CalogSocketT fd;
int32_t status;
lib = (NetLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "tcpListen expects (port)");
if (argCount < 1 || argCount > 2 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "tcpListen expects (port [, opts])");
}
if (argCount == 2 && (args[1].type != calogAggE || !calogAggIsKeyed(args[1].as.agg))) {
return calogFail(result, calogErrArgE, "tcpListen: opts must be a map");
}
if (!netPortOk(args[0].as.i)) {
return calogFail(result, calogErrArgE, "tcpListen: port out of range");
}
// TLS is opt-in: { tls = true, cert = "...pem", key = "...pem" } builds a server SSL_CTX that the
// listener owns; tcpAccept then wraps each accepted connection in a TLS session.
opts = argCount == 2 ? &args[1] : NULL;
ctx = NULL;
if (opts != NULL) {
const CalogValueT *tls;
const CalogValueT *cert;
tls = netOptField(opts, "tls");
cert = netOptField(opts, "cert");
if ((tls != NULL && tls->type == calogBoolE && tls->as.b) || cert != NULL) {
const char *err;
err = NULL;
ctx = netTlsServerContext(opts, &err);
if (ctx == NULL) {
return calogFail(result, calogErrArgE, err != NULL ? err : "tcpListen: TLS setup failed");
}
}
}
status = netOpenBound((uint16_t)args[0].as.i, SOCK_STREAM, true, result, &fd);
if (status != calogOkE) {
if (ctx != NULL) {
SSL_CTX_free(ctx);
}
return status;
}
return netStore(lib, fd, NET_TYPE_TCP_LISTEN, result);
status = netStore(lib, fd, NET_TYPE_TCP_LISTEN, result);
if (status != calogOkE) {
if (ctx != NULL) {
SSL_CTX_free(ctx); // netStore already closed fd
}
return status;
}
if (ctx != NULL) {
NetSocketT *listener;
listener = (NetSocketT *)calogHandleGet(lib->handles, result->as.i, NET_TYPE_TCP_LISTEN);
listener->tlsCtx = ctx;
}
return calogOkE;
}
@ -419,7 +637,13 @@ static int32_t tcpRecv(CalogValueT *args, int32_t argCount, CalogValueT *result,
if (buffer == NULL) {
return calogFail(result, calogErrOomE, "tcpRecv: out of memory");
}
received = recv(sock->fd, buffer, (size_t)args[1].as.i, 0);
if (sock->ssl != NULL) {
int got;
got = SSL_read(sock->ssl, buffer, (int)args[1].as.i);
received = got > 0 ? (ssize_t)got : (got == 0 ? 0 : -1); // 0 = clean TLS shutdown -> EOF
} else {
received = recv(sock->fd, buffer, (size_t)args[1].as.i, 0);
}
if (received < 0) {
status = calogFail(result, calogErrArgE, calogSockErrStr());
free(buffer);
@ -453,7 +677,15 @@ static int32_t tcpSend(CalogValueT *args, int32_t argCount, CalogValueT *result,
total = 0;
while (total < args[1].as.s.length) {
ssize_t sent;
sent = send(sock->fd, args[1].as.s.bytes + total, (size_t)(args[1].as.s.length - total), CALOG_MSG_NOSIGNAL);
if (sock->ssl != NULL) {
int chunk;
int wrote;
chunk = (args[1].as.s.length - total) > INT32_MAX ? INT32_MAX : (int)(args[1].as.s.length - total);
wrote = SSL_write(sock->ssl, args[1].as.s.bytes + total, chunk);
sent = wrote > 0 ? (ssize_t)wrote : -1;
} else {
sent = send(sock->fd, args[1].as.s.bytes + total, (size_t)(args[1].as.s.length - total), CALOG_MSG_NOSIGNAL);
}
if (sent < 0) {
return calogFail(result, calogErrArgE, calogSockErrStr());
}

View file

@ -35,8 +35,4 @@
// process-wide socket registry). Returns calogOkE or an error.
int32_t calogNetRegister(CalogT *calog);
// Close any still-open sockets and free the process-wide registry. Call it AFTER the
// runtime is torn down (calogDestroy), since it invalidates the natives' state.
void calogNetShutdown(void);
#endif

View file

@ -1,7 +1,8 @@
// calogProc.c -- calog subprocess library (see calogProc.h). Spawns a child with posix_spawn (NOT
// fork -- calog runs many pthreads), feeds its stdin while draining stdout/stderr through one poll
// loop so a large transfer cannot deadlock, and returns its exit code + captured output. POSIX-only;
// on Windows procRun reports "unsupported" rather than shipping a half-working implementation.
// calogProc.c -- calog subprocess library (see calogProc.h). Spawns a child, feeds its stdin while
// draining stdout/stderr so a large transfer cannot deadlock, and returns its exit code + captured
// output. POSIX spawns with posix_spawn (NOT fork -- calog runs many pthreads) and multiplexes the
// three pipes with one poll loop. Windows spawns with CreateProcess and, because anonymous pipes
// have no poll, writes stdin on a helper thread while the main thread drains stdout/stderr.
#define _GNU_SOURCE
@ -13,10 +14,13 @@
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
#ifdef _WIN32
#include <windows.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
@ -25,6 +29,8 @@ extern char **environ;
// Upper bound on captured stdout/stderr, so a runaway child cannot exhaust memory.
#define PROC_MAX (64 * 1024 * 1024)
// Transfer granularity for a single read/write while draining the child's pipes.
#define PROC_CHUNK (64 * 1024)
typedef struct ProcBufT {
char *data;
@ -32,9 +38,23 @@ typedef struct ProcBufT {
size_t cap;
} ProcBufT;
#ifdef _WIN32
// Payload handed to the stdin-writer thread (see procWinStdinThread).
typedef struct ProcWinStdinT {
HANDLE handle;
const char *bytes;
size_t length;
} ProcWinStdinT;
#endif
static int32_t procBufAppend(ProcBufT *buffer, const void *bytes, size_t length);
static CalogValueT *procOpt(CalogAggT *opts, const char *name);
static int32_t procResult(CalogValueT *result, int32_t exitCode, ProcBufT *out, ProcBufT *err);
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
#ifdef _WIN32
static int32_t procWinAppendArg(ProcBufT *cmd, const char *arg);
static DWORD WINAPI procWinStdinThread(LPVOID param);
#endif
int32_t calogProcRegister(CalogT *calog) {
@ -79,21 +99,6 @@ static CalogValueT *procOpt(CalogAggT *opts, const char *name) {
}
#ifdef _WIN32
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
return calogFail(result, calogErrUnsupportedE, "procRun: subprocess spawning is not supported on Windows in this build");
}
#else
// Build a result map { exit, stdout, stderr } and hand ownership to result.
static int32_t procResult(CalogValueT *result, int32_t exitCode, ProcBufT *out, ProcBufT *err) {
CalogAggT *map;
@ -119,6 +124,330 @@ static int32_t procResult(CalogValueT *result, int32_t exitCode, ProcBufT *out,
}
#ifdef _WIN32
// Spawn a child with CreateProcess. argvList is quoted into a single command line; opts may carry a
// stdin string, a cwd, and an env map. stdin is written on a helper thread (anonymous pipes have no
// poll, and a blocking write would otherwise deadlock the stdout/stderr drain done here). Output is
// capped at PROC_MAX. Returns { exit, stdout, stderr }; exit is the process exit code. Build-verified
// via the zig Windows cross build; not run-verified (no Windows host here).
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
STARTUPINFOA si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
ProcWinStdinT stdinJob;
ProcBufT cmd;
ProcBufT envBlock;
ProcBufT outBuf;
ProcBufT errBuf;
HANDLE inRead;
HANDLE inWrite;
HANDLE outRead;
HANDLE outWrite;
HANDLE errRead;
HANDLE errWrite;
HANDLE stdinThread;
const char *stdinBytes;
const char *cwd;
char *envArg;
CalogValueT *field;
int64_t stdinLen;
int64_t argListCount;
int64_t i;
int32_t status;
DWORD exitStatus;
bool outOpen;
bool errOpen;
(void)userData;
calogValueNil(result);
if (argCount < 1 || argCount > 2 || args[0].type != calogAggE || calogAggIsKeyed(args[0].as.agg)) {
return calogFail(result, calogErrArgE, "procRun expects (argvList [, opts])");
}
if (argCount == 2 && (args[1].type != calogAggE || !calogAggIsKeyed(args[1].as.agg))) {
return calogFail(result, calogErrArgE, "procRun: opts must be a map");
}
argListCount = args[0].as.agg->arrayCount;
if (argListCount < 1) {
return calogFail(result, calogErrArgE, "procRun: argvList must have at least the program");
}
// Quote each argv element into one command line (CreateProcess takes a single string).
memset(&cmd, 0, sizeof(cmd));
for (i = 0; i < argListCount; i++) {
if (args[0].as.agg->array[i].type != calogStringE) {
free(cmd.data);
return calogFail(result, calogErrArgE, "procRun: every argv element must be a string");
}
if ((i > 0 && procBufAppend(&cmd, " ", 1) != calogOkE) ||
procWinAppendArg(&cmd, args[0].as.agg->array[i].as.s.bytes) != calogOkE) {
free(cmd.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
}
if (procBufAppend(&cmd, "\0", 1) != calogOkE) {
free(cmd.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
stdinBytes = NULL;
stdinLen = 0;
cwd = NULL;
envArg = NULL;
memset(&envBlock, 0, sizeof(envBlock));
if (argCount == 2) {
field = procOpt(args[1].as.agg, "stdin");
if (field != NULL && field->type == calogStringE) {
stdinBytes = field->as.s.bytes;
stdinLen = field->as.s.length;
}
field = procOpt(args[1].as.agg, "cwd");
if (field != NULL && field->type == calogStringE) {
cwd = field->as.s.bytes;
}
field = procOpt(args[1].as.agg, "env");
if (field != NULL && field->type == calogAggE && calogAggIsKeyed(field->as.agg)) {
CalogAggT *envMap;
int64_t e;
envMap = field->as.agg;
for (e = 0; e < envMap->pairCount; e++) {
if (envMap->pairs[e].key.type != calogStringE || envMap->pairs[e].value.type != calogStringE) {
continue;
}
if (procBufAppend(&envBlock, envMap->pairs[e].key.as.s.bytes, (size_t)envMap->pairs[e].key.as.s.length) != calogOkE ||
procBufAppend(&envBlock, "=", 1) != calogOkE ||
procBufAppend(&envBlock, envMap->pairs[e].value.as.s.bytes, (size_t)envMap->pairs[e].value.as.s.length) != calogOkE ||
procBufAppend(&envBlock, "\0", 1) != calogOkE) {
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
}
// An environment block is terminated by a final extra NUL (a double NUL overall).
if (procBufAppend(&envBlock, "\0", 1) != calogOkE) {
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrOomE, "procRun: out of memory");
}
envArg = envBlock.data;
}
}
// Three inheritable pipes; the parent's own ends are made non-inheritable so the child cannot
// keep them open (which would keep our reads from ever seeing end-of-file).
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create the three pipes one at a time so a partial failure can close the ends already opened
// (a single short-circuited guard would leak them).
if (!CreatePipe(&inRead, &inWrite, &sa, 0)) {
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
if (!CreatePipe(&outRead, &outWrite, &sa, 0)) {
CloseHandle(inRead);
CloseHandle(inWrite);
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
if (!CreatePipe(&errRead, &errWrite, &sa, 0)) {
CloseHandle(inRead);
CloseHandle(inWrite);
CloseHandle(outRead);
CloseHandle(outWrite);
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: could not create pipes");
}
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, 0);
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = inRead;
si.hStdOutput = outWrite;
si.hStdError = errWrite;
memset(&pi, 0, sizeof(pi));
if (!CreateProcessA(NULL, cmd.data, NULL, NULL, TRUE, 0, envArg, cwd, &si, &pi)) {
CloseHandle(inRead);
CloseHandle(inWrite);
CloseHandle(outRead);
CloseHandle(outWrite);
CloseHandle(errRead);
CloseHandle(errWrite);
free(cmd.data);
free(envBlock.data);
return calogFail(result, calogErrArgE, "procRun: CreateProcess failed");
}
free(cmd.data);
free(envBlock.data);
// The child owns the far ends now; close ours so end-of-file becomes observable.
CloseHandle(inRead);
CloseHandle(outWrite);
CloseHandle(errWrite);
stdinThread = NULL;
if (stdinLen > 0) {
stdinJob.handle = inWrite;
stdinJob.bytes = stdinBytes;
stdinJob.length = (size_t)stdinLen;
stdinThread = CreateThread(NULL, 0, procWinStdinThread, &stdinJob, 0, NULL);
}
if (stdinThread == NULL) {
CloseHandle(inWrite); // nothing to send, or the thread could not start
}
memset(&outBuf, 0, sizeof(outBuf));
memset(&errBuf, 0, sizeof(errBuf));
status = calogOkE;
outOpen = true;
errOpen = true;
while (status == calogOkE && (outOpen || errOpen)) {
bool didRead;
HANDLE pipes[2];
bool *open[2];
ProcBufT *bufs[2];
int p;
didRead = false;
pipes[0] = outRead;
pipes[1] = errRead;
open[0] = &outOpen;
open[1] = &errOpen;
bufs[0] = &outBuf;
bufs[1] = &errBuf;
for (p = 0; p < 2; p++) {
DWORD avail;
DWORD got;
char chunk[PROC_CHUNK];
if (!*open[p]) {
continue;
}
if (!PeekNamedPipe(pipes[p], NULL, 0, NULL, &avail, NULL)) {
*open[p] = false; // broken pipe: the child closed this stream and it is drained
continue;
}
if (avail == 0) {
continue; // open but idle; the Sleep below yields before we retry
}
if (!ReadFile(pipes[p], chunk, (avail > PROC_CHUNK) ? PROC_CHUNK : avail, &got, NULL) || got == 0) {
*open[p] = false;
continue;
}
if (bufs[p]->len + got > PROC_MAX || procBufAppend(bufs[p], chunk, got) != calogOkE) {
status = calogFail(result, calogErrRangeE, (p == 0) ? "procRun: stdout exceeds the size cap" : "procRun: stderr exceeds the size cap");
break;
}
didRead = true;
}
if (status == calogOkE && !didRead && (outOpen || errOpen)) {
Sleep(1);
}
}
if (status != calogOkE) {
TerminateProcess(pi.hProcess, 1);
}
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exitStatus);
if (stdinThread != NULL) {
WaitForSingleObject(stdinThread, INFINITE);
CloseHandle(stdinThread);
}
CloseHandle(outRead);
CloseHandle(errRead);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
if (status != calogOkE) {
free(outBuf.data);
free(errBuf.data);
return status;
}
status = procResult(result, (int32_t)exitStatus, &outBuf, &errBuf);
free(outBuf.data);
free(errBuf.data);
return status;
}
// Append arg to cmd as one command-line token, quoted per the CommandLineToArgvW rules: a run of
// backslashes is doubled only when it precedes a double quote or ends the (quoted) token, and the
// token is wrapped in quotes when it is empty or contains whitespace or a quote.
static int32_t procWinAppendArg(ProcBufT *cmd, const char *arg) {
size_t len;
size_t i;
bool quote;
len = strlen(arg);
quote = (len == 0);
for (i = 0; i < len && !quote; i++) {
if (arg[i] == ' ' || arg[i] == '\t' || arg[i] == '\n' || arg[i] == '\v' || arg[i] == '"') {
quote = true;
}
}
if (!quote) {
return procBufAppend(cmd, arg, len);
}
if (procBufAppend(cmd, "\"", 1) != calogOkE) {
return calogErrOomE;
}
i = 0;
while (i < len) {
size_t slashes;
size_t s;
slashes = 0;
while (i < len && arg[i] == '\\') {
slashes++;
i++;
}
if (i == len) {
slashes *= 2; // trailing backslashes precede the closing quote
} else if (arg[i] == '"') {
slashes = slashes * 2 + 1; // backslashes then the escaped quote
}
for (s = 0; s < slashes; s++) {
if (procBufAppend(cmd, "\\", 1) != calogOkE) {
return calogErrOomE;
}
}
if (i < len) {
if (procBufAppend(cmd, &arg[i], 1) != calogOkE) {
return calogErrOomE;
}
i++;
}
}
return procBufAppend(cmd, "\"", 1);
}
// Write the whole stdin payload, then close the pipe. Runs on its own thread so a blocking WriteFile
// on a full pipe cannot stall the main thread's stdout/stderr drain.
static DWORD WINAPI procWinStdinThread(LPVOID param) {
ProcWinStdinT *job;
size_t pos;
job = (ProcWinStdinT *)param;
pos = 0;
while (pos < job->length) {
DWORD wrote;
DWORD chunk;
chunk = (job->length - pos > PROC_CHUNK) ? PROC_CHUNK : (DWORD)(job->length - pos);
if (!WriteFile(job->handle, job->bytes + pos, chunk, &wrote, NULL) || wrote == 0) {
break; // the child closed its stdin (or exited); stop feeding it
}
pos += wrote;
}
CloseHandle(job->handle);
return 0;
}
#else
static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
posix_spawn_file_actions_t actions;
ProcBufT outBuf;
@ -316,7 +645,7 @@ static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result,
}
}
if (outIdx >= 0 && (fds[outIdx].revents & (POLLIN | POLLERR | POLLHUP))) {
char chunk[65536];
char chunk[PROC_CHUNK];
ssize_t got;
got = read(outPipe[0], chunk, sizeof(chunk));
if (got > 0) {
@ -331,7 +660,7 @@ static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result,
}
}
if (errIdx >= 0 && (fds[errIdx].revents & (POLLIN | POLLERR | POLLHUP))) {
char chunk[65536];
char chunk[PROC_CHUNK];
ssize_t got;
got = read(errPipe[0], chunk, sizeof(chunk));
if (got > 0) {
@ -358,6 +687,11 @@ static int32_t procRun(CalogValueT *args, int32_t argCount, CalogValueT *result,
if (errPipe[0] >= 0) {
close(errPipe[0]);
}
// If we aborted early (e.g. the output cap was exceeded), the child may still be running and
// blocked writing to the pipe ends we just closed; kill it so waitpid cannot hang forever.
if (status != calogOkE) {
kill(pid, SIGKILL);
}
while (waitpid(pid, &waitStatus, 0) < 0 && errno == EINTR) {
continue;
}

View file

@ -1,16 +1,21 @@
// calogProc.h -- calog subprocess library: run a child process, capture its output.
//
// procRun(argv: list(string) [, opts: map]) -> map{ exit: int, stdout: string, stderr: string }
// Spawn argv[0] with the given arguments, wait for it, and return its exit code (or the
// negative signal number if it was killed) plus its captured stdout/stderr (binary-safe).
// opts: { stdin: string (fed to the child), cwd: string (working directory), env: map
// (string->string; REPLACES the environment -- omit to inherit the parent's) }.
// Spawn argv[0] with the given arguments, wait for it, and return its exit code plus its
// captured stdout/stderr (binary-safe). On POSIX a child killed by a signal reports the
// negative signal number as exit. opts: { stdin: string (fed to the child), cwd: string
// (working directory), env: map (string->string; REPLACES the environment -- omit to inherit
// the parent's) }.
//
// The child is started with posix_spawn (NOT fork -- calog runs many pthreads, and fork in a
// multithreaded process is a well-known hazard). The native is inline and blocks the calling
// context's own thread until the child exits, feeding stdin while draining stdout/stderr through a
// single poll loop so a large transfer cannot deadlock. Subprocess spawning is POSIX-only; on
// Windows the native returns an "unsupported" error rather than a half-working implementation.
// POSIX starts the child with posix_spawn (NOT fork -- calog runs many pthreads, and fork in a
// multithreaded process is a well-known hazard) and multiplexes the three pipes with one poll loop.
// Windows starts it with CreateProcess and, because anonymous pipes have no poll, writes stdin on a
// helper thread while the main thread drains stdout/stderr; the argv list is quoted into a single
// command line. NOTE: on Windows a bare program name (no path separator) is resolved by
// CreateProcess's search order, which includes the application directory and the current directory
// AHEAD of PATH -- pass an absolute/relative path when that shadowing matters. Either way the native
// is inline and blocks the calling context's own thread until the child exits, feeding stdin while
// draining stdout/stderr so a large transfer cannot deadlock.
#ifndef CALOG_PROC_H
#define CALOG_PROC_H

View file

@ -30,12 +30,4 @@
// across runtimes (shared registry).
int32_t calogPubsubRegister(CalogT *calog);
// Release every subscribed function (once the last registered runtime unregisters). Like the
// export library, call this while the subscribing contexts are still ALIVE -- before you
// close them and before calogDestroy -- because a subscription is a live reference into its
// owner's interpreter; releasing it after that context is gone would touch freed memory. The
// static registry bookkeeping itself is intentionally never freed (the natives stay callable
// until calogDestroy), so this is safe to call and re-register across runtimes.
void calogPubsubShutdown(void);
#endif

View file

@ -39,9 +39,4 @@
// process-wide connection registry and a single libssh2_init). Returns calogOkE or an error.
int32_t calogSshRegister(CalogT *calog);
// Close any still-open connections, free the process-wide registry, and call libssh2_exit
// when the last runtime unregisters. Call it AFTER the runtime is torn down (calogDestroy),
// since it invalidates the natives' state.
void calogSshShutdown(void);
#endif

View file

@ -49,8 +49,4 @@ int32_t calogTaskRegister(CalogT *calog);
// promptly instead of lingering until calogDestroy. Returns how many were reaped.
int32_t calogTaskReap(void);
// Free the process-wide task registry. Call it AFTER the runtime is torn down
// (calogDestroy), which is what actually closes any still-open task contexts.
void calogTaskShutdown(void);
#endif

View file

@ -18,6 +18,13 @@
// finalize (as in calogExport); it never re-enters the timer library.
#define _POSIX_C_SOURCE 200809L
#if defined(__APPLE__)
// macOS pthreads have no pthread_condattr_setclock, so this file waits with a RELATIVE timeout
// (pthread_cond_timedwait_relative_np) on Darwin instead of an absolute CLOCK_MONOTONIC deadline.
// That _np extension is only declared when _DARWIN_C_SOURCE is set (strict _POSIX_C_SOURCE hides
// it). Apple-only; the Linux/Windows compile never sees this.
#define _DARWIN_C_SOURCE
#endif
#include "calogTimer.h"
@ -127,7 +134,12 @@ static int32_t timerEnsureThreadLocked(void) {
if (pthread_condattr_init(&attr) != 0) {
return calogErrUnsupportedE;
}
#if defined(__linux__)
// Only Linux can pin the condvar to CLOCK_MONOTONIC (so timerThreadMain waits on the absolute
// monotonic deadline directly). macOS has no pthread_condattr_setclock, and winpthreads rejects any
// non-realtime clock (returns EINVAL) -- both use a realtime-based wait in timerThreadMain instead.
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
#endif
if (pthread_cond_init(&gTimerCond, &attr) != 0) {
pthread_condattr_destroy(&attr);
return calogErrUnsupportedE;
@ -316,9 +328,37 @@ static void *timerThreadMain(void *arg) {
struct timespec target;
int64_t fireAt;
fireAt = gTimers[minIndex].nextFireMonoNs;
#if defined(__APPLE__)
// macOS condvars wait against the realtime clock, but the scheduler is monotonic, so
// wait a RELATIVE interval (fireAt - now). pthread_cond_timedwait_relative_np is clock-
// agnostic, preserving the CLOCK_MONOTONIC timing intent across wall-clock changes.
{
int64_t deltaNs;
deltaNs = fireAt - nowNs;
target.tv_sec = (time_t)(deltaNs / NS_PER_SEC);
target.tv_nsec = (long)(deltaNs % NS_PER_SEC);
pthread_cond_timedwait_relative_np(&gTimerCond, &gTimerMutex, &target);
}
#elif defined(_WIN32)
// winpthreads rejects a non-realtime condvar clock, so gTimerCond is CLOCK_REALTIME. An
// absolute CLOCK_MONOTONIC deadline (time-since-boot) reads as decades in the past against
// realtime, so pthread_cond_timedwait would return instantly and the thread would busy-spin
// at 100% CPU. Convert the monotonic delay to an absolute REALTIME deadline instead.
{
struct timespec rt;
int64_t deadlineNs;
clock_gettime(CLOCK_REALTIME, &rt);
deadlineNs = (int64_t)rt.tv_sec * NS_PER_SEC + rt.tv_nsec + (fireAt - nowNs);
target.tv_sec = (time_t)(deadlineNs / NS_PER_SEC);
target.tv_nsec = (long)(deadlineNs % NS_PER_SEC);
pthread_cond_timedwait(&gTimerCond, &gTimerMutex, &target);
}
#else
// Linux: condvar pinned to CLOCK_MONOTONIC; wait on the absolute monotonic deadline.
target.tv_sec = (time_t)(fireAt / NS_PER_SEC);
target.tv_nsec = (long)(fireAt % NS_PER_SEC);
pthread_cond_timedwait(&gTimerCond, &gTimerMutex, &target);
#endif
continue;
}
{

View file

@ -20,10 +20,4 @@
// registry is reference-counted, so this is safe to call on any number of runtimes.
int32_t calogTimerRegister(CalogT *calog);
// Release every still-pending timer's callback and (once the last registered runtime
// unregisters) stop the background thread and free the timer list. Like calogExport, call
// this while the timer-owning contexts are still ALIVE -- before you close them and before
// calogDestroy -- because a pending callback is a live reference into its owner's interpreter.
void calogTimerShutdown(void);
#endif

View file

@ -10,6 +10,33 @@
#include <pthread.h>
#ifdef _WIN32
// memmem is a GNU/BSD extension that mingw-w64's libc does not provide, so calog's own scanners
// (calogHttp header parsing) get a portable fallback on Windows only. POSIX builds use
// the libc memmem via <string.h> and never see this. static inline: no link-time duplication.
#include <string.h>
static inline void *memmem(const void *haystack, size_t haystackLen, const void *needle, size_t needleLen) {
const unsigned char *hay;
const unsigned char *ndl;
size_t i;
if (needleLen == 0) {
return (void *)haystack;
}
if (haystackLen < needleLen) {
return NULL;
}
hay = (const unsigned char *)haystack;
ndl = (const unsigned char *)needle;
for (i = 0; i + needleLen <= haystackLen; i++) {
if (hay[i] == ndl[0] && memcmp(hay + i, ndl, needleLen) == 0) {
return (void *)(hay + i);
}
}
return NULL;
}
#endif
// The host context's id (the thread that owns the runtime). A callable owned by the host
// (ownerCtxId == CALOG_HOST_ID) has no separate interpreter to outlive.
#define CALOG_HOST_ID 0
@ -132,8 +159,9 @@ bool calogContextRegistered(CalogT *runtime, uint64_t ctxId);
// ---- per-context resource limits (sandboxing) ----
// The mutable state a limited context enforces on its OWN thread: memUsed is charged by the engine's
// allocator (only Lua and QuickJS can do this cleanly), and deadlineMs is a monotonic-clock deadline
// the engine's periodic hook checks. Touched only on the context thread, so no atomics are needed.
// allocator (Lua and QuickJS natively; my-basic via a global counting allocator + a thread-local owner,
// since its allocator is process-global -- see mybasicAdapter.c), and deadlineMs is a monotonic-clock
// deadline the engine's periodic hook checks. Touched only on the context thread, so no atomics needed.
typedef struct CalogLimitStateT {
int64_t memUsed; // bytes currently charged against the cap
int64_t memCap; // 0 = unlimited
@ -199,4 +227,19 @@ int32_t calogMapSetBool(CalogAggT *map, const char *key, bool flag);
void calogRegistryRetain(pthread_mutex_t *initMutex, int32_t *refCount);
void calogRegistryRelease(pthread_mutex_t *initMutex, int32_t *refCount, void (*freeAll)(void));
// ---- library shutdown hooks ----
// Each is defined in its libs/calog<Name>.c and registered with the runtime via calogAtDestroy, so
// calogDestroy runs it automatically in the right phase. They are internal, NOT part of the
// embedding surface -- the per-library headers no longer declare them, and an embedder never calls
// them by hand. (A couple of tests reach them here to exercise post-shutdown behavior directly.)
void calogArchiveShutdown(void);
void calogDbShutdown(void);
void calogExportShutdown(void);
void calogKvShutdown(void);
void calogNetShutdown(void);
void calogPubsubShutdown(void);
void calogSshShutdown(void);
void calogTaskShutdown(void);
void calogTimerShutdown(void);
#endif

View file

@ -30,7 +30,6 @@
#include "calogExport.h"
#include "calogFs.h"
#include "calogHttp.h"
#include "calogHttpd.h"
#include "calogJson.h"
#include "calogKv.h"
#include "calogNet.h"
@ -54,6 +53,13 @@
#include <strings.h>
#include <time.h>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h> // SetConsoleCtrlHandler: Windows never delivers SIGTERM and has no SIGHUP
#endif
// One host-thread pump iteration parks this long between drains (0.5 ms), matching the test
// harness -- long enough not to spin a core, short enough to feel responsive.
#define PUMP_INTERVAL_NS 500000
@ -61,6 +67,9 @@
// The base for a signal-derived exit code (128 + signal number), the shell convention.
#define SIGNAL_EXIT_BASE 128
#ifdef _WIN32
static BOOL WINAPI consoleHandler(DWORD ctrlType);
#endif
static const CalogEngineT *engineForExtension(const char *ext);
static const char *extensionOf(const char *arg);
static int32_t nativeCalogExit(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -119,7 +128,6 @@ static const struct {
{ "export", calogExportRegister },
{ "fs", calogFsRegister },
{ "http", calogHttpRegister },
{ "httpd", calogHttpdRegister },
{ "json", calogJsonRegister },
{ "kv", calogKvRegister },
{ "net", calogNetRegister },
@ -250,6 +258,19 @@ static void onSignal(int sig) {
}
#ifdef _WIN32
// Windows delivers no SIGTERM and no SIGHUP; console/session events (Ctrl+C, Ctrl+Break, window
// close, logoff, shutdown) arrive here instead. Route them to the same orderly teardown as onSignal
// (reporting a SIGTERM-style exit code) and return TRUE to mark the event handled.
static BOOL WINAPI consoleHandler(DWORD ctrlType) {
(void)ctrlType;
atomic_store(&gExitCode, (int32_t)(SIGNAL_EXIT_BASE + SIGTERM));
atomic_store(&gShutdown, true);
return TRUE;
}
#endif
static void printUsage(FILE *stream, const char *program) {
size_t index;
@ -483,6 +504,11 @@ int main(int argc, char **argv) {
signal(SIGINT, onSignal);
signal(SIGTERM, onSignal);
#ifdef _WIN32
// Catch the console/session events Windows uses in place of SIGTERM/SIGHUP so an external
// terminate, window close, logoff, or shutdown still tears down cleanly.
SetConsoleCtrlHandler(consoleHandler, TRUE);
#endif
#ifdef SIGPIPE
// Ignore SIGPIPE so a peer resetting a socket cannot terminate the process. Linux avoids the
// signal via MSG_NOSIGNAL on send(), but macOS lacks that flag, so this is the portable guard.

View file

@ -43,7 +43,11 @@ static inline void calogPlatformNetShutdown(void) { WSACleanup(); }
// Best-effort message for the last socket error (Winsock has no errno; strerror does not map it).
// Uses a thread-local buffer so concurrent context threads do not clobber each other.
static inline const char *calogSockErrStr(void) {
static __declspec(thread) char buffer[256];
// _Thread_local (C11) rather than __declspec(thread): the clang/mingw cross-toolchain silently
// ignores __declspec(thread) here, which would make this buffer process-global and let
// concurrent context threads clobber each other; _Thread_local is honored and keeps it
// per-thread. Windows-only code (inside the _WIN32 branch), so POSIX builds are unaffected.
static _Thread_local char buffer[256];
int err;
DWORD written;
err = WSAGetLastError();
@ -89,12 +93,25 @@ static inline void calogPlatformNetShutdown(void) { (void)0; }
#endif
// SIGPIPE suppression on send(): Linux uses the MSG_NOSIGNAL flag; macOS/Windows lack it (macOS
// uses the SO_NOSIGPIPE socket option, Windows has no SIGPIPE). Falls back to 0 where absent.
// SIGPIPE suppression on send(): Linux uses the MSG_NOSIGNAL send() flag (CALOG_MSG_NOSIGNAL below);
// macOS has no MSG_NOSIGNAL, so the SO_NOSIGPIPE socket option (set via calogSockNoSigpipe at socket
// creation) is the per-socket guard there; Windows has no SIGPIPE at all. Falls back to 0 where absent.
#ifdef MSG_NOSIGNAL
#define CALOG_MSG_NOSIGNAL MSG_NOSIGNAL
#else
#define CALOG_MSG_NOSIGNAL 0
#endif
// Suppress SIGPIPE for writes to socket `s`. On macOS this is the ONLY per-socket guard (no
// MSG_NOSIGNAL), so an embedder linking a calog transport without the CLI's process-wide
// signal(SIGPIPE, SIG_IGN) stays safe. No-op on Linux (MSG_NOSIGNAL covers it) and Windows (no SIGPIPE).
static inline void calogSockNoSigpipe(CalogSocketT s) {
#ifdef SO_NOSIGPIPE
int on = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&on, sizeof(on));
#else
(void)s;
#endif
}
#endif

View file

@ -15,10 +15,11 @@
#include <stdlib.h>
#include <string.h>
#define MB_BANK_SIZE 256
#define MB_INITIAL_ARGS 8
#define MB_NAME_MAX 128
#define MB_NATIVE_ERROR SE_RN_FAILED_TO_OPERATE
#define MB_BANK_SIZE 256
#define MB_INITIAL_ARGS 8
#define MB_NAME_MAX 128
#define MB_NATIVE_ERROR SE_RN_FAILED_TO_OPERATE
#define MB_STEP_TIME_CHECK 256 // check the wall-clock deadline once per this many statements
typedef struct BindingT {
const char *name; // registry name; dispatched through calogCall so the actor
@ -39,15 +40,44 @@ typedef struct MbForeignFnT {
CalogFnT *callable;
} MbForeignFnT;
// A binary byte-buffer value, my-basic's faithful container for a calog string that carries embedded
// NUL bytes (which a my-basic MB_DT_STRING, being a NUL-terminated C string, cannot hold). It is a
// refcounted usertype-ref: the VM already dispatches clone/dtor/hash/cmp/fmt and the '+' meta-operator
// to the hooks below, so the whole type lives in this adapter and the vendored VM's string path is
// untouched. `data` owns exactly `length` bytes and is NOT NUL-terminated. The tag distinguishes our
// byte refs from the foreign-fn refs above (both are usertype-refs) on the way back out.
#define MB_BYTES_TAG 0x43425954u
typedef struct MbBytesT {
uint32_t tag;
size_t length;
uint8_t *data;
} MbBytesT;
struct CalogMyBasicT {
struct mb_interpreter_t *bas;
CalogT *broker;
uint64_t ctxId;
void **currentL;
CalogLimitStateT *limits; // sandbox limits (mem cap + deadline), or NULL if unlimited
uint32_t stepCounter; // amortises the wall-clock check in the per-statement hook
BindingT bank[MB_BANK_SIZE];
int32_t bankCount;
};
// my-basic's memory manager (mb_set_memory_manager) is process-global and gets only a size, no
// interpreter -- so a per-context memory cap is charged through a thread-local pointer to the currently
// running context's limit state (contexts run on dedicated threads, so this is exact and race-free) plus
// a small header on each allocation recording its size and owning context. The allocator only COUNTS --
// it never refuses, because my-basic assumes infallible allocation (mb_malloc asserts); the cap is
// enforced at the next statement boundary by mbStepHandler. NULL owner = an allocation outside any capped
// context (mb_init singletons, or an unlimited context), which is never charged.
typedef struct MbAllocHeaderT {
size_t size; // total bytes malloc'd (this header included), charged to owner
CalogLimitStateT *owner; // context whose memUsed this is charged to, or NULL
} MbAllocHeaderT;
static _Thread_local CalogLimitStateT *gMbLimits = NULL;
// Serializes my-basic context lifecycle (mb_init/mb_dispose + mbContextCount); the engine
// holds it, via calogMyBasicLifecycleLock, around create/destroy. NOT held during execution,
// so scripts run concurrently. Lock and counter live together so the invariant is local.
@ -55,21 +85,41 @@ static pthread_mutex_t mbLifecycleLock = PTHREAD_MUTEX_INITIALIZER;
static int32_t mbContextCount = 0;
static int32_t mbAggregateToCollDepth(CalogMyBasicT *context, void **l, const CalogAggT *aggregate, mb_value_t *out, int32_t depth);
static int mbByteAt(struct mb_interpreter_t *s, void **l);
static int mbByteConcat(struct mb_interpreter_t *s, void **l);
static int mbByteLen(struct mb_interpreter_t *s, void **l);
static int mbBytesAdd(struct mb_interpreter_t *s, void **l, mb_value_t *fst, mb_value_t *scd, mb_value_t *ret);
static void *mbBytesClone(struct mb_interpreter_t *s, void *p);
static int mbBytesCmp(struct mb_interpreter_t *s, void *p1, void *p2);
static void mbBytesDtor(struct mb_interpreter_t *s, void *p);
static int mbBytesFmt(struct mb_interpreter_t *s, void *p, char *buf, unsigned size);
static unsigned mbBytesHash(struct mb_interpreter_t *s, void *p);
static int mbByteSlice(struct mb_interpreter_t *s, void **l);
static int32_t mbBytesMake(CalogMyBasicT *context, void **l, const uint8_t *src, size_t length, mb_value_t *out);
static bool mbBytesOperand(struct mb_interpreter_t *s, void **l, mb_value_t value, const uint8_t **out, size_t *len);
static MbBytesT *mbBytesUnwrap(struct mb_interpreter_t *s, void **l, mb_value_t value);
static int mbByteToStr(struct mb_interpreter_t *s, void **l);
static int32_t mbCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void mbCallableRelease(CalogFnT *callable);
static int32_t mbDictToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth);
static int mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l);
static void mbDisposePopped(CalogMyBasicT *context, mb_value_t value);
static int mbDynamicFuncHandler(struct mb_interpreter_t *s, void **l, const char *name);
static void *mbForeignFnClone(struct mb_interpreter_t *s, void *p);
static void mbForeignFnDtor(struct mb_interpreter_t *s, void *p);
static int mbForeignInvokeNative(struct mb_interpreter_t *s, void **l);
static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogValueT *value, mb_value_t *out, int32_t depth);
static int mbInputer(struct mb_interpreter_t *s, const char *prompt, char *buffer, int size);
static int mbInvokeWithOpenBracket(CalogMyBasicT *context, struct mb_interpreter_t *s, void **l, CalogFnT *callable);
static char *mbLimitAlloc(unsigned long long size);
static void mbLimitFree(char *ptr);
static int32_t mbListToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth);
static int mbPrinter(struct mb_interpreter_t *s, const char *format, ...);
static void mbReleaseSetValue(CalogMyBasicT *context, mb_value_t value);
static void mbReportError(CalogMyBasicT *context, const char *stage);
static void mbRetainBeforeSet(CalogMyBasicT *context, void **l, mb_value_t value);
static int mbStepHandler(struct mb_interpreter_t *s, void **l, const char *file, int pos, unsigned short row, unsigned short col);
static int mbStrToByte(struct mb_interpreter_t *s, void **l);
static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t *value, CalogValueT *out, int32_t depth);
static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFnT **out);
@ -435,6 +485,484 @@ static int32_t mbAggregateToCollDepth(CalogMyBasicT *context, void **l, const Ca
}
// byteAt(b, i) -> the i-th byte of b as an integer 0..255. Byte-accurate: an embedded 0x00 reads back
// as 0, unlike ASC on a string which stops at the first NUL.
static int mbByteAt(struct mb_interpreter_t *s, void **l) {
CalogMyBasicT *context;
MbBytesT *holder;
mb_value_t arg;
void *userData;
int_t index;
int_t result;
int code;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
code = mb_attempt_open_bracket(s, l);
if (code != MB_FUNC_OK) {
return code;
}
mb_make_nil(arg);
if (mb_pop_value(s, l, &arg) != MB_FUNC_OK) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
holder = mbBytesUnwrap(s, l, arg);
index = 0;
if (holder == NULL || mb_pop_int(s, l, &index) != MB_FUNC_OK) {
mbDisposePopped(context, arg);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
if (index < 0 || (size_t)index >= holder->length) {
mbDisposePopped(context, arg);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
// Read the byte before disposing arg -- disposing the ref frees holder->data.
result = (int_t)holder->data[index];
mbDisposePopped(context, arg);
if (mb_attempt_close_bracket(s, l) != MB_FUNC_OK) {
return MB_FUNC_ERR;
}
return mb_push_int(s, l, result);
}
// byteConcat(a, b, ...) -> a new byte buffer that is the byte-wise concatenation of every argument.
// Each argument may be a byte buffer or a string (a string contributes its bytes), so a response can
// be assembled from text headers and a binary body in one call.
static int mbByteConcat(struct mb_interpreter_t *s, void **l) {
CalogMyBasicT *context;
mb_value_t result;
void *userData;
uint8_t *buffer;
size_t length;
size_t capacity;
int code;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
code = mb_attempt_open_bracket(s, l);
if (code != MB_FUNC_OK) {
return code;
}
buffer = NULL;
length = 0;
capacity = 0;
while (mb_has_arg(s, l)) {
mb_value_t arg;
const uint8_t *bytes;
size_t count;
mb_make_nil(arg);
if (mb_pop_value(s, l, &arg) != MB_FUNC_OK) {
free(buffer);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
bytes = NULL;
count = 0;
if (!mbBytesOperand(s, l, arg, &bytes, &count)) {
// A refused arg (a list/dict/routine) is an owned reference and must be released; a
// borrowed string or scalar is not -- mbDisposePopped encodes that distinction.
mbDisposePopped(context, arg);
free(buffer);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
if (count > 0) {
if (length + count > capacity) {
size_t newCap;
uint8_t *grown;
newCap = capacity == 0 ? length + count : capacity;
while (newCap < length + count) {
newCap *= CALOG_GROWTH_FACTOR;
}
grown = (uint8_t *)realloc(buffer, newCap);
if (grown == NULL) {
mbDisposePopped(context, arg);
free(buffer);
return mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
}
buffer = grown;
capacity = newCap;
}
// Copy the operand's bytes before disposing arg (which frees a byte ref's buffer).
memcpy(buffer + length, bytes, count);
length += count;
}
mbDisposePopped(context, arg);
}
if (mb_attempt_close_bracket(s, l) != MB_FUNC_OK) {
free(buffer);
return MB_FUNC_ERR;
}
mb_make_nil(result);
code = (int)mbBytesMake(context, l, buffer, length, &result);
free(buffer);
if (code != (int)calogOkE) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
return mb_push_value(s, l, result);
}
// byteLen(b) -> the number of bytes in b (its true byte count, embedded NULs included).
static int mbByteLen(struct mb_interpreter_t *s, void **l) {
CalogMyBasicT *context;
MbBytesT *holder;
mb_value_t arg;
void *userData;
int_t length;
int code;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
code = mb_attempt_open_bracket(s, l);
if (code != MB_FUNC_OK) {
return code;
}
mb_make_nil(arg);
if (mb_pop_value(s, l, &arg) != MB_FUNC_OK) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
holder = mbBytesUnwrap(s, l, arg);
if (holder == NULL) {
mbDisposePopped(context, arg);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
length = (int_t)holder->length;
mbDisposePopped(context, arg);
if (mb_attempt_close_bracket(s, l) != MB_FUNC_OK) {
return MB_FUNC_ERR;
}
return mb_push_int(s, l, length);
}
// The '+' meta-operator for byte buffers: fires when either operand is a byte ref, so "text" + bytes
// and bytes + bytes both yield a new byte buffer. Operands are borrowed (the VM converted them to the
// public views fst/scd and marshals the result back), so this only reads them.
static int mbBytesAdd(struct mb_interpreter_t *s, void **l, mb_value_t *fst, mb_value_t *scd, mb_value_t *ret) {
CalogMyBasicT *context;
void *userData;
const uint8_t *left;
const uint8_t *right;
uint8_t *buffer;
size_t leftLen;
size_t rightLen;
mb_make_nil(*ret);
if (!mbBytesOperand(s, l, *fst, &left, &leftLen) || !mbBytesOperand(s, l, *scd, &right, &rightLen)) {
return MB_FUNC_ERR;
}
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
buffer = NULL;
if (leftLen + rightLen > 0) {
buffer = (uint8_t *)malloc(leftLen + rightLen);
if (buffer == NULL) {
return MB_FUNC_ERR;
}
if (leftLen > 0) {
memcpy(buffer, left, leftLen);
}
if (rightLen > 0) {
memcpy(buffer + leftLen, right, rightLen);
}
}
if (mbBytesMake(context, l, buffer, leftLen + rightLen, ret) != calogOkE) {
free(buffer);
return MB_FUNC_ERR;
}
free(buffer);
return MB_FUNC_OK;
}
// Deep-clone hook: assignment copies a byte buffer by value (its own buffer), matching my-basic string
// semantics. Returns the new payload, or NULL on OOM (the VM leaves the target nil).
static void *mbBytesClone(struct mb_interpreter_t *s, void *p) {
MbBytesT *src;
MbBytesT *dup;
(void)s;
src = (MbBytesT *)p;
dup = (MbBytesT *)malloc(sizeof(*dup));
if (dup == NULL) {
return NULL;
}
dup->tag = src->tag;
dup->length = src->length;
dup->data = NULL;
if (src->length > 0) {
dup->data = (uint8_t *)malloc(src->length);
if (dup->data == NULL) {
free(dup);
return NULL;
}
memcpy(dup->data, src->data, src->length);
}
return dup;
}
// Compare hook, used for dict-key equality and (via the fork's comparison patch) the = <> < > operators.
// Byte-accurate: memcmp over the shared length, then shorter-sorts-first. Defensive against a foreign
// (non-byte) usertype-ref reaching here -- treats a tag mismatch as not-equal without touching its body.
static int mbBytesCmp(struct mb_interpreter_t *s, void *p1, void *p2) {
MbBytesT *a;
MbBytesT *b;
size_t shared;
int order;
(void)s;
a = (MbBytesT *)p1;
b = (MbBytesT *)p2;
if (a == NULL || b == NULL || a->tag != MB_BYTES_TAG || b->tag != MB_BYTES_TAG) {
return a == b ? 0 : 1;
}
shared = a->length < b->length ? a->length : b->length;
order = shared > 0 ? memcmp(a->data, b->data, shared) : 0;
if (order != 0) {
return order;
}
if (a->length < b->length) {
return -1;
}
if (a->length > b->length) {
return 1;
}
return 0;
}
// Destructor hook: frees the buffer and the payload when the last reference drops.
static void mbBytesDtor(struct mb_interpreter_t *s, void *p) {
MbBytesT *holder;
(void)s;
holder = (MbBytesT *)p;
if (holder != NULL) {
free(holder->data);
free(holder);
}
}
// Format hook for PRINT: a compact, terminal-safe summary (the raw bytes are not printable).
static int mbBytesFmt(struct mb_interpreter_t *s, void *p, char *buf, unsigned size) {
MbBytesT *holder;
(void)s;
holder = (MbBytesT *)p;
return snprintf(buf, size, "bytes[%zu]", holder->length);
}
// Hash hook (FNV-1a over the bytes) for byte buffers used as dict keys. Must agree with mbBytesCmp:
// equal content hashes equal.
static unsigned mbBytesHash(struct mb_interpreter_t *s, void *p) {
MbBytesT *holder;
unsigned hash;
size_t index;
(void)s;
holder = (MbBytesT *)p;
hash = 2166136261u;
for (index = 0; index < holder->length; index++) {
hash ^= holder->data[index];
hash *= 16777619u;
}
return hash;
}
// byteSlice(b, start, count) -> a new byte buffer of up to `count` bytes starting at `start`. Start and
// count are clamped to the buffer; a negative start or count yields an empty slice.
static int mbByteSlice(struct mb_interpreter_t *s, void **l) {
CalogMyBasicT *context;
MbBytesT *holder;
mb_value_t arg;
mb_value_t result;
void *userData;
int_t start;
int_t count;
size_t from;
size_t take;
int code;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
code = mb_attempt_open_bracket(s, l);
if (code != MB_FUNC_OK) {
return code;
}
mb_make_nil(arg);
if (mb_pop_value(s, l, &arg) != MB_FUNC_OK) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
holder = mbBytesUnwrap(s, l, arg);
start = 0;
count = 0;
if (holder == NULL || mb_pop_int(s, l, &start) != MB_FUNC_OK || mb_pop_int(s, l, &count) != MB_FUNC_OK) {
mbDisposePopped(context, arg);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
from = start < 0 ? holder->length : (size_t)start;
if (from > holder->length) {
from = holder->length;
}
take = count < 0 ? 0 : (size_t)count;
if (take > holder->length - from) {
take = holder->length - from;
}
mb_make_nil(result);
// mbBytesMake copies, so it is safe to build the slice before disposing arg.
code = (int)mbBytesMake(context, l, holder->data + from, take, &result);
mbDisposePopped(context, arg);
if (code != (int)calogOkE) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
if (mb_attempt_close_bracket(s, l) != MB_FUNC_OK) {
mb_dispose_value(context->bas, result);
return MB_FUNC_ERR;
}
return mb_push_value(s, l, result);
}
// Build a byte-buffer value from `length` bytes at `src` (copied). Installs the '+' concat operator on
// the new ref; clones preserve it (_clone_usertype_ref copies calc_operators). `l` may be NULL on a
// deferred callback -- mb_override_value never dereferences it in the ref path but rejects NULL, so a
// stand-in is passed.
static int32_t mbBytesMake(CalogMyBasicT *context, void **l, const uint8_t *src, size_t length, mb_value_t *out) {
MbBytesT *holder;
void *fallback;
mb_make_nil(*out);
holder = (MbBytesT *)malloc(sizeof(*holder));
if (holder == NULL) {
return calogErrOomE;
}
holder->tag = MB_BYTES_TAG;
holder->length = length;
holder->data = NULL;
if (length > 0) {
holder->data = (uint8_t *)malloc(length);
if (holder->data == NULL) {
free(holder);
return calogErrOomE;
}
memcpy(holder->data, src, length);
}
if (mb_make_ref_value(context->bas, holder, out, mbBytesDtor, mbBytesClone, mbBytesHash, mbBytesCmp, mbBytesFmt) != MB_FUNC_OK) {
free(holder->data);
free(holder);
return calogErrOomE;
}
fallback = NULL;
mb_override_value(context->bas, l != NULL ? l : &fallback, *out, MB_MF_ADD, (void *)(intptr_t)mbBytesAdd);
return calogOkE;
}
// Extract the raw bytes of an operand for concatenation: a byte ref contributes its buffer, a string
// contributes its bytes (up to its NUL). Returns false for any other type.
static bool mbBytesOperand(struct mb_interpreter_t *s, void **l, mb_value_t value, const uint8_t **out, size_t *len) {
MbBytesT *holder;
holder = mbBytesUnwrap(s, l, value);
if (holder != NULL) {
*out = holder->data;
*len = holder->length;
return true;
}
if (value.type == MB_DT_STRING) {
*out = (const uint8_t *)value.value.string;
*len = value.value.string != NULL ? strlen(value.value.string) : 0;
return true;
}
return false;
}
// Recover the byte payload from a value, or NULL if it is not one of our byte refs (the tag rejects a
// foreign-fn ref, which is also a usertype-ref).
static MbBytesT *mbBytesUnwrap(struct mb_interpreter_t *s, void **l, mb_value_t value) {
void *refData;
if (value.type != MB_DT_USERTYPE_REF) {
return NULL;
}
refData = NULL;
if (mb_get_ref_value(s, l, value, &refData) != MB_FUNC_OK) {
return NULL;
}
if (refData == NULL || ((MbBytesT *)refData)->tag != MB_BYTES_TAG) {
return NULL;
}
return (MbBytesT *)refData;
}
// byteToStr(b) -> a my-basic string holding b's bytes. Best-effort text view: embedded NULs make the
// string look truncated to string builtins, but the bytes are copied faithfully.
static int mbByteToStr(struct mb_interpreter_t *s, void **l) {
CalogMyBasicT *context;
MbBytesT *holder;
mb_value_t arg;
void *userData;
char *scratch;
char *owned;
int code;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
code = mb_attempt_open_bracket(s, l);
if (code != MB_FUNC_OK) {
return code;
}
mb_make_nil(arg);
if (mb_pop_value(s, l, &arg) != MB_FUNC_OK) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
holder = mbBytesUnwrap(s, l, arg);
if (holder == NULL) {
mbDisposePopped(context, arg);
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
if (mb_attempt_close_bracket(s, l) != MB_FUNC_OK) {
mbDisposePopped(context, arg);
return MB_FUNC_ERR;
}
// mb_push_string lazily frees the buffer, so it must be the interpreter allocator's (mb_memdup).
// holder->data is read here, before arg is disposed.
scratch = (char *)malloc(holder->length + 1);
if (scratch == NULL) {
mbDisposePopped(context, arg);
return mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
}
if (holder->length > 0) {
memcpy(scratch, holder->data, holder->length);
}
scratch[holder->length] = '\0';
owned = mb_memdup(scratch, (unsigned)(holder->length + 1));
free(scratch);
mbDisposePopped(context, arg);
if (owned == NULL) {
return mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
}
return mb_push_string(s, l, owned);
}
static int32_t mbCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
MyBasicRoutineT *routine;
CalogMyBasicT *context;
@ -601,7 +1129,7 @@ static int mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l) {
// popped was never handed to the marshaller, so any collection or
// routine scope it owns is ours to free here (same rule as the
// refused-argument path below), or it leaks.
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT || popped.type == MB_DT_ROUTINE) {
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT || popped.type == MB_DT_ROUTINE || popped.type == MB_DT_USERTYPE_REF) {
mb_dispose_value(context->bas, popped);
}
code = mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
@ -611,10 +1139,10 @@ static int mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l) {
capacity = newCap;
}
status = mbToValueDepth(context, l, &popped, &args[argCount], 0);
// A popped collection is owned by us (the consumer) and must be released
// once marshalled; popped strings are borrowed interior pointers and
// routine values are owned by their scope, so neither is disposed here.
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT) {
// A popped collection or usertype-ref (a byte buffer or a foreign callable) is owned by us
// (the consumer) and must be released once marshalled; popped strings are borrowed interior
// pointers and routine values are owned by their scope, so neither of those is disposed here.
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT || popped.type == MB_DT_USERTYPE_REF) {
mb_dispose_value(context->bas, popped);
}
if (status != calogOkE) {
@ -673,6 +1201,18 @@ cleanupArgs:
}
// Release a value popped from a native's argument list, but only when we own it: a collection, routine,
// or usertype-ref (a byte buffer or a foreign callable) is an owned reference that mb_pop_value handed
// us; a string is a BORROWED interior pointer and a scalar owns nothing, so disposing either would be
// wrong -- a borrowed string would be double-freed. Same rule the mbDispatch argument loop applies to a
// popped value it did not hand off.
static void mbDisposePopped(CalogMyBasicT *context, mb_value_t value) {
if (value.type == MB_DT_LIST || value.type == MB_DT_DICT || value.type == MB_DT_ROUTINE || value.type == MB_DT_USERTYPE_REF) {
mb_dispose_value(context->bas, value);
}
}
// [calog fork hook] Resolve a bare name called like a function -- exportedFn(args) -- against the
// export registry (case-insensitively, since my-basic uppercases identifiers at parse time) and,
// if it is an export, consume the (args) and invoke it. Returns MB_FUNC_IGNORE when the name is
@ -797,15 +1337,20 @@ static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogVal
mb_make_int(*out, value->as.b ? 1 : 0);
return calogOkE;
case calogIntE:
if (value->as.i < INT32_MIN || value->as.i > INT32_MAX) {
return calogErrRangeE;
}
// my-basic int_t is now 64-bit (vendored fork), so the full calogIntE range passes
// through without the old >2^31 clamp.
mb_make_int(*out, (int_t)value->as.i);
return calogOkE;
case calogRealE:
mb_make_real(*out, (real_t)value->as.r);
return calogOkE;
case calogStringE:
// A calog string carries an explicit length and may hold embedded NULs. One that does is
// not representable as a my-basic C-string, so it ingresses as a binary byte buffer; a
// NUL-free string stays a plain my-basic string (existing behaviour, zero change for text).
if (value->as.s.length > 0 && memchr(value->as.s.bytes, '\0', (size_t)value->as.s.length) != NULL) {
return mbBytesMake(context, l, (const uint8_t *)value->as.s.bytes, (size_t)value->as.s.length, out);
}
duplicate = mb_memdup(value->as.s.bytes, (unsigned)(value->as.s.length + 1));
if (duplicate == NULL) {
return calogErrOomE;
@ -837,6 +1382,19 @@ static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogVal
}
// Sandbox INPUT: never read host stdin (the default inputer, mb_gets, calls fgets(stdin), which would
// block the context thread and read host input). A my-basic INPUT yields an empty line instead, so I/O
// stays on calog natives like every other engine.
static int mbInputer(struct mb_interpreter_t *s, const char *prompt, char *buffer, int size) {
(void)s;
(void)prompt;
if (size > 0) {
buffer[0] = '\0';
}
return 0;
}
// Shared by mbForeignInvokeNative and mbDynamicFuncHandler: with '(' already consumed, pop the
// remaining arguments, consume ')', invoke `callable` (borrowed -- the caller owns the reference),
// and push the marshalled result. Returns an MB_FUNC_* code. Mirrors mbDispatch's arg discipline.
@ -867,7 +1425,7 @@ static int mbInvokeWithOpenBracket(CalogMyBasicT *context, struct mb_interpreter
newCap = (capacity == 0) ? MB_INITIAL_ARGS : capacity * CALOG_GROWTH_FACTOR;
grown = (CalogValueT *)realloc(args, (size_t)newCap * sizeof(CalogValueT));
if (grown == NULL) {
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT || popped.type == MB_DT_ROUTINE) {
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT || popped.type == MB_DT_ROUTINE || popped.type == MB_DT_USERTYPE_REF) {
mb_dispose_value(context->bas, popped);
}
code = mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
@ -877,7 +1435,7 @@ static int mbInvokeWithOpenBracket(CalogMyBasicT *context, struct mb_interpreter
capacity = newCap;
}
status = mbToValueDepth(context, l, &popped, &args[argCount], 0);
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT) {
if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT || popped.type == MB_DT_USERTYPE_REF) {
mb_dispose_value(context->bas, popped);
}
if (status != calogOkE) {
@ -927,6 +1485,46 @@ cleanup:
}
// The counting allocator installed via mb_set_memory_manager (see MbAllocHeaderT). It always succeeds
// (my-basic asserts allocation never fails); it only records the size and charges the running context's
// memUsed, so mbStepHandler can retire a script that overruns its memory cap at the next statement.
static char *mbLimitAlloc(unsigned long long size) {
CalogLimitStateT *owner;
MbAllocHeaderT *base;
size_t total;
owner = gMbLimits;
total = sizeof(MbAllocHeaderT) + (size_t)size;
base = (MbAllocHeaderT *)malloc(total);
if (base == NULL) {
return NULL;
}
base->size = total;
base->owner = (owner != NULL && owner->memCap > 0) ? owner : NULL;
if (base->owner != NULL) {
base->owner->memUsed += (int64_t)total;
}
return (char *)(base + 1);
}
// The matching free: uncharges the context recorded in the header. Memory is charged on the owning
// context's thread and freed on the same thread (each interpreter is single-threaded), so memUsed needs
// no atomics.
static void mbLimitFree(char *ptr) {
MbAllocHeaderT *base;
if (ptr == NULL) {
return;
}
base = ((MbAllocHeaderT *)ptr) - 1;
if (base->owner != NULL) {
base->owner->memUsed -= (int64_t)base->size;
}
free(base);
}
static int32_t mbListToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth) {
CalogAggT *aggregate;
int32_t status;
@ -1013,6 +1611,76 @@ static void mbRetainBeforeSet(CalogMyBasicT *context, void **l, mb_value_t value
}
// Per-statement hook (mb_debug_set_stepped_handler), installed only for a limited context. The statement
// boundary is the one point my-basic can be aborted cleanly -- returning non-OK unwinds the interpreter
// like a runtime error. Memory is checked every statement (a cheap compare against the memUsed the
// allocator maintains); the wall-clock deadline every MB_STEP_TIME_CHECK statements, to amortise the
// clock read. Either breach retires the context (as the Lua/JS hooks do) and unwinds the script.
static int mbStepHandler(struct mb_interpreter_t *s, void **l, const char *file, int pos, unsigned short row, unsigned short col) {
CalogMyBasicT *context;
void *userData;
(void)file;
(void)pos;
(void)row;
(void)col;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
if (context == NULL || context->limits == NULL) {
return MB_FUNC_OK;
}
if (context->limits->memCap > 0 && context->limits->memUsed > context->limits->memCap) {
calogCurrentRetire();
return mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
}
if (context->limits->deadlineMs != 0) {
context->stepCounter++;
if (context->stepCounter >= MB_STEP_TIME_CHECK) {
context->stepCounter = 0;
if (calogMonotonicMillis() >= context->limits->deadlineMs) {
calogCurrentRetire();
return mb_raise_error(s, l, SE_RN_PROGRAM_TOO_LONG, MB_FUNC_ERR);
}
}
}
return MB_FUNC_OK;
}
// strToByte(str) -> a byte buffer holding the string's bytes (up to its NUL; a my-basic string cannot
// itself carry an embedded NUL). The counterpart of byteToStr.
static int mbStrToByte(struct mb_interpreter_t *s, void **l) {
CalogMyBasicT *context;
mb_value_t result;
void *userData;
char *text;
int code;
userData = NULL;
mb_get_userdata(s, &userData);
context = (CalogMyBasicT *)userData;
code = mb_attempt_open_bracket(s, l);
if (code != MB_FUNC_OK) {
return code;
}
text = NULL;
if (mb_pop_string(s, l, &text) != MB_FUNC_OK || text == NULL) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
if (mb_attempt_close_bracket(s, l) != MB_FUNC_OK) {
return MB_FUNC_ERR;
}
mb_make_nil(result);
code = (int)mbBytesMake(context, l, (const uint8_t *)text, strlen(text), &result);
if (code != (int)calogOkE) {
return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
}
return mb_push_value(s, l, result);
}
static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t *value, CalogValueT *out, int32_t depth) {
CalogFnT *callable;
int32_t status;
@ -1049,6 +1717,17 @@ static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t
}
calogValueFn(out, callable);
return calogOkE;
case MB_DT_USERTYPE_REF: {
// Our own binary byte buffer egresses as a length-carrying calog string -- faithful, NUL
// bytes and all. A foreign-fn ref (the only other usertype-ref this adapter makes) has no
// value analogue on the way out and stays unsupported.
MbBytesT *holder;
holder = mbBytesUnwrap(context->bas, l, *value);
if (holder != NULL) {
return calogValueString(out, holder->data != NULL ? (const char *)holder->data : "", (int64_t)holder->length);
}
return calogErrUnsupportedE;
}
default:
return calogErrUnsupportedE;
}
@ -1080,7 +1759,7 @@ static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFn
}
int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId) {
int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId, CalogLimitStateT *limits) {
CalogMyBasicT *context;
struct mb_interpreter_t *bas;
@ -1090,26 +1769,53 @@ int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId)
return calogErrOomE;
}
if (mbContextCount == 0) {
// Wrap my-basic's process-global allocator once, before the first allocation, so a limited
// context can charge its memory. gMbLimits is NULL here (a fresh context thread), so the
// mb_init singletons are charged to no context.
mb_set_memory_manager(mbLimitAlloc, mbLimitFree);
mb_init();
}
// Charge this context's allocations (mb_open onward) to its limit state. This runs on the context's
// dedicated thread, so the thread-local is exact for this context's whole lifetime.
gMbLimits = limits;
bas = NULL;
if (mb_open(&bas) != MB_FUNC_OK || bas == NULL) {
if (mbContextCount == 0) {
mb_dispose();
}
gMbLimits = NULL;
free(context);
return calogErrOomE;
}
context->bas = bas;
context->broker = broker;
context->ctxId = ctxId;
context->currentL = NULL;
context->bas = bas;
context->broker = broker;
context->ctxId = ctxId;
context->currentL = NULL;
context->limits = limits;
context->stepCounter = 0;
mbContextCount++;
mb_set_userdata(bas, context);
mb_set_printer(bas, mbPrinter);
// Sandbox host stdin: a my-basic INPUT yields an empty line instead of reading it (see mbInputer).
mb_set_inputer(bas, mbInputer);
// A limited context checks its memory and time budgets at every statement boundary. Unlimited
// contexts install nothing, so the step hook adds no per-statement cost to the common case.
if (limits != NULL) {
mb_debug_set_stepped_handler(bas, mbStepHandler, NULL);
}
// A host callable handed into a script (mbFromValueDepth's calogFnE case) becomes a
// usertype-ref; calogInvoke(fn, ...args) is how a BASIC script calls it.
mb_register_func(bas, "calogInvoke", mbForeignInvokeNative);
// Binary byte-buffer helpers -- my-basic's faithful handling of calog strings that carry embedded
// NUL bytes. my-basic-only: every other engine's strings are already binary-safe, so these live in
// the adapter rather than the shared broker. '+' concatenates byte buffers via a meta-operator, so
// no byteConcat is strictly required, but it is handy for joining several parts (including strings).
mb_register_func(bas, "byteAt", mbByteAt);
mb_register_func(bas, "byteConcat", mbByteConcat);
mb_register_func(bas, "byteLen", mbByteLen);
mb_register_func(bas, "byteSlice", mbByteSlice);
mb_register_func(bas, "byteToStr", mbByteToStr);
mb_register_func(bas, "strToByte", mbStrToByte);
// Let a bare name that is otherwise undefined but called like a function resolve to an
// export -- so exportedFn(args) works without an explicit calogCall (like the hook engines).
mb_set_dynamic_func_handler(bas, mbDynamicFuncHandler);

View file

@ -17,7 +17,11 @@
typedef struct CalogMyBasicT CalogMyBasicT;
int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId);
// `limits` is the context's sandbox limit state (memory cap + wall-clock deadline), or NULL for an
// unlimited context. A limited context installs a per-statement step hook that retires the script when
// it exceeds its memory or time budget; my-basic's global allocator is wrapped to charge per-context
// memory. See mbStepHandler / mbLimitAlloc.
int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId, CalogLimitStateT *limits);
void calogMyBasicDestroy(CalogMyBasicT *context);
int32_t calogMyBasicExportRoutine(CalogMyBasicT *context, const char *routineName, CalogFnT **out);
int32_t calogMyBasicExpose(CalogMyBasicT *context, const char *name);

View file

@ -41,7 +41,7 @@ static int32_t mybasicEngineCreate(CalogContextT *context, void **interpOut) {
*interpOut = NULL;
calogMyBasicLifecycleLock();
status = calogMyBasicCreate(&mb, calogContextBroker(context), calogContextId(context));
status = calogMyBasicCreate(&mb, calogContextBroker(context), calogContextId(context), calogContextLimitState(context));
if (status == calogOkE) {
calogForEach(calogContextBroker(context), mybasicExposeVisitor, mb);
*interpOut = mb;

38
tests/fuzzParser.c Normal file
View file

@ -0,0 +1,38 @@
// fuzzParser.c -- one libFuzzer driver, compiled once per parser. The build selects the parser by
// #include'ing its whole translation unit (so the static entry point is visible) and naming its
// native via -DFUZZ_INCLUDE and -DFUZZ_NATIVE, e.g.
//
// clang -fsanitize=fuzzer,address,undefined -DFUZZ_INCLUDE='"calogJson.c"' \
// -DFUZZ_NATIVE=jsonParseNative ... tests/fuzzParser.c src/value.c src/broker.c ...
//
// Each parser native has the same shape (CalogValueT *args, int32_t argCount, CalogValueT *result,
// void *userData): we wrap the raw fuzz bytes in a binary-safe string as args[0] and drive the
// parser directly, so the fuzzer explores the parser/marshaller, not the script layer. See the
// `make fuzz` target. Build with clang throughout -- libcalog.a is gcc-ASan and cannot be mixed
// with clang's fuzzer/ASan runtime, so the core sources are compiled fresh alongside this file.
#include <stddef.h>
#include <stdint.h>
#include "calog.h"
// The parser under test, pulled in whole so its static native is reachable.
#include FUZZ_INCLUDE
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
CalogValueT args[1];
CalogValueT result;
if (calogValueString(&args[0], (const char *)data, (int64_t)size) != calogOkE) {
return 0;
}
// argCount 1: parsers that take an optional second argument (e.g. csvParse's delimiter) use
// their default, which is exactly the path we want to hammer with arbitrary input.
if (FUZZ_NATIVE(args, 1, &result, NULL) == calogOkE) {
calogValueFree(&result);
} else {
calogValueFree(&result); // a failed parse still initialises result (calogFail path)
}
calogValueFree(&args[0]);
return 0;
}

View file

@ -17,16 +17,60 @@
#define PUMP_LIMIT 4000
#define CONTEXT_COUNT 3
// A value well beyond 32 bits: my-basic's int_t is a 64-bit long long in calog's fork, so this
// round-trips as a literal, through arithmetic, and both across the host boundary. 5e9 > 2^32.
#define WIDE_VALUE 5000000000LL
#define WIDE_ADDEND 1000000000LL
#define WIDE_SUM (WIDE_VALUE + WIDE_ADDEND)
static CalogT *calog = NULL;
static _Atomic int32_t bumpCount = 0;
static _Atomic uint64_t bumpCtxId = 0xFFFFu;
static _Atomic int64_t reportedValue = 0;
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
static int32_t blobEcho(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t blobLen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t bump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int64_t evalAndReport(const char *script);
static int32_t makeBlob(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t makeBlob2(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetWide(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t report(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void testBinaryStrings(void);
static void testConcurrentContexts(void);
static void testForeignFunction(void);
static void testHostNative(void);
static void testWideInteger(void);
static int32_t blobEcho(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogStringE) {
return calogFail(result, calogErrArgE, "blobEcho expects one string");
}
// Hand the same bytes straight back; the script re-ingresses them, exercising the egress ->
// ingress round-trip for a binary buffer (the httpd tcpRecv -> manipulate -> tcpSend path).
return calogValueString(result, args[0].as.s.bytes, args[0].as.s.length);
}
static int32_t blobLen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogStringE) {
return calogFail(result, calogErrArgE, "blobLen expects one string");
}
// Reports the egress byte length: a byte buffer must arrive as a length-3 string, not truncated
// to length 1 at the embedded NUL.
calogValueInt(result, args[0].as.s.length);
return calogOkE;
}
static int32_t bump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
@ -49,6 +93,193 @@ static void checkImpl(bool condition, const char *message, const char *file, int
}
// Open a fresh my-basic context, run `script` (which must end by calling bump()), pump until it
// finishes, and return whatever the script last handed to report(). Serialises the boilerplate the
// foreign-function and wide-integer tests share.
static int64_t evalAndReport(const char *script) {
CalogContextT *ctx;
struct timespec ts = { 0, 500000 };
int32_t i;
ctx = calogContextOpen(calog, &calogMyBasicEngine);
atomic_store(&bumpCount, 0);
atomic_store(&reportedValue, 0);
calogContextEval(ctx, script);
for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 1; i++) {
calogPump(calog);
nanosleep(&ts, NULL);
}
calogContextClose(ctx);
return atomic_load(&reportedValue);
}
static int32_t makeBlob(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
// A 3-byte binary value with an embedded NUL ('a', 0x00, 'b'). Because it holds a NUL, it
// ingresses to my-basic as a byte buffer rather than a (truncated) string.
return calogValueString(result, "a\0b", 3);
}
static int32_t makeBlob2(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
// Same length as makeBlob() but differs only AFTER the NUL, so a NUL-terminated compare would
// wrongly call it equal -- the byte compare must look past the NUL.
return calogValueString(result, "a\0c", 3);
}
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
return calogFail(result, calogErrArgE, "add expects two integers");
}
calogValueInt(result, args[0].as.i + args[1].as.i);
return calogOkE;
}
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// Hand the script a host-owned function value; invoking it from BASIC routes back to nativeAdd.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeGetWide(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
// A 64-bit value handed into the script, to prove ingress no longer clamps at 2^31.
calogValueInt(result, WIDE_VALUE);
return calogOkE;
}
static int32_t report(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "report expects one integer");
}
atomic_store(&reportedValue, args[0].as.i);
return calogOkE;
}
// Binary byte buffers: my-basic's faithful handling of calog strings that carry embedded NUL bytes,
// via the adapter's usertype-ref BYTE type. Non-zero sentinels (42, 11) are reported on success so a
// script that errored (leaving reportedValue at 0) fails the check instead of passing silently.
static void testBinaryStrings(void) {
int64_t got;
// Egress: a byte buffer handed to a native arrives as a full length-3 string, not truncated to 1.
got = evalAndReport("report(blobLen(makeBlob()))\nbump()");
CHECK(got == 3, "a byte buffer egresses to the host as a full-length binary string");
// byteLen sees the true byte count.
got = evalAndReport("b = makeBlob()\nreport(byteLen(b))\nbump()");
CHECK(got == 3, "byteLen reports the full length including the embedded NUL");
// byteAt reads every byte, the embedded NUL included.
got = evalAndReport(
"b = makeBlob()\n"
"r = 0\n"
"IF byteAt(b, 0) = 97 THEN\n"
"IF byteAt(b, 1) = 0 THEN\n"
"IF byteAt(b, 2) = 98 THEN\n"
"r = 42\n"
"ENDIF\nENDIF\nENDIF\n"
"report(r)\nbump()");
CHECK(got == 42, "byteAt reads each byte including the embedded NUL as 0");
// '+' concatenates byte buffers and the interior NUL survives.
got = evalAndReport(
"b = makeBlob()\n"
"c = b + b\n"
"r = 0\n"
"IF byteLen(c) = 6 THEN\n"
"IF byteAt(c, 4) = 0 THEN\n"
"IF byteAt(c, 3) = 97 THEN\n"
"r = 42\n"
"ENDIF\nENDIF\nENDIF\n"
"report(r)\nbump()");
CHECK(got == 42, "the + operator concatenates byte buffers and preserves the interior NUL");
// Mixed concat: a text prefix plus a binary body (the httpd response-building case).
got = evalAndReport(
"b = makeBlob()\n"
"c = \"X\" + b\n"
"r = 0\n"
"IF byteLen(c) = 4 THEN\n"
"IF byteAt(c, 0) = 88 THEN\n"
"r = 42\n"
"ENDIF\nENDIF\n"
"report(r)\nbump()");
CHECK(got == 42, "a string prefix concatenates with a byte buffer (string + bytes)");
// Content equality via the fork's comparison patch: distinct buffers, identical bytes.
got = evalAndReport("b = makeBlob()\nd = makeBlob()\nIF b = d THEN\nreport(11)\nELSE\nreport(22)\nENDIF\nbump()");
CHECK(got == 11, "= compares byte buffers by content (equal content compares equal)");
// ...and buffers that differ only past the NUL compare unequal.
got = evalAndReport("b = makeBlob()\ne = makeBlob2()\nIF b = e THEN\nreport(11)\nELSE\nreport(22)\nENDIF\nbump()");
CHECK(got == 22, "= distinguishes byte buffers that differ only after the NUL");
// A byte buffer works as a content-addressed dict key (the hash and cmp hooks agree).
got = evalAndReport(
"m = DICT()\n"
"b = makeBlob()\n"
"e = makeBlob2()\n"
"SET(m, b, 10)\n"
"SET(m, e, 20)\n"
"report(GET(m, makeBlob()))\nbump()");
CHECK(got == 10, "a byte buffer is a content-addressed dict key");
// byteSlice extracts a sub-range, NUL included, clamped to the buffer.
got = evalAndReport(
"b = makeBlob()\n"
"s = byteSlice(b, 1, 2)\n"
"r = 0\n"
"IF byteLen(s) = 2 THEN\n"
"IF byteAt(s, 0) = 0 THEN\n"
"IF byteAt(s, 1) = 98 THEN\n"
"r = 42\n"
"ENDIF\nENDIF\nENDIF\n"
"report(r)\nbump()");
CHECK(got == 42, "byteSlice extracts a byte sub-range spanning the NUL");
// byteConcat joins several parts (byte buffers and strings) into one buffer.
got = evalAndReport("report(byteLen(byteConcat(makeBlob(), \"X\", makeBlob())))\nbump()");
CHECK(got == 7, "byteConcat joins byte buffers and strings (3 + 1 + 3 = 7)");
// Round-trip: egress to a native and back keeps the bytes.
got = evalAndReport("report(byteLen(blobEcho(makeBlob())))\nbump()");
CHECK(got == 3, "a byte buffer round-trips out to a native and back intact");
// strToByte / byteToStr round-trip NUL-free text.
got = evalAndReport("IF byteToStr(strToByte(\"hi\")) = \"hi\" THEN\nreport(11)\nELSE\nreport(22)\nENDIF\nbump()");
CHECK(got == 11, "strToByte and byteToStr round-trip NUL-free text");
}
static void testConcurrentContexts(void) {
CalogContextT *ctxs[CONTEXT_COUNT];
struct timespec ts = { 0, 500000 };
@ -73,6 +304,16 @@ static void testConcurrentContexts(void) {
}
static void testForeignFunction(void) {
int64_t got;
// getAdder() yields a host callable; calogInvoke(fn, ...) calls it from BASIC. This is the
// my-basic side of function-into-script -- the fork's usertype-ref + calogInvoke native.
got = evalAndReport("f = getAdder()\nreport(calogInvoke(f, 2, 3))\nbump()");
CHECK(got == 5, "my-basic invoked a host function value handed into the script");
}
static void testHostNative(void) {
CalogContextT *ctx;
struct timespec ts = { 0, 500000 };
@ -92,16 +333,43 @@ static void testHostNative(void) {
}
static void testWideInteger(void) {
int64_t got;
// A >2^32 literal: exercises strtoll parsing and the 64-bit int_t, then egress to the host.
got = evalAndReport("report(5000000000)\nbump()");
CHECK(got == WIDE_VALUE, "my-basic parsed and returned a 64-bit integer literal");
// Ingress (getWide, no clamp) + 64-bit arithmetic in BASIC + egress back to the host.
got = evalAndReport("report(getWide() + 1000000000)\nbump()");
CHECK(got == WIDE_SUM, "my-basic round-tripped a 64-bit value through arithmetic");
// ABS() must use llabs, not the 32-bit C abs(), now that int_t is 64-bit.
got = evalAndReport("report(ABS(0 - 5000000000))\nbump()");
CHECK(got == WIDE_VALUE, "my-basic ABS() preserves a 64-bit magnitude (no int truncation)");
}
int main(void) {
calog = calogCreate();
if (calog == NULL) {
printf("calog create failed\n");
return 1;
}
calogRegister(calog, "blobEcho", blobEcho, NULL);
calogRegister(calog, "blobLen", blobLen, NULL);
calogRegister(calog, "bump", bump, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegister(calog, "getWide", nativeGetWide, NULL);
calogRegister(calog, "makeBlob", makeBlob, NULL);
calogRegister(calog, "makeBlob2", makeBlob2, NULL);
calogRegister(calog, "report", report, NULL);
testHostNative();
testConcurrentContexts();
testForeignFunction();
testWideInteger();
testBinaryStrings();
calogDestroy(calog);

View file

@ -6,6 +6,7 @@
#include "calog.h"
#include "calogExport.h"
#include "calogInternal.h" // calogExportShutdown: internal, exercised here for post-shutdown behavior
#include <stdatomic.h>
#include <stdio.h>

View file

@ -1,171 +0,0 @@
// testHttpd.c -- the polyglot HTTP server. A Lua context and a JavaScript context each stand up a
// server with route handlers written in their own language; the test drives real HTTP requests over
// a socket and checks the responses, a 404, a response map (custom status), and HOT RELOAD
// (re-registering a route on a live server swaps the handler).
#define _POSIX_C_SOURCE 200809L
#include "calog.h"
#include "calogHttpd.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#define LUA_PORT 38900
#define JS_PORT 38901
#define PUMP_LIMIT 4000
static CalogT *calog = NULL;
static _Atomic int32_t readyCount = 0;
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
#define CHECK(cond, msg) checkImpl((cond), (msg), __LINE__)
static void checkImpl(bool condition, const char *message, int32_t line);
static bool httpGet(int port, const char *path, int *statusOut, char *body, size_t bodyCap);
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void pumpForReady(int32_t target);
static void checkImpl(bool condition, const char *message, int32_t line) {
testsRun++;
if (!condition) {
testsFailed++;
printf("FAIL testHttpd.c:%d %s\n", line, message);
}
}
// Minimal HTTP/1.1 client: connect, GET path, parse status code + body.
static bool httpGet(int port, const char *path, int *statusOut, char *body, size_t bodyCap) {
struct sockaddr_in addr;
char request[256];
char response[8192];
char *bodyStart;
ssize_t total;
ssize_t got;
int fd;
int requestLen;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return false;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(fd);
return false;
}
requestLen = snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n", path);
if (send(fd, request, (size_t)requestLen, 0) != requestLen) {
close(fd);
return false;
}
total = 0;
while ((got = recv(fd, response + total, sizeof(response) - 1 - (size_t)total, 0)) > 0) {
total += got;
if ((size_t)total >= sizeof(response) - 1) {
break;
}
}
close(fd);
if (total <= 0) {
return false;
}
response[total] = '\0';
*statusOut = 0;
if (strncmp(response, "HTTP/1.1 ", 9) == 0) {
*statusOut = atoi(response + 9);
}
bodyStart = strstr(response, "\r\n\r\n");
body[0] = '\0';
if (bodyStart != NULL) {
snprintf(body, bodyCap, "%s", bodyStart + 4);
}
return true;
}
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
atomic_fetch_add(&readyCount, 1);
calogValueNil(result);
return calogOkE;
}
static void pumpForReady(int32_t target) {
struct timespec ts = { 0, 500000 };
int32_t i;
for (i = 0; i < PUMP_LIMIT; i++) {
calogPump(calog);
if (atomic_load(&readyCount) >= target) {
return;
}
nanosleep(&ts, NULL);
}
}
int main(void) {
CalogContextT *lua;
CalogContextT *js;
char body[8192];
int status;
calog = calogCreate();
if (calog == NULL) {
printf("calog create failed\n");
return 1;
}
calogHttpdRegister(calog);
calogRegister(calog, "ready", nativeReady, NULL);
// Lua server: a plain-string route, a response-map route (custom status), and a 404 for the rest.
lua = calogContextOpen(calog, &calogLuaEngine);
calogContextEval(lua,
"srv = httpdListen(38900)\n"
"httpdRoute(srv, 'GET', '/hi', function(req) return 'hello from lua path=' .. req.path end)\n"
"httpdRoute(srv, 'GET', '/made', function(req) return { status = 201, body = 'created' } end)\n"
"ready()");
pumpForReady(1);
// JavaScript server on another port -- a handler written in a different language.
js = calogContextOpen(calog, &calogJsEngine);
calogContextEval(js,
"var s = httpdListen(38901);\n"
"httpdRoute(s, 'GET', '/hi', function(req) { return 'hello from js'; });\n"
"ready();");
pumpForReady(2);
CHECK(httpGet(LUA_PORT, "/hi", &status, body, sizeof(body)) && status == 200 && strstr(body, "hello from lua") != NULL && strstr(body, "path=/hi") != NULL, "lua route serves + sees the request path");
CHECK(httpGet(JS_PORT, "/hi", &status, body, sizeof(body)) && status == 200 && strstr(body, "hello from js") != NULL, "js route serves on its own server");
CHECK(httpGet(LUA_PORT, "/made", &status, body, sizeof(body)) && status == 201 && strcmp(body, "created") == 0, "response map sets a custom status");
CHECK(httpGet(LUA_PORT, "/nope", &status, body, sizeof(body)) && status == 404, "an unmatched route is 404");
// Hot reload: re-register /hi on the LIVE lua server with a new handler.
atomic_store(&readyCount, 0);
calogContextEval(lua, "httpdRoute(srv, 'GET', '/hi', function(req) return 'reloaded' end); ready()");
pumpForReady(1);
CHECK(httpGet(LUA_PORT, "/hi", &status, body, sizeof(body)) && status == 200 && strcmp(body, "reloaded") == 0, "re-registering a route hot-swaps the handler");
calogDestroy(calog);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);
return testsFailed == 0 ? 0 : 1;
}

560
tests/testHttpdLua.c Normal file
View file

@ -0,0 +1,560 @@
// testHttpdLua.c -- the httpd-as-a-script (examples/httpd.lua). A Lua context loads the script (which
// implements HTTP/1.1 + routing + keep-alive + WebSocket purely in Lua over calog's tcp* transport and
// crypto natives) and serves on a port; this test drives real requests over a loopback socket: routing,
// a 404, a response-map custom status, HTTP/1.1 keep-alive (two requests on one connection), and a full
// WebSocket handshake + echo. There is no C HTTP code under test -- the point is that the protocol
// lives in the script.
#define _GNU_SOURCE // strcasestr
#include "calog.h"
#include "calogCrypto.h"
#include "calogNet.h"
#include <openssl/ssl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#define PORT 38910
#define HTTPS_PORT 38911
#define PUMP_LIMIT 4000
static CalogT *calog = NULL;
static _Atomic bool serving = true;
static _Atomic bool isReady = false;
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
#define CHECK(cond, msg) checkImpl((cond), (msg), __LINE__)
static void checkImpl(bool condition, const char *message, int32_t line);
static bool genCert(const char *certPath, const char *keyPath);
static int clientConnect(int port);
static bool httpsGet(int port, const char *path, int *statusOut, char *body, size_t bodyCap);
static char *loadScript(const char *path);
static int32_t nativeKeepServing(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static bool readResponse(int fd, int *statusOut, char *body, size_t bodyCap);
static void checkImpl(bool condition, const char *message, int32_t line) {
testsRun++;
if (!condition) {
testsFailed++;
printf("FAIL testHttpdLua.c:%d %s\n", line, message);
}
}
static int clientConnect(int port) {
struct sockaddr_in addr;
int fd;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(fd);
return -1;
}
return fd;
}
// Read up to one full HTTP response (headers + Content-Length body) from fd. Parses the status and
// copies the body out. Returns false on a closed/short read. Consumes EXACTLY one response (headers
// byte-by-byte to the CRLFCRLF, then precisely Content-Length body bytes) so a following pipelined
// response is left in the socket for the next call -- a bulk recv could coalesce two responses and
// make the second read block forever.
static bool readResponse(int fd, int *statusOut, char *body, size_t bodyCap) {
char response[8192];
char *clHeader;
int total;
int headerLen;
int contentLen;
int bodyRead;
total = 0;
headerLen = -1;
while (total < (int)sizeof(response) - 1) {
ssize_t got;
got = recv(fd, response + total, 1, 0);
if (got <= 0) {
break;
}
total += (int)got;
if (total >= 4 && memcmp(response + total - 4, "\r\n\r\n", 4) == 0) {
headerLen = total;
break;
}
}
if (headerLen < 0) {
return false;
}
response[total] = '\0';
contentLen = 0;
clHeader = strcasestr(response, "Content-Length:");
if (clHeader != NULL) {
contentLen = atoi(clHeader + 15);
}
bodyRead = 0;
while (bodyRead < contentLen && headerLen + bodyRead < (int)sizeof(response) - 1) {
ssize_t got;
got = recv(fd, response + headerLen + bodyRead, 1, 0);
if (got <= 0) {
break;
}
bodyRead += (int)got;
}
*statusOut = 0;
if (strncmp(response, "HTTP/1.1 ", 9) == 0) {
*statusOut = atoi(response + 9);
}
response[headerLen + bodyRead] = '\0';
snprintf(body, bodyCap, "%s", response + headerLen);
return true;
}
static char *loadScript(const char *path) {
FILE *f;
char *buffer;
long size;
size_t got;
f = fopen(path, "rb");
if (f == NULL) {
return NULL;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size < 0) {
fclose(f);
return NULL;
}
buffer = (char *)malloc((size_t)size + 1);
if (buffer == NULL) {
fclose(f);
return NULL;
}
got = fread(buffer, 1, (size_t)size, f);
buffer[got] = '\0';
fclose(f);
return buffer;
}
// Generate a throwaway self-signed cert + key via the system openssl CLI, for the HTTPS test.
static bool genCert(const char *certPath, const char *keyPath) {
char cmd[512];
snprintf(cmd, sizeof(cmd),
"openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 1 -nodes -subj /CN=localhost >/dev/null 2>&1",
keyPath, certPath);
return system(cmd) == 0;
}
// Minimal HTTPS client: connect, TLS handshake (no cert verification -- self-signed test cert), GET
// the path, parse the status + body. Exercises the tcp transport's tls option end to end.
static bool httpsGet(int port, const char *path, int *statusOut, char *body, size_t bodyCap) {
SSL_CTX *ctx;
SSL *ssl;
char request[256];
char response[8192];
char *bodyStart;
int fd;
int total;
int requestLen;
bool ok;
ctx = SSL_CTX_new(TLS_client_method());
if (ctx == NULL) {
return false;
}
fd = clientConnect(port);
if (fd < 0) {
SSL_CTX_free(ctx);
return false;
}
ssl = SSL_new(ctx);
if (ssl == NULL) {
close(fd);
SSL_CTX_free(ctx);
return false;
}
SSL_set_fd(ssl, fd);
ok = SSL_connect(ssl) == 1;
if (ok) {
requestLen = snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n", path);
SSL_write(ssl, request, requestLen);
total = 0;
for (;;) {
int got;
got = SSL_read(ssl, response + total, (int)sizeof(response) - 1 - total);
if (got <= 0) {
break;
}
total += got;
if (total >= (int)sizeof(response) - 1) {
break;
}
}
response[total > 0 ? total : 0] = '\0';
*statusOut = 0;
if (strncmp(response, "HTTP/1.1 ", 9) == 0) {
*statusOut = atoi(response + 9);
}
bodyStart = strstr(response, "\r\n\r\n");
body[0] = '\0';
if (bodyStart != NULL) {
snprintf(body, bodyCap, "%s", bodyStart + 4);
}
ok = total > 0;
}
if (ssl != NULL) {
SSL_shutdown(ssl);
SSL_free(ssl);
}
close(fd);
SSL_CTX_free(ctx);
return ok;
}
static int32_t nativeKeepServing(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
calogValueBool(result, atomic_load(&serving));
return calogOkE;
}
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
atomic_store(&isReady, true);
calogValueNil(result);
return calogOkE;
}
int main(void) {
CalogContextT *lua;
CalogContextT *tls;
char *script;
char *source;
char body[8192];
const char *setup;
int i;
int status;
int fd;
calog = calogCreate();
if (calog == NULL) {
printf("calog create failed\n");
return 1;
}
calogNetRegister(calog);
calogCryptoRegister(calog);
calogRegisterInline(calog, "keepServing", nativeKeepServing, NULL);
calogRegisterInline(calog, "ready", nativeReady, NULL);
script = loadScript("examples/httpd.lua");
if (script == NULL) {
printf("could not load examples/httpd.lua\n");
return 1;
}
// Wrap the module (it ends in `return httpd`) so it binds to a global, then register routes and
// serve. keepServing lets this test stop the accept loop; ready fires once the socket is bound.
setup =
"\nlocal s = httpd.new()\n"
"s:route('GET', '/hi', function(req) return 'hello ' .. req.path end)\n"
"s:route('GET', '/made', function(req) return { status = 201, body = 'created' } end)\n"
"s:route('GET', '/boom', function(req) return nil .. 'x' end)\n" /* runtime error -> 500 */
"s:websocket('/ws', function(msg) return 'echo: ' .. msg.message end)\n"
"s:serve(38910, { keep = keepServing, onReady = ready })\n";
source = (char *)malloc(strlen(script) + strlen(setup) + 64);
if (source == NULL) {
return 1;
}
sprintf(source, "httpd = (function()\n%s\nend)()\n%s", script, setup);
free(script);
lua = calogContextOpen(calog, &calogLuaEngine);
calogContextEval(lua, source);
free(source);
// Wait until the script reports the socket is listening (it runs on its own context thread).
for (i = 0; i < PUMP_LIMIT && !atomic_load(&isReady); i++) {
struct timespec ts = { 0, 500000 };
calogPump(calog);
nanosleep(&ts, NULL);
}
CHECK(atomic_load(&isReady), "the script httpd bound and is listening");
// --- plain HTTP routing (Connection: close) ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
send(fd, req, strlen(req), 0);
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0, "GET route serves and sees the path");
close(fd);
} else {
CHECK(false, "connect for GET /hi");
}
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /made HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
send(fd, req, strlen(req), 0);
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 201 && strcmp(body, "created") == 0, "response map sets a custom status");
close(fd);
}
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /nope HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
send(fd, req, strlen(req), 0);
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 404, "an unmatched route is 404");
close(fd);
}
// --- HTTP/1.1 keep-alive: two requests on ONE connection ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n";
bool first;
bool second;
send(fd, req, strlen(req), 0);
first = readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0;
send(fd, req, strlen(req), 0);
second = readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0;
CHECK(first && second, "keep-alive serves two requests on one connection");
close(fd);
}
// --- WebSocket: handshake (RFC 6455 sample key -> known accept) + a masked echo round-trip ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /ws HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n";
unsigned char frame[10];
unsigned char reply[64];
char handshake[1024];
ssize_t got;
ssize_t rgot;
send(fd, req, strlen(req), 0);
got = recv(fd, handshake, sizeof(handshake) - 1, 0);
handshake[got > 0 ? got : 0] = '\0';
CHECK(got > 0 && strstr(handshake, "101 Switching Protocols") != NULL &&
strstr(handshake, "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") != NULL,
"WebSocket handshake returns the RFC 6455 accept value");
// One masked text frame carrying "ping": FIN|text, MASK|len=4, mask 01020304, "ping" XOR mask.
frame[0] = 0x81;
frame[1] = 0x80 | 4;
frame[2] = 0x01; frame[3] = 0x02; frame[4] = 0x03; frame[5] = 0x04;
frame[6] = (unsigned char)('p' ^ 0x01);
frame[7] = (unsigned char)('i' ^ 0x02);
frame[8] = (unsigned char)('n' ^ 0x03);
frame[9] = (unsigned char)('g' ^ 0x04);
send(fd, frame, sizeof(frame), 0);
rgot = recv(fd, reply, sizeof(reply), 0);
// Server frame: 0x81, len=10 (unmasked), "echo: ping".
CHECK(rgot >= 12 && reply[0] == 0x81 && reply[1] == 10 && memcmp(reply + 2, "echo: ping", 10) == 0,
"WebSocket echoes a masked text frame back through the Lua handler");
close(fd);
}
// --- WebSocket FRAGMENTATION: "hello" split across a text frame (FIN=0) + a continuation (FIN=1) ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /ws HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n";
unsigned char frames[16];
unsigned char reply[64];
char handshake[512];
ssize_t rgot;
send(fd, req, strlen(req), 0);
recv(fd, handshake, sizeof(handshake) - 1, 0); // consume the 101
// Frame 1: opcode 0x1 (text) FIN=0, masked, len 3 "hel" ^ {1,2,3,4}
frames[0] = 0x01; frames[1] = 0x83;
frames[2] = 1; frames[3] = 2; frames[4] = 3; frames[5] = 4;
frames[6] = (unsigned char)('h' ^ 1); frames[7] = (unsigned char)('e' ^ 2); frames[8] = (unsigned char)('l' ^ 3);
// Frame 2: opcode 0x0 (continuation) FIN=1, masked, len 2 "lo" ^ {1,2,3,4}
frames[9] = 0x80; frames[10] = 0x82;
frames[11] = 1; frames[12] = 2; frames[13] = 3; frames[14] = 4;
frames[15] = (unsigned char)('l' ^ 1);
send(fd, frames, sizeof(frames), 0);
send(fd, (const unsigned char[]){ (unsigned char)('o' ^ 2) }, 1, 0); // last masked byte
rgot = recv(fd, reply, sizeof(reply), 0);
// Server reply frame: 0x81, len 11, "echo: hello" -- the two fragments were reassembled.
CHECK(rgot >= 13 && reply[0] == 0x81 && reply[1] == 11 && memcmp(reply + 2, "echo: hello", 11) == 0,
"WebSocket reassembles a fragmented text message");
close(fd);
}
// --- A handler that ERRORS returns 500, and the server keeps serving afterward (pcall isolation) ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /boom HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
send(fd, req, strlen(req), 0);
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 500, "a handler runtime error becomes a 500");
close(fd);
}
// --- An abrupt RST mid-request must NOT kill the server (native I/O raises; handle's pcall catches) ---
fd = clientConnect(PORT);
if (fd >= 0) {
struct linger sl;
const char *partial = "GET /hi HTTP/1.1\r\nHost: x\r\n"; // no terminating CRLF -> server waits, then sees RST
sl.l_onoff = 1;
sl.l_linger = 0; // close() sends RST, not FIN
setsockopt(fd, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl));
send(fd, partial, strlen(partial), 0);
close(fd);
}
{
struct timespec ts = { 0, 20000000 };
nanosleep(&ts, NULL); // let the server process the RST
}
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
send(fd, req, strlen(req), 0);
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0,
"server survives a client RST mid-request and keeps serving");
close(fd);
}
// --- HTTP/1.1 PIPELINING: two requests in one write; the leftover buffer must carry the second ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *reqs = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n"
"GET /made HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n";
bool one;
bool two;
send(fd, reqs, strlen(reqs), 0);
one = readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0;
two = readResponse(fd, &status, body, sizeof(body)) && status == 201 && strcmp(body, "created") == 0;
CHECK(one && two, "pipelined second request (buffered past the first) is served, not lost");
close(fd);
}
// --- SMUGGLING: a Transfer-Encoding request must be REJECTED (its trailing bytes must not be
// re-parsed as a smuggled pipelined request via the leftover buffer) ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "POST /made HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n"
"0\r\n\r\nGET /made HTTP/1.1\r\nHost: x\r\n\r\n";
bool served;
send(fd, req, strlen(req), 0);
served = readResponse(fd, &status, body, sizeof(body));
CHECK(!served || status != 201, "Transfer-Encoding request rejected; the smuggled GET /made is not served");
close(fd);
}
// --- SMUGGLING: conflicting duplicate Content-Length must be REJECTED (CL.CL desync) ---
fd = clientConnect(PORT);
if (fd >= 0) {
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nContent-Length: 0\r\nContent-Length: 5\r\nConnection: close\r\n\r\nhello";
bool served;
send(fd, req, strlen(req), 0);
served = readResponse(fd, &status, body, sizeof(body));
CHECK(!served || status != 200, "conflicting duplicate Content-Length is rejected");
close(fd);
}
// --- HTTPS: a second context serves TLS with a generated self-signed cert (tcp tls option) ---
tls = NULL;
{
const char *certPath = "/tmp/calogHttpdTest-cert.pem";
const char *keyPath = "/tmp/calogHttpdTest-key.pem";
if (genCert(certPath, keyPath)) {
char *tlsScript;
char *tlsSource;
char tlsSetup[512];
atomic_store(&isReady, false);
tlsScript = loadScript("examples/httpd.lua");
snprintf(tlsSetup, sizeof(tlsSetup),
"\nlocal s = httpd.new()\n"
"s:route('GET', '/hi', function(req) return 'secure ' .. req.path end)\n"
"s:serve(38911, { tls = true, cert = '%s', key = '%s', keep = keepServing, onReady = ready })\n",
certPath, keyPath);
tlsSource = tlsScript != NULL ? (char *)malloc(strlen(tlsScript) + strlen(tlsSetup) + 64) : NULL;
if (tlsSource != NULL) {
sprintf(tlsSource, "httpd = (function()\n%s\nend)()\n%s", tlsScript, tlsSetup);
tls = calogContextOpen(calog, &calogLuaEngine);
calogContextEval(tls, tlsSource);
for (i = 0; i < PUMP_LIMIT && !atomic_load(&isReady); i++) {
struct timespec ts = { 0, 500000 };
calogPump(calog);
nanosleep(&ts, NULL);
}
CHECK(httpsGet(HTTPS_PORT, "/hi", &status, body, sizeof(body)) && status == 200 && strcmp(body, "secure /hi") == 0,
"HTTPS: the tcp transport's tls option serves an encrypted request");
// A bad handshake (plain HTTP to the TLS port) makes tcpAccept raise; the accept loop
// must survive it. Fire one, then confirm the TLS server still serves.
{
int bad;
bad = clientConnect(HTTPS_PORT);
if (bad >= 0) {
const char *probe = "GET / HTTP/1.1\r\nHost: x\r\n\r\n"; // not a TLS ClientHello
send(bad, probe, strlen(probe), 0);
close(bad);
}
{
struct timespec ts = { 0, 20000000 };
nanosleep(&ts, NULL);
}
CHECK(httpsGet(HTTPS_PORT, "/hi", &status, body, sizeof(body)) && status == 200,
"TLS server survives a bad/plain-HTTP handshake probe and keeps serving");
}
}
free(tlsScript);
free(tlsSource);
unlink(certPath);
unlink(keyPath);
} else {
printf("SKIP: openssl CLI could not generate a test cert (HTTPS check skipped)\n");
}
}
// Stop the accept loops and let the context threads unwind before teardown.
atomic_store(&serving, false);
for (i = 0; i < PUMP_LIMIT; i++) {
struct timespec ts = { 0, 500000 };
calogPump(calog);
nanosleep(&ts, NULL);
}
calogContextClose(lua);
if (tls != NULL) {
calogContextClose(tls);
}
calogDestroy(calog);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);
return testsFailed == 0 ? 0 : 1;
}

View file

@ -2,7 +2,10 @@
// runs on loopback with a freshly generated SELF-SIGNED certificate (not chained to any trusted
// CA). A default httpGet must therefore REJECT it (verification on), while an httpRequest with
// insecure=true must ACCEPT it (verification off). Proves both the default-secure behavior and
// the documented opt-out.
// the documented opt-out. A third request then proves the trust-store loader end to end: with
// CALOG_CA_BUNDLE pointing at the server's own certificate, a DEFAULT (verifying) request must
// SUCCEED, since the pinned bundle makes that certificate a trusted anchor. The certificate carries
// a subjectAltName of DNS:localhost so hostname verification passes for an https://localhost URL.
#define _GNU_SOURCE
@ -14,6 +17,7 @@
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
@ -23,6 +27,7 @@
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)
@ -36,6 +41,9 @@ static _Atomic int32_t errorCount = 0;
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
static int gListenFd = -1;
static X509 *gCert = NULL;
static EVP_PKEY *gPkey = NULL;
static char gCaFile[64] = { 0 };
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static EVP_PKEY *makeSelfSigned(X509 **certOut);
@ -44,6 +52,7 @@ static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *re
static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntilDone(int32_t target);
static void *serverThread(void *arg);
static bool writeCaBundle(char *path, size_t cap, X509 *cert);
static void checkImpl(bool condition, const char *message, const char *file, int32_t line) {
@ -56,8 +65,10 @@ static void checkImpl(bool condition, const char *message, const char *file, int
static EVP_PKEY *makeSelfSigned(X509 **certOut) {
EVP_PKEY *pkey;
X509 *cert;
EVP_PKEY *pkey;
X509 *cert;
X509_EXTENSION *ext;
X509V3_CTX v3ctx;
pkey = EVP_RSA_gen(2048);
if (pkey == NULL) {
@ -75,6 +86,18 @@ static EVP_PKEY *makeSelfSigned(X509 **certOut) {
X509_set_pubkey(cert, pkey);
X509_NAME_add_entry_by_txt(X509_get_subject_name(cert), "CN", MBSTRING_ASC, (const unsigned char *)"127.0.0.1", -1, -1, 0);
X509_set_issuer_name(cert, X509_get_subject_name(cert));
// subjectAltName = DNS:localhost, so SSL_set1_host("localhost") hostname verification passes for
// the https://localhost request that trusts this certificate via CALOG_CA_BUNDLE.
X509V3_set_ctx_nodb(&v3ctx);
X509V3_set_ctx(&v3ctx, cert, cert, NULL, NULL, 0);
ext = X509V3_EXT_conf_nid(NULL, &v3ctx, NID_subject_alt_name, "DNS:localhost");
if (ext == NULL) {
X509_free(cert);
EVP_PKEY_free(pkey);
return NULL;
}
X509_add_ext(cert, ext, -1);
X509_EXTENSION_free(ext);
if (X509_sign(cert, pkey, EVP_sha256()) == 0) {
X509_free(cert);
EVP_PKEY_free(pkey);
@ -135,27 +158,21 @@ static void pumpUntilDone(int32_t target) {
static void *serverThread(void *arg) {
static const char response[] = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello";
SSL_CTX *ctx;
EVP_PKEY *pkey;
X509 *cert;
int n;
(void)arg;
cert = NULL;
pkey = makeSelfSigned(&cert);
if (pkey == NULL) {
return NULL;
}
ctx = SSL_CTX_new(TLS_server_method());
if (ctx == NULL) {
X509_free(cert);
EVP_PKEY_free(pkey);
return NULL;
}
SSL_CTX_use_certificate(ctx, cert);
SSL_CTX_use_PrivateKey(ctx, pkey);
// Two connections: the first (default verify) has its handshake aborted by the client when
// it rejects the untrusted cert; the second (insecure=true) completes and gets the reply.
for (n = 0; n < 2; n++) {
// gCert / gPkey are created (and freed) by main so the certificate can also be written to the
// CALOG_CA_BUNDLE pin file the third request trusts.
SSL_CTX_use_certificate(ctx, gCert);
SSL_CTX_use_PrivateKey(ctx, gPkey);
// Three connections: the first (default verify) has its handshake aborted by the client when it
// rejects the untrusted cert; the second (insecure=true) completes and gets the reply; the third
// (default verify, cert pinned via CALOG_CA_BUNDLE) verifies successfully and gets the reply.
for (n = 0; n < 3; n++) {
SSL *ssl;
int cfd;
cfd = accept(gListenFd, NULL, NULL);
@ -174,12 +191,37 @@ static void *serverThread(void *arg) {
close(cfd);
}
SSL_CTX_free(ctx);
X509_free(cert);
EVP_PKEY_free(pkey);
return NULL;
}
// Write cert as a PEM bundle to a fresh temp file, storing its path in path (cap bytes). Used as the
// CALOG_CA_BUNDLE pin so a verifying request can trust the server's own self-signed certificate.
static bool writeCaBundle(char *path, size_t cap, X509 *cert) {
FILE *fp;
int fd;
if ((size_t)snprintf(path, cap, "/tmp/calogHttpsCa.XXXXXX") >= cap) {
return false;
}
fd = mkstemp(path);
if (fd < 0) {
return false;
}
fp = fdopen(fd, "wb");
if (fp == NULL) {
close(fd);
return false;
}
if (PEM_write_X509(fp, cert) != 1) {
fclose(fp);
return false;
}
fclose(fp);
return true;
}
int main(void) {
CalogContextT *ctx;
pthread_t server;
@ -224,6 +266,12 @@ int main(void) {
return 1;
}
port = (int)ntohs(addr.sin_port);
gPkey = makeSelfSigned(&gCert);
if (gPkey == NULL || !writeCaBundle(gCaFile, sizeof(gCaFile), gCert)) {
printf("certificate/bundle setup failed\n");
return 1;
}
pthread_create(&server, NULL, serverThread, NULL);
snprintf(script, sizeof(script),
@ -238,15 +286,33 @@ int main(void) {
calogContextEval(ctx, script);
pumpUntilDone(1);
// Now trust the server's own certificate via CALOG_CA_BUNDLE and repeat with DEFAULT (verifying)
// requests: the handshake must succeed, exercising httpLoadTrustStore's pinned-bundle path.
setenv("CALOG_CA_BUNDLE", gCaFile, 1);
snprintf(script, sizeof(script),
"local r = httpRequest({url = 'https://localhost:%d/'})\n"
"report(4, r.status)\n" // 200, the cert is trusted via the pinned bundle
"report(5, r.body == 'hello' and 1 or 0)\n" // 1, body over verified TLS
"done()", port);
calogContextEval(ctx, script);
pumpUntilDone(2);
pthread_join(server, NULL);
close(gListenFd);
CHECK(atomic_load(&results[1]) == 1, "httpGet rejects an untrusted self-signed certificate by default");
CHECK(atomic_load(&results[2]) == 200, "httpRequest with insecure=true completes the TLS request");
CHECK(atomic_load(&results[3]) == 1, "the body is delivered over TLS when verification is skipped");
CHECK(atomic_load(&results[4]) == 200, "a verifying request succeeds when the cert is trusted via CALOG_CA_BUNDLE");
CHECK(atomic_load(&results[5]) == 1, "the body is delivered over verified TLS through the pinned bundle");
CHECK(atomic_load(&errorCount) == 0, "no uncaught errors");
calogDestroy(calog);
if (gCaFile[0] != '\0') {
unlink(gCaFile);
}
X509_free(gCert);
EVP_PKEY_free(gPkey);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);

View file

@ -245,7 +245,7 @@ static int32_t runProgram(const char *source) {
calogMyBasicDestroy(basic);
basic = NULL;
}
status = calogMyBasicCreate(&basic, broker, 1);
status = calogMyBasicCreate(&basic, broker, 1, NULL);
if (status != calogOkE) {
return status;
}

View file

@ -42,7 +42,7 @@ static int32_t basicRun(const char *source) {
calogMyBasicDestroy(basic);
basic = NULL;
}
status = calogMyBasicCreate(&basic, broker, BASIC_CTX_ID);
status = calogMyBasicCreate(&basic, broker, BASIC_CTX_ID, NULL);
if (status != calogOkE) {
return status;
}

View file

@ -13,6 +13,7 @@
#include "calog.h"
#include "calogPubsub.h"
#include "calogInternal.h" // calogPubsubShutdown: internal, exercised here for post-shutdown behavior
#include <stdatomic.h>
#include <stdio.h>

View file

@ -1,8 +1,8 @@
// testSandbox.c -- per-context resource limits (calogContextOpenLimited). Verifies the allow-list
// (a script may call only permitted natives, engine-agnostically), the Lua memory cap (an
// over-budget allocation fails cleanly instead of exhausting the host), and the wall-clock budget
// on Lua and JavaScript (a runaway loop is retired). The honest coverage is documented on
// CalogLimitsT: memory/time apply to Lua + JS only; the allow-list applies to every engine.
// (a script may call only permitted natives, engine-agnostically), the memory cap (an over-budget
// script is retired instead of exhausting the host), and the wall-clock budget (a runaway loop is
// retired) -- on Lua, JavaScript, AND my-basic, which the fork brought to parity (design.md sec 23).
// The other engines (Squirrel/Berry/s7/Wren/mruby/Tcl/Janet) still get the allow-list only.
#define _POSIX_C_SOURCE 200809L
@ -178,6 +178,24 @@ int main(void) {
runLimited(&calogJsEngine, "while (true) {}", &timeLimits);
CHECK(atomic_load(&errorCount) >= 1, "time budget (JS): a runaway loop is retired");
// 4. my-basic parity: the fork owns its VM, so it gets the same three limits as Lua/JS -- the
// allow-list (already engine-agnostic), the wall-clock budget (a per-statement step hook), and the
// memory cap (a counting allocator enforced at the statement boundary).
resetFlags();
runLimited(&calogMyBasicEngine, "reached()\nforbidden()\ndone()", &allowLimits);
CHECK(atomic_load(&reachedFlag), "allow-list (my-basic): a permitted native runs");
CHECK(atomic_load(&errorCount) >= 1, "allow-list (my-basic): calling a forbidden native errors");
CHECK(!atomic_load(&forbiddenFlag), "allow-list (my-basic): the forbidden native body never ran");
resetFlags();
runLimited(&calogMyBasicEngine, "x = 0\nWHILE 1\nx = x + 1\nWEND", &timeLimits);
CHECK(atomic_load(&errorCount) >= 1, "time budget (my-basic): a runaway loop is retired");
// Doubling a string blows the 2 MiB budget within ~21 iterations; the step hook retires it.
resetFlags();
runLimited(&calogMyBasicEngine, "s = \"x\"\nWHILE 1\ns = s + s\nWEND", &memLimits);
CHECK(atomic_load(&errorCount) >= 1, "memory cap (my-basic): an over-budget script is retired");
calogDestroy(calog);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);

View file

@ -9,9 +9,10 @@
# static testArchive (real round-trip through every codec). The Windows artifact is a testArchive.exe
# verified as a valid PE (running it needs a Windows host or wine).
#
# Scope: this cross-builds libarchive + the five codecs only. The XAR format (native builds enable it
# via libxml2 + OpenSSL + iconv) is NOT included here -- cross XAR would need those three cross-built
# too. So XAR is a native-only feature; the cross artifacts cover every codec and the other formats.
# Scope: the MUSL target is full-featured -- it also cross-builds OpenSSL + libxml2 (iconv is in musl's
# libc) so its libarchive has XAR read+write, and the static testArchive round-trips xar on the host.
# The WINDOWS target is codecs + the other formats only; cross XAR for Windows additionally needs
# OpenSSL + libxml2 + win-iconv built for mingw, which is not done yet (a follow-up).
#
# Requirements: zig (https://ziglang.org/download/) and cmake. Point ZIG at the binary or PATH it:
# ZIG=/path/to/zig ./tools/crossArchive.sh
@ -26,7 +27,7 @@
set -u
cd "$(dirname "$0")/.."
R=$(pwd)
ZIG=${ZIG:-zig}
ZIG=${ZIG:-${CALOG_ZIG:-/home/scott/zig/current/zig}}
X=${OUT:-build/cross}
XABS="$R/$X"
@ -84,14 +85,19 @@ build_codecs() {
"$XABS/bin/zar" rcs "$O/lib/libzstd.a" "$O"/obj/zs_*.o
}
# build_cmake_libs <toolchain> <outdir> -- xz/liblzma then libarchive, each codec backend pinned to
# the just-built cross libs (headers are arch-independent -> include dirs stay the vendored source).
# build_xz <toolchain> <outdir> -- vendored xz/liblzma (libarchive's lzma + 7-zip backend).
build_xz() {
cmake -S vendor/xz -B "$2/xz" -DCMAKE_TOOLCHAIN_FILE="$1" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DENABLE_THREADS=OFF -DENABLE_NLS=OFF -DENABLE_DOXYGEN=OFF >"$2/xz-cfg.log" 2>&1 || { echo " xz configure FAILED (see $2/xz-cfg.log)"; return 1; }
cmake --build "$2/xz" --target liblzma -j >"$2/xz-build.log" 2>&1 || { echo " xz build FAILED (see $2/xz-build.log)"; return 1; }
}
# build_cmake_libs <toolchain> <outdir> -- xz then libarchive WITHOUT xar, each codec backend pinned
# to the just-built cross libs (headers are arch-independent -> include dirs stay the vendored source).
build_cmake_libs() {
local TC=$1 O=$2
cmake -S vendor/xz -B "$O/xz" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DENABLE_THREADS=OFF -DENABLE_NLS=OFF -DENABLE_DOXYGEN=OFF >"$O/xz-cfg.log" 2>&1 || { echo " xz configure FAILED (see $O/xz-cfg.log)"; return 1; }
cmake --build "$O/xz" --target liblzma -j >"$O/xz-build.log" 2>&1 || { echo " xz build FAILED (see $O/xz-build.log)"; return 1; }
build_xz "$TC" "$O" || return 1
cmake -S vendor/libarchive -B "$O/libarchive" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
@ -107,17 +113,72 @@ build_cmake_libs() {
cmake --build "$O/libarchive" --target archive_static -j >"$O/la-build.log" 2>&1 || { echo " libarchive build FAILED (see $O/la-build.log)"; return 1; }
}
# --- XAR (read+write) support for musl. libarchive's xar needs an XML lib + MD5/SHA1 + iconv; musl
# supplies iconv in libc, so only OpenSSL (MD5/SHA1) and libxml2 must be cross-built. ---
build_openssl_musl() { # <outdir>
local O=$1 SRC="$1/openssl-src"
# OpenSSL cannot build out-of-tree against a source tree already configured in-tree (which the
# native build leaves vendor/openssl in), so build in a fresh COPY -- vendor/openssl is untouched.
rm -rf "$SRC"; cp -r "$R/vendor/openssl" "$SRC" || return 1
# Drop the copied glibc objects (cp preserves mtimes, so a plain make would treat them as current).
find "$SRC" -type f \( -name '*.o' -o -name '*.a' -o -name '*.d' \) -delete
rm -f "$SRC/configdata.pm" "$SRC/Makefile"
( cd "$SRC" && perl ./Configure linux-x86_64 no-shared no-tests no-docs CC="$XABS/bin/zcc-musl" >"$O/openssl-cfg.log" 2>&1 \
&& make -j4 build_libs >"$O/openssl-build.log" 2>&1 ) || { echo " openssl-musl FAILED (see $O/openssl-build.log)"; return 1; }
}
build_libxml2_musl() { # <outdir>
local O=$1
cmake -S vendor/libxml2 -B "$O/libxml2" -DCMAKE_TOOLCHAIN_FILE="$XABS/toolchain-musl.cmake" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_TESTS=OFF \
-DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_ICU=OFF \
-DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_ZLIB=OFF -DLIBXML2_WITH_ICONV=OFF \
-DLIBXML2_WITH_MODULES=OFF -DLIBXML2_WITH_CATALOG=OFF -DLIBXML2_WITH_THREADS=ON >"$O/libxml2-cfg.log" 2>&1 \
&& cmake --build "$O/libxml2" --target LibXml2 -j >"$O/libxml2-build.log" 2>&1 || { echo " libxml2-musl FAILED (see $O/libxml2-build.log)"; return 1; }
}
build_libarchive_xar_musl() { # <outdir> -- libarchive with xar, iconv+libxml2+openssl pinned to the musl builds.
local O=$1
# Empty dir: satisfies libarchive's IF(ICONV_INCLUDE_DIR) so FIND_PATH(iconv.h) does NOT return
# the host /usr/include (glibc), whose headers break the musl compile. zig cc finds musl's iconv.h
# implicitly, so an empty -I is enough. LIBXML2_INCLUDE_DIR is a two-entry list (source API +
# the build dir with the generated xmlversion.h).
mkdir -p "$O/noinc"
cmake -S vendor/libarchive -B "$O/libarchive-xar" -DCMAKE_TOOLCHAIN_FILE="$XABS/toolchain-musl.cmake" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
-DENABLE_ACL=OFF -DENABLE_XATTR=OFF -DENABLE_LIBB2=OFF \
-DENABLE_LZO=OFF -DENABLE_NETTLE=OFF -DENABLE_MBEDTLS=OFF -DENABLE_PCREPOSIX=OFF -DENABLE_PCRE2POSIX=OFF \
-DENABLE_ICONV=ON -DICONV_INCLUDE_DIR="$O/noinc" \
-DENABLE_LIBXML2=ON "-DLIBXML2_INCLUDE_DIR=$R/vendor/libxml2/include;$O/libxml2" -DLIBXML2_LIBRARY="$O/libxml2/libxml2.a" \
-DENABLE_OPENSSL=ON -DOPENSSL_ROOT_DIR="$O/openssl-src" -DOPENSSL_INCLUDE_DIR="$O/openssl-src/include" \
-DOPENSSL_SSL_LIBRARY="$O/openssl-src/libssl.a" -DOPENSSL_CRYPTO_LIBRARY="$O/openssl-src/libcrypto.a" \
-DENABLE_ZLIB=ON -DZLIB_INCLUDE_DIR="$R/vendor/zlib" -DZLIB_LIBRARY="$O/lib/libz.a" \
-DENABLE_BZip2=ON -DBZIP2_INCLUDE_DIR="$R/vendor/bzip2" -DBZIP2_LIBRARIES="$O/lib/libbz2.a" \
-DENABLE_LZMA=ON -DLIBLZMA_INCLUDE_DIR="$R/vendor/xz/src/liblzma/api" -DLIBLZMA_LIBRARY="$O/xz/liblzma.a" \
-DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR="$R/vendor/zstd" -DZSTD_LIBRARY="$O/lib/libzstd.a" \
-DENABLE_LZ4=ON -DLZ4_INCLUDE_DIR="$R/vendor/lz4" -DLZ4_LIBRARY="$O/lib/liblz4.a" \
>"$O/la-xar-cfg.log" 2>&1 || { echo " libarchive-xar configure FAILED (see $O/la-xar-cfg.log)"; return 1; }
cmake --build "$O/libarchive-xar" --target archive_static -j >"$O/la-xar-build.log" 2>&1 || { echo " libarchive-xar build FAILED (see $O/la-xar-build.log)"; return 1; }
}
# the compression stack link line (libarchive FIRST for static link order), per target outdir.
archlibs() { echo "$1/libarchive/libarchive/libarchive.a $1/xz/liblzma.a $1/lib/libzstd.a $1/lib/liblz4.a $1/lib/libbz2.a $1/lib/libz.a"; }
# the full XAR stack link line (musl): xar-enabled libarchive + libxml2 + codecs + openssl (its MD5/SHA1).
archlibsXar() { echo "$1/libarchive-xar/libarchive/libarchive.a $1/libxml2/libxml2.a $1/xz/liblzma.a $1/lib/libzstd.a $1/lib/liblz4.a $1/lib/libbz2.a $1/lib/libz.a $1/openssl-src/libssl.a $1/openssl-src/libcrypto.a"; }
ARCHINC="-Isrc -Isrc/lua -Ilibs -Ivendor/lua/src -Ivendor/libarchive/libarchive"
echo "== musl (fully static Linux, built AND run) =="
if build_codecs "$XABS/bin/zcc-musl" "$XABS/musl" && build_cmake_libs "$XABS/toolchain-musl.cmake" "$XABS/musl"; then
echo "== musl (fully static Linux, built AND run -- includes XAR) =="
# musl gets full XAR: codecs + xz, then OpenSSL + libxml2, then a libarchive built with them + iconv
# (in musl's libc). The static testArchive links the whole XAR stack and runs its xar round-trip here.
if build_codecs "$XABS/bin/zcc-musl" "$XABS/musl" && build_xz "$XABS/toolchain-musl.cmake" "$XABS/musl" \
&& build_openssl_musl "$XABS/musl" && build_libxml2_musl "$XABS/musl" && build_libarchive_xar_musl "$XABS/musl"; then
if "$XABS/bin/zcc-musl" -static -O2 -pthread -w $ARCHINC -DLUA_USE_POSIX \
$CORE src/lua/luaEngine.c src/lua/luaAdapter.c libs/calogArchive.c libs/calogHandle.c tests/testArchive.c \
$LUASRC $(archlibs "$XABS/musl") -lm -o "$XABS/musl/testArchive-musl" 2>"$XABS/musl/testArchive-link.err"; then
$LUASRC $(archlibsXar "$XABS/musl") -lm -o "$XABS/musl/testArchive-musl" 2>"$XABS/musl/testArchive-link.err"; then
if "$XABS/musl/testArchive-musl" >/dev/null 2>&1; then
echo " [musl RUN ok] testArchive (libarchive + zlib/bzip2/lz4/zstd/xz, fully static)"; pass=$((pass+1))
echo " [musl RUN ok] testArchive (libarchive + 5 codecs + XAR via libxml2/openssl, fully static)"; pass=$((pass+1))
else echo " [musl RAN, nonzero] testArchive"; fail=$((fail+1)); fi
else echo " [musl LINK FAIL] testArchive (see $XABS/musl/testArchive-link.err)"; fail=$((fail+1)); fi
else fail=$((fail+1)); fi

814
tools/crossDeps.sh Executable file
View file

@ -0,0 +1,814 @@
#!/usr/bin/env bash
# crossDeps.sh -- reproducibly (re)build the heavy vendored dependencies for a cross target from
# vendor/ source using a single zig toolchain, into build/cross/<target>/. This is what makes the
# full-CLI cross builds (tools/crossMacFull.sh, tools/crossWinFull.sh) reproducible from a clean
# checkout: those scripts CONSUME the pinned deps this script PRODUCES (OpenSSL, libxml2, PCRE2,
# libssh2, MariaDB, PostgreSQL/libpq, Tcl, mruby, the compression/archive stack, and -- on Windows --
# winpthreads). It regenerates the zig compiler wrappers + the CMake toolchain file first, so nothing
# under build/cross/ needs to pre-exist.
#
# Usage: [ZIG=/path/to/zig] ./tools/crossDeps.sh <win|mac-x64|mac-arm64> [dep ...]
# dep (optional): build only the named dep(s), e.g. `openssl libssh2`. Default: all, in dep order.
# Deps in order: codecs xz openssl libxml2 pcre2 libssh2 mariadb postgres tcl mruby winpthreads libarchive
#
# Requirements: zig (https://ziglang.org/download/), cmake, perl (OpenSSL), and a POSIX make/rake env.
set -eu
cd "$(dirname "$0")/.."
R=$(pwd)
ZIG=${ZIG:-${CALOG_ZIG:-/home/scott/zig/current/zig}}
[ -x "$ZIG" ] || command -v "$ZIG" >/dev/null 2>&1 || { echo "error: zig not found at '$ZIG'. Set ZIG=/path/to/zig (a PERMANENT install; see https://ziglang.org/download/)." >&2; exit 1; }
ZIGABS=$(command -v "$ZIG" 2>/dev/null || echo "$ZIG"); ZIGABS=$(readlink -f "$ZIGABS" 2>/dev/null || echo "$ZIGABS")
export CALOG_ZIG="$ZIGABS"
command -v cmake >/dev/null 2>&1 || { echo "error: cmake not found." >&2; exit 1; }
T=${1:-}
case "$T" in
win) TRIPLE=x86_64-windows-gnu; SYSNAME=Windows; SYSPROC=x86_64 ;;
mac-x64) TRIPLE=x86_64-macos; SYSNAME=Darwin; SYSPROC=x86_64 ;;
mac-arm64) TRIPLE=aarch64-macos; SYSNAME=Darwin; SYSPROC=aarch64 ;;
*) echo "usage: $0 <win|mac-x64|mac-arm64> [dep ...]" >&2; exit 1 ;;
esac
shift
BIN="$R/build/cross/bin"
O="${OUT:-$R/build/cross/$T}" # OUT overrides the per-target output dir (e.g. to verify without clobbering pins)
TC="$R/build/cross/toolchain-$T.cmake"
CC="$BIN/zcc-$T"
CXX="$BIN/zcxx-$T"
AR="$BIN/zar"
RANLIB="$BIN/zranlib"
mkdir -p "$BIN" "$O"
# ---------------------------------------------------------------------------------------------------
# Regenerate the single-binary zig wrappers + the CMake toolchain file (idempotent). The wrappers
# resolve zig via $CALOG_ZIG so they never bake in a session/temp path.
# ---------------------------------------------------------------------------------------------------
gen_wrapper() { # <name> <zig-subcommand-and-flags>
printf '#!/bin/sh\n# calog cross wrapper -- resolves zig via $CALOG_ZIG (permanent install).\nexec "%s" %s "$@"\n' \
'${CALOG_ZIG:-/home/scott/zig/current/zig}' "$2" > "$BIN/$1"
chmod +x "$BIN/$1"
}
gen_wrapper zar "ar"
gen_wrapper zranlib "ranlib"
gen_wrapper "zcc-$T" "cc -target $TRIPLE"
gen_wrapper "zcxx-$T" "c++ -target $TRIPLE"
if [ "$T" = win ]; then
# windres-compatible shim backed by `zig rc` (CMake appends win32/libxml2.rc for libxml2, etc.).
cat > "$BIN/zwindres-win" <<'WINDRES'
#!/bin/sh
# windres-compatible shim backed by `zig rc`, targeting x86_64-windows. Handles BOTH arg styles:
# CMake's positional `windres -O coff <defs> <incs> INPUT OUTPUT`, and the GNU/PostgreSQL form
# `windres -i INPUT -o OUTPUT --include-dir=DIR`.
ZIG="${CALOG_ZIG:-/home/scott/zig/current/zig}"
DEFS=""; INCS=""; POS=""; IN=""; OUT=""
while [ $# -gt 0 ]; do
case "$1" in
-O) shift ;; # output-format selector; we always emit coff
-O*) ;;
-i) shift; IN="$1" ;;
-o) shift; OUT="$1" ;;
-D) shift; DEFS="$DEFS /d $1" ;;
-D*) DEFS="$DEFS /d ${1#-D}" ;;
-I) shift; INCS="$INCS /i $1" ;;
-I*) INCS="$INCS /i ${1#-I}" ;;
--include-dir) shift; INCS="$INCS /i $1" ;;
--include-dir=*) INCS="$INCS /i ${1#--include-dir=}" ;;
*) POS="$POS $1" ;;
esac
shift
done
[ -n "$IN" ] || { set -- $POS; IN="$1"; OUT="$2"; } # positional INPUT OUTPUT when no -i/-o given
exec "$ZIG" rc /:output-format coff /:target x86_64 /:auto-includes gnu $DEFS $INCS /fo "$OUT" "$IN"
WINDRES
chmod +x "$BIN/zwindres-win"
fi
# CMake toolchain: SYSTEM_NAME triggers cross mode (skips run-only checks); the mac SYSTEM_NAME is
# Darwin, win is Windows. FIND_ROOT_PATH modes keep CMake off host libs/headers.
cat > "$TC" <<EOF
set(CMAKE_SYSTEM_NAME $SYSNAME)
set(CMAKE_SYSTEM_PROCESSOR $SYSPROC)
set(CMAKE_C_COMPILER "$CC")
set(CMAKE_CXX_COMPILER "$CXX")
set(CMAKE_AR "$AR")
set(CMAKE_RANLIB "$RANLIB")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
EOF
if [ "$T" = win ]; then
# CMake compiles win32 .rc resources (libxml2 has win32/libxml2.rc) with windres; point it at the
# zig-rc shim so no host windres is needed.
echo "set(CMAKE_RC_COMPILER \"$BIN/zwindres-win\")" >> "$TC"
fi
echo "== crossDeps: target=$T triple=$TRIPLE zig=$($ZIG version) =="
echo " wrappers + $TC regenerated"
# ===================================================================================================
# Per-dependency build functions. Each builds vendor/<dep> into $O, producing the artifact the full
# CLI link expects. (Populated below -- reconstructed by tools and verified by build.)
# ===================================================================================================
# ---- codecs (confidence: high) ----
build_codecs() {
# Builds the 4 object-rule compression codecs (zlib, bzip2, lz4, zstd) from
# vendor/ source into $O/lib as static archives. Pure C, no configure/cmake.
# Target-agnostic: the caller's $CC (zcc-$T) already selects the triple, so
# win / mac-x64 / mac-arm64 all take the identical path. Verified against
# tools/crossArchive.sh build_codecs and tools/crossMacFull.sh:38-48, and
# against the pinned build/cross/<T>/{lib,obj} object listings.
local c
mkdir -p "$O/obj" "$O/lib" || return 1
# zlib: -DHAVE_UNISTD_H so it includes <unistd.h> for lseek (configure would
# normally define this; the object-rule build must pass it explicitly).
for c in "$R"/vendor/zlib/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -DHAVE_UNISTD_H -w -O2 -I"$R/vendor/zlib" -c "$c" -o "$O/obj/z_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/libz.a" "$O"/obj/z_*.o || return 1
# bzip2
for c in "$R"/vendor/bzip2/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -w -O2 -I"$R/vendor/bzip2" -c "$c" -o "$O/obj/bz_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/libbz2.a" "$O"/obj/bz_*.o || return 1
# lz4 (lz4.c lz4frame.c lz4hc.c xxhash.c)
for c in "$R"/vendor/lz4/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -w -O2 -I"$R/vendor/lz4" -c "$c" -o "$O/obj/l4_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/liblz4.a" "$O"/obj/l4_*.o || return 1
# zstd: -DZSTD_DISABLE_ASM (no huf_decompress_amd64 asm under a cross triple).
# Only common/ compress/ decompress/ are vendored (no legacy/ dictBuilder/).
for c in "$R"/vendor/zstd/common/*.c "$R"/vendor/zstd/compress/*.c "$R"/vendor/zstd/decompress/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -w -O2 -DZSTD_DISABLE_ASM -I"$R/vendor/zstd" -c "$c" -o "$O/obj/zs_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/libzstd.a" "$O"/obj/zs_*.o || return 1
echo " [codecs] $O/lib/{libz,libbz2,liblz4,libzstd}.a"
}
# ---- xz (confidence: high) ----
build_xz() {
# xz/liblzma -- CMake, out-of-tree, pure C (no CXX, no sibling deps).
# Identical recipe for win / mac-x64 / mac-arm64: the target triple + AR/RANLIB
# all come from $TC (the per-target CMake toolchain file), so no per-target branches.
# vendor/xz stays pristine: -S <source> -B <builddir under $O>.
local SRC="$R/vendor/xz"
local B="$O/xz"
cmake -S "$SRC" -B "$B" \
-DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DENABLE_THREADS=OFF \
-DENABLE_NLS=OFF \
-DENABLE_DOXYGEN=OFF \
>"$O/xz-cfg.log" 2>&1 \
|| { echo " xz configure FAILED (see $O/xz-cfg.log)"; return 1; }
cmake --build "$B" --target liblzma -j \
>"$O/xz-build.log" 2>&1 \
|| { echo " xz build FAILED (see $O/xz-build.log)"; return 1; }
# Artifact: $O/xz/liblzma.a . Consumers (libarchive full-CLI link) also need the
# public headers, exposed straight from the pristine vendor tree at
# $R/vendor/xz/src/liblzma/api -- nothing is installed or copied.
test -f "$B/liblzma.a" || { echo " xz: liblzma.a missing after build"; return 1; }
}
# ---- openssl (confidence: high) ----
build_openssl() {
# Cross-build OpenSSL 3.5.7 static libs (libssl.a + libcrypto.a) for $T from vendor/openssl.
# Contract vars assumed set by caller: R, T, CC, AR, O.
# vendor/openssl is configured IN-TREE by the native build, so we build in a COPY under
# $O/openssl-src and leave vendor/ pristine.
local SRC="$O/openssl-src"
# 1) Fresh copy of the vendored tree. cp -r preserves mtimes, so any glibc/native objects
# left in vendor/openssl would look "up to date" -- drop all build products + the previous
# configure state so Configure/make start clean.
rm -rf "$SRC"
cp -r "$R/vendor/openssl" "$SRC" || return 1
find "$SRC" -type f \( -name '*.o' -o -name '*.a' -o -name '*.d' \) -delete
rm -f "$SRC/configdata.pm" "$SRC/Makefile"
# 2) Pick the OpenSSL Configure target for this platform (names verified against each
# pinned openssl-src/cfg.log + configdata.pm "perlargv").
local OSSL_TARGET
case "$T" in
win) OSSL_TARGET="mingw64" ;;
mac-x64) OSSL_TARGET="darwin64-x86_64-cc" ;;
mac-arm64) OSSL_TARGET="darwin64-arm64-cc" ;;
*) echo "build_openssl: unknown target $T" >&2; return 1 ;;
esac
# 3) Configure + build the static libs only (build_libs skips apps/tests).
# no-asm on all three targets (zig cc / mingw has no gas-compatible perlasm path here).
# CC is the only toolchain var passed -- AR/RANLIB stay at host defaults (host ar emits
# GNU-format .a; mac fixes that with the repack in step 4, win/mingw reads GNU fine).
(
cd "$SRC" || exit 1
perl ./Configure "$OSSL_TARGET" no-shared no-tests no-docs no-asm CC="$CC" \
> "$O/openssl-cfg.log" 2>&1 || exit 1
make -j"$(nproc)" build_libs > "$O/openssl-build.log" 2>&1 || exit 1
) || { echo " openssl ($T) FAILED (see $O/openssl-cfg.log / $O/openssl-build.log)" >&2; return 1; }
# Artifacts now at: $O/openssl-src/libssl.a and $O/openssl-src/libcrypto.a
[ -f "$SRC/libssl.a" ] && [ -f "$SRC/libcrypto.a" ] || { echo " openssl ($T): missing libs" >&2; return 1; }
# 4) MAC ONLY: zig's Mach-O linker cannot parse the GNU-format .a that OpenSSL's build emits
# ("unknown cpu architecture"). Repack the SAME objects into BSD/darwin ar format
# (#1/N long names + __.SYMDEF) at $O/openssl-repack/. Member basenames are already
# unique (OpenSSL prefixes each object with its lib target, e.g. libcrypto-lib-*.o,
# libcommon-lib-*.o), so flat extraction cannot collide.
if [ "$T" = mac-x64 ] || [ "$T" = mac-arm64 ]; then
local REPACK="$O/openssl-repack"
rm -rf "$REPACK"
mkdir -p "$REPACK"
local lib
for lib in ssl crypto; do
local tmp="$REPACK/tmp-$lib"
rm -rf "$tmp"
mkdir -p "$tmp"
(
cd "$tmp" || exit 1
"$AR" x "$SRC/lib$lib.a" || exit 1
"$AR" --format=darwin crs "$REPACK/lib$lib.a" ./*.o || exit 1
) || { echo " openssl repack lib$lib ($T) FAILED" >&2; return 1; }
rm -rf "$tmp"
done
# Convenience: expose the same generated headers under the repack dir (as the pinned
# openssl-repack/ did) so an OPENSSL_ROOT_DIR=openssl-repack also finds include/.
ln -sfn "$SRC/include" "$REPACK/include"
[ -f "$REPACK/libssl.a" ] && [ -f "$REPACK/libcrypto.a" ] || { echo " openssl repack ($T): missing libs" >&2; return 1; }
fi
}
# ---- libxml2 (confidence: high) ----
build_libxml2() {
# libxml2 static lib, cross-built out-of-tree from vendor/libxml2 with the
# per-target CMake toolchain ($TC). Flags are identical for win/mac-x64/mac-arm64
# (verified against each target's build/cross/<T>/libxml2/CMakeCache.txt) and match
# tools/crossArchive.sh build_libxml2_musl. Only $TC differs per target, so there is
# no per-target if-branch. Pure C: no CXX, no zwindres (PROGRAMS=OFF). No sibling deps
# or host libs: HTTP/FTP/ICU/LZMA/ZLIB/ICONV all OFF, so libxml2 is self-contained
# (mac iconv would come from libSystem but is disabled here; win/mingw likewise).
# Produces exactly: $O/libxml2/libxml2.a (path the full-CLI link expects).
# Clean build dir first: CMAKE_RC_COMPILER (win .rc -> zwindres-win) is cached on the FIRST
# configure, so a stale cache from an earlier run would keep an old windres path.
rm -rf "$O/libxml2"
cmake -S "$R/vendor/libxml2" -B "$O/libxml2" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_TESTS=OFF \
-DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_ICU=OFF \
-DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_ZLIB=OFF -DLIBXML2_WITH_ICONV=OFF \
-DLIBXML2_WITH_MODULES=OFF -DLIBXML2_WITH_CATALOG=OFF -DLIBXML2_WITH_THREADS=ON \
>"$O/libxml2-cfg.log" 2>&1 \
&& cmake --build "$O/libxml2" --target LibXml2 -j >"$O/libxml2-build.log" 2>&1 \
|| { echo " libxml2 ($T) FAILED (see $O/libxml2-build.log)"; return 1; }
}
# ---- pcre2 (confidence: high) ----
build_pcre2() {
# pcre2: 8-bit, static, NO JIT, Unicode ON. Pure C (no CXX needed -- so
# win's missing zcxx wrapper is irrelevant here). Out-of-tree CMake build
# directly INTO $O/pcre2 so the generated pcre2.h and libpcre2-8.a land at
# the exact path the full-CLI link expects (-I$O/pcre2 / $O/pcre2/libpcre2-8.a).
# vendor/pcre2 stays pristine (clean out-of-tree, no in-tree config gotcha).
echo " building pcre2 for $T ..."
rm -rf "$O/pcre2"
cmake -G Ninja -S "$R/vendor/pcre2" -B "$O/pcre2" \
-DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DPCRE2_BUILD_PCRE2_8=ON \
-DPCRE2_BUILD_PCRE2_16=OFF \
-DPCRE2_BUILD_PCRE2_32=OFF \
-DPCRE2_SUPPORT_JIT=OFF \
-DPCRE2_SUPPORT_UNICODE=ON \
-DPCRE2_NEWLINE=LF \
-DPCRE2_BUILD_TESTS=OFF \
-DPCRE2_BUILD_PCRE2GREP=OFF \
>"$O/pcre2-cfg.log" 2>&1 || { echo " pcre2 configure FAILED (see $O/pcre2-cfg.log)"; return 1; }
# Static-lib target name is pcre2-8-static; it emits $O/pcre2/libpcre2-8.a.
cmake --build "$O/pcre2" --target pcre2-8-static -j \
>"$O/pcre2-build.log" 2>&1 || { echo " pcre2 build FAILED (see $O/pcre2-build.log)"; return 1; }
test -f "$O/pcre2/libpcre2-8.a" || { echo " pcre2 artifact missing"; return 1; }
echo " pcre2 OK -> $O/pcre2/libpcre2-8.a"
}
# ---- libssh2 (confidence: high) ----
build_libssh2() {
# libssh2 -- CMake, out-of-tree (vendor/libssh2 stays pristine), static, OpenSSL crypto backend
# pinned to the cross OpenSSL built earlier under $O/openssl-src. No zlib (compression stays off,
# matching the pinned build), no examples, no tests. Pure C -- $CXX is unused.
#
# Uses caller globals: R T CC CXX AR RANLIB TC O (set by crossDeps.sh harness).
# Dependency: build_openssl must have run first ($O/openssl-src/{include,libcrypto.a,libssl.a}).
[ -f "$O/openssl-src/libcrypto.a" ] || { echo " libssh2: missing cross OpenSSL at $O/openssl-src (build openssl first)"; return 1; }
local B="$O/libssh2/_build" # out-of-tree build dir; matches the pinned layout exactly
rm -rf "$O/libssh2"
mkdir -p "$B"
# NOTE: CMAKE_BUILD_TYPE=Release (i.e. -O2, NO UBSan). The ORIGINAL pinned mac libssh2 was built
# with NO build type, so it picked up zig's default UndefinedBehaviorSanitizer instrumentation
# (why the pinned mac libssh2.a is ~4.5-4.9MB vs ~1.5MB here) and the mac final link had to pass
# -fsanitize=undefined. Building Release here drops that instrumentation, so tools/crossMacFull.sh
# can drop its link-only -fsanitize=undefined once deps are rebuilt with this script.
cmake -S vendor/libssh2 -B "$B" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON \
-DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF \
-DENABLE_DEBUG_LOGGING=OFF \
-DCRYPTO_BACKEND=OpenSSL \
-DOPENSSL_ROOT_DIR="$O/openssl-src" \
-DOPENSSL_INCLUDE_DIR="$O/openssl-src/include" \
-DOPENSSL_CRYPTO_LIBRARY="$O/openssl-src/libcrypto.a" \
-DOPENSSL_SSL_LIBRARY="$O/openssl-src/libssl.a" \
>"$O/libssh2-cfg.log" 2>&1 || { echo " libssh2 configure FAILED (see $O/libssh2-cfg.log)"; return 1; }
# Static-lib target is libssh2_static (LIB_NAME_static); it emits src/libssh2.a inside $B.
cmake --build "$B" --target libssh2_static -j >"$O/libssh2-build.log" 2>&1 \
|| { echo " libssh2 build FAILED (see $O/libssh2-build.log)"; return 1; }
# Artifact: $O/libssh2/_build/src/libssh2.a (this is what the mac link consumes directly).
# The Windows full-CLI link expects it at $O/libssh2/libssh2.a, so mirror the pinned copy step.
if [ "$T" = win ]; then
cp "$B/src/libssh2.a" "$O/libssh2/libssh2.a" || return 1
echo " [lib] libssh2.a -> $O/libssh2/libssh2.a"
else
echo " [lib] libssh2.a -> $B/src/libssh2.a"
fi
}
# ---- mariadb (confidence: high) ----
build_mariadb() {
# MariaDB Connector/C (client library only, no server).
# CMake out-of-tree: source stays pristine at vendor/mariadb, build tree at $O/mariadb.
# Depends on the already-built sibling OpenSSL under $O/openssl-src (pinned, never host).
local src="$R/vendor/mariadb"
local bld="$O/mariadb"
local ossl="$O/openssl-src"
# Pin the sibling cross OpenSSL; refuse to fall back to host TLS.
if [ ! -f "$ossl/libssl.a" ] || [ ! -f "$ossl/libcrypto.a" ]; then
echo "build_mariadb: sibling OpenSSL missing at $ossl (build openssl first)" >&2
return 1
fi
# Fresh out-of-tree build dir (== the pinned layout the full-CLI link expects).
rm -rf "$bld"
mkdir -p "$bld"
# Generator: pins used Unix Makefiles (win) / Ninja (mac); either yields
# identical artifacts. Prefer Ninja when present.
local gen="Unix Makefiles"
if command -v ninja >/dev/null 2>&1; then
gen="Ninja"
fi
# Configure. The toolchain file ($TC) already hardcodes CMAKE_C_COMPILER
# (zcc-$T), CMAKE_AR (zar), CMAKE_RANLIB (zranlib) and the Darwin/Windows
# CMAKE_SYSTEM_NAME, so CC/AR/RANLIB are not re-passed. Pure C -- no CXX,
# which matters for win (no zcxx). The only per-target input that varies is
# $TC and the $O path; both are already target-scoped by the caller.
cmake -S "$src" -B "$bld" \
-G "$gen" \
-DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_SSL=OPENSSL \
-DOPENSSL_ROOT_DIR="$ossl" \
-DOPENSSL_INCLUDE_DIR="$ossl/include" \
-DOPENSSL_SSL_LIBRARY="$ossl/libssl.a" \
-DOPENSSL_CRYPTO_LIBRARY="$ossl/libcrypto.a" \
-DWITH_EXTERNAL_ZLIB=OFF \
-DWITH_UNIT_TESTS=OFF \
-DWITH_CURL=OFF \
> "$O/mariadb-cfg.log" 2>&1 || {
echo "build_mariadb: configure failed (see $O/mariadb-cfg.log)" >&2
return 1
}
# Build ONLY the static client lib target (no shared libmariadb, no tools,
# no plugins-as-DLLs). This is exactly what both pinned build.logs did.
cmake --build "$bld" --target mariadbclient -j"$(nproc)" \
> "$O/mariadb-build.log" 2>&1 || {
echo "build_mariadb: build failed (see $O/mariadb-build.log)" >&2
return 1
}
# Verify the pinned artifact path the full-CLI link consumes.
if [ ! -f "$bld/libmariadb/libmariadbclient.a" ]; then
echo "build_mariadb: artifact libmariadbclient.a missing" >&2
return 1
fi
}
# ---- tcl (confidence: high) ----
build_tcl() {
# Builds Tcl 9.0.4 as a STATIC archive for target $T using the caller's
# zig-cc toolchain, out-of-tree, from vendored source. Tcl bundles its own
# zlib + libtommath, so it depends on NO sibling deps under $O.
#
# Artifact (in-place in the build dir, exactly where the full-CLI link expects):
# mac-x64 / mac-arm64 : $O/tcl/libtcl9.0.a
# win : $O/tcl/libtcl90.a
#
# Toolchain contract (set by caller): R T CC CXX AR RANLIB TC O ZIG
local BUILD="$O/tcl"
local WINRC="$R/build/cross/bin/zwindres-win"
local SRC # absolute path to the configure script's directory
local CFG # absolute path to configure
local LIB # target-specific static lib filename == make target
local HOSTTRIPLE
rm -rf "$BUILD"
mkdir -p "$BUILD"
if [ "$T" = "win" ]; then
# GOTCHA: the vendored tree (vendor/tcl) was trimmed to a unix-only
# checkout -- it has generic/ unix/ compat/ libtommath/ library/ but NO
# win/ subdirectory. The Windows port is driven by win/configure +
# win/Makefile.in + the tclWin*.c sources, none of which are vendored.
# The original pinned build used a full upstream tcl9.0.4 tree unpacked
# in a scratchpad. To reproduce, vendor/tcl MUST first be restored to a
# complete tcl9.0.4 source tree (re-add the win/ subdir from the
# upstream tarball). We resolve the win source root here and fail loudly
# if it is missing rather than silently producing a broken lib.
SRC="$R/vendor/tcl/win"
if [ ! -x "$SRC/configure" ]; then
echo "build_tcl: ERROR: $SRC/configure not found." >&2
echo " vendor/tcl lacks the win/ subdir; restore the full tcl9.0.4" >&2
echo " source tree (with win/) before cross-building tcl for win." >&2
return 1
fi
CFG="$SRC/configure"
LIB="libtcl90.a"
HOSTTRIPLE="x86_64-w64-mingw32"
else
# mac-x64 / mac-arm64: unix/configure, driven out-of-tree from $O/tcl.
# GOTCHA: like win/, the trimmed vendor/tcl also lacks the macosx/ subdir,
# which configure --host=*-apple-darwin pulls in (tclMacOSXBundle.c etc.).
# The native Linux build never needs it; a mac cross build does. Fail loudly
# here rather than emit a cryptic "No rule to make target .../macosx/*.c".
if [ ! -f "$R/vendor/tcl/macosx/tclMacOSXBundle.c" ]; then
echo "build_tcl: ERROR: vendor/tcl lacks the macosx/ subdir (needed for a" >&2
echo " Darwin cross build). Restore the full tcl9.0.4 source tree (with" >&2
echo " macosx/) before cross-building tcl for mac-x64/mac-arm64." >&2
return 1
fi
SRC="$R/vendor/tcl/unix"
CFG="$SRC/configure"
LIB="libtcl9.0.a"
if [ "$T" = "mac-arm64" ]; then
HOSTTRIPLE="aarch64-apple-darwin"
else
HOSTTRIPLE="x86_64-apple-darwin"
fi
fi
(
cd "$BUILD" || exit 1
if [ "$T" = "win" ]; then
# win: mingw cross via --host, static, zig windres for the .rc.
# No CXX and no tcl_cv_sys_version needed (win/configure does not
# probe the target with a runnable uname). Verified against
# build/cross/win/tcl/config.log (the $ configure line).
"$CFG" \
--host="$HOSTTRIPLE" \
--disable-shared \
CC="$CC" \
AR="$AR" \
RANLIB="$RANLIB" \
RC="$WINRC" \
> cfg.log 2>&1 || { echo "build_tcl: configure failed (see $BUILD/cfg.log)" >&2; exit 1; }
else
# mac: unix/configure via --host with an Apple-Darwin triple.
# tcl_cv_sys_version=Darwin-24.0.0 is REQUIRED: it short-circuits the
# configure step that would otherwise run `uname -r` on the target
# (impossible when cross-compiling; on the Linux host it would
# misdetect and disable Darwin code paths). Value taken verbatim
# from build/cross/mac-*/tcl/config.log. No CXX (Tcl is pure C).
"$CFG" \
--host="$HOSTTRIPLE" \
--disable-shared \
CC="$CC" \
AR="$AR" \
RANLIB="$RANLIB" \
tcl_cv_sys_version=Darwin-24.0.0 \
> cfg.log 2>&1 || { echo "build_tcl: configure failed (see $BUILD/cfg.log)" >&2; exit 1; }
fi
# Build ONLY the static library target (== $LIB). This deliberately
# avoids the default `all`/`binaries` targets, which link tclsh -- a
# cross-compiled binary that cannot run on the build host. The lib rule
# only needs object files + ar/ranlib (STLIB_LD = "$AR cr"; RANLIB).
# win rule (Makefile ~605): ${TCL_LIB_FILE}: ${TCL_OBJS} tclWinPanic
# ${DDE_OBJS} ${REG_OBJS} -> ar cr; ranlib (no zip prereq)
# mac rule (Makefile ~803): ${LIB_FILE}: ${STUB_LIB_FILE} ${OBJS}
# ${TCL_ZIP_FILE} -> ar cr; ranlib. The ${TCL_ZIP_FILE} prereq
# (libtcl9.0.4.zip) is built with the HOST compiler + host zip
# (minizip) -- it does NOT run the cross tclsh. With ZIPFS_BUILD=2
# the zip is a sidecar file and is NOT appended into the .a, so
# the archive contents are pure object files either way.
make -j"$(nproc)" "$LIB" > build.log 2>&1 || { echo "build_tcl: make $LIB failed (see $BUILD/build.log)" >&2; exit 1; }
) || return 1
if [ ! -f "$BUILD/$LIB" ]; then
echo "build_tcl: expected artifact $BUILD/$LIB not produced" >&2
return 1
fi
echo "build_tcl: produced $BUILD/$LIB"
}
# ---- mruby (confidence: high) ----
# ---------------------------------------------------------------------------------------------------
# mruby 4.0.0 (Ruby engine). UNLIKE the C-source deps, libmruby.a is GENERATED by mruby's own Rake
# build: mrbc (the bytecode compiler, run DURING the build to compile the Ruby-written stdlib/gems)
# must be NATIVE, while libmruby.a itself is cross-compiled with the zig wrappers. We therefore emit
# a two-target build_config: a 'host' build that produces ONLY native mrbc, and a cross build that
# produces the target libmruby.a. Gembox matches calog's lean native src/mruby/build_config.rb
# (stdlib + stdlib-ext + math + metaprog; metaprog pulls mruby-compiler for runtime eval). MRB_INT64
# keeps script integers 64-bit. No bin gems in the cross target -- calog links only the library.
#
# Artifact (both mac + win): $O/mruby/libmruby.a
# The mruby Rake writes to vendor/mruby/build/<buildname>/lib/libmruby.a; we copy that to $O/mruby/.
# The generated include tree (mrbconf.h + mruby/presym.h) stays at
# vendor/mruby/build/<buildname>/include -- the adapter compile in crossMacFull/crossWinFull.sh
# includes it directly, so we do NOT relocate it.
# Requires on the build host: ruby (>=2.5), the `rake` gem (minirake just exec's rake), and bison
# (mruby-compiler regenerates y.tab.c). These are host tools; the cross objects come from zig.
# ---------------------------------------------------------------------------------------------------
build_mruby() {
# Per-target build name + host_target triple. The build name must match what the full-CLI adapter
# compile expects for its -I: crossWinFull.sh uses cross-mingw; crossMacFull.sh uses cross-$T.
local buildname
local hosttriple
case "$T" in
win) buildname=cross-mingw; hosttriple=x86_64-w64-mingw32 ;;
mac-x64) buildname=cross-mac-x64; hosttriple=x86_64-apple-darwin ;;
mac-arm64) buildname=cross-mac-arm64; hosttriple=aarch64-apple-darwin ;;
esac
# Windows target links executables with .exe; also gates for_windows? source paths in gems.
local execext_line=""
if [ "$T" = win ]; then
execext_line=" conf.exts.executable = '.exe'"
fi
mkdir -p "$O/mruby"
# Emit the cross build_config next to the artifact (matches the pinned layout: the build_config.rb
# sits in $O/mruby/ and mruby drops its build_config.rb.lock there too). Unquoted heredoc so the
# shell interpolates the wrapper paths / build name / triple; the Ruby %{outfile}/%{objs} tokens
# and << appends contain no shell metacharacters.
cat > "$O/mruby/build_config.rb" <<EOF
# GENERATED by tools/crossDeps.sh -- calog cross build_config for mruby ($T).
# host -- native gcc build producing ONLY mrbc (runs on the build host to compile the
# Ruby-written stdlib/gems into libmruby.a). libmruby disabled here.
# $buildname -- the cross static libmruby.a, compiled by the zig wrapper for $T.
# Host target: native gcc, mrbc only. Named exactly 'host' so CrossBuild#mrbcfile finds it.
MRuby::Build.new('host') do |conf|
conf.toolchain :gcc
# Match the target's integer width so mrbc emits bytecode consistent with 64-bit script ints.
conf.cc.defines << 'MRB_INT64'
conf.build_mrbc_exec
conf.disable_libmruby
end
# Cross target: $T static libmruby.a via zig.
MRuby::CrossBuild.new('$buildname') do |conf|
conf.toolchain :gcc
# host_target drives for_windows? and platform-aware source paths.
conf.host_target = '$hosttriple'
conf.cc.command = "$CC"
conf.cc.flags << '-O2'
conf.cc.defines << 'MRB_INT64'
conf.linker.command = "$CC"
# archiver: mruby PREPENDS conf.archiver.command to archive_options, so the rcs letters must live
# in archive_options (putting them in command would inject a stray "rcs" arg before the options).
conf.archiver.command = "$AR"
conf.archiver.archive_options = 'rcs "%{outfile}" %{objs}'
$execext_line
# Lean gembox -- identical to calog's native src/mruby/build_config.rb.
conf.gembox 'stdlib'
conf.gembox 'stdlib-ext'
conf.gembox 'math'
conf.gembox 'metaprog'
end
EOF
# Force a clean rebuild of THIS target's cross tree so no stale objects survive (the native 'host'
# mrbc is left cached across targets -- it is architecture-neutral). vendor/mruby stays pristine
# apart from its own build/ output dir, exactly as the native Makefile build already uses it.
rm -rf "$R/vendor/mruby/build/$buildname"
# CALOG_ZIG is already exported by the caller so the zcc-*/zar wrappers resolve zig. Run mruby's
# Rake (minirake exec's the system rake) from the mruby tree so `conf.gembox 'name'` resolves
# against vendor/mruby/mrbgems. -j4 matches the native build.
( cd "$R/vendor/mruby" && MRUBY_CONFIG="$O/mruby/build_config.rb" ruby ./minirake -j4 )
# Copy the pinned artifact to the path the full-CLI link expects.
cp "$R/vendor/mruby/build/$buildname/lib/libmruby.a" "$O/mruby/libmruby.a"
}
# ---- winpthreads (confidence: high) ----
build_winpthreads() {
# WIN ONLY. macOS has native pthreads in libSystem (zig links it); no
# vendored winpthreads and no -DWINPTHREAD_STATIC on mac. Return early.
case "$T" in
mac-x64|mac-arm64)
echo "== winpthreads: skipped for $T (native libSystem pthreads) =="
return 0
;;
esac
# --- Windows (x86_64-windows-gnu / mingw) -------------------------------
# Object-rule build: compile every top-level src/*.c (NOT src/libgcc/*),
# archive into the single static lib the full-CLI link expects.
echo "== building vendored winpthreads for $T =="
local SRCDIR="$R/vendor/winpthreads"
local OBJ="$O/wpobj"
local OUTLIB="$O/libwinpthreads.a"
rm -rf "$OBJ"
mkdir -p "$OBJ"
local c
for c in "$SRCDIR"/src/*.c; do
"$CC" -c -O2 -w \
-I"$SRCDIR/include" -I"$SRCDIR/src" \
-DWINPTHREAD_STATIC=1 -DIN_WINPTHREAD=1 \
"$c" -o "$OBJ/$(basename "$c" .c).o" \
|| { echo "winpthreads build failed on $c"; return 1; }
done
rm -f "$OUTLIB"
"$AR" rcs "$OUTLIB" "$OBJ"/*.o \
|| { echo "winpthreads archive failed"; return 1; }
echo " [ok] $OUTLIB"
}
# ---- postgres (confidence: high) ----
# build_postgres -- PATCHED out-of-tree (in a COPY) build of vendor/postgres; produces the libpq
# frontend stack the full-CLI link consumes (libpq.a + libpgcommon_shlib.a + libpgport_shlib.a +
# fe_memutils.o + generated src/include). mac uses libSystem pthreads (no patch); win renames
# .obj->.o, forces a real ar rule for libpq.a, drops unsupported LDFLAGS, and localizes libpq's
# bundled pthread-win32 symbols to pg_pthread_* (vs winpthreads).
build_postgres() {
local BLD="$O/postgres-build"
local HOST LDOSSL PGLIBS PGWINDRES J
J=$(nproc 2>/dev/null || echo 4)
# PGLIBS: the Windows system libs OpenSSL depends on, so configure's `-lcrypto` link test passes
# (mingw OpenSSL pulls in ws2_32/crypt32/bcrypt/...). Empty on mac (libSystem covers it).
# PGWINDRES: on win, libpq compiles a win32ver.rc version resource (win32ver.o) via $(WINDRES) --
# point it at the zig-rc shim. Empty on mac (no .rc).
case "$T" in
win) HOST=x86_64-w64-mingw32 ; LDOSSL="$O/openssl-src" ; PGLIBS="-lws2_32 -lcrypt32 -lsecur32 -lbcrypt -lgdi32 -luser32 -ladvapi32" ; PGWINDRES="WINDRES=$BIN/zwindres-win" ;;
mac-x64) HOST=x86_64-apple-darwin ; LDOSSL="$O/openssl-repack" ; PGLIBS="" ; PGWINDRES="" ;;
mac-arm64) HOST=aarch64-apple-darwin; LDOSSL="$O/openssl-repack" ; PGLIBS="" ; PGWINDRES="" ;;
esac
# Fresh in-tree COPY so vendor/postgres stays pristine; distclean any inherited config/objects.
rm -rf "$BLD"
cp -a "$R/vendor/postgres" "$BLD"
( cd "$BLD" && make distclean >/dev/null 2>&1 || true )
if [ "$T" = win ]; then
# (1) Drop the LDFLAGS zig's lld/mingw rejects (absent from the produced Makefile.global).
perl -pi -e 's/ -Wl,--allow-multiple-definition//g; s/ -Wl,--disable-auto-import//g;' \
"$BLD/src/template/win32"
# (2) On win32 the stock Makefile.shlib builds libpq.a as a DLL import lib. Neutralize
# haslibarule + delete that override so the stock static-lib rule (rm; ar crs; touch)
# builds a real archive -- the recipe recorded in pg-libpq.log.
perl -pi -e 's/^\s*haslibarule\s*=\s*yes\s*$//' "$BLD/src/Makefile.shlib"
perl -0pi -e 's/\$\(stlib\): \$\(shlib\)\n\ttouch \$\@\n//g' "$BLD/src/Makefile.shlib"
fi
# configure (in-tree; template auto-selected from --host). SSL=openssl gives libpq TLS + RNG;
# CPPFLAGS/LDFLAGS point at the pinned OpenSSL (mac links the BSD-ar repack).
( cd "$BLD" && ./configure --host="$HOST" \
--without-readline --without-zlib --without-icu --with-ssl=openssl \
CC="$CC" AR="$AR" RANLIB="$RANLIB" \
CPPFLAGS="-I$O/openssl-src/include" LDFLAGS="-L$LDOSSL" LIBS="$PGLIBS" ) >"$O/pg-cfg.log" 2>&1
if [ "$T" = win ]; then
# (3) autoconf named conditional LIBOBJS objects .obj (OBJEXT=obj on mingw) but PG archives .o.
perl -pi -e 's/\.obj\b/.o/g' "$BLD/src/Makefile.global"
fi
# Generated headers first (codegen must not race under -j), then the leaf libs.
( cd "$BLD" && make -C src/backend generated-headers $PGWINDRES ) >"$O/pg-genhdr.log" 2>&1
( cd "$BLD" && make -C src/port -j"$J" $PGWINDRES ) >"$O/pg-port.log" 2>&1
( cd "$BLD" && make -C src/common -j"$J" $PGWINDRES ) >"$O/pg-common.log" 2>&1
( cd "$BLD" && make -C src/interfaces/libpq all-static-lib -j"$J" $PGWINDRES ) >"$O/pg-libpq.log" 2>&1
if [ "$T" = win ]; then
# (4) Localize libpq's bundled pthread-win32 symbols (ABI-incompatible with winpthreads).
local LIBPQ="$BLD/src/interfaces/libpq/libpq.a" TMP
TMP=$(mktemp -d)
( cd "$TMP" && cp "$LIBPQ" . && "$AR" x libpq.a && \
printf 'pthread_self pg_pthread_self\npthread_setspecific pg_pthread_setspecific\npthread_getspecific pg_pthread_getspecific\npthread_mutex_init pg_pthread_mutex_init\npthread_mutex_lock pg_pthread_mutex_lock\npthread_mutex_unlock pg_pthread_mutex_unlock\n' > s.txt && \
for o in *.o; do "${OBJCOPY:-objcopy}" --redefine-syms=s.txt "$o" "$o.n" && mv "$o.n" "$o"; done && \
rm -f "$LIBPQ" && "$AR" rcs "$LIBPQ" *.o )
rm -rf "$TMP"
echo " [patch] libpq.a pthread-win32 symbols localized (pg_pthread_*)"
fi
echo " [lib] postgres: libpq.a + libpgcommon_shlib.a + libpgport_shlib.a + fe_memutils.o ($T)"
}
# ---- libarchive (confidence: high) ----
# build_libarchive -- libarchive.a with all 5 codecs pinned to the just-built $O artifacts. mac = XAR
# via pinned OpenSSL (MD5/SHA1, BSD-ar repack) + libxml2 + iconv-from-libSystem (empty ICONV dir);
# win = codecs only (Windows xar auto-uses system XmlLite + CNG/bcrypt at the final link).
build_libarchive() {
if [ "$T" = win ]; then
rm -rf "$O/libarchive"
cmake -S vendor/libarchive -B "$O/libarchive" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
-DENABLE_ACL=OFF -DENABLE_XATTR=OFF -DENABLE_ICONV=OFF -DENABLE_LIBB2=OFF \
-DENABLE_LIBXML2=OFF -DENABLE_EXPAT=OFF -DENABLE_OPENSSL=OFF \
-DENABLE_LZO=OFF -DENABLE_NETTLE=OFF -DENABLE_MBEDTLS=OFF -DENABLE_PCREPOSIX=OFF -DENABLE_PCRE2POSIX=OFF \
-DENABLE_ZLIB=ON -DZLIB_INCLUDE_DIR="$R/vendor/zlib" -DZLIB_LIBRARY="$O/lib/libz.a" \
-DENABLE_BZip2=ON -DBZIP2_INCLUDE_DIR="$R/vendor/bzip2" -DBZIP2_LIBRARIES="$O/lib/libbz2.a" \
-DENABLE_LZMA=ON -DLIBLZMA_INCLUDE_DIR="$R/vendor/xz/src/liblzma/api" -DLIBLZMA_LIBRARY="$O/xz/liblzma.a" \
-DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR="$R/vendor/zstd" -DZSTD_LIBRARY="$O/lib/libzstd.a" \
-DENABLE_LZ4=ON -DLZ4_INCLUDE_DIR="$R/vendor/lz4" -DLZ4_LIBRARY="$O/lib/liblz4.a" \
>"$O/la-cfg.log" 2>&1
cmake --build "$O/libarchive" --target archive_static -j >"$O/la-build.log" 2>&1
echo " [lib] libarchive.a (codecs only; win xar via XmlLite/CNG) ($T)"
else
# macOS: libarchive gates libxml2 (hence xar) on a working iconv, but zig's SDK-less libSystem
# stub exports no iconv symbols, so the detection link-test fails and xar silently stubs out.
# With an Apple SDK, feed CMake the SDK's iconv.h plus the minimal link stub
# (tools/macStubs/libiconv.tbd, which resolves to the real /usr/lib/libiconv at runtime) so
# iconv is detected and xar is really built; without one, keep the empty-dir path (the build
# still succeeds, but xar ends up stubbed). musl uses crossArchive.sh, not this script.
local MACSDK ICONVFLAGS
MACSDK="${CALOG_MACSDK:-/home/scott/macos-sdk/MacOSX13.3.sdk}"
rm -rf "$O/libarchive-xar"
if [ -f "$MACSDK/usr/include/iconv.h" ]; then
mkdir -p "$O/iconvinc"
cp "$MACSDK/usr/include/iconv.h" "$O/iconvinc/iconv.h"
ICONVFLAGS="-DICONV_INCLUDE_DIR=$O/iconvinc -DLIBICONV_PATH=$R/tools/macStubs/libiconv.tbd"
echo " [sdk] mac libarchive: iconv via SDK ($MACSDK) -> xar ENABLED"
else
mkdir -p "$O/noinc"
ICONVFLAGS="-DICONV_INCLUDE_DIR=$O/noinc"
echo " [sdk] mac libarchive: no Apple SDK at $MACSDK -> xar will be STUBBED (set CALOG_MACSDK)"
fi
cmake -S vendor/libarchive -B "$O/libarchive-xar" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
-DENABLE_ACL=OFF -DENABLE_XATTR=OFF -DENABLE_LIBB2=OFF \
-DENABLE_LZO=OFF -DENABLE_NETTLE=OFF -DENABLE_MBEDTLS=OFF -DENABLE_PCREPOSIX=OFF -DENABLE_PCRE2POSIX=OFF \
-DENABLE_EXPAT=OFF -DPOSIX_REGEX_LIB=NONE \
-DENABLE_ICONV=ON $ICONVFLAGS \
-DENABLE_LIBXML2=ON "-DLIBXML2_INCLUDE_DIR=$R/vendor/libxml2/include;$O/libxml2" -DLIBXML2_LIBRARY="$O/libxml2/libxml2.a" \
-DENABLE_OPENSSL=ON -DOPENSSL_ROOT_DIR="$O/openssl-src" -DOPENSSL_INCLUDE_DIR="$O/openssl-src/include" \
-DOPENSSL_SSL_LIBRARY="$O/openssl-repack/libssl.a" -DOPENSSL_CRYPTO_LIBRARY="$O/openssl-repack/libcrypto.a" \
-DENABLE_ZLIB=ON -DZLIB_INCLUDE_DIR="$R/vendor/zlib" -DZLIB_LIBRARY="$O/lib/libz.a" \
-DENABLE_BZip2=ON -DBZIP2_INCLUDE_DIR="$R/vendor/bzip2" -DBZIP2_LIBRARIES="$O/lib/libbz2.a" \
-DENABLE_LZMA=ON -DLIBLZMA_INCLUDE_DIR="$R/vendor/xz/src/liblzma/api" -DLIBLZMA_LIBRARY="$O/xz/liblzma.a" \
-DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR="$R/vendor/zstd" -DZSTD_LIBRARY="$O/lib/libzstd.a" \
-DENABLE_LZ4=ON -DLZ4_INCLUDE_DIR="$R/vendor/lz4" -DLZ4_LIBRARY="$O/lib/liblz4.a" \
>"$O/la-xar-cfg.log" 2>&1
cmake --build "$O/libarchive-xar" --target archive_static -j >"$O/la-xar-build.log" 2>&1
echo " [lib] libarchive.a (+xar via libxml2/openssl) ($T)"
fi
}
# ===================================================================================================
# Dispatch: build the requested deps (or all, in dependency order).
# ===================================================================================================
ALL="codecs xz openssl libxml2 pcre2 libssh2 mariadb postgres tcl mruby winpthreads libarchive"
DEPS=${*:-$ALL}
# Tolerant dispatch: build every requested dep, collect failures, and report at the end (so one
# broken dep -- e.g. tcl when vendor/tcl lacks win//macosx/ -- does not abort the rest). The `if`
# scopes off `set -e` for the build call; `set -e` still applies INSIDE each build function.
FAILED=""
for d in $DEPS; do
echo "==================== build $d ($T) ===================="
if "build_$d"; then :; else FAILED="$FAILED $d"; fi
done
if [ -n "$FAILED" ]; then
echo "== crossDeps: FAILED:$FAILED (other deps built OK) =="
exit 1
fi
echo "== crossDeps done: $DEPS =="

211
tools/crossMacFull.sh Executable file
View file

@ -0,0 +1,211 @@
#!/usr/bin/env bash
# crossMacFull.sh -- cross-build the FULL calog CLI as macOS Mach-O executables for BOTH
# x86_64 (Intel) and arm64 (Apple Silicon) with a single zig toolchain on Linux. No Apple
# SDK: zig ships a libSystem stub (libc/pthreads/BSD sockets/getaddrinfo/dlopen/iconv), which
# is everything calog's OS surface needs.
#
# It is the Darwin sibling of tools/crossWinFull.sh. The Windows-isms are dropped: macOS has
# native pthreads in libSystem (no vendored winpthreads, no -DWINPTHREAD_STATIC), the C++
# engine (Squirrel) links against zig's libc++ via the c++ driver, there is no -ldl, no Win32
# import libs, and no libpq pthread-symbol patch (libpq uses libSystem pthreads directly). The
# compression/archive stack is built here per the crossArchive.sh MUSL xar recipe -- macOS is
# like musl for xar: iconv is in libSystem (empty-dir ICONV_INCLUDE_DIR pin), and there is no
# CommonCrypto without the Apple SDK, so xar's MD5/SHA1 come from the pinned OpenSSL.
#
# Per arch under build/cross/<mac-x64|mac-arm64>/ the heavy deps -- openssl-src (+openssl-repack),
# libxml2, pcre2, libssh2, mariadb, tcl, mruby, postgres-build, the codecs, xz, libarchive -- are
# PINNED (not rebuilt here). Rebuild them reproducibly from vendor/ source with
# `./tools/crossDeps.sh mac-x64` (or mac-arm64) before running this script; that is what makes the
# full cross build reproducible from a clean checkout.
# This script still builds: sqlite, enet (unix.c backend), the engine VMs (lua/quickjs/squirrel/
# my-basic/berry/s7/wren/janet), and all of calog's own source, then links the complete runner.
set -eu
cd /home/scott/claude/calog
R=/home/scott/claude/calog
ZIG=${ZIG:-${CALOG_ZIG:-/home/scott/zig/current/zig}}
[ -x "$ZIG" ] || { echo "error: zig not found at $ZIG. Set ZIG=/path/to/zig (a PERMANENT install, not a session/temp dir); see https://ziglang.org/download/." >&2; exit 1; }
export CALOG_ZIG="$ZIG" # the build/cross/bin wrappers resolve zig via $CALOG_ZIG
AR="$R/build/cross/bin/zar"
# macOS Keychain trust for the HTTPS client (calogHttp httpLoadMacRoots). It needs the real Apple SDK
# framework headers (Security, CoreFoundation, libDER), which zig does NOT ship. When an SDK is
# present, calogHttp is compiled with -DCALOG_MAC_KEYCHAIN_TRUST against those headers and linked
# against the minimal tools/macStubs/*.tbd (zig's Mach-O linker segfaults on the real multi-target
# SDK .tbd, so we link tiny hand-written stubs that export only the few symbols we call; the binary
# still imports the real system frameworks by install-name at runtime). Without an SDK the build
# omits Keychain trust and HTTPS falls back to SSL_CERT_FILE / CALOG_CA_BUNDLE. Set CALOG_MACSDK to
# point at a MacOSX*.sdk directory (default: a permanent install alongside zig).
MACSDK="${CALOG_MACSDK:-/home/scott/macos-sdk/MacOSX13.3.sdk}"
MACTRUST=""
MACTRUSTLINK=""
if [ -d "$MACSDK/System/Library/Frameworks/Security.framework" ]; then
MACTRUST="-DCALOG_MAC_KEYCHAIN_TRUST -F$MACSDK/System/Library/Frameworks -isystem $MACSDK/usr/include"
# Link stubs (resolved to the real system libraries by install-name at runtime): the Keychain-trust
# frameworks, plus libiconv, which the SDK-built libarchive references for its xar support.
MACTRUSTLINK="$R/tools/macStubs/CoreFoundation.tbd $R/tools/macStubs/Security.tbd $R/tools/macStubs/libiconv.tbd"
echo "[sdk] macOS Keychain trust + libarchive xar ENABLED (SDK: $MACSDK)"
else
echo "[sdk] macOS SDK not found at $MACSDK -- Keychain trust DISABLED (HTTPS needs SSL_CERT_FILE/CALOG_CA_BUNDLE); set CALOG_MACSDK to enable"
fi
# ---------------------------------------------------------------------------------------------
# Helpers (compile a directory of sources into one static archive).
# ---------------------------------------------------------------------------------------------
mklib(){ local out=$1 cc=$2 vo=$3; shift 3; local fl=(); while [ "$1" != -- ]; do fl+=("$1"); shift; done; shift
local objs=() s b; rm -rf "$vo/$out"; mkdir -p "$vo/$out"
for s in "$@"; do b=$(basename "$s"); b=${b%.*}; "$cc" "${fl[@]}" -c "$s" -o "$vo/$out/$b.o"; objs+=("$vo/$out/$b.o"); done
"$AR" rcs "$out" "${objs[@]}"; echo " [lib] $(basename "$out")"; }
# build_codecs <cc> <outdir> -- zlib/bzip2/lz4/zstd object-rule codecs (zlib needs
# -DHAVE_UNISTD_H for lseek, as in crossArchive.sh).
build_codecs(){ local CC=$1 O=$2 c
mkdir -p "$O/obj" "$O/lib"
for c in vendor/zlib/*.c; do "$CC" -std=c11 -D_GNU_SOURCE -DHAVE_UNISTD_H -w -O2 -Ivendor/zlib -c "$c" -o "$O/obj/z_$(basename "$c" .c).o"; done
"$AR" rcs "$O/lib/libz.a" "$O"/obj/z_*.o
for c in vendor/bzip2/*.c; do "$CC" -std=c11 -D_GNU_SOURCE -w -O2 -Ivendor/bzip2 -c "$c" -o "$O/obj/bz_$(basename "$c" .c).o"; done
"$AR" rcs "$O/lib/libbz2.a" "$O"/obj/bz_*.o
for c in vendor/lz4/*.c; do "$CC" -std=c11 -D_GNU_SOURCE -w -O2 -Ivendor/lz4 -c "$c" -o "$O/obj/l4_$(basename "$c" .c).o"; done
"$AR" rcs "$O/lib/liblz4.a" "$O"/obj/l4_*.o
for c in vendor/zstd/common/*.c vendor/zstd/compress/*.c vendor/zstd/decompress/*.c; do "$CC" -std=c11 -D_GNU_SOURCE -w -O2 -DZSTD_DISABLE_ASM -Ivendor/zstd -c "$c" -o "$O/obj/zs_$(basename "$c" .c).o"; done
"$AR" rcs "$O/lib/libzstd.a" "$O"/obj/zs_*.o
echo " [lib] libz/libbz2/liblz4/libzstd.a"; }
# build_xz <toolchain> <outdir> -- vendored xz/liblzma via its own CMake, out of tree.
build_xz(){ cmake -S vendor/xz -B "$2/xz" -DCMAKE_TOOLCHAIN_FILE="$1" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DENABLE_THREADS=OFF -DENABLE_NLS=OFF -DENABLE_DOXYGEN=OFF >"$2/xz-cfg.log" 2>&1
cmake --build "$2/xz" --target liblzma -j >"$2/xz-build.log" 2>&1
echo " [lib] liblzma.a"; }
# build_libarchive_xar <toolchain> <outdir> -- libarchive WITH xar, exactly the crossArchive.sh
# MUSL recipe but with the PINNED mac OpenSSL + libxml2. iconv comes from libSystem (empty-dir
# ICONV_INCLUDE_DIR pin so FIND_PATH does not grab host glibc headers; zig resolves iconv.h).
# CommonCrypto is unavailable (no Apple SDK), so OpenSSL supplies xar's MD5/SHA1.
build_libarchive_xar(){ local TC=$1 O=$2
mkdir -p "$O/noinc"
cmake -S vendor/libarchive -B "$O/libarchive-xar" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
-DENABLE_ACL=OFF -DENABLE_XATTR=OFF -DENABLE_LIBB2=OFF \
-DENABLE_LZO=OFF -DENABLE_NETTLE=OFF -DENABLE_MBEDTLS=OFF -DENABLE_PCREPOSIX=OFF -DENABLE_PCRE2POSIX=OFF \
-DENABLE_EXPAT=OFF -DPOSIX_REGEX_LIB=NONE \
-DENABLE_ICONV=ON -DICONV_INCLUDE_DIR="$O/noinc" \
-DENABLE_LIBXML2=ON "-DLIBXML2_INCLUDE_DIR=$R/vendor/libxml2/include;$O/libxml2" -DLIBXML2_LIBRARY="$O/libxml2/libxml2.a" \
-DENABLE_OPENSSL=ON -DOPENSSL_ROOT_DIR="$O/openssl-src" -DOPENSSL_INCLUDE_DIR="$O/openssl-src/include" \
-DOPENSSL_SSL_LIBRARY="$O/openssl-repack/libssl.a" -DOPENSSL_CRYPTO_LIBRARY="$O/openssl-repack/libcrypto.a" \
-DENABLE_ZLIB=ON -DZLIB_INCLUDE_DIR="$R/vendor/zlib" -DZLIB_LIBRARY="$O/lib/libz.a" \
-DENABLE_BZip2=ON -DBZIP2_INCLUDE_DIR="$R/vendor/bzip2" -DBZIP2_LIBRARIES="$O/lib/libbz2.a" \
-DENABLE_LZMA=ON -DLIBLZMA_INCLUDE_DIR="$R/vendor/xz/src/liblzma/api" -DLIBLZMA_LIBRARY="$O/xz/liblzma.a" \
-DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR="$R/vendor/zstd" -DZSTD_LIBRARY="$O/lib/libzstd.a" \
-DENABLE_LZ4=ON -DLZ4_INCLUDE_DIR="$R/vendor/lz4" -DLZ4_LIBRARY="$O/lib/liblz4.a" \
>"$O/la-xar-cfg.log" 2>&1
cmake --build "$O/libarchive-xar" --target archive_static -j >"$O/la-xar-build.log" 2>&1
echo " [lib] libarchive.a (+xar via libxml2/openssl)"; }
# =============================================================================================
# build_arch <mac-x64|mac-arm64>
# =============================================================================================
build_arch(){
local A=$1
local CC="$R/build/cross/bin/zcc-$A"
local CXX="$R/build/cross/bin/zcxx-$A"
local TC="$R/build/cross/toolchain-$A.cmake"
local M="$R/build/cross/$A"
local LIB="$M/lib" VO="$M/vmobj" CO="$M/cobj"
mkdir -p "$LIB" "$VO" "$CO"
echo "==================== $A ===================="
# --- 1. compression / archive stack -------------------------------------------------------
build_codecs "$CC" "$M"
build_xz "$TC" "$M"
build_libarchive_xar "$TC" "$M"
# --- 2. engine VMs + sqlite + enet (unix.c backend on macOS, native HAS_* defines) --------
mklib "$LIB/liblua.a" "$CC" "$VO" -std=c99 -w -O2 -DLUA_USE_MACOSX -Ivendor/lua/src -- $(ls vendor/lua/src/*.c | grep -vE '/(lua|luac)\.c$')
mklib "$LIB/libquickjs.a" "$CC" "$VO" -std=c11 -w -O1 -D_GNU_SOURCE -Ivendor/quickjs -- vendor/quickjs/quickjs.c vendor/quickjs/libregexp.c vendor/quickjs/libunicode.c vendor/quickjs/dtoa.c
mklib "$LIB/libsquirrel.a" "$CXX" "$VO" -std=c++11 -w -O1 -D_SQ64 -DSQUSEDOUBLE -Ivendor/squirrel-src/include -Ivendor/squirrel-src/squirrel -- $(ls vendor/squirrel-src/squirrel/*.cpp)
mklib "$LIB/libmybasic.a" "$CC" "$VO" -std=gnu11 -w -O1 -DMB_DOUBLE_FLOAT -- vendor/ourbasic/ourBasic.c
mklib "$LIB/libberry.a" "$CC" "$VO" -std=c99 -w -O1 -Ivendor/berry/src -Ivendor/berry -- $(ls vendor/berry/src/*.c) vendor/berry/default/be_port.c vendor/berry/default/be_modtab.c
mklib "$LIB/libs7.a" "$CC" "$VO" -std=c99 -w -O1 -D_GNU_SOURCE -Ivendor/s7 -- vendor/s7/s7.c
mklib "$LIB/libwren.a" "$CC" "$VO" -std=c99 -w -O1 -Ivendor/wren -- vendor/wren/wren.c
mklib "$LIB/libjanet.a" "$CC" "$VO" -std=c11 -w -O2 -DJANET_NO_NET -DJANET_NO_PROCESSES -DJANET_NO_DYNAMIC_MODULES -Ivendor/janet -- vendor/janet/janet.c
mklib "$LIB/libsqlite3.a" "$CC" "$VO" -std=c11 -w -O2 -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_OMIT_LOAD_EXTENSION -Ivendor/sqlite -- vendor/sqlite/sqlite3.c
mklib "$LIB/libenet.a" "$CC" "$VO" -std=c11 -w -O2 -D_GNU_SOURCE -DHAS_FCNTL=1 -DHAS_POLL=1 -DHAS_GETADDRINFO=1 -DHAS_GETNAMEINFO=1 -DHAS_GETHOSTBYNAME_R=1 -DHAS_GETHOSTBYADDR_R=1 -DHAS_INET_PTON=1 -DHAS_INET_NTOP=1 -DHAS_MSGHDR_FLAGS=1 -DHAS_SOCKLEN_T=1 -Ivendor/enet/include -- $(ls vendor/enet/*.c | grep -vE '/win32\.c$')
# --- 3. compile calog's own objects (core + 10 engine adapters/engines + 19 libs + main) --
local INC="-Isrc -Isrc/lua -Isrc/mybasic -Isrc/squirrel -Isrc/js -Isrc/berry -Isrc/s7 -Isrc/wren -Isrc/mruby -Isrc/tcl -Isrc/janet -Ilibs"
# -Wall -Wextra (not -Werror: vendored headers under clang-cross are not warning-clean) so the
# macOS-only #ifdef branches of calog's own sources get compiler scrutiny they never get on Linux.
local BASE="-std=c11 -Wall -Wextra -O1 -g -pthread $INC"
local ENGDEF="-DCALOG_WITH_LUA -DCALOG_WITH_JS -DCALOG_WITH_SQUIRREL -DCALOG_WITH_MYBASIC -DCALOG_WITH_BERRY -DCALOG_WITH_S7 -DCALOG_WITH_WREN -DCALOG_WITH_MRUBY -DCALOG_WITH_TCL -DCALOG_WITH_JANET"
cc(){ local out=$1 src=$2; shift 2; "$CC" $BASE "$@" -c "$src" -o "$CO/$out.o"; }
cc broker src/broker.c
cc value src/value.c
cc context src/context.c
cc luaEngine src/lua/luaEngine.c; cc jsEngine src/js/jsEngine.c
cc squirrelEngine src/squirrel/squirrelEngine.c; cc mybasicEngine src/mybasic/mybasicEngine.c
cc berryEngine src/berry/berryEngine.c; cc s7Engine src/s7/s7Engine.c
cc wrenEngine src/wren/wrenEngine.c; cc mrubyEngine src/mruby/mrubyEngine.c
cc tclEngine src/tcl/tclEngine.c; cc janetEngine src/janet/janetEngine.c
cc luaAdapter src/lua/luaAdapter.c -Ivendor/lua/src
cc jsAdapter src/js/jsAdapter.c -Ivendor/quickjs
cc squirrelAdapter src/squirrel/squirrelAdapter.c -D_SQ64 -DSQUSEDOUBLE -Ivendor/squirrel-src/include
cc mybasicAdapter src/mybasic/mybasicAdapter.c -Ivendor/ourbasic -DMB_DOUBLE_FLOAT
cc berryAdapter src/berry/berryAdapter.c -Ivendor/berry/src -Ivendor/berry
cc s7Adapter src/s7/s7Adapter.c -Ivendor/s7
cc wrenAdapter src/wren/wrenAdapter.c -Ivendor/wren
cc mrubyAdapter src/mruby/mrubyAdapter.c -Ivendor/mruby/include -Ivendor/mruby/build/cross-$A/include
cc tclAdapter src/tcl/tclAdapter.c -Ivendor/tcl/generic -DSTATIC_BUILD
cc janetAdapter src/janet/janetAdapter.c -Ivendor/janet
cc calogMain src/calogMain.c $ENGDEF
cc calogArchive libs/calogArchive.c -Ivendor/libarchive/libarchive
cc calogCrypto libs/calogCrypto.c -I"$M/openssl-src/include"
cc calogCsv libs/calogCsv.c
cc calogDbFull libs/calogDb.c -Ivendor/sqlite -Ivendor/postgres/src/interfaces/libpq -Ivendor/postgres/src/include -I"$M/postgres-build/src/include" -Ivendor/mariadb/include -I"$M/mariadb/include" -DCALOG_WITH_SQLITE -DCALOG_WITH_PG -DCALOG_WITH_MYSQL
cc calogExport libs/calogExport.c
cc calogFs libs/calogFs.c
cc calogHttp libs/calogHttp.c -I"$M/openssl-src/include" $MACTRUST
cc calogJson libs/calogJson.c
cc calogKv libs/calogKv.c
cc calogNet libs/calogNet.c -Ivendor/enet/include -I"$M/openssl-src/include"
cc calogProc libs/calogProc.c
cc calogPubsub libs/calogPubsub.c
cc calogRegex libs/calogRegex.c -I"$M/pcre2" -DPCRE2_STATIC
cc calogSsh libs/calogSsh.c -Ivendor/libssh2/include
cc calogTask libs/calogTask.c $ENGDEF
cc calogTime libs/calogTime.c
cc calogTimer libs/calogTimer.c
cc calogXml libs/calogXml.c -Ivendor/libxml2/include -I"$M/libxml2" -DLIBXML_STATIC
cc calogHandle libs/calogHandle.c
echo " [obj] all calog objects compiled"
# --- 4. link the full Mach-O. C++ driver pulls zig's libc++ (Squirrel). Native pthreads,
# sockets, DNS, dlopen, iconv all resolve against libSystem -- no -ldl, no sys libs.
# The PG frontend archives are circular, so they go in a --start-group with the
# separately-shipped fe_memutils.o (excluded from libpgcommon_shlib.a). OpenSSL is
# linked from openssl-repack/ (the BSD-format __.SYMDEF archives): OpenSSL's own build
# emitted GNU-format .a files under openssl-src/, which zig's Mach-O linker cannot
# parse ("unknown cpu architecture"); the repack is the same objects in BSD ar form. --
local VMLIBS="$LIB/liblua.a $LIB/libquickjs.a $LIB/libsquirrel.a $LIB/libmybasic.a $LIB/libberry.a $LIB/libs7.a $LIB/libwren.a $LIB/libjanet.a $M/mruby/libmruby.a $M/tcl/libtcl9.0.a $LIB/libsqlite3.a $LIB/libenet.a"
local ARCH="$M/libarchive-xar/libarchive/libarchive.a $M/libxml2/libxml2.a $M/xz/liblzma.a $LIB/libzstd.a $LIB/liblz4.a $LIB/libbz2.a $LIB/libz.a"
local PG="$M/postgres-build/src/interfaces/libpq/libpq.a $M/postgres-build/src/common/libpgcommon_shlib.a $M/postgres-build/src/port/libpgport_shlib.a $M/postgres-build/src/common/fe_memutils.o"
# libssh2 is built Release (-O2, no UBSan) by tools/crossDeps.sh, so no __ubsan_handle_* symbols to
# resolve and NO -fsanitize=undefined at link -- matching the Windows/Linux binaries (which never
# carried the UBSan runtime). If you instead use an old UBSan-instrumented libssh2.a, add
# `-fsanitize=undefined` back here to pull zig's UBSan runtime.
"$CXX" -O1 -g -o "$M/calog" $CO/*.o \
$VMLIBS "$M/libssh2/_build/src/libssh2.a" $ARCH "$M/pcre2/libpcre2-8.a" \
-Wl,--start-group $PG -Wl,--end-group \
"$M/mariadb/libmariadb/libmariadbclient.a" "$M/openssl-repack/libssl.a" "$M/openssl-repack/libcrypto.a" \
$MACTRUSTLINK \
-lm
echo " [BIN] $M/calog"
file "$M/calog"
}
# Build the arch(s) named on the command line, or both by default.
ARCHS=("$@")
[ ${#ARCHS[@]} -eq 0 ] && ARCHS=(mac-x64 mac-arm64)
for a in "${ARCHS[@]}"; do build_arch "$a"; done
echo "== done =="

117
tools/crossWinFull.sh Normal file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env bash
# crossWinFull.sh -- cross-build the FULL calog CLI as a Windows PE (x86_64-windows-gnu) with zig.
#
# Prereqs under build/cross/win/ (pinned, NOT rebuilt here): openssl (libssl/libcrypto), libxml2,
# pcre2, libssh2, mariadb, postgres (libpq+pgcommon+pgport), tcl (libtcl90), mruby (libmruby),
# libarchive+codecs (xz/zstd/lz4/bz2/z), libwinpthreads. Rebuild them reproducibly from vendor/
# source with `./tools/crossDeps.sh win` before running this script -- that is what makes the full
# cross build reproducible from a clean checkout.
# Compilers: zcc-win / zcxx-win = `zig cc|c++ -target x86_64-windows-gnu`; zar = `zig ar`.
set -eu
cd /home/scott/claude/calog
R=/home/scott/claude/calog
CC="$R/build/cross/bin/zcc-win"
CXX="$R/build/cross/bin/zcxx-win"
AR="$R/build/cross/bin/zar"
ZIG=${ZIG:-${CALOG_ZIG:-/home/scott/zig/current/zig}}
[ -x "$ZIG" ] || { echo "error: zig not found at $ZIG. Set ZIG=/path/to/zig (a PERMANENT install, not a session/temp dir); see https://ziglang.org/download/." >&2; exit 1; }
export CALOG_ZIG="$ZIG" # the build/cross/bin wrappers resolve zig via $CALOG_ZIG
W=$R/build/cross/win
LIB=$W/lib; VO=$W/vmobj; CO=$W/cobj
mkdir -p "$LIB" "$VO" "$CO"
# ---------------------------------------------------------------------------------------------
# 1. Build the engine VMs + sqlite + enet (win32 backend) into static archives.
# s7 needs a <sys/utsname.h> shim on mingw (see build/cross/win/shim/).
# ---------------------------------------------------------------------------------------------
mklib(){ local out=$1 cc=$2; shift 2; local fl=(); while [ "$1" != -- ]; do fl+=("$1"); shift; done; shift
local objs=() s b; rm -rf "$VO/$out"; mkdir -p "$VO/$out"
for s in "$@"; do b=$(basename "$s"); b=${b%.*}; "$cc" "${fl[@]}" -c "$s" -o "$VO/$out/$b.o"; objs+=("$VO/$out/$b.o"); done
"$AR" rcs "$LIB/$out" "${objs[@]}"; echo " [lib] $out"; }
mklib liblua.a "$CC" -std=c99 -w -O2 -Ivendor/lua/src -- $(ls vendor/lua/src/*.c | grep -vE '/(lua|luac)\.c$')
mklib libquickjs.a "$CC" -std=c11 -w -O1 -D_GNU_SOURCE -Ivendor/quickjs -- vendor/quickjs/quickjs.c vendor/quickjs/libregexp.c vendor/quickjs/libunicode.c vendor/quickjs/dtoa.c
mklib libsquirrel.a "$CXX" -std=c++11 -w -O1 -D_SQ64 -DSQUSEDOUBLE -Ivendor/squirrel-src/include -Ivendor/squirrel-src/squirrel -- $(ls vendor/squirrel-src/squirrel/*.cpp)
mklib libmybasic.a "$CC" -std=gnu11 -w -O1 -DMB_DOUBLE_FLOAT -- vendor/ourbasic/ourBasic.c
mklib libberry.a "$CC" -std=c99 -w -O1 -Ivendor/berry/src -Ivendor/berry -- $(ls vendor/berry/src/*.c) vendor/berry/default/be_port.c vendor/berry/default/be_modtab.c
mklib libs7.a "$CC" -std=c99 -w -O1 -D_GNU_SOURCE -I"$W/shim" -Ivendor/s7 -- vendor/s7/s7.c
mklib libwren.a "$CC" -std=c99 -w -O1 -Ivendor/wren -- vendor/wren/wren.c
mklib libjanet.a "$CC" -std=c11 -w -O2 -DJANET_NO_NET -DJANET_NO_PROCESSES -DJANET_NO_DYNAMIC_MODULES -Ivendor/janet -- vendor/janet/janet.c
mklib libsqlite3.a "$CC" -std=c11 -w -O2 -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_OMIT_LOAD_EXTENSION -Ivendor/sqlite -- vendor/sqlite/sqlite3.c
mklib libenet.a "$CC" -std=c11 -w -O2 -Ivendor/enet/include -- $(ls vendor/enet/*.c | grep -vE '/unix\.c$')
# ---------------------------------------------------------------------------------------------
# 2. Patch the pinned libpq.a: rename its private win32 pthread emulation (pg_pthread_*) so it
# stops colliding with winpthreads (ABI-incompatible pthread_mutex_t). Writes a copy; the
# pinned original is left untouched.
# ---------------------------------------------------------------------------------------------
PW=$W/patched; rm -rf "$PW"; mkdir -p "$PW"
TMP=$(mktemp -d); ( cd "$TMP" && cp "$W/postgres/lib/libpq.a" . && "$AR" x libpq.a && \
printf 'pthread_self pg_pthread_self\npthread_setspecific pg_pthread_setspecific\npthread_getspecific pg_pthread_getspecific\npthread_mutex_init pg_pthread_mutex_init\npthread_mutex_lock pg_pthread_mutex_lock\npthread_mutex_unlock pg_pthread_mutex_unlock\n' > s.txt && \
for o in *.o; do objcopy --redefine-syms=s.txt "$o" "$o.n" && mv "$o.n" "$o"; done && \
"$AR" rcs "$PW/libpq.a" *.o ); rm -rf "$TMP"
echo " [patch] libpq.a pthread symbols localized -> $PW/libpq.a"
# ---------------------------------------------------------------------------------------------
# 3. Compile calog's own objects (core + 10 engine adapters/engines + 19 libraries + main).
# ---------------------------------------------------------------------------------------------
INC="-Isrc -Isrc/lua -Isrc/mybasic -Isrc/squirrel -Isrc/js -Isrc/berry -Isrc/s7 -Isrc/wren -Isrc/mruby -Isrc/tcl -Isrc/janet -Ilibs"
WP="-Ivendor/winpthreads/include -DWINPTHREAD_STATIC=1"
# -Wall -Wextra (not -Werror: vendored headers under clang-cross are not warning-clean) so the
# Windows-only #ifdef branches of calog's own sources get compiler scrutiny they never get on Linux.
BASE="-std=c11 -Wall -Wextra -O1 -g -pthread $INC $WP"
ENGDEF="-DCALOG_WITH_LUA -DCALOG_WITH_JS -DCALOG_WITH_SQUIRREL -DCALOG_WITH_MYBASIC -DCALOG_WITH_BERRY -DCALOG_WITH_S7 -DCALOG_WITH_WREN -DCALOG_WITH_MRUBY -DCALOG_WITH_TCL -DCALOG_WITH_JANET"
cc(){ local out=$1 src=$2; shift 2; "$CC" "$@" -c "$src" -o "$CO/$out.o"; }
for f in broker value context; do cc $f src/$f.c $BASE; done
cc luaEngine src/lua/luaEngine.c $BASE; cc jsEngine src/js/jsEngine.c $BASE
cc squirrelEngine src/squirrel/squirrelEngine.c $BASE; cc mybasicEngine src/mybasic/mybasicEngine.c $BASE
cc berryEngine src/berry/berryEngine.c $BASE; cc s7Engine src/s7/s7Engine.c $BASE
cc wrenEngine src/wren/wrenEngine.c $BASE; cc mrubyEngine src/mruby/mrubyEngine.c $BASE
cc tclEngine src/tcl/tclEngine.c $BASE; cc janetEngine src/janet/janetEngine.c $BASE
cc luaAdapter src/lua/luaAdapter.c $BASE -Ivendor/lua/src
cc jsAdapter src/js/jsAdapter.c $BASE -Ivendor/quickjs
cc squirrelAdapter src/squirrel/squirrelAdapter.c $BASE -D_SQ64 -DSQUSEDOUBLE -Ivendor/squirrel-src/include
cc mybasicAdapter src/mybasic/mybasicAdapter.c $BASE -Ivendor/ourbasic -DMB_DOUBLE_FLOAT
cc berryAdapter src/berry/berryAdapter.c $BASE -Ivendor/berry/src -Ivendor/berry
cc s7Adapter src/s7/s7Adapter.c $BASE -Ivendor/s7
cc wrenAdapter src/wren/wrenAdapter.c $BASE -Ivendor/wren
cc mrubyAdapter src/mruby/mrubyAdapter.c $BASE -Ivendor/mruby/include -Ivendor/mruby/build/cross-mingw/include
cc tclAdapter src/tcl/tclAdapter.c $BASE -Ivendor/tcl/generic -DSTATIC_BUILD
cc janetAdapter src/janet/janetAdapter.c $BASE -Ivendor/janet
cc calogMain src/calogMain.c $BASE $ENGDEF
cc calogArchive libs/calogArchive.c $BASE -Ivendor/libarchive/libarchive
cc calogCrypto libs/calogCrypto.c $BASE -I"$W/openssl-src/include"
cc calogCsv libs/calogCsv.c $BASE
cc calogDbFull libs/calogDb.c $BASE -Ivendor/sqlite -I"$W/postgres/include" -Ivendor/mariadb/include -I"$W/mariadb/include" -DCALOG_WITH_SQLITE -DCALOG_WITH_PG -DCALOG_WITH_MYSQL
cc calogExport libs/calogExport.c $BASE
cc calogFs libs/calogFs.c $BASE
cc calogHttp libs/calogHttp.c $BASE -I"$W/openssl-src/include"
cc calogJson libs/calogJson.c $BASE
cc calogKv libs/calogKv.c $BASE
cc calogNet libs/calogNet.c $BASE -Ivendor/enet/include -I"$W/openssl-src/include"
cc calogProc libs/calogProc.c $BASE
cc calogPubsub libs/calogPubsub.c $BASE
cc calogRegex libs/calogRegex.c $BASE -I"$W/pcre2" -DPCRE2_STATIC
cc calogSsh libs/calogSsh.c $BASE -Ivendor/libssh2/include
cc calogTask libs/calogTask.c $BASE $ENGDEF
cc calogTime libs/calogTime.c $BASE
cc calogTimer libs/calogTimer.c $BASE
cc calogXml libs/calogXml.c $BASE -Ivendor/libxml2/include -I"$W/libxml2" -DLIBXML_STATIC
cc calogHandle libs/calogHandle.c $BASE
echo " [obj] all calog objects compiled"
# ---------------------------------------------------------------------------------------------
# 4. Link the full PE. C++ driver (Squirrel) pulls zig's libc++; winpthreads static; the PG
# frontend archives (+ fe_memutils.o) go in a --start-group; Win32 import libs at the end.
# ---------------------------------------------------------------------------------------------
VMLIBS="$LIB/liblua.a $LIB/libquickjs.a $LIB/libsquirrel.a $LIB/libmybasic.a $LIB/libberry.a $LIB/libs7.a $LIB/libwren.a $LIB/libjanet.a $W/mruby/libmruby.a $W/tcl/libtcl90.a $LIB/libsqlite3.a $LIB/libenet.a"
ARCH="$W/libarchive/libarchive/libarchive.a $W/libxml2/libxml2.a $W/xz/liblzma.a $LIB/libzstd.a $LIB/liblz4.a $LIB/libbz2.a $LIB/libz.a"
PG="$PW/libpq.a $W/postgres/lib/libpgcommon_shlib.a $W/postgres/lib/libpgport_shlib.a $W/postgres-build/src/common/fe_memutils.o"
SYS="-lws2_32 -lwinmm -lbcrypt -lxmllite -lole32 -loleaut32 -luuid -lcrypt32 -lsecur32 -ladvapi32 -lshlwapi -luserenv -lnetapi32 -liphlpapi -luser32 -lgdi32 -lshell32 -lwldap32 -ldnsapi -lkernel32"
"$CXX" -O1 -g -o "$W/calog.exe" $CO/*.o \
$VMLIBS "$W/libssh2/libssh2.a" $ARCH "$W/pcre2/libpcre2-8.a" \
-Wl,--start-group $PG -Wl,--end-group \
"$W/mariadb/libmariadb/libmariadbclient.a" "$W/openssl-src/libssl.a" "$W/openssl-src/libcrypto.a" \
"$W/libwinpthreads.a" -lm $SYS
file "$W/calog.exe"

View file

@ -0,0 +1,15 @@
# Minimal link-time stub for macOS CoreFoundation.framework -- calog cross build (tools/crossMacFull.sh).
#
# zig 0.16's Mach-O linker segfaults parsing the real SDK CoreFoundation.tbd (1000+ symbols across
# maccatalyst/arm64e/x86_64h targets plus reexports). calog links against this hand-written stub
# instead: it exports ONLY the CoreFoundation symbols httpLoadMacRoots uses (see libs/calogHttp.c),
# so the resulting binary imports the real system framework by install-name at runtime on macOS.
# Headers still come from the real SDK at compile time; this file is link-time only. ASCII only.
--- !tapi-tbd
tbd-version: 4
targets: [ x86_64-macos, arm64-macos ]
install-name: '/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation'
exports:
- targets: [ x86_64-macos, arm64-macos ]
symbols: [ _CFRelease, _CFArrayGetCount, _CFArrayGetValueAtIndex, _CFDataGetBytePtr, _CFDataGetLength ]
...

View file

@ -0,0 +1,14 @@
# Minimal link-time stub for macOS Security.framework -- calog cross build (tools/crossMacFull.sh).
#
# zig 0.16's Mach-O linker segfaults parsing the real SDK Security.tbd. calog links against this
# hand-written stub instead: it exports ONLY the Security symbols httpLoadMacRoots uses (see
# libs/calogHttp.c), so the resulting binary imports the real system framework by install-name at
# runtime on macOS. Headers still come from the real SDK at compile time; link-time only. ASCII only.
--- !tapi-tbd
tbd-version: 4
targets: [ x86_64-macos, arm64-macos ]
install-name: '/System/Library/Frameworks/Security.framework/Versions/A/Security'
exports:
- targets: [ x86_64-macos, arm64-macos ]
symbols: [ _SecTrustCopyAnchorCertificates, _SecCertificateCopyData ]
...

View file

@ -0,0 +1,15 @@
# Minimal link-time stub for macOS libiconv -- calog cross build (tools/crossDeps.sh mac libarchive).
#
# libarchive gates its libxml2 (hence xar) support on a working iconv, but zig's SDK-less libSystem
# stub exports no iconv symbols, so the detection link-test fails and xar is silently stubbed out.
# calog links against this hand-written stub (the real /usr/lib/libiconv.2.dylib is loaded by
# install-name at runtime on macOS) so libarchive detects iconv, enables libxml2, and builds a real
# xar read+write backend. Headers come from the real SDK at build time; link-time only. ASCII only.
--- !tapi-tbd
tbd-version: 4
targets: [ x86_64-macos, arm64-macos ]
install-name: '/usr/lib/libiconv.2.dylib'
exports:
- targets: [ x86_64-macos, arm64-macos ]
symbols: [ _iconv, _iconv_open, _iconv_close ]
...

View file

@ -1075,7 +1075,7 @@ static _object_t* _exp_assign = 0;
opndv2.data = opnd2->type == _DT_VAR ? opnd2->data.variable->data->data : opnd2->data; \
val->type = _DT_INT; \
if(opndv1.type == opndv2.type) { \
val->data.integer = (int_t)(mb_memcmp(&opndv1.data, &opndv2.data, sizeof(_raw_t)) __optr 0); \
val->data.integer = (int_t)(_calog_obj_type_cmp(s, &opndv1, &opndv2) __optr 0); \
} else { \
val->data.integer = (int_t)(opndv1.type __optr opndv2.type); \
} \
@ -3255,7 +3255,7 @@ static void* mb_malloc(size_t s) {
rs += _MB_MEM_TAG_SIZE;
#endif /* MB_ENABLE_ALLOC_STAT */
if(_mb_allocate_func)
ret = _mb_allocate_func((unsigned)rs);
ret = _mb_allocate_func(rs); /* [calog fork] no (unsigned) truncation -- param is now 64-bit */
else
ret = (char*)malloc(rs);
mb_assert(ret);
@ -15337,6 +15337,22 @@ _exit:
return result;
}
/* [calog fork] Content-compare two same-type operands for the =, <>, <, >, <=, >= operators. A
usertype-ref that carries a cmp hook (calog's binary byte buffers) compares by content; every other
type keeps the historical raw-representation comparison, so all existing behaviour is unchanged. */
static int _calog_obj_type_cmp(mb_interpreter_t* s, _object_t* a, _object_t* b) {
#ifdef MB_ENABLE_USERTYPE_REF
if(a->type == _DT_USERTYPE_REF) {
mb_cmp_func_t f = a->data.usertype_ref->cmp ? a->data.usertype_ref->cmp : b->data.usertype_ref->cmp;
if(f)
return f(s, a->data.usertype_ref->usertype, b->data.usertype_ref->usertype);
}
#else /* MB_ENABLE_USERTYPE_REF */
mb_unrefvar(s);
#endif /* MB_ENABLE_USERTYPE_REF */
return mb_memcmp(&a->data, &b->data, sizeof(_raw_t));
}
/* Operator = (equal) */
static int _core_equal(mb_interpreter_t* s, void** l) {
int result = MB_FUNC_OK;
@ -17348,7 +17364,7 @@ static int _std_abs(mb_interpreter_t* s, void** l) {
switch(arg.type) {
case MB_DT_INT:
arg.value.integer = (int_t)abs(arg.value.integer);
arg.value.integer = (int_t)llabs(arg.value.integer); /* calog fork: int_t is 64-bit, so int abs() would truncate */
break;
case MB_DT_REAL:

View file

@ -225,7 +225,12 @@ extern "C" {
# define bool_t unsigned char
#endif /* bool_t */
#ifndef int_t
# define int_t int
// --- calog fork ---: widen the BASIC integer type from 32-bit `int` to 64-bit `long long`,
// so my-basic round-trips the full calog int64 value range (the host's calogIntE) without the
// >2^31 clamp the adapter used to enforce. With MB_DOUBLE_FLOAT the value union is already 8
// bytes (real_t=double / void*), so this does not grow mb_value_t. MB_INT_FMT and mb_strtol
// below are updated in lockstep so printing and literal parsing use the 64-bit width too.
# define int_t long long
#endif /* int_t */
#ifndef real_t
# ifdef MB_DOUBLE_FLOAT
@ -236,14 +241,16 @@ extern "C" {
#endif /* real_t */
#ifndef mb_strtol
# define mb_strtol(__s, __e, __r) strtol((__s), (__e), (__r))
// --- calog fork ---: strtoll to match the 64-bit int_t (see the int_t note above).
# define mb_strtol(__s, __e, __r) strtoll((__s), (__e), (__r))
#endif /* mb_strtol */
#ifndef mb_strtod
# define mb_strtod(__s, __e) strtod((__s), (__e))
#endif /* mb_strtod */
#ifndef MB_INT_FMT
# define MB_INT_FMT "%d"
// --- calog fork ---: "%lld" to match the 64-bit int_t (see the int_t note above).
# define MB_INT_FMT "%lld"
#endif /* MB_INT_FMT */
#ifndef MB_REAL_FMT
# define MB_REAL_FMT "%g"
@ -315,7 +322,11 @@ extern "C" {
#endif /* mb_unrefvar */
#ifndef mb_mem_tag_t
typedef unsigned short mb_mem_tag_t;
/* [calog fork] Widened from `unsigned short` (size_t is not yet in scope in this header). The alloc-stat
size tag prefixes every allocation, and mb_malloc returns NULL (then the caller dereferences it ->
crash) for any size that does not fit the tag. A 16-bit tag capped a single allocation at 65535 bytes,
so a script building a >64 KB string or array crashed the host. A 64-bit tag removes that cap. */
typedef unsigned long long mb_mem_tag_t;
#endif /* mb_mem_tag_t */
#ifndef mb_bytes_size
@ -595,7 +606,10 @@ typedef void (* mb_alive_value_checker_t)(struct mb_interpreter_t*, void*, mb_va
typedef int (* mb_meta_operator_t)(struct mb_interpreter_t*, void**, mb_value_t*, mb_value_t*, mb_value_t*);
typedef mb_meta_status_e (* mb_meta_func_t)(struct mb_interpreter_t*, void**, mb_value_t*, const char*);
typedef unsigned (* mb_string_measure_func_t)(const char*);
typedef char* (* mb_memory_allocate_func_t)(unsigned);
/* [calog fork] Param widened from `unsigned` in step with mb_mem_tag_t: mb_malloc passes size+tag to
this callback, so a 32-bit param truncated a >4 GiB request into a tiny buffer the caller then filled
at full size (heap overflow). unsigned long long is >= size_t on every target. */
typedef char* (* mb_memory_allocate_func_t)(unsigned long long);
typedef void (* mb_memory_free_func_t)(char*);
MBAPI unsigned long mb_ver(void);

211
vendor/tcl/macosx/GNUmakefile vendored Normal file
View file

@ -0,0 +1,211 @@
########################################################################################################
#
# Makefile wrapper to build tcl on Mac OS X in a way compatible with the tk/macosx Xcode buildsystem
# uses the standard Unix build system in tcl/unix (which can be used directly instead of this
# if you are not using the tk/macosx projects).
#
# Copyright (c) 2002-2008 Daniel A. Steffen <das@users.sourceforge.net>
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
########################################################################################################
#-------------------------------------------------------------------------------------------------------
# customizable settings
DESTDIR ?=
INSTALL_ROOT ?= ${DESTDIR}
BUILD_DIR ?= ${CURDIR}/../../build
SYMROOT ?= ${BUILD_DIR}/${PROJECT}
OBJROOT ?= ${SYMROOT}
EXTRA_CONFIGURE_ARGS ?=
EXTRA_MAKE_ARGS ?=
INSTALL_PATH ?= /Library/Frameworks
PREFIX ?= /usr/local
BINDIR ?= ${PREFIX}/bin
LIBDIR ?= ${INSTALL_PATH}
MANDIR ?= ${PREFIX}/man
# set to non-empty value to install manpages in addition to html help:
INSTALL_MANPAGES ?=
# Checks and overrides for subframework builds
ifeq (${SUBFRAMEWORK},1)
ifeq (${DYLIB_INSTALL_DIR},)
@echo "Cannot install subframework with empty DYLIB_INSTALL_DIR !" && false
endif
ifeq (${DESTDIR},)
@echo "Cannot install subframework with empty DESTDIR !" && false
endif
override BUILD_DIR = ${DESTDIR}/build
override INSTALL_PATH = /Frameworks
endif
#-------------------------------------------------------------------------------------------------------
# meta targets
meta := all install embedded install-embedded clean distclean test
styles := develop deploy
all := ${styles}
all : ${all}
install := ${styles:%=install-%}
install : ${install}
install-%: action := install-
embedded := ${styles:%=embedded-%}
embedded : embedded-deploy
install-embedded := ${embedded:%=install-%}
install-embedded : install-embedded-deploy
clean := ${styles:%=clean-%}
clean : ${clean}
clean-%: action := clean-
distclean := ${styles:%=distclean-%}
distclean : ${distclean}
distclean-%: action := distclean-
test := ${styles:%=test-%}
test : ${test}
test-%: action := test-
targets := $(foreach v,${meta},${$v})
#-------------------------------------------------------------------------------------------------------
# build styles
BUILD_STYLE =
CONFIGURE_ARGS =
OBJ_DIR = ${OBJROOT}/${BUILD_STYLE}
empty :=
space := ${empty} ${empty}
objdir = $(subst ${space},\ ,${OBJ_DIR})
develop_make_args := BUILD_STYLE=Development CONFIGURE_ARGS=--enable-symbols
deploy_make_args := BUILD_STYLE=Deployment INSTALL_TARGET=install-strip \
EXTRA_CFLAGS=-DNDEBUG
embedded_make_args := EMBEDDED_BUILD=1
install_make_args := INSTALL_BUILD=1
${targets}:
${MAKE} ${action}${PROJECT} \
$(foreach s,${styles} embedded install,$(if $(findstring $s,$@),${${s}_make_args}))
#-------------------------------------------------------------------------------------------------------
# project specific settings
PROJECT := tcl
PRODUCT_NAME := Tcl
UNIX_DIR := ${CURDIR}/../unix
VERSION := $(shell awk -F= '/^TCL_VERSION/ {print $$2; nextfile}' ${UNIX_DIR}/configure.ac)
TCLSH := tclsh${VERSION}
BUILD_TARGET := all tcltest
INSTALL_TARGET := install
export CPPROG := cp -p
INSTALL_TARGETS = install-binaries install-headers install-libraries
ifeq (${EMBEDDED_BUILD},)
INSTALL_TARGETS += install-private-headers
endif
ifeq (${INSTALL_BUILD}_${EMBEDDED_BUILD}_${BUILD_STYLE},1__Deployment)
INSTALL_TARGETS += install-packages html-tcl
ifneq (${INSTALL_MANPAGES},)
INSTALL_TARGETS += install-doc
endif
endif
MAKE_VARS := INSTALL_ROOT INSTALL_TARGETS VERSION GENERIC_FLAGS
MAKE_ARGS_V = $(foreach v,${MAKE_VARS},$v='${$v}')
build-${PROJECT}: target = ${BUILD_TARGET}
install-${PROJECT}: target = ${INSTALL_TARGET}
clean-${PROJECT} distclean-${PROJECT} test-${PROJECT}: \
target = $*
DO_MAKE = +${MAKE} -C "${OBJ_DIR}" ${target} ${MAKE_ARGS_V} ${MAKE_ARGS} ${EXTRA_MAKE_ARGS}
#-------------------------------------------------------------------------------------------------------
# build rules
${PROJECT}:
${MAKE} install-${PROJECT} INSTALL_ROOT="${OBJ_DIR}/"
${objdir}/Makefile: ${UNIX_DIR}/Makefile.in ${UNIX_DIR}/configure \
${UNIX_DIR}/tclConfig.sh.in Tcl-Info.plist.in
mkdir -p "${OBJ_DIR}" && cd "${OBJ_DIR}" && \
if [ ${UNIX_DIR}/configure -nt config.status ]; then ${UNIX_DIR}/configure -C \
--prefix="${PREFIX}" --bindir="${BINDIR}" --libdir="${LIBDIR}" \
--mandir="${MANDIR}" --enable-framework --enable-dtrace --disable-zipfs \
${CONFIGURE_ARGS} ${EXTRA_CONFIGURE_ARGS}; else ./config.status; fi
build-${PROJECT}: ${objdir}/Makefile
${DO_MAKE}
ifeq (${INSTALL_BUILD},)
# symbolic link hackery to trick
# 'make install INSTALL_ROOT=${OBJ_DIR}'
# into building Tcl.framework and tclsh in ${SYMROOT}
@cd "${OBJ_DIR}" && mkdir -p $(dir $(subst ${space},\ ,.${LIBDIR})) $(dir $(subst ${space},\ ,.${BINDIR})) "${SYMROOT}" && \
rm -f ".${LIBDIR}" ".${BINDIR}" && ln -fs "${SYMROOT}" ".${LIBDIR}" && \
ln -fs "${SYMROOT}" ".${BINDIR}" && ln -fs "${OBJ_DIR}/tcltest" "${SYMROOT}"
endif
install-${PROJECT}: build-${PROJECT}
ifeq (${EMBEDDED_BUILD}_${INSTALL_ROOT},1_)
@echo "Cannot install-embedded with empty INSTALL_ROOT !" && false
endif
ifeq (${EMBEDDED_BUILD},1)
@rm -rf "${INSTALL_ROOT}${LIBDIR}/Tcl.framework"
endif
${DO_MAKE}
ifeq (${INSTALL_BUILD},1)
ifeq (${EMBEDDED_BUILD},1)
# if we are embedding frameworks, don't install tclsh
@rm -f "${INSTALL_ROOT}${BINDIR}/${TCLSH}" && \
rmdir -p "${INSTALL_ROOT}${BINDIR}" 2>&- || true
else
# install tclsh symbolic link
@ln -fs ${TCLSH} "${INSTALL_ROOT}${BINDIR}/tclsh"
endif
endif
ifeq (${BUILD_STYLE}_${EMBEDDED_BUILD},Development_)
# keep copy of debug library around, so that
# Deployment build can be installed on top
# of Development build without overwriting
# the debug library
@if [ -d "${INSTALL_ROOT}${LIBDIR}/${PRODUCT_NAME}.framework/Versions/${VERSION}" ]; then \
cd "${INSTALL_ROOT}${LIBDIR}/${PRODUCT_NAME}.framework/Versions/${VERSION}"; \
ln -f "${PRODUCT_NAME}" "${PRODUCT_NAME}_debug"; \
fi
endif
clean-${PROJECT}: %-${PROJECT}:
${DO_MAKE}
rm -rf "${SYMROOT}"/{${PRODUCT_NAME}.framework,${TCLSH},tcltest}
rm -f "${OBJ_DIR}"{"${LIBDIR}","${BINDIR}"} && \
rmdir -p "${OBJ_DIR}"$(dir $(subst ${space},\ ,${LIBDIR})) 2>&- || true && \
rmdir -p "${OBJ_DIR}"$(dir $(subst ${space},\ ,${BINDIR})) 2>&- || true
distclean-${PROJECT}: %-${PROJECT}: clean-${PROJECT}
${DO_MAKE}
rm -rf "${OBJ_DIR}"
test-${PROJECT}: %-${PROJECT}: build-${PROJECT}
${DO_MAKE}
#-------------------------------------------------------------------------------------------------------
.PHONY: ${meta} ${targets} ${PROJECT} build-${PROJECT} install-${PROJECT} \
clean-${PROJECT} distclean-${PROJECT}
.NOTPARALLEL:
#-------------------------------------------------------------------------------------------------------

133
vendor/tcl/macosx/README vendored Normal file
View file

@ -0,0 +1,133 @@
Tcl macOS README
-------------------
This is the README file for the macOS/Darwin version of Tcl.
1. Where to go for support
--------------------------
- The tcl-mac mailing list on sourceforge is the best place to ask questions
specific to Tcl & Tk on Mac OS X:
http://lists.sourceforge.net/lists/listinfo/tcl-mac
(this page also has a link to searchable archives of the list, please check them
before asking on the list, many questions have already been answered).
- For general Tcl/Tk questions, the newsgroup comp.lang.tcl is your best bet:
http://groups.google.com/group/comp.lang.tcl/
- The Tcl'ers Wiki also has many pages dealing with Tcl & Tk on Mac OS X, see
http://wiki.tcl.tk/_/ref?N=3753
http://wiki.tcl.tk/_/ref?N=8361
- Please report bugs with Tcl on Mac OS X to the tracker:
https://core.tcl-lang.org/tcl/reportlist
2. Using Tcl on Mac OS X
------------------------
- At a minimum, Mac OS X 10.3 is required to run Tcl.
- Unless weak-linking is used, Tcl built on Mac OS X 10.x will not run on 10.y
with y < x; on the other hand Tcl built on 10.y will always run on 10.x with
y <= x (but without any of the fixes and optimizations that would be available
in a binary built on 10.x).
Weak-linking is available on OS X 10.2 or later, it additionally allows Tcl
built on 10.x to run on any 10.y with x > y >= z (for a chosen z >= 2).
- Tcl extensions can be installed in any of:
$HOME/Library/Tcl /Library/Tcl
$HOME/Library/Frameworks /Library/Frameworks
(searched in that order).
Given a potential package directory $pkg, Tcl on OSX checks for the file
$pkg/Resources/Scripts/pkgIndex.tcl as well as the usual $pkg/pkgIndex.tcl.
This allows building extensions as frameworks with all script files contained in
the Resources/Scripts directory of the framework.
- [load]able binary extensions can linked as either ordinary shared libraries
(.dylib) or as MachO bundles (since 8.4.10/8.5a3); bundles have the advantage
that they are [load]ed more efficiently from a tcl VFS (no temporary copy to the
native filesystem required), and prior to Mac OS X 10.5, only bundles can be
[unload]ed.
- The 'deploy' target of macosx/GNUmakefile installs the html manpages into the
standard documentation location in the Tcl framework:
Tcl.framework/Resources/Documentation/Reference/Tcl
No nroff manpages are installed by default by the GNUmakefile.
- The Tcl framework can be installed in any of the system's standard
framework directories:
$HOME/Library/Frameworks /Library/Frameworks
3. Building Tcl on Mac OS X
---------------------------
- Tcl supports macOS 10.13 and newer.
While Tcl may build on earlier versions of the OS, it is not tested on versions
older than 10.13. You will need to install an Apple clang toolchain either by
downloading the Xcode app from Apple's App Store, or by installing the Command
Line Tools. The Command Line Tools can be installed by running the command:
xcode-select --install
in the Terminal.
- Tcl is most easily built as a macOS framework via the GNUmakefile in tcl/macosx
(see below for details), but can also be built with the standard unix configure
and make buildsystem in tcl/unix as on any other unix platform (indeed, the
GNUmakefile is just a wrapper around the unix buildsystem).
The Mac OS X specific configure flags are --enable-framework and
--disable-corefoundation (which disables CF and notably reverts to the standard
select based notifier).
- To build universal binaries for macOS 10.13 and newer set CFLAGS as follows:
export CFLAGS="-arch x86_64 -arch arm64 -mmacosx-version-min=10.13"
(This will cause clang to set macOS 11 as the target OS for the arm64 architecture
since Apple Silicon was not supported until macOS 11.)
Universal builds of Tcl TEA extensions are also possible with CFLAGS set as
above, they will be [load]able by universal as well as thin binaries of Tcl.
Detailed Instructions for building with macosx/GNUmakefile
----------------------------------------------------------
- Unpack the Tcl source release archive.
- The following instructions assume the Tcl source tree is named "tcl${ver}",
(where ${ver} is a shell variable containing the Tcl version number e.g. '9.0').
Setup this shell variable as follows:
ver="9.0"
- Setup environment variables as desired, for example:
CFLAGS="-arch x86_64 -arch arm64 -mmacosx-version-min=10.13"
export CFLAGS
- Change to the directory containing the Tcl source tree and build:
make -C tcl${ver}/macosx
- Install Tcl onto the root volume (admin password required):
sudo make -C tcl${ver}/macosx install
if you don't have an admin password, you can install into your home directory
instead by passing an INSTALL_ROOT argument to make:
make -C tcl${ver}/macosx install INSTALL_ROOT="${HOME}/"
- The default GNUmakefile targets will build _both_ debug and optimized versions
of the Tcl framework with the standard convention of naming the debug library
Tcl.framework/Tcl_debug.
This allows switching to the debug libraries at runtime by setting
export DYLD_IMAGE_SUFFIX=_debug
(c.f. man dyld for more details)
If you only want to build and install the debug or optimized build, use the
'develop' or 'deploy' target variants of the GNUmakefile, respectively.
For example, to build and install only the optimized versions:
make -C tcl${ver}/macosx deploy
sudo make -C tcl${ver}/macosx install-deploy
- To build a Tcl.framework for use as a subframework in another framework, use the
install-embedded target and set SUBFRAMEWORK=1. Set the DYLIB_INSTALL_DIR
variable to the path which should be the install_name path of the Tcl library, set
the DESTDIR variable to the pathname of a staging directory where the framework
will be written . For example, running this command in the Tcl source directory:
make -C macosx install-embedded SUBFRAMEWORK=1 DESTDIR=/tmp/tcl \
DYLIB_INSTALL_DIR=/Library/Frameworks/Some.framework/Versions/X.Y/Frameworks/Tcl.framework
will produce a Tcl.framework intended for installing as a subframework of
Some.framework. The framework will be found in /tmp/tcl/Frameworks/

36
vendor/tcl/macosx/Tcl-Info.plist.in vendored Normal file
View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
Copyright (c) 2005-2007 Daniel A. Steffen <das@users.sourceforge.net>
See the file "license.terms" for information on usage and redistribution of
this file, and for a DISCLAIMER OF ALL WARRANTIES.
-->
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>@TCL_LIB_FILE@</string>
<key>CFBundleGetInfoString</key>
<string>Tcl @TCL_VERSION@@TCL_PATCH_LEVEL@,
Copyright © 1987-@TCL_YEAR@ Tcl Core Team,
Copyright © 2001-@TCL_YEAR@ Daniel A. Steffen,
Copyright © 2001-2009 Apple Inc.,
Copyright © 2001-2002 Jim Ingham &amp; Ian Reid</string>
<key>CFBundleIdentifier</key>
<string>com.tcltk.tcllibrary</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Tcl @TCL_VERSION@</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>@TCL_VERSION@@TCL_PATCH_LEVEL@</string>
<key>CFBundleSignature</key>
<string>Tcl </string>
<key>CFBundleVersion</key>
<string>@TCL_VERSION@@TCL_PATCH_LEVEL@</string>
</dict>
</plist>

36
vendor/tcl/macosx/Tclsh-Info.plist.in vendored Normal file
View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
Copyright (c) 2005-2007 Daniel A. Steffen <das@users.sourceforge.net>
See the file "license.terms" for information on usage and redistribution of
this file, and for a DISCLAIMER OF ALL WARRANTIES.
-->
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>tclsh@TCL_VERSION@</string>
<key>CFBundleGetInfoString</key>
<string>Tcl Shell @TCL_VERSION@@TCL_PATCH_LEVEL@,
Copyright © 1987-@TCL_YEAR@ Tcl Core Team,
Copyright © 2001-@TCL_YEAR@ Daniel A. Steffen,
Copyright © 2001-2009 Apple Inc.,
Copyright © 2001-2002 Jim Ingham &amp; Ian Reid</string>
<key>CFBundleIdentifier</key>
<string>com.tcltk.tclsh</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>tclsh</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>@TCL_VERSION@@TCL_PATCH_LEVEL@</string>
<key>CFBundleSignature</key>
<string>TclS</string>
<key>CFBundleVersion</key>
<string>@TCL_VERSION@@TCL_PATCH_LEVEL@</string>
</dict>
</plist>

12848
vendor/tcl/macosx/configure vendored Executable file

File diff suppressed because it is too large Load diff

11
vendor/tcl/macosx/configure.ac vendored Normal file
View file

@ -0,0 +1,11 @@
#! /bin/bash -norc
dnl This file is an input file used by the GNU "autoconf" program to
dnl generate the file "configure", which is run during Tcl installation
dnl to configure the system for the local environment.
dnl Ensure that the config (auto)headers support is used, then just
dnl include the configure sources from ../unix:
m4_include(../unix/aclocal.m4)
m4_define(SC_USE_CONFIG_HEADERS)
m4_include(../unix/configure.ac)

40
vendor/tcl/macosx/license.terms vendored Normal file
View file

@ -0,0 +1,40 @@
This software is copyrighted by the Regents of the University of
California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
Corporation and other parties. The following terms apply to all files
associated with the software unless explicitly disclaimed in
individual files.
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
they apply.
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
GOVERNMENT USE: If you are acquiring this software on behalf of the
U.S. government, the Government shall have only "Restricted Rights"
in the software and related documentation as defined in the Federal
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
are acquiring the software on behalf of the Department of Defense, the
software shall be classified as "Commercial Computer Software" and the
Government shall have only "Restricted Rights" as defined in Clause
252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the
authors grant the U.S. Government and others acting in its behalf
permission to use and distribute the software in accordance with the
terms specified in this license.

214
vendor/tcl/macosx/tclMacOSXBundle.c vendored Normal file
View file

@ -0,0 +1,214 @@
/*
* tclMacOSXBundle.c --
*
* This file implements functions that inspect CFBundle structures on
* MacOS X.
*
* Copyright © 2001-2009 Apple Inc.
* Copyright © 2003-2009 Daniel A. Steffen <das@users.sourceforge.net>
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclPort.h"
#include "tclInt.h"
#ifdef HAVE_COREFOUNDATION
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#ifdef TCL_DEBUG_LOAD
#define TclLoadDbgMsg(m, ...) \
do { \
fprintf(stderr, "%s:%d: %s(): " m ".\n", \
strrchr(__FILE__, '/')+1, __LINE__, __func__, \
##__VA_ARGS__); \
} while (0)
#else
#define TclLoadDbgMsg(m, ...)
#endif /* TCL_DEBUG_LOAD */
/*
* Forward declaration of functions defined in this file:
*/
static short OpenResourceMap(CFBundleRef bundleRef);
#endif /* HAVE_COREFOUNDATION */
/*
*----------------------------------------------------------------------
*
* OpenResourceMap --
*
* Wrapper that dynamically acquires the address for the function
* CFBundleOpenBundleResourceMap before calling it, since it is only
* present in full CoreFoundation on Mac OS X and not in CFLite on pure
* Darwin. Factored out because it is moderately ugly code.
*
*----------------------------------------------------------------------
*/
#ifdef HAVE_COREFOUNDATION
static short
OpenResourceMap(
CFBundleRef bundleRef)
{
static int initialized = FALSE;
static short (*openresourcemap)(CFBundleRef) = NULL;
if (!initialized) {
{
openresourcemap = (short (*)(CFBundleRef))dlsym(RTLD_NEXT,
"CFBundleOpenBundleResourceMap");
#ifdef TCL_DEBUG_LOAD
if (!openresourcemap) {
const char *errMsg = dlerror();
TclLoadDbgMsg("dlsym() failed: %s", errMsg);
}
#endif /* TCL_DEBUG_LOAD */
}
initialized = TRUE;
}
if (openresourcemap) {
return openresourcemap(bundleRef);
}
return -1;
}
#endif /* HAVE_COREFOUNDATION */
/*
*----------------------------------------------------------------------
*
* Tcl_MacOSXOpenVersionedBundleResources --
*
* Given the bundle and version name for a shared library (version name
* can be NULL to indicate latest version), this routine sets libraryPath
* to the Resources/Scripts directory in the framework package. If
* hasResourceFile is true, it will also open the main resource file for
* the bundle.
*
* Results:
* TCL_OK if the bundle could be opened, and the Scripts folder found.
* TCL_ERROR otherwise.
*
* Side effects:
* libraryVariableName may be set, and the resource file opened.
*
*----------------------------------------------------------------------
*/
int
Tcl_MacOSXOpenVersionedBundleResources(
TCL_UNUSED(Tcl_Interp *),
const char *bundleName,
const char *bundleVersion,
int hasResourceFile,
Tcl_Size maxPathLen,
char *libraryPath)
{
#ifdef HAVE_COREFOUNDATION
CFBundleRef bundleRef, versionedBundleRef = NULL;
CFStringRef bundleNameRef;
CFURLRef libURL;
libraryPath[0] = '\0';
bundleNameRef = CFStringCreateWithCString(NULL, bundleName,
kCFStringEncodingUTF8);
bundleRef = CFBundleGetBundleWithIdentifier(bundleNameRef);
CFRelease(bundleNameRef);
if (bundleVersion && bundleRef) {
/*
* Create bundle from bundleVersion subdirectory of 'Versions'.
*/
CFURLRef bundleURL = CFBundleCopyBundleURL(bundleRef);
if (bundleURL) {
CFStringRef bundleVersionRef = CFStringCreateWithCString(NULL,
bundleVersion, kCFStringEncodingUTF8);
if (bundleVersionRef) {
CFComparisonResult versionComparison = kCFCompareLessThan;
CFStringRef bundleTailRef = CFURLCopyLastPathComponent(
bundleURL);
if (bundleTailRef) {
versionComparison = CFStringCompare(bundleTailRef,
bundleVersionRef, 0);
CFRelease(bundleTailRef);
}
if (versionComparison != kCFCompareEqualTo) {
CFURLRef versURL = CFURLCreateCopyAppendingPathComponent(
NULL, bundleURL, CFSTR("Versions"), TRUE);
if (versURL) {
CFURLRef versionedBundleURL =
CFURLCreateCopyAppendingPathComponent(
NULL, versURL, bundleVersionRef, TRUE);
if (versionedBundleURL) {
versionedBundleRef = CFBundleCreate(NULL,
versionedBundleURL);
if (versionedBundleRef) {
bundleRef = versionedBundleRef;
}
CFRelease(versionedBundleURL);
}
CFRelease(versURL);
}
}
CFRelease(bundleVersionRef);
}
CFRelease(bundleURL);
}
}
if (bundleRef) {
if (hasResourceFile) {
(void) OpenResourceMap(bundleRef);
}
libURL = CFBundleCopyResourceURL(bundleRef, CFSTR("Scripts"),
NULL, NULL);
if (libURL) {
/*
* FIXME: This is a quick fix, it is probably not right for
* internationalization.
*/
CFURLGetFileSystemRepresentation(libURL, TRUE,
(unsigned char *) libraryPath, maxPathLen);
CFRelease(libURL);
}
if (versionedBundleRef) {
{
CFRelease(versionedBundleRef);
}
}
}
if (libraryPath[0]) {
return TCL_OK;
}
#endif /* HAVE_COREFOUNDATION */
return TCL_ERROR;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

720
vendor/tcl/macosx/tclMacOSXFCmd.c vendored Normal file
View file

@ -0,0 +1,720 @@
/*
* tclMacOSXFCmd.c
*
* This file implements the MacOSX specific portion of file manipulation
* subcommands of the "file" command.
*
* Copyright © 2003-2007 Daniel A. Steffen <das@users.sourceforge.net>
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
#ifdef HAVE_GETATTRLIST
#include <sys/attr.h>
#include <sys/paths.h>
#include <libkern/OSByteOrder.h>
#endif
/* Darwin 8 copyfile API. */
#ifdef HAVE_COPYFILE
#ifdef HAVE_COPYFILE_H
#include <copyfile.h>
#else /* HAVE_COPYFILE_H */
int copyfile(const char *from, const char *to,
void *state, uint32_t flags);
#define COPYFILE_ACL (1<<0)
#define COPYFILE_XATTR (1<<2)
#define COPYFILE_NOFOLLOW_SRC (1<<18)
#endif /* HAVE_COPYFILE_H */
#endif /* HAVE_COPYFILE */
#ifdef WEAK_IMPORT_COPYFILE
#define MayUseCopyFile() (copyfile != NULL)
#elif defined(HAVE_COPYFILE)
#define MayUseCopyFile() (1)
#else
#define MayUseCopyFile() (0)
#endif
#include <libkern/OSByteOrder.h>
/*
* Constants for file attributes subcommand. Need to be kept in sync with
* tclUnixFCmd.c !
*/
enum {
UNIX_GROUP_ATTRIBUTE,
UNIX_OWNER_ATTRIBUTE,
UNIX_PERMISSIONS_ATTRIBUTE,
#ifdef HAVE_CHFLAGS
UNIX_READONLY_ATTRIBUTE,
#endif
#ifdef MAC_OSX_TCL
MACOSX_CREATOR_ATTRIBUTE,
MACOSX_TYPE_ATTRIBUTE,
MACOSX_HIDDEN_ATTRIBUTE,
MACOSX_RSRCLENGTH_ATTRIBUTE,
#endif
};
typedef u_int32_t OSType;
static int GetOSTypeFromObj(Tcl_Interp *interp,
Tcl_Obj *objPtr, OSType *osTypePtr);
static Tcl_Obj * NewOSTypeObj(const OSType newOSType);
static int SetOSTypeFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr);
static void UpdateStringOfOSType(Tcl_Obj *objPtr);
static const Tcl_ObjType tclOSTypeType = {
"osType", /* name */
NULL, /* freeIntRepProc */
NULL, /* dupIntRepProc */
UpdateStringOfOSType, /* updateStringProc */
SetOSTypeFromAny, /* setFromAnyProc */
TCL_OBJTYPE_V0
};
enum {
kIsInvisible = 0x4000,
};
#define kFinfoIsInvisible (OSSwapHostToBigConstInt16(kIsInvisible))
typedef struct finderinfo {
u_int32_t type;
u_int32_t creator;
u_int16_t fdFlags;
u_int32_t location;
u_int16_t reserved;
u_int32_t extendedFileInfo[4];
} __attribute__ ((__packed__)) finderinfo;
typedef struct {
u_int64_t reserved1; /* Make sure data is 8-byte aligned */
u_int32_t reserved2; /* See [992f94d847] */
u_int32_t info_length;
u_int32_t data[8];
} fileinfobuf;
/*
*----------------------------------------------------------------------
*
* TclMacOSXGetFileAttribute
*
* Gets a MacOSX attribute of a file. Which attribute is controlled by
* objIndex. The object will have ref count 0.
*
* Results:
* Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
* is no error.
*
* Side effects:
* A new object is allocated.
*
*----------------------------------------------------------------------
*/
int
TclMacOSXGetFileAttribute(
Tcl_Interp *interp, /* The interp we are using for errors. */
int objIndex, /* The index of the attribute. */
Tcl_Obj *fileName, /* The name of the file (UTF-8). */
Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */
{
#ifdef HAVE_GETATTRLIST
int result;
Tcl_StatBuf statBuf;
struct attrlist alist;
fileinfobuf finfo;
finderinfo *finder = (finderinfo *) &finfo.data;
off_t *rsrcForkSize = (off_t *) &finfo.data;
const char *native;
result = TclpObjStat(fileName, &statBuf);
if (result != 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"could not read \"%s\": %s",
TclGetString(fileName), Tcl_PosixError(interp)));
return TCL_ERROR;
}
if (S_ISDIR(statBuf.st_mode) && objIndex != MACOSX_HIDDEN_ATTRIBUTE) {
/*
* Directories only support attribute "-hidden".
*/
errno = EISDIR;
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"invalid attribute: %s", Tcl_PosixError(interp)));
return TCL_ERROR;
}
bzero(&alist, sizeof(struct attrlist));
alist.bitmapcount = ATTR_BIT_MAP_COUNT;
if (objIndex == MACOSX_RSRCLENGTH_ATTRIBUTE) {
alist.fileattr = ATTR_FILE_RSRCLENGTH;
} else {
alist.commonattr = ATTR_CMN_FNDRINFO;
}
native = (const char *)Tcl_FSGetNativePath(fileName);
result = getattrlist(native, &alist, &finfo.info_length,
sizeof(fileinfobuf) - offsetof(fileinfobuf, info_length), 0);
if (result != 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"could not read attributes of \"%s\": %s",
TclGetString(fileName), Tcl_PosixError(interp)));
return TCL_ERROR;
}
switch (objIndex) {
case MACOSX_CREATOR_ATTRIBUTE:
*attributePtrPtr = NewOSTypeObj(
OSSwapBigToHostInt32(finder->creator));
break;
case MACOSX_TYPE_ATTRIBUTE:
*attributePtrPtr = NewOSTypeObj(
OSSwapBigToHostInt32(finder->type));
break;
case MACOSX_HIDDEN_ATTRIBUTE:
TclNewIntObj(*attributePtrPtr,
(finder->fdFlags & kFinfoIsInvisible) != 0);
break;
case MACOSX_RSRCLENGTH_ATTRIBUTE:
TclNewIntObj(*attributePtrPtr, *rsrcForkSize);
break;
}
return TCL_OK;
#else
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"Mac OS X file attributes not supported", TCL_INDEX_NONE));
Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", (char *)NULL);
return TCL_ERROR;
#endif /* HAVE_GETATTRLIST */
}
/*
*---------------------------------------------------------------------------
*
* TclMacOSXSetFileAttribute --
*
* Sets a MacOSX attribute of a file. Which attribute is controlled by
* objIndex.
*
* Results:
* Standard TCL result.
*
* Side effects:
* As above.
*
*---------------------------------------------------------------------------
*/
int
TclMacOSXSetFileAttribute(
Tcl_Interp *interp, /* The interp for error reporting. */
int objIndex, /* The index of the attribute. */
Tcl_Obj *fileName, /* The name of the file (UTF-8). */
Tcl_Obj *attributePtr) /* New owner for file. */
{
#ifdef HAVE_GETATTRLIST
int result;
Tcl_StatBuf statBuf;
struct attrlist alist;
fileinfobuf finfo;
finderinfo *finder = (finderinfo *) &finfo.data;
off_t *rsrcForkSize = (off_t *) &finfo.data;
const char *native;
result = TclpObjStat(fileName, &statBuf);
if (result != 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"could not read \"%s\": %s",
TclGetString(fileName), Tcl_PosixError(interp)));
return TCL_ERROR;
}
if (S_ISDIR(statBuf.st_mode) && objIndex != MACOSX_HIDDEN_ATTRIBUTE) {
/*
* Directories only support attribute "-hidden".
*/
errno = EISDIR;
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"invalid attribute: %s", Tcl_PosixError(interp)));
return TCL_ERROR;
}
bzero(&alist, sizeof(struct attrlist));
alist.bitmapcount = ATTR_BIT_MAP_COUNT;
if (objIndex == MACOSX_RSRCLENGTH_ATTRIBUTE) {
alist.fileattr = ATTR_FILE_RSRCLENGTH;
} else {
alist.commonattr = ATTR_CMN_FNDRINFO;
}
native = (const char *)Tcl_FSGetNativePath(fileName);
result = getattrlist(native, &alist, &finfo.info_length,
sizeof(fileinfobuf) - offsetof(fileinfobuf, info_length), 0);
if (result != 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"could not read attributes of \"%s\": %s",
TclGetString(fileName), Tcl_PosixError(interp)));
return TCL_ERROR;
}
if (objIndex != MACOSX_RSRCLENGTH_ATTRIBUTE) {
OSType t;
int h;
switch (objIndex) {
case MACOSX_CREATOR_ATTRIBUTE:
if (GetOSTypeFromObj(interp, attributePtr, &t) != TCL_OK) {
return TCL_ERROR;
}
finder->creator = OSSwapHostToBigInt32(t);
break;
case MACOSX_TYPE_ATTRIBUTE:
if (GetOSTypeFromObj(interp, attributePtr, &t) != TCL_OK) {
return TCL_ERROR;
}
finder->type = OSSwapHostToBigInt32(t);
break;
case MACOSX_HIDDEN_ATTRIBUTE:
if (Tcl_GetBooleanFromObj(interp, attributePtr, &h) != TCL_OK) {
return TCL_ERROR;
}
if (h) {
finder->fdFlags |= kFinfoIsInvisible;
} else {
finder->fdFlags &= ~kFinfoIsInvisible;
}
break;
}
result = setattrlist(native, &alist,
&finfo.data, sizeof(finfo.data), 0);
if (result != 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"could not set attributes of \"%s\": %s",
TclGetString(fileName), Tcl_PosixError(interp)));
return TCL_ERROR;
}
} else {
Tcl_WideInt newRsrcForkSize;
if (TclGetWideIntFromObj(interp, attributePtr,
&newRsrcForkSize) != TCL_OK) {
return TCL_ERROR;
}
if (newRsrcForkSize != *rsrcForkSize) {
Tcl_DString ds;
/*
* Only setting rsrclength to 0 to strip a file's resource fork is
* supported.
*/
if (newRsrcForkSize != 0) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"setting nonzero rsrclength not supported", TCL_INDEX_NONE));
Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", (char *)NULL);
return TCL_ERROR;
}
/*
* Construct path to resource fork.
*/
Tcl_DStringInit(&ds);
Tcl_DStringAppend(&ds, native, TCL_INDEX_NONE);
Tcl_DStringAppend(&ds, _PATH_RSRCFORKSPEC, TCL_INDEX_NONE);
result = truncate(Tcl_DStringValue(&ds), 0);
if (result != 0) {
/*
* truncate() on a valid resource fork path may fail with a
* permission error in some OS releases, try truncating with
* open() instead:
*/
int fd = open(Tcl_DStringValue(&ds), O_WRONLY | O_TRUNC);
if (fd > 0) {
result = close(fd);
}
}
Tcl_DStringFree(&ds);
if (result != 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"could not truncate resource fork of \"%s\": %s",
TclGetString(fileName), Tcl_PosixError(interp)));
return TCL_ERROR;
}
}
}
return TCL_OK;
#else
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"Mac OS X file attributes not supported", TCL_INDEX_NONE));
Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", (char *)NULL);
return TCL_ERROR;
#endif
}
/*
*---------------------------------------------------------------------------
*
* TclMacOSXCopyFileAttributes --
*
* Copy the MacOSX attributes and resource fork (if present) from one
* file to another.
*
* Results:
* Standard Tcl result.
*
* Side effects:
* MacOSX attributes and resource fork are updated in the new file to
* reflect the old file.
*
*---------------------------------------------------------------------------
*/
int
TclMacOSXCopyFileAttributes(
const char *src, /* Path name of source file (native). */
const char *dst, /* Path name of target file (native). */
const Tcl_StatBuf *statBufPtr)
/* Stat info for source file */
{
if (MayUseCopyFile()) {
#ifdef HAVE_COPYFILE
if (0 == copyfile(src, dst, NULL, (S_ISLNK(statBufPtr->st_mode)
? COPYFILE_XATTR | COPYFILE_NOFOLLOW_SRC
: COPYFILE_XATTR | COPYFILE_ACL))) {
return TCL_OK;
}
#endif /* HAVE_COPYFILE */
} else {
#if (!defined(HAVE_COPYFILE) || defined(WEAK_IMPORT_COPYFILE)) && defined(HAVE_GETATTRLIST)
struct attrlist alist;
fileinfobuf finfo;
off_t *rsrcForkSize = (off_t *) &finfo.data;
Tcl_DString srcBuf, dstBuf;
int result;
bzero(&alist, sizeof(struct attrlist));
alist.bitmapcount = ATTR_BIT_MAP_COUNT;
alist.commonattr = ATTR_CMN_FNDRINFO;
if (getattrlist(src, &alist, &finfo.info_length,
sizeof(fileinfobuf) - offsetof(fileinfobuf, info_length), 0)) {
return TCL_ERROR;
}
if (setattrlist(dst, &alist, &finfo.data, sizeof(finfo.data), 0)) {
return TCL_ERROR;
}
/*
* If we're a directory, we're done as they never have resource forks.
*/
if (S_ISDIR(statBufPtr->st_mode)) {
return TCL_OK;
}
/*
* We only copy a non-empty resource fork, so determine if that's the
* case first.
*/
alist.commonattr = 0;
alist.fileattr = ATTR_FILE_RSRCLENGTH;
if (getattrlist(src, &alist, &finfo.info_length,
sizeof(fileinfobuf) - offsetof(fileinfobuf, info_length), 0)) {
return TCL_ERROR;
} else if (*rsrcForkSize == 0) {
return TCL_OK;
}
/*
* Construct paths to resource forks.
*/
Tcl_DStringInit(&srcBuf);
Tcl_DStringAppend(&srcBuf, src, TCL_INDEX_NONE);
Tcl_DStringAppend(&srcBuf, _PATH_RSRCFORKSPEC, TCL_INDEX_NONE);
Tcl_DStringInit(&dstBuf);
Tcl_DStringAppend(&dstBuf, dst, TCL_INDEX_NONE);
Tcl_DStringAppend(&dstBuf, _PATH_RSRCFORKSPEC, TCL_INDEX_NONE);
/*
* Do the copy.
*/
result = TclUnixCopyFile(Tcl_DStringValue(&srcBuf),
Tcl_DStringValue(&dstBuf), statBufPtr, 1);
Tcl_DStringFree(&srcBuf);
Tcl_DStringFree(&dstBuf);
if (result == 0) {
return TCL_OK;
}
#endif /* (!HAVE_COPYFILE || WEAK_IMPORT_COPYFILE) && HAVE_GETATTRLIST */
}
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* TclMacOSXMatchType --
*
* This routine is used by the globbing code to check if a file matches a
* given mac type and/or creator code.
*
* Results:
* The return value is 1, 0 or -1 indicating whether the file matches the
* given criteria, does not match them, or an error occurred (in which
* case an error is left in interp).
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TclMacOSXMatchType(
Tcl_Interp *interp, /* Interpreter to receive errors. */
const char *pathName, /* Native path to check. */
const char *fileName, /* Native filename to check. */
Tcl_StatBuf *statBufPtr, /* Stat info for file to check */
Tcl_GlobTypeData *types) /* Type description to match against. */
{
#ifdef HAVE_GETATTRLIST
struct attrlist alist;
fileinfobuf finfo;
finderinfo *finder = (finderinfo *) &finfo.data;
OSType osType;
bzero(&alist, sizeof(struct attrlist));
alist.bitmapcount = ATTR_BIT_MAP_COUNT;
alist.commonattr = ATTR_CMN_FNDRINFO;
if (getattrlist(pathName, &alist, &finfo.info_length,
sizeof(fileinfobuf) - offsetof(fileinfobuf, info_length), 0)) {
return 0;
}
if ((types->perm & TCL_GLOB_PERM_HIDDEN) &&
!((finder->fdFlags & kFinfoIsInvisible) || (*fileName == '.'))) {
return 0;
}
if (S_ISDIR(statBufPtr->st_mode)
&& (types->macType || types->macCreator)) {
/*
* Directories don't support types or creators.
*/
return 0;
}
if (types->macType) {
if (GetOSTypeFromObj(interp, types->macType, &osType) != TCL_OK) {
return -1;
}
if (osType != OSSwapBigToHostInt32(finder->type)) {
return 0;
}
}
if (types->macCreator) {
if (GetOSTypeFromObj(interp, types->macCreator, &osType) != TCL_OK) {
return -1;
}
if (osType != OSSwapBigToHostInt32(finder->creator)) {
return 0;
}
}
#endif
return 1;
}
/*
*----------------------------------------------------------------------
*
* GetOSTypeFromObj --
*
* Attempt to return an OSType from the Tcl object "objPtr".
*
* Results:
* Standard TCL result. If an error occurs during conversion, an error
* message is left in interp->objResult.
*
* Side effects:
* The string representation of objPtr will be updated if necessary.
*
*----------------------------------------------------------------------
*/
static int
GetOSTypeFromObj(
Tcl_Interp *interp, /* Used for error reporting if not NULL. */
Tcl_Obj *objPtr, /* The object from which to get an OSType. */
OSType *osTypePtr) /* Place to store resulting OSType. */
{
int result = TCL_OK;
if (!TclHasInternalRep(objPtr, &tclOSTypeType)) {
result = SetOSTypeFromAny(interp, objPtr);
}
*osTypePtr = (OSType) objPtr->internalRep.wideValue;
return result;
}
/*
*----------------------------------------------------------------------
*
* NewOSTypeObj --
*
* Create a new OSType object.
*
* Results:
* The newly created OSType object is returned, it has ref count 0.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static Tcl_Obj *
NewOSTypeObj(
const OSType osType) /* OSType used to initialize the new
* object. */
{
Tcl_Obj *objPtr;
TclNewObj(objPtr);
TclInvalidateStringRep(objPtr);
objPtr->internalRep.wideValue = (Tcl_WideInt) osType;
objPtr->typePtr = &tclOSTypeType;
return objPtr;
}
/*
*----------------------------------------------------------------------
*
* SetOSTypeFromAny --
*
* Attempts to force the internal representation for a Tcl object to
* tclOSTypeType, specifically.
*
* Results:
* The return value is a standard object Tcl result. If an error occurs
* during conversion, an error message is left in the interpreter's
* result unless "interp" is NULL.
*
*----------------------------------------------------------------------
*/
static int
SetOSTypeFromAny(
Tcl_Interp *interp, /* Tcl interpreter */
Tcl_Obj *objPtr) /* Pointer to the object to convert */
{
const char *string;
int result = TCL_OK;
Tcl_DString ds;
Tcl_Encoding encoding = Tcl_GetEncoding(NULL, "macRoman");
Tcl_Size length;
string = TclGetStringFromObj(objPtr, &length);
Tcl_UtfToExternalDStringEx(NULL, encoding, string, length, TCL_ENCODING_PROFILE_TCL8, &ds, NULL);
if (Tcl_DStringLength(&ds) > 4) {
if (interp) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"expected Macintosh OS type but got \"%s\": ", string));
Tcl_SetErrorCode(interp, "TCL", "VALUE", "MAC_OSTYPE", (char *)NULL);
}
result = TCL_ERROR;
} else {
OSType osType;
char bytes[4] = {'\0','\0','\0','\0'};
memcpy(bytes, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
osType = (OSType) bytes[0] << 24 |
(OSType) bytes[1] << 16 |
(OSType) bytes[2] << 8 |
(OSType) bytes[3];
TclFreeInternalRep(objPtr);
objPtr->internalRep.wideValue = (Tcl_WideInt) osType;
objPtr->typePtr = &tclOSTypeType;
}
Tcl_DStringFree(&ds);
Tcl_FreeEncoding(encoding);
return result;
}
/*
*----------------------------------------------------------------------
*
* UpdateStringOfOSType --
*
* Update the string representation for an OSType object. Note: This
* function does not free an existing old string rep so storage will be
* lost if this has not already been done.
*
* Results:
* None.
*
* Side effects:
* The object's string is set to a valid string that results from the
* OSType-to-string conversion.
*
*----------------------------------------------------------------------
*/
static void
UpdateStringOfOSType(
Tcl_Obj *objPtr) /* OSType object whose string rep to
* update. */
{
const size_t size = TCL_UTF_MAX * 4;
char *dst = Tcl_InitStringRep(objPtr, NULL, size);
OSType osType = (OSType) objPtr->internalRep.wideValue;
int written = 0;
Tcl_Encoding encoding;
char src[5];
TclOOM(dst, size+1);
src[0] = (char) (osType >> 24);
src[1] = (char) (osType >> 16);
src[2] = (char) (osType >> 8);
src[3] = (char) (osType);
src[4] = '\0';
encoding = Tcl_GetEncoding(NULL, "macRoman");
Tcl_ExternalToUtf(NULL, encoding, src, TCL_INDEX_NONE, /* flags */ 0,
/* statePtr */ NULL, dst, size, /* srcReadPtr */ NULL,
/* dstWrotePtr */ &written, /* dstCharsPtr */ NULL);
Tcl_FreeEncoding(encoding);
(void)Tcl_InitStringRep(objPtr, NULL, written);
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

2089
vendor/tcl/macosx/tclMacOSXNotify.c vendored Normal file

File diff suppressed because it is too large Load diff

1231
vendor/tcl/win/Makefile.in vendored Normal file

File diff suppressed because it is too large Load diff

102
vendor/tcl/win/README vendored Normal file
View file

@ -0,0 +1,102 @@
Tcl 9.0 for Windows
1. Introduction
---------------
This is the directory where you configure and compile the Windows
version of Tcl. This directory also contains source files for Tcl
that are specific to Microsoft Windows.
The information in this file is maintained on the web at:
https://www.tcl-lang.org/doc/howto/compile.html#win
2. Compiling Tcl
----------------
In order to compile Tcl for Windows, you need the following:
Tcl 9.0 Source Distribution (plus any patches)
and
Visual Studio 2015 or newer
or
Linux + MinGW-w64 [https://www.mingw-w64.org/]
(win32 or win64)
or
Cygwin + MinGW-w64 [https://cygwin.com/install.html]
(win32 or win64)
or
Darwin + MinGW-w64 [https://www.mingw-w64.org/]
(win32 or win64)
or
Msys + MinGW-w64 [https://www.mingw-w64.org/]
(win32 or win64)
or
LLVM MinGW [https://github.com/mstorsjo/llvm-mingw/]
(win32 or win64, IX86, AMD64 or ARM64)
In practice, this release is built with Visual C++ 6.0 and the TEA
Makefile.
If you are building with Visual C++, in the "win" subdirectory of the
source release, you will find "makefile.vc". This is the makefile for the
Visual C++ compiler and uses the stock NMAKE tool. Detailed directions for
using it, are in the comments of "makefile.vc". A quick example would be:
C:\tcl_source\win\>nmake -f makefile.vc
There is also a Developer Studio workspace and project file, too, if you
would like to use them.
If you are building with Linux, Cygwin or Msys, you can use the configure
script that lives in the win subdirectory. The Linux/Cygwin/Msys based
configure/build process works just like the UNIX one, so you will want
to refer to ../unix/README for available configure options.
If you want 64-bit executables (x86_64), you need to configure using
the --enable-64bit (or --enable-64bit=arm64) option. Make sure that
the x86_64-w64-mingw32 (or aarch64-w64-mingw32) compiler is present.
For Cygwin the x86_64 compiler can be found in the
"mingw64-x86_64-gcc-core" package, which can be installed through
the normal Cygwin install process. If you only want 32-bit executables,
the "mingw64-i686-gcc-core" package is what you need. For Linux, Darwin
and Msys, you can download a suitable win32 or win64 compiler from
[https://sourceforge.net/projects/mingw-w64/files/]
Use the Makefile "install" target to install Tcl. It will install it
according to the prefix options you provided in the correct directory
structure.
Note that in order to run tclsh90.exe, you must ensure that tcl90.dll,
libtommath.dll and zlib1.dll are on your path, in the system
directory, or in the directory containing tclsh90.exe.
Note: Tcl no longer provides support for systems earlier than Windows 7.
You will also need the Windows Universal C runtime (UCRT):
[https://support.microsoft.com/en-us/topic/update-for-universal-c-runtime-in-windows-c0514201-7fe6-95a3-b0a5-287930f3560c]
3. Test suite
-------------
This distribution contains an extensive test suite for Tcl. Some of the
tests are timing dependent and will fail from time to time. If a test is
failing consistently, please send us a bug report with as much detail as
you can manage to our tracker:
https://core.tcl-lang.org/tcl/reportlist
In order to run the test suite, you build the "test" target using the
appropriate makefile for your compiler.

1
vendor/tcl/win/aclocal.m4 vendored Normal file
View file

@ -0,0 +1 @@
builtin(include,tcl.m4)

105
vendor/tcl/win/buildall.vc.bat vendored Executable file
View file

@ -0,0 +1,105 @@
@echo off
:: This is an example batchfile for building everything. Please
:: edit this (or make your own) for your needs and wants using
:: the instructions for calling makefile.vc found in makefile.vc
set SYMBOLS=
:OPTIONS
if "%1" == "/?" goto help
if /i "%1" == "/help" goto help
if %1.==symbols. goto SYMBOLS
if %1.==debug. goto SYMBOLS
goto OPTIONS_DONE
:SYMBOLS
set SYMBOLS=symbols
shift
goto OPTIONS
:OPTIONS_DONE
:: reset errorlevel
cd > nul
:: You might have installed your developer studio to add itself to the
:: path or have already run vcvars32.bat. Testing these envars proves
:: cl.exe and friends are in your path.
::
if defined VCINSTALLDIR (goto :startBuilding)
if defined MSDEVDIR (goto :startBuilding)
if defined MSVCDIR (goto :startBuilding)
if defined MSSDK (goto :startBuilding)
if defined WINDOWSSDKDIR (goto :startBuilding)
:: We need to run the development environment batch script that comes
:: with developer studio (v4,5,6,7,etc...) All have it. This path
:: might not be correct. You should call it yourself prior to running
:: this batchfile.
::
REM call "C:\Program Files\Microsoft Developer Studio\vc98\bin\vcvars32.bat"
set "VSCMD_START_DIR=%CD%"
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"
if errorlevel 1 (goto no_vcvars)
:startBuilding
echo.
echo Sit back and have a cup of coffee while this grinds through ;)
echo You asked for *everything*, remember?
echo.
title Building Tcl, please wait...
:: makefile.vc uses this for its default anyways, but show its use here
:: just to be explicit and convey understanding to the user. Setting
:: the INSTALLDIR envar prior to running this batchfile affects all builds.
::
if "%INSTALLDIR%" == "" set INSTALLDIR=C:\Program Files\Tcl
:: Build the normal stuff along with the help file.
::
set OPTS=none
if not %SYMBOLS%.==. set OPTS=symbols
nmake -nologo -f makefile.vc release htmlhelp OPTS=%OPTS% %1
if errorlevel 1 goto error
:: Build the static core and shell.
::
set OPTS=static
if not %SYMBOLS%.==. set OPTS=symbols,static
nmake -nologo -f makefile.vc shell OPTS=%OPTS% %1
if errorlevel 1 goto error
set OPTS=
set SYMBOLS=
goto end
:error
echo *** BOOM! ***
goto end
:no_vcvars
echo vcvars32.bat was not run prior to this batchfile, nor are the MS tools in your path.
goto out
:help
title buildall.vc.bat help message
echo usage:
echo %0 : builds Tcl for all build types (do this first)
echo %0 install : installs all the release builds (do this second)
echo %0 symbols : builds Tcl for all debugging build types
echo %0 symbols install : install all the debug builds.
echo.
goto out
:end
title Building Tcl, please wait... DONE!
echo DONE!
goto out
:out
pause
title Command Prompt

35
vendor/tcl/win/cat.c vendored Normal file
View file

@ -0,0 +1,35 @@
/*
* cat.c --
*
* Program used when testing tclWinPipe.c
*
* Copyright (c) 1996 by Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include <stdio.h>
#include <io.h>
#include <string.h>
#include <tchar.h>
int
_tmain(void)
{
char buf[1024];
int n;
const char *err;
while (1) {
n = _read(0, buf, sizeof(buf));
if (n <= 0) {
break;
}
_write(1, buf, n);
}
err = (sizeof(int) == 2) ? "stderr16" : "stderr32";
_write(2, err, (unsigned int)strlen(err));
return 0;
}

7227
vendor/tcl/win/configure vendored Executable file

File diff suppressed because it is too large Load diff

495
vendor/tcl/win/configure.ac vendored Normal file
View file

@ -0,0 +1,495 @@
#! /bin/bash -norc
# This file is an input file used by the GNU "autoconf" program to
# generate the file "configure", which is run during Tcl installation
# to configure the system for the local environment.
AC_INIT([tcl],[9.0])
AC_CONFIG_SRCDIR([../generic/tcl.h])
AC_PREREQ([2.69])
# The following define is needed when building with Cygwin since newer
# versions of autoconf incorrectly set SHELL to /bin/bash instead of
# /bin/sh. The bash shell seems to suffer from some strange failures.
SHELL=/bin/sh
TCL_VERSION=9.0
TCL_MAJOR_VERSION=9
TCL_MINOR_VERSION=0
TCL_PATCH_LEVEL=".4"
VER=$TCL_MAJOR_VERSION$TCL_MINOR_VERSION
TCL_DDE_VERSION=1.4
TCL_DDE_MAJOR_VERSION=1
TCL_DDE_MINOR_VERSION=4
DDEVER=$TCL_DDE_MAJOR_VERSION$TCL_DDE_MINOR_VERSION
TCL_REG_VERSION=1.3
TCL_REG_MAJOR_VERSION=1
TCL_REG_MINOR_VERSION=3
REGVER=$TCL_REG_MAJOR_VERSION$TCL_REG_MINOR_VERSION
PKG_CFG_ARGS=$@
#------------------------------------------------------------------------
# Empty slate for bundled packages, to avoid stale configuration
#------------------------------------------------------------------------
rm -Rf pkgs
#------------------------------------------------------------------------
# Handle the --prefix=... option
#------------------------------------------------------------------------
if test "${prefix}" = "NONE"; then
prefix=/usr/local
fi
if test "${exec_prefix}" = "NONE"; then
exec_prefix=$prefix
fi
# libdir must be a fully qualified path (not ${exec_prefix}/lib)
eval libdir="$libdir"
#------------------------------------------------------------------------
# Standard compiler checks
#------------------------------------------------------------------------
# If the user did not set CFLAGS, set it now to keep
# the AC_PROG_CC macro from adding "-g -O2".
if test "${CFLAGS+set}" != "set" ; then
CFLAGS=""
fi
AC_PROG_CC
AC_C_INLINE
AC_CHECK_TOOL(AR, ar)
AC_CHECK_TOOL(RANLIB, ranlib)
AC_CHECK_TOOL(RC, windres)
#--------------------------------------------------------------------
# Checks to see if the make program sets the $MAKE variable.
#--------------------------------------------------------------------
AC_PROG_MAKE_SET
#--------------------------------------------------------------------
# Determines the correct binary file extension (.o, .obj, .exe etc.)
#--------------------------------------------------------------------
AC_OBJEXT
AC_EXEEXT
#------------------------------------------------------------------------
# Embedded configuration information, encoding to use for the values, TIP #59
#------------------------------------------------------------------------
SC_TCL_CFG_ENCODING
#--------------------------------------------------------------------
# The statements below define a collection of symbols related to
# building libtcl as a shared library instead of a static library.
#--------------------------------------------------------------------
SC_ENABLE_SHARED
#--------------------------------------------------------------------
# The statements below define a collection of compile flags. This
# macro depends on the value of SHARED_BUILD, and should be called
# after SC_ENABLE_SHARED checks the configure switches.
#--------------------------------------------------------------------
SC_CONFIG_CFLAGS
# Cross-compiling
case ${host_alias} in
*mingw32*)
TCL_EXE="tclsh"
;;
*)
TCL_EXE="TCL_LIBRARY=\"\${LIBRARY_DIR}\"; export TCL_LIBRARY; ./\${TCLSH}"
;;
esac
#------------------------------------------------------------------------
# Add stuff for zlib/libtommath; note that this is mostly done in the
# makefile now as we just assume that the platform hasn't got usable
# z.lib/tommath.lib
#------------------------------------------------------------------------
AS_IF([test "${enable_shared+set}" = "set"], [
enableval="$enable_shared"
tcl_ok=$enableval
], [
tcl_ok=yes
])
zlib_lib_name=zdll.lib
tommath_lib_name=tommath.lib
AS_IF([test "$tcl_ok" = "yes"], [
AC_SUBST(ZLIB_DLL_FILE,[\${ZLIB_DLL_FILE}])
AC_SUBST(TOMMATH_DLL_FILE,[\${TOMMATH_DLL_FILE}])
AC_DEFINE(TCL_WITH_EXTERNAL_TOMMATH, 1, [Tcl with external libtommath])
AS_IF([test "$do64bit" != "no"], [
AC_DEFINE(MP_64BIT, 1, [Using libtommath.dll in 64-bit mode])
AS_IF([test "$do64bit" = "arm64" -o "$do64bit" = "aarch64"], [
AS_IF([test "$GCC" = "yes"],[
AC_SUBST(ZLIB_LIBS,[\${ZLIB_DIR_NATIVE}/win64-arm/libz.dll.a])
AC_SUBST(TOMMATH_LIBS,[\${TOMMATH_DIR_NATIVE}/win64-arm/libtommath.dll.a])
zlib_lib_name=libz.dll.a
tommath_lib_name=libtommath.dll.a
], [
AC_SUBST(ZLIB_LIBS,[\${ZLIB_DIR_NATIVE}/win64-arm/zdll.lib])
AC_SUBST(TOMMATH_LIBS,[\${TOMMATH_DIR_NATIVE}/win64-arm/tommath.lib])
])
], [
AS_IF([test "$GCC" = "yes"],[
AC_SUBST(ZLIB_LIBS,[\${ZLIB_DIR_NATIVE}/win64/libz.dll.a])
AC_SUBST(TOMMATH_LIBS,[\${TOMMATH_DIR_NATIVE}/win64/libtommath.dll.a])
zlib_lib_name=libz.dll.a
tommath_lib_name=libtommath.dll.a
], [
AC_SUBST(ZLIB_LIBS,[\${ZLIB_DIR_NATIVE}/win64/zdll.lib])
AC_SUBST(TOMMATH_LIBS,[\${TOMMATH_DIR_NATIVE}/win64/tommath.lib])
])
])
], [
AC_SUBST(ZLIB_LIBS,[\${ZLIB_DIR_NATIVE}/win32/zdll.lib])
AC_SUBST(TOMMATH_LIBS,[\${TOMMATH_DIR_NATIVE}/win32/tommath.lib])
])
], [
AC_DEFINE(TCL_WITH_INTERNAL_ZLIB, 1, [Tcl with internal zlib])
AC_SUBST(ZLIB_OBJS,[\${ZLIB_OBJS}])
AC_SUBST(TOMMATH_OBJS,[\${TOMMATH_OBJS}])
])
AC_SUBST(TCL_ZLIB_LIB_NAME, $zlib_lib_name)
AC_SUBST(TCL_TOMMATH_LIB_NAME, $tommath_lib_name)
AC_CHECK_TYPES([intptr_t, uintptr_t],,,[[
#include <stdint.h>
]])
#--------------------------------------------------------------------
# Zipfs support - Tip 430
#--------------------------------------------------------------------
AC_ARG_ENABLE(zipfs,
AS_HELP_STRING([--enable-zipfs],
[build with Zipfs support (default: on)]),
[tcl_ok=$enableval], [tcl_ok=yes])
if test "$tcl_ok" = "yes" ; then
#
# Find a native compiler
#
AX_CC_FOR_BUILD
#
# Find a native zip implementation
#
SC_PROG_TCLSH
SC_ZIPFS_SUPPORT
ZIPFS_BUILD=1
TCL_ZIP_FILE=libtcl${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}${TCL_PATCH_LEVEL}.zip
else
ZIPFS_BUILD=0
TCL_ZIP_FILE=
fi
# Do checking message here to not mess up interleaved configure output
AC_MSG_CHECKING([for building with zipfs])
if test "${ZIPFS_BUILD}" = 1; then
if test "${SHARED_BUILD}" = 0; then
ZIPFS_BUILD=2;
AC_DEFINE(ZIPFS_BUILD, 2, [Are we building with zipfs enabled?])
else
AC_DEFINE(ZIPFS_BUILD, 1, [Are we building with zipfs enabled?])\
fi
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
INSTALL_LIBRARIES=install-libraries
INSTALL_MSGS=install-msgs
fi
AC_SUBST(ZIPFS_BUILD)
AC_SUBST(TCL_ZIP_FILE)
AC_SUBST(INSTALL_LIBRARIES)
AC_SUBST(INSTALL_MSGS)
#--------------------------------------------------------------------
# Perform additinal compiler tests.
#--------------------------------------------------------------------
# See if declarations like FINDEX_INFO_LEVELS are
# missing from winbase.h. This is known to be
# a problem with VC++ 5.2.
AC_CACHE_CHECK(for FINDEX_INFO_LEVELS in winbase.h,
tcl_cv_findex_enums,
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
]], [[
FINDEX_INFO_LEVELS i;
FINDEX_SEARCH_OPS j;
]])],
[tcl_cv_findex_enums=yes],
[tcl_cv_findex_enums=no])
)
if test "$tcl_cv_findex_enums" = "no"; then
AC_DEFINE(HAVE_NO_FINDEX_ENUMS, 1,
[Defined when enums are missing from winbase.h])
fi
# See if the compiler supports intrinsics.
AC_CACHE_CHECK(for intrinsics support in compiler,
tcl_cv_intrinsics,
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#include <intrin.h>
]], [[
__cpuidex(0,0,0);
]])],
[tcl_cv_intrinsics=yes],
[tcl_cv_intrinsics=no])
)
if test "$tcl_cv_intrinsics" = "yes"; then
AC_DEFINE(HAVE_INTRIN_H, 1,
[Defined when the compilers supports intrinsics])
fi
# See if the compiler supports cpuid header.
AC_CACHE_CHECK(for cpuid.h,
tcl_cv_cpuid_h,
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <cpuid.h>
]], [[
__get_cpuid(0, 0, 0, 0, 0);
]])],
[tcl_cv_cpuid_h=yes],
[tcl_cv_cpuid_h=no])
)
if test "$tcl_cv_cpuid_h" = "yes"; then
AC_DEFINE(HAVE_CPUID_H, 1,
[Defined when cpuid.h exists])
fi
# See if the <wspiapi.h> header file is present
AC_CACHE_CHECK(for wspiapi.h,
tcl_cv_wspiapi_h,
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <wspiapi.h>
]], [[]])],
[tcl_cv_wspiapi_h=yes],
[tcl_cv_wspiapi_h=no])
)
if test "$tcl_cv_wspiapi_h" = "yes"; then
AC_DEFINE(HAVE_WSPIAPI_H, 1,
[Defined when wspiapi.h exists])
fi
# See if declarations like FINDEX_INFO_LEVELS are
# missing from winbase.h. This is known to be
# a problem with VC++ 5.2.
AC_CACHE_CHECK(for FINDEX_INFO_LEVELS in winbase.h,
tcl_cv_findex_enums,
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
]], [[
FINDEX_INFO_LEVELS i;
FINDEX_SEARCH_OPS j;
]])],
[tcl_cv_findex_enums=yes],
[tcl_cv_findex_enums=no])
)
if test "$tcl_cv_findex_enums" = "no"; then
AC_DEFINE(HAVE_NO_FINDEX_ENUMS, 1,
[Defined when enums are missing from winbase.h])
fi
#--------------------------------------------------------------------
# Set the default compiler switches based on the --enable-symbols
# option. This macro depends on C flags, and should be called
# after SC_CONFIG_CFLAGS macro is called.
#--------------------------------------------------------------------
SC_ENABLE_SYMBOLS
#--------------------------------------------------------------------
# Embed the manifest if we can determine how
#--------------------------------------------------------------------
SC_EMBED_MANIFEST
#------------------------------------------------------------------------
# tclConfig.sh refers to this by a different name
#------------------------------------------------------------------------
TCL_SHARED_BUILD=${SHARED_BUILD}
#--------------------------------------------------------------------
# Perform final evaluations of variables with possible substitutions.
#--------------------------------------------------------------------
eval "TCL_SRC_DIR=\"`cd $srcdir/..; $CYGPATH $(pwd)`\""
eval "TCL_DLL_FILE=tcl${VER}${DLLSUFFIX}"
eval "TCL_LIB_FLAG=\"-ltcl${VER}${LIBFLAGSUFFIX}\""
eval "TCL_STUB_LIB_FILE=\"${LIBPREFIX}tclstub${LIBSUFFIX}\""
eval "TCL_STUB_LIB_FLAG=\"-ltclstub${LIBFLAGSUFFIX}\""
eval "TCL_BUILD_STUB_LIB_SPEC=\"-L`$CYGPATH $(pwd)` ${TCL_STUB_LIB_FLAG}\""
eval "TCL_STUB_LIB_SPEC=\"-L${libdir} ${TCL_STUB_LIB_FLAG}\""
eval "TCL_BUILD_STUB_LIB_PATH=\"`$CYGPATH $(pwd)`/${TCL_STUB_LIB_FILE}\""
eval "TCL_STUB_LIB_PATH=\"${libdir}/${TCL_STUB_LIB_FILE}\""
if test ${SHARED_BUILD} = 0 -o "$GCC" != "yes" ; then
eval "TCL_LIB_FLAG=\"${LIBPREFIX}tcl${VER}${LIBSUFFIX}\""
eval "TCL_LIB_FILE=\"${LIBPREFIX}tcl${VER}${LIBSUFFIX}\""
else
eval "TCL_LIB_FLAG=\"-ltcl${VER}${FLAGSUFFIX}\""
eval "TCL_LIB_FILE=\"${LIBPREFIX}tcl${VER}${DLLSUFFIX}.a\""
fi
eval "TCL_BUILD_LIB_SPEC=\"-L`$CYGPATH $(pwd)` ${TCL_LIB_FLAG}\""
eval "TCL_LIB_SPEC=\"-L${libdir} ${TCL_LIB_FLAG}\""
# Install time header dir can be set via --includedir
eval "TCL_INCLUDE_SPEC=\"-I${includedir}\""
TCL_SHARED_LIB_SUFFIX="\${NODOT_VERSION}${DLLSUFFIX}"
TCL_UNSHARED_LIB_SUFFIX="\${NODOT_VERSION}${LIBSUFFIX}"
CFG_TCL_SHARED_LIB_SUFFIX=${TCL_SHARED_LIB_SUFFIX}
CFG_TCL_UNSHARED_LIB_SUFFIX=${TCL_UNSHARED_LIB_SUFFIX}
#--------------------------------------------------------------------
# Adjust the defines for how the resources are built depending
# on symbols and static vs. shared.
#--------------------------------------------------------------------
if test ${SHARED_BUILD} = 0 ; then
RC_DEFINES="${RC_DEFINE} STATIC_BUILD"
else
RC_DEFINES=""
fi
#--------------------------------------------------------------------
# The statements below define the symbol TCL_PACKAGE_PATH, which
# gives a list of directories that may contain packages. The list
# consists of one directory for machine-dependent binaries and
# another for platform-independent scripts.
#--------------------------------------------------------------------
if test "$prefix/lib" != "$libdir"; then
TCL_PACKAGE_PATH="${libdir};${prefix}\\lib"
else
TCL_PACKAGE_PATH="${prefix}\\lib"
fi
# The tclsh.exe.manifest requires these
# TCL_WIN_VERSION is the 4 dotted pair Windows version format which needs
# the release level, and must account for interim release versioning
case "$TCL_PATCH_LEVEL" in
*a*) TCL_RELEASE_LEVEL=0 ;;
*b*) TCL_RELEASE_LEVEL=1 ;;
*) TCL_RELEASE_LEVEL=2 ;;
esac
TCL_WIN_VERSION="$TCL_VERSION.$TCL_RELEASE_LEVEL.`echo $TCL_PATCH_LEVEL | tr -d ab.`"
AC_SUBST(TCL_WIN_VERSION)
# X86|AMD64|ARM64|IA64 for manifest
AC_SUBST(MACHINE)
AC_SUBST(TCL_VERSION)
AC_SUBST(TCL_MAJOR_VERSION)
AC_SUBST(TCL_MINOR_VERSION)
AC_SUBST(TCL_PATCH_LEVEL)
AC_SUBST(PKG_CFG_ARGS)
AC_SUBST(TCL_EXE)
AC_SUBST(TCL_LIB_FILE)
AC_SUBST(TCL_LIB_FLAG)
AC_SUBST(TCL_STATIC_LIB_FILE)
AC_SUBST(TCL_STATIC_LIB_FLAG)
AC_SUBST(TCL_IMPORT_LIB_FILE)
AC_SUBST(TCL_IMPORT_LIB_FLAG)
# empty on win
AC_SUBST(TCL_LIBS)
AC_SUBST(TCL_LIB_SPEC)
AC_SUBST(TCL_STUB_LIB_FILE)
AC_SUBST(TCL_STUB_LIB_FLAG)
AC_SUBST(TCL_STUB_LIB_SPEC)
AC_SUBST(TCL_STUB_LIB_PATH)
AC_SUBST(TCL_INCLUDE_SPEC)
AC_SUBST(TCL_BUILD_STUB_LIB_SPEC)
AC_SUBST(TCL_BUILD_STUB_LIB_PATH)
AC_SUBST(TCL_DLL_FILE)
AC_SUBST(TCL_SRC_DIR)
AC_SUBST(TCL_BIN_DIR)
AC_SUBST(CFG_TCL_SHARED_LIB_SUFFIX)
AC_SUBST(CFG_TCL_UNSHARED_LIB_SUFFIX)
# win/tcl.m4 doesn't set (CFLAGS)
AC_SUBST(CFLAGS_DEFAULT)
AC_SUBST(EXTRA_CFLAGS)
AC_SUBST(CYGPATH)
AC_SUBST(DEPARG)
AC_SUBST(CC_OBJNAME)
AC_SUBST(CC_EXENAME)
# win/tcl.m4 doesn't set (LDFLAGS)
AC_SUBST(LDFLAGS_DEFAULT)
AC_SUBST(LDFLAGS_DEBUG)
AC_SUBST(LDFLAGS_OPTIMIZE)
AC_SUBST(LDFLAGS_CONSOLE)
AC_SUBST(LDFLAGS_WINDOW)
AC_SUBST(AR)
AC_SUBST(RANLIB)
AC_SUBST(STLIB_LD)
AC_SUBST(SHLIB_LD)
AC_SUBST(SHLIB_LD_LIBS)
AC_SUBST(SHLIB_CFLAGS)
AC_SUBST(SHLIB_SUFFIX)
AC_SUBST(TCL_SHARED_BUILD)
AC_SUBST(LIBS)
AC_SUBST(LIBS_GUI)
AC_SUBST(DLLSUFFIX)
AC_SUBST(LIBPREFIX)
AC_SUBST(LIBSUFFIX)
AC_SUBST(EXESUFFIX)
AC_SUBST(LIBRARIES)
AC_SUBST(MAKE_LIB)
AC_SUBST(MAKE_STUB_LIB)
AC_SUBST(POST_MAKE_LIB)
AC_SUBST(MAKE_DLL)
AC_SUBST(MAKE_EXE)
AC_SUBST(TCL_BUILD_LIB_SPEC)
AC_SUBST(TCL_PACKAGE_PATH)
# win only
AC_SUBST(TCL_DDE_VERSION)
AC_SUBST(TCL_DDE_MAJOR_VERSION)
AC_SUBST(TCL_DDE_MINOR_VERSION)
AC_SUBST(TCL_REG_VERSION)
AC_SUBST(TCL_REG_MAJOR_VERSION)
AC_SUBST(TCL_REG_MINOR_VERSION)
AC_SUBST(RC)
AC_SUBST(RC_OUT)
AC_SUBST(RC_TYPE)
AC_SUBST(RC_INCLUDE)
AC_SUBST(RC_DEFINE)
AC_SUBST(RC_DEFINES)
AC_SUBST(RES)
AC_CONFIG_FILES([Makefile tclConfig.sh tclsh.exe.manifest tcl.pc:../unix/tcl.pc.in])
AC_OUTPUT
dnl Local Variables:
dnl mode: autoconf
dnl End:

1
vendor/tcl/win/gitmanifest.in vendored Normal file
View file

@ -0,0 +1 @@
git-

40
vendor/tcl/win/license.terms vendored Normal file
View file

@ -0,0 +1,40 @@
This software is copyrighted by the Regents of the University of
California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
Corporation and other parties. The following terms apply to all files
associated with the software unless explicitly disclaimed in
individual files.
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
they apply.
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
GOVERNMENT USE: If you are acquiring this software on behalf of the
U.S. government, the Government shall have only "Restricted Rights"
in the software and related documentation as defined in the Federal
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
are acquiring the software on behalf of the Department of Defense, the
software shall be classified as "Commercial Computer Software" and the
Government shall have only "Restricted Rights" as defined in Clause
252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the
authors grant the U.S. Government and others acting in its behalf
permission to use and distribute the software in accordance with the
terms specified in this license.

1195
vendor/tcl/win/makefile.vc vendored Normal file

File diff suppressed because it is too large Load diff

820
vendor/tcl/win/nmakehlp.c vendored Normal file
View file

@ -0,0 +1,820 @@
/*
* ----------------------------------------------------------------------------
* nmakehlp.c --
*
* This is used to fix limitations within nmake and the environment.
*
* Copyright (c) 2002 David Gravereaux.
* Copyright (c) 2006 Pat Thoyts
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
* ----------------------------------------------------------------------------
*/
#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#ifdef _MSC_VER
#pragma comment (lib, "user32.lib")
#pragma comment (lib, "kernel32.lib")
#endif
#include <stdio.h>
/*
* This library is required for x64 builds with _some_ versions of MSVC
*/
#if defined(_M_IA64) || defined(_M_AMD64)
#if _MSC_VER >= 1400 && _MSC_VER < 1500
#pragma comment(lib, "bufferoverflowU")
#endif
#endif
/* ISO hack for dumb VC++ */
#if defined(_WIN32) && defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
/* protos */
static int CheckForCompilerFeature(const char *option);
static int CheckForLinkerFeature(char **options, int count);
static int IsIn(const char *string, const char *substring);
static int SubstituteFile(const char *substs, const char *filename);
static int QualifyPath(const char *path);
static int LocateDependency(const char *keyfile);
static const char *GetVersionFromFile(const char *filename, const char *match, int numdots);
static DWORD WINAPI ReadFromPipe(LPVOID args);
/* globals */
#define CHUNK 25
#define STATICBUFFERSIZE 1000
typedef struct {
HANDLE pipe;
char buffer[STATICBUFFERSIZE];
} pipeinfo;
pipeinfo Out = {INVALID_HANDLE_VALUE, ""};
pipeinfo Err = {INVALID_HANDLE_VALUE, ""};
/*
* exitcodes: 0 == no, 1 == yes, 2 == error
*/
int
main(
int argc,
char *argv[])
{
char msg[300];
DWORD dwWritten;
int chars;
const char *s;
/*
* Make sure children (cl.exe and link.exe) are kept quiet.
*/
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
/*
* Make sure the compiler and linker aren't effected by the outside world.
*/
SetEnvironmentVariable("CL", "");
SetEnvironmentVariable("LINK", "");
if (argc > 1 && *argv[1] == '-') {
switch (*(argv[1]+1)) {
case 'c':
if (argc != 3) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -c <compiler option>\n"
"Tests for whether cl.exe supports an option\n"
"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 2;
}
return CheckForCompilerFeature(argv[2]);
case 'l':
if (argc < 3) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -l <linker option> ?<mandatory option> ...?\n"
"Tests for whether link.exe supports an option\n"
"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 2;
}
return CheckForLinkerFeature(&argv[2], argc-2);
case 'f':
if (argc == 2) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -f <string> <substring>\n"
"Find a substring within another\n"
"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 2;
} else if (argc == 3) {
/*
* If the string is blank, there is no match.
*/
return 0;
} else {
return IsIn(argv[2], argv[3]);
}
case 's':
if (argc == 2) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -s <substitutions file> <file>\n"
"Perform a set of string map type substutitions on a file\n"
"exitcodes: 0\n",
argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 2;
}
return SubstituteFile(argv[2], argv[3]);
case 'V':
if (argc != 4) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -V filename matchstring\n"
"Extract a version from a file:\n"
"eg: pkgIndex.tcl \"package ifneeded http\"",
argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 0;
}
s = GetVersionFromFile(argv[2], argv[3], *(argv[1]+2) - '0');
if (s && *s) {
printf("%s\n", s);
return 0;
} else
return 1; /* Version not found. Return non-0 exit code */
case 'Q':
if (argc != 3) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -Q path\n"
"Emit the fully qualified path\n"
"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 2;
}
return QualifyPath(argv[2]);
case 'L':
if (argc != 3) {
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -L keypath\n"
"Emit the fully qualified path of directory containing keypath\n"
"exitcodes: 0 == success, 1 == not found, 2 == error\n", argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
&dwWritten, NULL);
return 2;
}
return LocateDependency(argv[2]);
}
}
chars = snprintf(msg, sizeof(msg) - 1,
"usage: %s -c|-f|-l|-Q|-s|-V ...\n"
"This is a little helper app to equalize shell differences between WinNT and\n"
"Win9x and get nmake.exe to accomplish its job.\n",
argv[0]);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
return 2;
}
static int
CheckForCompilerFeature(
const char *option)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
DWORD threadID;
char msg[300];
BOOL ok;
HANDLE hProcess, h, pipeThreads[2];
char cmdline[100];
hProcess = GetCurrentProcess();
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
memset(&si, 0, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = INVALID_HANDLE_VALUE;
memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = FALSE;
/*
* Create a non-inheritable pipe.
*/
CreatePipe(&Out.pipe, &h, &sa, 0);
/*
* Dupe the write side, make it inheritable, and close the original.
*/
DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
/*
* Same as above, but for the error side.
*/
CreatePipe(&Err.pipe, &h, &sa, 0);
DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
/*
* Base command line.
*/
lstrcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X -Fp.\\_junk.pch ");
/*
* Append our option for testing
*/
lstrcat(cmdline, option);
/*
* Filename to compile, which exists, but is nothing and empty.
*/
lstrcat(cmdline, " .\\nul");
ok = CreateProcess(
NULL, /* Module name. */
cmdline, /* Command line. */
NULL, /* Process handle not inheritable. */
NULL, /* Thread handle not inheritable. */
TRUE, /* yes, inherit handles. */
DETACHED_PROCESS, /* No console for you. */
NULL, /* Use parent's environment block. */
NULL, /* Use parent's starting directory. */
&si, /* Pointer to STARTUPINFO structure. */
&pi); /* Pointer to PROCESS_INFORMATION structure. */
if (!ok) {
DWORD err = GetLastError();
int chars = snprintf(msg, sizeof(msg) - 1,
"Tried to launch: \"%s\", but got error [%lu]: ", cmdline, err);
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|
FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPSTR)&msg[chars],
(300-chars), 0);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL);
return 2;
}
/*
* Close our references to the write handles that have now been inherited.
*/
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdError);
WaitForInputIdle(pi.hProcess, 5000);
CloseHandle(pi.hThread);
/*
* Start the pipe reader threads.
*/
pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
/*
* Block waiting for the process to end.
*/
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
/*
* Wait for our pipe to get done reading, should it be a little slow.
*/
WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
CloseHandle(pipeThreads[0]);
CloseHandle(pipeThreads[1]);
/*
* Look for the commandline warning code in both streams.
* - in MSVC 6 & 7 we get D4002, in MSVC 8 we get D9002.
*/
return !(strstr(Out.buffer, "D4002") != NULL
|| strstr(Err.buffer, "D4002") != NULL
|| strstr(Out.buffer, "D9002") != NULL
|| strstr(Err.buffer, "D9002") != NULL
|| strstr(Out.buffer, "D2021") != NULL
|| strstr(Err.buffer, "D2021") != NULL);
}
static int
CheckForLinkerFeature(
char **options,
int count)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
DWORD threadID;
char msg[300];
BOOL ok;
HANDLE hProcess, h, pipeThreads[2];
int i;
char cmdline[255];
hProcess = GetCurrentProcess();
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
memset(&si, 0, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = INVALID_HANDLE_VALUE;
memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
/*
* Create a non-inheritible pipe.
*/
CreatePipe(&Out.pipe, &h, &sa, 0);
/*
* Dupe the write side, make it inheritable, and close the original.
*/
DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
/*
* Same as above, but for the error side.
*/
CreatePipe(&Err.pipe, &h, &sa, 0);
DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
/*
* Base command line.
*/
lstrcpy(cmdline, "link.exe -nologo ");
/*
* Append our option for testing.
*/
for (i = 0; i < count; i++) {
lstrcat(cmdline, " \"");
lstrcat(cmdline, options[i]);
lstrcat(cmdline, "\"");
}
ok = CreateProcess(
NULL, /* Module name. */
cmdline, /* Command line. */
NULL, /* Process handle not inheritable. */
NULL, /* Thread handle not inheritable. */
TRUE, /* yes, inherit handles. */
DETACHED_PROCESS, /* No console for you. */
NULL, /* Use parent's environment block. */
NULL, /* Use parent's starting directory. */
&si, /* Pointer to STARTUPINFO structure. */
&pi); /* Pointer to PROCESS_INFORMATION structure. */
if (!ok) {
DWORD err = GetLastError();
int chars = snprintf(msg, sizeof(msg) - 1,
"Tried to launch: \"%s\", but got error [%lu]: ", cmdline, err);
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|
FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPSTR)&msg[chars],
(300-chars), 0);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err, NULL);
return 2;
}
/*
* Close our references to the write handles that have now been inherited.
*/
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdError);
WaitForInputIdle(pi.hProcess, 5000);
CloseHandle(pi.hThread);
/*
* Start the pipe reader threads.
*/
pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
/*
* Block waiting for the process to end.
*/
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
/*
* Wait for our pipe to get done reading, should it be a little slow.
*/
WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
CloseHandle(pipeThreads[0]);
CloseHandle(pipeThreads[1]);
/*
* Look for the commandline warning code in the stderr stream.
*/
return !(strstr(Out.buffer, "LNK1117") != NULL ||
strstr(Err.buffer, "LNK1117") != NULL ||
strstr(Out.buffer, "LNK4044") != NULL ||
strstr(Err.buffer, "LNK4044") != NULL ||
strstr(Out.buffer, "LNK4224") != NULL ||
strstr(Err.buffer, "LNK4224") != NULL);
}
static DWORD WINAPI
ReadFromPipe(
LPVOID args)
{
pipeinfo *pi = (pipeinfo *) args;
char *lastBuf = pi->buffer;
DWORD dwRead;
BOOL ok;
again:
if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) {
CloseHandle(pi->pipe);
return (DWORD)-1;
}
ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L);
if (!ok || dwRead == 0) {
CloseHandle(pi->pipe);
return 0;
}
lastBuf += dwRead;
goto again;
return 0; /* makes the compiler happy */
}
static int
IsIn(
const char *string,
const char *substring)
{
return (strstr(string, substring) != NULL);
}
/*
* GetVersionFromFile --
* Looks for a match string in a file and then returns the version
* following the match where a version is anything acceptable to
* package provide or package ifneeded.
*/
static const char *
GetVersionFromFile(
const char *filename,
const char *match,
int numdots)
{
static char szBuffer[100];
char *szResult = NULL;
FILE *fp = fopen(filename, "rt");
if (fp != NULL) {
/*
* Read data until we see our match string.
*/
while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) {
LPSTR p, q;
p = strstr(szBuffer, match);
if (p != NULL) {
/*
* Skip to first digit after the match.
*/
p += strlen(match);
while (*p && !isdigit((unsigned char)*p)) {
++p;
}
/*
* Find ending whitespace.
*/
q = p;
while (*q && (strchr("0123456789.ab", *q)) && (((!strchr(".ab", *q)
&& !strchr("ab", q[-1])) || --numdots))) {
++q;
}
*q = 0;
szResult = p;
break;
}
}
fclose(fp);
}
return szResult;
}
/*
* List helpers for the SubstituteFile function
*/
typedef struct list_item_t {
struct list_item_t *nextPtr;
char * key;
char * value;
} list_item_t;
/* insert a list item into the list (list may be null) */
static list_item_t *
list_insert(list_item_t **listPtrPtr, const char *key, const char *value)
{
list_item_t *itemPtr = (list_item_t *)malloc(sizeof(list_item_t));
if (itemPtr) {
itemPtr->key = strdup(key);
itemPtr->value = strdup(value);
itemPtr->nextPtr = NULL;
while(*listPtrPtr) {
listPtrPtr = &(*listPtrPtr)->nextPtr;
}
*listPtrPtr = itemPtr;
}
return itemPtr;
}
static void
list_free(list_item_t **listPtrPtr)
{
list_item_t *tmpPtr, *listPtr = *listPtrPtr;
while (listPtr) {
tmpPtr = listPtr;
listPtr = listPtr->nextPtr;
free(tmpPtr->key);
free(tmpPtr->value);
free(tmpPtr);
}
}
/*
* SubstituteFile --
* As windows doesn't provide anything useful like sed and it's unreliable
* to use the tclsh you are building against (consider x-platform builds -
* e.g. compiling AMD64 target from IX86) we provide a simple substitution
* option here to handle autoconf style substitutions.
* The substitution file is whitespace and line delimited. The file should
* consist of lines matching the regular expression:
* \s*\S+\s+\S*$
*
* Usage is something like:
* nmakehlp -S << $** > $@
* @PACKAGE_NAME@ $(PACKAGE_NAME)
* @PACKAGE_VERSION@ $(PACKAGE_VERSION)
* <<
*/
static int
SubstituteFile(
const char *substitutions,
const char *filename)
{
static char szBuffer[1024], szCopy[1024];
list_item_t *substPtr = NULL;
FILE *fp, *sp;
fp = fopen(filename, "rt");
if (fp != NULL) {
/*
* Build a list of substitutions from the first filename
*/
sp = fopen(substitutions, "rt");
if (sp != NULL) {
while (fgets(szBuffer, sizeof(szBuffer), sp) != NULL) {
unsigned char *ks, *ke, *vs, *ve;
ks = (unsigned char*)szBuffer;
while (ks && *ks && isspace(*ks)) ++ks;
ke = ks;
while (ke && *ke && !isspace(*ke)) ++ke;
vs = ke;
while (vs && *vs && isspace(*vs)) ++vs;
ve = vs;
while (ve && *ve && !(*ve == '\r' || *ve == '\n')) ++ve;
*ke = 0, *ve = 0;
list_insert(&substPtr, (char*)ks, (char*)vs);
}
fclose(sp);
}
/* debug: dump the list */
#ifndef NDEBUG
{
int n = 0;
list_item_t *p = NULL;
for (p = substPtr; p != NULL; p = p->nextPtr, ++n) {
fprintf(stderr, "% 3d '%s' => '%s'\n", n, p->key, p->value);
}
}
#endif
/*
* Run the substitutions over each line of the input
*/
while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) {
list_item_t *p = NULL;
for (p = substPtr; p != NULL; p = p->nextPtr) {
char *m = strstr(szBuffer, p->key);
if (m) {
char *cp, *op, *sp;
cp = szCopy;
op = szBuffer;
while (op != m) *cp++ = *op++;
sp = p->value;
while (sp && *sp) *cp++ = *sp++;
op += strlen(p->key);
while (*op) *cp++ = *op++;
*cp = 0;
memcpy(szBuffer, szCopy, sizeof(szCopy));
}
}
printf("%s", szBuffer);
}
list_free(&substPtr);
}
fclose(fp);
return 0;
}
BOOL FileExists(LPCTSTR szPath)
{
#ifndef INVALID_FILE_ATTRIBUTES
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
DWORD pathAttr = GetFileAttributes(szPath);
return (pathAttr != INVALID_FILE_ATTRIBUTES &&
!(pathAttr & FILE_ATTRIBUTE_DIRECTORY));
}
/*
* QualifyPath --
*
* This composes the current working directory with a provided path
* and returns the fully qualified and normalized path.
* Mostly needed to setup paths for testing.
*/
static int
QualifyPath(
const char *szPath)
{
char szCwd[MAX_PATH + 1];
GetFullPathName(szPath, sizeof(szCwd)-1, szCwd, NULL);
printf("%s\n", szCwd);
return 0;
}
/*
* Implements LocateDependency for a single directory. See that command
* for an explanation.
* Returns 0 if found after printing the directory.
* Returns 1 if not found but no errors.
* Returns 2 on any kind of error
* Basically, these are used as exit codes for the process.
*/
static int LocateDependencyHelper(const char *dir, const char *keypath)
{
HANDLE hSearch;
char path[MAX_PATH+1];
size_t dirlen;
int keylen, ret;
WIN32_FIND_DATA finfo;
if (dir == NULL || keypath == NULL) {
return 2; /* Have no real error reporting mechanism into nmake */
}
dirlen = strlen(dir);
if (dirlen > sizeof(path) - 3) {
return 2;
}
strncpy(path, dir, dirlen);
strncpy(path+dirlen, "\\*", 3); /* Including terminating \0 */
keylen = strlen(keypath);
#if 0 /* This function is not available in Visual C++ 6 */
/*
* Use numerics 0 -> FindExInfoStandard,
* 1 -> FindExSearchLimitToDirectories,
* as these are not defined in Visual C++ 6
*/
hSearch = FindFirstFileEx(path, 0, &finfo, 1, NULL, 0);
#else
hSearch = FindFirstFile(path, &finfo);
#endif
if (hSearch == INVALID_HANDLE_VALUE) {
return 1; /* Not found */
}
/* Loop through all subdirs checking if the keypath is under there */
ret = 1; /* Assume not found */
do {
int sublen;
/*
* We need to check it is a directory despite the
* FindExSearchLimitToDirectories in the above call. See SDK docs
*/
if ((finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
continue;
}
sublen = strlen(finfo.cFileName);
if ((dirlen+1+sublen+1+keylen+1) > sizeof(path)) {
continue; /* Path does not fit, assume not matched */
}
strncpy(path+dirlen+1, finfo.cFileName, sublen);
path[dirlen+1+sublen] = '\\';
strncpy(path+dirlen+1+sublen+1, keypath, keylen+1);
if (FileExists(path)) {
/* Found a match, print to stdout */
path[dirlen+1+sublen] = '\0';
QualifyPath(path);
ret = 0;
break;
}
} while (FindNextFile(hSearch, &finfo));
FindClose(hSearch);
return ret;
}
/*
* LocateDependency --
*
* Locates a dependency for a package.
* keypath - a relative path within the package directory
* that is used to confirm it is the correct directory.
* The search path for the package directory is currently only
* the parent and grandparent of the current working directory.
* If found, the command prints
* name_DIRPATH=<full path of located directory>
* and returns 0. If not found, does not print anything and returns 1.
*/
static int LocateDependency(const char *keypath)
{
size_t i;
int ret;
static const char *const paths[] = {"..", "..\\..", "..\\..\\.."};
for (i = 0; i < (sizeof(paths)/sizeof(paths[0])); ++i) {
ret = LocateDependencyHelper(paths[i], keypath);
if (ret == 0) {
return ret;
}
}
return ret;
}
/*
* Local variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* indent-tabs-mode: t
* tab-width: 8
* End:
*/

123
vendor/tcl/win/rules-ext.vc vendored Normal file
View file

@ -0,0 +1,123 @@
# This file should only be included in makefiles for Tcl extensions,
# NOT in the makefile for Tcl itself.
!ifndef _RULES_EXT_VC
# We need to run from the directory the parent makefile is located in.
# nmake does not tell us what makefile was used to invoke it so parent
# makefile has to set the MAKEFILEVC macro or we just make a guess and
# warn if we think that is not the case.
!if "$(MAKEFILEVC)" == ""
!if exist("$(PROJECT).vc")
MAKEFILEVC = $(PROJECT).vc
!elseif exist("makefile.vc")
MAKEFILEVC = makefile.vc
!endif
!endif # "$(MAKEFILEVC)" == ""
!if !exist("$(MAKEFILEVC)")
MSG = ^
You must run nmake from the directory containing the project makefile.^
If you are doing that and getting this message, set the MAKEFILEVC^
macro to the name of the project makefile.
!message WARNING: $(MSG)
!endif
!if "$(PROJECT)" == "tcl"
!error The rules-ext.vc file is not intended for Tcl itself.
!endif
# We extract version numbers using the nmakehlp program. For now use
# the local copy of nmakehlp. Once we locate Tcl, we will use that
# one if it is newer.
!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "$(NATIVE_ARCH)"
!if [$(CC) -nologo -DNDEBUG "nmakehlp.c" -link -subsystem:console > nul]
!endif
!else
!if [copy x86_64-w64-mingw32-nmakehlp.exe nmakehlp.exe >NUL]
!endif
!endif
# First locate the Tcl directory that we are working with.
!if "$(TCLDIR)" != ""
_RULESDIR = $(TCLDIR:/=\)
!else
# If an installation path is specified, that is also the Tcl directory.
# Also Tk never builds against an installed Tcl, it needs Tcl sources
!if defined(INSTALLDIR) && "$(PROJECT)" != "tk"
_RULESDIR=$(INSTALLDIR:/=\)
!else
# Locate Tcl sources
!if [echo _RULESDIR = \> nmakehlp.out] \
|| [nmakehlp -L generic\tcl.h >> nmakehlp.out]
_RULESDIR = ..\..\tcl
!else
!include nmakehlp.out
!endif
!endif # defined(INSTALLDIR)....
!endif # ifndef TCLDIR
# Now look for the targets.vc file under the Tcl root. Note we check this
# file and not rules.vc because the latter also exists on older systems.
!if exist("$(_RULESDIR)\lib\nmake\targets.vc") # Building against installed Tcl
_RULESDIR = $(_RULESDIR)\lib\nmake
!elseif exist("$(_RULESDIR)\win\targets.vc") # Building against Tcl sources
_RULESDIR = $(_RULESDIR)\win
!else
# If we have not located Tcl's targets file, most likely we are compiling
# against an older version of Tcl and so must use our own support files.
_RULESDIR = .
!endif
!if "$(_RULESDIR)" != "."
# Potentially using Tcl's support files. If this extension has its own
# nmake support files, need to compare the versions and pick newer.
!if exist("rules.vc") # The extension has its own copy
!if [echo TCL_RULES_MAJOR = \> versions.vc] \
&& [nmakehlp -V "$(_RULESDIR)\rules.vc" RULES_VERSION_MAJOR >> versions.vc]
!endif
!if [echo TCL_RULES_MINOR = \>> versions.vc] \
&& [nmakehlp -V "$(_RULESDIR)\rules.vc" RULES_VERSION_MINOR >> versions.vc]
!endif
!if [echo OUR_RULES_MAJOR = \>> versions.vc] \
&& [nmakehlp -V "rules.vc" RULES_VERSION_MAJOR >> versions.vc]
!endif
!if [echo OUR_RULES_MINOR = \>> versions.vc] \
&& [nmakehlp -V "rules.vc" RULES_VERSION_MINOR >> versions.vc]
!endif
!include versions.vc
# We have a newer version of the support files, use them
!if ($(TCL_RULES_MAJOR) != $(OUR_RULES_MAJOR)) || ($(TCL_RULES_MINOR) < $(OUR_RULES_MINOR))
_RULESDIR = .
!endif
!endif # if exist("rules.vc")
!endif # if $(_RULESDIR) != "."
# Let rules.vc know what copy of nmakehlp.c to use.
NMAKEHLPC = $(_RULESDIR)\nmakehlp.c
# Get rid of our internal defines before calling rules.vc
!undef TCL_RULES_MAJOR
!undef TCL_RULES_MINOR
!undef OUR_RULES_MAJOR
!undef OUR_RULES_MINOR
!if exist("$(_RULESDIR)\rules.vc")
!message *** Using $(_RULESDIR)\rules.vc
!include "$(_RULESDIR)\rules.vc"
!else
!error *** Could not locate rules.vc in $(_RULESDIR)
!endif
!endif # _RULES_EXT_VC

1919
vendor/tcl/win/rules.vc vendored Normal file

File diff suppressed because it is too large Load diff

1
vendor/tcl/win/svnmanifest.in vendored Normal file
View file

@ -0,0 +1 @@
svn-r

99
vendor/tcl/win/targets.vc vendored Normal file
View file

@ -0,0 +1,99 @@
#------------------------------------------------------------- -*- makefile -*-
# targets.vc --
#
# Part of the nmake based build system for Tcl and its extensions.
# This file defines some standard targets for the convenience of extensions
# and can be optionally included by the extension makefile.
# See TIP 477 (https://core.tcl-lang.org/tips/doc/main/tip/477.md) for docs.
$(PROJECT): setup pkgindex $(PRJLIB)
!ifdef PRJ_STUBOBJS
$(PROJECT): $(PRJSTUBLIB)
$(PRJSTUBLIB): $(PRJ_STUBOBJS)
$(LIBCMD) $**
$(PRJ_STUBOBJS):
$(CCSTUBSCMD) %s
!endif # PRJ_STUBOBJS
!ifdef PRJ_MANIFEST
$(PROJECT): $(PRJLIB).manifest
$(PRJLIB).manifest: $(PRJ_MANIFEST)
@nmakehlp -s << $** >$@
@MACHINE@ $(MACHINE:IX86=X86)
<<
!endif
!if "$(PROJECT)" != "tcl" && "$(PROJECT)" != "tk"
$(PRJLIB): $(PRJ_OBJS) $(RESFILE)
!if $(STATIC_BUILD)
$(LIBCMD) $**
!else
$(DLLCMD) $**
$(_VC_MANIFEST_EMBED_DLL)
!endif
-@del $*.exp
!endif
!if "$(PRJ_HEADERS)" != "" && "$(PRJ_OBJS)" != ""
$(PRJ_OBJS): $(PRJ_HEADERS)
!endif
# If parent makefile has defined stub objects, add their installation
# to the default install
!if "$(PRJ_STUBOBJS)" != ""
default-install: default-install-stubs
!endif
# Unlike the other default targets, these cannot be in rules.vc because
# the executed command depends on existence of macro PRJ_HEADERS_PUBLIC
# that the parent makefile will not define until after including rules-ext.vc
!if "$(PRJ_HEADERS_PUBLIC)" != ""
default-install: default-install-headers
default-install-headers:
@echo Installing headers to '$(INCLUDE_INSTALL_DIR)'
@if not exist "$(INCLUDE_INSTALL_DIR)" $(MKDIR) "$(INCLUDE_INSTALL_DIR)"
@for %f in ($(PRJ_HEADERS_PUBLIC)) do @$(COPY) %f "$(INCLUDE_INSTALL_DIR)"
!endif
!if "$(DISABLE_STANDARD_TARGETS)" == ""
DISABLE_STANDARD_TARGETS = 0
!endif
!if "$(DISABLE_TARGET_setup)" == ""
DISABLE_TARGET_setup = 0
!endif
!if "$(DISABLE_TARGET_install)" == ""
DISABLE_TARGET_install = 0
!endif
!if "$(DISABLE_TARGET_clean)" == ""
DISABLE_TARGET_clean = 0
!endif
!if "$(DISABLE_TARGET_test)" == ""
DISABLE_TARGET_test = 0
!endif
!if "$(DISABLE_TARGET_shell)" == ""
DISABLE_TARGET_shell = 0
!endif
!if !$(DISABLE_STANDARD_TARGETS)
!if !$(DISABLE_TARGET_setup)
setup: default-setup
!endif
!if !$(DISABLE_TARGET_install)
install: default-install
!endif
!if !$(DISABLE_TARGET_clean)
clean: default-clean
realclean: hose
hose: default-hose
distclean: realclean default-distclean
!endif
!if !$(DISABLE_TARGET_test)
test: default-test
!endif
!if !$(DISABLE_TARGET_shell)
shell: default-shell
!endif
!endif # DISABLE_STANDARD_TARGETS

1511
vendor/tcl/win/tcl.dsp vendored Normal file

File diff suppressed because it is too large Load diff

29
vendor/tcl/win/tcl.dsw vendored Normal file
View file

@ -0,0 +1,29 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "tcl"=.\tcl.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
}}}
###############################################################################
Global:
Package=<5>
{{{
}}}
Package=<3>
{{{
}}}
###############################################################################

1272
vendor/tcl/win/tcl.m4 vendored Normal file

File diff suppressed because it is too large Load diff

51
vendor/tcl/win/tcl.rc vendored Normal file
View file

@ -0,0 +1,51 @@
//
// Version Resource Script
//
#include <winver.h>
#include <tcl.h>
//
// build-up the name suffix that defines the type of build this is.
//
#if DEBUG && !UNCHECKED
#define SUFFIX_DEBUG "g"
#else
#define SUFFIX_DEBUG ""
#endif
#define SUFFIX SUFFIX_DEBUG
LANGUAGE 0x9, 0x1 /* LANG_ENGLISH, SUBLANG_DEFAULT */
VS_VERSION_INFO VERSIONINFO
FILEVERSION TCL_MAJOR_VERSION,TCL_MINOR_VERSION,TCL_RELEASE_LEVEL,TCL_RELEASE_SERIAL
PRODUCTVERSION TCL_MAJOR_VERSION,TCL_MINOR_VERSION,TCL_RELEASE_LEVEL,TCL_RELEASE_SERIAL
FILEFLAGSMASK 0x3fL
#ifdef DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" /* LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP */
BEGIN
VALUE "FileDescription", "Tcl DLL\0"
VALUE "OriginalFilename", "tcl" STRINGIFY(TCL_MAJOR_VERSION) STRINGIFY(TCL_MINOR_VERSION) SUFFIX ".dll\0"
VALUE "FileVersion", TCL_PATCH_LEVEL
VALUE "LegalCopyright", "Copyright \251 1987-2022 Regents of the University of California and other parties\0"
VALUE "ProductName", "Tcl " TCL_VERSION " for Windows\0"
VALUE "ProductVersion", TCL_PATCH_LEVEL
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

208
vendor/tcl/win/tclAppInit.c vendored Normal file
View file

@ -0,0 +1,208 @@
/*
* tclAppInit.c --
*
* Provides a default version of the main program and Tcl_AppInit
* procedure for tclsh and other Tcl-based applications (without Tk).
* Note that this program must be built in Win32 console mode to work
* properly.
*
* Copyright (c) 1993 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
* Copyright (c) 1998-1999 Scriptics Corporation.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tcl.h"
#if TCL_MAJOR_VERSION < 9
# if defined(USE_TCL_STUBS)
# error "Don't build with USE_TCL_STUBS!"
# endif
# define Tcl_LibraryInitProc Tcl_PackageInitProc
# define Tcl_StaticLibrary Tcl_StaticPackage
#endif
#ifdef TCL_TEST
#ifdef __cplusplus
extern "C" {
#endif
extern Tcl_LibraryInitProc Tcltest_Init;
extern Tcl_LibraryInitProc Tcltest_SafeInit;
#ifdef __cplusplus
}
#endif
#endif /* TCL_TEST */
#if defined(STATIC_BUILD)
extern Tcl_LibraryInitProc Registry_Init;
extern Tcl_LibraryInitProc Dde_Init;
extern Tcl_LibraryInitProc Dde_SafeInit;
#endif
#define WIN32_LEAN_AND_MEAN
#define STRICT /* See MSDN Article Q83456 */
#include <windows.h>
#undef STRICT
#undef WIN32_LEAN_AND_MEAN
#include <locale.h>
#include <stdlib.h>
#include <tchar.h>
#if defined(__GNUC__)
int _CRT_glob = 0;
#endif /* __GNUC__ */
/*
* The following #if block allows you to change the AppInit function by using
* a #define of TCL_LOCAL_APPINIT instead of rewriting this entire file. The
* #if checks for that #define and uses Tcl_AppInit if it does not exist.
*/
#ifndef TCL_LOCAL_APPINIT
#define TCL_LOCAL_APPINIT Tcl_AppInit
#endif
#ifndef MODULE_SCOPE
# define MODULE_SCOPE extern
#endif
MODULE_SCOPE int TCL_LOCAL_APPINIT(Tcl_Interp *);
/*
* The following #if block allows you to change how Tcl finds the startup
* script, prime the library or encoding paths, fiddle with the argv, etc.,
* without needing to rewrite Tcl_Main()
*/
#ifdef TCL_LOCAL_MAIN_HOOK
MODULE_SCOPE int TCL_LOCAL_MAIN_HOOK(int *argc, TCHAR ***argv);
#endif
/*
*----------------------------------------------------------------------
*
* main --
*
* This is the main program for the application.
*
* Results:
* None: Tcl_Main never returns here, so this procedure never returns
* either.
*
* Side effects:
* Just about anything, since from here we call arbitrary Tcl code.
*
*----------------------------------------------------------------------
*/
int
_tmain(
int argc, /* Number of command-line arguments. */
TCHAR *argv[]) /* Values of command-line arguments. */
{
TCHAR *p;
/*
* Set up the default locale to be standard "C" locale so parsing is
* performed correctly.
*/
setlocale(LC_ALL, "C");
/*
* Forward slashes substituted for backslashes.
*/
for (p = argv[0]; *p != '\0'; p++) {
if (*p == '\\') {
*p = '/';
}
}
#ifdef TCL_LOCAL_MAIN_HOOK
TCL_LOCAL_MAIN_HOOK(&argc, &argv);
#elif TCL_MAJOR_VERSION > 8 && (!defined(_WIN32) || defined(UNICODE))
/* New in Tcl 9.0. This doesn't work on Windows without UNICODE */
TclZipfs_AppHook(&argc, &argv);
#endif
Tcl_Main(argc, argv, TCL_LOCAL_APPINIT);
return 0; /* Needed only to prevent compiler warning. */
}
/*
*----------------------------------------------------------------------
*
* Tcl_AppInit --
*
* This procedure performs application-specific initialization. Most
* applications, especially those that incorporate additional packages,
* will have their own version of this procedure.
*
* Results:
* Returns a standard Tcl completion code, and leaves an error message in
* the interp's result if an error occurs.
*
* Side effects:
* Depends on the startup script.
*
*----------------------------------------------------------------------
*/
int
Tcl_AppInit(
Tcl_Interp *interp) /* Interpreter for application. */
{
if (Tcl_Init(interp) == TCL_ERROR) {
return TCL_ERROR;
}
#if defined(STATIC_BUILD)
Tcl_StaticLibrary(NULL, "Registry", Registry_Init, NULL);
Tcl_StaticLibrary(NULL, "Dde", Dde_Init, Dde_SafeInit);
#endif
#ifdef TCL_TEST
if (Tcltest_Init(interp) == TCL_ERROR) {
return TCL_ERROR;
}
Tcl_StaticLibrary(interp, "Tcltest", Tcltest_Init, Tcltest_SafeInit);
#endif /* TCL_TEST */
/*
* Call the init procedures for included packages. Each call should look
* like this:
*
* if (Mod_Init(interp) == TCL_ERROR) {
* return TCL_ERROR;
* }
*
* where "Mod" is the name of the module. (Dynamically-loadable packages
* should have the same entry-point name.)
*/
/*
* Call Tcl_CreateObjCommand for application-specific commands, if they
* weren't already created by the init procedures called above.
*/
/*
* Specify a user-specific startup file to invoke if the application is
* run interactively. Typically the startup file is "~/.apprc" where "app"
* is the name of the application. If this line is deleted then no
* user-specific startup file will be run under any conditions.
*/
(void)Tcl_EvalEx(interp,
"set tcl_rcFileName [file tildeexpand ~/tclshrc.tcl]",
TCL_AUTO_LENGTH, TCL_EVAL_GLOBAL);
return TCL_OK;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

176
vendor/tcl/win/tclConfig.sh.in vendored Normal file
View file

@ -0,0 +1,176 @@
# tclConfig.sh --
#
# This shell script (for sh) is generated automatically by Tcl's
# configure script. It will create shell variables for most of
# the configuration options discovered by the configure script.
# This script is intended to be included by the configure scripts
# for Tcl extensions so that they don't have to figure this all
# out for themselves.
#
# The information in this file is specific to a single platform.
TCL_DLL_FILE="@TCL_DLL_FILE@"
# Tcl's version number.
TCL_VERSION='@TCL_VERSION@'
TCL_MAJOR_VERSION='@TCL_MAJOR_VERSION@'
TCL_MINOR_VERSION='@TCL_MINOR_VERSION@'
TCL_PATCH_LEVEL='@TCL_PATCH_LEVEL@'
# C compiler to use for compilation.
TCL_CC='@CC@'
# -D flags for use with the C compiler.
TCL_DEFS='@DEFS@'
# Default flags used in an optimized and debuggable build, respectively.
TCL_CFLAGS_DEBUG='@CFLAGS_DEBUG@'
TCL_CFLAGS_OPTIMIZE='@CFLAGS_OPTIMIZE@'
# Default linker flags used in an optimized and debuggable build, respectively.
TCL_LDFLAGS_DEBUG='@LDFLAGS_DEBUG@'
TCL_LDFLAGS_OPTIMIZE='@LDFLAGS_OPTIMIZE@'
# Flag, 1: we built a shared lib, 0 we didn't
TCL_SHARED_BUILD=@TCL_SHARED_BUILD@
# The name of the Tcl library (may be either a .a file or a shared library):
TCL_LIB_FILE='@TCL_LIB_FILE@'
# The name of a zip containing the /library and /encodings (may be either a .zip file or a shared library):
TCL_ZIP_FILE='@TCL_ZIP_FILE@'
# Flag to indicate whether shared libraries need export files.
TCL_NEEDS_EXP_FILE=''
# Additional libraries to use when linking Tcl.
TCL_LIBS='@LIBS@'
# Top-level directory in which Tcl's platform-independent files are
# installed.
TCL_PREFIX='@prefix@'
# Top-level directory in which Tcl's platform-specific files (e.g.
# executables) are installed.
TCL_EXEC_PREFIX='@exec_prefix@'
# Flags to pass to cc when compiling the components of a shared library:
TCL_SHLIB_CFLAGS='@SHLIB_CFLAGS@'
# Flags to pass to cc to get warning messages
TCL_CFLAGS_WARNING='@CFLAGS_WARNING@'
# Extra flags to pass to cc:
TCL_EXTRA_CFLAGS='@EXTRA_CFLAGS@'
# Base command to use for combining object files into a shared library:
TCL_SHLIB_LD='@SHLIB_LD@'
# Base command to use for combining object files into a static library:
TCL_STLIB_LD='@STLIB_LD@'
# Either '$LIBS' (if dependent libraries should be included when linking
# shared libraries) or an empty string. See Tcl's configure.ac for more
# explanation.
TCL_SHLIB_LD_LIBS='@SHLIB_LD_LIBS@'
# Suffix to use for the name of a shared library.
TCL_SHLIB_SUFFIX='@SHLIB_SUFFIX@'
# Library file(s) to include in tclsh and other base applications
# in order to provide facilities needed by DLOBJ above.
TCL_DL_LIBS=''
# Flags to pass to the compiler when linking object files into
# an executable tclsh or tcltest binary.
TCL_LD_FLAGS='@LDFLAGS@'
# Flags to pass to cc/ld, such as "-R /usr/local/tcl/lib", that tell the
# run-time dynamic linker where to look for shared libraries such as
# libtcl.so. Used when linking applications. Only works if there
# is a variable "LIB_RUNTIME_DIR" defined in the Makefile.
TCL_CC_SEARCH_FLAGS=''
TCL_LD_SEARCH_FLAGS=''
# Additional object files linked with Tcl to provide compatibility
# with standard facilities from ANSI C or POSIX.
TCL_COMPAT_OBJS='@LIBOBJS@'
# Name of the ranlib program to use.
TCL_RANLIB='@RANLIB@'
# -l flag to pass to the linker to pick up the Tcl library
TCL_LIB_FLAG='@TCL_LIB_FLAG@'
# String to pass to linker to pick up the Tcl library from its
# build directory.
TCL_BUILD_LIB_SPEC='@TCL_BUILD_LIB_SPEC@'
# String to pass to linker to pick up the Tcl library from its
# installed directory.
TCL_LIB_SPEC='@TCL_LIB_SPEC@'
# String to pass to the compiler so that an extension can
# find installed Tcl headers.
TCL_INCLUDE_SPEC='@TCL_INCLUDE_SPEC@'
# Indicates whether a version numbers should be used in -l switches
# ("ok" means it's safe to use switches like -ltcl7.5; "nodots" means
# use switches like -ltcl75). SunOS and FreeBSD require "nodots", for
# example.
TCL_LIB_VERSIONS_OK='nodots'
# String that can be evaluated to generate the part of a shared library
# name that comes after the "libxxx" (includes version number, if any,
# extension, and anything else needed). May depend on the variables
# VERSION and SHLIB_SUFFIX. On most UNIX systems this is
# ${VERSION}${SHLIB_SUFFIX}.
TCL_SHARED_LIB_SUFFIX='@CFG_TCL_SHARED_LIB_SUFFIX@'
# String that can be evaluated to generate the part of an unshared library
# name that comes after the "libxxx" (includes version number, if any,
# extension, and anything else needed). May depend on the variable
# VERSION. On most UNIX systems this is ${VERSION}.a.
TCL_UNSHARED_LIB_SUFFIX='@CFG_TCL_UNSHARED_LIB_SUFFIX@'
# Location of the top-level source directory from which Tcl was built.
# This is the directory that contains a README file as well as
# subdirectories such as generic, unix, etc. If Tcl was compiled in a
# different place than the directory containing the source files, this
# points to the location of the sources, not the location where Tcl was
# compiled.
TCL_SRC_DIR='@TCL_SRC_DIR@'
# List of standard directories in which to look for packages during
# "package require" commands. Contains the "prefix" directory plus also
# the "exec_prefix" directory, if it is different.
TCL_PACKAGE_PATH='@TCL_PACKAGE_PATH@'
# Tcl supports stub.
TCL_SUPPORTS_STUBS=1
# The name of the Tcl stub library (.a):
TCL_STUB_LIB_FILE='@TCL_STUB_LIB_FILE@'
# -l flag to pass to the linker to pick up the Tcl stub library
TCL_STUB_LIB_FLAG='@TCL_STUB_LIB_FLAG@'
# String to pass to linker to pick up the Tcl stub library from its
# build directory.
TCL_BUILD_STUB_LIB_SPEC='@TCL_BUILD_STUB_LIB_SPEC@'
# String to pass to linker to pick up the Tcl stub library from its
# installed directory.
TCL_STUB_LIB_SPEC='@TCL_STUB_LIB_SPEC@'
# Path to the Tcl stub library in the build directory.
TCL_BUILD_STUB_LIB_PATH='@TCL_BUILD_STUB_LIB_PATH@'
# Path to the Tcl stub library in the install directory.
TCL_STUB_LIB_PATH='@TCL_STUB_LIB_PATH@'
# Name of the zlib library that extensions should use
TCL_ZLIB_LIB_NAME='@TCL_ZLIB_LIB_NAME@'
# Name of the tommath library that extensions should use
TCL_TOMMATH_LIB_NAME='@TCL_TOMMATH_LIB_NAME@'

1
vendor/tcl/win/tclUuid.h.in vendored Normal file
View file

@ -0,0 +1 @@
#define TCL_VERSION_UUID \

532
vendor/tcl/win/tclWin32Dll.c vendored Normal file
View file

@ -0,0 +1,532 @@
/*
* tclWin32Dll.c --
*
* This file contains the DLL entry point and other low-level bit bashing
* code that needs inline assembly.
*
* Copyright © 1995-1996 Sun Microsystems, Inc.
* Copyright © 1998-2000 Scriptics Corporation.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclWinInt.h"
#if defined(HAVE_CPUID_H)
# include <cpuid.h>
#elif defined(_MSC_VER)
# include <intrin.h>
#endif
/*
* The following variables keep track of information about this DLL on a
* per-instance basis. Each time this DLL is loaded, it gets its own new data
* segment with its own copy of all static and global information.
*/
static HINSTANCE hInstance; /* HINSTANCE of this DLL. */
#if defined(__GNUC__)
/*
* Need to add noinline flag to DllMain declaration so that gcc -O3 does not
* inline asm code into DllEntryPoint and cause a compile time error because
* of redefined local labels.
*/
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason,
LPVOID reserved) __attribute__ ((noinline));
#else /* !__GNUC__ */
/*
* The following declaration is for the VC++ DLL entry point.
*/
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason,
LPVOID reserved);
#endif /* __GNUC__ */
/*
* The following structure and linked list is to allow us to map between
* volume mount points and drive letters on the fly (no Win API exists for
* this).
*/
typedef struct MountPointMap {
WCHAR *volumeName; /* Native wide string volume name. */
WCHAR driveLetter; /* Drive letter corresponding to the volume
* name. */
struct MountPointMap *nextPtr;
/* Pointer to next structure in list, or
* NULL. */
} MountPointMap;
/*
* This is the head of the linked list, which is protected by the mutex which
* follows, for thread-enabled builds.
*/
MountPointMap *driveLetterLookup = NULL;
TCL_DECLARE_MUTEX(mountPointMap)
/*
* We will need this below.
*/
#ifdef _WIN32
#ifndef STATIC_BUILD
/*
*----------------------------------------------------------------------
*
* DllEntryPoint --
*
* This wrapper function is used by Borland to invoke the initialization
* code for Tcl. It simply calls the DllMain routine.
*
* Results:
* See DllMain.
*
* Side effects:
* See DllMain.
*
*----------------------------------------------------------------------
*/
BOOL APIENTRY
DllEntryPoint(
HINSTANCE hInst, /* Library instance handle. */
DWORD reason, /* Reason this function is being called. */
LPVOID reserved)
{
return DllMain(hInst, reason, reserved);
}
/*
*----------------------------------------------------------------------
*
* DllMain --
*
* This routine is called by the VC++ C run time library init code, or
* the DllEntryPoint routine. It is responsible for initializing various
* dynamically loaded libraries.
*
* Results:
* TRUE on sucess, FALSE on failure.
*
* Side effects:
* Initializes most rudimentary Windows bits.
*
*----------------------------------------------------------------------
*/
BOOL APIENTRY
DllMain(
HINSTANCE hInst, /* Library instance handle. */
DWORD reason, /* Reason this function is being called. */
TCL_UNUSED(LPVOID))
{
switch (reason) {
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hInst);
TclWinInit(hInst);
return TRUE;
/*
* DLL_PROCESS_DETACH is unnecessary as the user should call
* Tcl_Finalize explicitly before unloading Tcl.
*/
}
return TRUE;
}
#endif /* !STATIC_BUILD */
#endif /* _WIN32 */
/*
*----------------------------------------------------------------------
*
* TclWinGetTclInstance --
*
* Retrieves the global library instance handle.
*
* Results:
* Returns the global library instance handle.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void *
TclWinGetTclInstance(void)
{
return hInstance;
}
/*
*----------------------------------------------------------------------
*
* TclWinInit --
*
* This function initializes the internal state of the tcl library.
*
* Results:
* None.
*
* Side effects:
* Initializes the tclPlatformId variable.
*
*----------------------------------------------------------------------
*/
void
TclWinInit(
HINSTANCE hInst) /* Library instance handle. */
{
OSVERSIONINFOW os;
hInstance = hInst;
os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
GetVersionExW(&os);
/*
* We no longer support Win32s or Win9x or Windows CE or Windows XP, so just
* in case someone manages to get a runtime there, make sure they know that.
*/
if (os.dwPlatformId != VER_PLATFORM_WIN32_NT) {
Tcl_Panic("Windows 7 is the minimum supported platform");
}
}
/*
*-------------------------------------------------------------------------
*
* TclWinNoBackslash --
*
* We're always iterating through a string in Windows, changing the
* backslashes to slashes for use in Tcl.
*
* Results:
* All backslashes in given string are changed to slashes.
*
* Side effects:
* None.
*
*-------------------------------------------------------------------------
*/
char *
TclWinNoBackslash(
char *path) /* String to change. */
{
char *p;
for (p = path; *p != '\0'; p++) {
if (*p == '\\') {
*p = '/';
}
}
return path;
}
/*
*---------------------------------------------------------------------------
*
* TclWinEncodingsCleanup --
*
* Called during finalization to clean up any memory allocated in our
* mount point map which is used to follow certain kinds of symlinks.
*
* Results:
* None.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
void
TclWinEncodingsCleanup(void)
{
MountPointMap *dlIter, *dlIter2;
/*
* Clean up the mount point map.
*/
Tcl_MutexLock(&mountPointMap);
dlIter = driveLetterLookup;
while (dlIter != NULL) {
dlIter2 = dlIter->nextPtr;
Tcl_Free(dlIter->volumeName);
Tcl_Free(dlIter);
dlIter = dlIter2;
}
Tcl_MutexUnlock(&mountPointMap);
}
/*
*--------------------------------------------------------------------
*
* TclWinDriveLetterForVolMountPoint
*
* Unfortunately, Windows provides no easy way at all to get hold of the
* drive letter for a volume mount point, but we need that information to
* understand paths correctly. So, we have to build an associated array
* to find these correctly, and allow quick and easy lookup from volume
* mount points to drive letters.
*
* We assume here that we are running on a system for which the wide
* character interfaces are used, which is valid for Win 2000 and WinXP
* which are the only systems on which this function will ever be called.
*
* Result:
* The drive letter, or -1 if no drive letter corresponds to the given
* mount point.
*
*--------------------------------------------------------------------
*/
char
TclWinDriveLetterForVolMountPoint(
const WCHAR *mountPoint)
{
MountPointMap *dlIter, *dlPtr2;
WCHAR Target[55]; /* Target of mount at mount point */
WCHAR drive[4] = L"A:\\";
/*
* Detect the volume mounted there. Unfortunately, there is no simple way
* to map a unique volume name to a DOS drive letter. So, we have to build
* an associative array.
*/
Tcl_MutexLock(&mountPointMap);
dlIter = driveLetterLookup;
while (dlIter != NULL) {
if (wcscmp(dlIter->volumeName, mountPoint) == 0) {
/*
* We need to check whether this information is still valid, since
* either the user or various programs could have adjusted the
* mount points on the fly.
*/
drive[0] = (WCHAR) dlIter->driveLetter;
/*
* Try to read the volume mount point and see where it points.
*/
if (GetVolumeNameForVolumeMountPointW(drive,
Target, 55) != 0) {
if (wcscmp(dlIter->volumeName, Target) == 0) {
/*
* Nothing has changed.
*/
Tcl_MutexUnlock(&mountPointMap);
return (char) dlIter->driveLetter;
}
}
/*
* If we reach here, unfortunately, this mount point is no longer
* valid at all.
*/
if (driveLetterLookup == dlIter) {
dlPtr2 = dlIter;
driveLetterLookup = dlIter->nextPtr;
} else {
for (dlPtr2 = driveLetterLookup;
dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
if (dlPtr2->nextPtr == dlIter) {
dlPtr2->nextPtr = dlIter->nextPtr;
dlPtr2 = dlIter;
break;
}
}
}
/*
* Now dlPtr2 points to the structure to free.
*/
Tcl_Free(dlPtr2->volumeName);
Tcl_Free(dlPtr2);
/*
* Restart the loop - we could try to be clever and continue half
* way through, but the logic is a bit messy, so it's cleanest
* just to restart.
*/
dlIter = driveLetterLookup;
continue;
}
dlIter = dlIter->nextPtr;
}
/*
* We couldn't find it, so we must iterate over the letters.
*/
for (drive[0] = 'A'; drive[0] <= 'Z'; drive[0]++) {
/*
* Try to read the volume mount point and see where it points.
*/
if (GetVolumeNameForVolumeMountPointW(drive,
Target, 55) != 0) {
int alreadyStored = 0;
for (dlIter = driveLetterLookup; dlIter != NULL;
dlIter = dlIter->nextPtr) {
if (wcscmp(dlIter->volumeName, Target) == 0) {
alreadyStored = 1;
break;
}
}
if (!alreadyStored) {
dlPtr2 = (MountPointMap *)Tcl_Alloc(sizeof(MountPointMap));
dlPtr2->volumeName = (WCHAR *)TclNativeDupInternalRep(Target);
dlPtr2->driveLetter = (WCHAR) drive[0];
dlPtr2->nextPtr = driveLetterLookup;
driveLetterLookup = dlPtr2;
}
}
}
/*
* Try again.
*/
for (dlIter = driveLetterLookup; dlIter != NULL;
dlIter = dlIter->nextPtr) {
if (wcscmp(dlIter->volumeName, mountPoint) == 0) {
Tcl_MutexUnlock(&mountPointMap);
return (char) dlIter->driveLetter;
}
}
/*
* The volume doesn't appear to correspond to a drive letter - we remember
* that fact and store '-1' so we don't have to look it up each time.
*/
dlPtr2 = (MountPointMap *)Tcl_Alloc(sizeof(MountPointMap));
dlPtr2->volumeName = (WCHAR *)TclNativeDupInternalRep((void *)mountPoint);
dlPtr2->driveLetter = (WCHAR)-1;
dlPtr2->nextPtr = driveLetterLookup;
driveLetterLookup = dlPtr2;
Tcl_MutexUnlock(&mountPointMap);
return -1;
}
/*
*------------------------------------------------------------------------
*
* TclWinCPUID --
*
* Get CPU ID information on an Intel box under Windows
*
* Results:
* Returns TCL_OK if successful, TCL_ERROR if CPUID is not supported or
* fails.
*
* Side effects:
* If successful, stores EAX, EBX, ECX and EDX registers after the CPUID
* instruction in the four integers designated by 'regsPtr'
*
*----------------------------------------------------------------------
*/
int
TclWinCPUID(
int index, /* Which CPUID value to retrieve. */
int *regsPtr) /* Registers after the CPUID. */
{
int status = TCL_ERROR;
#if defined(HAVE_CPUID_H)
unsigned int *regs = (unsigned int *)regsPtr;
__get_cpuid(index, &regs[0], &regs[1], &regs[2], &regs[3]);
status = TCL_OK;
#elif defined(_MSC_VER) && defined(_WIN64) && defined(HAVE_CPUID)
__cpuid((int *)regsPtr, index);
status = TCL_OK;
#elif defined (_M_IX86)
/*
* Define a structure in the stack frame to hold the registers.
*/
struct {
DWORD dw0;
DWORD dw1;
DWORD dw2;
DWORD dw3;
} regs;
regs.dw0 = index;
/*
* Execute the CPUID instruction and save regs in the stack frame.
*/
_try {
_asm {
push ebx
push ecx
push edx
mov eax, regs.dw0
cpuid
mov regs.dw0, eax
mov regs.dw1, ebx
mov regs.dw2, ecx
mov regs.dw3, edx
pop edx
pop ecx
pop ebx
}
/*
* Copy regs back out to the caller.
*/
regsPtr[0] = regs.dw0;
regsPtr[1] = regs.dw1;
regsPtr[2] = regs.dw2;
regsPtr[3] = regs.dw3;
status = TCL_OK;
} __except(EXCEPTION_EXECUTE_HANDLER) {
/* do nothing */
}
#else
(void)index;
(void)regsPtr;
/*
* Don't know how to do assembly code for this compiler and/or
* architecture.
*/
#endif
return status;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

1760
vendor/tcl/win/tclWinChan.c vendored Normal file

File diff suppressed because it is too large Load diff

2451
vendor/tcl/win/tclWinConsole.c vendored Normal file

File diff suppressed because it is too large Load diff

1990
vendor/tcl/win/tclWinDde.c vendored Normal file

File diff suppressed because it is too large Load diff

425
vendor/tcl/win/tclWinError.c vendored Normal file
View file

@ -0,0 +1,425 @@
/*
* tclWinError.c --
*
* This file contains code for converting from Win32 errors to errno
* errors.
*
* Copyright © 1995-1996 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
/*
* The following table contains the mapping from Win32 errors to errno errors.
*/
static const unsigned char errorTable[] = {
0,
EINVAL, /* ERROR_INVALID_FUNCTION 1 */
ENOENT, /* ERROR_FILE_NOT_FOUND 2 */
ENOENT, /* ERROR_PATH_NOT_FOUND 3 */
EMFILE, /* ERROR_TOO_MANY_OPEN_FILES 4 */
EACCES, /* ERROR_ACCESS_DENIED 5 */
EBADF, /* ERROR_INVALID_HANDLE 6 */
ENOMEM, /* ERROR_ARENA_TRASHED 7 */
ENOMEM, /* ERROR_NOT_ENOUGH_MEMORY 8 */
ENOMEM, /* ERROR_INVALID_BLOCK 9 */
E2BIG, /* ERROR_BAD_ENVIRONMENT 10 */
ENOEXEC, /* ERROR_BAD_FORMAT 11 */
EACCES, /* ERROR_INVALID_ACCESS 12 */
EINVAL, /* ERROR_INVALID_DATA 13 */
ENOMEM, /* ERROR_OUT_OF_MEMORY 14 */
ENOENT, /* ERROR_INVALID_DRIVE 15 */
EACCES, /* ERROR_CURRENT_DIRECTORY 16 */
EXDEV, /* ERROR_NOT_SAME_DEVICE 17 */
ENOENT, /* ERROR_NO_MORE_FILES 18 */
EROFS, /* ERROR_WRITE_PROTECT 19 */
ENXIO, /* ERROR_BAD_UNIT 20 */
EBUSY, /* ERROR_NOT_READY 21 */
EIO, /* ERROR_BAD_COMMAND 22 */
EIO, /* ERROR_CRC 23 */
EIO, /* ERROR_BAD_LENGTH 24 */
EIO, /* ERROR_SEEK 25 */
EIO, /* ERROR_NOT_DOS_DISK 26 */
ENXIO, /* ERROR_SECTOR_NOT_FOUND 27 */
EBUSY, /* ERROR_OUT_OF_PAPER 28 */
EIO, /* ERROR_WRITE_FAULT 29 */
EIO, /* ERROR_READ_FAULT 30 */
EIO, /* ERROR_GEN_FAILURE 31 */
EACCES, /* ERROR_SHARING_VIOLATION 32 */
EACCES, /* ERROR_LOCK_VIOLATION 33 */
ENXIO, /* ERROR_WRONG_DISK 34 */
ENFILE, /* ERROR_FCB_UNAVAILABLE 35 */
ENFILE, /* ERROR_SHARING_BUFFER_EXCEEDED 36 */
EINVAL, /* 37 */
EINVAL, /* 38 */
ENOSPC, /* ERROR_HANDLE_DISK_FULL 39 */
EINVAL, /* 40 */
EINVAL, /* 41 */
EINVAL, /* 42 */
EINVAL, /* 43 */
EINVAL, /* 44 */
EINVAL, /* 45 */
EINVAL, /* 46 */
EINVAL, /* 47 */
EINVAL, /* 48 */
EINVAL, /* 49 */
ENODEV, /* ERROR_NOT_SUPPORTED 50 */
EBUSY, /* ERROR_REM_NOT_LIST 51 */
EEXIST, /* ERROR_DUP_NAME 52 */
ENOENT, /* ERROR_BAD_NETPATH 53 */
EBUSY, /* ERROR_NETWORK_BUSY 54 */
ENODEV, /* ERROR_DEV_NOT_EXIST 55 */
EAGAIN, /* ERROR_TOO_MANY_CMDS 56 */
EIO, /* ERROR_ADAP_HDW_ERR 57 */
EIO, /* ERROR_BAD_NET_RESP 58 */
EIO, /* ERROR_UNEXP_NET_ERR 59 */
EINVAL, /* ERROR_BAD_REM_ADAP 60 */
EFBIG, /* ERROR_PRINTQ_FULL 61 */
ENOSPC, /* ERROR_NO_SPOOL_SPACE 62 */
ENOENT, /* ERROR_PRINT_CANCELLED 63 */
ENOENT, /* ERROR_NETNAME_DELETED 64 */
EACCES, /* ERROR_NETWORK_ACCESS_DENIED 65 */
ENODEV, /* ERROR_BAD_DEV_TYPE 66 */
ENOENT, /* ERROR_BAD_NET_NAME 67 */
ENFILE, /* ERROR_TOO_MANY_NAMES 68 */
EIO, /* ERROR_TOO_MANY_SESS 69 */
EAGAIN, /* ERROR_SHARING_PAUSED 70 */
EINVAL, /* ERROR_REQ_NOT_ACCEP 71 */
EAGAIN, /* ERROR_REDIR_PAUSED 72 */
EINVAL, /* 73 */
EINVAL, /* 74 */
EINVAL, /* 75 */
EINVAL, /* 76 */
EINVAL, /* 77 */
EINVAL, /* 78 */
EINVAL, /* 79 */
EEXIST, /* ERROR_FILE_EXISTS 80 */
EINVAL, /* 81 */
ENOSPC, /* ERROR_CANNOT_MAKE 82 */
EIO, /* ERROR_FAIL_I24 83 */
ENFILE, /* ERROR_OUT_OF_STRUCTURES 84 */
EEXIST, /* ERROR_ALREADY_ASSIGNED 85 */
EPERM, /* ERROR_INVALID_PASSWORD 86 */
EINVAL, /* ERROR_INVALID_PARAMETER 87 */
EIO, /* ERROR_NET_WRITE_FAULT 88 */
EAGAIN, /* ERROR_NO_PROC_SLOTS 89 */
EINVAL, /* 90 */
EINVAL, /* 91 */
EINVAL, /* 92 */
EINVAL, /* 93 */
EINVAL, /* 94 */
EINVAL, /* 95 */
EINVAL, /* 96 */
EINVAL, /* 97 */
EINVAL, /* 98 */
EINVAL, /* 99 */
EINVAL, /* 100 */
EINVAL, /* 101 */
EINVAL, /* 102 */
EINVAL, /* 103 */
EINVAL, /* 104 */
EINVAL, /* 105 */
EINVAL, /* 106 */
EXDEV, /* ERROR_DISK_CHANGE 107 */
EAGAIN, /* ERROR_DRIVE_LOCKED 108 */
EPIPE, /* ERROR_BROKEN_PIPE 109 */
ENOENT, /* ERROR_OPEN_FAILED 110 */
EINVAL, /* ERROR_BUFFER_OVERFLOW 111 */
ENOSPC, /* ERROR_DISK_FULL 112 */
EMFILE, /* ERROR_NO_MORE_SEARCH_HANDLES 113 */
EBADF, /* ERROR_INVALID_TARGET_HANDLE 114 */
EFAULT, /* ERROR_PROTECTION_VIOLATION 115 */
EINVAL, /* 116 */
EINVAL, /* 117 */
EINVAL, /* 118 */
EINVAL, /* 119 */
EINVAL, /* 120 */
EINVAL, /* 121 */
EINVAL, /* 122 */
ENOENT, /* ERROR_INVALID_NAME 123 */
EINVAL, /* 124 */
EINVAL, /* 125 */
EINVAL, /* 126 */
EINVAL, /* ERROR_PROC_NOT_FOUND 127 */
ECHILD, /* ERROR_WAIT_NO_CHILDREN 128 */
ECHILD, /* ERROR_CHILD_NOT_COMPLETE 129 */
EBADF, /* ERROR_DIRECT_ACCESS_HANDLE 130 */
EINVAL, /* ERROR_NEGATIVE_SEEK 131 */
ESPIPE, /* ERROR_SEEK_ON_DEVICE 132 */
EINVAL, /* 133 */
EINVAL, /* 134 */
EINVAL, /* 135 */
EINVAL, /* 136 */
EINVAL, /* 137 */
EINVAL, /* 138 */
EINVAL, /* 139 */
EINVAL, /* 140 */
EINVAL, /* 141 */
EAGAIN, /* ERROR_BUSY_DRIVE 142 */
EINVAL, /* 143 */
EINVAL, /* 144 */
EEXIST, /* ERROR_DIR_NOT_EMPTY 145 */
EINVAL, /* 146 */
EINVAL, /* 147 */
EINVAL, /* 148 */
EINVAL, /* 149 */
EINVAL, /* 150 */
EINVAL, /* 151 */
EINVAL, /* 152 */
EINVAL, /* 153 */
EINVAL, /* 154 */
EINVAL, /* 155 */
EINVAL, /* 156 */
EINVAL, /* 157 */
EACCES, /* ERROR_NOT_LOCKED 158 */
EINVAL, /* 159 */
EINVAL, /* 160 */
ENOENT, /* ERROR_BAD_PATHNAME 161 */
EINVAL, /* 162 */
EINVAL, /* 163 */
EINVAL, /* 164 */
EINVAL, /* 165 */
EINVAL, /* 166 */
EACCES, /* ERROR_LOCK_FAILED 167 */
EINVAL, /* 168 */
EINVAL, /* 169 */
EINVAL, /* 170 */
EINVAL, /* 171 */
EINVAL, /* 172 */
EINVAL, /* 173 */
EINVAL, /* 174 */
EINVAL, /* 175 */
EINVAL, /* 176 */
EINVAL, /* 177 */
EINVAL, /* 178 */
EINVAL, /* 179 */
EINVAL, /* 180 */
EINVAL, /* 181 */
EINVAL, /* 182 */
EEXIST, /* ERROR_ALREADY_EXISTS 183 */
ECHILD, /* ERROR_NO_CHILD_PROCESS 184 */
EINVAL, /* 185 */
EINVAL, /* 186 */
EINVAL, /* 187 */
EINVAL, /* 188 */
EINVAL, /* 189 */
EINVAL, /* 190 */
EINVAL, /* 191 */
EINVAL, /* 192 */
EINVAL, /* 193 */
EINVAL, /* 194 */
EINVAL, /* 195 */
EINVAL, /* 196 */
EINVAL, /* 197 */
EINVAL, /* 198 */
EINVAL, /* 199 */
EINVAL, /* 200 */
EINVAL, /* 201 */
EINVAL, /* 202 */
EINVAL, /* 203 */
EINVAL, /* 204 */
EINVAL, /* 205 */
ENAMETOOLONG,/* ERROR_FILENAME_EXCED_RANGE 206 */
EINVAL, /* 207 */
EINVAL, /* 208 */
EINVAL, /* 209 */
EINVAL, /* 210 */
EINVAL, /* 211 */
EINVAL, /* 212 */
EINVAL, /* 213 */
EINVAL, /* 214 */
EINVAL, /* 215 */
EINVAL, /* 216 */
EINVAL, /* 217 */
EINVAL, /* 218 */
EINVAL, /* 219 */
EINVAL, /* 220 */
EINVAL, /* 221 */
EINVAL, /* 222 */
EINVAL, /* 223 */
EINVAL, /* 224 */
EINVAL, /* 225 */
EINVAL, /* 226 */
EINVAL, /* 227 */
EINVAL, /* 228 */
EINVAL, /* 229 */
EPIPE, /* ERROR_BAD_PIPE 230 */
EAGAIN, /* ERROR_PIPE_BUSY 231 */
EPIPE, /* ERROR_NO_DATA 232 */
EPIPE, /* ERROR_PIPE_NOT_CONNECTED 233 */
EINVAL, /* 234 */
EINVAL, /* 235 */
EINVAL, /* 236 */
EINVAL, /* 237 */
EINVAL, /* 238 */
EINVAL, /* 239 */
EINVAL, /* 240 */
EINVAL, /* 241 */
EINVAL, /* 242 */
EINVAL, /* 243 */
EINVAL, /* 244 */
EINVAL, /* 245 */
EINVAL, /* 246 */
EINVAL, /* 247 */
EINVAL, /* 248 */
EINVAL, /* 249 */
EINVAL, /* 250 */
EINVAL, /* 251 */
EINVAL, /* 252 */
EINVAL, /* 253 */
EINVAL, /* 254 */
EINVAL, /* 255 */
EINVAL, /* 256 */
EINVAL, /* 257 */
EINVAL, /* 258 */
EINVAL, /* 259 */
EINVAL, /* 260 */
EINVAL, /* 261 */
EINVAL, /* 262 */
EINVAL, /* 263 */
EINVAL, /* 264 */
EINVAL, /* 265 */
EINVAL, /* 266 */
ENOTDIR /* ERROR_DIRECTORY 267 */
};
/*
* The following table contains the mapping from WinSock errors to
* errno errors.
*/
static const unsigned char wsaErrorTable[] = {
EWOULDBLOCK, /* WSAEWOULDBLOCK */
EINPROGRESS, /* WSAEINPROGRESS */
EALREADY, /* WSAEALREADY */
ENOTSOCK, /* WSAENOTSOCK */
EDESTADDRREQ, /* WSAEDESTADDRREQ */
EMSGSIZE, /* WSAEMSGSIZE */
EPROTOTYPE, /* WSAEPROTOTYPE */
ENOPROTOOPT, /* WSAENOPROTOOPT */
EPROTONOSUPPORT, /* WSAEPROTONOSUPPORT */
ESOCKTNOSUPPORT, /* WSAESOCKTNOSUPPORT */
EOPNOTSUPP, /* WSAEOPNOTSUPP */
EPFNOSUPPORT, /* WSAEPFNOSUPPORT */
EAFNOSUPPORT, /* WSAEAFNOSUPPORT */
EADDRINUSE, /* WSAEADDRINUSE */
EADDRNOTAVAIL, /* WSAEADDRNOTAVAIL */
ENETDOWN, /* WSAENETDOWN */
ENETUNREACH, /* WSAENETUNREACH */
ENETRESET, /* WSAENETRESET */
ECONNABORTED, /* WSAECONNABORTED */
ECONNRESET, /* WSAECONNRESET */
ENOBUFS, /* WSAENOBUFS */
EISCONN, /* WSAEISCONN */
ENOTCONN, /* WSAENOTCONN */
ESHUTDOWN, /* WSAESHUTDOWN */
ETOOMANYREFS, /* WSAETOOMANYREFS */
ETIMEDOUT, /* WSAETIMEDOUT */
ECONNREFUSED, /* WSAECONNREFUSED */
ELOOP, /* WSAELOOP */
ENAMETOOLONG, /* WSAENAMETOOLONG */
EHOSTDOWN, /* WSAEHOSTDOWN */
EHOSTUNREACH, /* WSAEHOSTUNREACH */
ENOTEMPTY, /* WSAENOTEMPTY */
EAGAIN, /* WSAEPROCLIM */
EUSERS, /* WSAEUSERS */
EDQUOT, /* WSAEDQUOT */
ESTALE, /* WSAESTALE */
EREMOTE /* WSAEREMOTE */
};
/*
*----------------------------------------------------------------------
*
* Tcl_WinConvertError --
*
* This routine converts a Win32 error into an errno value.
*
* Results:
* None.
*
* Side effects:
* Sets the errno global variable.
*
*----------------------------------------------------------------------
*/
void
Tcl_WinConvertError(
unsigned errCode) /* Win32 error code. */
{
if ((unsigned)errCode >= sizeof(errorTable)/sizeof(errorTable[0])) {
errCode -= WSAEWOULDBLOCK;
if ((unsigned)errCode >= sizeof(wsaErrorTable)/sizeof(wsaErrorTable[0])) {
Tcl_SetErrno(errorTable[1]);
} else {
Tcl_SetErrno(wsaErrorTable[errCode]);
}
} else {
Tcl_SetErrno(errorTable[errCode]);
}
}
#ifdef __CYGWIN__
/*
*----------------------------------------------------------------------
*
* tclWinDebugPanic --
*
* Display a message. If a debugger is present, present it directly to
* the debugger, otherwise send it to stderr.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
MODULE_SCOPE void
tclWinDebugPanic(
const char *format, ...)
{
#define TCL_MAX_WARN_LEN 1024
va_list argList;
va_start(argList, format);
if (IsDebuggerPresent()) {
WCHAR msgString[TCL_MAX_WARN_LEN];
char buf[TCL_MAX_WARN_LEN * 3];
vsnprintf(buf, sizeof(buf), format, argList);
msgString[TCL_MAX_WARN_LEN-1] = '\0';
MultiByteToWideChar(CP_UTF8, 0, buf, -1, msgString, TCL_MAX_WARN_LEN);
/*
* Truncate MessageBox string if it is too long to not overflow the buffer.
*/
if (msgString[TCL_MAX_WARN_LEN-1] != '\0') {
memcpy(msgString + (TCL_MAX_WARN_LEN - 5), L" ...", 5 * sizeof(WCHAR));
}
OutputDebugStringW(msgString);
} else {
if (!isatty(fileno(stderr))) {
fprintf(stderr, "\xEF\xBB\xBF");
}
vfprintf(stderr, format, argList);
fprintf(stderr, "\n");
fflush(stderr);
}
}
#endif
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* tab-width: 8
* End:
*/

2104
vendor/tcl/win/tclWinFCmd.c vendored Normal file

File diff suppressed because it is too large Load diff

3367
vendor/tcl/win/tclWinFile.c vendored Normal file

File diff suppressed because it is too large Load diff

786
vendor/tcl/win/tclWinInit.c vendored Normal file
View file

@ -0,0 +1,786 @@
/*
* tclWinInit.c --
*
* Contains the Windows-specific interpreter initialization functions.
*
* Copyright © 1994-1997 Sun Microsystems, Inc.
* Copyright © 1998-1999 Scriptics Corporation.
* All rights reserved.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclWinInt.h"
#include <winnt.h>
#include <winbase.h>
#include <lmcons.h>
#if defined (__clang__) && (__clang_major__ > 20)
#pragma clang diagnostic ignored "-Wc++-keyword"
#endif
/*
* GetUserNameW() is found in advapi32.dll
*/
#ifdef _MSC_VER
# pragma comment(lib, "advapi32.lib")
#endif
/*
* The following declaration is a workaround for some Microsoft brain damage.
* The SYSTEM_INFO structure is different in various releases, even though the
* layout is the same. So we overlay our own structure on top of it so we can
* access the interesting slots in a uniform way.
*/
typedef struct {
WORD wProcessorArchitecture;
WORD wReserved;
} OemId;
/*
* The following arrays contain the human readable strings for the
* processor values.
*/
#define NUMPROCESSORS 15
static const char *const processors[NUMPROCESSORS] = {
"intel", "mips", "alpha", "ppc", "shx", "arm", "ia64", "alpha64", "msil",
"amd64", "ia32_on_win64", "neutral", "arm64", "arm32_on_win64",
"ia32_on_arm64"
};
/*
* Forward declarations
*/
static TclInitProcessGlobalValueProc InitializeDefaultLibraryDir;
static TclInitProcessGlobalValueProc InitializeSourceLibraryDir;
static void AppendEnvironment(Tcl_Obj *listPtr, const char *lib);
/*
* The default directory in which the init.tcl file is expected to be found.
*/
static ProcessGlobalValue defaultLibraryDir =
{0, 0, NULL, NULL, InitializeDefaultLibraryDir, NULL, NULL};
static ProcessGlobalValue sourceLibraryDir =
{0, 0, NULL, NULL, InitializeSourceLibraryDir, NULL, NULL};
/*
* TclpGetWindowsVersionOnce --
*
* Callback to retrieve Windows version information. To be invoked only
* through InitOnceExecuteOnce for thread safety.
*
* Results:
* None.
*/
static BOOL CALLBACK TclpGetWindowsVersionOnce(
TCL_UNUSED(PINIT_ONCE),
TCL_UNUSED(PVOID),
PVOID *lpContext)
{
typedef int(__stdcall getVersionProc)(void *);
static OSVERSIONINFOW osInfo;
/*
* GetVersionExW will not return the "real" Windows version so use
* RtlGetVersion if available and falling back.
*/
HMODULE handle = GetModuleHandleW(L"NTDLL");
getVersionProc *getVersion =
(getVersionProc *)(void *)GetProcAddress(handle, "RtlGetVersion");
osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
if (getVersion == NULL || getVersion(&osInfo)) {
if (!GetVersionExW(&osInfo)) {
/* Should never happen but ...*/
return FALSE;
}
}
*lpContext = (LPVOID)&osInfo;
return TRUE;
}
/*
* TclpGetWindowsVersion --
*
* Returns a pointer to the OSVERSIONINFOW structure containing the
* version information for the current Windows version.
*
* Results:
* Pointer to OSVERSIONINFOW structure.
*/
static const OSVERSIONINFOW *TclpGetWindowsVersion(void)
{
static INIT_ONCE osInfoOnce = INIT_ONCE_STATIC_INIT;
OSVERSIONINFOW *osInfoPtr = NULL;
BOOL result = InitOnceExecuteOnce(
&osInfoOnce, TclpGetWindowsVersionOnce, NULL, (LPVOID *)&osInfoPtr);
return result ? osInfoPtr : NULL;
}
/*
* TclpGetCodePageOnce --
*
* Callback to retrieve user code page. To be invoked only
* through InitOnceExecuteOnce for thread safety.
*
* Results:
* None.
*/
static BOOL CALLBACK
TclpGetCodePageOnce(
TCL_UNUSED(PINIT_ONCE),
TCL_UNUSED(PVOID),
PVOID *lpContext)
{
static char codePage[20];
codePage[0] = 'c';
codePage[1] = 'p';
DWORD size = sizeof(codePage) - 2;
/*
* When retrieving code page from registry,
* - use ANSI API's since all values will be ASCII and saves conversion
* - use RegGetValue, not RegQueryValueEx, since the latter does not
* guarantee the value is null terminated
* - added bonus, RegGetValue is much more convenient to use
*/
if (RegGetValueA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage",
"ACP", RRF_RT_REG_SZ, NULL, codePage+2,
&size) != ERROR_SUCCESS) {
/* On failure, fallback to GetACP() */
UINT acp = GetACP();
snprintf(codePage, sizeof(codePage), "cp%u", acp);
}
if (strcmp(codePage, "cp65001") == 0) {
strcpy(codePage, "utf-8");
}
*lpContext = (LPVOID)&codePage[0];
return TRUE;
}
/*
* TclpGetCodePage --
*
* Returns a pointer to the string identifying the user code page.
*
* For consistency with Windows, which caches the code page at program
* startup, the code page is not updated even if the value in the registry
* changes. (This is similar to environment variables.)
*/
static const char *
TclpGetCodePage(void)
{
static INIT_ONCE codePageOnce = INIT_ONCE_STATIC_INIT;
const char *codePagePtr = NULL;
BOOL result = InitOnceExecuteOnce(
&codePageOnce, TclpGetCodePageOnce, NULL, (LPVOID *)&codePagePtr);
#ifdef NDEBUG
(void) result; /* Keep gcc unused variable quiet */
#else
assert(result == TRUE);
#endif
assert(codePagePtr != NULL);
return codePagePtr;
}
/*
*---------------------------------------------------------------------------
*
* TclpInitPlatform --
*
* Initialize all the platform-dependent things like signals,
* floating-point error handling and sockets.
*
* Called at process initialization time.
*
* Results:
* None.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
void
TclpInitPlatform(void)
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
tclPlatform = TCL_PLATFORM_WINDOWS;
/*
* Initialize the winsock library. On Windows XP and higher this
* can never fail.
*/
WSAStartup(wVersionRequested, &wsaData);
#ifdef STATIC_BUILD
/*
* If we are in a statically linked executable, then we need to explicitly
* initialize the Windows function tables here since DllMain() will not be
* invoked.
*/
TclWinInit(GetModuleHandleW(NULL));
#endif
/* Initialize code page once at startup, will not be updated */
(void)TclpGetCodePage();
}
/*
*-------------------------------------------------------------------------
*
* TclpInitLibraryPath --
*
* This is the fallback routine that sets the library path if the
* application has not set one by the first time it is needed.
*
* Results:
* None.
*
* Side effects:
* Sets the library path to an initial value.
*
*-------------------------------------------------------------------------
*/
void
TclpInitLibraryPath(
char **valuePtr,
size_t *lengthPtr,
Tcl_Encoding *encodingPtr)
{
#define LIBRARY_SIZE 64
Tcl_Obj *pathPtr;
char installLib[LIBRARY_SIZE];
const char *bytes;
Tcl_Size length;
TclNewObj(pathPtr);
/*
* Initialize the substring used when locating the script library. The
* installLib variable computes the script library path relative to the
* installed DLL.
*/
snprintf(installLib, sizeof(installLib), "lib/tcl%s", TCL_VERSION);
/*
* Look for the library relative to the TCL_LIBRARY env variable. If the
* last dirname in the TCL_LIBRARY path does not match the last dirname in
* the installLib variable, use the last dir name of installLib in
* addition to the original TCL_LIBRARY path.
*/
AppendEnvironment(pathPtr, installLib);
/*
* Look for the library in its default location.
*/
Tcl_ListObjAppendElement(NULL, pathPtr,
TclGetProcessGlobalValue(&defaultLibraryDir));
/*
* Look for the library in its source checkout location.
*/
Tcl_ListObjAppendElement(NULL, pathPtr,
TclGetProcessGlobalValue(&sourceLibraryDir));
*encodingPtr = NULL;
bytes = TclGetStringFromObj(pathPtr, &length);
*lengthPtr = length++;
*valuePtr = (char *)Tcl_Alloc(length);
memcpy(*valuePtr, bytes, length);
Tcl_DecrRefCount(pathPtr);
}
/*
*---------------------------------------------------------------------------
*
* AppendEnvironment --
*
* Append the value of the TCL_LIBRARY environment variable onto the path
* pointer. If the env variable points to another version of tcl (e.g.
* "tcl8.6") also append the path to this version (e.g.,
* "tcl8.6/../tcl9.0")
*
* Results:
* None.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
static void
AppendEnvironment(
Tcl_Obj *pathPtr,
const char *lib)
{
Tcl_Size pathc;
WCHAR wBuf[MAX_PATH];
DWORD dw;
char buf[MAX_PATH * 3];
Tcl_Obj *objPtr;
Tcl_DString ds;
const char **pathv;
char *shortlib;
/*
* The shortlib value needs to be the tail component of the lib path. For
* example, "lib/tcl9.0" -> "tcl9.0" while "usr/share/tcl9.0" -> "tcl9.0".
*/
for (shortlib = (char *) &lib[strlen(lib)-1]; shortlib>lib ; shortlib--) {
if (*shortlib == '/') {
if ((size_t)(shortlib - lib) == strlen(lib) - 1) {
Tcl_Panic("last character in lib cannot be '/'");
}
shortlib++;
break;
}
}
if (shortlib == lib) {
Tcl_Panic("no '/' character found in lib");
}
dw = GetEnvironmentVariableW(L"TCL_LIBRARY", wBuf, MAX_PATH);
if (dw <= 0 || dw >= MAX_PATH) {
return;
}
if (WideCharToMultiByte(
CP_UTF8, 0, wBuf, -1, buf, MAX_PATH * 3, NULL, NULL) == 0) {
return;
}
if (buf[0] != '\0') {
objPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE);
Tcl_ListObjAppendElement(NULL, pathPtr, objPtr);
TclWinNoBackslash(buf);
Tcl_SplitPath(buf, &pathc, &pathv);
/*
* The lstrcmpiA() will work even if pathv[pathc-1] is random UTF-8
* chars because I know shortlib is ascii.
*/
if ((pathc > 0) && (lstrcmpiA(shortlib, pathv[pathc - 1]) != 0)) {
/*
* TCL_LIBRARY is set but refers to a different tcl installation
* than the current version. Try fiddling with the specified
* directory to make it refer to this installation by removing the
* old "tclX.Y" and substituting the current version string.
*/
pathv[pathc - 1] = shortlib;
Tcl_DStringInit(&ds);
(void) Tcl_JoinPath(pathc, pathv, &ds);
objPtr = Tcl_DStringToObj(&ds);
} else {
objPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE);
}
Tcl_ListObjAppendElement(NULL, pathPtr, objPtr);
Tcl_Free((void *)pathv);
}
}
/*
*---------------------------------------------------------------------------
*
* InitializeDefaultLibraryDir --
*
* Locate the Tcl script library default location relative to the
* location of the Tcl DLL.
*
* Results:
* None.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
static void
InitializeDefaultLibraryDir(
char **valuePtr,
size_t *lengthPtr,
Tcl_Encoding *encodingPtr)
{
HMODULE hModule = (HMODULE)TclWinGetTclInstance();
WCHAR wName[MAX_PATH + LIBRARY_SIZE];
char name[(MAX_PATH + LIBRARY_SIZE) * 3];
char *end, *p;
GetModuleFileNameW(hModule, wName, sizeof(wName)/sizeof(WCHAR));
WideCharToMultiByte(CP_UTF8, 0, wName, -1, name, sizeof(name), NULL, NULL);
end = strrchr(name, '\\');
*end = '\0';
p = strrchr(name, '\\');
if (p != NULL) {
end = p;
}
*end = '\\';
TclWinNoBackslash(name);
snprintf(end + 1, LIBRARY_SIZE, "lib/tcl%s", TCL_VERSION);
*lengthPtr = strlen(name);
*valuePtr = (char *)Tcl_Alloc(*lengthPtr + 1);
*encodingPtr = NULL;
memcpy(*valuePtr, name, *lengthPtr + 1);
}
/*
*---------------------------------------------------------------------------
*
* InitializeSourceLibraryDir --
*
* Locate the Tcl script library default location relative to the
* location of the Tcl DLL as it exists in the build output directory
* associated with the source checkout.
*
* Results:
* None.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
static void
InitializeSourceLibraryDir(
char **valuePtr,
size_t *lengthPtr,
Tcl_Encoding *encodingPtr)
{
HMODULE hModule = (HMODULE)TclWinGetTclInstance();
WCHAR wName[MAX_PATH + LIBRARY_SIZE];
char name[(MAX_PATH + LIBRARY_SIZE) * 3];
char *end, *p;
GetModuleFileNameW(hModule, wName, sizeof(wName)/sizeof(WCHAR));
WideCharToMultiByte(CP_UTF8, 0, wName, -1, name, sizeof(name), NULL, NULL);
end = strrchr(name, '\\');
*end = '\0';
p = strrchr(name, '\\');
if (p != NULL) {
end = p;
}
*end = '\\';
TclWinNoBackslash(name);
snprintf(end + 1, LIBRARY_SIZE, "../library");
*lengthPtr = strlen(name);
*valuePtr = (char *)Tcl_Alloc(*lengthPtr + 1);
*encodingPtr = NULL;
memcpy(*valuePtr, name, *lengthPtr + 1);
}
/*
*---------------------------------------------------------------------------
*
* TclpSetInitialEncodings --
*
* Based on the locale, determine the encoding of the operating system
* and the default encoding for newly opened files.
*
* Called at process initialization time, and part way through startup,
* we verify that the initial encodings were correctly setup. Depending
* on Tcl's environment, there may not have been enough information first
* time through (above).
*
* Results:
* None.
*
* Side effects:
* The Tcl library path is converted from native encoding to UTF-8, on
* the first call, and the encodings may be changed on first or second
* call.
*
*---------------------------------------------------------------------------
*/
void
TclpSetInitialEncodings(void)
{
Tcl_DString encodingName;
Tcl_SetSystemEncoding(NULL,
Tcl_GetEncodingNameFromEnvironment(&encodingName));
Tcl_DStringFree(&encodingName);
}
const char *
Tcl_GetEncodingNameForUser(
Tcl_DString *bufPtr)
{
Tcl_DStringInit(bufPtr);
Tcl_DStringAppend(bufPtr, TclpGetCodePage(), -1);
return Tcl_DStringValue(bufPtr);
}
const char *
Tcl_GetEncodingNameFromEnvironment(
Tcl_DString *bufPtr)
{
const OSVERSIONINFOW *osInfoPtr = TclpGetWindowsVersion();
/*
* TIP 716 - for Build 18362 or higher, force utf-8. Note Windows build
* numbers always increase, so no need to check major / minor versions.
*/
if (osInfoPtr && osInfoPtr->dwBuildNumber >= 18362) {
Tcl_DStringInit(bufPtr);
Tcl_DStringAppend(bufPtr, "utf-8", 5);
return Tcl_DStringValue(bufPtr);
} else {
return Tcl_GetEncodingNameForUser(bufPtr);
}
}
const char *
TclpGetUserName(
Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with
* the name of user. */
{
Tcl_DStringInit(bufferPtr);
if (TclGetEnv("USERNAME", bufferPtr) == NULL) {
WCHAR szUserName[UNLEN+1];
DWORD cchUserNameLen = UNLEN;
if (!GetUserNameW(szUserName, &cchUserNameLen)) {
return NULL;
}
cchUserNameLen--;
Tcl_DStringInit(bufferPtr);
Tcl_WCharToUtfDString(szUserName, cchUserNameLen, bufferPtr);
}
return Tcl_DStringValue(bufferPtr);
}
/*
*---------------------------------------------------------------------------
*
* TclpSetVariables --
*
* Performs platform-specific interpreter initialization related to the
* tcl_platform and env variables, and other platform-specific things.
*
* Results:
* None.
*
* Side effects:
* Sets "tcl_platform", and "env(HOME)" Tcl variables.
*
*----------------------------------------------------------------------
*/
void
TclpSetVariables(
Tcl_Interp *interp) /* Interp to initialize. */
{
typedef int(__stdcall getVersionProc)(void *);
const char *ptr;
char buffer[TCL_INTEGER_SPACE * 2];
union {
SYSTEM_INFO info;
OemId oemId;
} sys;
static OSVERSIONINFOW osInfo;
static int osInfoInitialized = 0;
Tcl_DString ds;
Tcl_SetVar2Ex(interp, "tclDefaultLibrary", NULL,
TclGetProcessGlobalValue(&defaultLibraryDir), TCL_GLOBAL_ONLY);
if (!osInfoInitialized) {
HMODULE handle = GetModuleHandleW(L"NTDLL");
getVersionProc *getVersion = (getVersionProc *) (void *)
GetProcAddress(handle, "RtlGetVersion");
osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
if (!getVersion || getVersion(&osInfo)) {
GetVersionExW(&osInfo);
}
osInfoInitialized = 1;
}
GetSystemInfo(&sys.info);
/*
* Define the tcl_platform array.
*/
Tcl_SetVar2(interp, "tcl_platform", "platform", "windows",
TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, "tcl_platform", "os", "Windows NT", TCL_GLOBAL_ONLY);
if (osInfo.dwMajorVersion == 10 && osInfo.dwBuildNumber >= 22000) {
osInfo.dwMajorVersion = 11;
}
snprintf(buffer, sizeof(buffer), "%ld.%ld",
osInfo.dwMajorVersion, osInfo.dwMinorVersion);
Tcl_SetVar2(interp, "tcl_platform", "osVersion", buffer, TCL_GLOBAL_ONLY);
if (sys.oemId.wProcessorArchitecture < NUMPROCESSORS) {
Tcl_SetVar2(interp, "tcl_platform", "machine",
processors[sys.oemId.wProcessorArchitecture],
TCL_GLOBAL_ONLY);
}
/*
* Set up the HOME environment variable from the HOMEDRIVE & HOMEPATH
* environment variables, if necessary.
*/
Tcl_DStringInit(&ds);
ptr = Tcl_GetVar2(interp, "env", "HOME", TCL_GLOBAL_ONLY);
if (ptr == NULL) {
ptr = Tcl_GetVar2(interp, "env", "HOMEDRIVE", TCL_GLOBAL_ONLY);
if (ptr != NULL) {
Tcl_DStringAppend(&ds, ptr, TCL_INDEX_NONE);
}
ptr = Tcl_GetVar2(interp, "env", "HOMEPATH", TCL_GLOBAL_ONLY);
if (ptr != NULL) {
Tcl_DStringAppend(&ds, ptr, TCL_INDEX_NONE);
}
if (Tcl_DStringLength(&ds) > 0) {
Tcl_SetVar2(interp, "env", "HOME", Tcl_DStringValue(&ds),
TCL_GLOBAL_ONLY);
} else {
/* None of HOME, HOMEDRIVE, HOMEPATH exists. Try USERPROFILE */
ptr = Tcl_GetVar2(interp, "env", "USERPROFILE", TCL_GLOBAL_ONLY);
if (ptr != NULL && ptr[0]) {
Tcl_SetVar2(interp, "env", "HOME", ptr, TCL_GLOBAL_ONLY);
} else {
/* Last resort */
Tcl_SetVar2(interp, "env", "HOME", "c:\\", TCL_GLOBAL_ONLY);
}
}
}
/*
* Initialize the user name from the environment first, since this is much
* faster than asking the system.
* Note: cchUserNameLen is number of characters including nul terminator.
*/
ptr = TclpGetUserName(&ds);
Tcl_SetVar2(interp, "tcl_platform", "user", ptr ? ptr : "",
TCL_GLOBAL_ONLY);
Tcl_DStringFree(&ds);
/*
* Define what the platform PATH separator is. [TIP #315]
*/
Tcl_SetVar2(interp, "tcl_platform", "pathSeparator", ";", TCL_GLOBAL_ONLY);
}
/*
*----------------------------------------------------------------------
*
* TclpFindVariable --
*
* Locate the entry in environ for a given name. On Unix this routine is
* case sensitive, on Windows this matches mixed case.
*
* Results:
* The return value is the index in environ of an entry with the name
* "name", or -1 if there is no such entry. The integer
* at *lengthPtr is filled in with the length of name (if a matching
* entry is found) or the length of the environ array (if no
* matching entry is found).
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
Tcl_Size
TclpFindVariable(
const char *name, /* Name of desired environment variable
* (UTF-8). */
Tcl_Size *lengthPtr) /* Used to return length of name (for
* successful searches) or number of non-NULL
* entries in environ (for unsuccessful
* searches). */
{
Tcl_Size i, length, result = TCL_INDEX_NONE;
const WCHAR *env;
const char *p1, *p2;
char *envUpper, *nameUpper;
Tcl_DString envString;
/*
* Convert the name to all upper case for the case insensitive comparison.
*/
length = strlen(name);
nameUpper = (char *)Tcl_Alloc(length + 1);
memcpy(nameUpper, name, length+1);
Tcl_UtfToUpper(nameUpper);
Tcl_DStringInit(&envString);
for (i = 0, env = _wenviron[i]; env != NULL; i++, env = _wenviron[i]) {
/*
* Chop the env string off after the equal sign, then Convert the name
* to all upper case, so we do not have to convert all the characters
* after the equal sign.
*/
Tcl_DStringInit(&envString);
envUpper = Tcl_WCharToUtfDString(env, TCL_INDEX_NONE, &envString);
p1 = strchr(envUpper, '=');
if (p1 == NULL) {
continue;
}
length = p1 - envUpper;
Tcl_DStringSetLength(&envString, length+1);
Tcl_UtfToUpper(envUpper);
p1 = envUpper;
p2 = nameUpper;
for (; *p2 == *p1; p1++, p2++) {
/* NULL loop body. */
}
if ((*p1 == '=') && (*p2 == '\0')) {
*lengthPtr = length;
result = i;
goto done;
}
Tcl_DStringFree(&envString);
}
*lengthPtr = i;
done:
Tcl_DStringFree(&envString);
Tcl_Free(nameUpper);
return result;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

130
vendor/tcl/win/tclWinInt.h vendored Normal file
View file

@ -0,0 +1,130 @@
/*
* tclWinInt.h --
*
* Declarations of Windows-specific shared variables and procedures.
*
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#ifndef _TCLWININT
#define _TCLWININT
#include "tclInt.h"
#ifdef HAVE_NO_SEH
/*
* Unlike Borland and Microsoft, we don't register exception handlers by
* pushing registration records onto the runtime stack. Instead, we register
* them by creating an TCLEXCEPTION_REGISTRATION within the activation record.
*/
typedef struct TCLEXCEPTION_REGISTRATION {
struct TCLEXCEPTION_REGISTRATION *link;
EXCEPTION_DISPOSITION (*handler)(
struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*);
void *ebp;
void *esp;
int status;
} TCLEXCEPTION_REGISTRATION;
#endif
/*
* Declarations of functions that are not accessible by way of the
* stubs table.
*/
MODULE_SCOPE char TclWinDriveLetterForVolMountPoint(
const WCHAR *mountPoint);
MODULE_SCOPE void TclWinEncodingsCleanup(void);
MODULE_SCOPE void TclWinInit(HINSTANCE hInst);
MODULE_SCOPE TclFile TclWinMakeFile(HANDLE handle);
MODULE_SCOPE Tcl_Channel TclWinOpenConsoleChannel(HANDLE handle,
char *channelName, int permissions);
MODULE_SCOPE Tcl_Channel TclWinOpenSerialChannel(HANDLE handle,
char *channelName, int permissions);
MODULE_SCOPE HANDLE TclWinSerialOpen(HANDLE handle, const WCHAR *name,
DWORD access);
MODULE_SCOPE int TclWinSymLinkCopyDirectory(const WCHAR *LinkOriginal,
const WCHAR *LinkCopy);
MODULE_SCOPE int TclWinSymLinkDelete(const WCHAR *LinkOriginal,
int linkOnly);
MODULE_SCOPE int TclWinFileOwned(Tcl_Obj *);
MODULE_SCOPE void TclWinGenerateChannelName(char channelName[],
const char *channelTypeName, void *channelImpl);
MODULE_SCOPE const char*TclpGetUserName(Tcl_DString *bufferPtr);
/* Needed by tclWinFile.c and tclWinFCmd.c */
#ifndef FILE_ATTRIBUTE_REPARSE_POINT
#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400
#endif
/*
*----------------------------------------------------------------------
* Declarations of helper-workers threaded facilities for a pipe based channel.
*
* Corresponding functionality provided in "tclWinPipe.c".
*----------------------------------------------------------------------
*/
typedef struct TclPipeThreadInfo {
HANDLE evControl; /* Auto-reset event used by the main thread to
* signal when the pipe thread should attempt
* to do read/write operation. Additionally
* used as signal to stop (state set to -1) */
volatile LONG state; /* Indicates current state of the thread */
void *clientData; /* Referenced data of the main thread */
} TclPipeThreadInfo;
/* If pipe-workers will use some tcl subsystem, we can use Tcl_Alloc without
* more overhead for finalize thread (should be executed anyway)
*
* #define _PTI_USE_CKALLOC 1
*/
/*
* State of the pipe-worker.
*
* State PTI_STATE_STOP possible from idle state only, worker owns TI structure.
* Otherwise PTI_STATE_END used (main thread hold ownership of the TI).
*/
enum PipeWorkerStates {
PTI_STATE_IDLE = 0, /* idle or not yet initialzed */
PTI_STATE_WORK = 1, /* in work */
PTI_STATE_STOP = 2, /* thread should stop work (owns TI structure) */
PTI_STATE_END = 4, /* thread should stop work (worker is busy) */
PTI_STATE_DOWN = 8 /* worker is down */
};
MODULE_SCOPE
TclPipeThreadInfo * TclPipeThreadCreateTI(TclPipeThreadInfo **pipeTIPtr,
void *clientData);
MODULE_SCOPE int TclPipeThreadWaitForSignal(
TclPipeThreadInfo **pipeTIPtr);
static inline void
TclPipeThreadSignal(
TclPipeThreadInfo **pipeTIPtr)
{
TclPipeThreadInfo *pipeTI = *pipeTIPtr;
if (pipeTI) {
SetEvent(pipeTI->evControl);
}
};
static inline int
TclPipeThreadIsAlive(
TclPipeThreadInfo **pipeTIPtr)
{
TclPipeThreadInfo *pipeTI = *pipeTIPtr;
return (pipeTI && pipeTI->state != PTI_STATE_DOWN);
};
MODULE_SCOPE int TclPipeThreadStopSignal(TclPipeThreadInfo **pipeTIPtr);
MODULE_SCOPE void TclPipeThreadStop(TclPipeThreadInfo **pipeTIPtr,
HANDLE hThread);
MODULE_SCOPE void TclPipeThreadExit(TclPipeThreadInfo **pipeTIPtr);
#endif /* _TCLWININT */

436
vendor/tcl/win/tclWinLoad.c vendored Normal file
View file

@ -0,0 +1,436 @@
/*
* tclWinLoad.c --
*
* This function provides a version of the TclLoadFile that works with
* the Windows "LoadLibrary" and "GetProcAddress" API for dynamic
* loading.
*
* Copyright © 1995-1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclWinInt.h"
#if defined (__clang__) && (__clang_major__ > 20)
#pragma clang diagnostic ignored "-Wc++-keyword"
#endif
/*
* Native name of the directory in the native filesystem where DLLs used in
* this process are copied prior to loading, and mutex used to protect its
* allocation.
*/
static WCHAR *dllDirectoryName = NULL;
#if TCL_THREADS
static Tcl_Mutex dllDirectoryNameMutex;
#endif
/*
* Static functions defined within this file.
*/
static void * FindSymbol(Tcl_Interp *interp,
Tcl_LoadHandle loadHandle, const char *symbol);
static int InitDLLDirectoryName(void);
static void UnloadFile(Tcl_LoadHandle loadHandle);
/*
*----------------------------------------------------------------------
*
* TclpDlopen --
*
* Dynamically loads a binary code file into memory and returns a handle
* to the new code.
*
* Results:
* A standard Tcl completion code. If an error occurs, an error message
* is left in the interp's result.
*
* Side effects:
* New code suddenly appears in memory.
*
*----------------------------------------------------------------------
*/
int
TclpDlopen(
Tcl_Interp *interp, /* Used for error reporting. */
Tcl_Obj *pathPtr, /* Name of the file containing the desired
* code (UTF-8). */
Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded
* file which will be passed back to
* (*unloadProcPtr)() to unload the file. */
Tcl_FSUnloadFileProc **unloadProcPtr,
/* Filled with address of Tcl_FSUnloadFileProc
* function which should be used for this
* file. */
TCL_UNUSED(int) /*flags*/)
{
HINSTANCE hInstance = NULL;
const WCHAR *nativeName;
Tcl_LoadHandle handlePtr;
DWORD firstError;
/*
* First try the full path the user gave us. This is particularly
* important if the cwd is inside a vfs, and we are trying to load using a
* relative path.
*/
nativeName = (const WCHAR *)Tcl_FSGetNativePath(pathPtr);
if (nativeName != NULL) {
hInstance = LoadLibraryExW(nativeName, NULL,
LOAD_WITH_ALTERED_SEARCH_PATH);
}
if (hInstance == NULL) {
/*
* Let the OS loader examine the binary search path for whatever
* string the user gave us which hopefully refers to a file on the
* binary path.
*/
Tcl_DString ds;
/*
* Remember the first error on load attempt to be used if the
* second load attempt below also fails.
*/
firstError = (nativeName == NULL) ?
ERROR_MOD_NOT_FOUND : GetLastError();
Tcl_DStringInit(&ds);
nativeName = Tcl_UtfToWCharDString(TclGetString(pathPtr), TCL_INDEX_NONE, &ds);
hInstance = LoadLibraryExW(nativeName, NULL,
LOAD_WITH_ALTERED_SEARCH_PATH);
Tcl_DStringFree(&ds);
}
if (hInstance == NULL) {
DWORD lastError;
Tcl_Obj *errMsg;
/*
* We choose to only use the error from the second call if the first
* call failed due to the file not being found. Else stick to the
* first error for reporting purposes.
*/
if (firstError == ERROR_MOD_NOT_FOUND ||
firstError == ERROR_DLL_NOT_FOUND) {
lastError = GetLastError();
} else {
lastError = firstError;
}
errMsg = Tcl_ObjPrintf("couldn't load library \"%s\": ",
TclGetString(pathPtr));
/*
* Check for possible DLL errors. This doesn't work quite right,
* because Windows seems to only return ERROR_MOD_NOT_FOUND for just
* about any problem, but it's better than nothing. It'd be even
* better if there was a way to get what DLLs
*/
if (interp) {
switch (lastError) {
case ERROR_MOD_NOT_FOUND:
Tcl_SetErrorCode(interp, "WIN_LOAD", "MOD_NOT_FOUND", (char *)NULL);
goto notFoundMsg;
case ERROR_DLL_NOT_FOUND:
Tcl_SetErrorCode(interp, "WIN_LOAD", "DLL_NOT_FOUND", (char *)NULL);
notFoundMsg:
Tcl_AppendToObj(errMsg, "this library or a dependent library"
" could not be found in library path", TCL_INDEX_NONE);
break;
case ERROR_PROC_NOT_FOUND:
Tcl_SetErrorCode(interp, "WIN_LOAD", "PROC_NOT_FOUND", (char *)NULL);
Tcl_AppendToObj(errMsg, "A function specified in the import"
" table could not be resolved by the system. Windows"
" is not telling which one, I'm sorry.", TCL_INDEX_NONE);
break;
case ERROR_INVALID_DLL:
Tcl_SetErrorCode(interp, "WIN_LOAD", "INVALID_DLL", (char *)NULL);
Tcl_AppendToObj(errMsg, "this library or a dependent library"
" is damaged", TCL_INDEX_NONE);
break;
case ERROR_DLL_INIT_FAILED:
Tcl_SetErrorCode(interp, "WIN_LOAD", "DLL_INIT_FAILED", (char *)NULL);
Tcl_AppendToObj(errMsg, "the library initialization"
" routine failed", TCL_INDEX_NONE);
break;
case ERROR_BAD_EXE_FORMAT:
Tcl_SetErrorCode(interp, "WIN_LOAD", "BAD_EXE_FORMAT", (char *)NULL);
Tcl_AppendToObj(errMsg, "Bad exe format. Possibly a 32/64-bit mismatch.", TCL_INDEX_NONE);
break;
default:
Tcl_WinConvertError(lastError);
Tcl_AppendToObj(errMsg, Tcl_PosixError(interp), TCL_INDEX_NONE);
}
Tcl_SetObjResult(interp, errMsg);
}
return TCL_ERROR;
}
/*
* Succeded; package everything up for Tcl.
*/
handlePtr = (Tcl_LoadHandle)Tcl_Alloc(sizeof(struct Tcl_LoadHandle_));
handlePtr->clientData = (void *)hInstance;
handlePtr->findSymbolProcPtr = &FindSymbol;
handlePtr->unloadFileProcPtr = &UnloadFile;
*loadHandle = handlePtr;
*unloadProcPtr = &UnloadFile;
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* FindSymbol --
*
* Looks up a symbol, by name, through a handle associated with a
* previously loaded piece of code (shared library).
*
* Results:
* Returns a pointer to the function associated with 'symbol' if it is
* found. Otherwise returns NULL and may leave an error message in the
* interp's result.
*
*----------------------------------------------------------------------
*/
static void *
FindSymbol(
Tcl_Interp *interp,
Tcl_LoadHandle loadHandle,
const char *symbol)
{
HINSTANCE hInstance = (HINSTANCE) loadHandle->clientData;
void *proc = NULL;
/*
* For each symbol, check for both Symbol and _Symbol, since Borland
* generates C symbols with a leading '_' by default.
*/
proc = (void *)GetProcAddress(hInstance, symbol);
if (proc == NULL) {
Tcl_DString ds;
const char *sym2;
Tcl_DStringInit(&ds);
TclDStringAppendLiteral(&ds, "_");
sym2 = Tcl_DStringAppend(&ds, symbol, TCL_INDEX_NONE);
proc = (void *)GetProcAddress(hInstance, sym2);
Tcl_DStringFree(&ds);
}
if (proc == NULL && interp != NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"cannot find symbol \"%s\"", symbol));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, (char *)NULL);
}
return proc;
}
/*
*----------------------------------------------------------------------
*
* UnloadFile --
*
* Unloads a dynamically loaded binary code file from memory. Code
* pointers in the formerly loaded file are no longer valid after calling
* this function.
*
* Results:
* None.
*
* Side effects:
* Code removed from memory.
*
*----------------------------------------------------------------------
*/
static void
UnloadFile(
Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to
* TclpDlopen(). The loadHandle is a token
* that represents the loaded file. */
{
HINSTANCE hInstance = (HINSTANCE) loadHandle->clientData;
FreeLibrary(hInstance);
Tcl_Free(loadHandle);
}
/*
*----------------------------------------------------------------------
*
* TclpTempFileNameForLibrary --
*
* Constructs a temporary file name for loading a shared object (DLL).
*
* Results:
* Returns the constructed file name.
*
* On Windows, a DLL is identified by the final component of its path name.
* Cross linking among DLL's (and hence, preloading) will not work unless this
* name is preserved when copying a DLL from a VFS to a temp file for
* preloading. For this reason, all DLLs in a given process are copied to a
* temp directory, and their names are preserved.
*
*----------------------------------------------------------------------
*/
Tcl_Obj *
TclpTempFileNameForLibrary(
Tcl_Interp *interp, /* Tcl interpreter. */
Tcl_Obj *path) /* Path name of the DLL in the VFS. */
{
Tcl_Obj *fileName; /* Name of the temp file. */
Tcl_Obj *tail; /* Tail of the source path. */
Tcl_MutexLock(&dllDirectoryNameMutex);
if (dllDirectoryName == NULL) {
if (InitDLLDirectoryName() == TCL_ERROR) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"couldn't create temporary directory: %s",
Tcl_PosixError(interp)));
Tcl_MutexUnlock(&dllDirectoryNameMutex);
return NULL;
}
}
Tcl_MutexUnlock(&dllDirectoryNameMutex);
/*
* Now we know where to put temporary DLLs, construct the name.
*/
fileName = TclpNativeToNormalized(dllDirectoryName);
tail = TclPathPart(interp, path, TCL_PATH_TAIL);
if (tail == NULL) {
Tcl_DecrRefCount(fileName);
return NULL;
}
Tcl_AppendToObj(fileName, "/", 1);
Tcl_AppendObjToObj(fileName, tail);
return fileName;
}
/*
*----------------------------------------------------------------------
*
* InitDLLDirectoryName --
*
* Helper for TclpTempFileNameForLibrary; builds a temporary directory
* that is specific to the current process. Should only be called once
* per process start. Caller must hold dllDirectoryNameMutex.
*
* Results:
* Tcl result code.
*
* Side-effects:
* Creates temp directory.
* Allocates memory pointed to by dllDirectoryName.
*
*----------------------------------------------------------------------
* [Candidate for process global?]
*/
static int
InitDLLDirectoryName(void)
{
size_t nameLen; /* Length of the temp folder name. */
WCHAR name[MAX_PATH]; /* Path name of the temp folder. */
DWORD id; /* The process id. */
DWORD lastError; /* Last error to happen in Win API. */
int i;
/*
* Determine the name of the directory to use, and create it. (Keep
* trying with new names until an attempt to create the directory
* succeeds)
*/
nameLen = GetTempPathW(MAX_PATH, name);
if (nameLen >= MAX_PATH-12) {
Tcl_SetErrno(ENAMETOOLONG);
return TCL_ERROR;
}
wcscpy(name+nameLen, L"TCLXXXXXXXX");
nameLen += 11;
id = GetCurrentProcessId();
lastError = ERROR_ALREADY_EXISTS;
for (i=0 ; i<256 ; i++) {
wsprintfW(name+nameLen-8, L"%08x", id);
if (CreateDirectoryW(name, NULL)) {
/*
* Issue: we don't schedule this directory for deletion by anyone.
* Can we ask the OS to do this for us? There appears to be
* potential for using CreateFile (with the flag
* FILE_FLAG_BACKUP_SEMANTICS) and RemoveDirectory to do this...
*/
goto copyToGlobalBuffer;
}
lastError = GetLastError();
if (lastError != ERROR_ALREADY_EXISTS) {
break;
}
id *= 16777619;
}
Tcl_WinConvertError(lastError);
return TCL_ERROR;
/*
* Store our computed value in the global.
*/
copyToGlobalBuffer:
dllDirectoryName = (WCHAR *)Tcl_Alloc((nameLen+1) * sizeof(WCHAR));
wcscpy(dllDirectoryName, name);
return TCL_OK;
}
/*
* These functions are fallbacks if we somehow determine that the platform can
* do loading from memory but the user wishes to disable it. They just report
* (gracefully) that they fail.
*/
#ifdef TCL_LOAD_FROM_MEMORY
MODULE_SCOPE void *
TclpLoadMemoryGetBuffer(
TCL_UNUSED(size_t))
{
return NULL;
}
MODULE_SCOPE int
TclpLoadMemory(
TCL_UNUSED(void *),
TCL_UNUSED(size_t),
TCL_UNUSED(Tcl_Size),
TCL_UNUSED(const char *),
TCL_UNUSED(Tcl_LoadHandle *),
TCL_UNUSED(Tcl_FSUnloadFileProc **),
TCL_UNUSED(int))
{
return TCL_ERROR;
}
#endif /* TCL_LOAD_FROM_MEMORY */
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

638
vendor/tcl/win/tclWinNotify.c vendored Normal file
View file

@ -0,0 +1,638 @@
/*
* tclWinNotify.c --
*
* This file contains Windows-specific procedures for the notifier, which
* is the lowest-level part of the Tcl event loop. This file works
* together with ../generic/tclNotify.c.
*
* Copyright © 1995-1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
/*
* The following static indicates whether this module has been initialized.
*/
#define INTERVAL_TIMER 1 /* Handle of interval timer. */
#define WM_WAKEUP WM_USER /* Message that is send by
* Tcl_AlertNotifier. */
/*
* The following static structure contains the state information for the
* Windows implementation of the Tcl notifier. One of these structures is
* created for each thread that is using the notifier.
*/
typedef struct {
CRITICAL_SECTION crit; /* Monitor for this notifier. */
DWORD thread; /* Identifier for thread associated with this
* notifier. */
HANDLE event; /* Event object used to wake up the notifier
* thread. */
int pending; /* Alert message pending, this field is locked
* by the notifierMutex. */
HWND hwnd; /* Messaging window. */
int timerActive; /* 1 if interval timer is running. */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;
/*
* The following static indicates the number of threads that have initialized
* notifiers. It controls the lifetime of the TclNotifier window class.
*
* You must hold the notifierMutex lock before accessing this variable.
*/
static int notifierCount = 0;
static const WCHAR className[] = L"TclNotifier";
static int initialized = 0;
static CRITICAL_SECTION notifierMutex;
/*
* Static routines defined in this file.
*/
static LRESULT CALLBACK NotifierProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam);
/*
*----------------------------------------------------------------------
*
* Tcl_InitNotifier --
*
* Initializes the platform specific notifier state.
*
* Results:
* Returns a handle to the notifier state for this thread..
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void *
TclpInitNotifier(void)
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
TclpGlobalLock();
if (!initialized) {
initialized = 1;
InitializeCriticalSection(&notifierMutex);
}
TclpGlobalUnlock();
/*
* Register Notifier window class if this is the first thread to use this
* module.
*/
EnterCriticalSection(&notifierMutex);
if (notifierCount == 0) {
WNDCLASSW clazz;
clazz.style = 0;
clazz.cbClsExtra = 0;
clazz.cbWndExtra = 0;
clazz.hInstance = (HINSTANCE) TclWinGetTclInstance();
clazz.hbrBackground = NULL;
clazz.lpszMenuName = NULL;
clazz.lpszClassName = className;
clazz.lpfnWndProc = NotifierProc;
clazz.hIcon = NULL;
clazz.hCursor = NULL;
if (!RegisterClassW(&clazz)) {
Tcl_Panic("Tcl_InitNotifier: %s",
"unable to register TclNotifier window class");
}
}
notifierCount++;
LeaveCriticalSection(&notifierMutex);
tsdPtr->pending = 0;
tsdPtr->timerActive = 0;
InitializeCriticalSection(&tsdPtr->crit);
tsdPtr->hwnd = NULL;
tsdPtr->thread = GetCurrentThreadId();
tsdPtr->event = CreateEventW(NULL, TRUE /* manual */,
FALSE /* !signaled */, NULL);
return tsdPtr;
}
/*
*----------------------------------------------------------------------
*
* TclpFinalizeNotifier --
*
* This function is called to cleanup the notifier state before a thread
* is terminated.
*
* Results:
* None.
*
* Side effects:
* May dispose of the notifier window and class.
*
*----------------------------------------------------------------------
*/
void
TclpFinalizeNotifier(
void *clientData) /* Pointer to notifier data. */
{
ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
/*
* Only finalize the notifier if a notifier was installed in the current
* thread; there is a route in which this is not guaranteed to be true
* (when tclWin32Dll.c:DllMain() is called with the flag
* DLL_PROCESS_DETACH by the OS, which could be doing so from a thread
* that's never previously been involved with Tcl, e.g. the task manager)
* so this check is important.
*
* Fixes Bug #217982 reported by Hugh Vu and Gene Leache.
*/
if (tsdPtr == NULL) {
return;
}
DeleteCriticalSection(&tsdPtr->crit);
CloseHandle(tsdPtr->event);
/*
* Clean up the timer and messaging window for this thread.
*/
if (tsdPtr->hwnd) {
KillTimer(tsdPtr->hwnd, INTERVAL_TIMER);
DestroyWindow(tsdPtr->hwnd);
}
/*
* If this is the last thread to use the notifier, unregister the notifier
* window class.
*/
EnterCriticalSection(&notifierMutex);
if (notifierCount) {
notifierCount--;
if (notifierCount == 0) {
UnregisterClassW(className, (HINSTANCE) TclWinGetTclInstance());
}
}
LeaveCriticalSection(&notifierMutex);
}
/*
*----------------------------------------------------------------------
*
* TclpAlertNotifier --
*
* Wake up the specified notifier from any thread. This routine is called
* by the platform independent notifier code whenever the Tcl_ThreadAlert
* routine is called. This routine is guaranteed not to be called on a
* given notifier after Tcl_FinalizeNotifier is called for that notifier.
* This routine is typically called from a thread other than the
* notifier's thread.
*
* Results:
* None.
*
* Side effects:
* Sends a message to the messaging window for the notifier if there
* isn't already one pending.
*
*----------------------------------------------------------------------
*/
void
TclpAlertNotifier(
void *clientData) /* Pointer to thread data. */
{
ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
/*
* Note that we do not need to lock around access to the hwnd because the
* race condition has no effect since any race condition implies that the
* notifier thread is already awake.
*/
if (tsdPtr->hwnd) {
/*
* We do need to lock around access to the pending flag.
*/
EnterCriticalSection(&tsdPtr->crit);
if (!tsdPtr->pending) {
PostMessageW(tsdPtr->hwnd, WM_WAKEUP, 0, 0);
}
tsdPtr->pending = 1;
LeaveCriticalSection(&tsdPtr->crit);
} else {
SetEvent(tsdPtr->event);
}
}
/*
*----------------------------------------------------------------------
*
* TclpSetTimer --
*
* This procedure sets the current notifier timer value. The notifier
* will ensure that Tcl_ServiceAll() is called after the specified
* interval, even if no events have occurred.
*
* Results:
* None.
*
* Side effects:
* Replaces any previous timer.
*
*----------------------------------------------------------------------
*/
void
TclpSetTimer(
const Tcl_Time *timePtr) /* Maximum block time, or NULL. */
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
UINT timeout;
/*
* We only need to set up an interval timer if we're being called from an
* external event loop. If we don't have a window handle then we just
* return immediately and let Tcl_WaitForEvent handle timeouts.
*/
if (!tsdPtr->hwnd) {
return;
}
if (!timePtr) {
timeout = 0;
} else {
/*
* Make sure we pass a non-zero value into the timeout argument.
* Windows seems to get confused by zero length timers.
*/
timeout = (UINT)timePtr->sec * 1000 + (unsigned long)timePtr->usec / 1000;
if (timeout == 0) {
timeout = 1;
}
}
if (timeout != 0) {
tsdPtr->timerActive = 1;
SetTimer(tsdPtr->hwnd, INTERVAL_TIMER, timeout, NULL);
} else {
tsdPtr->timerActive = 0;
KillTimer(tsdPtr->hwnd, INTERVAL_TIMER);
}
}
/*
*----------------------------------------------------------------------
*
* TclpServiceModeHook --
*
* This function is invoked whenever the service mode changes.
*
* Results:
* None.
*
* Side effects:
* If this is the first time the notifier is set into TCL_SERVICE_ALL,
* then the communication window is created.
*
*----------------------------------------------------------------------
*/
void
TclpServiceModeHook(
int mode) /* Either TCL_SERVICE_ALL, or
* TCL_SERVICE_NONE. */
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
* If this is the first time that the notifier has been used from a modal
* loop, then create a communication window. Note that after this point,
* the application needs to service events in a timely fashion or Windows
* will hang waiting for the window to respond to synchronous system
* messages. At some point, we may want to consider destroying the window
* if we leave the modal loop, but for now we'll leave it around.
*/
if (mode == TCL_SERVICE_ALL && !tsdPtr->hwnd) {
tsdPtr->hwnd = CreateWindowW(className, className, WS_TILED,
0, 0, 0, 0, NULL, NULL, (HINSTANCE) TclWinGetTclInstance(),
NULL);
/*
* Send an initial message to the window to ensure that we wake up the
* notifier once we get into the modal loop. This will force the
* notifier to recompute the timeout value and schedule a timer if one
* is needed.
*/
Tcl_AlertNotifier(tsdPtr);
}
}
/*
*----------------------------------------------------------------------
*
* TclAsyncNotifier --
*
* This procedure is a no-op on Windows.
*
* Result:
* Always true.
*
* Side effetcs:
* None.
*----------------------------------------------------------------------
*/
int
TclAsyncNotifier(
TCL_UNUSED(int), /* Signal number. */
TCL_UNUSED(Tcl_ThreadId), /* Target thread. */
TCL_UNUSED(void *), /* Notifier data. */
TCL_UNUSED(int *), /* Flag to mark. */
TCL_UNUSED(int)) /* Value of mark. */
{
return 0;
}
/*
*----------------------------------------------------------------------
*
* NotifierProc --
*
* This procedure is invoked by Windows to process events on the notifier
* window. Messages will be sent to this window in response to external
* timer events or calls to TclpAlertTsdPtr->
*
* Results:
* A standard windows result.
*
* Side effects:
* Services any pending events.
*
*----------------------------------------------------------------------
*/
static LRESULT CALLBACK
NotifierProc(
HWND hwnd, /* Passed on... */
UINT message, /* What messsage is this? */
WPARAM wParam, /* Passed on... */
LPARAM lParam) /* Passed on... */
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (message == WM_WAKEUP) {
EnterCriticalSection(&tsdPtr->crit);
tsdPtr->pending = 0;
LeaveCriticalSection(&tsdPtr->crit);
} else if (message != WM_TIMER) {
return DefWindowProcW(hwnd, message, wParam, lParam);
}
/*
* Process all of the runnable events.
*/
Tcl_ServiceAll();
return 0;
}
/*
*----------------------------------------------------------------------
*
* TclpNotifierData --
*
* This function returns a void pointer to be associated
* with a Tcl_AsyncHandler.
*
* Results:
* On Windows, returns always NULL.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void *
TclpNotifierData(void)
{
return NULL;
}
/*
*----------------------------------------------------------------------
*
* TclpWaitForEvent --
*
* This function is called by Tcl_DoOneEvent to wait for new events on
* the message queue. If the block time is 0, then Tcl_WaitForEvent just
* polls the event queue without blocking.
*
* Results:
* Returns -1 if a WM_QUIT message is detected, returns 1 if a message
* was dispatched, otherwise returns 0.
*
* Side effects:
* Dispatches a message to a window procedure, which could do anything.
*
*----------------------------------------------------------------------
*/
int
TclpWaitForEvent(
const Tcl_Time *timePtr) /* Maximum block time, or NULL. */
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
MSG msg;
DWORD timeout, result;
int status;
/*
* Compute the timeout in milliseconds.
*/
if (timePtr) {
/*
* TIP #233 (Virtualized Time). Convert virtual domain delay to
* real-time.
*/
Tcl_Time myTime;
myTime.sec = timePtr->sec;
myTime.usec = timePtr->usec;
if (myTime.sec != 0 || myTime.usec != 0) {
TclScaleTime(&myTime);
}
timeout = (DWORD)myTime.sec * 1000 + (unsigned long)myTime.usec / 1000;
} else {
timeout = INFINITE;
}
/*
* Check to see if there are any messages in the queue before waiting
* because MsgWaitForMultipleObjects will not wake up if there are events
* currently sitting in the queue.
*/
if (!PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE)) {
/*
* Wait for something to happen (a signal from another thread, a
* message, or timeout) or loop servicing asynchronous procedure calls
* queued to this thread.
*/
do {
result = MsgWaitForMultipleObjectsEx(1, &tsdPtr->event, timeout,
QS_ALLINPUT, MWMO_ALERTABLE);
} while (result == WAIT_IO_COMPLETION);
if (result == WAIT_FAILED) {
status = -1;
goto end;
}
}
/*
* Check to see if there are any messages to process.
*/
if (PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE)) {
/*
* Retrieve and dispatch the first message.
*/
result = GetMessageW(&msg, NULL, 0, 0);
if (result == 0) {
/*
* We received a request to exit this thread (WM_QUIT), so
* propagate the quit message and start unwinding.
*/
PostQuitMessage((int) msg.wParam);
status = -1;
} else if (result == (DWORD) -1) {
/*
* We got an error from the system. I have no idea why this would
* happen, so we'll just unwind.
*/
status = -1;
} else {
TranslateMessage(&msg);
DispatchMessageW(&msg);
status = 1;
}
} else {
status = 0;
}
end:
ResetEvent(tsdPtr->event);
return status;
}
/*
*----------------------------------------------------------------------
*
* Tcl_Sleep --
*
* Delay execution for the specified number of milliseconds.
*
* Results:
* None.
*
* Side effects:
* Time passes.
*
*----------------------------------------------------------------------
*/
void
Tcl_Sleep(
int ms) /* Number of milliseconds to sleep. */
{
/*
* Simply calling 'Sleep' for the requisite number of milliseconds can
* make the process appear to wake up early because it isn't synchronized
* with the CPU performance counter that is used in tclWinTime.c. This
* behavior is probably benign, but messes up some of the corner cases in
* the test suite. We get around this problem by repeating the 'Sleep'
* call as many times as necessary to make the clock advance by the
* requisite amount.
*/
Tcl_Time now; /* Current wall clock time. */
Tcl_Time desired; /* Desired wakeup time. */
Tcl_Time vdelay; /* Time to sleep, for scaling virtual ->
* real. */
DWORD sleepTime; /* Time to sleep, real-time */
vdelay.sec = ms / 1000;
vdelay.usec = (ms % 1000) * 1000;
Tcl_GetTime(&now);
desired.sec = now.sec + vdelay.sec;
desired.usec = now.usec + vdelay.usec;
if (desired.usec > 1000000) {
++desired.sec;
desired.usec -= 1000000;
}
/*
* TIP #233: Scale delay from virtual to real-time.
*/
TclScaleTime(&vdelay);
sleepTime = (DWORD)vdelay.sec * 1000 + (unsigned long)vdelay.usec / 1000;
for (;;) {
SleepEx(sleepTime, TRUE);
Tcl_GetTime(&now);
if (now.sec > desired.sec) {
break;
} else if ((now.sec == desired.sec) && (now.usec >= desired.usec)) {
break;
}
vdelay.sec = desired.sec - now.sec;
vdelay.usec = desired.usec - now.usec;
TclScaleTime(&vdelay);
sleepTime = (DWORD)vdelay.sec * 1000 + (unsigned long)vdelay.usec / 1000;
}
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

88
vendor/tcl/win/tclWinPanic.c vendored Normal file
View file

@ -0,0 +1,88 @@
/*
* tclWinPanic.c --
*
* Contains the Windows-specific command-line panic proc.
*
* Copyright © 2013 Jan Nijtmans.
* All rights reserved.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
/*
*----------------------------------------------------------------------
*
* Tcl_ConsolePanic --
*
* Display a message. If a debugger is present, present it directly to
* the debugger, otherwise send it to stderr.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
TCL_NORETURN void
Tcl_ConsolePanic(
const char *format, ...)
{
#define TCL_MAX_WARN_LEN 26000
va_list argList;
WCHAR msgString[TCL_MAX_WARN_LEN];
char buf[TCL_MAX_WARN_LEN * 3];
HANDLE handle = GetStdHandle(STD_ERROR_HANDLE);
DWORD dummy;
va_start(argList, format);
vsnprintf(buf+3, sizeof(buf)-3, format, argList);
buf[sizeof(buf)-1] = 0;
msgString[TCL_MAX_WARN_LEN-1] = '\0';
MultiByteToWideChar(CP_UTF8, 0, buf+3, -1, msgString, TCL_MAX_WARN_LEN);
/*
* Truncate MessageBox string if it is too long to not overflow the buffer.
*/
if (msgString[TCL_MAX_WARN_LEN-1] != '\0') {
memcpy(msgString + (TCL_MAX_WARN_LEN - 5), L" ...", 5 * sizeof(WCHAR));
}
if (IsDebuggerPresent()) {
OutputDebugStringW(msgString);
} else if (_isatty(2)) {
WriteConsoleW(handle, msgString, (DWORD)wcslen(msgString), &dummy, 0);
} else {
buf[0] = '\xEF'; buf[1] = '\xBB'; buf[2] = '\xBF'; /* UTF-8 bom */
WriteFile(handle, buf, (DWORD)strlen(buf), &dummy, 0);
WriteFile(handle, "\n", 1, &dummy, 0);
FlushFileBuffers(handle);
}
# if defined(__GNUC__)
__builtin_trap();
# elif defined(_WIN64)
__debugbreak();
# elif defined(_MSC_VER)
_asm {int 3}
# else
DebugBreak();
# endif
#if defined(_WIN32)
ExitProcess(1);
#else
abort();
#endif
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* tab-width: 8
* End:
*/

3717
vendor/tcl/win/tclWinPipe.c vendored Normal file

File diff suppressed because it is too large Load diff

546
vendor/tcl/win/tclWinPort.h vendored Normal file
View file

@ -0,0 +1,546 @@
/*
* tclWinPort.h --
*
* This header file handles porting issues that occur because of
* differences between Windows and Unix. It should be the only
* file that contains #ifdefs to handle different flavors of OS.
*
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#ifndef _TCLWINPORT
#define _TCLWINPORT
#if !defined(_WIN64) && !defined(__MINGW_USE_VC2005_COMPAT)
/* See [Bug 3354324]: file mtime sets wrong time */
# define __MINGW_USE_VC2005_COMPAT
#endif
#if defined(_MSC_VER) && defined(_WIN64) && !defined(STATIC_BUILD) \
&& !defined(MP_32BIT) && !defined(MP_64BIT)
# define MP_64BIT
#endif
/*
* We must specify the lower version we intend to support.
*
* WINVER = 0x0601 means Windows 7 and above
*/
#ifndef WINVER
# define WINVER 0x0601
#endif
#ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0601
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
/* Compatibility to older visual studio / windows platform SDK */
#if !defined(MAXULONG_PTR)
typedef DWORD DWORD_PTR;
typedef DWORD_PTR * PDWORD_PTR;
#endif
/*
* Ask for the winsock function typedefs, also.
*/
#ifndef INCL_WINSOCK_API_TYPEDEFS
# define INCL_WINSOCK_API_TYPEDEFS 1
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#ifdef HAVE_WSPIAPI_H
# include <wspiapi.h>
#endif
/*
* Pull in the typedef of TCHAR for windows.
*/
#include <tchar.h>
#ifndef _TCHAR_DEFINED
/* Borland seems to forget to set this. */
typedef _TCHAR TCHAR;
# define _TCHAR_DEFINED
#endif
#if defined(_MSC_VER) && defined(__STDC__)
/* VS2005 SP1 misses this. See [Bug #3110161] */
typedef _TCHAR TCHAR;
#endif
/*
*---------------------------------------------------------------------------
* The following sets of #includes and #ifdefs are required to get Tcl to
* compile under the windows compilers.
*---------------------------------------------------------------------------
*/
#include <time.h>
#include <wchar.h>
#include <io.h>
#include <errno.h>
#include <fcntl.h>
#include <float.h>
#include <malloc.h>
#include <process.h>
#include <signal.h>
#ifdef HAVE_INTTYPES_H
# include <inttypes.h>
#endif
#include <limits.h>
#ifndef __GNUC__
# define strncasecmp _strnicmp
# define strcasecmp _stricmp
#endif
/*
* Need to block out these includes for building extensions with MetroWerks
* compiler for Win32.
*/
#ifndef __MWERKS__
#include <sys/stat.h>
#include <sys/timeb.h>
#include <sys/utime.h>
#endif /* __MWERKS__ */
/*
* The following defines redefine the Windows Socket errors as
* BSD errors so Tcl_PosixError can do the right thing.
*/
#ifndef ENOTEMPTY
# define ENOTEMPTY 41 /* Directory not empty */
#endif
#ifndef EREMOTE
# define EREMOTE 66 /* The object is remote */
#endif
#ifndef EPFNOSUPPORT
# define EPFNOSUPPORT 96 /* Protocol family not supported */
#endif
#ifndef EADDRINUSE
# define EADDRINUSE 100 /* Address already in use */
#endif
#ifndef EADDRNOTAVAIL
# define EADDRNOTAVAIL 101 /* Can't assign requested address */
#endif
#ifndef EAFNOSUPPORT
# define EAFNOSUPPORT 102 /* Address family not supported */
#endif
#ifndef EALREADY
# define EALREADY 103 /* Operation already in progress */
#endif
#ifndef EBADMSG
# define EBADMSG 104 /* Not a data message */
#endif
#ifndef ECANCELED
# define ECANCELED 105 /* Canceled */
#endif
#ifndef ECONNABORTED
# define ECONNABORTED 106 /* Software caused connection abort */
#endif
#ifndef ECONNREFUSED
# define ECONNREFUSED 107 /* Connection refused */
#endif
#ifndef ECONNRESET
# define ECONNRESET 108 /* Connection reset by peer */
#endif
#ifndef EDESTADDRREQ
# define EDESTADDRREQ 109 /* Destination address required */
#endif
#ifndef EHOSTUNREACH
# define EHOSTUNREACH 110 /* No route to host */
#endif
#ifndef EIDRM
# define EIDRM 111 /* Identifier removed */
#endif
#ifndef EINPROGRESS
# define EINPROGRESS 112 /* Operation now in progress */
#endif
#ifndef EISCONN
# define EISCONN 113 /* Socket is already connected */
#endif
#ifndef ELOOP
# define ELOOP 114 /* Symbolic link loop */
#endif
#ifndef EMSGSIZE
# define EMSGSIZE 115 /* Message too long */
#endif
#ifndef ENETDOWN
# define ENETDOWN 116 /* Network is down */
#endif
#ifndef ENETRESET
# define ENETRESET 117 /* Network dropped connection on reset */
#endif
#ifndef ENETUNREACH
# define ENETUNREACH 118 /* Network is unreachable */
#endif
#ifndef ENOBUFS
# define ENOBUFS 119 /* No buffer space available */
#endif
#ifndef ENODATA
# define ENODATA 120 /* No data available */
#endif
#ifndef ENOLINK
# define ENOLINK 121 /* Link has be severed */
#endif
#ifndef ENOMSG
# define ENOMSG 122 /* No message of desired type */
#endif
#ifndef ENOPROTOOPT
# define ENOPROTOOPT 123 /* Protocol not available */
#endif
#ifndef ENOSR
# define ENOSR 124 /* Out of stream resources */
#endif
#ifndef ENOSTR
# define ENOSTR 125 /* Not a stream device */
#endif
#ifndef ENOTCONN
# define ENOTCONN 126 /* Socket is not connected */
#endif
#ifndef ENOTRECOVERABLE
# define ENOTRECOVERABLE 127 /* Not recoverable */
#endif
#ifndef ENOTSOCK
# define ENOTSOCK 128 /* Socket operation on non-socket */
#endif
#ifndef ENOTSUP
# define ENOTSUP 129 /* Operation not supported */
#endif
#ifndef EOPNOTSUPP
# define EOPNOTSUPP 130 /* Operation not supported on socket */
#endif
#ifndef EOTHER
# define EOTHER 131 /* Other error */
#endif
#ifndef EOVERFLOW
# define EOVERFLOW 132 /* File too big */
#endif
#ifndef EOWNERDEAD
# define EOWNERDEAD 133 /* Owner dead */
#endif
#ifndef EPROTO
# define EPROTO 134 /* Protocol error */
#endif
#ifndef EPROTONOSUPPORT
# define EPROTONOSUPPORT 135 /* Protocol not supported */
#endif
#ifndef EPROTOTYPE
# define EPROTOTYPE 136 /* Protocol wrong type for socket */
#endif
#ifndef ETIME
# define ETIME 137 /* Timer expired */
#endif
#ifndef ETIMEDOUT
# define ETIMEDOUT 138 /* Connection timed out */
#endif
#ifndef ETXTBSY
# define ETXTBSY 139 /* Text file or pseudo-device busy */
#endif
#ifndef EWOULDBLOCK
# define EWOULDBLOCK 140 /* Operation would block */
#endif
/* Visual Studio doesn't have these, so just choose some high numbers */
#ifndef ESOCKTNOSUPPORT
# define ESOCKTNOSUPPORT 240 /* Socket type not supported */
#endif
#ifndef ESHUTDOWN
# define ESHUTDOWN 241 /* Can't send after socket shutdown */
#endif
#ifndef ETOOMANYREFS
# define ETOOMANYREFS 242 /* Too many references: can't splice */
#endif
#ifndef EHOSTDOWN
# define EHOSTDOWN 243 /* Host is down */
#endif
#ifndef EUSERS
# define EUSERS 244 /* Too many users (for UFS) */
#endif
#ifndef EDQUOT
# define EDQUOT 245 /* Disc quota exceeded */
#endif
#ifndef ESTALE
# define ESTALE 246 /* Stale NFS file handle */
#endif
/*
* Signals not known to the standard ANSI signal.h. These are used
* by Tcl_WaitPid() and generic/tclPosixStr.c
*/
#ifndef SIGTRAP
# define SIGTRAP 5
#endif
#ifndef SIGBUS
# define SIGBUS 10
#endif
/*
* Supply definitions for macros to query wait status, if not already
* defined in header files above.
*/
#ifdef TCL_UNION_WAIT
# define WAIT_STATUS_TYPE union wait
#else
# define WAIT_STATUS_TYPE int
#endif /* TCL_UNION_WAIT */
#ifndef WIFEXITED
# define WIFEXITED(stat) (((*((int *) &(stat))) & 0xC0000000) == 0)
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat) (*((int *) &(stat)))
#endif
#ifndef WIFSIGNALED
# define WIFSIGNALED(stat) ((*((int *) &(stat))) & 0xC0000000)
#endif
#ifndef WTERMSIG
# define WTERMSIG(stat) ((*((int *) &(stat))) & 0x7F)
#endif
#ifndef WIFSTOPPED
# define WIFSTOPPED(stat) 0
#endif
#ifndef WSTOPSIG
# define WSTOPSIG(stat) (((*((int *) &(stat))) >> 8) & 0xFF)
#endif
/*
* Define constants for waitpid() system call if they aren't defined
* by a system header file.
*/
#ifndef WNOHANG
# define WNOHANG 1
#endif
#ifndef WUNTRACED
# define WUNTRACED 2
#endif
/*
* Define access mode constants if they aren't already defined.
*/
#ifndef F_OK
# define F_OK 00
#endif
#ifndef X_OK
# define X_OK 01
#endif
#ifndef W_OK
# define W_OK 02
#endif
#ifndef R_OK
# define R_OK 04
#endif
#ifndef O_ACCMODE
# define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
#endif
/*
* Define macros to query file type bits, if they're not already
* defined.
*/
#ifndef S_IFLNK
# define S_IFLNK 0120000 /* Symbolic Link */
#endif
/*
* Windows compilers do not define S_IFBLK. However, Tcl uses it in
* GetTypeFromMode to identify blockSpecial devices based on the
* value in the statsbuf st_mode field. We have no other way to pass this
* from NativeStat on Windows so are forced to define it here.
* The definition here is essentially what is seen on Linux and MingW.
* XXX - the root problem is Tcl using Unix definitions instead of
* abstracting the structure into a platform independent one. Sigh - perhaps
* Tcl 9
*/
#ifndef S_IFBLK
# define S_IFBLK (S_IFDIR | S_IFCHR)
#endif
#ifndef S_ISREG
# ifdef S_IFREG
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
# else
# define S_ISREG(m) 0
# endif
#endif /* !S_ISREG */
#ifndef S_ISDIR
# ifdef S_IFDIR
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
# else
# define S_ISDIR(m) 0
# endif
#endif /* !S_ISDIR */
#ifndef S_ISCHR
# ifdef S_IFCHR
# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
# else
# define S_ISCHR(m) 0
# endif
#endif /* !S_ISCHR */
#ifndef S_ISBLK
# ifdef S_IFBLK
# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
# else
# define S_ISBLK(m) 0
# endif
#endif /* !S_ISBLK */
#ifndef S_ISFIFO
# ifdef S_IFIFO
# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
# else
# define S_ISFIFO(m) 0
# endif
#endif /* !S_ISFIFO */
#ifndef S_ISLNK
# ifdef S_IFLNK
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
# else
# define S_ISLNK(m) 0
# endif
#endif /* !S_ISLNK */
/*
* Define MAXPATHLEN in terms of MAXPATH if available
*/
#ifndef MAXPATH
# define MAXPATH MAX_PATH
#endif /* MAXPATH */
#ifndef MAXPATHLEN
# define MAXPATHLEN MAXPATH
#endif /* MAXPATHLEN */
/*
* Define pid_t and uid_t if they're not already defined.
*/
#if !defined(TCL_PID_T)
# define pid_t int
#endif /* !TCL_PID_T */
#if !defined(TCL_UID_T)
# define uid_t int
#endif /* !TCL_UID_T */
/*
* Visual C++ has some odd names for common functions, so we need to
* define a few macros to handle them. Also, it defines EDEADLOCK and
* EDEADLK as the same value, which confuses Tcl_ErrnoId().
*/
#if defined(_MSC_VER) || defined(__MSVCRT__)
# define environ _environ
# define exception _exception
# undef EDEADLOCK
# if defined(_MSC_VER)
# define timezone _timezone
# endif
#endif /* _MSC_VER || __MSVCRT__ */
#if defined(_MSC_VER)
# pragma warning(disable:4090) /* see: https://developercommunity.visualstudio.com/t/c-compiler-incorrect-propagation-of-const-qualifie/390711 */
# pragma warning(disable:4146)
# pragma warning(disable:4244)
#if !defined(_WIN64)
# pragma warning(disable:4305)
#endif
# pragma warning(disable:4267)
# pragma warning(disable:4996)
# pragma warning(disable:5287) /* See [1dcda0e862] */
#endif
/*
*---------------------------------------------------------------------------
* The following macros and declarations represent the interface between
* generic and windows-specific parts of Tcl. Some of the macros may
* override functions declared in tclInt.h.
*---------------------------------------------------------------------------
*/
/*
* The default platform eol translation on Windows is TCL_TRANSLATE_CRLF:
*/
#define TCL_PLATFORM_TRANSLATION TCL_TRANSLATE_CRLF
/*
* Declare dynamic loading extension macro.
*/
#define TCL_SHLIB_EXT ".dll"
/*
* The following define ensures that we use the native putenv
* implementation to modify the environment array. This keeps
* the C level environment in synch with the system level environment.
*/
#define USE_PUTENV 1
#define USE_PUTENV_FOR_UNSET 1
/*
* Msvcrt's putenv() copies the string rather than takes ownership of it.
*/
#if defined(_MSC_VER) || defined(__MSVCRT__)
# define HAVE_PUTENV_THAT_COPIES 1
#endif
/*
* Older version of Mingw are known to lack a MWMO_ALERTABLE define.
*/
#if !defined(MWMO_ALERTABLE)
# define MWMO_ALERTABLE 2
#endif
/*
* The following defines wrap the system memory allocation routines for
* use by tclAlloc.c.
*/
#define TclpSysAlloc(size) ((void*)HeapAlloc(GetProcessHeap(), \
0, size))
#define TclpSysFree(ptr) (HeapFree(GetProcessHeap(), \
0, (HGLOBAL)ptr))
#define TclpSysRealloc(ptr, size) ((void*)HeapReAlloc(GetProcessHeap(), \
0, (LPVOID)ptr, size))
/* This type is not defined in the Windows headers */
#define socklen_t int
/*
* The following macros have trivial definitions, allowing generic code to
* address platform-specific issues.
*/
#define TclpReleaseFile(file) Tcl_Free(file)
/*
* The following macros and declarations wrap the C runtime library
* functions.
*/
#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER 0xFFFFFFFF
#endif /* INVALID_SET_FILE_POINTER */
#ifndef LABEL_SECURITY_INFORMATION
# define LABEL_SECURITY_INFORMATION (0x00000010L)
#endif
#endif /* _TCLWINPORT */

1598
vendor/tcl/win/tclWinReg.c vendored Normal file

File diff suppressed because it is too large Load diff

2317
vendor/tcl/win/tclWinSerial.c vendored Normal file

File diff suppressed because it is too large Load diff

3350
vendor/tcl/win/tclWinSock.c vendored Normal file

File diff suppressed because it is too large Load diff

678
vendor/tcl/win/tclWinTest.c vendored Normal file
View file

@ -0,0 +1,678 @@
/*
* tclWinTest.c --
*
* Contains commands for platform specific tests on Windows.
*
* Copyright © 1996 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#undef BUILD_tcl
#undef STATIC_BUILD
#ifndef USE_TCL_STUBS
# define USE_TCL_STUBS
#endif
#include "tclInt.h"
#ifdef TCL_WITH_EXTERNAL_TOMMATH
# include "tommath.h"
#else
# include "tclTomMath.h"
#endif
/*
* For TestplatformChmod on Windows
*/
#include <aclapi.h>
#include <sddl.h>
/*
* MinGW 3.4.2 does not define this.
*/
#ifndef INHERITED_ACE
#define INHERITED_ACE (0x10)
#endif
/*
* Forward declarations of functions defined later in this file:
*/
static Tcl_ObjCmdProc TesteventloopCmd;
static Tcl_ObjCmdProc TestvolumetypeCmd;
static Tcl_ObjCmdProc TestwinclockCmd;
static Tcl_ObjCmdProc TestwinsleepCmd;
static Tcl_ObjCmdProc TestExceptionCmd;
static int TestplatformChmod(const char *nativePath, int pmode);
static Tcl_ObjCmdProc TestchmodCmd;
/*
*----------------------------------------------------------------------
*
* TclplatformtestInit --
*
* Defines commands that test platform specific functionality for Windows
* platforms.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* Defines new commands.
*
*----------------------------------------------------------------------
*/
int
TclplatformtestInit(
Tcl_Interp *interp) /* Interpreter to add commands to. */
{
/*
* Add commands for platform specific tests for Windows here.
*/
Tcl_CreateObjCommand(interp, "testchmod", TestchmodCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testeventloop", TesteventloopCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testvolumetype", TestvolumetypeCmd,
NULL, NULL);
Tcl_CreateObjCommand(interp, "testwinclock", TestwinclockCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testwinsleep", TestwinsleepCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testexcept", TestExceptionCmd, NULL, NULL);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TesteventloopCmd --
*
* This function implements the "testeventloop" command. It is used to
* test the Tcl notifier from an "external" event loop (i.e. not
* Tcl_DoOneEvent()).
*
* Results:
* A standard Tcl result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TesteventloopCmd(
TCL_UNUSED(void *),
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
static int *framePtr = NULL;/* Pointer to integer on stack frame of
* innermost invocation of the "wait"
* subcommand. */
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "done|wait");
return TCL_ERROR;
}
if (strcmp(Tcl_GetString(objv[1]), "done") == 0) {
*framePtr = 1;
} else if (strcmp(Tcl_GetString(objv[1]), "wait") == 0) {
int *oldFramePtr, done;
int oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
/*
* Save the old stack frame pointer and set up the current frame.
*/
oldFramePtr = framePtr;
framePtr = &done;
/*
* Enter a standard Windows event loop until the flag changes. Note
* that we do not explicitly call Tcl_ServiceEvent().
*/
done = 0;
while (!done) {
MSG msg;
if (!GetMessageW(&msg, NULL, 0, 0)) {
/*
* The application is exiting, so repost the quit message and
* start unwinding.
*/
PostQuitMessage((int) msg.wParam);
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
(void) Tcl_SetServiceMode(oldMode);
framePtr = oldFramePtr;
} else {
Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]),
"\": must be done or wait", (char *)NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Testvolumetype --
*
* This function implements the "testvolumetype" command. It is used to
* check the volume type (FAT, NTFS) of a volume.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TestvolumetypeCmd(
TCL_UNUSED(void *),
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
#define VOL_BUF_SIZE 32
int found;
char volType[VOL_BUF_SIZE];
const char *path;
if (objc > 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?name?");
return TCL_ERROR;
}
if (objc == 2) {
/*
* path has to be really a proper volume, but we don't get query APIs
* for that until NT5
*/
path = Tcl_GetString(objv[1]);
} else {
path = NULL;
}
found = GetVolumeInformationA(path, NULL, 0, NULL, NULL, NULL, volType,
VOL_BUF_SIZE);
if (found == 0) {
Tcl_AppendResult(interp, "could not get volume type for \"",
(path?path:""), "\"", (char *)NULL);
Tcl_WinConvertError(GetLastError());
return TCL_ERROR;
}
Tcl_AppendResult(interp, volType, (char *)NULL);
return TCL_OK;
#undef VOL_BUF_SIZE
}
/*
*----------------------------------------------------------------------
*
* TestwinclockCmd --
*
* Command that returns the seconds and microseconds portions of the
* system clock and of the Tcl clock so that they can be compared to
* validate that the Tcl clock is staying in sync.
*
* Usage:
* testclock
*
* Parameters:
* None.
*
* Results:
* Returns a standard Tcl result comprising a four-element list: the
* seconds and microseconds portions of the system clock, and the seconds
* and microseconds portions of the Tcl clock.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TestwinclockCmd(
TCL_UNUSED(void *),
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Argument count */
Tcl_Obj *const objv[]) /* Argument vector */
{
static const FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE };
/* The Posix epoch, expressed as a Windows
* FILETIME */
Tcl_Time tclTime; /* Tcl clock */
FILETIME sysTime; /* System clock */
Tcl_Obj *result; /* Result of the command */
LARGE_INTEGER t1, t2;
LARGE_INTEGER p1, p2;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
QueryPerformanceCounter(&p1);
Tcl_GetTime(&tclTime);
GetSystemTimeAsFileTime(&sysTime);
t1.LowPart = posixEpoch.dwLowDateTime;
t1.HighPart = posixEpoch.dwHighDateTime;
t2.LowPart = sysTime.dwLowDateTime;
t2.HighPart = sysTime.dwHighDateTime;
t2.QuadPart -= t1.QuadPart;
QueryPerformanceCounter(&p2);
result = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, result,
Tcl_NewWideIntObj(t2.QuadPart / 10000000));
Tcl_ListObjAppendElement(interp, result,
Tcl_NewWideIntObj((t2.QuadPart / 10) % 1000000));
Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(tclTime.sec));
Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(tclTime.usec));
Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(p1.QuadPart));
Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(p2.QuadPart));
Tcl_SetObjResult(interp, result);
return TCL_OK;
}
static int
TestwinsleepCmd(
TCL_UNUSED(void *),
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
Tcl_Obj *const * objv) /* Parameter vector */
{
int ms;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "ms");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[1], &ms) != TCL_OK) {
return TCL_ERROR;
}
Sleep((DWORD) ms);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TestExceptionCmd --
*
* Causes this process to end with the named exception. Used for testing
* Tcl_WaitPid().
*
* Usage:
* testexcept <type>
*
* Parameters:
* Type of exception.
*
* Results:
* None, this process closes now and doesn't return.
*
* Side effects:
* This Tcl process closes, hard... Bang!
*
*----------------------------------------------------------------------
*/
static int
TestExceptionCmd(
TCL_UNUSED(void *),
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Argument count */
Tcl_Obj *const objv[]) /* Argument vector */
{
static const char *const cmds[] = {
"access_violation", "datatype_misalignment", "array_bounds",
"float_denormal", "float_divbyzero", "float_inexact",
"float_invalidop", "float_overflow", "float_stack", "float_underflow",
"int_divbyzero", "int_overflow", "private_instruction", "inpageerror",
"illegal_instruction", "noncontinue", "stack_overflow",
"invalid_disp", "guard_page", "invalid_handle", "ctrl+c",
NULL
};
static const DWORD exceptions[] = {
EXCEPTION_ACCESS_VIOLATION, EXCEPTION_DATATYPE_MISALIGNMENT,
EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_FLT_DENORMAL_OPERAND,
EXCEPTION_FLT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT,
EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW,
EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW,
EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW,
EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_IN_PAGE_ERROR,
EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_NONCONTINUABLE_EXCEPTION,
EXCEPTION_STACK_OVERFLOW, EXCEPTION_INVALID_DISPOSITION,
EXCEPTION_GUARD_PAGE, EXCEPTION_INVALID_HANDLE, CONTROL_C_EXIT
};
int cmd;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 0, objv, "<type-of-exception>");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObj(interp, objv[1], cmds, "command", 0,
&cmd) != TCL_OK) {
return TCL_ERROR;
}
/*
* Make sure the GPF dialog doesn't popup.
*/
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
/*
* As Tcl does not handle structured exceptions, this falls all the way
* back up the instruction stack to the C run-time portion that called
* main() where the process will now be terminated with this exception
* code by the default handler the C run-time provides.
*/
/* SMASH! */
RaiseException(exceptions[cmd], EXCEPTION_NONCONTINUABLE, 0, NULL);
return TCL_OK;
}
/*
* This "chmod" works sufficiently for test script purposes. Do not expect
* it to be exact emulation of Unix chmod (not sure if that's even possible)
*/
static int
TestplatformChmod(
const char *nativePath,
int pmode)
{
/*
* Note FILE_DELETE_CHILD missing from dirWriteMask because we do
* not want overriding of child's delete setting when testing
*/
static const DWORD dirWriteMask =
FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA |
FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | STANDARD_RIGHTS_WRITE | DELETE |
SYNCHRONIZE;
static const DWORD dirReadMask =
FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_LIST_DIRECTORY |
STANDARD_RIGHTS_READ | SYNCHRONIZE;
/* Note - default user privileges allow ignoring TRAVERSE setting */
static const DWORD dirExecuteMask =
FILE_TRAVERSE | STANDARD_RIGHTS_READ | SYNCHRONIZE;
static const DWORD fileWriteMask =
FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_WRITE_DATA |
FILE_APPEND_DATA | STANDARD_RIGHTS_WRITE | DELETE | SYNCHRONIZE;
static const DWORD fileReadMask =
FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA |
STANDARD_RIGHTS_READ | SYNCHRONIZE;
static const DWORD fileExecuteMask =
FILE_EXECUTE | STANDARD_RIGHTS_READ | SYNCHRONIZE;
DWORD attr, newAclSize;
PACL newAcl = NULL;
int res = 0;
HANDLE hToken = NULL;
int i;
int nSids = 0;
struct {
PSID pSid;
DWORD mask;
DWORD sidLen;
} aceEntry[3];
DWORD dw;
int isDir;
TOKEN_USER *pTokenUser = NULL;
Tcl_DString ds;
res = -1; /* Assume failure */
Tcl_DStringInit(&ds);
Tcl_UtfToChar16DString(nativePath, -1, &ds);
attr = GetFileAttributesW((WCHAR *)Tcl_DStringValue(&ds));
if (attr == 0xFFFFFFFF) {
goto done; /* Not found */
}
isDir = (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
goto done;
}
/* Get process SID */
if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dw)
&& GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
goto done;
}
pTokenUser = (TOKEN_USER *)Tcl_Alloc(dw);
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dw, &dw)) {
goto done;
}
aceEntry[nSids].sidLen = GetLengthSid(pTokenUser->User.Sid);
aceEntry[nSids].pSid = (PSID)Tcl_Alloc(aceEntry[nSids].sidLen);
if (!CopySid(aceEntry[nSids].sidLen, aceEntry[nSids].pSid,
pTokenUser->User.Sid)) {
Tcl_Free(aceEntry[nSids].pSid); /* Since we have not ++'ed nSids */
goto done;
}
/*
* Always include DACL modify rights so we don't get locked out
*/
aceEntry[nSids].mask = READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE |
FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES;
if (pmode & 0700) {
/* Owner permissions. Assumes current process is owner */
if (pmode & 0400) {
aceEntry[nSids].mask |= isDir ? dirReadMask : fileReadMask;
}
if (pmode & 0200) {
aceEntry[nSids].mask |= isDir ? dirWriteMask : fileWriteMask;
}
if (pmode & 0100) {
aceEntry[nSids].mask |= isDir ? dirExecuteMask : fileExecuteMask;
}
}
++nSids;
if (pmode & 0070) {
/* Group permissions. */
TOKEN_PRIMARY_GROUP *pTokenGroup;
/* Get primary group SID */
if (!GetTokenInformation(
hToken, TokenPrimaryGroup, NULL, 0, &dw) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
goto done;
}
pTokenGroup = (TOKEN_PRIMARY_GROUP *)Tcl_Alloc(dw);
if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dw, &dw)) {
Tcl_Free(pTokenGroup);
goto done;
}
aceEntry[nSids].sidLen = GetLengthSid(pTokenGroup->PrimaryGroup);
aceEntry[nSids].pSid = (PSID)Tcl_Alloc(aceEntry[nSids].sidLen);
if (!CopySid(aceEntry[nSids].sidLen, aceEntry[nSids].pSid, pTokenGroup->PrimaryGroup)) {
Tcl_Free(pTokenGroup);
Tcl_Free(aceEntry[nSids].pSid); /* Since we have not ++'ed nSids */
goto done;
}
Tcl_Free(pTokenGroup);
/* Generate mask for group ACL */
aceEntry[nSids].mask = 0;
if (pmode & 0040) {
aceEntry[nSids].mask |= isDir ? dirReadMask : fileReadMask;
}
if (pmode & 0020) {
aceEntry[nSids].mask |= isDir ? dirWriteMask : fileWriteMask;
}
if (pmode & 0010) {
aceEntry[nSids].mask |= isDir ? dirExecuteMask : fileExecuteMask;
}
++nSids;
}
if (pmode & 0007) {
/* World permissions */
PSID pWorldSid;
if (!ConvertStringSidToSidA("S-1-1-0", &pWorldSid)) {
goto done;
}
aceEntry[nSids].sidLen = GetLengthSid(pWorldSid);
aceEntry[nSids].pSid = (PSID)Tcl_Alloc(aceEntry[nSids].sidLen);
if (!CopySid(aceEntry[nSids].sidLen, aceEntry[nSids].pSid, pWorldSid)) {
LocalFree(pWorldSid);
Tcl_Free(aceEntry[nSids].pSid); /* Since we have not ++'ed nSids */
goto done;
}
LocalFree(pWorldSid);
/* Generate mask for world ACL */
aceEntry[nSids].mask = 0;
if (pmode & 0004) {
aceEntry[nSids].mask |= isDir ? dirReadMask : fileReadMask;
}
if (pmode & 0002) {
aceEntry[nSids].mask |= isDir ? dirWriteMask : fileWriteMask;
}
if (pmode & 0001) {
aceEntry[nSids].mask |= isDir ? dirExecuteMask : fileExecuteMask;
}
++nSids;
}
/* Allocate memory and initialize the new ACL. */
newAclSize = sizeof(ACL);
/* Add in size required for each ACE entry in the ACL */
for (i = 0; i < nSids; ++i) {
newAclSize +=
offsetof(ACCESS_ALLOWED_ACE, SidStart) + aceEntry[i].sidLen;
}
newAcl = (PACL)Tcl_Alloc(newAclSize);
if (!InitializeAcl(newAcl, newAclSize, ACL_REVISION)) {
goto done;
}
for (i = 0; i < nSids; ++i) {
if (!AddAccessAllowedAce(newAcl, ACL_REVISION, aceEntry[i].mask, aceEntry[i].pSid)) {
goto done;
}
}
/*
* Apply the new ACL. Note PROTECTED_DACL_SECURITY_INFORMATION can be used
* to remove inherited ACL (we need to overwrite the default ACL's in this case)
*/
if (SetNamedSecurityInfoW((LPWSTR)Tcl_DStringValue(&ds), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
NULL, NULL, newAcl, NULL) == ERROR_SUCCESS) {
res = 0;
}
done:
if (pTokenUser) {
Tcl_Free(pTokenUser);
}
if (hToken) {
CloseHandle(hToken);
}
if (newAcl) {
Tcl_Free(newAcl);
}
for (i = 0; i < nSids; ++i) {
Tcl_Free(aceEntry[i].pSid);
}
if (res == 0) {
/* Run normal chmod command */
res = _wchmod((WCHAR*)Tcl_DStringValue(&ds), pmode);
}
Tcl_DStringFree(&ds);
return res;
}
/*
*---------------------------------------------------------------------------
*
* TestchmodCmd --
*
* Implements the "testchmod" cmd. Used when testing "file" command. The
* only attribute used by the Windows platform is the user write flag; if
* this is not set, the file is made read-only. Otherwise, the file is
* made read-write.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* Changes permissions of specified files.
*
*---------------------------------------------------------------------------
*/
static int
TestchmodCmd(
TCL_UNUSED(void *),
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Parameter count */
Tcl_Obj *const * objv) /* Parameter vector */
{
int i, mode;
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "mode file ?file ...?");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[1], &mode) != TCL_OK) {
return TCL_ERROR;
}
for (i = 2; i < objc; i++) {
Tcl_DString buffer;
const char *translated;
translated = Tcl_TranslateFileName(interp, Tcl_GetString(objv[i]), &buffer);
if (translated == NULL) {
return TCL_ERROR;
}
if (TestplatformChmod(translated, mode) != 0) {
Tcl_AppendResult(interp, translated, ": ", Tcl_PosixError(interp),
(char *)NULL);
return TCL_ERROR;
}
Tcl_DStringFree(&buffer);
}
return TCL_OK;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/

1156
vendor/tcl/win/tclWinThrd.c vendored Normal file

File diff suppressed because it is too large Load diff

1246
vendor/tcl/win/tclWinTime.c vendored Normal file

File diff suppressed because it is too large Load diff

19
vendor/tcl/win/tclooConfig.sh vendored Normal file
View file

@ -0,0 +1,19 @@
# tclooConfig.sh --
#
# This shell script (for sh) is generated automatically by TclOO's configure
# script, or would be except it has no values that we substitute. It will
# create shell variables for most of the configuration options discovered by
# the configure script. This script is intended to be included by TEA-based
# configure scripts for TclOO extensions so that they don't have to figure
# this all out for themselves.
#
# The information in this file is specific to a single platform.
# These are mostly empty because no special steps are ever needed from Tcl 8.6
# onwards; all libraries and include files are just part of Tcl.
TCLOO_LIB_SPEC=""
TCLOO_STUB_LIB_SPEC=""
TCLOO_INCLUDE_SPEC=""
TCLOO_PRIVATE_INCLUDE_SPEC=""
TCLOO_CFLAGS=""
TCLOO_VERSION=1.3

Some files were not shown because too many files have changed in this diff Show more