All ports should be at parity now.
This commit is contained in:
parent
7a5744f619
commit
90634e4244
106 changed files with 67412 additions and 1228 deletions
59
API.md
59
API.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
64
Makefile
64
Makefile
|
|
@ -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
|
||||
|
|
|
|||
80
PORTING.md
80
PORTING.md
|
|
@ -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
169
design.md
|
|
@ -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
316
examples/httpd.lua
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
278
libs/calogHttp.c
278
libs/calogHttp.c
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
258
libs/calogNet.c
258
libs/calogNet.c
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
378
libs/calogProc.c
378
libs/calogProc.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
38
tests/fuzzParser.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
560
tests/testHttpdLua.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
814
tools/crossDeps.sh
Executable 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
211
tools/crossMacFull.sh
Executable 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
117
tools/crossWinFull.sh
Normal 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"
|
||||
15
tools/macStubs/CoreFoundation.tbd
Normal file
15
tools/macStubs/CoreFoundation.tbd
Normal 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 ]
|
||||
...
|
||||
14
tools/macStubs/Security.tbd
Normal file
14
tools/macStubs/Security.tbd
Normal 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 ]
|
||||
...
|
||||
15
tools/macStubs/libiconv.tbd
Normal file
15
tools/macStubs/libiconv.tbd
Normal 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 ]
|
||||
...
|
||||
22
vendor/ourbasic/ourBasic.c
vendored
22
vendor/ourbasic/ourBasic.c
vendored
|
|
@ -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:
|
||||
|
|
|
|||
24
vendor/ourbasic/ourBasic.h
vendored
24
vendor/ourbasic/ourBasic.h
vendored
|
|
@ -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
211
vendor/tcl/macosx/GNUmakefile
vendored
Normal 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
133
vendor/tcl/macosx/README
vendored
Normal 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
36
vendor/tcl/macosx/Tcl-Info.plist.in
vendored
Normal 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 & 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
36
vendor/tcl/macosx/Tclsh-Info.plist.in
vendored
Normal 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 & 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
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
11
vendor/tcl/macosx/configure.ac
vendored
Normal 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
40
vendor/tcl/macosx/license.terms
vendored
Normal 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
214
vendor/tcl/macosx/tclMacOSXBundle.c
vendored
Normal 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
720
vendor/tcl/macosx/tclMacOSXFCmd.c
vendored
Normal 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
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
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
102
vendor/tcl/win/README
vendored
Normal 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
1
vendor/tcl/win/aclocal.m4
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
builtin(include,tcl.m4)
|
||||
105
vendor/tcl/win/buildall.vc.bat
vendored
Executable file
105
vendor/tcl/win/buildall.vc.bat
vendored
Executable 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
35
vendor/tcl/win/cat.c
vendored
Normal 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
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
495
vendor/tcl/win/configure.ac
vendored
Normal 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
1
vendor/tcl/win/gitmanifest.in
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
git-
|
||||
40
vendor/tcl/win/license.terms
vendored
Normal file
40
vendor/tcl/win/license.terms
vendored
Normal 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
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
820
vendor/tcl/win/nmakehlp.c
vendored
Normal 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
123
vendor/tcl/win/rules-ext.vc
vendored
Normal 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
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
1
vendor/tcl/win/svnmanifest.in
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
svn-r
|
||||
99
vendor/tcl/win/targets.vc
vendored
Normal file
99
vendor/tcl/win/targets.vc
vendored
Normal 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
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
29
vendor/tcl/win/tcl.dsw
vendored
Normal 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
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
51
vendor/tcl/win/tcl.rc
vendored
Normal 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
208
vendor/tcl/win/tclAppInit.c
vendored
Normal 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
176
vendor/tcl/win/tclConfig.sh.in
vendored
Normal 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
1
vendor/tcl/win/tclUuid.h.in
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
#define TCL_VERSION_UUID \
|
||||
532
vendor/tcl/win/tclWin32Dll.c
vendored
Normal file
532
vendor/tcl/win/tclWin32Dll.c
vendored
Normal 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, ®s[0], ®s[1], ®s[2], ®s[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
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
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
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
425
vendor/tcl/win/tclWinError.c
vendored
Normal 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
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
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
786
vendor/tcl/win/tclWinInit.c
vendored
Normal 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
130
vendor/tcl/win/tclWinInt.h
vendored
Normal 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
436
vendor/tcl/win/tclWinLoad.c
vendored
Normal 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
638
vendor/tcl/win/tclWinNotify.c
vendored
Normal 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(¬ifierMutex);
|
||||
}
|
||||
TclpGlobalUnlock();
|
||||
|
||||
/*
|
||||
* Register Notifier window class if this is the first thread to use this
|
||||
* module.
|
||||
*/
|
||||
|
||||
EnterCriticalSection(¬ifierMutex);
|
||||
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(¬ifierMutex);
|
||||
|
||||
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(¬ifierMutex);
|
||||
if (notifierCount) {
|
||||
notifierCount--;
|
||||
if (notifierCount == 0) {
|
||||
UnregisterClassW(className, (HINSTANCE) TclWinGetTclInstance());
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(¬ifierMutex);
|
||||
}
|
||||
|
||||
/*
|
||||
*----------------------------------------------------------------------
|
||||
*
|
||||
* 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
88
vendor/tcl/win/tclWinPanic.c
vendored
Normal 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
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
546
vendor/tcl/win/tclWinPort.h
vendored
Normal 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
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
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
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
678
vendor/tcl/win/tclWinTest.c
vendored
Normal 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
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
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
19
vendor/tcl/win/tclooConfig.sh
vendored
Normal 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
Loading…
Add table
Reference in a new issue